指针和数组 – 艰难地学习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学习好运! 确保花时间真正理解数组和指针的相等性; 如果你不这样做,它会让很多事情变得更加艰难。
我的猜测是,这意味着
-
使用
malloc()
为每个数组分配内存,并使用free()
释放内存。 -
在所有for循环中使用指针算法。
“使用指针使用而不是数组使用来重写代码”的任务没有充分明确地表达出来。 在C语言中,99.9%(只是非正式数字)的数组function基于隐式数组到指针的转换,这意味着几乎每次使用数组时,都会使用指针。 没有办法绕过它。
换句话说,正式地说,没有必要重写任何东西。
如果您通过执行重写代码
int *ptr = &ages[0];
并且使用ptr
代替ages
,你只需要隐式地显示代码中已经存在的东西。 如果这就是那个任务的真正意义,那么你当然可以做到这一点。 但是我没有看到这种减少运动的重点。
这是一种在不使用动态分配的情况下将ages
和names
更改为指针的方法。
// 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;
names
和cur_name
都是指针类型,尽管它们指向数组。