本地数组在循环内重复! C++

Posted

技术标签:

【中文标题】本地数组在循环内重复! C++【英文标题】:A local array repeats inside a loop! C++ 【发布时间】:2015-11-11 23:00:55 【问题描述】:

current_name 是以下循环内的本地 char 数组。我在循环中声明了它,所以每次我从文件中读取新行时它都会改变。但是,由于某种原因,之前的数据没有从current_name 中删除!如果旧数据没有被下一行的新字符覆盖,它会打印出旧数据。

有什么想法吗?

  while (isOpen && !file.eof()) 
    char current_line[LINE];
    char current_name[NAME];

    file.getline(current_line, LINE);

    int i = 0;
    while (current_line[i] != ';') 
      current_name[i] = current_line[i];
      i++;
    

    cout << current_name << endl;
  

【问题讨论】:

没有异常。标准中没有规定块局部变量必须在每次迭代时改变值。只是您不能假设它将保持其价值。 局部变量不会自动初始化,因此在分配之前访问它会导致未定义的行为。 why while(!file.eof()) is always wrong 删除之前的数据有什么关系?您正在用当前行中的数据覆盖它。问题是您没有添加空终止符。 每次迭代就像一个新变量。所以正如@Barmar 所说,如果您在写入之前阅读它(在新的迭代中),它的数据是 undefined. 【参考方案1】:

您不会在填写 current_name 后终止它。在cout 之前的内部循环之后添加current_name[i] = 0。如果您阅读 abcdef 然后阅读 jkl 并可能得到 jkldef 输出,您可能会看到这一点


更新

您想知道是否有更好的方法。有——我们会解决的。但是,来自 Java,您的问题和后续行动发现了一些我认为您应该注意的更大问题。小心你想要的——你实际上可能会得到它 [and more] :-)。以下都是基于爱...


所有 Java 程序员注意!欢迎来到“美丽新世界”!


基本概念

在我们了解C 语言之前,我们需要先谈谈一些概念。

计算机架构:https://en.wikipedia.org/wiki/Computer_architecturehttps://en.wikipedia.org/wiki/Instruction_set

计算机程序的内存布局:http://www.geeksforgeeks.org/memory-layout-of-c-program/

内存地址/指针与 Java 引用的区别:Is Java "pass-by-reference" or "pass-by-value"?https://softwareengineering.stackexchange.com/questions/141834/how-is-a-java-reference-different-from-a-c-pointer


Java 程序员的陌生概念

C 语言让您可以直接访问底层计算机架构。它不会做任何你没有明确指定的事情。在这里,我提到了C [为简洁起见],但我真正谈论的是内存布局和计算机架构的组合

如果您读取未初始化的内存,您将看到看似随机的数据。 如果从堆中分配某些内容,则必须显式释放它。当它“超出范围”时,它不会神奇地被垃圾收集器标记为删除。 C 中没有垃圾收集器 C 指针比 Java 引用强大得多。您可以为指针添加和减去值。您可以减去两个指针并将差值用作索引值。您可以在不使用 的情况下使用索引变量遍历数组——您只需引用一个指针并递增指针。 Java 中自动变量的数据存储在堆中。每个变量都需要单独的堆分配。这是缓慢且耗时的。 在C 中,自动变量的数据存储在堆栈帧 中。堆栈帧是 字节 的连续区域。为了为堆栈帧分配空间,C 简单地从堆栈指针 [硬件寄存器] 中减去所需的大小。堆栈帧的大小是给定函数范围内的所有变量的总和,无论它们是否在函数内部的循环中声明。 它的初始值取决于之前使用该区域的函数以及它存储在那里的 byte 值。因此,如果main 调用函数fnca,它将用任何数据填充堆栈。如果然后main 调用fncb,它将看到 fnca 的值,就 fncb 而言,这些值是半随机的。 fnca 和 fncb 都必须在使用堆栈变量之前对其进行初始化。 C 变量声明没有 initializer 子句not 初始化变量。对于 bss 区域,它将为零。对于堆栈变量,您必须明确执行此操作。 没有对 C 中的数组索引 [或指向数组或数组元素的指针] 进行范围检查。如果您写入超出定义的区域,您将写入接下来已映射/链接到内存区域的任何内容。例如,如果您有一个内存区域:int x[10]; int y;,而您 [不经意间] 写入 x[10] [最后一个] 您将损坏 y 无论您的数组位于哪个内存部分(例如数据、bss、堆或堆栈),这都是正确的。 C 有 nostring 概念。当人们谈论“c 字符串”时,他们真正在谈论的是一个 char 数组,它有一个“字符串结尾”(又名 EOS)sentinel 字符在有用数据的末尾。 “标准” EOS char 几乎被普遍定义为 0x00 [自 ~1970] 架构支持的唯一内在类型是:charshortintlong/pointerlong longfloat/double。给定的拱门上可能还有其他一些,但这是通常的列表。其他所有内容(例如,classstruct 由编译器“构建”,以方便来自 Arch intrinsic 类型的程序员

以下是关于 C [和 C++] 的一些内容: - C 有 预处理器 宏。 Java 没有宏的概念。预处理器宏可以被认为是元编程的一种粗略形式。 - C 有inline 函数。它们看起来就像常规函数,但编译器会尝试将它们的代码直接插入到任何调用它的函数中。如果函数定义清晰但很小(例如几行),这很方便。它节省了实际调用函数的开销。


示例

以下是您的原始程序的几个版本作为示例:

// myfnc1 -- original
void
myfnc1(void)

    istream file;

    while (isOpen && !file.eof()) 
        char current_line[LINE];
        char current_name[NAME];

        file.getline(current_line, LINE);

        int i = 0;

        while (current_line[i] != ';') 
            current_name[i] = current_line[i];
            i++;
        

        current_name[i] = 0;

        cout << current_name << endl;
    


// myfnc2 -- moved definitions to function scope
void
myfnc2(void)

    istream file;
    int i;
    char current_line[LINE];
    char current_name[NAME];

    while (isOpen && !file.eof()) 
        file.getline(current_line, LINE);

        i = 0;

        while (current_line[i] != ';') 
            current_name[i] = current_line[i];
            i++;
        

        current_name[i] = 0;

        cout << current_name << endl;
    


// myfnc3 -- converted to for loop
void
myfnc(void)

    istream file;
    int i;
    char current_line[LINE];
    char current_name[NAME];

    while (isOpen && !file.eof()) 
        file.getline(current_line, LINE);

        for (i = 0;  current_line[i] != ';';  ++i)
            current_name[i] = current_line[i];
        current_name[i] = 0;

        cout << current_name << endl;
    


// myfnc4 -- converted to use pointers
void
myfnc4(void)

    istream file;
    const char *line;
    char *name;
    char current_line[LINE];
    char current_name[NAME];

    while (isOpen && !file.eof()) 
        file.getline(current_line, LINE);

        name = current_name;
        for (line = current_line;  *line != ';';  ++line, ++name)
            *name = *line;
        *name = 0;

        cout << current_name << endl;
    


// myfnc5 -- more efficient use of pointers
void
myfnc5(void)

    istream file;
    const char *line;
    char *name;
    int chr;
    char current_line[LINE];
    char current_name[NAME];

    while (isOpen && !file.eof()) 
        file.getline(current_line, LINE);

        name = current_name;
        line = current_line;
        for (chr = *line++;  chr != ';';  chr = *line++, ++name)
            *name = chr;
        *name = 0;

        cout << current_name << endl;
    


// myfnc6 -- fixes bug if line has no semicolon
void
myfnc6(void)

    istream file;
    const char *line;
    char *name;
    int chr;
    char current_line[LINE];
    char current_name[NAME];

    while (isOpen && !file.eof()) 
        file.getline(current_line, LINE);

        name = current_name;
        line = current_line;
        for (chr = *line++;  chr != 0;  chr = *line++, ++name) 
            if (chr == ';')
                break;
            *name = chr;
        
        *name = 0;

        cout << current_name << endl;
    


// myfnc7 -- recoded to use "smart" string
void
myfnc7(void)

    istream file;
    const char *line;
    char *name;
    int chr;
    char current_line[LINE];
    xstr_t current_name;
    xstr_t *name;

    name = &current_name;
    xstrinit(name);

    while (isOpen && !file.eof()) 
        file.getline(current_line, LINE);

        xstragain(name);

        line = current_line;
        for (chr = *line++;  chr != 0;  chr = *line++) 
            if (chr == ';')
                break;
            xstraddchar(name,chr);
        

        cout << xstrcstr(name) << endl;
    

    xstrfree(name);


这是一个类似于您习惯的“智能”字符串 [buffer] 类:

// xstr -- "smart" string "class" for C

typedef struct 
    size_t xstr_maxlen;                 // maximum space in string buffer
    char *xstr_lhs;                     // pointer to start of string
    char *xstr_rhs;                     // pointer to start of string
 xstr_t;

// xstrinit -- reset string buffer
void
xstrinit(xstr_t *xstr)


    memset(xstr,0,sizeof(xstr));


// xstragain -- reset string buffer
void
xstragain(xstr_t xstr)


    xstr->xstr_rhs = xstr->xstr_lhs;


// xstrgrow -- grow string buffer
void
xstrgrow(xstr_t *xstr,size_t needlen)

    size_t curlen;
    size_t newlen;
    char *lhs;

    lhs = xstr->xstr_lhs;

    // get amount we're currently using
    curlen = xstr->xstr_rhs - lhs;

    // get amount we'll need after adding the whatever
    newlen = curlen + needlen + 1;

    // allocate more if we need it
    if ((newlen + 1) >= xstr->xstr_maxlen) 
        // allocate what we'll need plus a bit more so we're not called on
        // each add operation
        xstr->xstr_maxlen = newlen + 100;

        // get more memory
        lhs = realloc(lhs,xstr->xstr_maxlen);
        xstr->xstr_lhs = lhs;

        // adjust the append pointer
        xstr->xstr_rhs = lhs + curlen;
    


// xstraddchar -- add character to string
void
xstraddchar(xstr_t *xstr,int chr)


    // get more space in string buffer if we need it
    xstrgrow(xstr,1);

    // add the character
    *xstr->xstr_rhs++ = chr;

    // maintain the sentinel/EOS as we go along
    *xstr->xstr_rhs = 0;


// xstraddstr -- add string to string
void
xstraddstr(xstr_t *xstr,const char *str)

    size_t len;

    len = strlen(str);

    // get more space in string buffer if we need it
    xstrgrow(xstr,len);

    // add the string
    memcpy(xstr->xstr_rhs,str,len);
    *xstr->xstr_rhs += len;

    // maintain the sentinel/EOS as we go along
    *xstr->xstr_rhs = 0;


// xstrcstr -- get the "c string" value
char *
xstrcstr(xstr_t *xstr,int chr)


    return xstr->xstr_lhs;


// xstrfree -- release string buffer data
void
xstrfree(xstr_t *xstr)

    char *lhs;

    lhs = xstr->xstr_lhs;
    if (lhs != NULL)
        free(lhs);

    xstrinit(xstr);


建议

在尝试“绕过”“c 字符串”之前,请先接受它。你会在很多地方遇到它。这是不可避免的。 了解如何像操作索引变量一样轻松地操作指针。它们更灵活,[一旦你掌握了它们]更容易使用。我见过没有学过这个的程序员编写的代码,他们的代码总是比它需要的复杂得多[而且通常充满了我需要修复的错误]。 良好的注释在任何语言中都很重要,但在某些方面,在 C 中可能比在 Java 中更重要。 始终使用-Wall -Werror 进行编译并修复任何警告。您已被警告 :-) 我会尝试一下我给你的myfnc 示例。这会有所帮助。 在您...之前牢牢掌握基础知识...

现在,谈谈 C++ ...

以上大部分内容是关于架构、内存布局和 C。所有这些仍然适用于 C++。

C++确实在函数返回并且超出范围时对堆栈变量进行更有限的回收。这有其优点和缺点。

C++ 有许多类来减轻常见函数/习语/样板的乏味。它有std 标准模板库。它也有boost。例如,std::string 可能会做你想做的事。但是,首先将它与我的 xstr 进行比较。

但是,我想再次提醒您。在你目前的水平上,基础开始工作,而不是围绕它们。

【讨论】:

没错!我要试试这个 效果很好!!您能解释一下我将如何以更好的方式防止这种情况发生(如果有的话)?因为,在 Java 中,我写的应该可以正常工作! @Mosedince C 风格的数组很奇怪。您可以改用 C++ 样式的数组,或者在本例中使用 C++ 字符串。【参考方案2】:

添加current_name[i] = 0;如上所述对我不起作用。 此外,我在 isOpen 上遇到错误,如问题所示。 因此,我徒手编写了一个修改后的程序,从问题中提供的代码开始,并进行调整直到它正常工作,因为输入文件包含两行文本,每组三个字母字符由“;”分隔,不带引号。也就是说,分隔代码是空格、分号、空格。此代码有效。

这是我的代码。

     #define LINE 1000

    int j = 0;
    while (!file1.eof()) 
        j++;
        if( j > 20)break; // back up escape for testing, in the event of an endless loop

        char current_line[LINE];
        //string current_name = ""; // see redefinition below

        file1.getline(current_line, LINE, '\n');

        stringstream ss(current_line); // stringstream works better in this case

        while (!ss.eof()) 

            string current_name;
            ss >> current_name;
            if (current_name != ";")
             
                cout << current_name << endl;
             // End if(current_name....

             // End while (!ss.eof...

     // End while(!file1.eof() ...

    file1.close();

    cout << "Done \n";

【讨论】:

这重复了while...eof的错误(两次) 没有错。注意一个是在文件上使用 eof,另一个是在字符串流上使用 elf。搜索它,您应该会在 *** 中看到示例。在标记错误然后将其标记下来之前,请确保您是正确的。建议你收回 -1。 不,这是一个错误。存在具有相同错误的其他帖子并不能使其成为非错误。 Read this page 没有错。检查文件流与字符串流的文档。你会得到同样的答案。并且不要忽视这样一个事实,这是完美的。请记住,字符串流与文件流。 eofios 的成员,ios 是这两个流的基类。 ifstream 上的错误与sstream 上的错误一样多。

以上是关于本地数组在循环内重复! C++的主要内容,如果未能解决你的问题,请参考以下文章

如何使用JavaScript将循环内数组中的所有项目相乘[重复]

C ++:在函数内声明数组时,表达式必须具有常量值[重复]

等待所有异步请求在循环内完成[重复]

输出没有重复元素的动态数组(缩小数组)C++

如何在 C 或 C++ 中以 O(n) 删除数组中的重复元素?

在 O(n) 时间内找到数组中的重复元素