430 likes | 556 Views
第 6 章 指针与数组. 十一、程序动态存储结构 十二、指针的类型转换和匹配关系 十三、下标表达式与访问指针寻址计算. 十一、程序动态存储结构 系统将内存数据分为三个段即数据段、代码段和堆栈 段。 编译程序对源程序进行编译时,将函数中的执行语句部 分编译成指令代码放置在代码区,将静态变量、全局变量与 字符串常数存放在全局数据区,函数的形式参数放置在临时 开辟的称之为先进后出的堆栈区,堆栈区内尚包含函数定义 部分中内部引入的局部变量或局部数组。 另外系统提供一些函数进行内存的动态控制,这一片可
E N D
第6章 指针与数组 • 十一、程序动态存储结构 • 十二、指针的类型转换和匹配关系 • 十三、下标表达式与访问指针寻址计算
十一、程序动态存储结构 • 系统将内存数据分为三个段即数据段、代码段和堆栈 • 段。 • 编译程序对源程序进行编译时,将函数中的执行语句部 • 分编译成指令代码放置在代码区,将静态变量、全局变量与 • 字符串常数存放在全局数据区,函数的形式参数放置在临时 • 开辟的称之为先进后出的堆栈区,堆栈区内尚包含函数定义 • 部分中内部引入的局部变量或局部数组。 • 另外系统提供一些函数进行内存的动态控制,这一片可 • 以由用户控制的动态区域称为堆结构或堆区,这一可由用户 • 动态控制的区域实际上是隶属于数据段的。 • 剩下的存储单元为自由内存空间。
1. C++中new运算符和delete运算符 • C++提供的关键字new是专门用于建立动态内存分配 • 的。 • new运算符试图动态地根据其后的类型名分配堆内存空 • 间,也即在堆空间定义变量或数组。 • new运算符不能用于分配一个函数,但可以用于分配一 • 个函数指针。 • new运算符定义堆中变量或对象的格式为: • new type(initialList) new 类名(初始化表) • 表达式的结果是type*类型的地址。 • 下面的几个语句: • int x; int *p=&x; *p=5;
对变量x进行了间接赋值,上面的指针变量p也可以指对变量x进行了间接赋值,上面的指针变量p也可以指 • 向堆空间: • p=new int(5); p=new int; *p=5; • 值得注意变量x的地址在变量x的生存期是不变的, • new运算符分配的地址在其生存期也具有类似的性质,不同 • 的是该存储空间可以由用户通过delete运算符释放。 • new运算符构成的表达式建立或定义动态一维数组的语 • 法格式为: • new type [dynSize] new 类名[动态数组大小] • 表达式的结果是type*类型的地址。
系统在堆区中开辟了一块大小为dynSize *sizeof (type) • 的空间,表达式的结果就指向这块存储空间的开始位置,因 • 此这一地址具有数组名一样的性质。 • 当new运算符建立动态数组时,不同时指定数组元素的 • 初值。 • 这个语法格式适用于一维数组的动态确定,动态数组大 • 小dynSize初始化的位置是灵活的,可以在运行时实时地输 • 入,但必须在new函数的调用点之前确定。
当不能成功地分配所需要的内存时,new返回0。判断当不能成功地分配所需要的内存时,new返回0。判断 • 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];
new运算符表达式分配或定义动态二维数组的格式为: • new double [ dynSize ][ maxn ] • // new 类名[动态数组大小][ 固定数组大小] • 表达式的结果为double (*)[maxn]型的地址,因此可以用如 • 下语句进行初始化: • double (*p)[ maxn ]= new double[ dynSize ][ maxn ]; • 也可以先定义数组指针然后再调用new运算符: • double (*p)[ maxn ]; ;... ; • p = new double[ dynSize ][ maxn ];
类似地new运算符分配或定义动态三维维数组的格式类似地new运算符分配或定义动态三维维数组的格式 • 为: • new int [ dynSize ] [ maxm ][ maxn ] • // new 类名[动态维数][固定维数m][固定维数n] • 表达式的结果为int (*)[maxm][maxn]型的地址,因此可以 • 用如下语句进行初始化: • int (*p) [ maxm ][ maxn ]= • new int[ dynSize ] [ maxm ][ maxn ]; • 上面的数组维数只有dynSize是可以动态输入的正数表达 • 式。 • 其余的必须是编译期间就确定的正整型常数表达式。
delete运算符释放由new运算符创建的对象内存。 • delete运算符表达式的结果为void类型的函数返回,因 • 此仅单独调用。 • delete的操作数必须是由new运算符返回的地址。 • delete运算符表达式的语法格式为: • delete 指针表达式; delete pointer; • 或: delete [ ]指针表达式; delete [ ] pointer;
delete运算符删除后的堆内存空间是未定义的,在删 • 除堆空间后间接访问这个指针则会产生意料不到的结果。 • 其中delete pointer形式的运算符与new type 匹配, • 即用于释放单个对象指针。 • 而delete [ ] pointer形式的运算符与new type[ size ] • 匹配,即用于释放动态数组。 • 对不是用new分配的对象指针使用delete会产生意料不 • 到的结果。
十一、程序动态存储结构 • 也可以当作多维数组使用。 • 这个void*型的首地址须强制转换为确定类型 • 的指针,也就是明确内存空间的具体访问规则。 • 如果系统没有足够的多余内存,malloc函数返 • 回NULL即0。
例如: • 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函数来清除。
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函数则要求提供类型转 • 换,以便将泛泛的一块内存空间用于具体类型的数据运算。
[例]动态申请一个一维数组 • #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遍历访问堆空间
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运算符 • 收回相应的内存空间。
[例]申请一个二维数组,第一个下标是可变的 • #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));
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); • }
定义语句{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函数收回相应的内存空间二维动态数组由一维动态指针 • 数组构成,该指针数组的每一个元素分别指向一维动态数 • 组。 • 二维动态可调数组是高频采用的编程技术。
下面的程序建立二级指针,该二级指针与二维动态数组下面的程序建立二级指针,该二级指针与二维动态数组 • 相联系,指向堆空间。 • [例]动态的二维数组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;
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; • for (k=0; k<M; k++) • { cout<<endl; • for (j=0; j<N; j++) • cout<<" pp["<<k<<"]["<<j<<"]="<<pp [ k ][ j ] • }
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
十二、指针的类型转换和匹配关系 • 1.指针类型转换 • 转换为一级指针和指向二维数组指针的强制类型转换的 • 语法格式为: • (type*)(p) (类型名*)指针表达式 • (T(*)[c])p (类型(*)[c]) (指针表达式) • 其中type,T可以是内置数据类型,可以是结构名,类类型 • 名,联合名,c是静定的整数。 • 指针强制类型转换的作用是将指针表达式的类型转换 • 为左边圆括号界定的指针类型。
指针的类型转换是复杂的。概括地说遵循下面的规则:指针的类型转换是复杂的。概括地说遵循下面的规则: • 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.整型数据和浮点数据内存位的解释不同,两种指针之 • 间不宜类型转换。
d.指针的类型转换时注意内存的空间映像,保证目标d.指针的类型转换时注意内存的空间映像,保证目标 • 指针在合适的存储空间移动。转换的结果一般作为右值,除 • 非将T*型的左值指针p转换为自身类型,如[(T*)p+=n;]。 • [例] char* p=(char*)a 表达式将long*型地址映射给 • char*型指针 • #include <stdio.h> • void main() • { long a[2]= {0x61626364,0x65666768}; • 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
根据多维数组在内存空间连续存放的性质,可将多维数根据多维数组在内存空间连续存放的性质,可将多维数 • 组名映射到一级指针。 • [例]一级指针遍历三维数组 • #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*
[例]一维内存空间张成多维数组 • #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]); • } //输出结果: • 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12
一维数组与多维数组共同点是相邻元素之间的地址根据一维数组与多维数组共同点是相邻元素之间的地址根据 • 线性规律增长。 • 因此可以将一维数组空间委托数组指针接管,这一性质 • 在new int[N][M]运算符或malloc(N)等的函数调用中隐含地 • 得到利用。 • 对于一片连续的内存区域,可以通过一级指针索引,也 • 可以通过指向多维数组的指针访问。 • 即可以将一维数组强行张成多维数组,也可以将多维数 • 组映射为一维数组。
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]的值通过赋值得到或关联一个指针数组名间接获得。因 • 此不将其它类型指针转换为二级指针。
正确的操作次序二级指针指向一级指针,一级指针指向正确的操作次序二级指针指向一级指针,一级指针指向 • 变量。 • 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]是右值。
[例]指针的匹配和转换 • #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]
十三、下标表达式与访问指针寻址计算 • 下标表达式的一般格式为: • ep[en] 指针表达式[整型表达式] • 下标表达式ep[en] 等价于(*(ep+en)),反过来(*(ep+en)) • 等价于ep[en]。 • 一般地ep为指针表达式,en为整型表达式,ep可以是 • 另一个下标表达式。在多级下标表达式嵌套中必须有且仅允 • 许其中一个表达式是用于定位地址的指针表达式。 • &ep[en]等价于&(*(ep+en))等价于ep+en,而ep+en • 的内存地址值由关系式给定: • _ep+en*step
其中_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 • 依此类推。
下标表达式ep[en]具有优美的可读性,运行效率当en不下标表达式ep[en]具有优美的可读性,运行效率当en不 • 等于0时等价于访问指针形式(*(ep+en)),优先采用下标表达 • 式代替访问指针形式。 • 实际上在专业的程序设计中几乎看不到访问指针形式 • (*(ep+en))的踪影,原因一是访问指针形式可读性欠佳类型 • 属性模糊,二是字符键入较难。 • ep[0] 等价于 (*ep) 但表达式ep不等价于*ep。
对于数组定义{ 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
对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无关。
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];
则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*)字节的 • 内存。这是数组名和同类型指针名的差异所在。
对于三维数组s或指向三维数组的指针s的定义:对于三维数组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*)字节的 • 内存。
具有n个下标的表达式指的是多维数组,一个多维数组具有n个下标的表达式指的是多维数组,一个多维数组 • 是一个其元素是数组的数组。访问指针运算符作用于一个n • 维数组类型上产生一个n-1维数组。 • 如果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]型 • 的右值地址。依此类推。
取地址运算符& 的操作数v一般为左值,记为 • &v。 • 表达式 &v的意义为取左值变量对应的存储地 • 址,其结果为一右值。 • 左值v为int型的变量,&v的结果为int*型的地 • 址。 • 左值v为int*类型的指针,&v的结果为int**类型 • 的地址。
二维数组d[i][j]的行地址d[i]是右值,d[i]之前可放置取二维数组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]的地 • 址。 • 类似地可以推广到多维数组的情形。取地址运算进行升 • 维处理,在运算中把源操作数变为右值。
函数名可以跟在取地址运算符&之后表示取函数名代表函数名可以跟在取地址运算符&之后表示取函数名代表 • 的代码段入口地址,函数名本身是右值表达式,无需取地址 • 运算符&可以直接得到函数的入口地址。 • 数组占有一片内存,一维数组和多维数组的元素是递增 • 有序的。指针仅在指向数组空间时才进行寻址遍历(即加减 • 或自增自减等)访问计算。寻址遍历时注意遵循下面几点: • 1. double*型的一级指针在double型的数组空间上寻 • 址遍历; • 2. char**型的二级指针在char*型的数组空间上寻址遍 • 历; • 3. long (*)[c]型的指针匹配相应二维数组long d[ ][c] • 的首行地址d+k • 4. int (*)[r][c]型的指针匹配相应三维数组int s[][r][c] • 的首页地址s+k