指向多维数组的指针变量
1 多维数组的指针
多维数组可以看作是一维数组的延伸,多维数组的内存单元也是连续的内存单元。换句话说,C语言实际上是把多维数组当成一维数组来处理的。下面以二维数组为例说明这个概念。
比如,现在有一个int型的二维数组a[3][4],计算机认为这是一个一维的数组a[3],数组的三个元素分别是a[0],a[1]和a[2]。其中每个元素又是一个一维数组,例如a[0]又是一个包含a[0][0],a[0][1],a[0][2]和a[0][3]共4个元素的数组。如果我们要引用数组元素a[1][2],可以首先根据下标1找到a[1],然后在a[1]中找到第3个元素a[1][2]。假设数组a的起始地址为FF10,对应的内存情况如图:
DS:FF10 a[0][0] DS:FF12 a[0][1] DS:FF14 a[0][2] DS:FF16 a[0][3]
DS:FF18 a[1][0] DS:FF1A a[1][1] DS:FF1C a[1][2] DS:FF1E a[1][3]
DS:FF20 a[2][0] DS:FF22 a[2][1] DS:FF24 a[2][2] DS:FF26 a[2][3]
可以看到,二维数组a[3][4]的12个元素在内存中是顺序排列的,因此&a[0][0]是数组第一个元素的地址,为FF10。
a[0][0],a[0][1]这些数组元素都有具体的内存单元值。但是,a[0],a[1]和a[2]这三个数组元素不占用内存单元,它们只是代号(其实就是一个指针常量),是虚拟的东西。a[0]本身又是一个数组,包含a[0][0],a[0][1],a[0][2]和a[0][3],那么a[0]作为数组名称,按照C语言的语法,a[0]就是数组首地址,也就是数组第一个元素的地址,即a[0][0]的地址FF10。同理,a[1]是第一个元素a[1][0]的地址,即FF18;a[2]是第一个元素a[2][0]的地址,即FF20。
相对于a[0],a[1]和a[2]来说,a也是数组的名称,是数组的首地址,即FF10。a是指向数组首地址的指针,a+1代表什么?a是数组名称,a[0],a[1]和a[2]是元素,那么a+1就是&a[1](如果一个一维数组int b[3],那么b+1是不是&b[1]?),即第二行元素的首地址,指针值为FF18。同理,a+2就是&a[2],指针值为FF20。
也可以换个角度去理解。a是数组首地址,指向二维数组第一行的首地址;计算a+1时,指针a跳过的是整个a[0],a+1指向二维数组第二行的首地址。指针a在做加法时,跳过的是一行元素。
a[1]+1代表什么?指针a[1]是一维数组a[1]中第一个元素a[1][0]的地址FF18。对于一维数组a[1]来说,指针a[1]+1指向了a[1]中第二个元素a[1][1]的地址,即FF1A。这里指针加法的单位是一列,每次跳过a[1]中的一个数组元素。
*a代表什么?*a也就是*(a+0),数组第一个元素的值,即a[0]。前面讲过,a[0]是一个代号,它不是一个具体元素的值,而是内嵌的一维数组a[0]的名字,a[0]本身也是一个指针值。同理,*(a+1)就是a[1],*(a+2)就是a[2]。
如果要访问二维数组里第i行第j列的某个元素,可以直接引用a[i-1][j-1],也可以使用指针的方法。指向第i行第j列元素的指针为a[i]+j,或者*(a+i)+j,因此,间接用指针引用二维数组里第i行第j列的某个元素,可以表示为*(a[i]+j)或者*(*(a+i)+j)。特别需要指出的是,a[i]不是一个具体的数组元素,它是一个代号,实际上是一个指针值,代表i+1行里中第一个元素的首地址。
因此&a[i]不能直接理解为a[i]的物理地址,a[i]不是变量,不存在物理地址;&a[i]代表第i+1行的行指针值,和a+i等价。同样,*a[i]也不能理解为对一个数组元素进行指针运算符操作。a[i]是一个指针值,是i+1行中第一个元素的首地址,那么*a[i]就是i+1行里中第一个元素的值,即a[i][0]。
总之,二维数组a[i][j]的指针由于出现了虚拟的a[i]这个概念,所以对于初学者很不好理解,请读者仔细消化理解。下表是上述概念的综合:
main()
{
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12},i,j;
/*打印a*/
printf("\na=%X", a);
/*打印a+i, &a[i]*/
for(i=0;i<3;i++)
printf("\na+%d=%X, &a[%d]=%X", i,a+i,i,&a[i]);
/*打印a[i], *(a+i)*/
for(i=0;i<3;i++)
printf("\na[%d]=%X, *(a+%d)=%X", i,a[i],i,*(a+i));
/*打印a[i]+j, *(a+i)+j, &a[i][j]*/
for(i=0;i<3;i++)
for(j=0;j<4;j++)
printf("\na[%d]+%d=%X, *(a+%d)+%d=%X, &a[%d][%d]=%X",
i,j,a[i]+j, i,j,*(a+i)+j, i,j,&a[i][j]);
/*打印**(a+i), *a[i]*/
for(i=0;i<3;i++)
printf("\n**(a+%d)=%X, *a[%d]=%X", i, **(a+i),i, *a[i]);
/*打印*(a[i]+j), *(*(a+i)+j), a[i][j]*/
for(i=0;i<3;i++)
for(j=0;j<4;j++)
printf("\na[%d]+%d=%d, *(a+%d)+%d=%d, &a[%d][%d]=%d",
i,j,*(a[i]+j), i,j,*(*(a+i)+j), i,j,a[i][j]);
}
程序运行的结果为(打印的地址值是动态分配的,不同环境下可能不同;但数组的地址偏移量是相同的):
a=FFBE
a+0=FFBE, &a[0]=FFBE
a+1=FFC6, &a[1]=FFC6
a+2=FFCE, &a[2]=FFCE
a[0]=FFBE, *(a+0)=FFBE
a[1]=FFC6, *(a+1)=FFC6
a[2]=FFCE, *(a+2)=FFCE
a[0]+0=FFBE, *(a+0)+0=FFBE, &a[0][0]=FFBE
a[0]+1=FFC0, *(a+0)+1=FFC0, &a[0][1]=FFC0
a[0]+2=FFC2, *(a+0)+2=FFC2, &a[0][2]=FFC2
a[0]+3=FFC4, *(a+0)+3=FFC4, &a[0][3]=FFC4
a[1]+0=FFC6, *(a+1)+0=FFC6, &a[1][0]=FFC6
a[1]+1=FFC8, *(a+1)+1=FFC8, &a[1][1]=FFC8
a[1]+2=FFCA, *(a+1)+2=FFCA, &a[1][2]=FFCA
a[1]+3=FFCC, *(a+1)+3=FFCC, &a[1][3]=FFCC
a[2]+0=FFCE, *(a+2)+0=FFCE, &a[2][0]=FFCE
a[2]+1=FFD0, *(a+2)+1=FFD0, &a[2][1]=FFD0
a[2]+2=FFD2, *(a+2)+2=FFD2, &a[2][2]=FFD2
a[2]+3=FFD4, *(a+2)+3=FFD4, &a[2][3]=FFD4
**(a+0)=1, *a[0]=1
**(a+1)=5, *a[1]=5
**(a+2)=9, *a[2]=9
a[0]+0=1, *(a+0)+0=1, &a[0][0]=1
a[0]+1=2, *(a+0)+1=2, &a[0][1]=2
a[0]+2=3, *(a+0)+2=3, &a[0][2]=3
a[0]+3=4, *(a+0)+3=4, &a[0][3]=4
a[1]+0=5, *(a+1)+0=5, &a[1][0]=5
a[1]+1=6, *(a+1)+1=6, &a[1][1]=6
a[1]+2=7, *(a+1)+2=7, &a[1][2]=7
a[1]+3=8, *(a+1)+3=8, &a[1][3]=8
a[2]+0=9, *(a+2)+0=9, &a[2][0]=9
a[2]+1=10, *(a+2)+1=10, &a[2][1]=10
a[2]+2=11, *(a+2)+2=11, &a[2][2]=11
a[2]+3=12, *(a+2)+3=12, &a[2][3]=12
从输出的结果可以看到,&a[i]和a[i]的输出值都是一样的。第i+1行的首地址是FFBE,第i+1行、第1列元素的首地址也是FFBE,这并不矛盾,因为二者都是一个指针值。但是,二者的含义是截然不同的,指针加法的偏移量也是不同的:前者加1指针跳到下一行;后者加1指针跳到下一列。
2 指向多维数组的指针变量
可以定义指针变量,指向多维数组及其元素。下面仍以二维数组为例进行说明。
二维数组在内存中是连续的内存单元,我们可以定义一个指向内存单元起始地址的指针变量,然后依次拨动指针,这样就可以遍历二维数组的所有元素。例如:
main()
{
float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0},*p;
inti;
for(p=*a;p<*a+2*3;p++)
printf("\n%f ",*p);
}
结果输出:
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
在上述例子中,定义了一个指向float型变量的指针变量。语句p=*a将数组第1行,第1列元素的地址赋给了p,p指向了二维数组第一个元素a[0][0]的地址。根据p的定义,指针p的加法运算单位正好是二维数组一个元素的长度,因此语句p++使
得p每次指向了二维数组的下一个元素,*p对应该元素的值。
根据二维数组在内存中存放的规律,我们也可以用下面的程序找到二维数组元素的值:
main()
{
float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0},*p;
inti,j;
printf("Please input i =");
scanf("%d", &i);
printf("Please input j =");
scanf("%d", &j);
p=a[0];
printf("\na[%d][%d]=%f ",i,j,*(p+i*3+j));
}
输入下标i和j的值后,程序就会输出a[i][j]的值。这里我们利用了公式p+i*3+j计算出了a[i][j]的首地址。计算二维数组中任何一个元素地址的一般公式如下:
二维数组首地址+i*二维数组列数+j
请读者分析二维数组的内存分配情况,自行证明上述公式。
上述的指针变量指向的是数组具体的某个元素,因此指针加法的单位是数组元素的长度。我们也可以定义指向一维数组的指针变量,使它的加法单位是若干个数组元素。定义这种指针变量的格式为:
数据类型(*变量名称)[一维数组长度];
说明:
(1) 括号一定不能少,否则[]的运算级别高,变量名称和[]先结合,结果就变成了后续章节要讲的指针数组;
(2) 指针加法的内存偏移量单位为:数据类型的字节数*一维数组长度。
例如,下面的语句定义了一个指向long型一维、5个元素数组的指针变量p:
long (*p)[5];
指针变量p的特点在于,加法的单位是4*5个字节,p+1跳过了数组的5个元素。
上述指针变量的特点正好和二维数组的行指针相同,因此我们也可以利用指针变量进行整行的跳动:
main()
{
float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0};
float (*p)[3];
inti,j;
printf("Please input i =");
scanf("%d", &i);
printf("Please input j =");
scanf("%d", &j);
p=a;
printf("\na[%d][%d]=%f ",i,j,*(*(p+i)+j));
}
说明:
(1) p定义为一个指向float型、一维、3个元素数组的指针变量p。
(2) 语句p=a将二维数组a的首地址赋给了p。根据p的定义,p加法的单位是3个float型单元,因此p+i等价于a+i,*(p+i)等价于*(a+i),即a[i][0]元素的地址,也就是该元素的指针。
(3) *(p+i)+j等价于& a[i][0]+j,即数组元素a[i][j]的地址;
(4) *(*(p+i)+j)等价于(*(p+i))[j],即a[i][j]的值。
p在定义时,对应数组的长度应该和a的列长度相同。否则编译器检查不出错误,但指针偏移量计算出错,导致错误结果。例8-1 输入一组整数(数量不超过100),统计其中偶数和奇数的总和(用指针方式)。
可以使用一个int型数组保存输入的一组整数,然后利用指针依次遍历数组的元素,统计偶数和奇数的总和。a[i]和*(a+i)是等价的。
偶数和奇数的辨别可以用整数%2的方法,%2为0则为偶数,否则为奇数。
main()
{
int a[100],i,j,nLen, nEvenCount=0, nOddCount=0,*p=a;
/*输入整数的个数*/
printf("\nPlease input count of integers: ");
scanf("%d",&nLen);
/*依次输入一组整数*/
printf("Please input integers: ");
for(i=0;i scanf("%d",p+i); /*等价于scanf("%d", &a[i])*/ /*统计偶数和奇数的个数*/ for(i=0;i { if(*p%2==0) nEvenCount++; else nOddCount++; } /*打印输出结果*/ printf("There are %d even numbers, %d odd numbers. ",nEvenCount, nOddCount); } 程序的运行结果为: Please input count of integers: 6 Please input integers: 1 3 4 65 8 66 There are 3 even numbers, 3 odd numbers. 上面的例子中,指针变量指向了数组a的首地址,p+i即为数组元素a[i]的指针,因此scanf语句中可以使用p+i作为参数。统计偶数和奇数时,循环每次使p指向数组的下一个元素,循环中*p的值是数组对应元素的值。注意nEvenCount和nOddCount一定要初始化为零,否则它们都是随机数。 例8-2 编写一个函数,可以实现2*3 3*4矩阵相乘运算(用指针方式)。 使用一个2×3的二维数组和一个3×4的二维数组保存原矩阵的数据;用一个2×4的二维数组保存结果矩阵的数据。 结果矩阵的每个元素都需要进行计算,可以用一个嵌套的循环(外层循环2次,内层循环4次)实现。 根据矩阵的运算规则,上述矩阵每个元素的计算方法为: 因此,内层循环里可以再使用一个循环,累加得到每个元素的值。一共使用三层嵌套的循环。 main() { inti,j,k, a[2][3],b[3][4],c[2][4]; /*输入a[2][3]的内容*/ printf("\nPlease input elements of a[2][3]:\n"); for(i=0;i<2;i++) for(j=0;j<3;j++) scanf("%d", a[i]+j); /* a[i]+j等价于&a[i][j]*/ /*输入b[3][4]的内容*/ printf("Please input elements of b[3][4]: \n"); for(i=0;i<3;i++) for(j=0;j<4;j++) scanf("%d", *(b+i)+j); /* *(b+i)+j等价于&b[i][j]*/ /*用矩阵运算的公式计算结果*/ for(i=0;i<2;i++) for(j=0;j<4;j++) { *(c[i]+j)=0; /* *(c[i]+j)等价于c[i][j]*/ for(k=0;k<3;k++) *(c[i]+j)+=a[i][k]*b[k][j]; } /*输出结果矩阵c[2][4]*/ printf("\nResults: "); for(i=0;i<2;i++) { printf("\n"); for(j=0;j<4;j++) printf("%d ",*(*(c+i)+j)); /* *(*(c+i)+j)等价于c[i][j]*/ } } 运行结果为: Please input elements of a[2][3]: 1 2 3 4 5 6 Please input elements of b[3][4]: 1 2 3 4 5 6 7 8 9 10 11 12 Results: 38 44 50 56 83 98 113 128 上面的例子复习了二维指针的各种表示方法,在实际应用中可以灵活使用。 要用指针处理二维数组,首先要解决从存储的角度对二维数组的认识问题。我们知道,一个二维数组在计算机中存储时,是按照先行后列的顺序依次存储的,当把每一行看作一个整体,即视为一个大的数组元素时,这个存储的二维数组也就变成了一个一维数组了。而每个大数组元素对应二维数组的一行,我们就称之为行数组元素,显然每个行数组元素都是一个一维数组 下面我们讨论指针和二维数组元素的对应关系,清楚了二者之间的关系,就能用指针处理二维数组了。 设p是指向数组a的指针变量,若有: p=a[0]; 则p+j将指向a[0]数组中的元素a[0][j]。 由于a[0]、a[1]┅a[M-1]等各个行数组依次连续存储,则对于a数组中的任一元素a[i][j],指针的一般形式如下: p+i*N+j 元素a[i][j]相应的指针表示为: *( p+i*N+j) 同样,a[i][j]也可使用指针下标法表示,如下: p[i*N+j] 例如,有如下定义: int a[3][4]={{10,20,30,40,},{50,60,70,80},{90,91,92,93}}; 则数组a有3个元素,分别为a[0]、a[1]、a[2]。而每个元素都是一个一维数组,各包含4个元素,如a[1]的4个元素是a[1][0]、a[1][1]、a[1]2]、a[1][3]。 若有: int *p=a[0]; 则数组a的元素a[1][2]对应的指针为:p+1*4+2 元素a[1][2]也就可以表示为:*( p+1*4+2) 用下标表示法,a[1][2]表示为:p[1*4+2] 特别说明: 对上述二维数组a,虽然a[0]、a都是数组首地址,但二者指向的对象不同,a[0]是一维数组的名字,它指向的是a[0]数组的首元素,对其进行“*”运算,得到的是一个数组元素值,即a[0]数组首元素值,*a等价于a[0] a[0]等价于&a[0][0],因此,*a[0]与a[0][0]是同一个值; C++指针函数习题 一、选择题 1.以下程序的运行结果是()。 sub(int x, int y, int *z) { *z=y-x; } void main() { int a,b; sub(10,5,&a); sub(7,a,&b); cout< #include<> 彻底搞定C指针---指向指针的指针 彻底搞定C指针---指向指针的指针一.回顾指针概念: 今天我们又要学习一个叫做指向另一指针地址的指针。让我们先回顾一下指针的概念吧! 当我们程序如下申明变量: short int i; char a; short int * pi; 程序会在内存某地址空间上为各变量开辟空间,如下图所示。 内存地址→6 7 8 9 10 11 12 13 14 15 ------------------------------------------------------------------------------------- … | | | | | | | | | | ------------------------------------------------------------------------------------- |short int i |char a| |short int * pi| 图中所示中可看出: i 变量在内存地址5的位置,占两个字节。 a变量在内存地址7的位置,占一个字节。 pi变量在内存地址9的位置,占两个字节。(注:pi 是指针,我这里指针的宽度只有两个字节,32位系统是四个字节) 接下来如下赋值: i=50; pi=&i; 经过上在两句的赋值,变量的内存映象如下: 内存地址→6 7 8 9 10 11 12 13 14 15 -------------------------------------------------------------------------------------- … | 50 | | | 6 | | | | -------------------------------------------------------------------------------------- |short int i |char a| |short int * pi| 看到没有:短整型指针变量pi的值为6,它就是I变量的内存起始地址。所以,这时当我们对*pi进行读写操作时,其实就是对i变量的读写操作。如:*pi=5; //就是等价于I=5; 你可以回看本系列的第二篇,那里有更加详细的解说。 二.指针的地址与指向另一指针地址的指针 在上一节中,我们看到,指针变量本身与其它变量一样也是在某个内存地址中的,如pi的内存起始地址是10。同样的,我们也可能让某个指针指向这个 一、指针数组及指向一维数组的指针(数组指针)讲解 1、数组指针(也称行指针) 定义 int (*p)[n]; ()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。 如要将二维数组赋给一指针,应这样赋值:int a[3][4];int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。 p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0] p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][] 所以数组指针也称指向一维数组的指针,亦称行指针。 2、指针数组 定义 int *p[n]; []优先级高,先与p结合成为一个数组,再由int *说明这是一个整型指针数组,它有n个指针类型的数组元素。这样赋值是错误的:p=a;只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。 如要将二维数组赋给一指针数组: int *p[3]; int a[3][4]; for(i=0;i<3;i++) p[i]=a[i]; 这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p [1]、p[2]所以要分别赋值。 这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。 还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。 比如要表示数组中i行j列一个元素: *(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j] 优先级:()>[]>* 例1、下列给定程序中,函数fun()的功能是:从N个字符串中找出最长的那个串,并将其地址作为函数值返回。 #include 在开发工业以太网项目的时候经常遇到一些小细节问题,在建立数据报进行传输的过程中传递txbuf缓冲区的地址的时候就遇到类似下面的问题。 一.简单说明1 定义一个2X3的int型的二维数组int array[2][3];并且给这个二维数组赋值1,2,3,4,5,6;array[0][0]=1 array[0][1]=2 array[0][2]=3 array[1][0]=4 array[1][1]=5 array[1][2]=6 输出结果 1 2 3 4 5 6 array[0]表示第一行的首地址,也就是第一行第一个数的地址,也就是&array[0][0] So array[0]==&array[0][0];其实&array[0]还==array[0]==&array[0][0],都表示第一行的首地址。 array[1]是第二行的首地址,也就是第二行第一个数的地址,也就是&array[1][0] so array[1]=&array[1][0];试试&array[1]还==array[1]==&array[1][0] 定义一个指针变量int *p;将第一行的首地址赋给p有3种方式。 1. p=array[0]; 2. p=&array[0]; 3. p=&array[0][0]; p[0]就等同于array[0][0],也就是p[0]==1;(为了形象记忆,可以用替换的角度去记忆和理解。因为之前说过p=array[0], so, p[0]就把p换成array[0]再加上[0]就是arary[0][0]) p[1]等于array[0][1]等于2 p[2]等于array[0][2]等于3 用名作为其他变量名地别名. ; 等价于; ()声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名地一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元.故:对引用求地址,就是对目标变量求地址.与相等. ()不能建立数组地引用.因为数组是一个由若干个元素所组成地集合,所以无法建立一个数组地别名. 引用应用 、引用作为参数 引用地一个重要作用就是作为函数地参数.以前地语言中函数参数传递是值传递,如果有大块数据作为参数传递地时候,采用地方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序地效率.但是现在(中)又增加了一种同样有效率地选择(在某些特殊情况下又是必须地选择),就是引用. 【例】: ( , ) 此处函数地形参, 都是引用 { ; ; ; ; } 为在程序中调用该函数,则相应地主调函数地调用点处,直接以变量作为实参进行调用即可,而不需要实参变量有任何地特殊要求.如:对应上面定义地函数,相应地主调函数可写为: ( ) { ; >>>>; 输入两变量地值 (); 直接以变量和作为实参调用函数 <<<< ' ' <<; 输出结果 } 上述程序运行时,如果输入数据并回车后,则输出结果为. 由【例】可看出: ()传递引用给函数与传递指针地效果是一样地.这时,被调函数地形参就成为原来主调函数中地实参变量或对象地一个别名来使用,所以在被调函数中对形参变量地操作就是对其相应地目标对象(在主调函数中)地操作. ()使用引用传递函数地参数,在内存中并没有产生实参地副本,它是直接对实参操作;而使用一般变量传递函数地参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量地副本;如果传递地是对象,还将调用拷贝构造函数.因此,当参数传递地数据较大时,用引用比用一般变量传递参数地效率和所占空间都好. ()使用指针作为函数地参数虽然也能达到与使用引用地效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"地形式进行运算,这很容易产生错误且程序地阅读性较差;另一方面,在主调函数地调用点处,必须用变量地地址作为实参.而引用更容易使用,更清晰. 如果既要利用引用提高程序地效率,又要保护传递给函数地数据不在函数中被改变,就应使用常引用. 、常引用 常引用声明方式:类型标识符引用名目标变量名; 用这种方式声明地引用,不能通过引用对目标变量地值进行修改,从而使引用地目标成为,达到了引用地安全性. 【例】: ; ; ; 错误 ; 正确 这不光是让代码更健壮,也有些其它方面地需要. 【例】:假设有如下函数声明: . 编程题 1用指向数组的指针变量输出数组的全部元素 2 使用函数调用,形参为指针,实参为数组,把一个数组逆序存放在输出 练习题: 一判断题 1.指针是变量,它具有的值是某个变量或对象的地址值,它还具有一个地址值,这两个地址值是相等的。 2.指针的类型是它所指向的变量或对象的类型。 3.定义指针时不可以赋初值。 4.指针可以赋值,给指针赋值时一定要类型相同,级别一致。5.指针可以加上或减去一个int型数,也可以加上一个指针。6.两个指针在任何情况下相减都是有意义的。 7.数组元素可以用下标表示,也可以用指针表示。 8.指向数组元素的指针只可指向数组的首元素。 9.字符指针是指向字符串的指针,可以用字符串常量给字符指针赋值。 10.引用是一种变量,它也有值和地址值。 11.引用是某个变量的别名,引用是被绑定在被引用的变量上。 12.创建引用时要用一个同类型的变量进行初始化。 13.指针是变量,它可以有引用,而引用不能有引用。 ;. . 二单选题 1.下列关于定义一个指向double型变量的指针,正确的是()。A.int a(5);double *pd=a; B.double d(2.5),*pd=&d;C.double d(2.5),*pd=d; D.double a(2.5),pd=d;。).下列关于创建一个int型变量的引用,正确的是(2A.int a(3),&ra=a; B int . a(3),&ra=&a;ra=a;D.int a(3), C.double d(3.1);int &rd=d;.下列关于指针概念的描述中,错误的是()。3 A.指针中存放的 是某变量或对象的地址值.指针的类型是它所存放的数值的类型 B .指针是变量,它也具有一个内存地址值 C .指针的值是可以改 变的D 。.下列关于引用概念的描述中,错误的是()4 A.引 用是变量,它具有值和地址值 B.引用不可以作数组元素 C.引用是变量的别名 D.创建引用时必须进行初始化。++*p相同的是()*p=a5.已知:int a[5],;则与a[0] . B.*++p A++a[0] .C*p++ D.;. . 6.已知:int a[ ]={1,2,3,4,5},*p=a;在下列数组元素地址的表 指向二维数组的指针 一. 二维数组元素的地址 为了说明问题, 我们定义以下二维数组: int a[3][4]={{0,1,2,3}, {4,5,6,7}, {8,9,10,11}}; a为二维数组名, 此数组有3行4列, 共12个元素。但也可这样来理解, 数组a由三个元素组成: a[0], a[1], a[2]。而它中每个元素又是一个一维数组, 且都含有4个元素(相当于4列), 例如, a[0]所代表的一维数组所包含的4 个元素为a[0][0], a[0][1], a[0][2], a[0][3]。如图5.所示: ┏━━━━┓┏━┳━┳━┳━┓ a─→┃a[0] ┃─→┃0 ┃1 ┃2 ┃3 ┃ ┣━━━━┫┣━╋━╋━╋━┫ ┃a[1] ┃─→┃4 ┃5 ┃6 ┃7 ┃ ┣━━━━┫┣━╋━╋━╋━┫ ┃a[2] ┃─→┃8 ┃9 ┃10┃11┃ ┗━━━━┛┗━┻━┻━┻━┛ 图5. 但从二维数组的角度来看, a代表二维数组的首地址, 当然也可看成是二维数组第0行的首地址。a+1就代表第1行的首地址, a+2就代表第2行的首地址。如果此二维数组的首地址为1000, 由于第0行有4个整型元素, 所以a+1为1008, a+2 也就为1016。如图6.所示 a[3][4] a ┏━┳━┳━┳━┓ (1000)─→┃0 ┃1 ┃2 ┃3 ┃ a+1 ┣━╋━╋━╋━┫ (1008)─→┃4 ┃5 ┃6 ┃7 ┃ a+2 ┣━╋━╋━╋━┫ (1016)─→┃8 ┃9 ┃10┃11┃ ┗━┻━┻━┻━┛ 图6. 既然我们把a[0], a[1], a[2]看成是一维数组名, 可以认为它们分别代表它们所对应的数组的首地址, 也就是讲, a[0]代表第0 行中第0 列元素的地址, 即&a[0][0], a[1]是第1行中第0列元素的地址, 即&a[1][0], 根据地址运算规则, a[0]+1即代表第0行第1列元素的地址, 即&a[0][1], 一般而言, a[i]+j即代表第i行第j列元素的地址, 即&a[i][j]。 另外, 在二维数组中, 我们还可用指针的形式来表示各元素的地址。如前所述, a[0]与*(a+0)等价, a[1]与*(a+1)等价, 因此a[i]+j就与*(a+i)+j等价, 它表示数组元素a[i][j]的地址。 因此, 二维数组元素a[i][j]可表示成*(a[i]+j)或*(*(a+i)+j), 它们都与a[i][j]等价, 或者还可写成(*(a+i))[j]。 另外, 要补充说明一下, 如果你编写一个程序输出打印a和*a, 你可发现它们的值是相同的, 这是为什么呢? 我们可这样来理解: 首先, 为了说明问题, 我们把二维数组人为地看成由三个数组元素a[0], a[1], a[2]组成, 将a[0], a[1], a[2]看成是数组名它们又分别是由4个元素组成的一维数组。因此, a表示数组第0行的地址, 而*a即为a[0], 它是数组名, 当然还是地址, 它就是数组第0 行第0 列元素的地址。 编程题 1用指向数组的指针变量输出数组的全部元素 2 使用函数调用,形参为指针,实参为数组,把一个数组逆序存放在输出 练习题: 一判断题 1.指针是变量,它具有的值是某个变量或对象的地址值,它还具有一个地址值,这两个地址值是相等的。 2.指针的类型是它所指向的变量或对象的类型。 3.定义指针时不可以赋初值。 4.指针可以赋值,给指针赋值时一定要类型相同,级别一致。 5.指针可以加上或减去一个int型数,也可以加上一个指针。 6.两个指针在任何情况下相减都是有意义的。 7.数组元素可以用下标表示,也可以用指针表示。 8.指向数组元素的指针只可指向数组的首元素。 9.字符指针是指向字符串的指针,可以用字符串常量给字符指针赋值。 10.引用是一种变量,它也有值和地址值。 11.引用是某个变量的别名,引用是被绑定在被引用的变量上。 12.创建引用时要用一个同类型的变量进行初始化。 13.指针是变量,它可以有引用,而引用不能有引用。 二单选题 1.下列关于定义一个指向double型变量的指针,正确的是()。 A.int a(5);double *pd=a;B.double d(2.5),*pd=&d;C.double d(2.5),*pd=d;D.double a(2.5),pd=d; 2.下列关于创建一个int型变量的引用,正确的是()。 A.int a(3),&ra=a;B.int a(3),&ra=&a; C.double d(3.1);int &rd=d;D.int a(3),ra=a; 3.下列关于指针概念的描述中,错误的是()。 A.指针中存放的是某变量或对象的地址值 B.指针的类型是它所存放的数值的类型 C.指针是变量,它也具有一个内存地址值 D.指针的值是可以改变的 4.下列关于引用概念的描述中,错误的是()。 A.引用是变量,它具有值和地址值 B.引用不可以作数组元素 C.引用是变量的别名 D.创建引用时必须进行初始化 5.已知:int a[5],*p=a;则与++*p相同的是()。 A.*++p B.a[0] C.*p++ D.++a[0] 一、指针:内容是指示一个内存地址的变量;类型是指示编译器怎么解释指针内容指向地址中的内容,以及该内存区域有多大; 例子: [cpp] int i = 0; int * pi = &i; printf(“pi = %x \n”, pi); // 打印pi的内容: 0x2000 printf(“*pi= %d \n” , *pi); // 打印pi指向地址中的值: 5 printf(“&pi= %x \n”, &pi); // 打印pi的地址: 0x100 从汇编的角度来看,指针是这样的: int i = 0; 010E139E mov dword ptr [i],0 int * pi = &i; 010E13A5 lea eax,[i] 010E13A8 mov dword ptr [pi],eax 二、数组:是一个单一数据类型对象的集合。其中单个对象没有被命名,通过索引访问。 数组名和指针的区别:数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组。数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量。指向数组的指针则是另外一种变量类型,仅仅意味着数组的存放地址 注意:虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改,如下:天骄无双:https://www.doczj.com/doc/9c2360618.html, [cpp] int intArray[10]; intArray++; // 错误 “指针和数组等价”说的是什么?索引操作相同,例如:p[2]; a[2]; 三、引用(reference)是一个对象的别名。用对象初始化引用后,对象的名字和引用都指向该对象; 引用是如何实现的?从汇编语言的角度来看,指针和引用是一样的: [cpp] int i = 0; 00E9139E mov dword ptr [i],0 int & ref = i; 00E913A5 lea eax,[i] 00E913A8 mov dword ptr [ref],eax int * pi = &i; 00E913AB lea eax,[i] 00E913AE mov dword ptr [pi],eax 指针和引用的区别(从C++使用角度来看): 不存在空引用 引用要初始化 引用初始化后,不能指向另一个对象 这是由编译阶段保证的。 备注:一个指向非常量的引用不能用字面值或者临时值初始化;但是一个指向常量的引用可以。天骄无双:https://www.doczj.com/doc/9c2360618.html,二维数组和指针
C指针函数习题
指向指针的指针——彻底搞定C指针
指针数组及指向一维数组的指针讲解
关于二维数组地址和指针之间的赋值
指针变量作为函数参数
指针练习题
指向二维数组的指针
指针练习题
C语言中指针、数组和引用例子实例
指向函数的指针