← 学习库 C++程序设计教程 本册目录

第7章 指针

原书第 116 页

第7章 数据的指南针——指针

在 C++ 乐园中,如果要找某个数据是很容易的,因为 C++ 中有个好导游,这就是数据的指南针——指针。它对程序中存放的数据的地址了如指掌,要什么数据的地址,它就能找到什么数据的地址。快跟着这个导游浏览 C++ 乐园吧。

7.1 例题详解

7.1.1 通过指针得到单个数据

【例 7-1】有一个数据存放在某变量中,请通过指针得到该变量的数据,并将其显示以确认得到了该数据。

1. 用心分析人想通,机随人行写提纲

假设在某变量的房间里有东西(数据),按照人的做法,首先要找到这个房间,通过什么找——门牌号码,按门牌号找到这个房间后,再把房间里的东西(数据)拿出来。

2. 对号入座写文章,头脑运行改文章

上述提纲对应的 C++语言是:C++中存放数据都用房间,如图 7-1 所示的 f 房间,有房间肯定有门牌号(地址),f 房间的地址是 0012FF78,C++把这些地址专门存放在通讯录——指针(或称为指针变量)e 中,是变量就有房间,指针变量 e 也有房间,该房间里放的是地址(0012FF78),那么,就像人找房间取东西一样,通过通讯录(指针)里的地址,找到 f 房间,再把房间里的东西(数据)取出来。

程序代码如下:

Image
图 7-1 指针与所指变量之间的关系示意图
原书第 117 页
  1. C++帮助且裁判,人定胜机凯歌扬运行结果:

20

结果正确。

7.1.2 通过指针得到一组数据

【例 7-2】有一组数据存放在某数组中,请通过指针得到该组数据,并将其显示以确认得到了这组数据。

1. 用心分析人想通,机随人行写提纲

此题与例 7-1 的区别是得到一组数据,人的做法是,拿完一个东西(数据)挨着拿下一个东西。像例 7-1 一样,先通过通讯录找到数组这个大房间,再进到这个大房间里,然后通过指针的指引逐个把数据从一个个小房间里取出来。

2. 对号入座写文章,头脑运行改文章

此题的示意图与图7-2类似。

程序代码如下:

//ex7-2.cpp
#include<iostream.h>
void main()
{
    int g[5]={8};//开始唱输入这部曲,定义数组g,初始化时只赋一个值,
//则剩下的4个元素的初始值为0
int *y;

y=&g[0];//将数组第一个元素的地址赋给指针,既取到数组的地址,

//也初始化了指针,即在通讯录(指针)里登记了数组这个门牌号。
// 或用 y=g;将数组的起始地址赋给指针,既取到数组的地址,
//也初始化了指针
for(int i=0;i<5;i++)//用指针y指引,循环输出数组g的各元素值,
//这是处理和输出这两部曲
cout<*(y++)<<endl;
}
//*(y++)中有两个动作,
//其一是*y,即先输出指针y指向的数组中的当前元素值,
//(第一次循环指向数组的第一个元素8);
//其二是y++,即y加1,也就是指针向前移动到下一个单元,
//指向数组的下一个元素(第一次循环指向数组的第二个元素0)
  1. C++帮助且裁判,人定胜机凯歌扬运行结果:

8

0

0

原书第 118 页

0

0

结果正确。

7.1.3 运用指针观察分析商场销售情况

【例 7-3】有一个商场的日销售金额已存储在计算机中,是用数组来存放的,请用指针指引,把该商场的日销售金额打印出来,以利于该商场的运营及管理人员和经理观察分析商场的销售情况。

  1. 用心分析人想通,机随人行写提纲此题与例7-2相似。
  1. 对号入座写文章,头脑运行改文章

指针与所指数组元素之间的关系如图7-2所示。

Image
图 7-2 指针与所指数组元素之间的关系示意图

程序代码如下:

//ex7-3.cpp
#include<iostream.h>
void main()
{
double marray[10]={13434.45,25322.03,9824.48,12566,8205.69,27536.05,

7932.61,9368.04,1498.31,2008.11};

//对数组初始化,赋予10个日销售金额数
double *pm=marray;//将双精度数组marray的地址赋给了双精度指针变量pm,
//即取地址,也是初始化,注意两者都是double,即双精度数据类型
cout.precision(10);//设置实数的精度为10位
for(int i=0;i<10;i++)//输出数组marray的所有元素值
{
cout<<*pm<<endl;//把指针指向的数组marray的内容输出,即取内容

pm++;//指针向前移动一个数(即数组单元)

}

  1. C++帮助且裁判,人定胜机凯歌扬

运行结果:

13434.45

25322.03

9824.48

原书第 119 页

12566

8205.69

27536.05

7932.61

9368.04

1498.31

2008.11

结果正确。

7.2 勤奋实践,总结评分

7.2.1 习题
  1. 有一位顾客问商场售货员,能否告知同类商品中价格最便宜的是哪一种,售货员欣然地回答了该顾客的问题。试用指针编程实现,假设同类商品的货物单价保存在一个一维数组中。假设顾客又问最贵的商品是什么,应该怎么做(还是用指针编程实现)?
  1. 编写利用字符串指针实现身份证号码验证的程序。
  1. 编写利用指针将一个一维矩阵转置的程序。

4.参加100m短跑比赛的10个参赛队员的成绩已记录在一维数组中,请列出冠、亚、季军的成绩,试利用指针等知识编程实现。

  1. 某工厂是边生产边自动记录某合格品的规格尺寸,假设出口产品要求好中挑好,例如从20个产品中再挑出15个更好一些的产品,请使用指针编写程序来实现。
  1. 在第5题中,假设要查找等于标称尺寸的产品,该如何找?请编程实现。
  1. 商场里的货物序号及数量存放在货物清单中,并用二维数组保存此货物清单,请打印输出该货物清单。提示:将二维数组的首个元素的地址赋给一级指针,再利用二维数组在内存中的线性连续排列,通过指针编程实现。
  1. 用二维数组存放 5 个学生的学号和数学成绩,请按成绩从高分到低分排序并输出显示,然后删除某位转学的学生。请用指针编程解决该问题。
  1. 利用字符串指针对 3 个字符串按照从长到短的顺序排序。
  1. 毕业联欢晚会上,女生们准备演出一个舞蹈节目,需要挑选出身材适中的10位女生。假设身材特征的信息存放在二维数组中,请按要求挑选出10位女生。试用指针编程实现。
7.2.2 思考题

1\. 使用指针进行字符串的复制。

  1. 把一段数字字符变为整型数,再用正序和反序分别输出。
7.2.3 总结评分,继续提高

浏览、体验了例题、习题和思考题之后,说说自己的体会和收获,并认真总结,按

原书第 120 页

表7-1给自己评判一下可以得多少分。

不论得分多少,大家都要总结经验,扬长避短,勤奋努力,不断提高。

最后浏览一下本章的第4节,可以先考考自己再浏览,以便使重点知识掌握得更牢固。

表7-1 总结评分表
成 绩ABCD不足的原因改进的方法备 注
C++知识完全理解和会用基本理解和会用肤浅理解和会用基本不理解和不会用
程序原创(独立编程)的比例(%)≥9080~8960~79<60

7.3 指针知识

给某个数据起了名称后,即定义了某个变量或常量后,C++就在计算机的存储器中开了一个房间,来存储这个变量或常量。有房间就有房号(或称为地址),有时为了运算和管理上的方便,需要用到这些变量或常量的地址,从而给数据更好的指引,使程序更清晰,编程更方便,可读性、可维护性等性能更好。C++用指针来指示这些变量或常量的地址。下面来细细地欣赏数据的指南针——指针吧。

7.3.1 在什么情况下用到指针

当需要用变量或常量的地址来解决问题时,就要用到指针。例如,利用变量、常量、数组的地址来管理变量、常量、数组。另外,在指针和数组或指针和结构组成的链表中也要用到指针,这样才能管理链表、操纵链表(关于链表的知识将在第8章中介绍)。

总之,就像日常生活中找人、找地方、寄信一样,有了地址就好办了,指针的强大威力正源于此。

7.3.2 关于指针
  1. 指针是什么

指针是一种数据类型,但它里面的数据全是地址,是指向变量或常量的地址。

  1. 如何定义指针

前面讲到定义一个整型变量 s 时的语句为 “int s;”,当写成 “int * s;” 时,就是定义一个整型指针变量 s 了。

如果想获取数据的地址,就用“&”这个操作符来获取。

定义指针的方式有以下两种。

(1)只定义,不初始化

1)指向变量的指针的定义方式为:

指针所指数据的数据类型 * 指针名:

原书第 121 页

例如:

double * q;//定义了一个指向双精度数据类型的指针 q,但 q 没有指向任何一个变量

2)指向常量的指针的定义方式为:

const 指针所指数据的数据类型 * 指针名:

例如:

const float * el;//定义了一个指向浮点型常数的指针 el,但 el 没有指向任何一个常量

(2)定义和初始化同时进行

1)指向变量的指针的定义方式为:

指针所指数据的数据类型 * 指针名=&所指向的变量;

例如:

double r;

double * q=&r;//用取地址符&获取了变量 r 的地址,并将此地址赋给指针 q,则定义了一个指向双 //精度变量 r 的双精度指针 q

2)指向常量的指针的定义方式为:

const 指针所指数据的数据类型 * 指针名=&所指向的常量;

【例 7-4】定义一个指向浮点型常量的浮点型指针及其取地址的操作。

定义语句如下:

const float e2=2.718;//定义了一个浮点型常数 e2

const float * e1=&e2;//用取地址符&获取了常量 e2 的地址,并将此地址赋给指针 e1,则定义了 //一个指向浮点型常数 e2 的浮点型指针 e1

3)指向相同数据类型指针的指针的定义方式。

假设已定义了两个相同数据类型的指针(如 x 和 y),如果有:

相同数据类型的指针名(如 x)=另一个相同数据类型的指针名(如 y);

则表明将指针x指向了y。

如例 7-8 中有:

m=n;//将指针 m 指向 n,二者均是字符型指针

3. 对指针进行的操作有哪些

(1)取指针所指向的数据的地址

可以用&这个取地址符来获取数据的地址。

上述定义指针的第2种方式中已叙述了获取数据的地址并赋给指针的操作与指针的定义是同时进行的。

在除定义外的程序中,也可以随时获取数据的地址。如例 7-1 中的 “e=&f; //将变量 f 的

原书第 122 页

地址赋给了指针变量 e,即取地址”。

(2)取指针所指向数据的内容(又称为间接引用)

如例 7-1 中的 “cout<<*e<<endl;// 把指针 e 指向的变量 f 的内容输出,即取内容”。

【例7-5】指针的一些应用。

//ex7-5.cpp
#include<iostream.h>

void main()
{
    int r=6;
    int *n=&r;//定义整型指针 n,并将整型变量 r 的地址赋给整型指针 n
    int m,l;
    m=&n;//将指针 n 所指向地址的内容取出来送变量 m 中保存,如图 7-3 所示
    l=r+*n;//将“r 的内容 6+指针 n 所指向地址的内容 6”的结果送变量 l 中保存,如图 7-3 所示
    cout&lt;&lt*n&lt;&lt" ;
    cout&lt;&lt;r&lt;&lt" " ;
    cout&lt;&lt;m&lt;&lt" " ;
    cout&lt;&lt;<n&lt;&lt;endl;
    cout&lt;&lt;n&lt;&lt"  "<<&r<<endl;//输出整型指针 n 与整型变量 r 的地址
}

运行结果:

66612

0x0012FF7C 0x0012FF7C

结果正确。

Image
图 7-3 指针与所指变量之间的关系示意图

注意:

1)“*”放在定义句中是定义某变量为指针变量。

2)“*”放在除定义句外的地方且在指针变量前是作为间接引用符来用,即取指针变量所指向数据的内容。

3)其余地方的“*”是运算符乘号

4)取内容时,“*指针名”放在赋值号“=”的左、右都可以,如例 7-10 中的“*m=*n;”。

原书第 123 页

(3) 指针的移动(即指针的加减运算)

为了让指针指到需要的数组数据的地址处,可以像拨闹钟一样拨动指针到所需的位置,C++通过指针的加减运算来拨动指针。

注意:指针的加减运算一般在数组中进行,否则,当对单个数据操作时,会不知道指针指向何处。

1)按规律拨动指针。

如例 7-3 中的 “pm++;//指针向前移动一个数(即数组单元)”。

2)不按规律拨动指针。

【例 7-6】在例 7-3 中,输出 10 日销售金额数后,再不按规律地拨动指针,其运行结果是怎样的?

pm=pm-10;

cout<<pm<<": "<<*pm<<endl;

运行结果:

0x0012FF30: 13434.45

结果正确。

请读者自行分析。

(4)一级指针与一维数组

(5)二级指针与二维数组

一级指针指向一维数组时,一级指针就等同于一维数组,此时对指针的操作就是对数组的操作,见例7-3。

二级指针即指向指针的指针,这类问题一般都可以用二维数组解决,而二维数组比二级指针容易理解和运用,下面举个例子。

【例7-7】对二级指针变量进行赋值运算等操作。

#include<iostream.h>
void main()
{
    char *d[]={"x","y"};//定义指针数组
    char**g;//定义二级指针
    g=d;//将指针数组的首地址赋给二级指针,即给二级指针初始化
    cout<**g<<endl;//输出二级指针g所指向的指针数组的第一个元素值
    cout<**+g<<endl; //二级指针g的地址增加1,则指向指针数组的
    //第二个元素的地址,再输出第二个元素的地址值
    cout<<g<<endl; //输出二级指针g的地址
}

运行结果:

x

y

0x0012FF7C

原书第 124 页

结果正确。

上机实验记录如表7-2所示。

表7-2 上机实验记录表
出错现象出错原因解决办法
cannot convert from 'char [2]' to 'char' Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast不能从指针数组转换成字符指针,因为 char*g 表示的是字符指针,而 char*d[]表示的是指针数组,即 二级指针改“char*g;”为“char**g;”,此错纠正
7.3.3 如何运用指针

本节按照上节所述的指针的知识来运用指针。

下面来看看人交换两种东西(数据)的做法(即交换红、蓝墨水的做法)。

例如交换两个字符串,把这两个字符串分别看成红、蓝墨水,此时红墨水瓶里的红墨水要与蓝墨水瓶里的蓝墨水互换,这必须要用到一个临时瓶子,即先将红墨水倒到临时瓶中,再将蓝墨水倒到红墨水瓶中,最后将临时瓶中的红墨水倒到蓝墨水瓶中,这叫做换内容(换红、蓝墨水),如例7-9和例7-10。另外,还可以换地址,即将指针所指的地址进行交换,如指针原来指向红墨水瓶的地址,现在则指向蓝墨水瓶的地址,如例7-8。

这种红、蓝墨水的交换必须有一个临时变量,才能换过来。当交换指针变量指向的地址时,就用临时指针变量;当交换数据内容时,就用该数据内容的数据类型的临时变量。

一般交换语句是3句,如例7-8中从“t=m;”开始的3句。

1. 取指针的地址(简称取地址)

下面介绍取出指针所指向的地址、交换指针所指向的地址(换地址)等运用。

【例 7-8】交换两个字符串的例子,原来的两个字符串分别为 “学生” 和 “好”,字符串输出显示为 “学生好”,交换后输出显示为 “好学生”。

这题可以采用换地址的方法编写程序。

程序代码如下:

//ex/-8-1.cpp
#include<iostream.h>

void main()
{
    char c1[5]="学生"; //定义两个字符数组变量,假设c1是红墨水瓶,
    //红墨水瓶里的内容就是红墨水(学生)
    char c2[3]="好"; //假设c2是蓝墨水瓶,蓝墨水瓶里的内容就是蓝墨水(好)
    char*m,*n,*t;//定义3个字符指针变量
    m=c1;//将数组c1的地址赋给了指针变量m,m指向红墨水瓶的地址(0012FF78)
    n=c2;//将数组c2的地址赋给了指针变量n,n指向蓝墨水瓶的地址(0012FF74)
    cout<="交换前:<<c1<<c2<<endl;//先输出红墨水瓶的内容,再输出蓝墨水瓶的内容
    cout<="交换前:<<m<<n<<endl;//输出字符指针就是输出字符串
    //先输出m指向的地址的内容:红墨水瓶的内容,再输出n指向的地址的内容:
    //蓝墨水瓶的内容
    t=m;//交换两个指针所指向的地址,先将m指向的地址存放在临时指针t中
原书第 125 页

m=n;//再将 m 指向 n 的地址,即 m 中的地址是 n 中的原地址(0012FF74)

n=t;//又将 n 指向临时指针 t 中的地址,即 n 中的地址为 m 中的原地址(0012FF78)

//从图 7-4 中看出,m 和 n 所指向的地址进行了交换
cout<<"交换后:"<<c1<<c2<<endl;//先输出红墨水瓶的内容,再输出蓝墨水瓶的内容,
//内容没换
cout<<"交换后:"<<m<<n<<endl;
//先输出 m 指向的地址的内容:蓝墨水瓶的内容(好),
//再输出 n 指向的地址的内容:红墨水瓶的内容(学生),
//输出结果证明 m 与 n 指向的地址交换了
}

运行结果:

交换前:学生好

交换前:学生好

交换后:学生好

交换后:好学生

结果正确。

Image
图 7-4 交换字符串地址示意图

2. 取指针所指地址的内容(简称取内容)

下面介绍取出指针所指地址的内容的运用。

有的时候,想把指针所指地址的内容进行交换,如例 7-10;还可以通过字符串变量的操作交换内容,如例 7-9。

【例 7-9】通过字符串变量交换字符串内容,以实现例 7-8 的功能。

//ex7-9-6.cpp
#include<iostream>//注意不要加.h
#include<string>//注意不要加.h
using namespace std;//要加此句
void main()
{
    string t, q, w; //定义了3个字符串变量
原书第 126 页

char c1[5]="学生";//定义两个字符数组变量,假设 c1 是红墨水瓶

char c2[3]="好";//假设 c2 是蓝墨水瓶

q=c1;//将红墨水送给(赋值给)字符串变量 q,q 的内容为红墨水(学生)

w=c2;//将蓝墨水送给(赋值给)字符串变量 w,w 的内容为蓝墨水(好)

cout<<"交换前:""<<c1<<c2<<endl;//先输出红墨水瓶的内容,再输出蓝墨水瓶的内容
cout<<"交换前:""<<q<<w<<endl;
//先输出 q 的内容:红墨水,再输出 w 的内容:蓝墨水

t=q;//交换 q、w 的内容,先将红墨水倒在临时瓶 t 中

q=w;//把蓝墨水倒入红墨水瓶中

w=t;//将临时瓶中的红墨水倒入蓝墨水瓶中,此时红、蓝墨水就互换了

cout<<"交换后:""<<c1<<c2<<endl;//先输出红墨水瓶的内容,再输出蓝墨水瓶的内容
cout<<"交换后:""<<q<<w<<endl;
//先输出 q 的内容:蓝墨水,再输出 w 的内容:红墨水,q、w 的内容互换了
}

运行结果:

交换前:学生好

交换前:学生好

交换后:学生好

交换后:好学生

结果正确。

本题的交换过程如图7-5所示。

Image
图 7-5 交换字符串内容示意图

【例7-10】这题也是交换红、蓝墨水(换内容)。

//ex7-9-7.cpp
#include<iostream.h>
void main()//交换两个数的值
{
    int c1=100;//定义了红墨水瓶(c1),瓶里的墨水就是 100
    int c2=300;//定义了蓝墨水瓶(c2),瓶里的墨水就是 300
原书第 127 页

int *m, *n; //定义两个字符指针变量

m=&c1; //将数组 c1 的地址赋给了指针变量 m,m 中放的是红墨水瓶的地址

n=&c2; //将数组 c2 的地址赋给了指针变量 n,n 中放的是蓝墨水瓶的地址

cout&lt;&lt;"交换前:&lt;&lt;c1&lt;&lt;t'&&c2&&endl;

cout&lt;&lt;"交换前:"

&lt;&lt;m&lt;&lt;t'&&n&&endl;

//*m 是按 m 中的地址(红墨水瓶的地址)找到红墨水瓶,
//再将红墨水瓶里的内容 100 输出,即*m 代表了取内容(取 100)
cout&lt;&lt;"交换前的地址:"&&m&&t'&&n&&endl; //m 是输出 m 的地址
int t=m; //交换两个指针所指向的地址的内容,即红、蓝墨水互换(换内容),
//先将 m 所指的红墨水(100)倒入临时瓶 t 中

*m=*n; //再将蓝墨水(300)倒入红墨水瓶中

*n=t; //最后将红墨水(100)倒入蓝墨水瓶中

//此时红、蓝墨水就交换了
cout&lt;&lt;"交换后:"&&c1&&t'&&c2&&endl; //先输出红墨水瓶的内容,再输出蓝墨水瓶的内容
cout&lt;&lt;"交换后:"

&lt;&lt;m&lt;&lt;t'&&n&&endl;

//通过指针指引输出红、蓝墨水瓶的内容

cout&lt;&lt;"交换后的地址:"&&m&&t'&&n&&endl;

运行结果:

交换前:100 300

交换前:100 300

交换前的地址:0x0012FF7C 0x0012FF78

交换后:300 100

交换后:300 100

交换后的地址:0x0012FF7C 0x0012FF78

结果正确。

由此看出地址没变,只是内容变了。

本题的交换过程如图7-6所示。

Image
图 7-6 交换字符串内容示意图
原书第 128 页

3. 指针所指地址的内容在数组中的运用

如例 7-2 和例 7-3。

关于指针在其他方面的一些应用请参阅后续章节,如第8章的例8-15和链表的几个例子及第10章的字符串指针的应用等。

7.4 指针的亮点

1. 在什么情况下用指针的亮点

当取数据的地址更有利于程序的编写、数据的调用和管理时用指针。

2. 指针具体知识的亮点

1)指针中存放的数据全是地址。

2)指针也是一种数据类型,相应地也有这种数据类型的变量(指针变量)和常量(指针常量),所以也必须先定义,后使用。

3)定义语句中的“*”用来定义是指针变量还是指针常量;非定义语句中的“*”,同时也不是乘法运算符时,表示对指针的间接引用,即取出指针所指地址的内容。

定义指针的方式有两种:一种是非初始化方式,另一种是初始化方式。

4)对指针的操作和运用有 4 种方式:一是取地址;二是取内容;三是结合数组一起运用,还可做指针的加减运算;四是运用在函数和结构等方面。

5)一级指针等同于一维数组,二级指针不等同于二维数组。

3. 运用指针的亮点

1)指针指的就是地址,这个变量里面放的也是地址,所以不要怕指针,指针会给数据当个好导游,是起到帮助作用的。

2)有时在调试程序时总也查不出错,可以尝试加个“*”或去掉一个“*”的办法,来试探性地查错、改错。

3)注意取地址符“&”的使用。

← 第6章 数组第8章 结构 →