第 3章 指针本章介绍C语言程序设计的基本方法和基本的程序语句。使读者对C程序有一个初步的认识,为后面各章的学习打下基础。
5.1 指针的概念
5.2 指针变量的定义与引用
5.3 指针变量的运算
5.4 指针与数组
5.5指针与字符串
5.6 指针数组与指向指针的指针
5.1 指针的概念
5.1.1 变量的地址与变量的内容不同类型的数据所占用的内存单元个数不等,如实型量占
4个单元,整型量占 2个单元,字符型量占 1个单元等 。 为了方便对这些内存单元访问,每一个内存单元都有一个编号,根据内存单元的编号即可准确地找到该内存单元,这个编号就是内存单元的地址 。 内存单元中存放的数据即为内存单元中的内容 。
在程序中定义变量时,系统会自动按变量的类型为其分配一定长度的内存单元 。
假设在程序中有如下语句:
int x,y,z;
x = 58
y = 32
z = x + y
5.1.2 直接访问与间接访问所谓直接访问是指按变量名或地址来存取变量的方式 。
例如,对上例中的语句 z = x + y;它的执行是先从变量 x的地址 3000开始的两个单元中取出 x的内容,再从变量 y的地址 3002开始的两个单元中取出 y的内容,将它们相加后再送到变量 z的地址
3004开始的两个单元中 。
既然根据内存单元的地址就可以找到所需的内存单元,所以通常把地址形象地称为指针 。 可以通过指针存取该单元中的数据 。
所谓间接访问是指用存储在一个变量中的地址去访问这个地址所指示的内存单元的方式 。
在计算机中,地址实际上也是数据 ( 如 3001,
3002等 ),所以地址也可以作为存储单元的内容存放在一个变量中,用来存放指针数据的变量叫做指针变量 。 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针 。
例如图 5-1中,变量 x的内容为 58,占用了从地址 3000开始的两个连续存储单元 。 假设又定义了一个变量 P,假若 P的值为 3000,也就是说,P的内容为 x的地址 。 如图 5-2所示 。
我们专门定义了一个变量 p用来存放另一个变量的地址,以后如果我们要访问变量 x的内容,可以不通过 x直接访问,而是通过通过变量 p,用变量 p
内的地址间接地去访问变量 x,这种访问方式就称为间接访问 。
5.1.3 指针与指针变量综前所述,对内存单元的访问是通过地址来实现的 。
在 C语言中把地址形象地称为指针 。 变量对应的地址称为变量的指针 。 例如,在图 5-1中,变量 x的指针为 3000,变量 y的指针为 3002。
严格地说,指针为地址,是常量 。 而指针变量却可以被赋予不同的指针值,是变量 。 但常把指针变量简称为指针 。 为了避免混淆,我们中约定:,指针,
是指地址,是常量,,指针变量,是指取值为地址的变量 。
定义指针的目的是为了通过指针去访问内存单元 。
5.2 指针变量的定义与引用
5.2.1指针变量的定义指针变量定义的一般形式为:
类型名 *指针变量名其中,类型名,表示指针变量所指向的变量的类型,,*” 表示定义的是指针变量 。
例如,int *p1;
注意,一个指针变量只能指向同一类型的数据 。
指针变量同普通变量一样,具有变量类型,变量名,
变量值三要素,但是,指针变量存放的是所指向的某个变量的地址值,并且只能是类型说明所规定的变量类型的地址 。 而普通变量存放的是该变量本身的值 。
5.2.2 指针变量的引用
1.指针变量的初始化指针变量中只能存放地址,不能将一个整型量 ( 或任何其它非地址类型的数据 ) 赋给一个指针变量 。 为此,C语言提供了取地址运算符,&”,它的作用是取变量所占用的内存单元的首地址 。 其一般形式为:
&变量名;
如 &x表示取变量 x的地址,&y表示取变量 y的地址 。
例如,以下定义表示将指针变量 p初始化为变量 x的地址 。 参考图 5-3。
int x;
int *p = &x;
x = 100;
注意,
( 1) 变量 x的类型必须和指针变量 p的类型一致,例如以下定义是非法的 。
float y;
int *p = &y;
因为 p为整型指针变量,y为实型变量,类型不一致 。
( 2)不允许把一个指针变量初始化为一个常数,下面的对指针变量 p初始化也是非法的。
int *p = 1000;
以上是在定义指针变量的同时对其进行了初始化,也可以先定义指针变量,然后再通过赋值的方式对其初始化,例如:
int x;
int *p;
p= &x;
注意,被赋值的指针变量前不能再加,*” 说明符,如写为 *p = &x;是非法的 。
还可以将指针变量初始化为空值 NULL。 例如:
p = NULL;
这时 p初始化为一个空值,表示它不指向任何内存单元 。
2.使用指针访问变量
C语言提供了指针运算符,*” ( 也称间接访问运算符 ),当,*” 作为运算符在表达式中出现,
作用于指针变量上时,用来表示指针变量所指的变量,含义是取出指针变量地址中存放的内容 。
需要注意的是指针运算符,*” 和指针变量说明中的指针类型名中的,*” 不是一回事 。 在指针变量说明中,,*” 是类型名,表示其后的变量是指针类型 。 而表达式中出现的,*” 则是一个指针运算符用以表示指针变量所指的变量 。
例如,假设有以下定义:
int i=200,x;
int *ip;
ip = &i;
执行语句 ip = &i; 此时指针变量 ip指向整型变量 i,以后便可以通过指针变量 ip间接访问变量 i了,例如执行语句,
x = *ip;
因为运算符,*” 访问以 ip为地址的存贮区域,而 ip中存放的是变量 i的地址,因此,
*ip表示访问变量 i的地址所占用的存贮区域,所以上面的赋值表达式等价于 x
= i; 所以 x的值为 200。。
【 例 5.1】 通过指针访问整型变量。
【 例 5.2】 求两个数的和、差与积。
【 例 5.3】 输入三个数,求最大和最小值 。
5.3 指针变量的运算
5.3.1 指针变量的赋值运算指针变量的赋值运算有以下几种形式 。
( 1) 指针变量初始化赋值,前面已作介绍 。
( 2) 把一个变量的地址赋予指向相同数据类型的指针变量 。
( 3) 把一个指针变量的值赋予指向相同类型变量的另一个指针变量 。
由于 pa,pb均为指向整型变量的指针变量,因此可以相互赋值 。
( 4) 把数组的首地址赋予指向数组的指针变量 。
( 5) 把字符串的首地址赋予指向字符类型的指针变量 。
( 6) 把函数的入口地址赋予指向函数的指针变量 。
【 例 5.4】 利用指针赋值运算,实现输入 a和 b两个整数,
按先大后小的顺序输出 。
5.3.2 指针变量的算术运算指针变量的算术运算主要有三种,指针变量与整数的加减运算,指针变量的自加自减运算和两个指针变量之间的相减运算 。
( 1) 指针变量与整数的加减运算允许对指针变量加上或减去一个整数 n 。
( 2) 指针变量的自加自减运算指针变量的自加自减运算和指针变量与整数的加减运算本质上是一样的 。
再次强调,指针变量向前或向后移动一个位置和地址加 1或减 1在概念上是不同的 。 指针变量加 1表示指针变量指向下一个数据元素的首地址 。 而不是在原地址基础上加 1。
指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的 。 对数组指针变量的加减运算在后面将详细介绍 。
( 3) 两个指针变量之间的相减运算只有指向同一数组的两个指针变量之间才能进行相减运算,否则运算毫无意义 。
两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数 。
注意:两个指针变量不能进行加法运算 。 例如,pf1+pf2毫无实际意义 。
5.3.3 指针变量的关系运算两指针变量进行关系运算只有在指向同一数组的两指针变量之间进行才有意义 。 表示它们所指数组元素之间的关系 。
例如:
p1==p2表示 p1和 p2指向同一数组元素;
p1>p2表示 p1处于高地址位置;
p1<p2表示 pf2处于低地址位置 。
指针变量还可以与 0比较 。
设 p为指针变量,则 p==0表明 p是空指针,它不指向任何变量;
p!=0表示 p不是空指针 。
空指针是由对指针变量赋予 0值而得到的 。
5.4 指针与数组
1.数组指针变量的定义在 C语言中,数组名代表数组的首地址,也就是连续存储区域的第 0号元素的地址 。
例如,对以下定义:
例如:
int a[10];
int *p;
则下面两个数组指针变量定义的语句是等价的 。
P = &a[0];
P = a;
注意,因为数组为 int型,所以指针变量也应为指向 int型的指针变量 。
也可以在定义指针变量时赋初值,例如:
int *p = &a[0];

int *p = a;
这时,p,a,&a[0]均指向同一单元,它们是数组 a的首地址,也是 0 号元素 a[0]的首地址 。 应该说明的是 p是变量,而 a,&a[0]都是常量 。 使用时应予以注意 。
2.通过指针引用数组元素在 C语言中,指针变量和数组有密切的关系,任何由数组下标完成的操作均可由指针来实现 。
根据 C语言规定,如果指针变量 p已指向数组的某一个元素,则 p + 1指向同一数组中的下一个元素,即 p + 1实际使指针移动一个数组元素所占存储单元数,利用这一特性,便可以处理整个数组了 。 例如,对以下定义:
int a[10],*p = a;
如果 p的初值为 &a[0],则存取数组中的第 0个元素 ( 下标为 0) 有以下几种方法:
1) a[0] 一般的下标存取方法;
2) *a a指向数组的首地址,*a即为取该地址处的内容;
3) *p 因有 p = a,*p即为取该地址处的内容;
若要存取下标为 i的数组元素,有以下几种方法:
1) a[i] 一般的下标存取方法;
2) *(a+i) a + i就是 a[i]的地址,取该内存单元的值;
3) *(p+i) p + i也是 a[i]的地址,取该内存单元的值;
因此,引用一个数组元素时,即可以用一般的下标法,也可以用指针法,即采用 *(a+i)或 *(p+i)
形式,用间接访问的方法来访问数组元素 。
数组和指针的关系见图 5-7所示 。
5.3.3指针与二维数组
1.二维数组的地址
C语言允许把一个二维数组分解为多个一维数组来处理,
并且C语言中规定二维数组在内存中按行存储 。 设有以下定义:
int a[3][4];
可以将 a看成是由 3个元素 a[0],a[1],a[2]构成的一维数组,每个元素又是长度为 4的整型一维数组 。 例如 a[0]
数组,含有 a[0][0],a[0][1],a[0][2],a[0][3]四个元素 。 如图 5-8 所示 。
二维数组的元素也是变量,也有自己的地址,如元素
a[i][j]的地址可表示为 &a[i][j]。
和一维数组名一样,二维数组名 a代表整个二维数组的首地址,也是二维数组 0行的首地址 。 而 a[0],a[1]和 a[2]
或 a,a+1,a+2分别代表每个一维数组的首地址,即二维数组每行的首地址 。 *a,*( a+1),*( a+2) 分别为 a的第 0个,第 1个和第 2个元素,等价于 a[0],a[1]和 a[2]。
因此,a,a[0],*(a+0),*a,&a[0][0]是等同的,
都是二维数组 0行的首地址 。
a+1,a[1],*(a+1),&a[1][0]是等同的,都是二维数组 1行的首地址 。
a+i,a[i],*(a+i),&a[i][0]是等同的,都是二维数组 i行的首地址 。
注意,二维数组名也是一个地址常量,不能被修改或赋值 。
对于二维数组 a中第 i行,第 j列的元素 a[i][j]的地址可表示为:
a[i]+j或 *(a+i)+j;
又因为二维数组在内存中按行顺序存放,元素 a[i][j]之前已经存放了 i行 ( 0到 i-1) 的元素,而在第 i行中,元素之前共 j(0到 j-1)个元素,显然,对于矩阵 a[m][n]( m
行 n列 ),元素 a[i][j]之前共有 i*n+j个元素,所以 a[i][j]
的地址还可表示为:
&a[0][0]+i*n+j或 a[0]+i*n+j。
由上述可知,二维数组任一元素元素 a[i][j]的地址有 5
种表示方法:
&a[i][j]; a[i]+j; *(a+i)+j; &a[0][0]+i*n+j;
a[0]+i*n+j;
二维数组中的任一元素元素 a[i][j]也有 5种表示方法
a[i][j]; *(a[i]+j);* ( * (a+i)+j ) ;(*(a+i))[j];
*(&a[0][0]+i*n+j);
2,指向二维维数组元素的指针变量若有定义:
int a[3][4],*pa=&a[0][0];
则 pa是整型指针变量,其初值指向整型数组元素
a[0][0],*pa的值就是 a[0][0],可以通过 pa引用元素 a[0][0]。
此时若执行,pa++;则 pa指向 a[0][0]的后继元素 a[0][1]。
由于数组在内存中是按行序存储的,因此,可以通过对指针变量 pa进行加减运算来达到访问任意数组元素的目的 。
3,指向二维维数组的行指针变量所谓行指针变量指的是用来存放,行,地址的变量,即指向一维数组的指针变量 。
行指针变量说明的一般形式为:
类型名 ( *指针变量名 ) [数组长度 ];
其中,类型名,为所指数组的数据类型 。,*”
表示其后的变量是指针类型 。,长度,表示二维数组分解为多个一维数组时,一维数组的长度,
也就是二维数组的列数 。 应注意,(*指针变量名 )”两边的括号不可少,否则意义就完全不同了 。
5.5指针与字符串
1,指向字符串的指针变量不用定义字符数组,用指向字符串的指针变量也可引用一个字符串 。 例如:
char *str = "I love China! "
该说明定义了一个指向字符串的指针变量 str,系统在内存分配了一个字符数组来存放字符串 "I love China! ",并将该串的首地址赋给了字符指针 str。 因此 str指向该字符数组的第一个元素 "I"。
注意,字符串的指针变量仅指向字符串的首地址,而不是将整个字符串赋值给指针变量 。
字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的 。 只能按对指针变量的赋值不同来区别 。 指向字符变量的指针变量应赋予该字符变量的地址 。
2,使用字符串指针变量与字符数组的区别用字符数组和字符指针变量都可实现字符串的存储和运算 。 但是两者是有区别的 。 在使用时应注意以下几个问题:
1.字符串指针变量本身是一个变量,用于存放字符串的首地址 。 而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以
‘ \0’作为串的结束标志 。 字符数组是由若干个数组元素组成的,用来存放整个字符串的内容 。
2.对字符串指针方式而只能对字符数组的各元素逐个赋值 。
5.6 指针数组与指向指针的指针
5.6.1指针数组指针数组是指元素为指针类型数据的数组,即数组的每一个元素是一个指针变量 。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量 。
指针数组定义的一般形式为:
类型名 *数组名 [数组长度 ]
其中,类型名,为指针值所指向的变量的类型 。
例如:
int *p[3];
二维数组的行指针变量是单个的变量,其一般形式中,(*
指针变量名 )”两边的括号不可少 。 而指针数组类型表示的是多个指针 (一组有序指针 ),在一般形式中,*指针数组名,两边不能有括号 。
指向指针的指针变量的定义形式如为:
类型名 **指针变量名;
例如:
int **p;
p前面有两个,*” 号,相当于,*(*p)”。 显然,*p”是指针变量的定义形式,如果没有最前面的,*”,那就是定义了一个指向整型数据的指针变量 。 现在它前面又有一个,*” 号,表示 p是指向一个整型指针型变量的指针变量 。,*p”就是 p所指向的另一个指针变量 。
若有:
int **pp,*p,t;
p=&t;
pp=&p;
则 t=10;等价于 *p=10;等价于 **pp=10;
通常指针数组与指向指针的指针变量是配合使用的 。