1
C/C ++程序设计
1.2.6.1 6.1 指针

6.1 指针

在计算机程序中使用的所有数据,都必须存储在计算机的存储单元中,并且应能从计算机的存储单元中取出。每个存储单元都有唯一的地址,这就好比街道上每家每户都会有自己的门牌号码一样。

计算机内存被划分成按顺序编号的内存单元,这样就形成了地址。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元,对变量赋值就相当于把值存储到该存储区域中。如图6-1所示。

img216

图6-1 定义整型变量的内存存储单元

不同的计算机使用不同的复杂的方式对内存进行编号,通常程序员不需要了解给定变量的具体地址,编译器会处理这些细节问题。在C语言中,只需要使用取地址运算符“&”,它就会返回对象在内存中的地址。

例如:

int num=1;

printf("num的值等于%d,num的地址是%d\n",num,&num);

输出结果:

num的值等于1,num的地址是1245052(num的地址在不同编译环境下值会不同)。

注意:“&”称为取地址运算符。

6.1.1 指针变量

指针变量是指专门用来存放地址的变量,简称为指针。它是一种特殊的变量,一般变量存放的是数据本身,而指针变量存放的是另一变量的地址。

定义指针变量形式:

指向数据类型*指针变量名;

例如:

img217

整型变量num值是1,指针变量p是指向整型变量num的指针,p存储的是num的地址,如图6-2所示。

img218

图6-2 变量和指针变量关系

还可以在定义指针变量的同时初始化,例如:

img219

注意:指针变量p的数据类型必须和它指向的变量num的数据类型相同。下面的形式是错误的:

int num;

float*p=#

【例6.1】变量、地址和指针的简单应用实例。

代码如下:

img220

img221

运行结果如图6-3所示。

img222

图6-3

分析:

num是整型变量,p是指针变量指向num,故&num的值和p的值相同,都是变量num的内存地址。*p的值等于num的值1,而&p输出的是指针变量p的地址。代码*p=2等价于num=2,故再次输出的num值和*p的值会发生改变,而&num的值、p的值和&p的值没有发生变化。

如果要访问num存储区域的数据,可以直接访问变量num实现,也可以访问指针变量p实现,方法如下:

img223

输出结果:

num的值等于2,*p的值是2。

注意:“*”称为取内容运算符。

6.1.2 传地址

函数调用时传递参数有两种方式,一种方式称为传值,另一种方式称为传地址。

传值的特点是,实参和形参分别占据不同的存储单元,调用函数时,实参赋值给形参,可以认为形参是实参的副本,形参值的改变不会影响实参值。

例如,主函数中调用fun函数,实参是整型变量a和b,如下所示:

int a,b;

fun(a,b);

函数fun的形参是整型变量x和y,fun函数头行形式如下所示:

int fun(int x,int y)

实参a和b赋值给形参x和y,它们分别占据不同的存储单元,改变x和y的值,对a和b没有影响。

传地址的特点是,调用函数时,实参把地址赋值给形参,形参引用的是实参地址中的数据。

例如,主函数中调用fun函数,实参是整型变量a和b的地址,如下所示:

int a,b;

fun(&a,&b);

函数fun的形参是整型指针变量x和y,fun函数头行形式如下所示:

int fun(int*x,int*y)

形参x和y存储的是实参a和b的地址,改变*x和*y的值,等价于改变实参a和b的值,从而实现了函数返回多个值的功能。

【例6.2】使用函数实现对输入的两个整数按从大到小的顺序输出。

代码如下:

img224

运行结果如图6-4所示。

img225

图6-4

分析:

主函数调用swap函数传递的参数是变量a和b的地址,swap函数的形参p1和p2分别指向实参a和b。

swap函数中交换的是指针p1所指向的地址中的值和指针p2所指向的地址中的值,即实参a和b的值,而不是形参p1和p2的值,所以主函数调用swap函数后输出的变量a和b的值发生了交换。

在例题的基础上,修改swap函数,代码如下所示:

img226

程序会正确执行吗,为什么呢?

与例题不同的是,例题中temp是整型变量,使用其作为中间量暂存*p1的值(即实参a的值),达到交换实参a和b的作用。而修改后的代码中,temp是整型指针,在其没有明确指向时就试图把*p1的值存储在temp所指向的存储单元中。调试修改后的swap函数无法正确执行,弹出内存不能读的错误对话框,这是因为代码*temp=*p1在把*p1的值存储在一个不存在的存储空间中。如何实现交换形参p1和p2的值,如果交换了又会产生什么结果呢?

交换形参p1和p2的值代码如下所示:

img227

主函数中调用swap函数后,变量a和b的值没有交换,和调用swap函数前的值一致,函数并没有起到交换的作用。

原因分析如下:首先定义的temp是整型指针变量(和p1、p2类型一致),交换的数据是p1和p2存储单元中的数据,即&a和&b。经过交换,p1存储的数据是&b,p2存储的数据是&a,指针变量p1和p2的指向发生了变化,此时如果输出*p1和*p2的值,结果将是b和a的值。虽然p1和p2的指向变换了,但是并没有改变实参a和b的值。

6.1.3 指针的自运算

大家已经知道了关于指针变量的运算方法了,那么指针变量自身又是怎么运算的呢?指针的运算就是地址的运算。对于这一特点,指针运算不同于普通变量,只允许有限的几种运算。除了可以对指针赋值外,指针的运算还包括移动指针、两个指针相减、指针与指针或指针与地址之间进行比较等。可以通过将指针加减一个整数,或者通过对指针赋值来移动指针。

例如:

p+n、p-n、p++、p--、++p和--p等,其中n是整数。

将指针p加上或者减去一个整数n,表示p向地址增加或减小的方向移动n个存储单元,每个存储单元的大小由指针p所指向的数据类型决定。指针p经过移动,指向一个新的地址。

如图6-5所示,变量a、b、c、d和e都是整型数据int类型,它们在内存中占据一块连续的存储区域,地址范围为1234~1253,每个变量占用4个字节。指针变量p指向变量a,也就是p的值是1234,那么按照上面的描述,p+1表示p向地址增加的方向移动了4个字节,从而指向一个新的地址,这个值就是1238,即指向了变量b。同理p+2地址就是1242,再次增加了4个字节,指向了变量c,依此类推。

img228

图6-5 存储一组整数的连续存储区域

【例6.3】指针变量自身的运算。

代码如下:

img229

运行结果如图6-6所示。

img230

图6-6

分析:

*(p1-1)表示以p1指向的地址后移1*sizeof(int)个字节后的地址为首地址,然后访问其中存储的数据;*p1-1表示的是在p1所指向的存储单元的值的基础上减1,故*(p1-1)和*p1-1结果不同。