4.5.1 指针数组
如果一个数组的元素是指针类型数据,则该数组称为指针数组。一维指针数组的定义形 式为:
类型符 *数组名[常量表达式];
其中,数组名之前的*表明定义指针数组,类型符表明数组元素所指对象的数据类型,即 元素指针的基类型,方括号中常量表达式的值表示数组元素的个数。例如
int *p[10];
定义了一维指针数组 p,它有 10 个元素,每个元素都是指向 int 型变量的指针变量。和普 通一维数组名一样,数组名 p 也代表第一个元素即 p[0]的地址。
注意,在*与数组名之外不能加圆括号,否则变成指向一维数组的行指针变量(前面已详 析)。如:
int (*p)[10];
则定义指向含 10 个整型元素的一维数组的行指针变量。
类似地可定义多维指针数组。
指针数组适合表示一批指针数据。例如,对多个字符串进行排序,这些字符串可用一个 字符型的二维数组存储,每行存储一个字符串。排序时一般要多次交换两个字符串在数组中的 位置,这样较费时。如用指针数组,让其元素指向各串,就不必交换两个字符串的位置,而只 交换指针数组中两个元素的指向。
【例 4.13】编写 void sort(char *p[],int n),用选择法(在例 4.2 中已介绍)将指针数组 p 元素值按元素所指字符串大小从小到大排序,即让 p[0]指向最小的串,p[n-1]指向最大的串,
而不改变字符串的存储位置。编写 main()等相关函数构成完整程序。要求从键盘输入各字符串,
且输出排序结果。
分析:从要求可知,sort()开始执行时,若数组 p 中 6 个元素及其指向的字符串如图 4-18
(a)所示,sort()执行后,6 个元素的指向发生改变,结果如图 4-18(b)所示。此处对数组 p 选择排序,每次确定 p 两元素值大小关系时,不是比较两元素本身,而是比较两元素指向的串。
因此将例 4.2 中的数组元素比较运算改用 strcmp(p[k],p[j])即可。
程序如下:
//*****ex4_13.cpp*****
#include <iostream>
#include <string>
using namespace std;
#define N 6 void main()
{ void sort(char *[],int),write(char *[],int);
char *name[N],str[N][30];
int i;
for(i=0;i<N;i++)
{ name[i]=str[i]; //str[i]指向二维数组 str 的第 i 行首字节
(a) (b)
for(i=0;i<n;i++) cout<<p[i]<<endl;
cout<<endl;
}
对于上例而言,若待排序的各字符串在编写程序时就能确定,则也可用它们来初始化 name 数组如下:
char *name[]={"Russia","America","Japan","France","Britain","China"};
这样,name 的元素分别指向大括号中的各字符串。
图 4-19 多级指针示意图 例如:
int **pp;
定义了指针变量 pp,它能指向另一个指针变量,该指针变量又能指向一个整型变量。pp 的前面有两个“*”号,由于指针运算符“*”是按自右向左顺序结合的,因此**p 相当于*(*p),
可以看出(*pp)是指针变量形式,它前面的“*”表示指针变量 pp 指向的又是一个指针变量,“int”
表示后一个指针变量指向的是整型变量。
要定义图 4-19 所示的变量,可如下定义:
int a=22,*ap=&a,**pp=≈
ap可用*pp 表示,a 可用*ap 和**pp 表示。
类似地可定义三级指针,例如:
char ***p;
编程时根据需要来确定指针变量的级数。
多级指针同样可以进行前面介绍的指针运算,只是指向关系多了一层而已,参加运算的 两指针级别要一致。
指针数组元素的地址是多级指针,指针数组名代表首元素的地址,是多级指针常量。如 同一维数组元素及其地址可以用指针变量表示(见表 4-1)一样,一维指针数组元素及其地址 也可以用二级指针变量表示。前面 4.3.2 节说过,一维数组名和指针变量作函数形参时可以通 用,类似地,一维指针数组名和二级指针变量作函数形参时也可通用,如例 4.13 中函数 sort() 的形参 p 的说明 char *p[]也可写成 char **p。
4.5.3 带形参的 main 函数
前面介绍的程序中,main 函数都没有形参,其实,main 函数也可以带形参,用来接收来 自命令行的实参。这个命令行是指运行 main 函数所在程序的命令。
带形参的 main()函数的一般形式是:
int main (int avgc, char *argv[] ) // 或 main (int avgc, char **argv) { … }
在操作系统命令状态下,启动、运行一个程序的命令行格式:
程序文件名 参数 1 参数 2 …… 参数 n
程序文件名和各参数之间用空格分隔。程序文件名、各参数都是字符串,统称为命令行参 数。此处字符串可以不带双引号,若字符串本身含有空格,则要用双引号括起来。
程序中的 main 函数利用其形参接收命令行的实参信息。其中 argc 表示命令行中参数的个 数,程序文件名字符串也算作其中一个,argv 是一个指向字符串的指针数组(或二级指针)。
argv[0]指向命令行中第一个字符串,即程序文件名串,argv[1]指向命令行中第二个字符串,其 余依次类推。
下面通过实例说明命令行参数是如何传递的。
【例 4.14】编写程序,要求输出运行该程序时所输入的命令行参数。源程序文件名为
&ap pp
&a ap,*pp
22 a,*ap,**pp
ex4_14.cpp,经编译连接后生成的可执行程序为 ex4_14.exe。
//*****ex4_14.cpp*****
#include <iostream>
using namespace std;
int main(int argc,char *argv[]) //或 main(int argc,char **argv) { int k;
cout<<"argc=" <<argc<<endl;
for(k=0;k<argc;k++)
cout<<"argv"<<k<<":"<<argv[k]<<'\n';
cout<<"\n";
return 0;
}
若运行该程序时输入的命令行是:
ex4_14 good better best↙ 则输出结果:
argc=4 argv0:ex4_14 argv1:good argv2:better argv3:best
一旦命令行输入完毕,操作系统接收并分析命令行内容,辨别出其中字符串个数并将个 数(4)传给形参 argc;将四个字符串:ex4_14、good、better、best 保存到特定位置,并将它 们首地址分别传给字符指针数组元素 argv[0]、argv[1]、argv[2]、argv[3],如图 4-20 所示。
argv argv[0] e x 4 _ 1 4 \0 argv[1] g o o d \0 argv[2] b e t t e r \0 argv[3] b e s t \0
图 4-20 命令行参数指针数组示意图
4.6 引用
程序可以用变量名直接访问变量,也可以用指针变量间接访问变量,另外,C++中,程序 还可以采用引用(reference)来访问变量。用引用作函数参数和返回值可以扩充函数传递数据 的能力。
4.6.1 变量的引用
变量的引用就是变量的别名。一个引用总依附于某个实体,如变量、类对象(后面章节介绍),
定义引用时必须进行初始化,说明是谁的引用,换句话说,总是为某个特定实体定义引用,例如:
int a;
int &ar=a;
为变量 a 定义了一个引用 ar,ar 也表示 a 的存储单元,此处&是引用声明符,不是取地址 运算符,&前面的 int 说明 ar 是整型变量的引用,即被引用的变量应该是整型变量。
可以在定义变量的同时为它定义引用,上面两行也可合并为:
int a, &ar=a; //还可同时初始化 ar 引用的变量:int a, &ar=a=100;
后续程序中变量 a 都可用它的引用 ar 替换,例如:
int *p=&ar; //&ar代表&a,定义指向 a 的指针变量
int &ar2=ar; //为引用 ar 定义引用 ar2,因 ar 代表 a,相当于为 a 再定义另一引用 ar2 ar=100; //等效于 a=100;
cin>>ar; //等效于 cin>>a;
cout<<ar; //等效于 cout<<a;
指针变量的引用定义格式如下:
int *q, *&qr=q; //还可同时初始化 qr 引用的指针变量:int a, *q, *&qr=q=&a;
此后 qr 就可作 q 用。
前面已介绍,变量或存储单元的间接访问形式为“*指针”,也可以为间接访问形式的变 量或存储单元定义引用,例如:
int a, *p=&a, &ar=*p; //定义 ar 是*p 的引用,而*p 是 a 的间接访问形式,故 ar 也就是 a 的引用 注意,不能定义引用数组,也不能定义指向引用的指针,下面的引用定义有语法错误。
int &refer[10], &*pr ;
变量引用的作用主要体现在作函数参数和返回值的情况。
4.6.2 引用作函数参数
前面多次提到,在函数调用时,实参的值传递给形参变量,函数在执行过程中改变了形 参的值,但对应的实参不会被改变,这就是所谓的函数参数值单向传递现象。为了让被调用函 数可以改变主调函数中变量的值,4.2.4 节介绍了用指针作函数参数的方法,此时实参向形参 传递指针值,被调函数通过形参变量间接访问主调函数中的变量。若将函数形参说明为变量的 引用(或引用型变量),也能达到修改实参变量的目的,而且编程更简洁。对于例 4.7 而言,
将 swap 函数修改如下:
void swap(int &r1,int &r2) //说明形参为变量的引用 { int t;
t=r1;
r1=r2;
r2=t;
}
相应的函数调用语句改为:
swap(a,b);
定义 swap()时说明形参为变量的引用,但没指定是哪一变量的别名。调用函数时,系统把 实参 a、b 变量名(非值)传递给形参 r1、r2,r1、r2 分别
变为 a、b 的引用(别名),即 r1、r2 也分别表示实参 a、b 对应的存储单元,如图 4-21 所示;此时,swap()修改形参 r1、r2 就是修改实参 a、b。此处本质上也是传递地址,与 指针变量作形参不同的是,此时形参不需占用临时存储空 间,而是直接引用 a、b,用引用作函数参数显得表达简单、
自然。
交换
实参 a,别名 r1
实参 b,别名 r2
图 4-21 交换两引用型形参的值
与引用型形参对应的实参可以是变量,如上面调用 swap(a,b)中的实参 a、b 是已定义的变 量;另外,实参也可以是指针所指向的变量或(动态)存储单元,例如,假设指针 x、y 已经 指向内存单元,则调用 swap(*x,*y)将交换 x、y 所指内存单元的值。
4.6.3 引用作函数返回值
函数也可返回变量(或存储单元)的引用,其定义一般格式是:
类型名 & 函数名(形参表) {…
…
return 变量、变量的引用或用“*指针”形式表示的存储单元;}
return后的变量不能是本函数中的局部变量,因为函数执行完毕返回主调函数后,函数中 的局部变量全部释放,主调函数再用其引用去访问它,不能保证结果正确。例如,下面的函数 在编译时会有警告提示:
int & minp(int &x,int &y) {int q;
q=x<y?x:y;
return q;
}
因为该函数试图返回其局部变量 q 的引用。return 后的变量应该是主调函数可寻址或可见 的变量或内存单元,如全局变量。该函数可改为
int & minp(int &x,int &y) { return x<y?x:y;}
该 minp()被调用时,x、y 变成实参变量的引用,显然,主调函数可找到实参变量。
习题 4
一、选择题
1.设有 int x[][3]={ {0},{1,2},{3,4,6},{5}}; 则 x[1][1]的值是( )。
A.3 B.2 C.6 D.4
2.在下面的二维数组定义中,正确的是( )。
A.int a[3][]; B.int a[][3];
C.int a(3)(3); D.int a[][3]={{1,2},{3}};
3.下面哪个定义或语句序列能使 p 指向 a?( )
A.int a,*p=a; B.int a,*p; *p=a;
C.int a,*p; *p=&a; D.int a,*p=&a;
4.若有:double a[10], *pa=a;,要将 10 赋值给 a 中的下标为 5 的元素,不正确的语句是( )。
A.pa[5]=10; B.*(pa+5)=10;
C.*(a[0]+5)=10; D.a[5]=10;
5.设有 int a[10], *p=&a[4] ; 则下面哪种表示与 a[9]不等价?( )
A.*(a+9) B.*(p+5) C.p[5] D.p+5
6.设有 int a[10], *p=a+5 ; 则下面哪种表示与 a[3]不等价?( )
A.*(a+9) B.*(p-2) C.p[-2] D.p+2
7.设有 int a[20], *p=a; 则下面哪个与 a[1]不等价?( )
A.p[1]; B.*++p; C.*(a+1); D.*++a;
8.已知 char a[][20]={"hunan","jiangxi","shandong"};,语句 cout<<a[3];得到的输出是( )。
A.a B.shandong C.输出结果不确定 D.数组定义有错
9.若函数形参是数组,则对应的实参( )。
A.只能是数组名 B.只能是指针
C.任何类型的数据 D.可以是数组名或指针
10.若函数形参是指针变量,则对应的实参( )。
10.若函数形参是指针变量,则对应的实参( )。