指针和数组 – 艰难地学习C语言

这个问题来自Zed Shaw的Learn C the Hard Way。 它是关于指针和数组的。 我们在这里给出了一些代码:

#include  int main(int argc, char *argv[]) { // create two arrays we care about int ages[] = {23, 43, 12, 89, 2}; char *names[] = { "Alan", "Frank", "Mary", "John", "Lisa" }; // safely get the size of ages int count = sizeof(ages) / sizeof(int); int i = 0; // first way using indexing for(i = 0; i < count; i++) { printf("%s has %d years alive.\n", names[i], ages[i]); } printf("---\n"); // setup the pointers to the start of the arrays int *cur_age = ages; char **cur_name = names; // second way using pointers for(i = 0; i < count; i++) { printf("%s is %d years old.\n", *(cur_name+i), *(cur_age+i)); } printf("---\n"); // third way, pointers are just arrays for(i = 0; i < count; i++) { printf("%s is %d years old again.\n", cur_name[i], cur_age[i]); } printf("---\n"); // fourth way with pointers in a stupid complex way for(cur_name = names, cur_age = ages; (cur_age - ages) < count; cur_name++, cur_age++) { printf("%s lived %d years so far.\n", *cur_name, *cur_age); } return 0; } 

该指令是“ 重写此程序中的所有数组使用,以便它是指针。 ”这是否意味着做类似的事情?

 int *ptr; ptr = &ages[0] 

让我先说一些关于主题的内容:

  • 我不认为这是一本非常好的书。 我认为它混淆了一些主题,使它们看起来比实际更难。 对于更好的高级C书,我会推荐Peter van der Linden的Deep C Secrets ,对于初学者的书,我推荐原版K&R

无论如何,看起来你正在看本章的额外学分练习。

  • 另外一点 – 我不认为这是一个特别明智的学习练习(另一个答案指出这个问题没有形成有意义),所以这个讨论会变得有点复杂。 我会推荐K&R第5章的练习。

首先,我们需要了解指针与数组不同 。 我在这里的另一个答案中对此进行了扩展,我将从C FAQ中借用相同的图表。 当我们声明一个数组或一个指针时,这就是内存中发生的事情:

  char a[] = "hello"; // array +---+---+---+---+---+---+ a: | h | e | l | l | o |\0 | +---+---+---+---+---+---+ char *p = "world"; // pointer +-----+ +---+---+---+---+---+---+ p: | *======> | w | o | r | l | d |\0 | +-----+ +---+---+---+---+---+---+ 

所以,在本书的代码中,当我们说:

 int ages[] = {23, 43, 12, 89, 2}; 

我们得到:

  +----+----+----+----+---+ ages: | 23 | 43 | 12 | 89 | 2 | +----+----+----+----+---+ 

我将使用非法声明进行解释 – 如果我们可以说:

 int *ages = {23, 43, 12, 89, 2}; // The C grammar prohibits initialised array // declarations being assigned to pointers, // but I'll get to that 

这将导致:

  +---+ +----+----+----+----+---+ ages: | *=====> | 23 | 43 | 12 | 89 | 2 | +---+ +----+----+----+----+---+ 

这些都可以在以后以相同的方式访问 – ages[0]可以访问第一个元素“23”,无论它是数组还是指针。 到现在为止还挺好。

但是,当我们想要计算时,我们会遇到问题。 C不知道有多大的数组 – 它只知道它知道的变量有多大(以字节为单位)。 这意味着,使用数组,您可以通过以下方式计算出大小:

 int count = sizeof(ages) / sizeof(int); 

或者,更安全:

 int count = sizeof(ages) / sizeof(ages[0]); 

在数组的情况下,这说:

 int count = the number of bytes in (an array of 6 integers) / the number of bytes in (an integer) 

这正确地给出了数组的长度。 但是,对于指针大小写,它将显示为:

 int count = the number of bytes in (**a pointer**) / the number of bytes in (an integer) 

这几乎肯定与数组的长度不一样。 在使用指向数组的指针的地方,我们需要使用另一种方法来计算数组的长度。 在C中,以下任一情况都是正常的:

  • 记住有多少元素:

     int *ages = {23, 43, 12, 89, 2}; // Remember you can't actually // assign like this, see below int ages_length = 5; for (i = 0 ; i < ages_length; i++) { 
  • 或者,保持一个sentinel值(它永远不会作为数组中的实际值出现)来指示数组的结尾:

     int *ages = {23, 43, 12, 89, 2, -1}; // Remember you can't actually // assign like this, see below for (i = 0; ages[i] != -1; i++) { 

    (这是字符串的工作方式,使用特殊的NUL值'\ 0'来表示字符串的结尾)


现在,记住我说你实际上不能写:

  int *ages = {23, 43, 12, 89, 2, -1}; // Illegal 

这是因为编译器不允许您将隐式数组分配给指针。 如果你真的想要,你可以写:

  int *ages = (int *) (int []) {23, 43, 12, 89, 2, -1}; // Horrible style 

但不要,因为阅读非常不愉快。 出于本练习的目的,我可能会写:

  int ages_array[] = {23, 43, 12, 89, 2, -1}; int *ages_pointer = ages_array; 

请注意,编译器将数组名称“衰减”为指向它的第一个元素 - 就像你写的那样:

  int ages_array[] = {23, 43, 12, 89, 2, -1}; int *ages_pointer = &(ages_array[0]); 

但是 - 您也可以动态分配数组。 对于这个示例代码,它将变得非常冗长,但我们可以将其作为学习练习。 而不是写:

 int ages[] = {23, 43, 12, 89, 2}; 

我们可以使用malloc分配内存:

 int *ages = malloc(sizeof(int) * 5); // create enough space for 5 integers if (ages == NULL) { /* we're out of memory, print an error and exit */ } ages[0] = 23; ages[1] = 43; ages[2] = 12; ages[3] = 89; ages[4] = 2; 

请注意,当我们完成记忆时,我们需要释放ages

 free(ages); 

另请注意,有几种方法可以编写malloc调用:

  int *ages = malloc(sizeof(int) * 5); 

这对于初学者来说更清楚,但通常被认为是不好的风格,因为如果你改变ages的类型,你需要改变两个地方。 相反,你可以写下:

  int *ages = malloc(sizeof(ages[0]) * 5); int *ages = malloc(sizeof(*ages) * 5); 

这些陈述是等价的 - 您选择的是个人风格的问题。 我更喜欢第一个。


最后一件事 - 如果我们将代码更改为使用数组,您可能会考虑更改此代码:

 int main(int argc, char *argv[]) { 

但是,你不需要。 之所以有点微妙。 首先,这个声明:

 char *argv[] 

说“有一个名为argv的char指针数组”。 但是,编译器将函数参数中的数组视为指向数组第一个元素的指针,因此如果您编写:

 int main(int argc, char *argv[]) { 

编译器实际上会看到:

 int main(int argc, char **argv) 

这也是您可以省略用作函数参数的多维数组的第一维的长度的原因 - 编译器将看不到它。

它可能意味着你所建议的东西,是的。

但请记住, ages已经是一个int指针( int * ) – 一个数组,在C中,只是一堆在内存中彼此相邻的东西。 表示该数组的变量只是指向该数组中第一个元素的指针,而[]运算符是一个取消引用。

你可以这样想:

当你的程序运行包含时,某处有一块内存
|...| 23 | 43 | 12 | 89 | 2 |...|
每个框表示足够的空间来容纳一个int
然后,程序中的变量ages只是一个指针,它保存该块中第一个元素的地址。 它“指向” 23 ,并且类型为int* 。 如果你取消引用它,你会发现*ages计算结果为23.同样,如果你拿到那个地址并“跳过”一个int -size向前,你将获得43 。 在代码中,这看起来像
*(ages + 1 * sizeof(int))
您可以在其中使用要跳过的多个元素替换1 。 因为这真的很丑陋和令人困惑,C为你提供了一个很好的方法来完成同样的事情: []运算符。 一般来说,

some_array[n] == *(some_array + n * sizeof(array_element_type))

希望有所帮助,祝C学习好运! 确保花时间真正理解数组和指针的相等性; 如果你不这样做,它会让很多事情变得更加艰难。

我的猜测是,这意味着

  1. 使用malloc()为每个数组分配内存,并使用free()释放内存。

  2. 在所有for循环中使用指针算法。

“使用指针使用而不是数组使用来重写代码”的任务没有充分明确地表达出来。 在C语言中,99.9%(只是非正式数字)的数组function基于隐式数组到指针的转换,这意味着几乎每次使用数组时,都会使用指针。 没有办法绕过它。

换句话说,正式地说,没有必要重写任何东西。

如果您通过执行重写代码

 int *ptr = &ages[0]; 

并且使用ptr代替ages ,你只需要隐式地显示代码中已经存在的东西。 如果这就是那个任务的真正意义,那么你当然可以做到这一点。 但是我没有看到这种减少运动的重点。

这是一种在不使用动态分配的情况下将agesnames更改为指针的方法。

  // create two arrays we care about const char *ages = "\x17\x2b\x0c\x59\x02"; const char (*names)[6] = (void *) "Alan\0\0" "Frank\0" "Mary\0\0" "John\0\0" "Lisa\0\0"; // safely get the size of ages int count = strlen(ages); //... // setup the pointers to the start of the arrays const char *cur_age = ages; const char (*cur_name)[6] = names; 

namescur_name都是指针类型,尽管它们指向数组。