2.9 变量的地址和指针型变量

2.9.1 变量的地址和指针型变量的概念

(1)内存地址

在程序中,一个变量实质上代表了“内存中的某个存储单元”,最基本的存储单位是字节,而且每个字节都有一个编号,这个编号就称为内存地址。就像旅馆的每个房间号一样,如果没有房间号,旅馆的工作人员就无法进行管理。同样道理,没有内存字节的编号,系统就无法对内存进行管理。内存的存储空间是连续的,因此内存地址也是连续的。一般内存地址用十六进制数来表示。

(2)变量的地址

若在程序中定义了一个变量,C编译系统就会根据所定义的变量类型,为其分配一定字节数的内存空间(在VC++中short int型数据占2字节,int型数据和float型数据占4字节,double型数据占8字节,char型数据占1字节,指针变量占4字节),此后这个变量的地址也就确定了。

例如:

short a;

float b;

char c;

这时系统为变量a分配两个字节的连续存储单元,为变量b分配四个字节的连续存储单元,为变量c分配一个字节的存储单元。如图2.5所示,图中的数字只是示意的字节地址。每个变量的地址就是该变量所占存储单元的第一个字节的地址。

图2.5 变量在内存中所占字节的地址示意图

在这里我们称变量a的地址为2019H,变量b的地址为1128H,变量c的地址为5050H。

一般情况下,我们在程序中只需指出变量名,无需知道每个变量在内存中的具体地址,每个变量与具体地址的联系由C编译系统来管理。程序中对变量进行存取操作,实际上就是对该变量所在的存储单元进行操作,这种直接按变量的地址存取变量值的方式称为“直接存取”方式。

(3)指针型变量

在C语言中,还可以定义一种特殊的变量,这种变量只是用来存放内存地址的。如图2.6所示,假设有一个变量p,它也有自己的地址(假设2001H)。若将变量a的地址(假设5050H)存放到变量p中,也就是说变量p的值就是5050H,这时要访问变量a所代表的存储单元,可以先找到变量p的地址(2001H),从中取出变量a的地址(5050H),然后去访问以5050H为首地址的存储单元。这种通过变量p间接得到变量a的地址,然后存取变量a的值的方式称为“间接存取”方式。这种用来存放地址的变量称为“指针型变量”,上述变量p就是指针型变量。

图2.6 存放内存地址的指针型变量示意图

以后我们会经常提到指针指向某个变量,其含义就是指针变量的值是某个变量的地址。图2.6中,我们可以说指针p指向了变量a。

2.9.2 指针型变量的定义和指针变量的基类型

定义指针变量的一般形式如下:

类型名 *指针变量名1,*指针变量名2,…;

例如:

int *p,*q;

float **r;

以上定义语句中,p和q都是用户标识符。在每个变量前的星号*是一个说明符,用来说明变量p和q是指针变量,如果省略了星号*,那么变量p和q就变成了整型变量了。p和q前面的int用来说明指针变量p和q所指向的存储单元中只能存放int型数据,这时我们称int是指针变量p和q的基类型。或者我们可以通俗地称p和q是两个整型指针,而且p和q属于一级指针(即指针变量p和q存放的是不同变量的地址)。

以上定义语句中的“float **r;”,其中r是浮点型指针变量,属于二级指针,也就是说指针变量r所指向的是一个一级指针变量,这个一级指针变量所指向的是一个float型的变量。

再如:

void *p;

其含义是:定义了一个指针型变量p,它所指的存储单元所存放的数据类型不定,称为无类型指针。

定义指针变量一定要区分基类型,因为对于不同基类型的指针变量在进行指针运算时的“跨度”是不同的,在以后的章节中会体会到。另外不同基类型的指针变量不能混合使用。

2.9.3 给指针变量赋值

 一个指针变量可以通过不同的方式获得一个确定的地址值,从而指向一个具体的对象。

(1)通过求地址运算符(&)获得地址值

单目运算符“&”用来求对象的地址,其结合性为从右到左。

  int a=3,*p;

则通过以下赋值语句:

p=&a;   /*给指针变量p赋值*/

也可以把上面的两句写成以下形式:

int a=3,*p=&a;  /*给指针变量初始化*/

注意不能写成:

int *p=&a,a;

通过上面的两种方式就把变量

a的地址赋给了指针变量p,此时称作指针变量p指向了变量a。如图2.7所示。

图2.7 指针变量p和变量a的指向关系示意图

注意以下几个方面:

①求地址运算符“&”的作用对象只能是变量或后面要讲到的数组,而不能是常量或表达式。

例如:

int *p,a;

p=&(a+1);  /*该赋值语句是错误的*/

②求地址运算符“&”的运算对象的类型必须与指针变量的基类型相同。

例如:

int *p,a;

float b;

p=&b;   /*该赋值语句是错误的*/

因为指针变量p的基类型是int型,而求地址运算符“&”作用的对象b的类型是float型。

(2)通过指针变量获得地址值

可以通过赋值运算,把一个指针变量中的地址值赋给另一个同类型的指针变量,从而使两个指针指向同一地址。

例如:

int a=3,*p=&a,*q;

q=p;

通过赋值运算q=p,使得指针变量p和q同时指向了变量a。

注意:p和q的基类型必须一致。如图2.8所示。

图2.8 指针变量p和q与变量a的关系示意图

(3)给指针变量赋“空”值

不允许给一个指针变量直接赋一个整数值。

例如:

int *p;

p=2009;   /*该赋值语句是错误的*/

但是可以给一个指针变量赋空值。

 例如:

int *p;

p=NULL;   /*该赋值语句是合法的*/

NULL是在stdio.h头文件中的预定义符,它的代码值为0,因此在使用NULL时,应在程序的前面出现预定义行:#include"stdio.h"或#include<stdio.h>。当执行了上述的赋值语句p=NULL后,称p为空指针。以上赋值语句等价于:

p='\0'; 或 p=0;

空指针的含义是:指针p并不是指向地址为0的存储单元,而是不指向任何存储单元。企图通过一个空指针去访问一个存储单元时,将会得到一个出错信息。

2.9.4 对指针变量的操作

通过上面的学习,我们已经了解关于指针变量的含义了,那么对于任何的存储单元就有两种形式来存取单元的数据了,一种是“直接存取”,另一种是“间接存取”。

所谓“直接存取”就是按变量的地址存取变量值的方式。通俗地说就是直接使用变量名来对该变量所对应的存储单元进行存取操作。

所谓“间接存取”就是通过指针变量p间接得到变量a的地址,然后存取变量a的值的方式。

C语言提供了一个称作“间接访问运算符”的单目运算符:“*”。“*”出现在程序中的不同位置,其含义是不同的。

例如:

int a=3,*p,b;  /*这里的“*”是个说明符,用来说明变量p是个指针型变量*/

p=&a;  /*通过取地址运算符&使指针变量p指向变量a,即先赋值*/

b=*p;  /*这里的“*”是代表“取数据”,即把p所指向的存储单元中的数据(3)读出来赋给变量b,等价于b=a*/

*p=5;  /*这里的“*”是代表存数据,即把一个整数5存到指针变量 p所指向的单元(也就是变量a),等价于a=5,此时a的值变为5*/

使用指针变量应注意以下几个方面:

①对指针变量的使用必须是先使指针变量有固定的指向然后才可以使用,即先赋值后使用。

例如:

int a,*p;

*p=5;        /*这种写法是错误的,因为此时指针变量p还没有固定指向,这样使用容易造成重要数据的破坏*/

②运算符“&”和“*”的优先级相同,结合性是从右到左。

例如:

int a=3,*p,**q;

p=&a;

q=&p;

a.&*p的含义是什么?由于&和*的优先级相同,按从右到左结合,等价于&(*p),*先和p结合,*p就是变量a,再执行&运算,相当于&a,即取变量a的地址。因此&*p等价于&a。

b.*&a的含义是什么?由于&和*的优先级相同,按从右到左结合,等价于*(&a),&先和a结合,即&a,取变量a的地址,再进行*运算,相当于取变量a的值。因此*&a等价于a。

c.q=&p;可以用图2.9来形象表示:

图2.9 变量q、p和a的关系

d.*、++、--的优先级是相同的,结合性为从右到左。

例如:

(*p)++      /*等价于a++*/

*p++        /*等价于*(p++)*/

++*p        /*等价于++a*/

*++p        /*等价于*(++p)*/

 【例2.15】  指针变量使用举例。

#include<stdio.h>

void main()

{ int a=9,*p=&a,**q=&p;

 printf("%d\n",a);      /*对变量的直接存取*/

 printf("%d\n",*p);      /*对变量的间接存取*/

 printf("%d\n",**q);      /*对变量的间接存取*/

}

程序运行结果:

   9

   9

   9

【例2.16】  指针变量使用举例。

#include<stdio.h>

void main()

{ int a=9,*p;

 p=&a;

  *p=*p+1;        /*等价于a=a+1*/

 printf("%d ",a);        /*对变量的直接存取*/

 printf("%d\n",*p);        /*对变量的间接存取*/

 printf("%d ",++*p);        /*对变量的间接存取*/

 printf("%d\n",(*p)++);        /*对变量的间接存取*/

 }

程序运行结果:

10 10

11 11