380 likes | 576 Views
第 6 章 指针与数组. 七、指向数组的指针 八、二级指针 九、指针数组 十、 void 关键字与 void * 型的指针. 七、指向数组的指针 1. 指向数组的指针的定义和性质 对于第二个维数相同的二维数组如 {int w[3][5],e[6][5];}, w,e 共性地具有类型属性 int(*)[5] ,第一个维数 3,6 协同确定 相应数组的总的内存空间,对于地址的映射计算并不起作 用。通过引进指向列数为 5 的二维数组的指针 x : int (*x)[5]; x 可以分别指向 w,e 。 例如: x=w; x=e+2;
E N D
第6章 指针与数组 • 七、指向数组的指针 • 八、二级指针 • 九、指针数组 • 十、void 关键字与void *型的指针
七、指向数组的指针 • 1.指向数组的指针的定义和性质 • 对于第二个维数相同的二维数组如{int w[3][5],e[6][5];}, • w,e共性地具有类型属性int(*)[5],第一个维数3,6协同确定 • 相应数组的总的内存空间,对于地址的映射计算并不起作 • 用。通过引进指向列数为5的二维数组的指针x: • int (*x)[5]; • x可以分别指向w,e。 • 例如:x=w; x=e+2; • 指向数组的指针简称为数组指针,一般地指向二维数组 • type d[r][c]的指针定义格式为: • type (*q) [c]; • 类型 (*数组指针名) [整型常数];
如上定义的指针q为type(*)[c] 类型的指针,是指向列 • 数为c的二维数组的指针。 • 数组指针q是一个指针,因此仅占一个指针对应的存储 • 空间,其中c是静态的整型常数,用于界定数组指针的步长 • 增量m = sizeof (type)*c。 • 一经定义数组指针q,则表达式q[ i ]等价于(*(q+i))为 • type*型右值,这个右值是自动计算出来的,不占数据区内 • 存。 • 表达式q[ i ][ j ]等价于(*(*(q+i)+j))为type型左值,左值 • 的内存应预先由数组定义语句分配。编译器不检查q[ i ][ j ] • 是否初始化,也不管是否越界。
如果q=d,则可以等价地通过指针名q和二维数组名d来如果q=d,则可以等价地通过指针名q和二维数组名d来 • 索引二维数组。此时关系表达式q[ i ]==d [ i ]为真。 • 在q=d期间q [ i ][ j ]等价于d [ i ][ j ],即对q [ i ][ j ]的操 • 作就是对d [ i ][ j ]的等价操作,如果q是形参,则形参在函 • 数中通过q [ i ][ j ]或**q等方式直接操作相应的实参数组。 • 令n = sizeof (type), m=n*c,q=d的地址为xxxx,则 • q+j的地址值为yyyy= xxxx+j*m。 • type(*)[c]型指针的位移步长增量为m。q+j的地址属性 • 为type(*)[c]。
d [ j ]和q [ j ]构成type*型的右值,其步长增量为n。 • q[ j ]的地址为yyyy,则 q[ j ]+k的地址值为yyyy+kn, • 即指向第j+1行的第k+1个元素,也就是指向 q[ j ][ k ],即关 • 系式q[ j ]+k==&q[ j ][ k ]为真。 • q[ j ]或(*(q+j))与q+j具有相同的地址值但地址类型属性 • 不同。 • 这里访问指针运算(*(q+j))或q[j]表示降维处理即将 • type(*)[c]型的地址降为type*型的右值地址和自动的寻址计 • 算而不表示直接索引内存数据,只有左值才访问操作内存数 • 据。 • d+j表示指向第j+1行,d[ j ]是第j+1行的首地址,两者 • 都指向同一内存位置但地址属性不同。
二维数组行地址 d [ 0 ] d [ 1 ] d [ 2 ] : d [ j ] : d [ r-1 ] 行地址的值 x x x x x x x x+1m x x x x+2m : x x x x+jm : x x x x+r m-m q+= j-1; q=d+1; 数组指针指向二维数组 每行的首地址,赋值一次 指针用加运算 数组指针向后移动j-1个位置 m=sizeof (type[c])=nc 二维数组type d[r][c]某行的首地址与数组指针的关系
赋值语句{ q=d; q+= j;}导致q的值为xxxx+jm,此时表 • 达式q[0][k]等价于 d[j][k],**q= q[0][0]=d[j][0]。 • d[0][0]总是索引数组d的第1个元素,q[0][0]未必。q++ • 向前移动一个位移步长即q从当前位置向后移动m个字节, • 也就是值向下一行的内存地址。 • 指针只能用相同属性的地址赋值,当指针不平级时,需 • 要进行指针类型转换。语句: • double d[3][5]; double (*q)[5]=d+1; • double *p=q[0]; • 定义数组指针q,初数化指向二维数组d的第2行,*q和 • d[1]为double *型的右值地址。d, q具有double(*) [5] 类型 • 属性,d=q是错误的,d为右值,q为左值。 • q- = 1; • //向前移动1个double[5]类型的步长,即前移5*8*1=40个字节.
语句{double(*r)[2];}定义double(*)[2]型的数组指针 • r,r与q类型属性不同,它们之间不能相互赋值。r=d,q=r是 • 错误的,等号两边的类型属性不同。r[ j ][ i ]=d[ i ][ j ]是可 • 以的。r[ i ],q[ j ]都是double型的右值地址,r[ i ]=q[ j ]是错 • 误的,而p = r[ i ],p = q[ j ]是正确的。 • 对于最后两个维数相同的三维数组如: • {int s[2][4][3],h[5][4][3];}, s,h共性地具有类型属性 • int (*)[4][3],第一个维数2,5对于地址的映射不起作用。 • 引进指向三维数组的指针q: • int (*q)[4][3]; • q可以方便地操纵s,h。例如:q=s;q=h+1;
设m,r,c时静态的整型常数,一般地指向三维数组type • s[ m ][ r ][ c ]的指针定义格式为: • type (*q)[r] [c]; • 类型 (*数组指针名) [常数2][常数3]; • q的类型属性为type (*)[r][c] ,如果q=s,则可以等价地 • 通过指针q和数组名s来索引三维数组。此时关系表达式 • q[ i ][ j ][ k ]==s[ i ][ j ][ k ],q[ i ][ j ]==s[ i ][ j ] • q[ i ]==s[ i ] 为真。 • 下面的关系是等价的,访问指针形式明显缺乏可读性且 • 各级表达式的类型属性含糊,也不容易用键盘输入。因此程 • 序设计中不宜用右边的访问指针形式: • s[ i ][ j ][ k ] == (*(*(*( s+i )+j)+k))
[例]同一个地址对应不同类型的地址属性 • #include <stdio.h> • void main() • { int b[3][2][2]; • //int (*p)[2][2]=b;int (*q)[2]=b[0];int * r=b[0][0]; • printf("%p,%p,%p\t",b,b[0],b[0][0]); • } //输出:0066FDC8, 0066FDC8, 0066FDC8 • 说明: {int b[3][2][2];}中的b具有地址属性int (*)[2][2], • b[0]具有地址属性int (*) [2],b[0][0]具有地址属性int *,它 • 们都具有相同的地址值。b+i,b[i]和b[i][0]也具有相同的值。 • 地址值相同地址类型属性不同的现象表示同一片内存 • 可用不同属性的指针寻址访问。
2.数组指针形参和二维数组形参 • c是静态的整型常数。下面是两个本质一致外在格式不 • 同的函数原型: • void f (int (*q)[c],...); void f (int q[ ][c],...); • 形如“ int ( *q )[ c ]”的形参为数组指针形参, 形如 • " int q[ ][c]"的形参为二维数组形参。 • 两者都视为int (*)[c]型的指向二维数组的指针,二维数 • 组共性地具有固定列数c。 • 函数定义 void f (int (*q) [ c ],...) {;...;}可等价地改为: • void f (int q[ ][ c ],...){;...;} 。
[例]指向二维数组的指针与二维数组 • # include<iostream.h> • void f (float (*q)[4],int m) • { for (int k=0;k<m;k++) • { float* p=q[ k ]; • for( int j=0;j<4;j++) • cout<< p[ j ]<<"," ; • } • } • void main (void) • { float d[ ][4]={0,1,2,3,4,5,6,7}; • int const M=sizeof (d)/sizeof (*d); • f (d,M); • f (d+1,M-1); • } //输出:0,1,2,3,4,5,6,7, 4,5,6,7,
[例]数组指针形参或二维数组形参实现求数组空间[例]数组指针形参或二维数组形参实现求数组空间 • 的和 • #include<stdio.h> • double sum (double q[ ][3],int n); • void main (void) • { double b[2][3]={1,2,3,4,5,6}; • printf ("%2.0f,%2.0f\n",sum (b,2),sum (b+1,1)); • double a[ ]={1,2,3,4,5,6,7}; • typedef double (*QA)[3]; • printf("%2.0f,%2.0f\n",sum((QA)a,2),sum((QA)(a+4),1)); • }
double sum (double (*q)[3],int n) • { double s=0 ; int i,j; double *p; • for ( i=0; i<n; i++, q++) • for (j=0, p=*q; j< 3; j++, p++) • s+=*p; • return s; • } • double sum1(double (*q)[3],int n) • { double s=0 ; • for (int i=0;i<n;i++) for (int j=0; j<3;j++) s+=q[ i ][ j ]; • return s; • }
typedef简化数组定义 • 数组指针的定义语句[int (*p)[N];]定义了一个指针变量 • p,定义语句[int d[M][N];]定义了一个二维数组d,可以通过 • typedef类型声明语句来建立一个数组类的别名,其格式 • 为: • typedef int (*PN)[N]; • // PN是int (*)[N]类型的别名 • typedef int AMN[M][N]; • // AMN是int[M][N]类型的别名 • typedef int AN[N]; • // AN是int[N]类型的别名,N,M是静定的整数。
去掉上面的typedef就得到数组定义语句。可用类型别去掉上面的typedef就得到数组定义语句。可用类型别 • 名定义数组或指针。 • PN p,q,r; • //定义指针变量p,q,r都拥有数据类型int (*)[N] • AN a,b; • //定义数组a,b其类型属性为int[N],等价于int a[N], b[N]; • AMN c,d; • //相当于定义二维数组int c[M][N], d[M][N];
# include <iostream.h> • const int L=2,M=2,N=2; • typedef int ALMN[ L][ M ][ N ]; • typedef int (*PMN)[M][N]; typedef int (*PN)[N]; • void main (void) • { int j, k; • ALMN v= {1, 2,3, 4,5,6,7,8}; • PMN d=v; PN a= v[1]; • for (j=0;j<M;j++) for (k=0;k<N; k++) • cout<<d[1][j][k]<<"," ; • for (j=0;j<M;j++) for(k = 0; k <N; k++) • cout<<a[j][k]<<";" ; • } ///输出结果: 5 ,6 ,7 ,8, 5 ;6 ;7 ;8;
八、二级指针 • 二级指针的定义格式为: • type** pp1 , **pp2 ,…,**ppn; • 类型** 指针名1,**指针名2,…,**指针名n; • 二级指针初始化定义语句的格式为: • type** pp = ptrExpression; • 类型** 指针名=指针表达式; • 指针表达式是与所定义的指针同类型的地址值。如上定 • 义的指针pp,pp1, pp2, ppn等抽象地称为type**类型的指针 • 或二级指针。二级指针的地址对应一个三级指针,基本上不 • 使用三级指针。一级指针的地址是右值,二级指针是一种存 • 放一级指针的地址数据的特殊类型变量,简单地说二级指针 • 是一级指针的地址的变量。
二级指针具有下面的特性: • 1.二级指针的类型属性,二级指针的类型是其所指向地 • 址变量的类型,二级指针的类型属性限定二级指针的增减以 • 指针类型步长sizeof(type*)=4或2为单位。二级指针可在一 • 级指针构成的数组空间中移动。pp++表示向后移动4或2个 • 字节。 • 2.一经定义二级指针pp,则表达式pp[ I ]等价于(*(pp+i)) • 为type*型左值;表达式pp[ I ][ j ]等价于(*(*(pp+i)+j))为 • type型左值,编译器不负责它们的内存分配,不检查它们是 • 否初始化,也不管是否越界。
&c &p 2.0 p pp c • pp[ j ]或(*(pp+j))与pp+j的地址值不同地址属性也不同. • 这里访问指针运算(*(pp+j))或pp[ j ]既表示降维处理也表示 • 直接索引pp+j指向的内存数据。pp+j的地址值自动根据关系 • 式(char*)pp+j*sizeof (type*)计算,而pp[ j ]的值由用户确定. • 当一级指针指向单一的变量或二级指针指向单一的一级 • 指针时,对于指针的加减运算都是越界行为,应予以避免。 double c,b; • double *p,**pp; • p=&b; pp= &p; *p=5.0; p=&c; • **pp=2.0; //等价于**pp*(*(&p))*(p)*(&c)c,c=2.0
九、指针数组 • 1.指针数组的定义和性质 • type*型的指针数组的定义格式为: • type *pa[N]; 类型 * 指针数组名[数组长度]; • 指针数组具有一维数组的一般性质,占有N个指针所需 • 的内存空间,而其特点如下: • a. pa为指针数组名,本身代表指针数组的首地址,此地 • 址是type**型的右值地址。 • sizeof (pa)=sizeof (type*[N])= N *sizeof (type*)。 • N= sizeof(pa)/ sizeof(pa[0])
b.指针数组的每一个元素pa[i]是type*型的左值,pa [i] • 等价于(*(pa+i));表达式pa[ I ][ j ]等价于(*(*(pa+i)+j))为 • type型左值,pa[ I ]的作用相当于一级指针p的作用,pa+I • 指向元素pa[ I ],pa[ I ]+j或*(pa+i)+j 指向变量pa[i][j].指针 • 数组的元素的初始化可由赋值语句进行,也可根据下面的格 • 式进行: • type *pa[ N ] ={initialList}; • 类型 *指针数组名[ ]={初始化地址列表}; • 初始化地址列表的每一个表达式应是type*型的地址。 • 对于type x,a[N],d[r][c];,如果指针数组元素pa[ i ]=a,则 • pa[ i ][ j ]索引一维数组元素a[ j ]。如果指针数组元素 • pa[ I ]=d[ k ],则pa[ I ][ j ]索引二维数组元素d[ k ][ j ]。如 • 果指针数组元素pa[ k ]=&x,则*pa[ k ]索引变量x。
例如: • double d[ 3 ][ 2 ]={1,2,{3,4},5,6}; • double* y[3]={d[2],d[1],d[0]}; • 相当于: y[0] =d[2]; y[1] = d[1]; y[2] = d[0]; • 此时有:y[0][0]=d[2][0]=5 ; y[1][0]=d[1][0]=3 ; • y[2][0]=d[0][0]=1 ; y[0][1]=d[2][1]=6 ; • y[1][1]=d[1][1]=4 ; y[2][1]=d[0][1]=2 ; • 指针数组的相邻元素指向的数据不必具有相邻的关系; • 指针数组的元素可指向生存期稳定的全局变量、静态变量也 • 可指向生存期瞬态变化的局部变量,尚可指向生存期由程序 • 员控制的堆空间。 • 但应保证指针指向的数据生存期对于指针具有可操作 • 性,或者说指针操作的内存数据的生存期在指针访问时是有 • 效的,是存在的。
指针数组元素 pa[ 0 ] pa[ 1 ] pa[ 2 ] : pa[ j ] : pa[N -1] 指针的初值 t1 t2 t3 : t5 : t7 t1的元素 t1[ 0 ] t[ 1 ] t1[ 2 ] : t1[ 4 ] : t1[ 6 ] pp=pa; pp+=j; • 考虑:typedef char type ; • type t1[7],t2[6],t3[5],t4[4],t5[3],t6[2], t7[1]; • type *pa[ ]={t1,t2,t3,t4,t5,t6,t7}; • type **pp=pa; 图 指针数组的值与二级指针
[例]指针数组的元素指向维数大小不同的一维数组[例]指针数组的元素指向维数大小不同的一维数组 • # include<stdio.h> • void show (long * a,int n) • { for (int k=0;k<n;k++) printf ("%d ",a[ k ]); • printf("-"); • } • long t1 [ 7 ]={1,2,3,4,5,6,7}; • void main() • { long t4[ 4 ]={1,2,3,4}; long *pa[ 3 ]={t4,t1}; • long ** pp=pa+1; • show (pa[0],4); show (pp[0-1],4); • show (pa[1],7); show (pp[1-1],7); • }//输出结果: 1 2 3 4 -1 2 3 4 -1 2 3 4 5 6 7 -1 2 3 4 5 6 7 -
2.二级指针形参和指针数组形参: • 如下是两个本质一致外在格式不同的函数原型: • int f (int **pp,...); int f (int *pp[],...); • 形如"int**pp"的形参为二级指针形参, 形如"int*pp[]“ • 的形参为指针数组形参,两者都视为 int**型的表达式。指 • 针数组形参要求实参指针数组的内存分配,每一元素的初始 • 化和相应元素指向的数据的内存分配。二级指针形参表明定 • 位内存地址的基准特性。 • 函数定义int f (int *pp[ ],...){;...;}等价于函数定义 • int f (int **pp,...) {;函数体代码不变;} • 引入一级指针用于访问变量或数组元素,引入指针数组 • 用于通过访问指针元素,最终访问指针元素指向的变量。
二级指针pp=pa,pp[ k ]等价于pa[ k ],对pp[ k ]的操作 • 就是对一级指针pa[k] 操作;如果pp是形参,则形参在函数中 • 通过pp [ i ]或*pp等方式直接操作相应的实参指针数组空间, • 通过pp[ i ][ j ]或**pp等方式操作相应的实参变量数组空间 . • [例]二级指针显示指针数组的元素指向的变量的值 • #include <stdio.h> • void f (int**pp,int n) • { for (int i=0;i<n;i++) printf ("%d,",**pp++); } • void main() • { int x=1,y[ ]={2,3},u[ ][1]={4,5}; • int * px [ ]={&x, &y[0], y+1, u[0], &u[1][0]}; • f (px,5); f (px+4,5-4); • for (int** pp=px, i=0; i<5; i++, pp++) • printf ("%d, ", **pp); }
[例]二级指针或指针数组作为形参实现求数组的和[例]二级指针或指针数组作为形参实现求数组的和 • #include<stdio.h> • double sum (double** pp, int n, int m=3); • double a[ ] = {2,3,4}; • void main (void) • { double b[2][3] = {1,2,3,4,5,0}; • double* x[ ]= {b[1],b[0],a}; • const char*fmt = "%2.0f,%2.0f,%2.0f "; • printf (fmt, sum (x,2), sum (x+1,1),sum (x+2,1)); • }
double sum (double* pp[ ], int n, int m) • { double s=0 ; int i, j; double *p; • for ( i=0; i<n; i++, pp++) • for (j=0, p=*pp; j<m; j++, p++) • s+=*p; return s; } • double sum1 (double* pp[ ], int n, int m) • { double s=0 ; • for (int i=0; i<n; i++) for (int j=0; j<m; j++) s+=pp [ i ][ j ]; • return s; }
对指针表达式或下标表达式降维过程中,二级指针和指对指针表达式或下标表达式降维过程中,二级指针和指 • 向二维数组的指针降维的结果恰好殊途同归: • double **型指针pp的下标表达式pp [ j ]+k是double * • 的地址,pp [ j ][ k ]是double的变量; • double(*)[3]型的数组指针q构成的下标表达式q[ j ]+k • 也是double *的地址,q[ j ][ k ]也是double的变量。 • 数组指针指向的内存具有连续递增的特点,q[ j ]是右 • 值;q[ j ]和q[ j+1 ]具有相近的性质,或者均指向全局数据 • 区,或者都指向局部内存区。
而指针pp[ j ]可作为左值,pp[ j ]和pp[ j+1]未必具有相 • 临的性质;pp[ j ]指向局部空间的时候,pp[j+1]可以指向全 • 局数组。 • q[ j ]是自动计算出来的值,其等于q+j的值;而pp[ j ]最 • 终通过赋值得到。 • 形如double d [ N ][ M ]的二维数组,如果需要匹配 • double * pp [ N ]的指针数组形参,调用之前可通过步骤进 • 行初始赋值: • for (int k=0; k<N; k++) pp[ k ] = d[ k ];
对于[double a[ M ]; double * p=a; ]容易看出表达式 • p[ n ]和p[0]+n是double型的变量,p [ n ]表示数组的元素 • a [n],p[0]+n表示a[0]加上n。 • 类似地,表达式pp[n]或*(pp+n)与表达式 pp[0]+n或 • *pp+n 是double*型的地址,但具有不同的含义: • *(pp+n)或pp[ n ]表示指针数组中的第n+1个元素为左 • 值,*pp+n 或pp[ 0 ]+n表示在指针数组当前元素(第一个元 • 素) 位置上后移n个类型步长,pp[ 0 ]+n为右值指向 • pp[ 0 ][ n ]。
十、void 关键字与void *型的指针 • void关键字只有三种用法: • 1.声明一个无返回值的函数,例如: • [void function(int j);],void 型函数调用一般只单独调用; • 2.声明一个不需要任何入口参数的函数,例如: • [int funct (void);]说明funct是一个不需要任何入口参数的 • 函数,其返回值为整型,funct()可作为整型右值参入各种运算; • 3.定义一个void*型的指针(用type表示区别于void的特 • 定类型,如算术类型等); • void*型的指针是一种宽泛类型的指针,void*型的指针 • 需要显式地转换为其它特定类型的指针,其它特定类型的指 • 针可隐含地转换为void*型的指针。
例如: • void* pv; type* pt, a, b; pt=(type*)pv; pv=pt; • void*型的指针本身绝不直接用于访问数据,即 • *pv= (void)a 以及b= (type)(*pv)形式的表达式是无意义 • 的。 • void*型的指针涉及到的内在含义 : • void*型的指针与void类型的函数,共借用同样一个 • 关键字,但其内在含义实质上无任何联系。特定类型的指针 • 可以隐含地支付给void*型的指针,这里所谓的隐含实际上 • 是编译器默许的指针转换,本质上带有强制性质。在采用 • void*型指针的值访问内存空间前必须显式地转换或回归到 • 确定类型的指针。
void*型的指针存放内存空间的宽泛的地址。将其它void*型的指针存放内存空间的宽泛的地址。将其它 • 类型的地址统一地存放在void*型指针中是旨在减少显式类 • 型转换引起的代码书写量。 • void*型的指针作为一种内存的定位基准,通用于操 • 作内存的数据。type*型指针的步长增量为sizeof (type)。 • 但void*型指针的类型步长增量是漂移的,没有void类 • 型的数据,sizeof(void)是不允许的,因此不对void*型指针 • 进行指针加减和访问运算,如pv++,*pv等是不允许的。这 • 种类型的宽泛性表明其数据操作的不完备性。 • 因此用void*型的指针编写入口形参时务必补充另一个 • 不可或缺的信息,即界定内存空间的精确大小。
供应商提供的void*型函数如memset ,memcpy等必然 • 要伴随一个size_t类型的参数来完备内存数据的操作, size_t • 是由类型声明语句[typedef unsigned int size_t;]引入的别 • 名,在函数内部另外具有确定类型的指针来进行具体的数据 • 运算,这种确定的指针类型一般隐含地是C语言中的char*。 • 系统的memset通常直接用汇编语言实现。memset函 • 数原型为: • void * memset (void *buf, int c, size_t n); • // 返回void* 类型指针的函数 • 函数原型中c指出填充内存缓冲区的值,n为填充的元 • 素数目,buf指向缓冲区的地址。函数返回指向buf的指针。
[例] void*指针的编程特点.(函数版本模拟系统的 • memset作用 ) • void* memset (void* pv, int c,size_t n) • { char * pt= (char *)pv ; • for(unsigned int k=0;k<n;k++) *pt++= (char)c; • return pv; • } • #include <iostream.h> • void main() • { char str[ ] = "123456789abcd"; • cout << str << " "; • char* result = (char*) memset (str, '8', 9); • cout << result << endl; • } //输出结果:123456789abcd 888888888abcd