http://blog.youkuaiyun.com/EndAll/archive/2006/12/25/1459857.aspx 第7章 指针
第 7 章 指针
1 .教学内容
( 1 )指针与指针变量的概念,指针变量的定义、赋值与引用,指针变量的运算。
( 2 )数组指针与数组指针变量的概念,一维数组元素地址的三种表示方式,一维数组元素值的四种表示方式及应用举例。
( 3 )二维数组行首地址、行地址与元素地址的概念与表示方法,二维数组元素值的四种表示方式。
( 4 )字符串指针与字符串指针变量的概念,用字符串指针变量处理字符串的方法。
( 5 )指针数组的概念、定义格式与使用方法;
( 6 )指向一维数组的指针变量的概念、作用、定义格式与使用方法;
( 7 )指针变量作为函数参数时,函数的定义格式、调用格式与参数的传送过程。
( 8 )指针变量与数组作为函数参数时,四种格式函数定义格式与调用方法。
( 9 )返回指针值函数的概念、定义格式与调用方法;
( 10 )函数指针与函数指针变量的概念、定义格式、赋值与调用方式;
( 11 )动态分配与回收内存 new 与 delete 运算符的定义格式与使用方式;
( 12 )引用变量的概念、作用、定义格式与使用方法;
( 13 ) const 常量与 const 指针的定义格式与使用方法;
2 .教学要求
( 1 )了解指针、指针变量、指针数组、指向一维数组的指针、返回指针值的函数、函数指针、引用类型变量的概念;
( 2 )掌握指针变量的定义格式及使用方法,重点掌握用指针变量处理有关变量、一维数组与字符串数组的问题;
( 3 )掌握用指针变量与数组作为函数参数时函数的使用方法
( 4 )了解二维数组中有关行首地址、行地址与元素地址的概念,了解二维数组中元素的各种表示方式;
( 5 )初步学会指针数组、指向一维数组的指针、返回指针值的函数与函数指针的定义格式与简单使用方法;
( 6 )学会用 new 与 delete 运算符动态分配与回收内存空间的方法;
( 7 )掌握引用类型变量的定义与使用方法。
( 8 )理解 const 常量与 const 指针的概念,初步掌握其定义格式与使用方法;
3 .教学方法
不同类型指针变量解介方式为:指针的概念、定义格式、引用方式、举例。
( 1 )从定义变量目的引出变量内存首地址即指针的概念,继而引出存放变量内存首地址(指针)变量即指针变量的概念。
( 2 )必须强调引入指针变量的目的是通过指针变量间接的使用变量内存单元值,实现方法如下:先定义指针变量(变量名前加“ * ”)、后用取地址运算符“ & ”将变量地址赋给指针变量、最后用指针运算符“ * ”来间接地使用变量。
( 3 )用例 7.5 讲述运算符“ * ”、“ & ”、“ ++ ”、“ -- ”优先级时,必须先让学生先搞清楚对指针变量 p 自加还是指针变量间接指向变量 *p 自加。例 7.5 可作为课程组的讨论题。
( 4 )应反复强调指针是变量、数组、函数的内存地址。指针变量是用于存放指针的变量。指针变量指向变量的方法是: p=&a ,指针变量间接访问变量的方法是 *p 。
( 5 )学生应预习 7.2 指针与数组的内容,用指针定义引出数组指针的概念,用例 7.6 来说明用指针表示数组元素地址的三种方式,表示元素值的四种方式。并用求最大值例子加深学生对此概念的理解。
( 6 )字符串指针变量是指向字符串的指针变量,用字符串指针变量处理字符串时可不考虑字符串的大小,可将字符串直接赋给字符串指针变量,并用指针变量直接输出字符串,但不能输入字符串。字符数组是 char 类型的数组,数组指针与数组指针变量的使用完全适用于字符数组。
( 7 )指针数组是由若干个同类型指针变量组成的数组,由于是数组,所以指针数组与指针变量定义格式不同之处是多了一个 [ 长度 ], 指针数组中的每个指针变量可指向二维数组的一行。
( 8 )指向一维数组的指针变量用于表示二维数组中某一行元素,其定义格式比指针数组多一个 () ,由于要表示二维数组中一行的每一个元素,所以定义格式中的长度必须与二维数组的列数相同。将二维数组第 i 行的行地址赋给指向一维数组的指针变量后,指向一维数组的指针变量的每一个元素就表示二维数组第 i 行对应元素。
( 9 )指针变量作为函数参数时,参数的传送采用传地址方式,因此,实参与形参占用同一内存单元,对形参的修改就是对实参的修改。
( 10 )指针与数组作为函数参数时,形参与实参为指针变量或数组名时有四种形式,通过求最值问题、排序问题使学生掌握此类函数的定义与调用方式,此节为重点内容。
( 11 )讲述返回指针值函数时,必须让学生知道:由于函数返回指针值(即地址),所以函数体中 return 语句必须返回变量地址或指针值。而调用函数值只能赋给指针变量。
( 12 )讲述函数指针与函数指针变量时,必须让学生知道:函数指针是函数的入口地址,而函数指针变量是存放函数入口地址的变量,将函数名(即函数地址)赋给函数指针变量后,即可 用函数指针变量来调用函数(格式: < 指针变量 >(< 实参表 >) ),这对于编写求不同函数定积分的通用程序来说是非常方便的。
( 13 )由定义数组时长度必须为常量,引出动态分配内存运算符 new 与动态回收内存运算符 delete 的概念、定义格式与使用方式。
( 14 )当变量作为形参时属值传送,不能返回形参运算结果。为解决此问题,引入引用类型变量,由于引用变量与其相关变量占用同一内存空间,所以当变量为实参,而引用变量为形参时,对形参的修改就是对实参的修改(属传地址),能返回形参运算结果。
( 15 ) const 常量与 define 常量作用上是相同的,只不过前者在编译时处理,而后者在编译预处理时处理。
( 16 ) const 指针有三种定义格式, const 放在 < 类型 > 前时指针所指数据值不能改,当 const 放在 < 指针变量 > 前时,指针变量中地址不能改。
7.1 指针与指针变量
7.1.1 指针的概念
图 7.1 存储单元地址 |
C++ 定义变量的目的是为变量分配存储单元。
如: int a ; // 为变量 a 分配四个字节的内存单元。设为变量 a 分配的内存地址为 1000 ,则为变量 a 分配的存储单元首地址 1000 称为变量 a 的指针。
( 1 )指针:系统为变量、数组、函数等分配内存的首地址称为指针。
( 2 )指针变量:用于存放指针(内存首地址)的变量称为指针变量。
引入指针变量的目的是提供一种对变量值进行间接访问的手段。指针变量必须先说明后引用。
7.1.2 指针变量的定义与引用
1.定义格式
〔存储类型〕 < 类型 > *< 指针变量名 1> 〔 ,*< 指针变量名 2>, … ,*< 指针变量名 n> 〕;
其中,星号“ * ” 说明定义的是指针变量,类型指出指针变量所指的数据类型。例如:
int *p; // 定义整型指针变量 p
float *pf; // 定义实型指针变量 pf
char *pc; // 定义字符型指针变量 pc 。
2.指针变量的引用
指针变量用于存放变量内存首地址,为将变量内存首地址赋给指针变量, C++ 提供了取地址运算符 & 。
( 1 )取地址运算符 & :返回变量内存首地址
例如:变量 a 的首地址为 1000 ,则 &a=1000 。
( 2 )指针变量的赋值:指针变量 =& 变量;或指针变量 = 指针变量;或指针变量 =0 ;
例如: int * p; // 定义整型指针变量 p
指针变量 p |
1000 |
变量 a |
1000 |
100 |
地址 1000 |
指针变量 p 1 |
0 |
指针变量 q |
图 7.2 指针变量 |
( 3 )指针运算符 * :通过指针变量间接访问变量对应存储单元内容。
例如:指针变量 p 指向变量 a ,则 *p 运算的结果为变量 a 的内容,即 *p 表示变量 a 的内容。
【例 7.1 】定义指针变量 p 、 p1 、 q ,并将变量 a 的地址赋给 p 、 p1 ,输出 a 、 p 、 p1 、 *p 、 *p1 的值。
# include <iostream.h>
void main(void)
{ int a=100; // 定义整型变量 a ,并赋初值 100
int *p , *p1, *q; // 定义整型指针变量 p 、 p1 与 q 。
p=&a; // 将变量 a 的地址赋给指针变量 p
p1=p; // 将 p 中 a 的地址赋给 p1 ,使 p1 与 p 均指向变量 a
q=0; // 指针变量 q 赋空值 0 表示 q 不指向任何变量
cout<<"a="<<a<<'/t'<<"*p="<<*p<<'/t'<<"p="<<p<<endl;
*p1=200; // 通过指针运算符“ * ”间接给变量 a 赋值 200
cout<<"a="<<a<<'/t'<<"*p="<<*p<<'/t'<<"p="<<p<<endl;
cout<<'/t'<<"*p1="<<*p1<<'/t'<<"p1="<<p1<<endl;
}
假设变量 a 的地址为 1000 ,则程序执行后输出结果为:
a=100 *p=100 p= 1000
a=200 *p=200 p= 1000
*p1=200 p1=1000
注意:实际执行程序时,变量 a 的地址是由操作系统动态分配的,事先无法确定。如: p=0x0065FDF4 。
通常指针变量的使用是:先定义指针变量,后给指针变量赋值,最后引用指针变量。
说明:
( 1 )指针变量定义:指针变量长度均为 4 个字节。
( 2 )指针变量 3 种赋值方式
①用“ & ”将变量地址赋给指针变量; p=&a;
②将一个指针变量中的地址赋给另一个指针变量;如: p1=p ;
③给指针变量赋空值 0 ,如 q=0 ;表示该指针变量不指向任何变量。
指针变量定义后其值为随机数,若此随机数为系统区的地址,则对该指针变量所指存储单元进行赋值运算,将改变系统区某单元中内容,可能导致系统的崩溃。所以,指针变量定义后必须赋某个变量的地址或 0 。
经过赋值后,使指针变量 p 、 p1 指向变量 a , q 不指向任何单元,如图 7.2 所示。
( 3 )指针变量的引用
指针变量的引用是通过指针运算符“ * ”实现。在上例中, *p 与 *p1 均表示变量 a 。
( 4 )指针变量初始化
指针变量可以象普通变量一样,在定义指针变量时赋初值,如上例中,定义指针变量 p 的语句可写成: int *p=&a;
7.1.3 指针变量的运算
指针变量的运算有三种:赋值运算、关系运算与算术运算。
1.指针变量赋值运算
指针变量赋值运算就是将变量的地址赋给指针变量。
【例 7.2 】定义三个整型变量 a1 、 a2 、 a3 ,用指针变量完成 a3=a1+a2 的操作。再定义两个实型变量 b1 、 b2 ,用指针变量完成 b1+b2 的操作。
# include <iostream.h>
p1 |
*p1=a1 |
1 |
2 |
p2 |
*p2=a2 |
3 |
p3 |
*p3=a3 |
+ |
= |
fp1 |
*fp1=b1 |
12.5 |
25.5 |
fp2 |
*fp2=b2 |
+ |
(a) *p3 =* p1+*p2 示意图 |
(b) * f p1+*fp2 示意图 |
图 7.3 指针的赋值与算术运算 |
{ int a1=1,a2=2,a3;
int *p1,* p2,*p3;
float b1=12.5,b2=25.5;
float *fp1,* fp2;
p1=&a1; //p1 指向 a1
p2=&a2; //p2 指向 a2
p3=&a3; //p3 指向 a3
* p3= * p1 + *p2; //a3=a1+a2
fp1=&b1; //fp1 指向 b1
fp2=&b2; //fp2 指向 b2
cout<<" *p1="<<*p1<<'/t'<<" *p2="<<*p2<<'/t' <<"*p1+*p2="<<*p3<<'/n';
cout<<"a1="<<a1<<'/t'<<" a2="<<a2<<'/t' <<"a1+a2="<<a3<<'/n';
cout<<"b1=" <<*fp1<<'/t'<<" b2="<<*fp2<<'/t'<<"b1+b2="<<*fp1+*fp2<<'/n';
}
程序执行后,输出:
*p1=1 *p2=2 *p1+ *p2=3
a1=1 a2=2 a1+a2=3
b1=12.5 b2=25.5 b1+b2=38
2.指针变量的算术运算
指针变量的算术运算主要有指针变量的自加、自减、加 n 和减 n 操作。
( 1 )自加运算
p |
|
p1 |
图 7.4 指针变量算术运算 |
作用:将指针变量指向下一个元素,即:
< 指针变量 >=< 指针变量 >+sizeof(< 指针变量类型 >) 。
例如:数组 a 的首地址为 1000 ,如图 7.4 所示。
int *p=&a[0]; //p=1000 ,指向 a[0] 元素
p++; //p 指向 a[1]
p=p+sizeof(int)=p+4=1004 ,使 p 指向下一个元素 a[1] 。
( 2 )自减运算
格式: < 指针变量 > ―― ;
作用:指针变量指向上一元素,即:
< 指针变量 >=< 指针变量 > ― sizeof(< 指针变量类型 >)
自加运算和自减运算既可后置,也可前置。
( 3 )指针变量加 n 运算
格式: < 指针变量 >=< 指针变量 >+n;
作用:将指针变量指向下 n 个元素的运算,即:
< 指针变量 >=< 指针变量 >+sizeof(< 指针变量类型 >)*n
( 4 )指针变量减 n 运算
格式: < 指针变量 >=< 指针变量 > ― n;
作用:将指针变量指向上 n 个元素的运算,即:
< 指针变量 >=< 指针变量 > ― sizeof(< 指针变量类型 >)*n
【例 7.3 】指针变量的自加、自减、加 n 和减 n 运算。假设数组 a 的首地址为 1000 ,如图 7.4 所示。
# include <iostream.h>
void main( void)
{ int a[5]={0,1,2,3,4};
int *p;
p=&a[0]; //p 指向 a[0] , p=1000
p++ ; //p 指向下一个元素 a[1] , p=1004
cout<< *p<<'/t'; // 输出 a[1] 的内容 1 。
p=p+3; //p 指向下 3 个元素 a[4] , p=1016
cout<< *p<<'/t'; // 输出 a[4] 的内容 4 。
p ―― ; //p 指向上一个元素 a[3] , p=1012
cout<< *p<<'/t'; // 输出 a[3] 的内容 3 。
p=p ― 3; //p 指向上 3 个元素 a[0],p=1000
cout<< *p<<'/t'; // 输出 a[0] 的内容 0 。
}
程序执行后输出:
1 4 3 0
从上例可以看出,通过对指针变量的加减算术运算,可以达到移动指针变量指向下 n 个元素单元或向上 n 个元素单元的目的。
3.指针变量的关系运算
指针变量的关系运算是指针变量值的大小比较 ,即对两个指针变量内的地址进行比较,主要用于对数组元素的判断。
【例 7.4 】用指针变量求一维实型数组元素和,并输出数组每个元素的值及数组和。
# include <iostream.h>
void main( void )
{ int a[5]={1,2,3,4,5};
int *p,*p1;
p1=&a[4]+1;
for (p=&a[0];p<p1;p++)
cout <<*p<<'/t';
int sum=0;
p=&a[0];
while (p!=p1) sum+=*p++;
cout <<"/n sum="<<sum<<endl;
}
执行程序后:输出:
1 2 3 4 5
sum=15
4 .指针运算符的混合运算与优先级
( 1 )指针运算符 * 与取地址运算符 & 的优先级相同,按自右向左的方向结合。
设有变量定义语句: int a, *p=&a;
则表达式: &*p 的求值顺序为先“ * ”后“ & ”,即 & (*p)=&a=p 。
而表达式: *&a 的求值顺序为先“ & ”后“ * ”,即 * (&a)=*p=a 。
( 2 )“ ++ ”、“ ―― ”、“ * ”、“ & ”的优先级相同,按自右向左方向结合。 下面结合例 7.5 子加以说明。设有变量定义语句:
int a[4]={100,200,300,400},b;
int * p=&a[0];
为了叙述方便,假设系统给数组 a 分配的首地址为 1000 ,如图 7.4 所示。与例 7.5 同时讲述,该题必须让学生先搞清楚对指针变量自加还是指针变量间接指向变量自加。
① b=*p++;
按自右向左结合的原则,表达式 *p++ 求值序顺为先“ ++ ”后“ * ”,即: *(p++) 。由于“ ++ ”在 p 之后为后置 ++ 运算符,所以表达式的实际操作是先取 *p 值,后进行 p++ 的自加操作。即赋值表达式 b=*p++; 等同于下面两条语句:
b=*p; // b=*p=a[0]=100
p++; //p=p+sizeof(int)= 1004
最后运算的结果为 b=100,p=1004 指向 a[1] 。
② b=*++p;
按自右向左结合的原则,表达式 *++p 求值顺序为先“ ++ ”后“ * ”,即: *(++p) 。由于 ++ 在 p 之前为前置 ++ 运算符,所以表达式的实际操作是进行 ++p 的自加操作,后取 *p 值。即赋值表达式 b=*++p; 等同于下面两条语句:
++p; //p=p+sizeof(int)= 1008 ,指向 a[2]
b=*p; // b=*p=a[2]=300
最后运算的结果为 b=300,p=1008 指向 a[2] 。
③ b=(*p)++;
由于括号内优先运算,所以表达式先取出 *p (即 a[2] )的值并赋给 b ,然后将 *p 的值即 a[2] 内容加 1 。所以表达式等同于下面两条语句:
b=*p; //b=a[2]=300
a[2]++ ; // a[2]=300+1=301
④ b=*(p++);
由①可知,该表达式等同于 *p++ ,运算结果为:
b=*p; //b=a[2]=301
p++; // p=p+sizeof(int)=1012 ,指向 a[3]
⑤ b=++*p ;
该表达式先进行“ * ”运算,再进行“ ++ ”运算,即先取出 *p 的值,再将该值加 1 。因此表达式实际进行了如下运算: b=++(*p)=++a[3]=400+1=401; p 仍指向 a[3] 不变。
将上述讨论中各语句汇总为例题如下:
【例 7.5 】指针运算符“ * ”、“ & ”、“ ++ ”优先级与结合律示例。设 a[0] 首地址为 0x0065FDE8
# include <iostream.h>
main()
{ int a[4]={100,200,300,400},b;
int *p=&a[0]; //p 指向 a[0]
cout<<'/t'<<"p="<<p<<endl; // 输出数组 a 首地址
b=*p++; //b=*p=a[0]=100 ; p++ ; 指向 a[1]
cout<<"b="<<b<<'/t'<<"p="<<p<<endl;
b=*++p; //++p; 指向 a[2] , b=*p=a[2]=300 ;
cout<<"b="<<b<<'/t'<<"p="<<p<<endl;
b=(*p)++; //b=*p=a[2]=300;a[2]++;a[2]=301
cout<<"b="<<b<<'/t'<<"p="<<p<<endl;
b=*(p++); //b=*p=a[2]=301;p++; 指向 a[3]
cout<<"b="<<b<<'/t'<<"p="<<p<<endl;
b=++*p; //b=++a[3]=401; p++; 指向 a[3]
cout<<"b="<<b<<'/t'<<"p="<<p<<endl;
}
运行结果为:
p=0x0065FDE8
b=100 p=0x0065FDEC
b=300 p=0x0065FDF0
b=300 p=0x0065FDF0
b=301 p=0x0065FDF4
b=401 p=0x0065FDF4
7.2 指针与数组
指针是变量、数组、函数的内存地址。指针变量是用于存放指针的变量。上节介绍的指针是变量内存地址 (p=&a) ,介绍了使用指针变量间接访问变量( *p )的方法。本节将介绍用指针作为数组的内存地址,用指针变量间接访问数组元素的方法 ( 如例 7.4) 。
使用指针变量来处理数组元素,不仅可使程序紧凑,而且还可提高程序的运算速率。
7.2.1 一维数组与指针
1.数组指针
数组的首地址称为数组指针。 例如:
图 7.5 一维数组与指针 |
p = a → |
p+i=a+i → |
数组 a :数组指针 = &a[0]=a 。
2.数组指针变量
存放数组元素地址的变量称为数组指针变量 。如:
int a[5];
int *p=&a[0]; // p 为数组指针变量
或 int *p=a;
注意:数组名 a 不能用来进行“ = ”、“ ++ ”、“ — — ”等运算。如: a=&a[1]; a++; 都是错误的。
当指针变量指向数组首地址后,就可使用该指针变量对数组中任何一个元素变量进行存取操作。
【例 7.6 】用指针变量访问数组元素。
# include <iostream.h>
void main( void)
{ int a[5]={0,1,2,3,4},i,j,*p,n=5;
p=a; // 数组首地址 a 赋给 p
for (i=0;i<n;i++)
{ cout <<*p<<'/t'; // 输出 *p=a[i]
p++; //p 指向下一个元素 a[i+1]
}
cout<<endl;
p=a;
for (i=0;i<n;i++) cout <<*(p+i)<< '/t'; //p+i 指向 a[i],*(p+i) 表示 a[i]
cout<<endl;
for (i=0;i<n;i++) cout <<*(a+i)<< '/t'; //a+i 指向 a[i],*(a+i) 表示 a[i]
cout<<endl;
for (i=0;i<n;i++) cout <<p[i]<< '/t'; //p[i] 等价于 a[i]
cout<<endl;
}
执行程序后,输出:
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4
由上例可以看出,访问数组元素值有三种方法:
( 1 )移动指针变量 (p++) ,依次访问数组元素 (*p) 。
( 2 )指针变量不变,用 p+i 或 a+i 访问数组第 i 个元素。
( 3 )以指针变量名 p 作为数组名 p[i] 访问数组元素 a[i] 。
3.数组元素的引用
对一维数组 a[ ] 而言,当 p=a 时:
①第 i 个元素地址: &a[i]= p+i=a+i 。
②第 i 个元素值: a[i]= *(p+i) =*(a+i)=p[i] 。
其中 p[i] 的运行效率最高。
由上所述可知:一维数组的第 i 个元素可用四种方式引用,即: a[i] 、 *(p+i) 、 *(a+i) 、 p[i] 。
例 7.61 :用数组指针的四种方法求一维数组中的最大值。
解:方法一:用指针变量名 p 代替数组名 a ,即用 p[i] 代替 a[i]
#include <iostream.h>
#define N 10
void main(void)
{ float a[N],max,*p=a;
int i;
cout<<"Input integers:";
for (i=0;i<N;i++)
cin>>p[i];
max=p[0];
for (i=0;i<N;i++)
if (p[i]>max)
max=p[i];
cout<<"max="<<max<<endl;
}
方法二:移动指针变量 p++ ,用 *p 访问 a[i]
#include <iostream.h>
#define N 10
void main(void)
{ float a[N],max, *p=a;
cout<<"Input integers:";
for (p=a;p<a+N;p++)
cin>>*p;
for (p=a,max=*p;p<a+N;p++)
if (*p>max)
max=*p;
cout<<"max="<<max<<endl;
}
方法三:使用 *(p+i) 访问第 i 个元素 a[i]
#include <iostream.h>
#define N 10
void main(void)
p → |
图 7.6 二维数组在内存中存放次序 |
int i;
cout<<"Input integers:";
for (i=0;i<N;i++)
cin>>*(p+i);
max=*p;
for (i=0;i<N;i++)
if (*(p+i)>max)
max=*(p+i);
cout<<"max="<<max<<endl;
}
7.2.2 二维数组与指针
1.二维数组元素在内存中的存放方式
( 1 )二维数组元素在内存按行顺序存放
定义二维整型数组 a[3][3] ,设为数组 a 分配的内存空间从 1000 开始到 1035 为止,则数组中各元素在内存中按行存放次序如图 7.6 所示。因此,与一维数组类似:
( 2 )可用指针变量来访问二维数组元素。
【例 7.7 】用指针变量输出二维数组各元素的值。
# include <iostream.h>
void main(void)
{ int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int *p=&a[0][0]; // 将二维数组首地址赋给指针变量 p
for (int i=0;i<9;i++)
{ cout<<*p<<'/t'; // 输出二维数组中第 i 个元素值
p++; // 指针变量 p 加 1 ,指向下一个元素
}
}
程序执行后输出结果为:
1 2 3 4 5 6 7 8 9
问题:对一维数组可用四种方式( *p 、 *(p+i) 、 *(a+i) 、 p[i] )访问其元素 a[i] ,对二维数组可用哪几种方式访问其元素 a[i][j] ,为了解访问二维数组元素的方法,必须了解三个地址概念,即:二维数组行首地址、行地址、元素地址,现介绍如下。
2 .二维数组行首地址
二维数组各元素按行排列可写成如图 7.7 所示矩阵形式,按 C++ 规定,二维数组 a[3][3] 可看成是由三个一维数组元素 a[0] 、 a[1] 、 a[2] 组成。即: a=(a[0] , a[1] , a[2]) ,其中: a[0] 、 a[1] 、 a[2] 是分别表示二维数组 a[3][3] 的第 0 、 1 、 2 行元素。
即: a[0]=(a[0][0],a[0][1],a[0][2])
a[0] a[1] a[2] |
图 7.7 二维数组与指针 |
a[2]=(a[2][0],a[2][1],a[2][2])
因为数组名可用来表示数组的首地址,所以一维数组名 a[i] 可表示一维数组 (a[i][0],a[i][1],a[i][2]) 的首地址 &a[i][0] ,即可表示第 i 行元素的首地址。
因此,在二维数组 a 中:
( 1 )第 i 行首地址(即第 i 行第 0 列元素地址):用 a[i] 表示, a[i]=&a[i][0]
一维数组的第 i 个元素地址可表示为:数组名 +i 。因此一维数组 a[i] 中第 j 个元素 a[i][j] 地址可表示为: a[i]+j
( 2 )元素 a[i][j] 的地址:用 a[i]+j 来表示,而元素 a[i]][j] 的值为: *(a[i]+j) 。
【例 7.8 】定义一个 3 行 3 列数组,输出每行的首地址及所有元素值。
# include <iostream.h>
void main(void)
{ int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
for (int i=0;i<3;i++)
{ cout<<"a[" <<i<<"]="<<a[i]<< "="<<&a[i][0]<<endl;
for (int j=0;j<3;j++)
cout<<"a[" <<i<<"]["<<j<<"]="<<*(a[i]+j)<< "="<<a[i][j]<<endl;
}
}
程序执行后输出:
a[0]=0x0065FDD4=0x0065FDD4
a[0][0]=1=1
a[0][1]=2=2
a[0][2]=3=3
a[1]=0x0065FDE0=0x0065FDE0
a[1][0]=4=4
a[1][1]=5=5
a[1][2]=6=6
a[2]=0x0065FDEC=0x0065FDEC
a[2][0]=7=7
a[2][1]=8=8
a[2][2]=9=9
由此例输出结果可看出 a[i]=&a[i][0] (i=0,1,2) ,这表明 a[i] 确实可以表示第 i 行首地址(即第 i 行第 0 列地址) &a[i][0] 。
3.二维数组行地址
为了区别数组指针与指向一维数组的指针, C++ 引入了行地址的概念。
规定二维数组 a 中:
( 1 )第 i 行的行地址:用 a+i 或 &a[i] 表示。
( 2 )行地址与行首地址区别
1 )行地址的值与行首地址的值是相同的,即: a+i=&a[i]=a[i]=&a[i][0]
2 )两者类型不同,行地址 a+i 与 &a[i] 只能用于指向一维数组的指针变量,而不能用于普通指针变量, 例如:
int a[3][3];
int *p=a+0; // 编译第二条指令时将会出错,编译系统提示用户 p 与 a+0 的类型不同。
3 )数组名 a 表示第 0 行的行地址,即 a=a+0=&a[0] 。
4.二维数组的元素地址与元素值
因为 a[i]=*&a[i]= *(a+i) ,所以 *(a+i) 可以表示第 i 行的首地址。因此二维数组 a :
( 1 )第 i 行首地址有三种表示方法: a[i] 、 *(a+i) 、 &a[i][0] 。
由此可推知:
( 2 )元素 a[i][j] 的地址有四种表示方法: a[i]+j 、 *(a+i)+j 、 &a[i][0]+j 、 &a[i][j]
( 3 )元素 a[i][j] 值也有四种表示方法: *(a[i]+j) 、 *(*(a+i)+j) 、 *(&a[i][0]+j) 、 a[i][j]
现将二维数组有关行地址、行首地址、元素地址、元素值的各种表示方式总结归纳如表 7.1 所示:
表 7.1 二维数组 a 的行地址、行首地址、元素地址、元素值的各种表示方式
行地址、元素地址、元素值 | 表示方式 |
第 i 行行地址 | a+i 、 &a[i] |
第 i 行首地址 ( 第 i 行第 0 列地址 ) | a[i] 、 *(a+i) 、 &a[i][0] |
元素 a[i][j] 的地址 | a[i]+j 、 *(a+i)+j 、 &a[i][0]+j 、 &a[i][j] |
第 i 行第 j 列元素值 | *(a[i]+j) 、 *(*(a+i)+j) 、 *(&a[i][0]+j) 、 a[i][j] |
【例 7.9 】定义二维数组 a[3][3] ,用二种方式输出行地址,用三种方式输出行首地址,用四种方式输出所有元素地址及元素值。
# include <iostream.h>
void main(void)
{ int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
for (int i=0;i<3;i++)
{ cout<<"&a["<<i<<"]="<<&a[i]<<"="<<a+i<<endl; // 输出第 i 行行地址
cout<<"a["<<i<<"]="<<a[i]<<"="<<*(a+i)<<"="<<&a[i][0]<<endl; // 行首地址
for (int j=0;j<3;j++)
{ cout<<"&a["<<i<<"]["<<j<<"]="<<a[i]+j<<"="<<*(a+i)+j<<"="<<
&a[i][0]+j<<"="<<&a[i][j]<<endl; // 输出元素 a[i][j] 的地址
cout<<"a["<<i<<"]["<<j<<"]="<<*(a[i]+j)<<"="<<*(*(a+i)+j)<<
"="<<*(&a[i][0]+j)<<"="<<a[i][j]<<endl; // 输出元素 a[i][j] 的值
}
}
}
程序执行后输出结果为:
&a[0]= 0x0065FDD4=0x0065FDD4 // 第 0 行行地址的两种表示方法输出结果相同
a[0]= 0x0065FDD4=0x0065FDD4=0x0065FDD4 // 第 0 行首地址的三种表示方法输出结果相同
&a[0][0]= 0x0065FDD4=0x0065FDD4=0x0065FDD4=0x0065FDD4
a[0][0]=1=1=1=1 // 第 0 行第 0 列元素值的四种表示方法输出结果相同
……
&a[2][2]= 0x0065FDF4=0x0065FDF4=0x0065FDF4=0x0065FDF4
a[2][2]=9=9=9=9
此例可说明表 7.1 中的二维数组 a 的行地址、行首地址、元素地址、元素值的各种表示方式是正确的。
7.2.3字符串与指针
1.字符串与字符串指针
( 1 )字符串指针:是字符串的首地址;
( 2 )字符串指针变量:存放字符串元素地址的变量;
( 3 )定义格式: char *< 指针变量 >= “字符串”;
当字符串作为一个整体使用时,用字符串指针变量来处理字符串更加方便。用字符串指针变量处理字符串时,只关心是否处理到字符串结束符 ’/0’ ,而不关心字符串的大小。
图 7.8 用指针拷贝字符串 |
p2 → |
字符串 s1 |
字符数组 s2 |
|
|
p1 → |
# include <iostream.h>
# include <string.h>
void main(void)
{ char *p1="I am a student" ;
char s1[30],s2[30];
strcpy( s1,p1); // 用命令拷贝字符串
char *p2=s2; // 将数组 s2 首地址赋 p2
for (;*p2++=*p1++;); // 用指针拷贝字符串
cout<<"s1="<<s1<<endl;
cout<<"s2="<<s2<<endl;
}
执行后输出:
s1= I am a student
s2= I am a student
说明:
( 1 )编译系统执行定义语句 char *p1="I am a student" 时,首先为字符串 "I am a student " 分配内存空间,然后将该内存空间首地址赋给指针变量 p1 。
( 2 )用指针变量拷贝字符串过程是,先将指针变量 p2 指向字符串数组 s2 的首地址,然后通过赋值语句 *p2=*p1 将字符由字符串 s1 拷贝到 s2 中,再移动 p1 、 p2 到下一个字符单元,依次循环直到字符串结束符 ’/0’ 为止,如图 7.8 所示。全部拷贝过程用一个 for 语句完成。在 for ( ;*p2++=*p1++; )语句中,表达式: *p2++=*p1++ 等价于下列三条语句,
*p1=*p2; // s2[i]=s1[i] ,将指针 p1 所指 s1[i] 赋给指针 p1 所指 s2[i] 。
p1++; // 指针 p1 加 1 指向 s1 的下一个元素
p2++; // 指针 p2 加 1 指向 s2 的下一个元素
上述语句不断循环,直到 p1 指向结束字符 ’/0’=0 时, for 语句因条件为假而结束。从而完成字符串 s1 拷贝到字符数组 s2 的任务。
( 3 )指针变量 p1 可以作为拷贝函数 strcpy(s1,p1) 的参数 。
2.字符型指针变量与字符数组的区别
( 1 )分配内存
设有定义字符型指针变量与字符数组的语句如下:
char *pc ,str[100];
则系统将为字符数组 str 分配 100 个字节的内存单元,用于存放 100 个字符。而系统只为指针变量 pc 分配 4 个存储单元,用于存放一个内存单元的地址。
( 2 )初始化赋值含义
字符数组与字符指针变量的初始化赋值形式相同,但其含义不同。例如:
char str[ ] ="I am a student ! " ,s[200];
char *pc="You are a student ! " ;
对于字符数组,是将字符串放到为数组分配的存储空间去,而对于字符型指针变量,是先将字符串存放到内存,然后将存放字符串的内存起始地址送到指针变量 pc 中。
( 3 )赋值方式
字符数组只能对其元素逐个赋值,而不能将字符串赋给字符数组名。对于字符指针变量,字符串地址可直接赋给字符指针变量。例如:
str="I love China! "; // 字符数组名 str 不能直接赋值,该语句是错误的。
pc="I love China! "; // 指针变量 pc 可以直接赋字符串地址,语句正确
( 4 )输入方式
可以将字符串直接输入字符数组,而不能将字符串直接输入指针变量。但可将指针变量所指字符串直接输出。
例如: cin >> str // 正确
cin >> pc // 错误
cout<<pc // 正确
( 5 )值的改变
在程序执行期间,字符数组名表示的起始地址是不能改变的,而指针变量的值是可以改变的。例如: str=str+5; // 错误
pc=str+5; // 正确
小结 字符数组 s[100] 指针变量 pc
( 1 )分配内存 分配 100 个单元 分配 4 个单元。
( 2 )赋值含义 字符串放到数组存储空间 先将字符串存放到内存
将存放串的首地址送到 pc 中。
( 3 )赋值方式 只能逐个元素赋值 串地址可赋给 pc
( 4 )输入方式: 串直接输入字符数组 不能将字符串直接输入指针变量
( 5 )值的改变: 字符数组首地址不能改变 指针变量的值可以改变
由以上区别可以看出,在某些情况下,用指针变量处理字符串,要比用数组处理字符串方便。
例 7.11 定义一个字符数组,输入一个字符串到数组中,用指针变量求字符串长度,并输出字符串。
# include <iostream.h>
void main(void)
{ char s[100],*p=s;
int l=0;
cout<<”Input String : ”;
cin >>s;
while (*p!=0)
{ p++;
l++;
}
cout<<p<<endl;
cout<<”l=”<<l<<endl;
}
注意:无论是指针变量、数组指针变量还是字符串指针变量,其定义格式是相同的,即: < 数据类型 > *< 指针变量名 > ; 只有赋给其地址时才能确定是属于哪一类指针变量。
如: int x=10,a[5]={1,2,3,4,5};
char s[100]=”ABCDE”;
int *p1=&x; // 指针变量
int *p2=a; // 数组指针变量
char *p3=s; // 字符串指针变量
因此,上述指针变量具有统一的定义格式,属同类指针变量。下面将讲述具有不同定义格式的指针变量。
7.3 指针数组和指向指针的指针变量
7.3.1指针数组
( 1 )指针数组:由若干个同类型指针变量所组成的数组称为指针数组,指针数组中每一个元素都是一个指针变量。
( 2 )指针数组定义格式为:
〔存储类型〕 < 类型 > *< 数组名 >[< 数组长度 >] ;
其中: *< 数组名 >[< 数组长度 >] 表示定义了一个指针数组,类型指明指针数组中每个元素所指向的数据类型。例如:
int *pi[4]; // 定义由 4 个整型指针元素 pi[0] 、 pi[1] 、 pi[2] 、 pi[3] 组成的整型指针数组。
float *pf[4]; // 定义由 4 个实型指针元素 pf[0] 、 pf[1] 、 pf[2] 、 pf[3] 组成的实型指针数组。
【例 7.11 】用指针数组输出字符串数组中各元素的值。
图 7.9 指针数组指向字符串数组 |
|
|
指针数组 |
字符串数组 |
|
|
void main(void )
{ int i;
char c[3][4]={"ABC","DEF","GHI"};
char *pc[3 ]={c[0],c[1],c[2]};
for (i=0;i<3;i++)
cout<<"c["<<i<<"]="<<c[i]<<"="<<pc[i]<<'/n';
}
程序执行后,输出 :
c[0]=ABC=ABC
c[1]=DEF=DEF
c[2]=GHI=GHI
注意:除了用初始化方法给字符串数组 c 赋值外,也可用 cin 语句输入字符串到 c[i] 中。
【例 7.12 】将若干个字符串按升序排序后输出。
( a ) 排序前 |
|
|
图 7.10 用指针数组对字符串排序 |
s[0] s[1] s[2] s[3] s[4]
|
|
|
( b ) 排序后 |
#include <iostream.h>
# include <string.h>
void main(void)
{ char *s[ ]={ "
PASCAL","BASIC","VC","DELPHI","VFP"};
char *pc;
int i,j;
for (i=0;i<4;i++)
{ for (j=i+1;j<5;j++)
if (strcmp (s[i],s[j])>0)
{ pc=s[i];s[i]=s[j];s[j]=pc;
}
}
for ( i=0;i<5;i++) cout<<s[i]<<endl;
}
执行程序后,输出:
BASIC
DELPHI
PASCAL
VC
VFP
注意:字符串比较必须用比较函数 strcmp(s[i],s[j]) 进行,从此题可以看出字符串指针变量是可以作为字符串函数的参数。
7.3.2指向一维数组的指针变量
( 1 )指向一维数组的指针变量的作用:用于表示二维数组某行各元素值。
( 2 )指向一维数组的指针变量定义格式: < 类型 > (*< 指针变量名 >)[< 数组的列数 >];
(*< 指针变量名 >) 定义了指向一维数组的指针变量,该指针变量可指向二维数组中某行。例如: int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int (*p)[3];
p=&a[0];
( *p )说明 p 是一个指针变量,再与 [3] 结合,表示 p 所指向的一维数组( *p ) [3] 是由三个元素 (*p)[0] 、 (*p)[1] 、 (*p)[2] 组成。如图 7.11 所示。
p |
|
图 7.11 指针变量 p 的指向 |
a[0] a[1] a[2] |
|
( 3 )指向二维数组 a 的第 i 行: p=&a[i]; 或 p=a+i;
( 4 )指向一维数组指针变量的引用: (*p)[0]=a[i][0] 、 (*p)[1]=a[i][1] 、… (*p)[n-1]=a[i][n-1]
( 5 )用 p=a+0 或 p=&a[0] 可使 p 指向第 0 行,然后在循环语句中用 p++ 指向数组的下一行。
上例中,赋值语句 p=&a[0] ;将 p 指向二维数组 a 的第 0 行,因此 p 所指的一维数组可表示 a 的第 0 行各元素,即: (*p)[0]=a[0][0] 、 (*p)[1]=a[0][1] 、 (*p)[2]=a[0][2] 。
结论:定义了指向一维数组的指针变量 p 后,只要将二维数组 a 第 i 行的行地址 &a[i] 赋给 p ,则可用 (*p)[0] 、 (*p)[1] 、… (*p)[n-1] 来表示数组 a 第 i 行的元素 a[i][0] 、 a[i][1] 、… a[i][n-1] 。
【例 7.13 】用指向一维数组的指针变量输出二维数组中各元素。
# include <iostream.h>
void main (void)
{ int i,j;
int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int (*p)[3]; // 定义指向一维数组的指针变量 p
cout<<" 用指向一维数组的指针变量输出数组各元素 "<<endl;
for (i=0;i<3;i++)
{ p=&a[i]; // 将第 i 行的行地址赋给 p
for (j=0;j<3;j++)
out<<"a["<<i<<"]["<<j<<"]="<<(*p)[j]<<'/t'; // 用 (*p)[j] 输出 a[i][j] 的值
cout<<endl;
}
}
用指向一个维数组的指针变量输出二维变量数组各元素如下:
a[0][0]=1 a[0][1]=2 a[0][2]=3
a[1][0]=4 a[1][1]=5 a[1][2]=6
a[2][0]=7 a[2][1]=8 a[2][2]=9
思考题:上述程序用 p++ 如何实现。
说明:
( 1 ) p 所指一维数组的长度应与数组 a 的列数相同 ,如 int (*p)[3] 中的长度与 int a[3][3] 中的列数均为 3 。
( 2 )只能将行地址 &a[i] 或 a+i 赋给指向一维数组的指针变量 p ,而不能将行首地址 a[i] 或 * ( a+i )赋给 p 。行首地址 a[i] 或 * ( a+i )只能赋给指向数组元素的指针变量。
( 3 )对指向数组元素的指针变量 q 加 1 后, q 指向数组的下一个元素。指向一维数组的指针变量 p 加 1 后, p 指向数组的下一行。
例如:
float (*p) [3] , *q , a[3][3];
q=a[0]; // q 为指向数组元素的指针变量,赋行首地址 a[0] 或元素地址 &a[0][0] 。
p=&a[0] ; // p 为指向一维数组的指针变量,赋行地址 &a[0] 。
q++;
p++;
在上例中, q 是指向数组元素的指针变量,所以 q++ 后, q 指向数组 a 中的第 2 个元素 a[0][1] 。而 p 是指向一维数组的指针变量,所以 p++ 后, p 指向数组 a 的第 1 行 a[1] 。
7.3.3指向指针的指针变量(自学)
( 1 )二级指针:定义指针变量 p 后,又定义指向 p 的指针变量 pp ,则称 pp 为指向指针的指针变量,简称为二级指针变量。
( 2 )二级指针变量的定义格式:
〔存储类型〕 < 类型 > **< 指针变量名 > ;
定义二级指针变量时,在其前面加两个“ * ”。同样定义三级指针变量时,要加三个“ * ”。 C++ 对定义指针的级数没有限制。通常只使用一、二、三级指针变量。
【例 7.14 】
p 3 : 2004 |
p 2 : 2000 |
p 1 : 1000 |
a: 10 |
2008 2004 2000 1000 |
图 7.12 三级指针示意图 |
# include <iostream.h>
void main( void)
{ int a=10;
int *p1,** p2,***p3;
p1=&a;p2=&p1;p3=&p2;
cout<<"a="<<*p1<<"="<<**p2<<"=" <<***p3;
}
若系统为 a 、 p1 、 p2 、 p3 分配的内存地址如图 7.12 所示,则执行程序后,输出:
a=10=10=10
三级指针 p1 、 p2 、 p3 与 a 之间的关系如图 7.12 所示,指针变量 p1 的值为变量 a 的地址 , 指针变量 p2 的值为指针变量 p1 的地址 , 指针变量 p3 的值为指针变量 p2 的地址。在二级指针变量 p2 前加一个“ * ”为地址,加两“ * ”才能得到数据。
7.4 指针与函数
7.4.1指针变量作为函数参数
( 1 )形参:指针变量
( 2 )实参:变量地址或指针变量
( 3 )参数传送方式:传地址
用指针变量作为函数形参时,实参为变量地址,因此参数的传送方式为传地址,即将实参变量地址传送给形参指针变量。使实参与形参占用同一内存单元,对形参的修改就是对实参的修改,所以传地址可对实参单元进行修改,并返回修改值。
当函数需要返回多个参数值时,可使用指针变量作为参数来实现。而传值方式的函数调用,只能返回一个函数值,其形参值是无法返回的。
图 7.13 通过指针实现数据交换 |
p |
20.5 |
10.5 |
q |
(b) 数据交换后 |
(a) 数据交换前 |
p |
10.5 |
20.5 |
q |
|
*p=x |
*q=y |
temp |
# include <iostream.h>
void swap (float *p, float * q)
{ float temp;
temp=*p;
*p=* q;
*q=temp;
}
void main( void)
{ float x=10.5,y=20.5;
cout<<"x="<<x<<'/t'<<"y="<<y<<endl;
swap (&x,&y);
cout<<"x="<<x<<'/t'<<"y="<<y<<endl;
}
程序执行后输出:
x=10.5 y=20.5
x=20.5 y=10.5
在调用交换函数 swap(&x,&y) 过程中,变量 x 、 y 的地址 &x 、 &y 作为实参传送给形参 p 、 q ,因此,参数的传送过程相当于执行了两条指针变量的赋值语句:
p=&x;
q=&y;
从而,使指针变量 p 与 q 分别指向变量 x 与 y 。使 *p=x, *q=y 。因此,在交换函数中对 *p 与 *q 的数据交换,就是对变量 x 与 y 的交换。如图 7.13 所示。
若将上题改为传值调用,交换函数的形参改为实型变量 x1 、 y1 。
void swap(flaot x1,float y1)
{ int temp;
temp=x1;x1=y1;y1=temp;
}
在 main( ) 主函数中,调用函数实参改为: swap (x,y);
图 7.14 通过指针实现数据交换 |
(a) 数据交换前 |
10.5 |
10.5 |
20.5 |
20.5 |
|
x1 |
y1 |
temp |
x |
y |
实参 x 传给形参 x1 |
实参 y 传给形参 y1 |
(b) 数据交换后 |
x1 |
x |
y |
y1 |
10.5 |
20.5 |
10.5 |
20.5 |
7.4.2数组与指针作为函数参数
由于数组名为数组的起始地址,当把数组名作为函数参数时,其作用与指针相同 , 均为传地址。数组与指针作为函数参数有四种情况:
( 1 )函数的实参为数组名,形参为数组。
( 2 )函数的实参为数组名,形参为指针变量,。
( 3 )函数的实参为指针变量,形参为数组。
( 4 )函数的实参为指针变量,形参为指针变量。
这四种形式的效果是一样的。下面用例题来说明数组与指针作为函数参数的四种情况
【例 7.16 】用指针与数组作为函数参数,用四种方法求整型数组的最大值。
#include <iostream.h>
int max1( int a[ ],int n) // 形参为数组名
{ int i,max=a[0];
for (i=1;i<n;i++)
if (a[i]>max) max=a[i];
return max;
}
int max2( int *p,int n) // 形参为指针
{ int i,max=*(p+0);
for (i=1;i<n;i++)
if (*(p+i)>max) max=*(p+i);
return max;
}
int max3( int a[ ],int n) // 形参为数组名
{ int i,max=*(a+0);
for (i=1;i<n;i++)
if (*(a+i)>max) max=*(a+i);
return max;
}
int max4(int *p,int n) // 形参为指针
{ int i,max=p[0];
for (i=1;i<n;i++)
if (p[i]>max) max=p[i];
return max;
}
void main( void)
{ int b[ ]={1,3,2,5,4,6},*pi;
cout<<"max1="<<max1(b,6)<<endl; // 实参为数组名,形参为数组
cout<<"max2="<<max2(b,6)<<endl; // 实参为数组名,形参为指针变量
pi=b;
cout<<"max3="<<max3(pi,6)<<endl; // 实参为指针变量,形参为数组
pi=b;
cout<<"max4="<<max4(pi,6)<<endl; // 实参为指针变量,形参指针变量
}
程序执行后输出结果
max1=6
max2=6
max3=6
max4=6
调用函数 max1() 的过程是将实参数组 b 的地址传送给形参数组 a ,使数组 a 与 b 共用同一内存区,然后通过形参数组 a 求出最大值 max ,并返回给调用函数 max1 。
调用函数 max2() 的过程是将实参数组 b 的地址传送给形参指针 p ,使指针变量 p 指向数组 b 的首地址。然后用 *(p+i) 表示数组 b 的第 i 个元素 b[i] ,求出数组 b 的最大值 max ,并返回给调用函数 max2 。
调用函数 max3() 的过程是,首先将数组 b 的首地址赋给指针变量 pi ,然后通过实参指针变量 pi 将数组 b 的首地址传送给形参数组 a[] ,然后用 *(a+i) 表示数组第 i 个元素 a[i] ,求出数组 a 的最大值 max ,并返回给调用函数 max3 。
调用函数 max4() 的过程是,首先将数组 b 的首地址赋给指针变量 pi ,然后通过实参指针变量 pi 将数组 b 的首地址传送给形参指针 p ,使指针变量 p 指向数组 b 的首地址。用 p[i] 表示数组 b 的第 i 个元素 b[i] ,求出数组 b 的最大值 max ,并返回给调用函数 max4 。
由四个求最大值函数 max1~max4 可以看出一维数组 a 第 i 个元素的表示方式有四种:
a[i],*(p+i),*(a+i),p[i] 。在程序的运算过程中, p[i] 的效率最高。
【例 7.17 】用指针与数组作为函数参数,用四种方法实现一维整型数组的升序排序(选择法)。
#include <iostream.h>
void sort1( int a[ ],int n) // 形参为数组名
{ int i,j,temp;
for (i=0;i<n-1;i++)
for (j=i+1;j<n;j++)
if (a[i]>a[j])
{ temp=a[i]; a[i]=a[j]; a[j]=temp; }
}
void sort2( int *p,int n) // 形参为指针
{ int i,j,temp;
for (i=0;i<n-1;i++)
for (j=i+1;j<n;j++)
if (*(p+i)>*(p+j))
{ temp=*(p+i); *(p+i)=*(p+j); *(p+j)=temp; }
}
void sort3( int a[ ],int n) // 形参为数组名
{ int i,j,temp;
for (i=0;i<n-1;i++)
for (j=i+1;j<n;j++)
if (*(a+i)>*(a+j))
{ temp=*(a+i); *(a+i)=*(a+j); *(a+j)=temp; }
}
void sort4( int *p,int n) // 形参为指针
{ int i,j,temp;
for (i=0;i<n-1;i++)
for (j=i+1;j<n;j++)
if (p[i]>p[j])
{ temp=p[i]; p[i]=p[j]; p[j]=temp; }
}
void main( void)
{ int a1[6]={1,3,2,5,4,6},*pi,i;
int a2[6]={1,3,2,5,4,6};
int a3[6]={1,3,2,5,4,6};
int a4[6]={1,3,2,5,4,6};
sort1(a1,6); // 实参为数组名,形参为数组
sort2(a2,6); // 实参为数组名,形参为指针变量
pi=a3;
sort3(pi,6) ; // 实参为指针变量,形参为数组
pi=a4;
sort4(pi,6) ; // 实参为指针变量,形参指针变量
for (i=0;i<6;i++) cout<<a1[i]<<'/t';
cout<<endl;
for (i=0;i<6;i++) cout<<a2[i]<<'/t';
cout<<endl;
for (i=0;i<6;i++) cout<<a3[i]<<'/t';
cout<<endl;
for (i=0;i<6;i++) cout<<a4[i]<<'/t';
cout<<endl;
}
程序执行后输出结果:
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
由程序执行的输出结果可以看出,四个排序函数的运行结果是相同的。由于数组与指针作为参数均属于传地址,所以在各排序函数 sort1() 、 sort2() 、 sort3() 、 sort4() 内的排序操作就是对实参数组 a1 、 a2 、 a3 、 a4 的排序操作,因此在主函数中,可直接输出数组 a1 、 a2 、 a3 、 a4 排序后的结果。
7.4.3返回指针值的函数
( 1 )返回指针值的函数:是函数返回值为指针类型的函数。
( 2 )定义格式: < 类型 > *< 函数名 >(< 形参 >)
{< 函数体 >}
其中:“ * ”说明函数返回一个指针,而该指针所指向数据类型由 < 类型 > 指定。
( 3 )函数的定义与调用
由于函数返回指针值,所以函数体中必须返回变量地址或指针变量。
调用函数值只能赋给指针变量。
【例 7.19 】用返回指针值的函数求两个整数的最大值。
#include <iostream.h>
int * max(int x,int y)
{ if (x>y) return &x;
a=5 |
b=9 |
p=1004 |
x=5 |
y=9 |
1000 |
1004 |
}
void main(void)
{ int a=5,b=9,*p;
p=max(a,b);
cout<<*p<<endl;
}
该程序定义了一个返回整型指针值的函数 int * max(int x,int y) ,在主函数中调用 max ( a,b )函数时,将实参 a 、 b 值传送给形参 x 、 y 。在 max() 函数中,将 x 与 y 中最大值的单元的地址返回给指针变量 p ,最后用 *p 输出。
【例 7.21 】输入两个字符串,将第二个字符串拼接到第一个字符串的尾部,然后输出拼接后的字符串。
# include <iostream.h>
# include <string.h>
char *stringcat(char * p1,char *p2)
{ char *p=p1; // 将目标串首地址赋给指针变量 p 。
while(*p1++); // 指针 p1 移到 s1 的串尾
p1 ―― ;
while (*p1++=*p2++); // 将源串 s2 中的字符依次复制到目标串 s1 中。
return p; // 返回指向目标串首地址的指针 p 。
}
void main(void)
{ char s1[200],s2[100];
cout<<" 输入第一个字符串: ";
cin.getline( s1,100);
cout<<" 输入第二个字符串: ";
cin.getline( s2,100);
cout<<" 拼接后的字符串: ";
cout<<stringcat(s1,s2)<<endl; // 将字符串 s2 拼接到字符串 s1 后输出 s1 的内容。
}
程序执行后提示:
输入第一个字符串: ABCD
输入第二个字符串: EFGH
拼接后的字符串: ABCDEFGH
程序在输入二个字符串到 s1 、 s2 后,调用返回字符指针值的函数 stringcat() 。调用过程中,先将实参 s1 、 s2 传送给指针变量 p1 、 p2 。循环语句: while(*p1++) 将指针 p1 由字符串 s1 首移到串尾。循环语句 while (*p1++=*p2++) 将字符串 s2 中的字符依次赋给字符串 s1 。最后通过 return p 返回字符串 s1 首地址给调用函数。
7.4.4函数指针变量
( 1 )函数指针:是函数的入口地址;
( 2 )函数指针变量:是用于存放函数指针的变量,也即存放函数入口地址的变量。
( 3 )函数指针变量的定义格式: < 类型 > (*< 变量名 >) (< 参数表 >) ;
其中: (*< 变量名 >) 表示一个指针变量, (< 参数表 >) 表示一个函数,两者结合表示该变量是函数指针变量。
例如: float (*pf) (flaot x); 定义了一个名为 pf 的函数指针变量。
( 4 )函数指针变量的赋值:函数名赋给函数指针变量
例如: float (*pf)( float); // 定义名为 pf 函数指针变量
float f( float); // 定义名为 f 的实型函数
pf=f; // 将函数 f() 的入口地址赋给函数指针变量 pf
注意:只能将与函数指针变量具有同类型、同参数的函数名赋给函数指针变量。
例如: float (*pf1) (void);
float f( float);
pf=f; // 错误 , 因为函数 f 与函数指针变量 pf 的参数类型不同。
对函数指针变量进行赋值后,可用该指针变量调用函数。
( 5 )调用函数的格式:( *< 指针变量 > ) (< 实参表 >) ;
或 : < 指针变量 >(< 实参表 >) ;
函数指针变量主要用作函数参数,下面举例说明。
【例 7.22 】分别编写求一维数组元素最小值与平均值的两个函数,然后在主函数中用函数指针变量求出最小值与平均值。
# include <iostream.h>
# include <stdlib.h>
float ave(float * p,int n) // 定义求平均值函数
{ float sum=0;
for ( int i=0;i<n;i++) sum+=*p++;
return sum/n;
}
float min( float * p,int n) // 定义求最小值函数
{ float min=*p;
for ( int i=0;i<n;i++)
{ if (*p<min ) min=*p;
p++;
}
return min;
}
void main(void)
{ float a[ ]={1,2,3,4,5};
float (*pf)(float *,int); // 定义函数指针变量
pf=ave; // 将函数 ave 的入口地址赋给 pf
cout<<"ave="<<pf(a,5)<<endl; // 对 pf() 的调用就是对 ave() 的调用
pf=min; // 将函数 min 的入口地址赋给 fp
cout<<"min="<<pf(a,5)<<endl; // 对 fp() 的调用就是对 min() 的调用
}
在上述程序中,先定义求平均值函数 ave( ) 与求最小值函数 min() 。然后在主函数中定义数组 a[5] 及函数指针变量 fp 。语句 fp=ave 是将函数 ave 的入口地址赋给 fp ,使 fp 指向函数 ave ,从而对函数 fp(a,5) 的调用就变成了对函数 ave(a,5) 的调用。同样经赋值 fp=min 后,对 fp(a,5) 调用变为对 min(a,5) 的调用。 C++ 中的函数指针变量在同一程序中求不同函数运算结果时特别有效。下面举例说明。
【例 7.23 】设计一个程序,用梯形法求下列定积分的值。
图 7.15 用梯形法求定积分面积 |
y y=f (x) yn-1 y2 yn y1 y0
△ s 0 △ s 1 △ s 2 … △ s n-1 x=a x=b
x0 x1 x2 x3 xn-1 xn x 0 a h b
|
分析:由高等数学可知, 的定积分值等于由曲线 y=f(x) 、直线 x=a 、 x=b 、 y=0 所围曲边梯形的面积 s ,如图 7.15 所示。现将曲边梯形划分成 n 个小曲边梯形 △ s 0 、 △ s 1 、 △ s 2 、 …、 △ s n-1 。每个曲边梯形的高均为 h=(b - a)/n ,用梯形近似曲边梯形后各曲边梯形的面积近似为:
△ s 0= (y0 +y1 )*h/2
△ s 1= (y1 +y2 )*h/2
△ s 2 = (y2 +y3 )*h/2
…
△ s n-1= (yn-1 +yn )*h/2
s = △ s 0+ △ s 1+ △ s 2+ … + △ s n-1 = (y0 +(y1 +y2 + … +yn-1 )*2+yn )*h/2
=((f (x0 )+f (xn ))/2+(f (x1 )+f (x2 )+ … +f (xn-1 ))*h
∵ x0 =a , xn =b , xi =a+i*h
∴用梯形法求定积分面积的公式为:
其中: a 、 b 分别为积分的下、上限, n 为积分区间的分隔数, h=(b - a)/n , h 为积分步长; f(x) 为被积函数。
先编一个求定积分的通用函数 integral() ,它需要四个形参:指向被积函数的函数指针变量 f 、积分的下限 a 、上限 b 、积分区间的分隔数 n 。程序如下:
# include <iostream.h>
# include <math.h>
float f1(float x)
{ return (1+x);}
float f2(float x)
{ return (x/(1+x*x));}
float f3(float x)
{ return (x+x*x)/(1+cos(x)+x*x);}
float integral (float (*f)(float),float a,float b,int n)
{
float y,h;
int i;
y=(f(a)+f(b))/2;
h=(b-a)/n;
for (i=1;i<n;i++) y+=f(a+i*h);
return (y*h);
}
void main (void )
{
cout<<"s1="<<integral(f1,1,4,1000)<<endl;
cout<<"s2="<<integral(f2,0,1,1000)<<endl;
cout<<"s3="<<integral(f3,1,3,1000)<<endl;
}
程序执行后输出:
s1=10.5
s2=0.346574
s3=2.44641
在上述程序中,首先定义了三个被积函数 f1(x) 、 f2(x) 、 f3(x) 。在积分函数 integral() 中用函数指针变量 f 、积分下限 a 、上限 b 、等分数 n 作为函数形参。在主函数中调用积分函数 integral(f1,1,4,1000) 时,将实参 f1 的入口地址传送给函数指针变量 f ,将实参 1 、 4 、 1000 分别传给形参 a 、 b 与 n ,则在执行积分函数时,函数指针变量 f 被换成被积函数 f1 ,从而实现对被积函数 f1 的积分。由此例可以看出,只要在调用积分函数的实参中,写入不同的函数名、不同的下限 a 、上限 b 、等分数 n 就可以使用一个函数 integral() 完成不同被积函数的积分。这就是函数指针变量的作用。
7.5 new 和 delete 运算符
定义数组时,数组的长度是常量,是不允许改变的。但实际应用程序中,往往要求能动态的分配内存。为了实现这一目的, C++ 提供动态分配内存命令 new 与动态回收内存命令 delete 。
7.5.1 new 运算符
用 new 可以动态的分配内存空间,并将分配内存的地址赋给指针变量,使用 new 为指针变量动态分配内存空间的语句有三种格式:
( 1 ) < 指针变量 > = new < 类型 > ;
作用:分配由类型确定长度的内存,将首地址赋给指针变量。
( 2 ) < 指针变量 > = new < 类型 > ( value ) ;
作用:完成( 1 )功能外,将 value 作为分配内存初值。
( 3 ) < 指针变量 > = new < 类型 >[< 表达式 >];
作用:分配指定类型的数组空间,并将首地址赋给指针变量。例如:
int n;
cin>>n;
float a[n]; // 用变量 n 作为数组长度来定义数组 a 是错误的。
float *p;
p=new float[n]; // 程序执行时,动态分配 n 个 float 类型的内存单元,并将其首地址赋给指针变量 p 。
7.5.2 delete 运算符
delete 运算符用来将动态分配的内存空间归还给系统,有三种格式:
( 1 ) delete < 指针变量 >;
作用:将指针变量所指的内存归还系统。
( 2 ) delete [ ]< 指针变量 >;
作用:将指针变量所指的一维数组内存归还系统。
( 3 ) delete [< 表达式 >] < 指针变量 >;
作用:将指针变量所指的一维数组内存空间归还给系统。
【例 7.24 】设计程序,实现整型、字符型、实型、一维整型数组的动态内存空间分配与归还。
# include <iostream.h>
void main(void)
{ int n,*pi,*pa;
char *pc;
float *pf;
pi=new int ; // 动态分配整型数内存空间,将其首地址赋给指针变量 pi 。
pc=new char; // 动态分配字符型内存空间,将其地址赋给 pc 。
pf=new float(2.5) ; // 动态分配实型数内存空间,赋初值 2.5 ,将其地址赋给 pf
cout<<" 输入数组长度 n: ";
cin>>n;
pa=new int[n]; // 动态分配由 n 个整型元素组成的一维数组,首地址赋 pa
*pi=10;
*pc='A';
for (int i=0;i<n;i++) pa[i]= i; // 给 10 个元素赋值。
cout<<"*pi="<<*pi<<endl;
cout<<"*pc="<<*pc<<endl;
cout<<"*pf="<<*pf<<endl;
for (i=0;i<n;i++)
{ cout<<"pa["<<i<<"]="<<pa[i]<<'/t'; // 第 i 个元素值可用 pa[i] 来表示。
if ((i+1) % 5==0) cout<<endl;
}
delete pi; // 动态归还 pi 所指内存空间
delete pc; // 动态归还 pc 所指内存空间
delete pf; // 动态归还 pf 所指内存空间
delete [n]pa; // 动态归还 pa 所指一维数组内存空间
}
程序执行后系统提示:
输入数组长度 n : 10
*pi=10
*pc=A
*pf=2.5
p[0]=0 p[1]=1 p[2]=2 p[3]=3 p[4]=4
p[5]=5 p[6]=6 p[7]=7 p[8]=8 p[9]=9
7.5.3 使用new 和 delete 运算符应注意的事项
( 1 )用 new 分配内存后,其初值为随机数。
( 2 )用 new 分配内存后,若指针变量值为 0 ,则表示分配失败。此时应终止程序执行。 例如: float *p;
p=new float [1000];
if (p==0)
{ cout<<" 动态分配内存失败,终止程序执行! ";
exit(3);
}
( 3 )动态分配存放数组的内存空间,不能在分配空间时进行初始化。
例如: int *pi;
pi=new int[10](0,1,2,3,4,5,6,7,8,9) ; // 是错误的。
( 4 )用 new 分配内存空间的指针值必须保存起来,以便用 delete 归还内存。否则会出现不可预测的后果。
例如: float *p,x;
p=new float;
*p=24.5;
p=&x;
delete p;
由于改变了指针 p 的值,所以系统已无法归还动态分配的存储空间。执行 delete p 时会出错。此外,用 new 动态分配的内存空间,若不用 delete 归还,则在程序执行后,这部分内存空间将从系统中丢失,直到重新启动计算机,系统重新初始化时,才能使用这部分内存空间。
7.6 引用类型变量和const类型的指针
7.6.1引用类型变量的定义及使用
当函数形参为变量名时,形参与实参使用不同的内存空间,因此,函数执行的结果无法通过形参返回给调用程序。这对于需要返回两个或两个以上运算结果的函数来说,使用变量作为形参是无法做到的。为此, C++ 提供了引用类型变量来解决上述问题。
1 .引用类型变量:是已定义变量的别名,与变量共用同一内存空间。
用引用变量可解决函数返回多个运算结果的问题。引用类型变量也必须先定义后使用。
2.引用类型变量的定义格式: < 类型 >&< 引用变量名 >=< 变量名 > ;
例如:
int a =0;
int &refera=a; //refera 为变量 a 的引用变量,与共用同一内存空间
refera=100; // 对引用变量 refera 赋值 100 的操作就是对变量 a 赋值 100 的操作
cout<<a; // 输出结果为 100
对引用类型变量须说明如下:
( 1 )定义引用类型变量时必须初始化,初始化变量必须与引用类型变量类型相同。
例如: float x;
int &px=x; // 由于 px 与 x 类型不同而产生编译错误。
( 2 )引用类型变量初始化值不能是常数。
例如: int &ref=5 ; 是错误的。
( 3 )可用动态分配内存空间来初始化一个引用变量 ,例如:
float &refx=* new float;
refx=200;
cout<<refx;
delete &refx;
因为 new 分配内存的结果是一个指针,所以 “* new float” 是一个变量,只有变量才能赋给引用类型变量 refx 。此外 delete 的操作数必须是指针,而不能是变量,所以在引用类型变量 refx 前加取地址运算符 & 。
注意: C++ 中“ & ”运算符有三种含义:按位与运算符“ & ”、取地址运算符“ & ”及引用运算符“ & ”。因此,“ & ”运算符将根据不同的操作数而呈现不同运算作用,使用时必须注意。
3.引用类型变量作为函数参数
引用变量作为形参时属传地址方式。由于形参为实参的别名,实参与形参占用同一内存单元,对形参的操作就是对实参的操作。因此,使用引用类型变量可返回参数运算结果。
【例 7.25 】用引用变量作为形参,编写使两个数据交换的函数,在主函数中调用该函数实现两个变量中数据互换。
# include <iostream.h>
void swap(int &rx,int &ry) // 将形参 rx 、 ry 定义为整形引用变量。
100 |
200 |
rx=x ry=y |
(a) 交换前的数据 |
图 7.16 形参为引用变量时数据交换 |
200 |
100 |
rx=x ry=y |
(b) 交换后的数据 |
temp=rx;rx=ry;ry=temp;
}
void main (void)
{ int x,y;
cout<<" 输入 X 与 Y : "<<endl;
cin>>x>>y;
swap(x,y);
cout<<"x="<<x<<'/t'<<"y="<<y<<endl;
}
程序执行后若输入 x 、 y 的值为 100 、 200 ,则输出结果为 x=200 y=100 。
上述程序调用 swap(x,y) 函数过程中,参数的传送过程相当于执行两条定义引用类型变量的语句: int &rx=x;
int &ry=y;
因此,引用变量 rx 与关联变量 x 占用同一内存单元,引用变量 ry 与关联变量 y 占用同一内存单元,如图 7.16 所示。在 swap() 函数中对引用变量 rx 、 ry 的交换就是对关联变量 x 、 y 的交换,因而通过引用变量可将交换后的数据返回调用函数。
7.6.2 const 类型变量
用保留字 const 可将数据定义为常量。分为 const 型常量和 const 型指针。
1 .定义 const 型常量
定义格式: const < 类型 > < 常量名 >=< 常量值 > ;
例如: const int MaxR=100;
const float PI=3.14159;
定义了常量 MaxR 与 PI ,其值分别为 100 与 3.14159 。
说明:
( 1 ) const 定义常量必须初始化,程序中不允许修改常量值。 如: MaxR=200; 是错误的。
( 2 ) const 与 define 定义常量的效果是相同的,区别:
define 定义常量由预编译处理程序来处理。
const 定义常量由编译程序进行处理的。
因此,在程序调试过程中,可用调试工具查看 const 常量,但不能查看 define 常量。 const 常量的作用域与普通变量的作用域相同,而 define 常量的作用域从定义位置开始,到文件结束为止。
2.定义const 型指针
有三种方法来定义 const 型指针:
( 1 ) const < 类型 > *< 指针变量名 > ;
作用:定义指针变量所指数据为常量,即:指针变量所指数据不能改变,但指针变量值可以改变。例如:
float x,y;
const float *p=&x; // 定义指针变量 p 所指数据值 *p 为常量
*p=25; // 错误, p 所指变量 x 数据值不能用 *p 形式进行改变
p=&y; // 正确,可改指针变量 p 的值
x=25; // 正确,变量 x 的值可以改变
( 2 ) < 类型 > * const < 指针变量名 > ;
作用:定义指针变量值为常量,即:指针变量值不能改变,但指针变量所指数据可以改变。例如:
float x,y;
float * const p=&x; // 定义指针变量 p 中的值为常量
*p=25; // 正确, p 所指变量 x 数据值可以用 *p 形式进行改变
p=&y; // 错误,指针变量 p 的值不能改变
用这种形式定义的指针变量,必须在定义时赋初值。
( 3 ) const < 类型 > * const < 指针变量名 > ;
作用:定义指针变量值为常量,指针变量所指数据为常量。即:指针变量值不能改变,指针变量所指数据值也不能改变。例如:
float x,y;
const float * const p=&x; // 定义指针变量 p 为常量
*p=25; ` // 错误, p 所指变量 x 数据值不能用 *p 形式进行改变
p=&y; // 错误,不能改变指针变量 p 的值
用这种形式定义指针变量,必须在定义时赋初值。
板书 定义格式 指针变量值 所指数据值
( 1 ) const < 类型 > *< 指针变量名 > ;
作用:定义指针变量所指数据为常量 可改变 不能改变
( 2 ) < 类型 > * const < 指针变量名 > ;
作用:定义指针变量值为常量 不能改变 能改变
( 3 ) const < 类型 > * const < 指针变量名 > ;
作用:定义指针变量值为常量 不能改变 不能改变
注意:
( 1 )因为引用变量类同于指针变量,所以这三种定义形式完全适应于引用类型变量。
( 2 )定义 const 类型指针的目的是提高程序的安全性,用 const 可限制程序随意修改指针值。
( 3 ) const 指针主要用作函数参数,以限制在函数体不能修改指针变量的值,或不能修改指针变量所指数据值。
本章小结(习题课)
通 过本章学习读者已了解指针与指针变量的概念,指针是变量、数组、字符串、函数等在内存的地址,指针变量是存放指针的变量。指针变量按定义格式大致可分为五 种:指针变量、指针数组、指向一维数组的指针变量、返回指针值的函数、函数指针变量。其中指针变量可以指向变量,也可以指向数组,也可以指向字符串。现小 结如下:
1.指针变量
( 1 )定义:〔存储类型〕 < 类型 > *< 指针变量名 > ;
指针变量定义后其值为随机数,因此必须给指针变量赋变量地址或数组地址。
( 2 )赋值: < 指针变量 >=&< 变量 > ;
( 3 )指针变量的引用: *< 指针变量 > ;
如: int b,a[10],* p; p=&b ; a[0]=*p;
若 p 为数组 a 指针变量,则 *p 表示数组元素 a[i] 。通过对指针变量的算术运算可以改变数组指针变量 p 的指向,如: p++( 或 p--) 可使 p 指向数组下 ( 或上 ) 一个元素 a[i+1]( 或 a[i-1]) , p=p+n 可使 p 指向数组的下 n 个元素 a[i+n] ,从而使 *p 能表示数组中的任一个元素值。
( 4 )一维数组 a :元素地址: a+i 、 p+i 。元素值: a[i] 、 *(p+i) 、 *(a+i) 、 p[i] 。
( 5 )二维数组 a[m][n] : a 中每一行都可作为一维数组,所以二维数组 a 可以看成由 m 个元素 a[0]~a[m-1] 组成,每个元素均为一维数组,即 a[i]={a[i][0],a[i][1], … ,a[i][n-1]} 。
对二维数组必须分清三个地址,即:行首地址、行地址与元素地址。
( 6 )行首地址(第 i 行第 0 列地址): *(a+i) 、 a[i] 、 &a[i][0] ,用于指向数组元素的指针变量。
( 7 )行地址(第 i 行地址): a+i 、 &a[i] ,用于指向一维数组的指针变量。
注意,二维数组名 a 是第 0 行的行地址 &a[0] ,而不是第 0 行第 0 列的元素地址 &a[0][0] 。
( 8 )元素 a[i][j] 地址: a[i]+j 、 *(a+i)+j 、 &a[i][0]+j 、 &a[i][j] 。
( 9 )元素 a[i][j] 值: *(a[i]+j) 、 *(*(a+i)+j) 、 *(&a[i][0]+j) 、 a[i][j] 。
( 10 )将字符数组地址赋给指针变量 pc ,则 pc 就是字符串指针变量。不能用 cin 给字符串指针变量输入字符串值,但可在定义字符串指针变量时赋字符串初值,也可将字符串指针变量赋给另一个字符串指针变量,也可用 cout 输出字符串指针变量。例如:
char *pc,*ps="I am a Student"; // 正确
cin>>pc; // 错误
pc=ps; // 正确
cout<<pc; // 正确
( 11 )数组与指针作为函数参数有 4 种情况:
l 函数的实参为数组名,形参为数组。
l 函数的实参为数组名,形参为指针变量。
l 函数的实参为指针变量,形参为数组。
l 函数的实参为指针变量,形参为指针变量。
2.指针数组
由若干指针元素组成的数组称为指针数组,其定义格式如下:
〔存储类型〕 < 类型 > *< 数组名 >[< 数组长度 >] ;
例如: char c[3 ][4]={ "ABC","DEF","GHI"};
char *pc[3 ]={c[0],c[1],c[2]};
上式定义的指针数组 pc[3] 由三个指针元素 pc[0] 、 pc[1] 与 pc[2] 组成。每个元素均为指针变量,分别指向字符串数组的三个元素 c[0] 、 c[1] 与 c[2] ,通过 pc[i] 可访问 c[i] 。指针数组常用于对字符串数组的处理,如排序等。
3.指向一维数组的指针变量
指向一维数组的指针变量可用于处理二维数组的运算问题,其定义格式为:
< 类型 > (*< 指针变量名 >)[< 数组列数 >];
如: int (*p)[3]; 定义了一个指向一维数组的指针变量 p ,一维数组是由元素 (*p)[0] 、 (*p)[1] 、 (*p)[2] 组成。若将二维数组 a[3][3] 中第 0 行地址赋给 p ,即: p=&a[0] ,则 (*p)[0] 、 (*p)[1] 、 (*p)[2] 可以表示二维数组 a 的第 0 行元素 a[0][0] 、 a[0][1] 、 a[0][2] 。将 p++ 后, p 将指向 a 的第 1 行,此时 (*p)[0] 、 (*p)[1] 、 (*p)[2] 表示数组 a 的第 1 行元素 a[1][0] 、 a[1][1] 、 a[1][2] 。由此可见,只要用算术运算改变 p 所指的行,则 (*p)[0] 、 (*p)[1] 、 (*p)[2] 可以表示数组 a 的任一行元素值。因此,指向一维数组的指针变量可用于处理二维数组的运算问题。注意:在定义语句中的“数组列数”必须与所指向的二维数组的列数相同。
【例 7.18 】用三种方法求二维实型数组元素和。
(1) 二维数组为函数的形参
(2) 普通指针变量为函数的形参
(3) 指向一维数组的指针变量为函数的形参
# include <iostream.h>
float sum1(float a[][4],int n,int m) // 形参与实参均为二维数组。
{ float sum=0;
for (int i=0 ;i<n;i++)
for (int j=0 ;j<m;j++) sum+=*(a[i]+j); //*(a[i]+j) 表示 a[i][j] 的元素值
return sum;
}
float sum2(float *p,int k) // 形参为普通指针变量
{ float sum=0;
for (int j=0 ;j<k;j++) sum+=*p++; //*p 表示 a[i][j] 的元素值
//p++ 后 p 将指向下一个元素
return sum;
}
float sum3(float (*p)[4],int m) // 形参 p 为指向一维数组的指针变量
{ float sum=0;
for (int i=0;i<m;i++)
{ for (int j=0;j<4;j++) sum+=(*p)[j]; //(*p)[j] 表示 a[i][j] 的元素值
p++; //p++ 后, p 将指向下一行
}
return sum;
}
void main(void)
{ float a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
cout<<"sum1="<<sum1(a,3,4)<<endl; // 实参为数组名,形参为数组
cout<<"sum2="<<sum2(&a[0][0],12)<<endl; // 实参为数组地址,形参为指针变量
cout<<"sum3="<<sum3(&a[0],3)<<endl; // 实参为行指针,
// 形参为指向一维数组的指针
}
程序执行后输出:
sum1=78
sum2=78
sum3=78
关于三种方法的说明:
( 1 )求和函数的形参与实参均为二维数组时,可按行、列元素依次相加求二维数组元素和,数组中第 i 行第 j 列元素 a[i][j] 用 *(a[i]+j) 表示。
( 2 )求和函数的形参定义为普通指针变量 p 时,实参必须为数组 a 的首地址 &a[0][0] 。此时,可将二维数组 a[3][4] 作为具有 12 个元素的一维数组来处理。只要将数组首地址 &a[0][0] 赋给 p 后,就可用指针变量 p 对数组 a 按一维数组的顺序求和。
( 3 )用指向一维数组的指针 (*p)[4] 作为形参时,实参必须是数组首行的行地址 &a[0] 。调用函数时,在行地址 &a[0] 赋给 p 后, (*p)[j] 表示第 0 行第 j 个元素 a[0][j] ,因此可用循环语句: for (int j=0;j<4;j++) sum+=(*p)[j]; 完成对第 0 行的求和工作。当第 0 行求和后,用 p++ 使 p 指向下一行,顺序求和,直到各行全部求完为止。
4.返回指针值的函数
函数可返回一个指针值,该指针指向一个已定义好的任一类型的数据。定义返回指针值的函数格式为: < 类型 > *< 函数名 >(< 形参 >){< 函数体 >}
如: float * f ( float x) { … } 定义了一个返回实型指针的函数 f () ,该函数的形参为实型的 x 。
【例 7.20 】设计简单的加密程序,将字符串存入字符数组 s ,然后将 s 中的每一个字符按下述加密函数进行加密处理。(习题课讲)
s[i] - 16 ; 当 48 ≤ s[i] ≤ 57 , s[i] 中的字符为数字 0~9 ;
s[i] = s[i] - 23 ; 当 65 ≤ s[i] ≤ 90 , s[i] 中的字符为大写字母 A~Z ;
s[i] + 5 ; 当 97 ≤ s[i] ≤ 122 , s[i] 中的字符为小写字母 a~z ;
其中 s[i] 为字符串中第 i 个字符的 ASCII 码。输入的字符串只能由字符或数字组成。
# include <iostream.h>
# include <string.h>
char *encrypt(char *pstr) // 定义加密函数为返回指针值的函数
{ char *p=pstr; // 保存字符串首地址到指针变量 p
while (*pstr)
{ if (*pstr>=48 && *pstr<=57) *pstr - =16; // 用指针变量 *pstr 表示 s[i] ,
else if (*pstr>=65 && *pstr<=90) *pstr - =23; // 进行加密处理
else if (*pstr>=97 && *pstr<=122) *pstr+=5;
pstr++; // 指针变量加 1 ,指向下个单元
}
return p; // 返回字符串指针
}
void main(void)
{ char s[80];
cin.getline(s,80); // 输入字符串到数组 s
cout<< encrypt (s)<<endl; // 将字符串加密后输出
}
程序执行时输入:
123ABCabc
加密后输出:
!’’#*+,fgh
程序执行后先输入字符串到字符数组 s 中,然后调用加密函数 encrypt (s) 。实参 s 将字符串数组首地址传送给指针变量 pstr ,使指针变量 pstr 指向数组第 1 个元素。赋值语句 p=pstr 将字符串首地址记录在指针变量 p 中,以便在函数的最后用 return p 语句将字符串首地址返回给调用函数。在循环程序中,使用 *pstr 表示数组 s 的元素值 s[i] ,按加密函数进行处理,处理后将 pstr 加 1 指向下一个元素,循环直到字符串结束标志 0 为止,完成加密工作。
5.函数指针变量
函数指针变量是用于存放函数指针的变量,也即存放函数入口地址的变量。函数指针变量的定义格式为: < 类型 > (*< 变量名 >) (< 参数表 >) ;
在实际使用时,必须将真实的函数入口地址赋给函数指针变量。此时对函数指针变量的调用就变成了对真实函数的调用。如:
float (*fp)( float); // 定义函数指针变量 fp
float ave( float a[3]); // 定义真实的求平均值函数 ave
{ return ((a[0]+a[1]+a[2])/3);}
fp=ave; // 将函数 ave 的入口地址赋给 fp
float b[]={1,2,3};
cout<<fp(b)<<endl; // 函数调用时,用 ave 替换 fp ,因此对函数指针变量 fp
// 调用就变成了对 ave 函数的调用。
6.new 和 delete 运算符
用 new 运算符可以动态地分配内存空间,并将分配内存的地址赋给指针变量,语句格式有: < 指针变量 > = new < 类型 > ;
< 指针变量 > = new < 类型 > ( value ) ;
< 指针变量 > = new < 类型 >[< 表达式 >];
delete 运算符用来将动态分配的内存空间归还给系统,语句格式有:
delete < 指针变量 >;
delete [ ]< 指针变量 >;
delete [< 表达式 >] < 指针变量 >;
7.引用类型变量和const类型的指针
( 1 )引用类型变量: < 类型 >&< 引用变量名 >=< 变量名 > ;如: int & refa=a;
引用类型变量 refa 是已定义变量 a 的别名,引用类型变量 refa 与其相关的变量 a 使用相同的内存空间。由于引用类型变量与相关变量使用相同的内存空间,所以用引用类型变量作为函数形参时,形参与实参变量使用相同的内存,在函数内对形参的运算就是对实参的运算,因此可通过参数返回函数运算结果。
( 2 ) const 型常量
const < 类型 > < 常量名 >=< 常量值 > ;如: const int PI=3.1415;
const 常量一经定义后,其值不能改变。
( 3 ) const 型指针
有三种方法来定义 const 型指针:
const < 类型 > *< 指针变量名 > ;
定义指针变量所指数据值不能改变,但指针变量值可以改变。
< 类型 > * const < 指针变量名 > ;
定义指针变量值不能改变,但指针变量所指数据值可以改变。
const < 类型 > * const < 指针变量名 > ;
指针变量值与指针变量所指数据值都不能改变。