您的位置:首页 > 其它

第7章 指针

2011-02-18 15:05 162 查看
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

指针变量p1

0

指针变量q

图7.2 指针变量

p=&a; //将变量a的地址1000赋给指针变量p,使p指向变量a,以便用指针变量p间接的使用变量a,如图7.2所示。如何用指针变量p间接的使用变量a,这要用到指针运算符“*”。

(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 指针的赋值与算术运算

void main (void)

{ 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

地址
数组

1000
a[0]=0



1004
a[1]=1



1008
a[2]=2



1012
a[3]=3



1016
a[4]=4



1020

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.数组指针
数组的首地址称为数组指针。例如:

内存地址
数组元素

1000
a[0]



1004
a[1]



1008
a[2]



1012
a[3]



1016
a[4]



图7.5 一维数组与指针

p = a →

p+i=a+i →

int a[5]; //为数组a分配的地址从1000到1019,则数组a的首地址1000为数组a的数组指针。C++规定,数组a的首地址可用数组名a表示,因此:

数组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
,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
,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→

内存地址
数组元素

1000
a[0][0]

1004
a[0][1]

1008
a[0][2]

1012
a[1][0]

1016
a[1][1]

1020
a[1][2]

1024
a[2][0]

1028
a[2][1]

1032
a[2][2]

图7.6 二维数组在内存中存放次序

{ float a
,max,*p=a;

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]

a[0][0]
a[0][1]
a[0][2]

a[1][0]
a[1][1]
a[1][2]

a[2][0]
a[2][1]
a[2][2]

图7.7 二维数组与指针

a[1]=(a[1][0],a[1][1],a[1][2])

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

I

a

m

a

s

t

u

d

e

n

t

0

p1→

【例7.10】用字符串指针变量实现字符串拷贝。

# 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 指针数组指向字符串数组

pc[0]

pc[1]

pc[2]

“ABC”

“DEF”

“GHI”

指针数组

字符串数组

c[0]

c[1]

c[2]

# include <iostream.h>

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)排序前

PASCAL

BASIC

VC

DELPHI

VFP

图7.10用指针数组对字符串排序

s[0]

s[1]

s[2]

s[3]

s[4]

PASCAL
Follow me

BASIC
BASIC

VC
Great wall

DELPHI
Departmemt

VFP
Computer design

(b)排序后

分析:先定义一个字符型指针数组s,并通过初始化语句使指针数组中各元素s[i]指向各字符串,如:s[0]= "PASCAL" 等等,如图7.10(a)所示。此时,通过对指针数组中各元素s[i]的排序就能完成对各字符串的排序。排序后指针数组各元素所指字符串如图7.10(b)所示,最后用指针数组元素str[i]输出各字符串的值。程序编写如下:

#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

(*p)[0]
(*p)[1]
(*p)[2]

图7.11 指针变量p的指向

a[0]

a[1]

a[2]

a[0][0]
a[0][1]
a[0][2]

a[1][0]
a[1][1]
a[1][2]

a[2][0]
a[2][1]
a[2][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】 p3:2004

p2:2000

p1:1000

a:10

2008 2004 2000 1000

图7.12 三级指针示意图

用三级指针使用整型变量a。

# 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

【例7.15】编写两个数据交换函数,用指针变量作为函数参数实现两个数据的交换,交换过程如图7.13所示。

# 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

由于传值调用过程中,形参与实参占用不同的存储单元,所以函数调用后(在swap(x,y)期间),形参x1、y1值被交换,但实参x、y的值保持不变,如图7.14所示。由此可见,对传值函数的调用不能返回参数值,而对传地址函数的调用可返回参数值。

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

else return &y;

}

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

△s0 △s1 △s2 … △sn-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个小曲边梯形△s0、△s1、△s2、…、△sn-1。每个曲边梯形的高均为h=(b-a)/n,用梯形近似曲边梯形后各曲边梯形的面积近似为:

△s0=(y0+y1)*h/2

△s1=(y1+y2)*h/2

△s2 =(y2+y3)*h/2



△sn-1=(yn-1+yn)*h/2

s =△s0+△s1+△s2+…+△sn-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作为数组长度来定义数组a是错误的。

float *p;

p=new float
; //程序执行时,动态分配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个整型元素组成的一维数组,首地址赋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
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) 交换后的数据

{ int temp;

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]
: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 <指针变量名>;

指针变量值与指针变量所指数据值都不能改变。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/beyondyxj/archive/2004/10/29/158109.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: