Welcome

首页 / 软件开发 / C语言 / TC编程手册之三

TC编程手册之三2009-10-04下面来介绍C语言功能最强大的特点,同时也是相对而言比较难掌握的概念之一——指针。

一、指针的基本概念

如同其它基本类型的变量一样,指针也是一种变量,但它是一种把内存地址作为其值的变量。因为指针通常包含的是一个拥有具体值的变量的地址,所以它可以间接地引用一个值。

二、指针变量的声明、初始化和运算符

声明语句

int *ptra, a;

声明了一个整型变量a与一个指向整数值的指针ptra,也就是说,在声明语句中使用*(称为“间接引用运算符”)即表示被声明的变量是一个指针。指针可被声明为指向任何数据类型。需要强调的是,在此语句中变量a只被声明为一个整型变量,这是因为间接引用运算符*并不针对一条声明语句中的所有变量,所以每一个指针都必须在其名字前面用前缀*声明。指针应该用声明语句或赋值语句初始化,可以把指针初始化为0、NULL或某个地址,具有值0或NULL的指针不指向任何值,而要想把某个变量地址赋给指针,需使用单目运算符&(称为“地址运算符”)。

例如程序已用声明语句

int *ptra, a=3;

声明了整型变量a(值为3)与指向整数值的指针a,那么通过赋值语句

ptra=&a;

就可以把变量a的地址赋给指针变量ptra。需要注意的是不可将运算符&用于常量、表达式或存储类别被声明为register的变量上。被赋值后的指针可以通过运算符*获得它所指向的对象的值,这叫做“指针的复引用”,例如打印语句

printf("%d", *ptra);

就会打印出指针变量ptra所指向的对象的值(也就是a的值)3。如果被复引用的指针没有被正确的初始化或没有指向具体的内存单元都会导致致命的执行错误或意外地修改重要的数据。Printf的转换说明符%p以十六进制整数形式输出内存地址,例如在以上的赋值后,打印语句

printf("%p", &a);
printf("%p", ptra);

都会打印出变量a的地址。

三、指针表达式和算术运算以及数组、字符串和指针的关系

在算术表达式、赋值表达式和比较表达式中,指针是合法的操作数,但是并非所有的运算符在与指针变量一起使用时都是合法的,可以对指针进行的有限的算术运算包括自增运算(++)、自减运算(--)、加上一个整数(+、+=)、减去一个整数(-或-=)以及减去另一个指针。

数组的各元素在内存中是连续存放的,这是指针运算的基础。现在假设在一台整数占4个字节的机器上,指针ptr被初始化指向整型数组a(共有三个元素)的元素a[0],而a[0]的地址是40000,那么各个变量的地址就会如下表所示:

表达式ptra&a[0]&a[1]&a[2]
表达式的意义指针ptra的值元素a[0]的地址元素a[1]的地址元素a[2]的地址
表达式的值40000400004000440008

必须注意,指针运算不同于常规的算术运算,一般地,40000+2的结果是40002,但当一个指针加上或减去一个整数时,指针并非简单地加上或减去该整数值,而是加上该整数与指针引用对象大小的乘积,而对象的大小则和机器与对象的数据类型有关。例如在上述情况下,语句

ptra+=2;

的结果是40000+4*2=40008, ptra也随之指向元素a[2],同理,诸如语句

ptra-=2;
ptra++;
++ptra;
ptra--;
ptra--;

等的运算原理也都与此相同,至于指针与指针相减,则会得到在两个地址之间所包含的数组元素的个数,例如ptra1包含存储单元40008,ptra2包含存储单元40000,那么语句

x = ptra1 - ptra2;

得到的结果就是2(仍假设整数在内存中占4个字节)。因为除了数组元素外,我们不能认为两个相同类型的变量是在内存中连续存储的,所以指针算数运算除了用于数组外没有什么意义。

如果两个指针类型相同,那么可以把一个指针赋给另一个指针,否则必须用强制类型转换运算符把赋值运算符右边的指针的类型转换为赋值运算符左边指针的类型。例如ptr1是指向整数的指针,而ptr2是指向浮点数的指针,那么要把ptr2的值赋给ptr1, 则须用语句

ptr1 = (int *) ptr2;

来实现。唯一例外的是指向void类型的指针(即void *),因为它可以表示任何类型的指针。任何类型的指针都可以赋给指向void类型的指针,指向void类型的指针也可以赋给任何类型的指针,这两种情况都不需要使用强制类型转换。

但是由于编译器不能根据类型确定void *类型的指针到底要引用多少个字节数,所以复引用void *指针是一种语法错误。

可以用相等测试运算符和关系运算符比较两个指针,但是除非他们指向同一个数组中的元素,否则这种比较一般没有意义。相等测试运算符则一般用来判断某个指针是否是NULL,这在本文后面提到内存操作中有一定的用途。

C语言中的数组和指针有着密切的关系,他们几乎可以互换,实际上数组名可以被认为是一个常量指针,假设a是有五个元素的整数数组,又已用赋值语句

ptra = a;

将第一个元素的地址赋给了指向整数的指针ptra,那么如下的一组表达式是等价的:

a[3] ptra[3] *(ptra+3) *(a+3)

它们都表示数组中第四个元素的值。

又因为C语言中的字符串是用空字符("")结束的字符数组,所以事实上,字符串就是指向其第一个字符的指针。但是还是要提醒大家,数组名和字符串名都是常量指针,他们的值是不可被改变的,例如程序段

char s[]="this is a test.";
for (;*s="";s++)
printf("%c", *s);

是错误的,因为它试图在循环中改变s的值,而s实际上是一个常量指针。

在本部分的最后,说一说指向函数的指针。指向函数的指针包含了该函数在内存中的地址,函数名实际上就是完成函数任务的代码在内存中的起始地址。函数指针常用在菜单驱动系统中。