读《The C Programming Language》(7)

本文深入探讨C语言中的指针概念,从基本定义到高级应用,包括指针与数组的关系、地址算术、字符指针及函数使用、多维数组、函数指针等内容。通过丰富的实例帮助读者掌握指针的精髓。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第五章讲的是指针和数组。指针是C语言的精华,但同时也是最难和最容易混淆的地方。读这一章真是如履薄冰,小心翼翼。从第一节到第十二节,难度依次增大,如果前面放过了一些不清楚的地方,到后面看到char (*(*x[3])())[5]这样的东西就只有哭的力气了。

本章一开始作者就告诉了你指针是什么,"A pointer is a variable that contains the address of a variable."这里面有三层意思:1.指针是一个变量;2.这个变量的内容是一个地址;3.这个地址是内存中变量的地址。理解了指针是内存中变量的地址,也就理解了指针的本质。

第一节 指针和地址

这一节在提到一元运算符&时提到,&只能被应用于内存中的对象,也就是变量和数组元素,而不能用在表达式、常量和寄存器变量。所以如果想把一个常数作为参数传给一个要求指针的函数,必须把这个常数赋给一个变量,然后把这个变量的地址传给函数。例如,int i = 8, j = 9; swap(&i, &j);

另外,作者在提到指针的声明时,说其实变量、表达式、函数的声明都是一致的。比如double d, *dp, atof(char *); *dp可以看成是一个表达式,它是一个double型的。以前我们写程序时,对指针会有两种写法,char* c和char *c,作者这么一说我明显觉得后者更合理了。

第二节 指针和函数参数

这一节提到因为C语言的函数调用是传值的,所以不可能改变调用函数的变量,改变的只是这些变量的一份拷贝。如果想改变,可以传指针过去,通过地址访问和改变调用函数的变量。作者同时提到,如果一个函数想返回两个或多个值,也可以通过传进来的指针记录这样的值,库函数scanf就是这么干的。

第三节 指针和数组

相信很多人对指针的糊涂就是从指针和数组的关系开始的。作者在这一节特别强调了指针和数组的密切关系,"Any operation that can be achieved by array subscripting can also be done with pointers." 任何用数组下标可以完成的操作都可以通过指针完成,而且指针的效率更高。实际上,C在计算数组元素a[i]时,会把它转化成*(a+i)。这两种形式是完全等价的。

另外,C函数的参数传递是传值的,这一点在参数是指针时仍然成立。被调用函数获得的参数指针实际上是调用函数中的指针的拷贝。比如一个函数调用function(a),其中a是一个数组名,那么传给函数function的参数实际上是a数组的首地址的拷贝。这使得函数function可以对传入的参数作诸如a++之类的操作,因为这时的指针参数是一个局部变量,而不是原来的数组名。而对数组名作诸如a++之类的操作是非法的。以上所讲的和我们以前说的传指针可以改变调用函数的变量并不矛盾。虽然传指针也是按值传了一份拷贝,但这份拷贝所指向的仍然是调用函数中的变量,所以针对它的操作仍然可以改变这个变量。

最后,作者提到,给一个函数还可以传递数组的一部分作为参数,比如f(&a[2])或者f(a+2)。在这种情况下,如果被调用函数可以肯定传来的指针指向的是数组中间的某个元素,它甚至可以用负数作为下标去访问这个元素前面的元素,比如p[-1], p[-2]等!

第四节 地址算术

在这一节作者强调。"C is consistent and regular in its approach to address arithmetic; its integration of pointers, arrays, and address arithmetic is one of the strengths of the language." 这一点我深表赞同,指针、数组和地址算术统一起来不仅是C的优势,而且应该是C最强大的地方。接下来作者用了一个最简单的内存管理程序(分配和释放)说明了这种统一。

这个十几行的小程序说明了内存分配和释放的最基本原理:内存分配就是返回空闲区域的指针,同时把空闲区域的指针向前挪n(n是刚分配的区域大小)个位置;而回收只是简单的把空闲区域的指针回挪n(n是刚回收的区域大小)个位置。这个程序还展示了指向同一个数组的两个指针可以比较大小,大的减小的还可以得出它们之间相差了几个位置。这个位置的差距和数组元素的类型大小无关,C语言在计算指针位置时会自动考虑数组元素的类型。这就是作者所说的"All the pointer manipulations automatically take into account the size of the objects pointed to."。

第五节 字符指针和函数

本节一开始作者就告诉读者,字符串常量是一个字符数组,它的存储长度要比字符数多一个('/0')。在程序中访问字符串常量是通过指向第一个字符的指针。我们用printf("Hello, world")打印字符串时,也许没有注意到printf接受的参数其实是char *。作者特别提醒

char amessage[] = "now is the time";
char *pmessage = "now is the time";

这两者是有很大不同的。前者是对数组的初始化过程,无论数组元素怎么改变,amessage始终指向固定的第一个数组元素。后者是一个指针变量的初始化过程,使pmessage 指向字符串常量"now is the time"的第一个字符,而它有可能随后被重新赋值,指向不同存储位置的另一个字符串。

第六节 指针数组;指针的指针

对指针和数组有点糊涂的人到这里就更糊涂了。为了说明指针数组和指针的指针的用法,作者举了一个给若干字符串排序的例子。给数组的每一个元素填充一个指向一个字符串的指针,这大概是指针数组最常用的形式吧。在这个例子中,声明char *lineptr[MAXLINES]指出lineptr是一个长度为MAXLINES的数组,里面每一个元素是指向char型的指针。lineptr[i]是一个字符指针,而*lineptr[i]是这个指针指向的字符。将*lineptr++放在循环中就可以遍历一个个字符串。

第七节 多维数组

C中的二维数组时按行存储的,相当于一个一维数组,每一个数组元素是一个数组。值得注意的是,二维数组作为函数参数时,列是必须指定的,而行则无关紧要。比如f(int daytab[][13]) { ... },它相当于f(int (*daytab)[13]) { ... },其中daytab是指向长度为13的整形数组的指针。对于多维数组来说,只有第一维可以不指定,其它的必须指定。

第八节 指针数组的初始化

作者举了一个字符串数组的例子:

/* month_name: return name of n-th month */
char *month_name(int n)
{
    static char *name[] = {
        "Illegal month",
        "January", "February", "March",
        "April", "May", "June",
        "July", "August", "September",
        "October", "November", "December"
    };
    return (n < 1 || n > 12) ? name[0] : name[n];
}

在这个例子里,除了能看到字符指针数组的初始化,还能看到静态局部变量的妙用。这个数组不会随着函数的调用和返回而创建和销毁,而且只能为这个函数所用。另外,这个例子再一次体现了字符串常量在程序中通过它的首地址访问。字符串的首地址被赋给字符指针数组的元素。

第九节 指针vs.多维数组

 作者重点比较了二维数组和指针数组的区别。比如int a[10][20];和int *b[10];,区别有以下几点:1. 前者是一个二维数组,系统给它分配了200个整形变量的存储空间,而后者是一个指针数组,系统只分配给它10个指针的存储空间;2. 前者只能是矩形,而后者可以是锯齿形的,指针数组中的指针可以指向任何长度的数组。

第十节 命令行参数

本节作者提到了给main函数传递参数的方式,并且举例介绍了如何处理这些参数。这一节并没有对指针的性质或者用法做什么扩展,但举的几个例子有一定难度,看懂之后会觉得写得相当经典,特别是最后一个。

第十一节 函数指针

函数指针让程序有了更大的灵活性,可以把程序中的相对独立又容易改变的算法部分用函数指针代替,从而让这部分可以配置,有点类似面向对象中的策略模式。作者用一个排序的例子说明了函数指针的用法,但并没有展开讲。也许是因为在实际总应用的少吧,感觉这部分的讲述不是很丰满。

第十二节 复杂声明

这一节出现了把指针、数组和函数揉在一起的表达式,就像开篇举的那个例子一样。作者为了让大家能读懂这样的变态表达式,特地写了一个程序把这样的式子翻译成英语,比如开篇的那个char (*(*x[3])())[5]就翻译成x: array[3] of pointer to function returning pointer to array[5] of char。可是在我看来,别说原来的表达式,就是这个翻译出来的英文“短”语大概也会让很多人当场晕倒。这个程序的教学意义应该要比实际意义大。我在想,能不能写一个把这种变态表达式翻译成汉语的程序,这个程序应该要比英语的难得多。呵呵,不知道有没有人有兴趣一试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值