C语言学习栏目目录
源码 vs2019环境
在上一张中我介绍过指针,指针提供一种以符号形式使用地址的方法。因为计算机的硬件指令非常依赖地址,指针在某种程度上把程序员想要传达的指令以更接近机器的方式表达。因此,使用指针的程序更有效率。尤其是,指针能有效地处理数组。我们很快就会学到,数组表示法其实是在变相地使用指针。
我们举一个变相使用指针的例子:数组名是数组首元素的地址。也就是说,如果flizny是一个数组,下面的语句成立:
flizny == &flizny[0]; // 数组名是该数组首元素的地址
flizny 和&flizny[0]都表示数组首元素的内存地址(&是地址运算符)。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值,如程序清单10.8所示。注意指针加上一个数时,它的值发生了什么变化(转换说明%p通常以十六进制显示指针的值)。
/************************************************************************
功能: 指针地址
/************************************************************************/
#include <stdio.h>
#define SIZE 4
int main(void)
{
short dates[SIZE];
short* pti;
short index;
double bills[SIZE];
double* ptf;
pti = dates; // 把数组地址赋给指针
ptf = bills;
printf("%23s %15s\n", "short", "double");
for (index = 0; index < SIZE; index++)
printf("指针 + %d: %10p %10p\n", index, pti + index,ptf + index);
return 0;
}
下面是该例的输出示例:(VS2019编译)
short double
指针 + 0: 00F3FCC8 00F3FC88
指针 + 1: 00F3FCCA 00F3FC90
指针 + 2: 00F3FCCC 00F3FC98
指针 + 3: 00F3FCCE 00F3FCA0
我们的系统中,地址按字节编址,short类型占用2字节,double类型占用8字节。在C中,指针加1指的是增加一个存储单元。对数组而言,这意味着把加1后的地址是下一个元素的地址,而不是下一个字节的地址(见图)。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道储存对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则*pt 就无法正确地取回地址上的值)。
现在可以更清楚地定义指向int的指针、指向float的指针,以及指向其他数据对象的指针。
指针的值是它所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。许多计算机(包括PC和Macintosh)都是按字节编址,意思是内存中的每个字节都按顺序编号。这里,一个较大对象的地址(如double类型的变量)通常是该对象第一个字节的地址。
在指针前面使用*运算符可以得到该指针所指向对象的值。
指针加1,指针的值递增它所指向类型的大小(以字节为单位)。
下面的等式体现了C语言的灵活性:
dates + 2 == &date[2] // 相同的地址
*(dates + 2) == dates[2] // 相同的值
以上关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素的值。从本质上看,同一个对象有两种表示法。实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是*(ar + n)。可以认为*(ar + n)的意思是“到内存的ar位置,然后移动n个单元,检索储存在那里的值”。
顺带一提,不要混淆 *(dates+2)和*dates+2。间接运算符(*)的优先级高于+,所以*dates+2相当于(*dates)+2:
*(dates + 2) // dates第3个元素的值
*dates + 2 // dates第1个元素的值加2
明白了数组和指针的关系,便可在编写程序时适时使用数组表示法或指针表示法。运行下程序清单后输出的结果和数组中的程序清单输出的结果相同。
/************************************************************************
功能: 指针地址
/************************************************************************/
#include <stdio.h>
#define SIZE 4
int main1(void)
{
short dates[SIZE];
short* pti;
short index;
double bills[SIZE];
double* ptf;
pti = dates; // 把数组地址赋给指针
ptf = bills;
printf("%23s %15s\n", "short", "double");
for (index = 0; index < SIZE; index++)
printf("指针 + %d: %10p %10p\n", index, pti + index,ptf + index);
return 0;
}
程序输出如下:
1 月有 31 天.
2 月有 28 天.
3 月有 31 天.
4 月有 30 天.
5 月有 31 天.
6 月有 30 天.
7 月有 31 天.
8 月有 31 天.
9 月有 30 天.
10 月有 31 天.
11 月有 30 天.
12 月有 31 天.
这里,days是数组首元素的地址,days + index是元素days[index]的地址,而*(days + index)则是该元素的值,相当于days[index]。for循环依次引用数组中的每个元素,并打印各元素的内容。这样编写程序是否有优势?不一定。编译器编译这两种写法生成的代码相同。上程序清单要注意的是,指针表示法和数组表示法是两种等效的方法。该例演示了可以用指针表示数组,反过来,也可以用数组表示指针。在使用以数组为参数的函数时要注意这点。