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

时间:2009-06-20 19:46:32

标签: c arrays

我开始通过阅读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 个答案:

答案 0 :(得分:20)

也许我只是老了,脾气暴躁,但我看到的其他答案似乎完全忽略了这一点。

C不执行数组赋值,句点。您不能通过简单的赋值将一个数组分配给另一个数组,这与其他语言不同(例如,PL / 1; Pascal及其许多后代--Ada,Modula,Oberon等)。 C也没有真正的字符串类型。它只有字符数组,你不能复制字符数组(除了你可以复制任何其他类型的数组)而不使用循环或函数调用。 [字符串文字实际上并不算作字符串类型。]

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

在我的K&amp; R第2版副本中,练习1-19要求提供功能reverse(s);在我的K&amp; R第1版副本中,它是练习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()也是一个坏主意。与fgets()相比,gets()的缺点是fgets()不会删除gets()所在的尾随换行符。 fgets()的优点是你没有得到数组溢出,你可以在遇到换行符之前判断程序是否找到换行符或者是否用完了空格(或数据)。

答案 1 :(得分:8)

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

s = tmp表示s应指向与tmp相同的内存位置。这意味着当tmp被释放时,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
    }
}

答案 2 :(得分:4)

因为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&amp; amp; R,指针在第4章中解释。快速浏览第一页可能会有所帮助。

答案 3 :(得分:1)

为了完善讨论,这里有另外两种可能的方法来反转为字符串:

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
   }
}

答案 4 :(得分:0)

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

答案 5 :(得分:0)

在s = tmp的情况下,tmp的值(也是数组的起始地址)将被复制到s。

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

欢呼

答案 6 :(得分:0)

这个线程中有一个关于数组和指针的有趣子线程 我在维基百科上找到了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只会指向tmp存在于内存中的地址。
您所概述的另一个问题是tmp是一个局部变量,因此当它超出范围时,即当函数返回时,它将变为“未定义”。

确保你彻底掌握这三个概念,你就不会出错了

  1. 范围
  2. 堆栈和堆之间的区别
  3. 指针
  4. 希望有所帮助并继续前进!

答案 7 :(得分:0)

尝试尝试,看看当你做这样的事情会发生什么:

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中修改数组中的值时,您正在修改实际数组本身,其地址存储在modifyArrayValues本地副本x中。当您返回时,x仍然保持同一个数组,但您已修改该数组中的值。

答案 8 :(得分:-2)

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

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

你会在适当的时候理解这些概念,所以请继续阅读本书。 我希望这个基本的解释有所帮助。