第29课 指针和数组分析(下)
1. 数组的访问方式
(1)以下标的形式访问数组中的元素:如a[i];
(2)以指针的形式访问数组中的元素:如*(a+i)
2. 下标形式 VS 指针形式
(1)指针形式以固定增量在数组中移动时,效率高于下标形式
(2)指针增量为1且硬件具有硬件增量模型时,效率更高
(3)下标形式与指针形式的转换: a[n]==*(a+n)==*(n+a)==n[a];
▲注意:
①现代编译器的生成代码优化率已大大提高,在固定增量时,下标形式的效率与指针形式相当;
②但从可读性和代码维护的角度看,下标形式更优
【实例分析】数组的访问方式
1 #include <stdio.h> 2 3 4 5 int main(){ 6 7 8 9 int a[5] = {0}; 10 11 int* p = a; 12 13 int i = 0; 14 15 16 17 for(i=0; i<5; i++) 18 19 { 20 21 p[i] = i + 1; //利用指针给数组元素赋值 22 23 } 24 25 26 27 for(i=0; i<5; i++) 28 29 { 30 31 //以指针形式来访问数组元素 32 33 printf("a[%d] = %d\n",i,*(a+i)); 34 35 } 36 37 38 39 for(i=0; i<5; i++) 40 41 { 42 43 //以下标形式来访问数组元素 44 45 i[a] = i + 10; //比较另类,相当于a[i] = i + 10 46 47 } 48 49 50 51 for(i=0; i<5; i++) 52 53 { 54 55 //通过指针来访问数组元素 56 57 printf("p[%d] = %d\n",i,p[i]); 58 59 } 60 61 62 63 return 0; 64 65 }
【编程实验】数组和指针不同
//ext.c
//实验时,本文件应为.c,而不能是头文件。因为头文件会被直接包含在相应文件中,
//而.c文件是分别编译的
int a[5] = {1, 2, 3, 4, 5}; //在该文件中,a被看作一个数组
//29-2.c
1 #include <stdio.h> 2 3 4 5 //命令行编译这两个文件:gcc 29-2.c ext.c 6 7 8 9 int main(){ 10 11 12 13 //外部文件中a被定义成一个数组。int a[5] = {1, 2, 3, 4, 5}; 14 15 16 17 //实验1: 18 19 extern int a[]; //本文件,如果这样声明,a仍被看作一个数组 20 21 22 23 printf("&a = %p\n", &a); //这里的&a为整个数组的地址 24 25 printf("a = %p\n",a); //a为首元素的地址,数值上等于&a 26 27 printf("*a = %d\n",*a); //打印出第1个元素,即1 28 29 30 31 32 33 //实验2: 34 35 /* 36 37 extern int* a; //如果这样声明,编译器将a当成是一个int型的 38 39 //指针来使用。 40 41 42 43 printf("&a = %p\n", &a); //会从符号表中查到指针a的地址,指向数组 44 45 printf("a = %p\n",a); //从指针a处,取一个int型的数据,即第1个元素,为1 46 47 printf("*a = %d\n",*a); //试图从地址0x01处取出数据,违规内存访问错误。 48 49 */ 50 51 return 0; 52 53 }
3. a和&a的区别
(1)a为数组首元素的地址
(2)&a为整个数组的地址,即可将数组看成是一种数据结据。如int[5];
(3)a和&a的区别在在指针运算
-
- a + 1 == (unsigned int)a + sizeof(*a); //a代表首元素地址,*a表示第1个元素
- &a + 1 == (unsigned int)(&a) + sizeof(*&a) == (unsigned int)(&a) + sizeof(a);
【实例分析】指针运算经典问题
1 #include <stdio.h> 2 3 4 5 int main(){ 6 7 8 9 int a[5] = {1, 2, 3, 4, 5}; 10 11 int* p1 = (int*)(&a + 1); //指向数组最后面,即第5个元素的后面 12 13 int* p2 = (int*)((int)a + 1);//指向数组的起始地址+1byte偏移处 14 15 int* p3 = (int*)(a + 1); //指向第2个元素 16 17 18 19 printf("p1[-1] = %d\n",p1[-1]);//输出第5个元素 20 21 22 23 //数组a在内存中从低地址到高地址的分布如下:(小端模式,低字节放入低地址) 24 25 //01 00 00 00,02 00 00 00,03 00 00 00,04 00 00 00,05 00 00 00 26 27 //p2指向数组起始地址+1字节的偏移处,即01的后面,从这个地方开始读 28 29 //出4个字节,00 00 00 02,根据小端模式规则,该值为0x02 00 00 00, 30 31 printf("p2[0] = 0x%X,p2[0] = %d\n",p2[0],p2[0]);//0x02000000==33554432 32 33 34 35 printf("p3[1] = %d\n",p3[1]); //输出第3个元素,即3 36 37 38 39 return 0; 40 41 }
4. 数组参数
(1)数组作为函数参数时,编译器将其编译为对应的指针。因此,一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小。
void f(int a[])等价于void f(int* a);
void f(int a[5])等价于void f(int* a); //就是一个指针,丢失了数组长度的信息
【实例分析】虚幻的数组参数
1 #include <stdio.h> 2 3 4 5 void func1(char a[5]) //编译时,a被编译为一个指针,丢失了数组长度的信息 6 7 { 8 9 printf("In func1:sizeof(a) = %d\n",sizeof(a));//a退化为指针,4 10 11 12 13 *a = 'a'; //对指针所指向的内存进行存取(注意数组名也可以这样对内存 14 15 //进行访问,如访问第1次元素*a,对i个元素*(a+i);这说明数组名 16 17 //在使用上很像指针,但数组名并不是指针,见下面的分析 18 19 20 21 //再次说明a是指针,而不是数组名 22 23 a = NULL;//编译通过,a是指针类型。(而不是数组名,数组名不能作为左值) 24 25 } 26 27 28 29 void func2(char b[]) //b被编译成一个指针,与数组小大有没有被指定无关。 30 31 { 32 33 printf("In func2:sizeof(b) = %d\n",sizeof(b));//b退化为指针,仍为4 34 35 36 37 *b = 'b'; 38 39 40 41 b = NULL;//编译通过,b是指针类型 42 43 } 44 45 46 47 int main(){ 48 49 50 51 char array[10] = {0}; 52 53 54 55 func1(array); 56 57 printf("array[0] = %c\n",array[0]); //array[0] = a; 58 59 60 61 func2(array); 62 63 printf("array[0] = %c\n",array[0]); //array[0] = b; 64 65 66 67 return 0; 68 69 }
5. 小结
(1)数组名和指针仅使用方式相同,数组名的本质不是指针,指针的本质也不是数组。
(2)数组名并不是数组的地址,而是数组首元素的地址。
(3)函数的数组参数退化为指针