Download presentation
Presentation is loading. Please wait.
1
第6章 指针与数组 十一、程序动态存储结构 十二、指针的类型转换和匹配关系 十三、下标表达式与访问指针寻址计算
2
十一、程序动态存储结构 系统将内存数据分为三个段即数据段、代码段和堆栈 段。 编译程序对源程序进行编译时,将函数中的执行语句部
分编译成指令代码放置在代码区,将静态变量、全局变量与 字符串常数存放在全局数据区,函数的形式参数放置在临时 开辟的称之为先进后出的堆栈区,堆栈区内尚包含函数定义 部分中内部引入的局部变量或局部数组。 另外系统提供一些函数进行内存的动态控制,这一片可 以由用户控制的动态区域称为堆结构或堆区,这一可由用户 动态控制的区域实际上是隶属于数据段的。 剩下的存储单元为自由内存空间。
3
1. C++中new运算符和delete运算符
的。 new运算符试图动态地根据其后的类型名分配堆内存空 间,也即在堆空间定义变量或数组。 new运算符不能用于分配一个函数,但可以用于分配一 个函数指针。 new运算符定义堆中变量或对象的格式为: new type(initialList) new 类名(初始化表) 表达式的结果是type*类型的地址。 下面的几个语句: int x; int *p=&x; *p=5;
4
对变量x进行了间接赋值,上面的指针变量p也可以指
向堆空间: p=new int(5); p=new int; *p=5; 值得注意变量x的地址在变量x的生存期是不变的, new运算符分配的地址在其生存期也具有类似的性质,不同 的是该存储空间可以由用户通过delete运算符释放。 new运算符构成的表达式建立或定义动态一维数组的语 法格式为: new type [dynSize] new 类名[动态数组大小] 表达式的结果是type*类型的地址。
5
系统在堆区中开辟了一块大小为dynSize *sizeof (type)
的空间,表达式的结果就指向这块存储空间的开始位置,因 此这一地址具有数组名一样的性质。 当new运算符建立动态数组时,不同时指定数组元素的 初值。 这个语法格式适用于一维数组的动态确定,动态数组大 小dynSize初始化的位置是灵活的,可以在运行时实时地输 入,但必须在new函数的调用点之前确定。
6
当不能成功地分配所需要的内存时,new返回0。判断
存供程序使用。例如: int *p=new int[10000]; if (p==0) cout<<”run out of memory”<<endl; 值为零的指针称为空指针,它表示指针的一种状态。 如下语句定义了一个固定指针hArray,hArray像一维 数组名一样不作为左值,hArray指向堆空间中的某个存储位 置: long* const hArray = new long[100];
7
new运算符表达式分配或定义动态二维数组的格式为:
new double [ dynSize ][ maxn ] // new 类名[动态数组大小][ 固定数组大小] 表达式的结果为double (*)[maxn]型的地址,因此可以用如 下语句进行初始化: double (*p)[ maxn ]= new double[ dynSize ][ maxn ]; 也可以先定义数组指针然后再调用new运算符: double (*p)[ maxn ]; ;... ; p = new double[ dynSize ][ maxn ];
8
类似地new运算符分配或定义动态三维维数组的格式
为: new int [ dynSize ] [ maxm ][ maxn ] // new 类名[动态维数][固定维数m][固定维数n] 表达式的结果为int (*)[maxm][maxn]型的地址,因此可以 用如下语句进行初始化: int (*p) [ maxm ][ maxn ]= new int[ dynSize ] [ maxm ][ maxn ]; 上面的数组维数只有dynSize是可以动态输入的正数表达 式。 其余的必须是编译期间就确定的正整型常数表达式。
9
delete运算符释放由new运算符创建的对象内存。
delete运算符表达式的结果为void类型的函数返回,因 此仅单独调用。 delete的操作数必须是由new运算符返回的地址。 delete运算符表达式的语法格式为: delete 指针表达式; delete pointer; 或: delete [ ]指针表达式; delete [ ] pointer;
10
delete运算符删除后的堆内存空间是未定义的,在删
除堆空间后间接访问这个指针则会产生意料不到的结果。 其中delete pointer形式的运算符与new type 匹配, 即用于释放单个对象指针。 而delete [ ] pointer形式的运算符与new type[ size ] 匹配,即用于释放动态数组。 对不是用new分配的对象指针使用delete会产生意料不 到的结果。
11
十一、程序动态存储结构 也可以当作多维数组使用。 这个void*型的首地址须强制转换为确定类型 的指针,也就是明确内存空间的具体访问规则。
如果系统没有足够的多余内存,malloc函数返 回NULL即0。
12
例如: double* p = (double*) malloc (24); //相当于p= (double*)malloc(sizeof(double[3])); long ( *q )[ 3 ] = (long (*)[ 3 ])malloc (24); // 或q= (long (*)[3])malloc(sizeof(long[2][3])); 表示两个24个字节的堆空间分别委托一级指针p和数组 指针q管理,相当于p管理double型的一维数组p[3],q管理 long型的二维数组q[2][3]。 系统并不自动释放用户申请 malloc函数分配的堆空 间,良好的编程习惯是及时地归还系统资源,malloc函数分 配的空间应由free函数来清除。
13
free函数的原型为:void free ( void* ptr );ptr匹配
malloc函数分配的内存块地址,例如:free(p), free(q) ; 不要将其它的指针值作为free的实参,以免引起不可预 料的错误。 new和delete运算符同malloc函数和free函数的工作 原理是一致的,本质上new和delete运算符是malloc和free 等内存控制函数的某种映射。 在新开发软件的时候优先采用new与delete运算符。 new运算函数的返回类型已经参照malloc内在机制进行了严 格的界定,因此无需提供显示类型转换,但此时接受指针应 与返回类型严格匹配才行。malloc函数则要求提供类型转 换,以便将泛泛的一块内存空间用于具体类型的数据运算。
14
[例] 动态申请一个一维数组 #include<stdio.h> #include<malloc.h> void main(void) { int m; scanf ("%d", &m); //数组的维数m动态实时输入 int * a,k; if (m%2) a= new int [m]; //相当于在堆空间定义int a[m]; else a= (int*)malloc (m*sizeof (int)); //与上面分支new等价的malloc版本。 for( k=0; k<m; k++) a [ k ]=k; //在a指向堆空间时不要改动a的值 int *p= a; //设置另一个指针p遍历访问堆空间
15
for (k=0; k<m; k++, p++)
printf ("a[%d]=%d ", k,*p); if (m%2) delete [ ] a; // delete [ ] a匹配a= new int[m] else free (a); // free(a) 匹配a=(int*)malloc (m*sizeof(int)) } //动态运行结果为:4 a[0]=0 a[1]=1 a[2]=2 a[3]=3 定义语句{int a[4];}定义一个4个元素的数组, 这个数组 名a具有int*const类型属性。不妨认为语句 {int* a=new int [m];} 定义一个m个元素的数组, 但m是可以动态改变的整型变 量。该数组通过int*型的指针a管理,同时指望delete运算符 收回相应的内存空间。
16
[例] 申请一个二维数组,第一个下标是可变的
#include<stdio.h> #include<malloc.h> void main(void) { const int N=4; int m, k, j; scanf("%d",&m); int (* d) [N]; if (m%2) d= new int [m][N]; else d= (int (*)[N]) malloc (m*N*sizeof (int));
17
for( k=0;k<m;k++) //在d指向堆空间时不要改动d的值 for( j=0; j<N; j++) d [k] [j]=k*N+j; int (*q)[N]= d; for (k=0; k<m; k++, q++) { int *p =*q; for( j=0; j<N; j++) printf ("d [%d][%d]=%d ", k, j, p[j]); printf ("\n"); } if (m%2) delete [ ] d; else free (d);
18
定义语句{int d[2][4];}定义一个2行4列的二维数组, 这
个数组名d具有int (*)[4]类型属性。不妨认为语句 {(int (*d)[N] = (int (*)[N]) malloc (m*N*sizeof (int));} 定义一个m行N列的二维数组d[m][N], 但m是可以动态改变 的整型变量。 该数组通过int (*)[N]类型属性的指针d管理,同时指望 free函数收回相应的内存空间二维动态数组由一维动态指针 数组构成,该指针数组的每一个元素分别指向一维动态数 组。 二维动态可调数组是高频采用的编程技术。
19
下面的程序建立二级指针,该二级指针与二维动态数组
相联系,指向堆空间。 [例] 动态的二维数组pp[M][N]分配,维数M,N都可实时输入 typedef long type; #include<iostream.h> #include<malloc.h> #include<process.h> void main(void) { type** pp; cout<<"input number M:"; int M=2,N=6; cin>>M; pp=(type**) malloc (M*sizeof (type*)); if (pp==NULL) exit (1); cout<<"input number N:"; cin>>N;
20
int j ; for (j=0; j<M; j++) { pp[ j ]=(type*) malloc (N*sizeof (type)); if (pp[ j ]==NULL) exit(1); } int k ; for (k=0; k<M; k++) for ( j=0; j<N; j++) pp[ k ][ j ]=k*N+j+1; { cout<<endl; for (j=0; j<N; j++) cout<<" pp["<<k<<"]["<<j<<"]="<<pp [ k ][ j ]
21
if (pp[ j ]!=NULL) free (pp[ j ]); if (pp!=NULL) free (pp); }
for (j=0; j<M; j++) if (pp[ j ]!=NULL) free (pp[ j ]); if (pp!=NULL) free (pp); } //运行程序输出结果为 input number M:2 input number N:6 pp[0][0]=1 pp[0][1]=2 pp[0][2]=3 pp[0][3]=4 pp[0][4]=5 pp[0][5]=6 pp[1][0]=7 pp[1][1]=8 pp[1][2]=9 pp[1][3]=10 pp[1][4]=11 pp[1][5]=12
22
十二、指针的类型转换和匹配关系 1.指针类型转换 转换为一级指针和指向二维数组指针的强制类型转换的 语法格式为:
(type*)(p) (类型名*)指针表达式 (T(*)[c])p (类型(*)[c]) (指针表达式) 其中type,T可以是内置数据类型,可以是结构名,类类型 名,联合名,c是静定的整数。 指针强制类型转换的作用是将指针表达式的类型转换 为左边圆括号界定的指针类型。
23
指针的类型转换是复杂的。概括地说遵循下面的规则:
a. 指针的类型转换就是把源指针的值复制给目标指 针,即表达式(type*)(p)或(T(*)[c])p 与p具有相同的地址值, 不同的类型属性。 b.目标指针维持自身的性质。例如:如果p是T*型的一 级指针,则*p是T型的间接变量,则*((type*)(p))是type型的 左值。p的步长增量为sizeof(T),(type*)p 的步长增量为 sizeof (type);而*(T(*)[c])p是T*型的右值,(T(*)[c])p的步 长增量为c*sizeof(T)。 c.整型数据和浮点数据内存位的解释不同,两种指针之 间不宜类型转换。
24
d.指针的类型转换时注意内存的空间映像,保证目标
指针在合适的存储空间移动。转换的结果一般作为右值,除 非将T*型的左值指针p转换为自身类型,如[(T*)p+=n;]。 [例] char* p=(char*)a 表达式将long*型地址映射给 char*型指针 #include <stdio.h> void main() { long a[2]= {0x ,0x }; char* p=(char*)a; for ( int k=0; k<8; k++,p++) printf ("%c-%x ",*p,*p); } //输出:d-64 c-63 b-62 a-61 h-68 g-67 f-66 e-65
25
根据多维数组在内存空间连续存放的性质,可将多维数
组名映射到一级指针。 [例]一级指针遍历三维数组 #include <stdio.h> void main() { const int L=2,M=3,N=2; int s [L][M][N]={1,2,3,4,5,6,7,8,9,10,11,12}; int b [L*M*N]; int *p=(int*)s; int *q=b; int k ; for (k=0; k<L*M*N;k++) *q++=*p++; q-=L*M*N; for (k=0;k<L*M*N; k++,q++) printf ("%d,%d*",*q, b [k]); } //输出结果: 1,1*2,2*3,3*4,4*5,5*6,6*7,7*8,8*9,9*10,10*11,11*12,12*
26
[例]一维内存空间张成多维数组 #include <stdio.h> void main() { const int L=2,M=3,N=2; int i=0,j=0,k=0; int b [L*M*N]= {1,2,3,4,5,6,7,8,9,10,11,12}; int (*s) [M][N]= (int (*) [M][N])b; for (i=0; i<L; i++) for(j=0; j<M; j++) for (k=0; k<N; k++) printf ("%d ",s [i][j][k]); int (*d)[L*N] = (int (*)[4])b; for (j=0; j<M; j++) for (k=0; k<L*N; k++) printf ("%d ",d[j][k]); } //输出结果:
27
一维数组与多维数组共同点是相邻元素之间的地址根据
线性规律增长。 因此可以将一维数组空间委托数组指针接管,这一性质 在new int[N][M]运算符或malloc(N)等的函数调用中隐含地 得到利用。 对于一片连续的内存区域,可以通过一级指针索引,也 可以通过指向多维数组的指针访问。 即可以将一维数组强行张成多维数组,也可以将多维数 组映射为一维数组。
28
2. 指针的类型匹配关系 对于数组定义{int d[r][c]; },二维数组名d代表二维数
组首元素的地址,其类型属性为int (*) [c ],该地址可以初 始化一个二级指针,但须进行强制类型转换。如: int **pp=(int**) d; 强制映射之后pp[i]的值是不确定的。原因在于: d[i]是int*型的右值地址,d+i非常特殊地等于d[i]的值,为 (char*)d+i*c*sizeof(int);pp[i]是int*型的左值指针,pp+I 不等于pp[i]的值。 pp+i的值自动计算为(char*)pp+i*sizeof(int*),而指针 pp[i]的值通过赋值得到或关联一个指针数组名间接获得。因 此不将其它类型指针转换为二级指针。
29
正确的操作次序二级指针指向一级指针,一级指针指向
变量。 int*型的一级指针p访问int的数组,int**型的二级指针 pp访问int*的指针数组等。例如: 一维数组a: int a[n]; 指针数组pa: int * pa[r]; 二维数组d: int d[r][c]; 一级指针p: int *p= a; 二级指针pp: int **pp= pa; 数组指针q: int (* q)[c]=d; 只要被关联数组元素事先适当赋值,在k不越界的前提 下pp[k]或p[k]的操作是有根据的。指针数组pa和数组指针q 的初始化是不同的,指针数组pa须要对每个元素赋值,而数 组指针q只需赋值一次。pa[k]是左值,而q[k]是右值。
30
[例]指针的匹配和转换 #include<stdio.h> void main(void) { int i, b[3][2]= {1,2,3,4,5,6}; int (*q)[3]= (int(*)[3])b; for( i=0; i<2; i++) printf ("[%d,%d,%d]\t", q[i][0] , q[i][1], q[i][2]); int* pa[ ]= {b[2],b[1],b[0]}; int** pp=pa; for (i=0; i<3; i++) printf ("[%d,%d]\t",pp[i][0] ,pp[i][1]); } //输出 [1,2,3] [4,5,6] [5,6] [3,4] [1,2]
31
十三、下标表达式与访问指针寻址计算 下标表达式的一般格式为: ep[en] 指针表达式[整型表达式]
下标表达式ep[en] 等价于(*(ep+en)),反过来(*(ep+en)) 等价于ep[en]。 一般地ep为指针表达式,en为整型表达式,ep可以是 另一个下标表达式。在多级下标表达式嵌套中必须有且仅允 许其中一个表达式是用于定位地址的指针表达式。 &ep[en]等价于&(*(ep+en))等价于ep+en,而ep+en 的内存地址值由关系式给定: _ep+en*step
32
其中_ep 是ep的地址值,step是指针ep的步长增量,
如果ep是double*型的指针,则 step=sizeof(double)=8 如果ep是char**型的指针表达式,则 step=sizeof(char*) 如果ep是long(*)[5] 型的指针,则 step=sizeof(long[5])=4*5=20 如果ep是double(*)[5][2] 型的指针,则 step=sizeof(double[5][2])=8*5*2=80 依此类推。
33
下标表达式ep[en]具有优美的可读性,运行效率当en不
式代替访问指针形式。 实际上在专业的程序设计中几乎看不到访问指针形式 (*(ep+en))的踪影,原因一是访问指针形式可读性欠佳类型 属性模糊,二是字符键入较难。 ep[0] 等价于 (*ep) 但表达式ep不等价于*ep。
34
对于数组定义{ type a[max1], d[max1][ max2],
s[max1][max2][ max3];}等,其中type表示各种数据类型。 令n=sizeof(type),一般地,下标表达式的地址映射按照下 面的规律进行: 对1维数组a[max1], 数组元素a[k]的地址值是: _a+k* n 对2维数组d[max1][ max2], 数组元素d[j][k]的地址是: _d+j* max2*n +k*n
35
对3维数组s[max1][max2][ max3], s[i][j][k]的地址是:
_s+(i* max2* max3+j* max3 +k)* n 对m维数组w[max1][max2]...[maxm], 数组元素 w[s1][s2]...[sm]的地址索引值为: _w+[(s1*max2*max3...maxm )+ (s2*max3...maxm) ... +sm]*n 其中_a,_d,_s,_w分别是a,d,s,w的地址值,具体地以字 节为单位,d[j]和d+j的地址值都是(char*)d+j*max2*n,s[i] 和s+i的地址值都是(char*)s+i* max2*max3*n,s[i][j] 的地 址值是: (char*)s+(i* max2 +j)*max3*n 依此类推,它们都与max1无关。
36
d[ j ],d+j,&d[ j ][ k ],s[ i ][ j ],s[ I ]和s+i等是根据上面
的规则生成的右值地址,编译器用于寻址计算,它们不是左 值,因而未分配内存空间。 同样的一个地址值可以具有不同的类型属性,可将右值 地址赋给同类型的左值指针,以优化相同的地址表达式的重 复的寻址计算。 设m,r,c是预先静定的正数,对于下面的二维数组d或指 向二维数组的指针d的定义: int d[r][c]; int (*d)[c];
37
则d+i是一个int(*)[c] 类型的地址,d[i]+j具有类型属性
int*,它们构成右值表达式。d[ i ][ j ]是int型的变量。d+i和 d[ i ]具有相同的地址值不同的类型属性。 二维数组名d具有int(*)[c]的地址属性,同时拥有数组 的大小信息,其类型属性抽象为int[r][c],这一性质主要用 在sizeof (d),可以确定数组占有的内存大小为 sizeof (int[r][c]),而左值指针名仅占有sizeof (int*)字节的 内存。这是数组名和同类型指针名的差异所在。
38
对于三维数组s或指向三维数组的指针s的定义:
int s[m][r][c]; int (*s)[r][c]; s+i具有类型属性 int (*)[r][c], s[i]+j具有类型属性 int (*)[c], s[i][j]+k具有类型属性int*; 它们构成右值表达式。s[i][j][k]为int型的变量。其中 i,j,k是整型表达式。 s[i]地位相当于二维数组名d,d[i]和s[i][j]的地位相当于 一维数组名a。 右值数组名s可以确定数组占有的内存大小为 sizeof (int[m][r][c]),而左值指针s占有sizeof (int*)字节的 内存。
39
具有n个下标的表达式指的是多维数组,一个多维数组
如果n大于1,n-1维数组存放的是右值地址,如果n是1 则产生一个变量或数组元素。 访问指针运算和下表运算作用于地址表达式,进行的是 降维处理。 p为int**型的地址则(*(p+i))或p[i]为int*型的左值指针, p 为int*型的地址则(*(p+i))或p[i]为int型的变量。 p为int(*)[c]型的地址则(*(p+i))或p[i]为int*型的右值地 址。 p为int(*)[r][c]型的地址,则(*(p+i))或p[i]为int(*)[c]型 的右值地址。依此类推。
40
取地址运算符& 的操作数v一般为左值,记为
址,其结果为一右值。 左值v为int型的变量,&v的结果为int*型的地 址。 左值v为int*类型的指针,&v的结果为int**类型 的地址。
41
二维数组d[i][j]的行地址d[i]是右值,d[i]之前可放置取
地址运算符构成&d [ i ],d[ i ] 等价于*(d+i), &d [ i ] 运算为 & *(d+i),最终为d+i。 对于数组int a[c];&a的结果是一个int(*)[c]地址。 对于指针int *a;&a的结果是一个int**的地址。 对于数组int d[r][c],&d的结果是一个int(*)[r][c]的地 址,对于数组指针int (*d)[c],&d的结果是一个int(**)[c]的地 址。 类似地可以推广到多维数组的情形。取地址运算进行升 维处理,在运算中把源操作数变为右值。
42
函数名可以跟在取地址运算符&之后表示取函数名代表
的代码段入口地址,函数名本身是右值表达式,无需取地址 运算符&可以直接得到函数的入口地址。 数组占有一片内存,一维数组和多维数组的元素是递增 有序的。指针仅在指向数组空间时才进行寻址遍历(即加减 或自增自减等)访问计算。寻址遍历时注意遵循下面几点: 1. double*型的一级指针在double型的数组空间上寻 址遍历; 2. char**型的二级指针在char*型的数组空间上寻址遍 历; 3. long (*)[c]型的指针匹配相应二维数组long d[ ][c] 的首行地址d+k 4. int (*)[r][c]型的指针匹配相应三维数组int s[][r][c] 的首页地址s+k
43
请打开“第7章.ppt”
Similar presentations