1
十一、程序动态存储结构十二、指针的类型转换和匹配关系十三、下标表达式与访问指针寻址计算
2
十一、程序动态存储结构系统将内存数据分为三个段即数据段、代码段和堆栈段。
编译程序对源程序进行编译时,将函数中的执行语句部分编译成指令代码放置在代码区,将静态变量、全局变量与字符串常数存放在全局数据区,函数的形式参数放置在临时开辟的称之为先进后出的堆栈区,堆栈区内尚包含函数定义部分中内部引入的局部变量或局部数组。
另外系统提供一些函数进行内存的动态控制,这一片可以由用户控制的动态区域称为堆结构或堆区,这一可由用户动态控制的区域实际上是隶属于数据段的。
剩下的存储单元为自由内存空间。
3
1,C++中 new运算符和 delete运算符
C++提供的关键字 new是专门用于建立动态内存分配的。
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。判断
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;
for (k=0; k<M; k++)
{ cout<<endl;
for (j=0; j<N; j++)
cout<<" pp["<<k<<"]["<<j<<"]="<<pp [ k ][ j ]
}
21
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]= {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
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]);
} //输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12
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不等于 0时等价于访问指针形式 (*(ep+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
维数组类型上产生一个 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]型的右值地址。依此类推。
40
取地址运算符 & 的操作数 v一般为左值,记为
&v。
表达式 &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
十一、程序动态存储结构十二、指针的类型转换和匹配关系十三、下标表达式与访问指针寻址计算
2
十一、程序动态存储结构系统将内存数据分为三个段即数据段、代码段和堆栈段。
编译程序对源程序进行编译时,将函数中的执行语句部分编译成指令代码放置在代码区,将静态变量、全局变量与字符串常数存放在全局数据区,函数的形式参数放置在临时开辟的称之为先进后出的堆栈区,堆栈区内尚包含函数定义部分中内部引入的局部变量或局部数组。
另外系统提供一些函数进行内存的动态控制,这一片可以由用户控制的动态区域称为堆结构或堆区,这一可由用户动态控制的区域实际上是隶属于数据段的。
剩下的存储单元为自由内存空间。
3
1,C++中 new运算符和 delete运算符
C++提供的关键字 new是专门用于建立动态内存分配的。
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。判断
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;
for (k=0; k<M; k++)
{ cout<<endl;
for (j=0; j<N; j++)
cout<<" pp["<<k<<"]["<<j<<"]="<<pp [ k ][ j ]
}
21
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]= {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
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]);
} //输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12
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不等于 0时等价于访问指针形式 (*(ep+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
维数组类型上产生一个 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]型的右值地址。依此类推。
40
取地址运算符 & 的操作数 v一般为左值,记为
&v。
表达式 &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