代码之家  ›  专栏  ›  技术社区  ›  felideon

为什么不能使用“=`”复制数组?

  •  7
  • felideon  · 技术社区  · 15 年前

    我开始通过阅读K&R和一些练习来学习C。经过一些努力,我终于能够完成练习1-19,代码如下:

    /* reverse: reverse the character string s */
    void reverse(char s[], int slen)
    {
      char tmp[slen];
      int i, j;
    
      i = 0;
      j = slen - 2;    /* skip '\0' and \n */
    
      tmp[i] = s[j];
      while (i <= slen) {
        ++i;
        --j;
        tmp[i] = s[j];
      }
    
      /* code from copy function p 29 */
      i = 0;
      while ((s[i] = tmp[i]) != '\0')
        ++i;
    
    }
    

    我的问题是关于最后一点代码 tmp char数组被复制到 s . 为什么不简单 s = tmp; 换工作?为什么必须按索引迭代数组复制索引?

    9 回复  |  直到 7 年前
        1
  •  20
  •   Jonathan Leffler    11 年前

    也许我只是老了,脾气暴躁,但我看到的其他答案似乎完全没有抓住要点。

    C不做数组赋值,句点。不能通过简单的赋值将一个数组分配给另一个数组,这与其他一些语言不同(例如,pl/1;pascal及其许多后代-ada、modula、oberon等)。C也没有真正的字符串类型。它只有字符数组,而且在不使用循环或函数调用的情况下,您不能复制字符数组(比复制任何其他类型的数组还要多)。[字符串文字不算作字符串类型。]

    复制数组的唯一时间是将数组嵌入到结构中并执行结构分配时。

    在我的K&R第二版中,练习1-19要求一个函数 reverse(s) 在我的K&R第一版中,它是练习1-17而不是练习1-19,但我问了同样的问题。

    由于指针在这个阶段还没有被覆盖,解决方案应该使用索引而不是指针。我相信这会导致:

    #include <string.h>
    void reverse(char s[])
    {
        int i = 0;
        int j = strlen(s) - 1;
        while (i < j)
        {
            char c = s[i];
            s[i++] = s[j];
            s[j--] = c;
        }
    }
    
    #ifdef TEST
    #include <stdio.h>
    int main(void)
    {
        char buffer[256];
        while (fgets(buffer, sizeof(buffer), stdin) != 0)
        {
            int len = strlen(buffer);
            if (len == 0)
                break;
            buffer[len-1] = '\0';  /* Zap newline */
            printf("In:  <<%s>>\n", buffer);
            reverse(buffer);
            printf("Out: <<%s>>\n", buffer);
        }
        return(0);
    }
    #endif /* TEST */
    

    用-dtest编译它以包含测试程序,而不需要只具有函数 reverse() 定义。

    使用问题中给出的函数签名,可以避免调用 strlen() 每行输入两次。注意使用 fgets() 即使在测试程序中,使用 gets() . 不利的一面 FGSE() 相比于 GETSH() 那是 FGSE() 不删除尾随换行符,其中 GETSH() 做。有利的一面 FGSE() 是说,您不会得到数组溢出,并且您可以在遇到新行之前判断程序是否找到新行,或者它是否耗尽了空间(或数据)。

        2
  •  8
  •   Johannes Schaub - litb    15 年前

    你的 tmp 数组在上声明 stack 因此,当方法完成时,用于保存值的内存将被释放,因为 scoping .

    s = tmp 意味着 s 应该指向与 川芎嗪 . 这意味着当 川芎嗪 被释放, S 仍将指向现在可能无效的已释放内存位置。

    这种类型的错误称为 dangling pointer .

    编辑: 这不是这个答案的评论中指出的悬而未决的修饰语。问题是这句话 S=TMP 只更改参数指向的内容,而不更改传递的实际数组。

    此外,您可以通过一次传递来执行反向操作,而不需要通过逐个交换值在内存中分配整个数组:

    void reverse(char s[], int slen) {
        int i = 0;        // First char
        int j = slen - 2; // Last char minus \n\0
        char tmp = 0;     // Temp for the value being swapped
    
        // Iterate over the array from the start until the two indexes collide.
        while(i < j) {
            tmp = s[i];  // Save the eariler char
            s[i] = s[j]; // Replace it with the later char
            s[j] = tmp;  // Place the earlier char in the later char's spot
            i++;         // Move forwards with the early char
            j--;         // Move backwards with the later char
        }
    }
    
        3
  •  4
  •   Tom    15 年前

    因为S和TMP都是内存地址。如果您s=tmp,两个指针将指向同一个数组。

    假设我们有

    char s[] ="ab"; 
    
    /*
    * Only for explanatory purposes.
    * 
    */
    void foo(char s[]){ 
        char tmp [] = "cd";
        s= tmp;
     }
    
    foo(s);
    

    在s=tmp之后

    s[0] : 'c'
    s[1] : 'd'
    s[2] : '\0'
    

    即使两个数组都有相同的数据,TMP中的更改也会影响这两个数组,因为两个数组实际上是相同的。它们都包含在同一内存地址中的数据。因此,通过改变TMP阵列的任何位置,或破坏TMP阵列,S也会受到同样的影响。

    通过在数组上循环,您要做的是将一段数据从一个内存地址移动到另一个内存地址。

    在我的K&R副本中,第4章解释了指针。快速浏览第一页可能会有所帮助。

        4
  •  1
  •   iwanttoprogram    15 年前

    为了使讨论更为全面,这里还有两种可能的方法可以作为字符串进行反转:

    void reverse(char string1[], char string2[])
    {
      int i = 0, len = 0;
    
      while(string2[len] != '\0')   // get the length of the string
          len++;
    
      while(len > 0)
      {
        string1[i] = string2[len-1]; // copy the elements in reverse
        i++;
        len--;
      }
      string1[i] = '\0'; // terminate the copied string 
    }
    

    或递归地:

    void reverse (const char *const sPtr)
    {
      //if end of string
      if (sPtr[0] == '\0')
      {
        return;
      }
      else  //not end of the string...
       {
        reverse(&sPtr[1]);  //recursive step
        putchar(sPtr[0]);   //display character
       }
    }
    
        5
  •  0
  •   Macarse    15 年前

    因为tmp是一个指针,你需要一个副本,而不是一个“链接”。

        6
  •  0
  •   Arnkrishn    15 年前

    在s=tmp的情况下,作为数组起始地址的tmp的值将被复制到s。

    这样,S和TMP都会指向内存中相同的地址,我认为这不是目的。

    干杯

        7
  •  0
  •   Salman A    15 年前

    尝试试验,看看当你这样做时会发生什么:

    void modifyArrayValues(char x[], int len)
    {
        for (int i = 0; i < len; ++i)
            x[i] = i;
    }
    
    void attemptModifyArray(char x[], int len)
    {
        char y[10];
        for (int i = 0; i < len; ++i)
            y[i] = i;
        x = y;
    }
    
    
    int main()
    {
        int i = 0;
        char x[10];
        for (i = 0; i < 10; ++i)
            x[i] = 0;
    
        attemptModifyArray(x, 10);
        for (i=0; i < 10; ++i)
            printf("%d\n", x[i]); // x is still all 0's
    
        modifyArrayValues(x, 10);
        for (i=0; i < 10; ++i)
            printf("%d\n", x[i]); // now x has 0-9 in it
    }
    

    当您直接在 attemptModifyArray ,您只是重写数组地址的本地副本。 x . 当你回来时,原来的地址还在 main 的副本X。

    当您在中修改数组中的值时, modifyArrayValues ,您正在修改实际数组本身,它的地址存储在 修改rayValues 本地副本 X . 当你回来的时候, X 仍然保留在同一个数组中,但您已经修改了该数组中的值。

        8
  •  0
  •   zebrabox    15 年前

    这个线程中有一个关于数组和指针的有趣的子线程 我发现 this link 在维基百科上,有一个特殊的代码片段,显示了“橡皮泥”C是如何的!

    /* x designates an array */
    x[i] = 1;
    *(x + i) = 1;
    *(i + x) = 1;
    i[x] = 1; /* strange, but correct: i[x] is equivalent to *(i + x) */
    

    当然,在C中更令人困惑的是,我可以这样做:

    unsigned int someval = 0xDEADD00D;
    char *p = (char *)&someval;
    
    p[2] = (char)0xF0;
    

    因此,指针和数组的互换性在C语言中似乎是如此的深刻,以至于几乎是有意的。
    其他人怎么想?

    ——原岗位---
    s和tmp都是指针,所以做s=tmp只会使s指向tmp在内存中的地址。
    您概述的另一个问题是,tmp是一个局部变量,因此当它超出范围时(即当函数返回时),它将变为“未定义”。

    确保你完全掌握了这三个概念,你不会走错太远。

    1. 范围
    2. 堆栈和堆之间的差异
    3. 指针

    希望能有所帮助,继续前进!

        9
  •  -2
  •   Elitecoder    15 年前

    一个非常直接的答案是- s和tmp都是指向内存位置的指针,而不是数组本身。 换句话说,s和tmp是存储数组值的内存地址,而不是值本身。 访问这些数组值的常见方法之一是使用s[0]或tmp[0]等索引。

    现在,如果您尝试简单地复制,s=tmp,tmp数组的内存地址将被复制到s。这意味着,原来的s数组将丢失,甚至s内存指针现在将指向tmp数组。

    随着时间的推移,你会很好地理解这些概念,所以继续阅读这本书。 我希望这个基本的解释有帮助。