2009-7-28 1
第 8章指针和引用
2009-7-28 2
指针和指针变量指针的概念
1.内存地址 ──内存中存储单元的编号
( 1)计算机硬件系统的内存储器中,拥有大量的存储单元(容量为1字节)。
为了方便管理,必须为每一个存储单元编号,
这个编号就是存储单元的,地址,。每个存储单元都有一个惟一的地址。
( 2)在地址所标识的存储单元中存放数据。
注意,内存单元的地址与内存单元中的数据是两个完全不同的概念。
2009-7-28 3
2.变量地址 ──系统分配给变量的内存单元的起始地址。
假设有这样一个程序:
void main( void) {
int num;
cin>>num;
cout<<"num=“<< num<<?\n?;
}
C++编译程序编译到该变量定义语句时,将变量
num 登录到,符号表,中 。 符号表的关键属性有两个:
一是,标识符名 ( id),,二是该标识符在内存空间中的,地址 ( addr),。
为描述方便,假设系统分配给变量 num的 2字节存储单元为 3000 和 3001,则起始地址 3000就是变量 num在内存中的地址 。 见下图 。
2009-7-28 4
3000
3001
3.变量值的存取 ──通过变量在内存中的地址进行。系统执行,cin>>num;”和,cout<<”num=”<<
num<<?\n?;”时,存取变量 num值的方式可以有两种:
( 1) 直接访问 ──直接利用变量的地址进行存取。
1)上例中 cin>>num的执行过程是这样的:
用变量名 num作为索引值,检索符号表,找到变量 num的起始地址 3000;然后将键盘输入的值(假设为3)送到内存单元 3000和 3001中。此时,变量 num
是内存中的地址和值。
2009-7-28 5
2) cout<<“num=”<< num<<?\n?的执行过程,与 cin
很相似:
首先找到变量 num的起始地址 3000,然后从 3000
和 3001中取出其值,最后将它输出 。
3000
30013
2009-7-28 6
如:假设定义了这样一个指针变量 num_pointer,
它被分配到 4000,4001单元,其值可通过赋值语句:
num_pointer=& num;
得到。此时,指针变量 num_pointer的值就是变量
num在内存中的起始地址 3000。
通过指针变量 num_pointer存取变量 num值的过程如下:
首先找到指针变量 num_pointer的地址( 4000),
取出其值 3000(正好是变量 num 的起始地址); 然后从 3000,3001中取出变量 num的值( 3)。
( 2) 间接访问 ──通过另一变量访问该变量的值
C ++语言规定:在程序中可以定义一种特殊的变量(称为 指针变量 ),用来存放其它变量的地址。
2009-7-28 7
( 3)两种访问方式的比较两种访问方式之间的关系,可以用某人甲(系统)
要找某人乙(变量)来类比。
一种情况是,甲知道乙在何处,直接去找就是(即直接访问)。
另一种情况是,甲不知道乙在哪,但丙(指针变量)
知道,此时甲可以这么做:先找丙,从丙处获得乙的去向,然后再找乙(即间接访问)。
num_pointer 3000
30013
… …
2009-7-28 8
4.指针与指针变量
( 1)指针 ──即地址。
一个变量的地址称为该变量的 指针 。通过变量的指针能够找到该变量。
( 2)指针变量 ──专门用于存储其它变量地址的变量。
指针变量 num_pointer的值就是变量 num的地址。
指针与指针变量的区别,就是变量值与变量的区别。
( 3)为表示指针变量和它指向的变量之间的关系,
用指针运算符,*”表示。
2009-7-28 9
如:指针变量 num_pointer与它所指向的变量
num的关系,表示为:
*num_pointer,即 *num_pointer等价于变量 num。
2009-7-28 10
num_pointer 3000
3001
3002
3003
3
… …
因此,下面两个语句的作用相同:
num=3; //将 3直接赋给变量 num
num_pointer=&num; //使 num_pointer指向 num/
*num_pointer=3; //将 3赋给指针变量 num_pointer/
//所指向的变量
2009-7-28 11
指针的运算?
,*” 是 取内容运算符,用来读取它所指向的内存中的值。
,&” 是 取址运算符,用来获取某一变量的地址。 如:
int *pi,a=10;
pi=&a; //pi的值为变量 a的地址
cout << *pi; //*pi表示取示 pi所指向的内容,即值为 10
又如:
int *ip1,*ip2,i=100,j;
ip1=&i; ip2=&j;
*ip2=*ip1+200; //将 300赋给 ip2所指向的内存单元
//这样赋值后 j的值为 300
2009-7-28 12
指针变量的说明一般格式:
存储类型 数据类型 *指针变量名 1[,*指针变量名
2,…];
如,float *p3; //p3是指向浮点变量的指针变量
char *p4,*p5; //p4,p5是指向字符变量的指针变量
staic int *p2; //p2是指向静态整型变量的指针变量注,( 1)编译程序为所有类型的指针变量分配大小相同的 4个字节空间,这是因为指针变量的值是一个地址,其取值范围不变。
( 2)指针类型变量的类型定义了指针变量所指向的数据类型,该类型确定了数据占用的存储空间的大小。
如 p2,p3占 4个字节,p4,p5占一个字节。
2009-7-28 13
( 3)在说明指针变量的同时也可以进行初始化。如:
int j,*p=&j; //说明了整型变量 j和整型指针变量 p,
//同时置 p的初值为 j的起始地址。
( 4) C++中允许将一个整型常数经强制类型转换后来初始 化指针变量。如:
int *pp=( int *) 0x5600; //置 pp的初值为 0x5600,
//即使 pp指向地址为 0x5600的内存单元。
建议初学者不要使用这种方法初始化指针变量。
指针可执行的运算指针有三种运算,赋值运算,关系运算 和 算术运算 。
2009-7-28 14
( 1)指针的赋值运算赋的值必须是 地址常量 或 变量,而不能是普通整数,有以下几种形式:
a,变量地址赋予指向相同数据类型的指针。
char c,*pc;
pc=&c; //将 c的地址赋给 pc
b.指针赋予相同数据类型的另一指针。
int *p,*q;
int i;
q=&i;
p=q; //将 q赋给 p
p1:
p2,1
a:
两个指针变量指向同一个变量
2009-7-28 15
程序执行后输出:
*p1=1 *p2=1 *p3=2
p1=0x0065FDDC p3=0x0065FDD8
*fp1=23.5 *fp2=55.6
指针变量的访问有两种方法:
一是 访问指针变量的值,另一是 访问指针变量所指向的内存单元中的数据,这种访问称为访问指针的内容 。
例 1,指针的赋值运算和算术运算。
81
2009-7-28 16
注,1,C++中可将 0赋给一指针变量,意为初始化,
其值为“空”。即该指针变量不指向任一内存单元,
也就是不指向任一变量。如:
#include <iostream.h>
void main(void){
int *pt;
pt=0;
*pt=100; //出错,因 pt不指向任一内存单
//元,因而不允许给 pt所指向的内存单元赋值。
cout<<*pt<<?\n?;
}
2,不同类型的指针变量间可强制转换后赋值,但通常没有意义。
2009-7-28 17
如,#include <iostream.h>
void main(void){
int i=100,*p1;
float x=2.5,*p3;
p1=&i;
cout<<“*p1=”<<*p1<<?\n?;
p3=&x;
cout<<“*p3=”<<*p3<<?\n?; //输出时按实数取
p1=(int *)p3;
cout<<“*p1=”<<*p1<<?\n?; //输出时按整数来取
}
执行后输出,*p1=100
*p3=2.5
*p1=1075838976 //错误
2009-7-28 18
struct student{
int number;
char name[4];
};
student *pst;
( 2)指针的算术运算
pst
pst=pst+1;
px?n:
px?n?sizeof(T)
px-py:
( px-py) /
sizeof(T)
px+n,px-n ——将指针从当前位置向前或向后移动 n个 数据单位,而不是 n个字节。
这取决于指针所指向的数据类型 (T)。
px-py求出的是两指针位置之间的数据个数,而不是地址差。
2009-7-28 19
例 2,指针变量的,++”和,--”运算。
82程序执行后输出:
*p1=20 p1=6618552
*p1=10 p1=6618556
*p2=100 p2=6618576
*p2=200 p2=6618580
*p2=100 p2=6618576
*pc1=a pc1=6618568
*pc1=b pc1=6618569
2009-7-28 20
注,C++中,同一个说明语句中所说明的变量均分配在同一个连续的存储空间,VC++按从右到左的顺序来分配。一般来说,分配的顺序随编译器不同而不同。
例 3,指针变量加一常数。
83
执行后输出,*p2=100 p2=6618576
*p2=500 p2=6618592
*p2=300 p2=6618584
2009-7-28 21
( 3)指针的关系运算指对两个相同类型的指针的运算,如 px<py,当
px所指位置在 py之前时,表达式的值为 1,否则为 0。
px= =0,px!=0用来判断 px是否为空指针。
注,不同类型的指针以及指针和一般整数间的关系运算是无意义的。
例 4,指针变量的关系运算。
84执行后输出,100 200 300 400 500
元素之和为,1500
2009-7-28 22
注:几种运算符的混合运算及其优先级。
( 1)指针运算符,*”与取地址运算符,&”优先级相同,按从右到左的顺序运算。如设:
int a[5]={100,200,300,400,500},
*p1=&a[0],b;
则,&*p1”的运算顺序是先进行,*”运算,再进行,&”运算。而,* &a[0]”则相反。
( 2),++”、,--”、指针运算符,*”和取地址运算符
,&”的优先级相同,按从右到左结合。如:
a) p1=&a[0]; b=*p1++;
结果是 b的值为 100,p1指向数组 a的第 1个元素
(从第 0个元素起)
2009-7-28 23
b) p1=&a[0],b=*++p1
结果是 b的值为 200,p1指向数组 a的第 1个元素
c) p1= &a[0],b=(*p1)++
结果是 b 的值为 100,p1指向数组 a的第 0个元素,
并将 a[0]的值改为 101。
d) *( p1++)
等同于 *p1++
e) p1=&a[1],b=++*p1
结果是 b的值为 201,a[1]的值修改为 201,p1仍指向数组 a的第 1个元素。
y=? ++ px——y=?( ++ px)
y=?px++——y=? ( px++ ) 注意优先级和结合顺序
问题,y=?px++和 y= (?px)++的意义
2009-7-28 24
数组的指针 ──数组在内存中的起始地址。
数组元素的指针 ──数组元素在内存中的起始地址。
指向数组的指针变量的定义:
指向数组的指针变量的定义,与指向普通变量的指针变量的定义方法一样。
如,int array[10],*pointer=array(或 &array[0]);
或者,int array[10],*pointer;
pointer= array;
注意,数组名代表数组在内存中的起始地址(与第 0个元素的地址相同),所以可以用数组名给指针变量赋值。
指针和数组指针与数组的关系
2009-7-28 25
可以用指针代替数组下标来访问数组,如:
#include <iostream.h>
void main(void ){
int a[4] = {11,22,33,44};
for (int i=0; i<4; i++)
cout << " *(a+ " << i << ")= "
<< *(a+i)<< ",a[" << i
<< "]= " << a[i] ;
}
指针与数组的差异:
指针是地址变量,
可任意改变它的值;
数组名是地址常量,
其值不能改变。
结果,*(a+0)=11,a[0]=11,*(a+1)=22,a[1]=22,
*(a+2)=33,a[2]=33,*(a+3)=44,a[3]=44
数组元素的引用,既可用 下标法,也可用 指针法 。
使用下标法,直观;而使用指针法,能使目标程序占用内存少、运行速度快。
2009-7-28 26
1,一维数组与指针例 5,用指针访问数组元素。
85
程序执行后输出:
a[0]=0 a[1]=1 a[2]=2 a[3]=3 a[4]=4 a[5]=5 a[6]=6 a[7]=7 a[8]=8 a[9]=9
a[0]=0 a[1]=1 a[2]=2 a[3]=3 a[4]=4 a[5]=5 a[6]=6 a[7]=7 a[8]=8 a[9]=9
a[0]=0 a[1]=1 a[2]=2 a[3]=3 a[4]=4 a[5]=5 a[6]=6 a[7]=7 a[8]=8 a[9]=9
a[0]=0 a[1]=1 a[2]=2 a[3]=3 a[4]=4 a[5]=5 a[6]=6 a[7]=7 a[8]=8 a[9]=9
2009-7-28 27
注,( 1)数组名等同于数组的第 0个元素的地址,也是整个数组的起始地址;
( 2)当 point指向数组 a的第 0个元素后,point+i
等同于 a+i,即 a[i]的地址,或称为 第 i个元素的指针 ;
( 3)当 point指向数组 a的第 0个元素后,*
( point+i),*( a+i),a[i],point[i],*&a[i]均表示 a[i];
2,多维数组与指针以二维数组为例介绍多维数组的指针变量。
设有整型二维数组 a[3][4]如下:
0 1 2 3
4 5 6 7
8 9 10 11
2009-7-28 28
设数组 a的首地址为 1000,各下标变量的首地址及其值如下图所示。
a[0][0] 0 1000
a[0][1] 1 1004
a[0][2] 2 1008
a[0][3] 3 1012
a[1][0] 4 1016
a[1][1] 5 1020
a[1][2] 6 1024
a[1][3] 7 1028
a[2][0] 8 1032
a[2][1] 9 1036
a[2][2] 10 1040
a[2][3] 11 1044
2009-7-28 29
由于 C++语言允许把一个二维数组分解为多个一维数组来处理,因此数组 a可分解为三个一维数组:
a[0],a[1],a[2]。
每一个一维数组又含有四个元素。如:
a[0],a[0][0],a[0][1],a[0][2],a[0][3]
a[1],a[1][0],a[1][1],a[1][2],a[1][3]
a[2],a[2][0],a[2][1],a[2][2],a[2][3]
2009-7-28 30
数组及数组元素的地址表示如下:
a是二维数组名,也是二维数组 0行的首地址,
等于 1000。 a[0]是第一个一维数组的数组名和首地址,因此也为 1000。 *(a+0)或 *a是与 a[0]等效的,它表示一维数组 a[0]的 0 号元素的首地址,
也为 1000。 &a[0][0]是二维数组 a的 0行 0列元素首地址,同样是 1000。因此,a,a[0],*(a+0),*a,
&a[0][0]是相等的。
2009-7-28 31
a+1是二维数组 1行的首地址,等于 1016。 a[1]是第二个一维数组的数组名和首地址,因此也为
1016。 &a[1][0]是二维数组 a的 1行 0列元素地址,也是 1016。因此 a+1,a[1],*(a+1),&a[1][0]是等同的。 由此可得出,a+i,a[i],*(a+i),&a[i][0]是等同的。 此外,&a[i]和 a[i]也是等同的。
注:不能把 &a[i]理解为元素 a[i]的地址,因为在二维数组中不存在元素 a[i]。
2009-7-28 32
C ++语言规定,&a[i]是一种地址计算方法,表示数组 a第 i行首地址。由此,a[i],&a[i],*(a+i)和
a+i也都是等同的。
另外,a[0]可以看成是 a[0]+0是一维数组 a[0]的 0
号元素的首地址,而 a[0]+1则是 a[0]的 1号元素首地址,由此可得出 a[i]+j则是一维数组 a[i]的 j号元素首地址,它等于 &a[i][j]。由 a[i]=*(a+i)得
a[i]+j=*(a+i)+j,由于 *(a+i)+j是二维数组 a的 i行 j列元素的首地址。该元素的值等于 *(*(a+i)+j)。
2009-7-28 33
例 6,输出二维数组的行列地址和元素值。
86程序执行后输出:
用四种不同的方式输出数组 a每一行的起始地址:
0x0064FDB4 0x0064FDB4 0x0064FDB4 0x0064FDB4
0x0064FDC4 0x0064FDC4 0x0064FDC4 0x0064FDC4
0x0064FDD4 0x0064FDD4 0x0064FDD4 0x0064FDD4
0x0064FDE4 0x0064FDE4 0x0064FDE4 0x0064FDE4
用指针输出数组的全部元素:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
2009-7-28 34
用三种不同的方法输出数组的元素:
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
6 6 6
7 7 7
8 8 8
9 9 9
10 10 10
11 11 12
13 13 13
14 14 14
15 15 15
16 16 16
用指针输出数组的各个元素:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
用另一种判断数组元素结束条件的方法输出数组的元素:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
2009-7-28 35
数组地址和元素的表示法
a 二维数组名,数组的起始地址,
数组第 0行的地址
a+0 第 0行的起始地址
a[0] 第 0行第 0列元素的起始地址
*a,*( a+0) 第 0行第 0列元素的地址
**a,**(a+0),*a[0],*(*(a+0)+0) 元素 a[0][0]
a+i,&a[i] 第 i行的起始地址
a+i+j,&a[i]+j 第 i+j行的起始地址
a[i],*(a+i) 第 i行第 0列元素的起始地址
*(a+i)+j,a[i]+j,&a[i][j],*&a[i]+j 第 i行第 j列元素的地址
*(*(a+i)+j),*(a[i]+j),*(&a[i][j]),第 i行第 j列元素的值
*(*&a[i]+j),a[i][j]
2009-7-28 36
指针和字符串对字符数组中的字符逐个处理时,可用指针与数组的关系。字符串通常作为整体使用。
例 7,用指针实现字符串的拷贝。 87
程序执行后输出,s3=I am a student!
s4=I am a student!
s5=You are a student!
s2= You are a student!
2009-7-28 37
注,虽然用字符指针变量和字符数组都能实现字符串的存储和处理,但二者是有区别的,不能混为一谈。
( 1) 存储内容不同。
字符指针变量中存储的是字符串的首地址,而字符数组中存储的是字符串本身(数组的每个元素存放一个字符)。
如,char *pc,str[100];
编译时为字符数组 str分配 100个字节的空间,而为指针变量 pc仅分配 4个字节的空间,只能放一个内存单元的地址。
( 2) 赋初值的形式类同,但含义不同。
如,char str[]=“I am a student!”,s[200];
char *str2=“You are a student!”,str3[100],*str4,*str5;
2009-7-28 38
字符数组是将字符串送到为数组分配的存储空间中,而字符型指针是先将字符串放到内存中,再将存放字符串的起始地址存放到指针变量中。
( 3) 赋值方式不同 。
对字符指针变量,可采用下面的赋值语句赋值:
char *pointer;
pointer="This is a example.";
而字符数组,虽然可以在定义时初始化,但不能用赋值语句整体赋值 。 下面的用法是非法的:
char char_array[20];
char_array="This is a example."; //非法用法
2009-7-28 39
( 4) 可以给字符数组直接输入字符串,而在给字符指针变量赋初值前,不允许将输入的字符串送到指针变量所指向的内存区域。 如:
char s1[50],*chptr,s2[200];
cin>>s1; //正确
cin>>chptr; //错误 。因不知道 chptr指向什么样的内存单元
chptr=s2;
cin>>chptr;
//正确。因 chptr指向一个已分配的内存空间指针与数组的差异:
指针是地址变量,
可任意改变它的值;
数组名是地址常量,
其值不能改变。
2009-7-28 40
( 5) 指针变量的值是可以改变的,字符指针变量也不例外;数组名代表数组的起始地址,是一个常量,而常量是不能被改变的。 如:
chptr=s1+5; //正确
chptr=chptr+5; //正确
s1=s1+2; //错误
2009-7-28 41
指针数组和指向指针的指针变量指针数组概念:数组的每个元素都是一个指针数据。指针数组比较适合用于指向多个字符串,使字符串处理更加方便、灵活。
定义格式:
存储类型 数据类型 *数组名 [元素个数 ]
注意,这里运算符,[ ]”的优先级高于,*”,因而,*数组名 [元素个数 ]”指明是一个指针数组。如:
int *p1[4]; //p1为一整型指针数组,有 4个元素 float *p2[16]; //p2是一实型指针数组,有 16个元素
2009-7-28 42
例 8,指针数组与数组元素的关系。
void main ( void){
int a[2][3],*pa[2];
pa[0]=a[0];
pa[1]=a[1];
for ( int i=0; i<3; i++ )
*( pa[0]+i)=i;
for ( i=0; i<3; i++ )
*( pa[1]+i)=i;
}
p a [ 0 ]
p a [ 1 ]
0
1
2
0
1
2
p a [ 2 ] a [ 2 ] [ 3 ]
a [ 0 ] [ 0 ]
a [ 0 ] [ 1 ]
a [ 0 ] [ 2 ]
a [ 1 ] [ 0 ]
a [ 1 ] [ 1 ]
a [ 1 ] [ 2 ]
2009-7-28 43
例 9,用指针数组输出数组中各元素的值。 88
程序执行后输出:
100 200 300 400 500
例 10,将若干个字符串按升序排序后输出。
分析,定义一个指针数组 str,它的每个元素指向一个字符串。先找到最小的字符串,并使该指针值与 str[0]中的值交换,使 str[0]指向最小的字符串,接着找次小的,使 str[1]指向次小的,依次类推,直到找到最大的为止。
2009-7-28 44
str[0]
str[1]
str[2]
str[3]
str[4]
Follow me
BASIC
Great wall
Department
Computer design
排序前指针数组 str的指向
str[0]
str[1]
str[2]
str[3]
str[4]
Follow me
BASIC
Great wall
Department
Computer design
排序后指针数组 str的指向 89
2009-7-28 45
程序执行后输出:
BASIC
Computer design
Department
Follow me
Great wall
指向一维数组的指针变量定义了一维数组后,再定义一个指针变量,
使指针变量指向该数组的起始地址后,可用指针变量来代替数组名。如,int j,a[100],
*p=&a[0];
则 a[j]与 p[j]作用相同。对二维数组同样。
2009-7-28 46
例 11,用不同的表示法输出二维数组中的元素。
810执行后输出,用行指针输出数组的各个元素:
5 6 7 8
9 10 11 12
13 14 15 16
用四种不同的方法输出数组的各个元素:
5 5 5 5
6 6 6 6
7 7 7 7
8 8 8 8
9 9 9 9
10 10 10 10
11 11 11 11
12 12 12 12
13 13 13 13
14 14 14 14
15 15 15 15
16 16 16 16
2009-7-28 47
注,( 1)说明语句,int (*p)[4];”中,p是一个指针变量,表示指针变量所指向的数据是一个一维数组,该数组有四个元素。若将二维数组的起始地址赋给 p后,(*p)[0]是二维数组中第 0行第 0列元素的值,
而 *(p+i)+j是一个地址。
( 2)设有说明语句:
float (*pp)[N],x[M][N];
pp=x;
此时的 pp=pp+1,使指针变量 pp指向二维数组的下一行,即做运算:
pp=pp+sizeof(float)*N
如,pp=x[i]; //错误,这是第 i行第 0个元素的地址,而不是行地址
pp=&x[i]; //正确,或 pp=x+i;
2009-7-28 48
( 3)定义这种指针变量时,它所指向的一维数组的元素个数应与二维数组的列数相同。
( 4)可把这种表示方法推广到三维或更多维的数组中。如定义指向二维数组的指针变量的方法为:
int (*ptr)[20][50];
//定义指针变量 ptr,有 20行 50列其加 1的运算为:
ptr=ptr+1
系统内部所做的实际运算是:
ptr=ptr+sizeof(int)*20*50
2009-7-28 49
指向指针的指针变量前面介绍的通过指针访问变量称为 间接访问,简称 间访 。由于指针变量直接指向变量,所以称为 单级间访 。 而如果通过指向指针的指针变量来访问变量则构成了 二级或多级间访 。在C ++中,对间访的级数并未明确限制,但是间访级数太多时不容易理解,也容易出错,因此,一般很少超过二级间访。
指向指针的指针变量说明的一般形式为:
存储类型 类型说明符 ** 指针变量名;
如,int ** pp; 表示 pp是一个指针变量,它指向另一个指针变量,而这个指针变量指向一个整型量。
下面举一个例子来说明这种关系。
2009-7-28 50
例 12,多级指针的简单使用。
811执行后输出:
i=10
Address of p1=0x0064FDF4
Address of p2=0x0064FDF0
i=10
i=10
程序中指针变量的关系如下:
p3,p1,i:10p2:
2009-7-28 51
指针和函数指针作为函数的参数将指针作为函数的参数时,传递给函数的是某一个变量的地址,称为 地址传递 。
指针值和指针所指向的数据均可作为函数的输入参数。也可将指针所指向的数据作为函数的输出参数。
例 13,实现两个数据的交换。
812执行后输出,a=100,b=200
a=200,b=100
2009-7-28 52
100
200
p1 a
p2 b
交换前
200
100
p1 a
p2 b
交换后
2009-7-28 53
若将该例中的 swap()函数改为:
void swap1( int x,int y) { //指针参数改为整型变量
int t;
t=x; x=y; y=t;
}
主函数中 swap( &a,&b);改为 swap1( a,b);
则 a,b的值不变。这是前面已介绍的函数参数的值传递方式。
a b
x y
交换前
100 200
100 200
a b
x y
交换后
100 200
200 100
2009-7-28 54
由于数组名表示数组的首地址,故数组名作为函数的参数时,作用与指针相同。下列四种情况一样:
( 1)数组作形参,实参为数组名;
( 2)指针型变量为形参,实参为数组名;
( 3)数组作形参,实参为指针;
( 4)指针型变量为形参,实参为指针。
例 14,设计一程序,使数组或指针作为函数的参数,
实现整型数组的排序。
813执行后输出,3 4 34 45 67 78
3 4 34 45 67 78
3 4 34 45 67 78
2009-7-28 55
多维数组作函数参数下列三种情况相同:
( 1)多维数组作形参,实参为多维数组名;
( 2)将多维数组作为一维数组处理,一维数组或一级指针作一个形参,另一形参为数组元素的个数,实参为数组的起始地址;
( 3)用指向一维数组的指针作参数。
例 15,设计一个通用的矩阵相乘的函数。
分析,将二维数组作一维数组处理。矩阵相乘公式为:

1n
0k
kjikij bac
814
2009-7-28 56
例 16,设计一程序,求二维数组的平均值。
815执行后输出:平均值 =82.25
平均值 =82.25
平均值 =82.25
例 17,设计一程序,实现 C语言的 Printf()库函数的功能,为了简化程序设计,适当地简化了
Printf()函数有关格式符方面的功能。
816执行后输出:
C++ language,25,c,%,34.5
2009-7-28 57
函数返回值为指针的函数用 return可返回一个值或不返回值,但需返回多于一个值时,要通过函数的参数来实现。另外还可返回一个指针,指针可指向任一已定义的类型数据。
一般定义格式:
类型符 *函数名(参数表) {…}
如,int *fp ( char ); //函数 fp返回一个指向整型的指针
2009-7-28 58
例 18,设计一程序,将输入的字符串按逆序输出。
分析,用字符型指针变量 p1指向原字符串的第 0个字符,用指针变量 p2指向最后一个字符,将 p1和 p2所指的字符交换,p1向后移一位,p2向前移一位,继续。
817若输入字符串:
Computer Department.
则输出:
.tnemtrapeD retupmoC
此题还可用递归函数实现。
8171
2009-7-28 59
例 19,输入两个字符串,设计一程序,把这两个字符串拼成一个新的字符串,然后输出这三个字符串,字符拷贝和拼接的函数自己设计。
818若输入字符串:
C++ Program和 ming Socre.
则输出:
s1=C++ Program
s2=ming Score.
将 s1拷贝到 s3中后,s3=C++Program
将 s2拼接到 s1后的字符串为,C++Programming Score.
2009-7-28 60
指向函数的指针虽然函数不是变量,仍占存储空间,此空间的首地址 ——函数入口地址 称为 函数的指针,若指针变量的值为一个函数的入口地址,则称它为 指向函数的指针变量 。
( 1)定义格式存储类型 数据类型 ( * 函数指针名)(参数表)
如,int (*fp) ( char ); //fp为一函数指针,此函数的
//返回值为 int,参数为 char
int (*fp)(float,float); // fp为指向 int型函数的指针变量注意:,*指针变量”外的括号不能缺,否则成了返回指针值的函数。
2009-7-28 61
( 2)赋值函数名代表该函数的入口地址。 因此,可用函数名给指向函数的指针变量赋值。
指向函数的指针变量=函数名 ;
注意,函数名后不能带括号和参数,且与该指针变量具有相同的返回值类型和相同参数(个数与顺序均一致)。
如,float f(float);
float (*fp1)(void);
float *(*fp2)(float *,float **);
float *f2(float *,float **);
fp1=f; //错误,变量类型不同
fp2=f2; //正确
2009-7-28 62
( 3)调用格式
(*函数指针变量 ) ([实参表 ])
或 函数指针变量 ([实参表 ])
( 4)指向函数的指针变量作函数参数指向函数的指针变量的常用用途之一,是将函数指针作参数,传递到其它函数。
函数名作实参时,因为要缺省括号和参数,造成编译器无法判断它是一个变量还是一个函数,所以必须加以说明。函数说明的格式,与前面介绍的一样。
注意,对指向函数的指针变量,诸如 p+i,p++/p--等运算是没有意义的。
2009-7-28 63
例 23,设计一程序,完成两个操作数的加、减、
乘、除四则运算。
822若输入,45+55
执行后输出,45+55=100
2009-7-28 64
例 24,已知一个一维数组的各个元素值,分别求出:
数组的各个元素之和、最大元素值、下标为奇数的元素之和及各元素的平均值。
823执行后输出:
12个元素之和是,719.71
12个元素中的最大值是,345.6
奇数下标元素之和,443.47
12个元素的平均值是,59.9758
由于将指向函数的指针变量作为函数的参数,能在调用一个相同名的函数过程中执行功能不同的函数,因而增加了程序设计的灵活性。
2009-7-28 65
例 25,设计一程序,用梯形法求下列定积分的近似值:
dx
xxs i n1
xx
3a r e a
dx
)x1(
x
2a r e a
dx)x1(1a r e a
3
1
2
2
5.2
1
2
4
2
2


2009-7-28 66
其中 a,b分别是积分的上下限,n为积分区间的分隔数,h=(b-a)/n为积分步长,f(x)为被积函数。
故需四个形参。
824执行后输出:
第一个积分值,20.6667
第二个积分值,0.643927
第三个积分值,1.98498
分析,梯形法求定积分的公式为:

1n
1i
h)]hia(f2 )b(f)a(f[are a
2009-7-28 67
new和 delete运算符
C++中的 new运算符可根据对象的类型,自动决定其大小,动态地分配空间,返回的是内存地址,可赋给一个指针,如,
int n;
cin>>n;
int *pi; //定义一个指针变量
pi=new int[n]; //分配内存地址给 pi
int *pp,a=9;
pp=&a; //同样给 pp分配了地址而
int n;
cin>>n;
int a[n]; //错误,无法实现注意,如果无内存分配,则返回空指针 NULL,所以应检查有无内存分配。
2009-7-28 68
nuw运算符的使用格式为:
pointer=new type; //分配由类型 type确定大小
//的一片连续空间,并将所分配内存空间
//的起始地址给 pointer,若分配不成功,则
//pointer的值为 0
或 pointer=new type(value);
//分配后将 value的值作为内存空间的初始值或 pointer=new type[<表达式 >];
//分配指定类型的数组空间
2009-7-28 69
delete运算符是释放不需要的内存空间。
delete运算符的使用格式为:
delete pointer;
//将 pointer所指的内存空间还给系统或 delete [ ]pointer;
//将 pointer所指的一维数组内存空间还给系统或 delete [<表达式 >]pointer;
//将 pointer所指的一维数组内存空间还给系统如,delete pi;
2009-7-28 70
注意,释放一个已释放的内存可能导致程序崩溃,。
所以释放后最好置为 0。如:
float *p=new float; //OK
delete p; //OK
delete p; //危险 !
例 26,设计一程序,实现动态内存空间分配。
826执行后输出:(*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
*p1=25
*fp1=2.5
*cp=A
2009-7-28 71
使用 new和 delete运算符应注意的事项
1,new所分配的存储空间的初值不确定,使用前应初始化。
2,一般应检验 new分配的空间是否分配成功,若失败应终止程序或作出错处理。如:
float *p;
p=new float [1000];
if(p= =0){
cout<<“动态分配内存不成功,
终止程序的执行! \n”;
exit(3);
}
3,动态分配存放数组的内存空间,或为结构体分配内存空间时,不能在分配空间时进行初始化。如:
int *pi;
pi=new int [10](1,2,3,4,5,6,7,8,9); //错误
2009-7-28 72
4,若 new运算符计算的指针类型与赋值运算符左操作数类型不一致时,要进行强制转换。如:
float (*p1)[10],(*p2)[20];
p1=new float[20]; //错误 。因 p1是用于指向
//二维数组的指针,而 new运算的结果为一
//维数组的指针而 p1=(float (*)[10])new float[10];
//正确。进行了强制类型转换或 p1=new float[1][10];
//正确。分配二维数组的内存空间又如:
p2=new float[10][20];
//正确。分配 10行,每行 20个元素的二维数组
int *p3;
p3=new int[10][20]; //错误而 p3=(int *)new int[10][20]; //正确或 p3=new int[10*20]; //正确
2009-7-28 73
5,用 new分配的内存空间指针值应保存,以便用
delete归还,否则出错。如:
float *fp,i;
fp=new float;
*fp=24.5;
fp=&i;
delete fp;
//出错 。因 fp已不指向动态分配的空间了
6,当动态分配了二维数组的存储空间后,在释放时要指明数组的行数。如:
int (*p)[100];
p=new int[30][100];

delete [30]p; //释放二维数组空间若写成,delete p;则仅释放第 0行所占用的空间。
2009-7-28 74
7,用 delete释放空间后,不返回任何值。故释放后不可再对其赋值。如:
float *p;
p=new float;

delete p;
*p=5; //出错 。因不知道 p指向何处
2009-7-28 75
引用和其他类型的指针
引用 ---别名的定义,同一实体,两个不同的名称。
C++中的引用:同一内存单元二种不同的变量名。
作用:通过引用,可以实现在不同的作用域内访问同一内存单元。如:
void fun(int &a)
{ a=3;
cout<<a<<?\n?;
}
void main(void)
{ int X=1;
fun(X);
cout<<X<<?\n?;
}
您知道 X与 a
是什么关系吗?
输出,
3
3
2009-7-28 76
格式,<类型 > & <引用变量名 >=<对象名或变量名 >
这里对象名或变量名是一个已定义过的变量。如:
int count;
int &refcount=count;
//定义了引用类型变量 refcount,
//是变量 count的别名,称 count为
//refcount引用的变量或关联的变量例 27,引用类型变量的使用。
827
执行后输出:
refi=200
i=400
2009-7-28 77
注,( 1)定义引用类型变量时,要初始化。初始化变量类型要与引用类型变量的类型相同。如:
float x;
int &px=x; //错误 。 px与 x的类型不同
( 2)引用类型变量的初始化值不能是常数。如:
int &ref1=5; //错误
( 3)能说明为引用类型变量的引用,不能说明为引用类型数组。但引用数组中的某一个元素是可以的。如:
int i,&refi1=i; int a[10];
int &ref5=refi1; //正确 int &refa=a; //错误
int &ref6=ref5; //正确 int &refaa[10]=a; //错误
int &refi2=&refi1; //错误 int &* refa3=a; //错误
int &&re=&refi1; //错误 int &refa2=a[2]; //正确
int &ref=i; //正确
2009-7-28 78
( 4)可用动态分配的内存空间来初始化一个引用变量。如:
float &reff=*new float; //new的结果是一个
//指针,而对引用初始化的值不是
//一个指针,故要加 *号
reff=200;
cout<<reff;
delete &reff; //对 delete的操作数必须是一个
//指针,故变量前要加取地址运算符 &
( 5)到现在介绍的 C++中,&”的三种含义:
按位与运算符,&”,一个二元运算符;
取地址运算符,&”,一个一元运算符;
引用运算符,&”,定义引用类型变量。
2009-7-28 79
引用和函数引用的目的 是在函数参数传递时方便。主要用于函数的参数或作函数的返回值类型。
若函数的参数说明为引用类型,称引用类型的实参为 引用传递 。引用传递既可作为函数的输入参数,也可作函数的输出参数。
例 28,设计一程序,输入两个整数,使之交换后输出。
828若输入 x,y的值为 300,400,a、
b的值为 100,200,则输出:
x=400 y=300
a=200 b=100
2009-7-28 80
const类型变量
用 const限制说明标识符时,说明的数据类型为常量类型。分 const型常量和 const型指针。
( 1) const型常量
定义格式,const <类型 > <变量 > [=<表达式 >]
如,const int MaxLine=1000;
const float Pi=3.1415926;
const int a=100;
注,只能定义时初始化,不能用赋值运算符赋值。如:
MaxLine=35; //错误
2009-7-28 81
const 修饰符的含义 ——不可以更改!
一经 const修饰后便不能修改此符号名之值 。
如,const int sumValue=10;
sumValue=0; //错误又如:
void Display(const int *ptr,int n){
cout<<ptr[0]; //同 *(ptr+0),显示数组中的第一个元素之值
ptr[0]=1; //错误,不能修改其值 (不能通过
//指针来改变目标单元之值 )
*ptr=1; //错误,不能修改其值 (不能通过指
//针来改变目标单元之值 )
}
2009-7-28 82
const型常量与用 define定义的符号常量的异同:
相同处,使用效果相同。
不同处:
用 define定义的符号常量在编译之前,由编译预处理程序处理,const型常量由编译程序处理。
const定义的常量作用域分为局部常量和全局常量,而用 define定义的符号常量的作用域始终是全局常量。
2009-7-28 83
( 2) const型指针有三种方法说明 const型指针。
将 const放在指针变量的类型之前。 表示指针变量所指向的数据是一个常量。不能改变指针变量所指向的数据值,可改变指针变量的值。定义时可赋初值,也可不赋。如:
const int *Pint; *Pf=25; //错误
float x,y; x=200; //正确
const float *Pf=&x; Pf=&y; //正确
2009-7-28 84
将 const放在指针变量的,*”之后。 表示指针变量的值是一个常量。不能改变指针变量的值,可以改变指针变量所指向的数据值。定义时必须赋初值。
如:
int n,i; *pn=25; //正确
int *const pn=&n; pn=&i; //错误?把一个 const放在指针变量的类型之前,另一个
const放在指针变量的,*”之后。 表示指针变量的值是一个常量,指针变量所指向的数据也是一个常量。不能改变指针变量的值,也不能改变指针变量所指向的数据值。如
int j,k; *pp=25; //错误
const int *const pp=&j; pp=&k; //错误
2009-7-28 85
指向常量的指针,const int *p p *p
&p 修改 ╳
修改 √
指向指针常量的指针,int *const p=1 p *p
&p 修改修改


2009-7-28 86
指向常量对象的指针常量,const int * constp p *p
&p 修改 ╳
修改 ╳
注意:不能把一个
const变量的地址赋给指向非 const的指针,否则会无形中改变常量的值
2009-7-28 87
C++中为什么要提供常量定义?
如果不使用常量,直接在程序中填写数字或字符串,
将会有什么麻烦?
程序的可读性(可理解性)变差。程序员自己会忘记那些数字或字符串是什么意思,用户则更加不知它们从何处来、表示什么。
在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。
如果要修改数字或字符串,则会在很多地方改动,
既麻烦又容易出错。
2009-7-28 88
正确区分 const在修饰指针时的两种不同用法,
const int *ptr=&X;
o 此时不能通过指针来访问改变目标单元之值,即
* ptr=1 是错误的,但是 ptr 本 身 可 以 被 改变
ptr=&Y
o 此种用法常见于函数的形参 。
void fun(const int * ptr)
{ *ptr=1; //错误
}
int * const ptr=&X;
o 此时 ptr本身不可以被改变 ( ptr=&Y是错误的 ) ;
但 ptr所指向的目标的单元之值是可以被改变的 。
如:
*ptr=2; ( 即 X=2)
o 此时 ptr类似于数组名 ( 常量指针 ) 。 如:
int ptr[10];
ptr[0]=2;
2009-7-28 89
正确区分符号名之前有无 const修饰时的不同:
int sum=1; //无 const修饰时此时 sum为变量,意味着其值可以被改变 ( 如:
sum=2;) ;同时其内存字节数为四个字节 。
const int sum=1; //有 const修饰时此时 sum为常量,意味着其值不可以被改变 ( 如:
sum=2;) ;同时不需要占用内存空间 。 另外 C++
语言视经 const修饰的符号名为常量特性,因而它可以用于任何可能出现数字常量的场合 ( 如数组的大小的说明等 ) 。 如:
const int ArraySize=10;
char array[ArraySize];
2009-7-28 90
const的应用实例例:符号常量
float Area( int r)
{ const float PI=3.1415926;
return PI*r*r;
}
例:限制不能通过形参来改变实参
void main( )
{ int x=1;
fun(&x);
cout<<x<<?\n?; //x=?
}
void fun( int *x)
{ *x=3;
}
void main( )
{ int x=1;
fun(&x);
cout<<x<<?\n?;
}
void fun(const int *x)
{ *x=3; //错误
}
x=3
2009-7-28 91
简单链表及其应用链表概述链表 是指将若干个同类型的结构体类型数据按一定的原则连接起来。
head

陈宏 王芳 马弛
1819 18
… … …
头指针 struct node{
char name[20];
int age;

node *next;
};
2009-7-28 92
链表的一般形式
( 1)头指针变量 head──指向链表的首结点。
( 2)每个结点由 2个域组成:
1)数据域 ──存储结点本身的信息。
2)指针域 ──指向后继结点的指针。
( 3)尾结点的指针域置为,NULL(空)”,作为链表结束的标志。
… …head ^
2009-7-28 93
对链表的基本操作有,创建,检索 ( 查找 ),
插入,删除 和 修改 等 。
( 1) 创建链表 是指,从无到有地建立起一个链表,即往空链表中依次插入若干结点,并保持结点之间的前驱和后继关系 。
( 2) 检索操作 是指,按给定的结点索引号或检索条件,查找某个结点。如果找到指定的结点,
则称为 检索成功 ;否则,称为 检索失败 。
( 3) 插入操作 是指,在结点 ki-1与 ki之间插入一个新的结点 k?,使线性表的长度增 1,且 ki-1与 ki的逻辑关系发生如下变化:
插入前,ki-1是 ki的前驱,ki是 ki-1的后继;插入后,新插入的结点 k?成为 ki-1的后继,ki的前驱 。
链表的操作
2009-7-28 94
… …
ki-1 ki
插入前
… …
ki-1 ki
插入后 k?
2009-7-28 95
( 4) 删除操作 是指,删除结点 ki,使线性表的长度减 1,且 ki-1,ki和 ki+1之间的逻辑关系发生如下变化:
删除前,ki是 ki+1的前驱,ki-1的后继;删除后,ki-1成为 ki+1的前驱,ki+1成为 ki-1的后继。
… …
ki-1 ki
删除前
ki+1
… …
ki-1 ki+1
删除后
2009-7-28 96
例 29,链表的基本操作。
包括:
( 1)建立一条无序链表;
( 2)输出链表上各结点的数据;
( 3)删除链表上一个结点;
( 4)建立一条有序链表,输出链表上的各结点的数据。
结点的结构如下:
struct node{
int data;
node *next;
};
2009-7-28 97
建立一条无序链表,函数如下:
831head
0
p1
a
head
p2
p1

p2
head
p1
p2
2009-7-28 98
输出链表上各个结点的值,函数如下:
8311
删除链表上具有指定值的一个结点,函数如下:
8312
释放链表的结点空间。函数如下:
8313
将一个结点插入有序链表仍保持有序。函数如下:
8314
2009-7-28 99
建立一条有序链表。函数如下:
8315
完成链表处理的完整程序如下:
8316
2009-7-28 100
除可直接使用C ++提供的标准类型和自定义的类型( 结构、共用、枚举 )外,也可使用 typedef定义已有类型的别名。该别名与标准类型名一样,可用来定义相应的变量。
定义的一般格式如下:
typedef 类型 标识符 1,标识符 2,… ;
如,typedef float REAL;
//给实型 float定义一个别名 REAL
REAL i,j; //等同于 float i,j;
如,typedef char *STRING;
STRING s1,s2; //等同于 char *s1,*s2;
类型定义
2009-7-28 101
如,typedef int VEC[50];
VEC x,y; //等同于 int x[50],y[50];
如,typedef struct node{
char *word;
int count;
struct node *left,*right;
}TREENODE,*TREEPTR;
TREENODE pp,p1[10];
//等同于 node pp,p1[10];
TREEPTR p; //等同于 node *p;
如,typedef int (*PF)(void);
PF f; //等同于 int(*f)(void)
2009-7-28 102
说明:
( 1)用 typedef只是给已有类型增加一个别名,并不能创造一个新的类型。就如同人一样,除学名外,可以再取一个小名(或雅号),但并不能创造出另一个人来。
( 2) typedef与 #define有相似之处,但二者是不同的:
前者是由编译器在编译时处理的;后者是由编译预处理器在编译预处理时处理的,而且只能作简单的字符串替换。
( 3) typedef只能定义类型标识符,不能定义变量。
( 4)定义的标识符要具有可读性和可理解性。如:
typedef int LENGTH,WIDTH,SIZE;
LENGTH l1,l2,l3; //变量 l1,l2,l3表示长度
WIDTH w1,w2,w3; //变量 w1,w2,w3表示宽度
SIZE s1,s2,s3; //变量 s1,s2,s3表示大小
2009-7-28 103
1,指针是C ++中一个重要的组成部分,使用指针编程有以下优点:
(1)提高程序的编译效率和执行速度。
(2)通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯。
(3)可以实现动态的存储分配。
(4)便于表示各种数据结构,编写高质量的程序。
2,指针的运算
(1)取地址运算符 &:求变量的地址
(2)取内容运算符 *:表示指针所指的变量课 堂 总 结
2009-7-28 104
(3)赋值运算
·把变量地址赋予指针变量
·同类型指针变量相互赋值
·把数组,字符串的首地址赋予指针变量
·把函数入口地址赋予指针变量
(4)加减运算对指向数组,字符串的指针变量可以进行加减运算,如
p+n,p-n,p++,p--等。对指向同一数组的两个指针变量可以相减。
对指向其它类型的指针变量作加减运算是无意义的。
(5)关系运算指向同一数组的两个指针变量之间可以进行大于、小于,等于比较运算。指针可与 0比较,p= =0表示 p为空指针。
2009-7-28 105
3,与指针有关的各种说明和意义见下表。
int *p; p为指向整型量的指针变量
int *p[n]; p为指针数组,由 n个指向整型量的指针元素组成。
int (*p)[n]; p为指向整型二维数组的指针变量,二维数组的列数为 n
int *p( ) p为返回指针值的函数,该指针指向整型量
int (*p)( ) p为指向函数的指针,该函数返回整型量
int **p p为一个指向另一指针的指针变量,该指针指向一个整型量。
4,有关指针的说明很多是由指针、数组、函数说明组合而成的。
但并不是可以任意组合,例如数组不能由函数组成,即数组元素不能是一个函数;函数也不能返回一个数组或返回另一个函数。如:
int a[5]( ); //错误的。
2009-7-28 106
5,关于括号在解释组合说明符时,标识符右边的方括号和圆括号优先于标识符左边的,*”号,而方括号和圆括号以相同的优先级从左到右结合。但可以用圆括号改变约定的结合顺序。
6,阅读组合说明符的规则是“从里向外”。
从标识符开始,先看它右边有无方括号或圆括号,如有则先作出解释,再看左边有无 *号。 如果在任何时候遇到了闭括号,则在继续之前必须用相同的规则处理括号内的内容。如:
int*(*(*a)())[10]
↑ ↑↑↑↑↑↑
7 6 4 2 1 3 5
2009-7-28 107
上面给出了由内向外的阅读顺序,下面来解释它:
(1)标识符 a被说明为;
(2)一个指针变量,它指向;
(3)一个函数,它返回;
(4)一个指针,该指针指向;
(5)一个有 10个元素的数组,其类型为;
(6)指针型,它指向;
(7)int型数据。
因此 a是一个函数指针变量,该函数返回的一个指针值又指向一个指针数组,该指针数组的元素指向整型量。