
第7章指针C语言之所以被认为是功能最强大的语言之一,其中一个主要原因是其具有极高的自由度,而这个自由度在指针方面得到了充分的体现。指针是C语言中最具特色的部分,具有功能异常强大和用法极其灵活的特点。利用指针可以编写出既简洁又高效的程序,但过于灵活的指针功能也会带来一些副作用。7.1变量的指针与指针变量可为了理解指针的概念,先来看一下数据在内存中是如何存储的。在一个程序运行之前,需要首先将程序的代码和数据存入计算机内存中。为便于管理,通常将内存划分为一个一个的内存单元,在当今的计算机中一般以1字节作为一个内存单元。同时,给每个内存单元分配一个编号,称为内存单元的地址。7.1.1指针C语言程序中的数据,在内存中所占用内存单元的个数是由其类型决定的。例如,int型数据占用4个内存单元,char型数据占用1个内存单元。可以发现,不管一个变量占用几个内存单元,只要知道了这个变量第一个单元的地址和这个变量的长度,就能够从内存中找到这个变量。因此,C语言规定:将一个变量所占内存单元区的首地址,简称为这个变量的地址。例如,有如下变量定义:inta;char ch;假设C语言编译系统对这两个变量的内存分配情况如图7.1(a)所示,那么就说变量a的地址是2009,变量ch的地址是201320092009201020101002011201120122012chAch20132013(a)(b)图7.1变量的地址与变量的值

一旦为变量分配了内存单元,对变量的操作实质上就是对其内存单元的操作。例如:a=100;ch="A';这两条赋值语句实现的功能,就是将整数100的二进制补码形式,存入从地址2009开始的4个连续内存单元中,将字符常量'A'的二进制ASCII码存人地址为2013的1个内存单元中,如图7.1(b)所示。什么是指针呢?一个变量的指针其实就是这个变量的地址。只不过指针这个名称,在某些场合显得更加形象一些。7.1.2指针变量在程序中,有时需要将一个变量的地址存储到另一个变量中。那么,这种专门用来存储其他变量地址(指针)的变量,就称为指针变量。如果在一个指针变量中存储了另一个变量的地址,就形象地说该指针变量指向了这个变量。指针变量也必须先定义后使用,定义指针变量的一般形式为类型说明符*变量名;其中,“*”表明这是一个指针变量:“类型说明符”是这个指针变量所指向的变量的数据类型。例如:int *pifloat *qi其中,p、q都是指针变量,不过二者是有区别的。p是指向int型变量的指针变量,只能存储一个int型变量的地址;q是指向float型变量的指针变量,只能存储一个float型变量的地址。需要注意,这里的“*”是指针类型的符号,但并不是变量名的组成部分。也就是说,这里定义的指针变量名是p、,而不是*p、*q。7.2变量的间接引用指针的基本功能是实现变量的间接引用。为了实现这种功能,在程序中需要使用两个最基本的运算符:&和*。c语7.2.1取地址运算符&言程取地址运算符&用于得到一个变量的地址。其一般引用形式为库&变量名设该表达式的运算结果就是取地址运算符&之后的变量的内存地址。计【例7.1】获取变量的地址示例。新#include思int main(void)路100

int i,*qi/*将变量1的地址赋给指针变量g*/q=&i;/*P用于以十六进制形式输出地址值*printf("q-opln",q);return O;程序运行结果:q=000000000022FE44需要注意,该程序的运行结果并不是固定不变的。因为变量i的内存空&i间是由编译系统随机分配的,所以这一次运行的结果跟下一次运行的结果有可能是不一样的。一旦将变量i的地址赋给了指针变量q,就形象地说q指向了变量i,如图7.2所示。图7.2&运7.2.2间接引用运算符*算符的使用间接引用运算符*也称作指针运算符,其一般使用形式为*指针变量该表达式的功能是间接引用给定的指针变量所指向的变量。【例7.2】通过指针变量实现间接引用。#includeintmain(void){inta,b,*p,*q;a=10;b=20;/*不能写成*p=&a;*/p=&a;/*不能写成*g=&b:*/q=&b;*p=100;*q=200;printf("a=%d,b=%din",a,b);return 0;程序运行结果:a=100,b=200这里的*p代表指针变量p所指向的变量,即变量a,因此*p=100与a=100是等价的;*g代表指针变量g所指向的变量,即变量b,因此*q=200与b=200是等价的。由此可见,对变量的访问有以下两种引用方式。(1)直接引用,即通过一个变量的变量名本身直接访问它。第例如:7a=10;章(2)间接引用,即通过一个指针变量对另一个变量进行间接访问。例如:指int a,*p;p=&a针*p=100;101

这里的*p就是对变量a的间接引用。【例7.3】通过间接引用方式,交换两个整型变量的值。编程思路:欲采用间接引用方式访问两个整型变量,需首先定义两个指针变量,并使之分别指向这两个整型变量。源程序:#include int main(void)int a,b,t;int *pl,*p2;a=123;b=456;pl=&ai版社p2=&b;/*此语句等价于t=a;*/t=*pl;/*此语句等价于a=b;*/*p1=*p2;/*此语句等价于b=t:*/*p2=t;printf("a=ed,b=dln",a,b);printf("*pl=%d,*p2=%dln",*pl,*p2);return o;程序运行结果:a=456,b=123*p1=456,*p2=123在该程序中,由于指针变量pi和p2分别指向变量a和b,所以*pi就是变量a的间接引用,*p2就是变量b的间接引用。或许有人会问,在该程序中采用间接引用的形式有什么优势吗?其实并没有,此例只是用来说明如何采用间接引用的形式来访问变量。指针的优势主要体现在后面几章的字符串处理和跨函数间接引用等方面。7.2.3指针变量的初始化在定义一个指针变量的同时给它赋值,称为指针变量的初始化,例如:int a,*p=&a;c要特别注意,上述语句的功能等价于以下这两条语句:语言int a,*p;程p=&a;/ /使得p指向变量a而不是以下这两条语句:序设inta,*pi计//不是使得*p指向变量a*p=&a;新因为这里的*p不是指针变量名,所以不能存储地址。这就是C语言的特点,有时候过思于灵活与简洁往往容易产生歧义。路102

几点说明:(1)不能使用未经赋值的指针变量进行间接引用。这是因为在计算机的内存中,通常有多个程序同时运行。为了避免不同程序之间相互干扰而造成内存数据覆盖,规定每个程序只能访问在该程序中明确分配的内存数据区(如在该程序中定义的变量和数组的内存单元)未经赋值的指针变量的值是随机的,它指向随机的内存单元,这些内存单元不属于明确分配的内存数据区,因而是禁止访问的【错例】#include int main(void)(int *p;/*指针变量p未经赋值*/*p=100;printf("*p=edin",*p):return 0;该程序运行时,将会显示应用程序错误提示,如m例.exea图7.3所示。错例.exe已停止工作Windows正在检重流问题的筛决方富(2)不能通过指定具体地址的方式对内存单元进行间接引用。这是因为在C语言中,内存单元是由编译取清系统负责管理和分配的。用户并不知道哪些内存单元是图7.3应用程序错误提示可用的,所以不能由用户直接指定内存单元的地址。【错例】#includeint main (void)(int *p;/*不能直接指定内存单元的地址*P=2000;*p=100;printf("*p=%d/n",*p);return 0;该程序运行时,同样将会显示如图7.3所示的错误提示。(3)两个类型不同的指针量之间,不能直接赋值。例如:int a,*p :floatx,*qip=&x;q=&a;这里的p=&x:和g-&a:都是错误的,因为赋值运算符两侧的类型不是赋值兼容的。第7品汇章7.3指针与一维数组指在C语言中,数组与指针的关系非常密切,可以说凡是用数组解决的问题都可以用指针解决。同时,可以通过指针灵活地访问数组的元素。针103

7.3.1指向一维数组元素的指针从本质上来说,一维数组的元素也是一个变量,也有自已的地址,因此完全可以定义指向一维数组元素的指针,如图7.4所示。P例如:&a[0]-a[0]inta[10],*p,*q;3a[1] p=&a[0] ;a[2] 9nq=&a[3];7&a[3]a[3]可以发现,这种数组元素地址的表示形式略显烦。为9a[4] 了使用方便,C语言规定:可以用一个一维数组的数组名来11a[5] 代表这个数组中0号元素的地址。13a[6] 例如,若有inta[10]*p;,则p=a;等价于p=&a[0];,因为a[7]115这里的a就代表了a[0]的地址。17a[8] 19a[9]7.3.2通过指针引用一维数组元素有了指向数组元素的指针,如何通过指针来引用一维数图7.4一维数组与指针组的元素呢?这里需要先明确一个问题,若有以下语句:int a[10],*p;p=a;则指针变量p指向数组元素a[0]的第一个单元,那么p+1将会指向哪一个内存单元呢?看起来有两种选择,一种是p+1指向数组元素a[0]的第二个单元;另一种是p+1指向数组元素a[1]的第一个单元。如果选择前一种,那么指针的运算将会相当烦。因此C语言规定,若指针p指向一维数组中的某个元素,则p+1将会指向该数组中的下一个元素,而不论每个数组元素占用几个内存单元。【例7.4】指针加减整数示例。#include int main(void){inta[10],*p=&a[5];printf("p=%pln",p):printf("p+1=%pln",p+1);printf("p-1=%pn",p-1);return 0;程序运行结果:c语p=000000000022FE34言P+1=000000000022FE38程p-1=000000000022FE30库可以发现,对于指向int型的指针,指针值加1将导致地址值加4,从而验证了前面的伊规定。计在此规定的基础上,可以得到如下几条推论:新(1)如果a是一个一维数组,那么a+i就是数组元素a[i的地址(等价于&a[),从而思*(a+i)就代表数组元素a[i]。路104

数组a(2)如果a是一个一维数组,而指针变量p指向a[0],p,a-a[0] p[0]那么p+i就是数组元素a[i]的地址(等价于&a[i);从而*(p+i)p+1, a+1-a[1] p[]]就代表数组元素a[il,如图7.5所示。下面,通过几个简单的例子,来总结一下访问一维数组的几种常用形式。【例7.5】从键盘输入10个整数存入一个一维数组中,p+i, a+i-*(p+i)a[] p[]然后逆序输出。要求使用数组名和间接引用运算符引用数组的元素。源程序:a[9] p[9]p+9,a+9#include 图7.5一维数组元素的间接int main(void)访问fint a[io],i;for(i=0;i=0;i--)printf("%d",*(a+i));/*等价于printf("%d",a[i]):*/return 0;【例7.6】从键盘输人10个整数存入一个一维数组中,然后逆序输出。要求使用指针变量引用数组元素。源程序:#include int main(void)Lint a[lo],*p=a,i;for(i=0;i=0;i--)printf("%d",*(p+i));/*等价于printf("%d",a[il);*/return O;可以发现,在该程序中为了访问不同的数组元素,改变的不是指针变量P的值,而是整型变量i的值。还有一点需要特别说明,在该程序中,虽然P是一个指针变量而不是一个数组,但是C语言却允许将指针形式的*(p+i)表示为数组元素形式的plil,从而也允许将指针p+i表示为&p[i]。从而得到如下的源程序:第7#include 章int main (void){int a[1o],*p=a,i指for(i=0;i=0;i--)针/*等价于printf("%d",*(p+i)):*/printf("sd",plij);105

return 0;【例7.7】从键盘输入10个整数存入一个一维数组中,然后逆序输出。要求利用指针变量自身的变化来引用不同的数组元素。编程思路:(1)若有inta[10]*p;,则可用如下语句组输人10个整数并存人数组a中。//a等价于&a[0]scanf("%d",a);//a+1等价于&a[1]scanf("gd",a+l);...scanf("%d",a+9);//a+9等价于&a[9]可以归纳为如下的单重循环:for(p=aip=aip--)printf("gd",*p);完整的源程序:#includeint main(void){int a[10],*p;for(p=aip=aip--)printf("sd",*p)return 0;在这些程序中利用指针来间接引用数组的元素有什么优势吗?其实并没有。指针引用数组元素的优势主要体现在后面几章的字符串处理和跨函数间接引用等方面。两点说明:(1)数组和指针变量是不是可以完全互换呢?当然不是。其实,数组名是指针常量,C而非指针变量,因为它始终指向数组的0号元素。语例如,若有inta[10],*p;p=a;,则a为指针常量,p为指针变量。故p=p+1正确,而a=a+1言是错误的(不能对指针常量赋值)。程(2)一个指针可以与整型数据相加减:两个相同类型的指针(通常是指向同一个数组序中不同元素的指针)可以相减、相比较,但不能相加。o例如:计新inta[10],*p,*gi思p=&a[0];路q=&a[3];106

则q-p的结果为3,但是p+q是没有意义的。由于标量类型变量的内存空间是随机分配的,并不像数组元素那样连续且有序分配,所以两个标量类型变量的指针之差并不是一个固定值。例如:intx,y,*p,*g;p=&x;q=&y;则q-p的结果不是一个固定值。7.4拓展:指针与二维数组7.4.1指向二维数组元素和行的指针1.用行数组名引用二维数组的元素在C语言中,二维数组中的一行可以看作一个一维数组。例如,若有二维数组inta[3]【4],则其第i行的所有元素a[i[0]、a[i[]]、a[i[2]和a[[3]可以看作一个一维数组,而a[i就是其数组名(此为关于二维数组的辩证法之一)。由此可以得出如下几条推论:(1)既然a[是第i行的数组名,那么a[i]就是第i行0号元素a[[O]的地址。(2)a[i]+j就是数组元素a[j[j]的地址。(3)从而*(a[i]+j)也就是数组元素a[i[i]。【例7.8】编写程序定义一个3行4列的二维数组a,然后输人12个整数存人数组a中,最后分行输出数组a中所有元素的值。要求使用二维数组中各行的数组名引用数组的元素。源程序:#includeint main(void)(int a[3][4],i,j;for(i=0;i<3;i++)forj=0;J<4;j++)scanf("sd",a[i]+j);//等价于scanf("%d",&a[i][jl】;for(i=0;i<3;i++)(for(j=0;j<4:j++)printf("%6d",*(a[i]+j);//等价于printf("%6d",a[i][j]);printf("In"):第7return 0;章2.用二维数组名引用二维数组的元素指从另一个角度来说,若将二维数组中的一行看作一个数组元素,则整个二维数组a将变成一个只有三个元素的一维数组,这三个元素分别是a[0]、a[1]和a[2](此为关于二维数针107

组的辩证法之二)。由此可以得出如下几条推论:(1)该一维数组的数组名a,就是其0号元素a[0](二维数组的第0行)的地址。(2)而a+i就是元素a[i(二维数组的第i行)的地址,从而*(a+i)等价于a[i]。(3)因为a[]是第i行0号元素a[i[0]的地址,所以*(a+i)也是第i行0号元素a[i[0]的地址。(4)而*(a+i)+j就是数组元素a[ij[]的地址。(5)从而*(*(a+i)+j)也就是数组元素a[i[i]。“““”上述指针对应关系如图7.6所示。(a+i)+1(a+i)+2 (a+i)+318+-两点说明:(1)这里的a和a+i都是指向二维数组中某一行的a[0][|a[0][] a [0][2]a[0][3]指针,称为行指针。将行指针的值加1则指向下一行。a [1][0]a [[] [1][2]a [1[3]a+14(2)这里的a[i]、a[}+j、*(a+i)和*(a+i)+j都a [2][0]a [2][] [2][2] a [2][3]一a+2是指向二维数组中某个元素的指针,称为元素指针。图7.6二维数组的指针将元素指针的值加1则指向下一个元素。【例7.9】编写程序定义一个3行4列的二维数组a,然后输人12个整数存人数组a中,最后分行输出数组a中所有元素的值。要求使用二维数组名引用数组的元素。源程序:#include int main (void){inta[3][4],i,jfor(i=0;i<3;i++)for(j=0;j<4;j++)//等价于scanf("%d",&a[i][j]);scanf("&d",*(a+i)+j);for(i=0;i<3;i++)(for(j=0;j<4:j++)printf("%6d",*(*(a+i)+j));//等价于printf("%6d",a[i][j]);printf("\n");1return 0;17.4.2行指针变量行指针变量是用于存储二维数组中行地址的变量。行指针也被称为指向一维数组的指C针,但是它通常用于指向二维数组中的一行,而极少用于指向某个单独的一维数组,因此语称其为行指针更贴切。定义行指针变量的一般形式为言类型说明符(*变量名)【行长度】;程其中,“类型说明符”是二维数组中元素的类型;“行长度”表示一行的长度,也就是二维序设数组的列数。计例如:新int(*p)【4];思该语句定义了一个行指针变量p,该指针变量只能用于指向每行有4列int型元素的二路108