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

我开始通过阅读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; 工作呢? 为什么必须通过索引遍历数组复制索引?

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

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

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

在我的K&R第2版副本中,练习1-19要求reverse(s)function; 在我的K&R第1版副本中,它是练习1-17而不是1-19,但问了同样的问题。

由于在此阶段尚未涵盖指针,因此解决方案应使用索引而不是指针。 我相信这会导致:

 #include  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  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()也是一个坏主意。 与gets() fgets()相比, fgets()的缺点是fgets()不会删除gets()所在的尾随换行符。 fgets()的优点是你没有得到数组溢出,你可以在遇到换行符之前判断程序是否找到换行符或者是否用完了空格(或数据)。

你的tmp数组是在堆栈上声明的,所以当你的方法完成时,用于保存值的内存将因为作用域而被释放。

s = tmp表示s应指向与tmp相同的内存位置。 这意味着当释放tmp时, s仍将指向现在可能无效的释放内存位置。

这种类型的错误称为悬空指针 。

编辑:这不是这个答案的评论中指出的悬空修饰符。 问题是说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 } } 

因为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' 

尽管两个arrays都具有相同的数据,但tmp的变化将影响它们,因为两个arrays实际上是相同的。 它们都包含相同内存地址的数据。 因此,通过改变tmp数组的任何位置,或者破坏tmp数组,s将以相同的方式受到影响。

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

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

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

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

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

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

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

干杯

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

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

在这个线程中有一个关于数组和指针的有趣的子线程我在维基百科上找到了这个链接 ,其中有一个特殊的代码片段,显示了’橡皮泥’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. 指针

希望有所帮助并继续前进!

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

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

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