《好学的C++程序设计》——2.3 循址访问是怎样的

  1. 云栖社区>
  2. 博客>
  3. 正文

《好学的C++程序设计》——2.3 循址访问是怎样的

异步社区 2017-05-02 09:21:00 浏览1286
展开阅读全文

本节书摘来自异步社区出版社《好学的C++程序设计》一书中的第2章,第2.3节,作者: 张祖浩 , 沈天晴,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.3 循址访问是怎样的

好学的C++程序设计

2.3.1 “牧童遥指杏花村”的启发

变量存储空间地址的指向
古诗云:“借问酒家何处有?牧童遥指杏花村。”路人向牧童请问酒家的地址,牧童感到光口头表述酒家地址不够清楚,要用一个手指指向酒家,就清楚了。并且路人由此可知,只要访问牧童所指,就能访问到所指杏花村酒楼的美酒佳肴了。

图2.4中,“2012是变量a的存储空间地址。”这个关系是用语言或文字来表述的。那么,在图形上如何来表述这种关系呢?受牧童启发,简单得很!从地址2012画一个箭头指向变量a就行了,如图2.5所示。这种图形让人一眼就看明白:地址2012指向变量a。C++约定好:这表明2012是变量a的地址,这当然也就是变量a存储空间的地址。

image

这样,变量地址就有了指向,是哪个变量的地址,它就指向哪个变量。说到底,是哪个存储空间的地址,它就指向哪个存储空间。由此,指向存储空间的地址就获得了一个名称,叫做“指针”。说白了,指针就是变量地址,就是变量存储空间地址,实际就是变量存储空间首字节地址。这些地址不仅有指向,而且有了与所指变量存储空间同样的类型。

地址运算符 &
变量地址已如上所述。那么,在程序中如何能从变量之名获得该变量之地址呢?这就要靠地址运算符 &。在这里,符号 & 作为地址运算符使用。

我们将变量名(例如a)置于地址运算符 & 的右侧得 &a,则 &a就是变量a的地址。同理,&b就是变量b的地址。对图2.4中情况而言,&a 就是地址2012,&b 就是地址2016。

要注意,& 用在不同的场合有不同的含义。在这里,不是在声明中,& 的右侧是一个变量,这时,& 是作为地址运算符使用。不要和别名声明中的 & 混淆。

2.3.2 指针变量概念

指针变量
以上讲的变量的地址,例如图2.5中的地址2012,它是变量a地址,也就是变量a存储空间的地址。这个地址之值还没有存储起来。如果要存储,当然还要另配给一个存储空间来存储它。

若某变量(比如p),系统为其配给的存储空间专门用来存放某另一变量(比如a)的地址,则此变量(p)就叫做指针变量,也可简称为指针。

上述地址2012就可用指针变量存储空间进行存储,只要将该地址赋给该指针变量就行。

例如,图2.6(a)中,右框表示某变量a的存储空间,地址为 &a。具体说,&a就是2012。左框表示指针变量p的存储空间,若将a的地址 &a作为指针值,赋给p,语句为:

p=&a;

则指针变量p的存储空间中就存储了变量a的地址 &a了,p的值就是变量a的地址2012。这时我们就说,变量p是变量a的指针,变量p指向了变量a。或者说,a是指针变量p所指的变量,如图2.6(A)中箭头所示。变量a和变量p之间的关系也可简示如图2.6(b)。

指针变量的值是可以变化的。如果指针p之值不是变量a的地址&a了,而改变为变量b的地址&b。则指针就不指向a,而改为指向b了。例如,看图2.7(a),经赋值语句p=&a;q=&b;使指针p指向了变量a,指针q指向了变量b。如果重作赋值p=&b;q=&a;使两个指针之值作了互换,则其指向也就互换了,如图2.7(b)所示。
image

以上讲的变量a、b都是int型变量,储存其地址的变量p、q是int型指针变量。

指针变量的类型
我们知道,指针变量的值总是某个存储空间地址。所指存储空间是有类型的。我们把指针所指存储空间的类型定义为指针变量的类型。这样一来就可以知道,float型指针是指向float型存储空间的;char型指针是指向char型存储空间的。指针是什么类型,所指存储空间就是什么类型。反过来说,所指存储空间是什么类型,指针就是什么类型。反正指针(地址)的类型和所指存储空间的类型是一致的。

例如,int型变量i的地址 &i就是int型指针,char型变量c的地址&c就是char型指针。变量地址的类型和变量存储空间的类型是一致的,当然也和变量类型是一致的。

总体来说,变量、变量存储空间、存储空间地址、存储空间首字节地址以及存放存储空间地址的指针变量,都有类型,变量是什么类型,它们就全都是什么类型。它们的类型和变量类型完全一致。

单纯的一个内存单元地址,它不指向某个变量(或存储空间)谈不上什么类型。没有类型的指针叫void型指针(如以vp表示)。任何其他类型的指针都可直接对vp进行赋值,赋值只是将一个地址值赋给了它,它仍然是void型指针。反过来,若void型指针vp想对别的指针进行赋值,则必须先将vp进行强迫类型转换为相同类型后,然后才可进行赋值。

2.3.3 指针变量怎样声明和赋值

指针变量初次出场也要作声明,表明指针变量属何类型。

指针变量声明过后,系统就配给它存储空间,用以存储指针变量之值。不管指针是何类型,指针值总是一个8位十六进制的地址值。用4个字节大小的存储空间存储地址就行。

但指针所指存储空间的大小,则随指针类型的不同而有所不同。比如,char型指针所指char型存储空间大小为1个字节,而double型指针所指double型存储空间大小则为8个字节(见表2-1)。

对于图2.7中的变量a和指针变量p,假设变量a是int类型,可作如下声明:

int a;
int *p=&a;

第一条语句是对int型变量a的声明,大家是熟悉的。

第二条语句是对指针变量p作的声明。在这个声明中,“int”和“*”一起作为一个整体,描述了p是一个int型指针变量。接着的“=”是用int型变量a的地址&a,给p初始化。记住!记住:谁的地址给指针变量初始化,指针变量就指向谁。这样,指针p的初值就是&a,指针p指向了变量a,如图2.8所示。
image

当然,不在声明中进行初始化,而在声明过后另写一个赋值语句也是可以的。例如:

int a;
int *p;
p=&a;

头两句对变量a和指针变量p作了声明。末句对指针p赋以 &a,使p指向了a。记住!记住:谁的地址给指针变量初始化(或赋值),指针变量就指向谁。

上述3条语句合并写成如下一条语句也行:

int a, *p=&a;

这里,虽然“int”和“”不紧靠在一起,但仍在同一个声明语句中,它俩仍然作为一个整体,描述了p是一个int型指针变量。接着的“=”是对p进行初始化,将变量a的地址 &a作为指针变量p的初值。这里,如果脱离整体考虑,而认为把 &a 赋给了 p ,那就错啦。

上述声明过后,我们可以称p为“int型指针变量”,也可以称p为“int 型变量”。其中“int ”作为一个整体,表明了变量p是一个int 型指针。

指针声明的一般形式为:

数据类型 *变量名;

同类型的指针,相互间可以赋值,例如:

int a, *p, *q;  //A
p=&a;         //B
q=p;         //C

A行语句中,声明了两个int型指针变量p和q。B行语句将变量a的地址&a赋给了指针变量p,使p指向了a。C行语句对同类型指针变量q赋以p值,使q值也等于a的地址,因而也指向了a。p和q都指向同一个变量a,如图2.9所示。

image

Void型指针vp的声明语句形式是:void *vp;

要注意,“”用在不同的场合有不同的含义。在这里,“”用在变量的声明中,它表示这是一个指针变量。

2.3.4 用指针所指对所指变量进行访问

我们知道,所谓对变量进行访问,就是对变量的存储空间进行访问,就是对变量的存储空间进行写值或读值。

与直呼其名的访问不同。用指针进行访问是循址访问。循址访问不涉及变量之名,而是对指针(地址)所指存储空间的内容进行访问。

那么,怎样才能由指针来获得指针所指存储空间的内容呢?这就要靠所指运算符 *。

所指运算符 *
在这里,运算符 作为所指运算符使用。我们将指针名(比如p)置于所指运算符 的右侧,得 p,则p就是指针p所指存储空间的内容。同理,指针q所指存储空间内容就是 *q。

要注意,“”用在不同的场合有不同的含义。在这里,不是在声明中,“”的右侧是一个指针p,这p就表示指针p所指存储空间的内容。“”作为所指运算符使用。

指针所指和所指变量
对于指针所指存储空间的内容,本书简称之为指针所指。例如p就是指针p所指存储空间的内容,简称 p为指针p的所指。

如果将变量a的地址 &a赋给指针p,即:

p=&a

则p就指向了变量a,这时,p所指存储空间的内容就是变量a。指针所指*p和所指变量a二者同是p所指存储空间的内容,二者是一回事,故得下列等价式:

公式3

由此可得结论:如果指针p指向变量a,则访问指针所指 *p,就是访问所指变量a。记住!记住!

快餐循址派送
张三想吃快餐,只要告知张三家住宅地址。大堂经理就启动送餐程序,送餐员就出马给地址所指赋送快餐。送餐程序之内没有客户尊姓大名,送餐员只管埋头按地址所指送餐。给地址所指的住宅空间赋送了快餐,实际就是给程序之外的张三赋送了快餐。真是:

对指针所指送餐于规程之内, 实际就是 对所指张三送餐于规程之外。

这里的关键就在于,必须将张三住宅地址赋入送餐员本子,使送餐员本子中的地址(指针)指向张三。这样,就能实现访问指针所指就是访问所指张三。给指针所指送餐就是给所指张三送餐。

用指针所指对所指变量进行访问例
【例2-3】参照【例2-2】,说明访问指针所指就是访问所指变量。程序如下:

#include<iostream>
using namespace std;
int main()
{
 int a=20,b=200,c=2000;                     //A
  cout<<"a="<<a<<'\t'<<"b="<<b<<'\t'<<"c="<<c<<'\n';      //B
  int *p1=&a, *p2=&b, *p3=&c;                 //C
  { *p1=*p1+8000; *p2=*p2+5000; *p3=*p3+2000; }       //D
  cout<<"a="<<a<<'\t'<<"b="<<b<<'\t'<<"c="<<c<<'\n';      //E
  return 0;
}

程序运行结果为:

a=20    b=200   c=2000                  //F
a=8020  b=5200   c=4000                  //G

此例不妨与【例2-2】对照来看。程序中,A行声明了3个int型变量a、b和c,并都有了初值。B行输出a、b和c,输出结果如F行所示。

在C行声明了3个int型指针变量p1、p2和p3,并且分别用变量a、b和c的地址给予初始化。这样,就使指针p1、p2和p3分别指向了变量a、b和c。

在D行的一对花括弧内,对指针所指 p1、p2和 *p3都分别作了增值赋值,即分别取三者的原值加上一定的值后,再重新分别赋给三者。

E行又输出a、b和c,输出结果如G行所示。

奇怪!从F和G行的输出结果相比来看,a、b和c的值都变大了。可是,从整个程序来看,并未对a、b和c进行增值赋值呀!何以会变大呢?

关键在于C行的语句,用a、b和c的地址分别对3个指针p1、p2和p3进行了初始化。谁的地址给指针变量初始化(或赋值),指针变量就指向谁。这就使3个指针分别指向了变量a、b和c。记得吗?如果指针指向了某变量,则访问指针所指就是访问所指变量。在D行,虽然是在花括弧内,对指针所指p1、p2和*p3进行增值赋值,但是实际就是在花括弧外,对所指变量a、b和c进行增值赋值。具体来说,就是有如下的关系:

(花括弧内)*p1=*p1+8000;  实际就是  a=a+8000;(花括弧外)
      *p2=*p2+5000;  实际就是  b=b+5000;
      *p3=*p3+2000;  实际就是  c=c+2000;

以后会看到,D行花括弧内的程序可以让一个函数来完成。到那时,可以看到如下情景:

访问指针所指于函数之内   实际就是   访问所指变量于函数之外

这恰似按送餐规程内的程序对指针所指进行送餐,实际就是规程之外的张三获得快餐。在这里,先提前让大家领会一下,指针在后面内容中将有精彩表现。

某指针指向某变量的两个等价式
综上所述可知,如果在图形中出现指针的指向关系:T图片 14H。则可获得下列两个等价式,这两个等价式在后续内容中常用到:

(1)T图片 15&H; (2)*T图片 16H。

(1)式表明T的值就是变量H的地址,指针T指向了变量H;

(2)式表明指针所指T就是所指变量H。访问指针所指T就是访问所指变量H。

随机值指针是危险分子
若声明了一个指针变量p,并未对它进行初始化或赋值,则p值(地址值)将是一个随机数,这样的指针叫做随机值指针。随机值指针说不定是指向哪个存储空间。所指存储空间的内容也说不定是什么内容。若所指存储空间内容是很重要的数据,这时冒然给指针所指 *p赋值,就将使该重要数据丢失,可能造成重大故障。因此说,随机值指针是一个危险分子。

若一时不知用哪个变量的地址对指针进行初始化或赋值为好,这时可暂时先用一个空值(NULL或0)进行初始化或赋值,以代替指针存储空间里的随机数。例如:

int *p=NULL;

或:`javascript
int *p=0;


NULL的ASCII码为0,故NULL与0通用。指针赋空值后,表示该指针未指向任何变量,因而无访问功能。若要恢复访问功能,只需赋以某变量的地址,让指针指向某变量就行。

###2.3.5 基本类型变量的指针
二级指针变量
设有一个基本类型变量a,变量u指向a、变量x指向u,如图2.10所示。显然,u是a的指针;x是u的指针,x是基本类型变量a的指针的指针。我们就说这种情况,x是u的一级指针,是a的二级指针;或者说,x一级指向变量u,二级指向变量a。

![image](https://yqfile.alicdn.com/5f55aa0e6b871e5060f7364d52b06aaf20920007.png)

图2.10 二级指针变量x的示意图

由指向关系u图片 17a,可得等价式:       *u图片 18a     //①

由指向关系x图片 19u,可得等价式:        *x图片 20u     //②

将②式中的u代入①式中,可得:* (*x)图片 21a,即  **x图片 22a     //③

①和②两式表明,u的一级所指就是a,x的一级所指就是u,这些大家熟悉,便不多谈。

③式表明,x所指 *x的所指 * (*x)就是a。我们把x所指的所指* (*x)表示为 **x,叫做x的二级所指。③式表明,x的二级所指 **x就是x的二级所指变量a。实际上,这二者同是x二级所指存储空间的内容,如图2.10所示。访问 **x和访问a实际是一回事。

由此得出结论:如果指针x二级指向变量a,则访问x的二级所指 **x就是访问x的二级所指变量a。

二级指针变量怎样声明和赋值
拿图2.10中的情况来说,设a为double型变量,则应作如下声明:

double a;
double *u=&a;
double **x=&u;

头两条语句大家已熟悉,不多谈。第三条语句对变量x作了声明。在此声明中,double和 **一起作为一个整体,描述了x是一个double型二级指针变量。接着的“=”是用 &u对x进行初始化。注意,对二级指针x进行初始化的是指针变量u的地址&u。

当然,不在声明中初始化,而在声明过后另写语句赋初值也是可以的。例如:

double a;
double *u;
double **x;
u=&a;
x=&u;

五条语句合并写成如下一条语句也行:

double a, u=&a, *x=&u;

这里,虽然double和 ** 不紧靠在一起,但仍在同一个声明中,它俩仍然作为一个整体,描述了x是一个double型二级指针变量。接着的“=”是用 &u作为x的初值。这里,如果脱离整体考虑,而认为是用 &u 对 **x赋值,那就错啦!

二级指针变量声明的一般形式如下:

数据类型 **变量名;

用二级指针所指对变量进行访问
由图2.10可知,用二级指针x所指对变量访问可根据上述②式和③式分为两点:

(1)由②式可知: 访问指针x的一级所指 *x,就是访问x的一级所指变量u。

(2)由③式可知: 访问指针x的二级所指**x,就是访问x的二级所指变量a。

这里第(1)点,实际就是前面所讲的“访问x指针所指*x就是访问x所指变量u”,这大家熟悉,不多谈。现举例说明第(2)点,用二级指针所指 **x来访问二级所指变量a。

【例2-4】变量的情况原先如图2.11(a)所示。设计程序将变量a和变量b之值进行交换,交换后情况如图2.11(b)所示。输出前后两种情况下,a和b之值。程序如下:

include

using namespace std;
int main()
{
  char a='A',b='B',t;           //A
  char u=&a, v=&b;           //B
  char x=&u, y=&v;          //C
  cout<<"a="<  t=x; x=y; y=t;         //E
  cout<<"a="<
  return 0;
}

程序运行结果如下:

a=A b=B                //G
a=B b=A               //H

图2.11

![image](https://yqfile.alicdn.com/00414fbb0e5e08ea0c59e27c746271980ff5cd91.png)

程序中A、B和C行对各变量作了声明和设置初值,其中char型变量t是准备在交换数据时用作中介的。D行输出a和b之值。输出结果如G行所示。

程序中,E行是以t作为中介,将x和y的二级指针所指 **x和 **y之值进行交换。按上述理论的说法:交换x和y的二级所指**x和**y,就是交换x和y的二级所指变量a和b。F行输出a和b之值。输出结果见H行,a和b之值果然是作了交换。

可见,访问(交换)x和y的二级所指 **x和 **y,就是访问(交换)x和y的二级所指变量a和b。简言之,对于二级指针,访问指针二级所指,就是访问二级所指变量。

程序中,变量t应该是什么类型?拿E行语句来说,t应与 **x类型一致,**x是x的二级所指变量a,变量a是char型,因此t也应该是char型。

网友评论

登录后评论
0/500
评论
异步社区
+ 关注