20 数组和指针
1 数组的本质
-
数组是一段连续的内存空间
-
数组的空间大小为
sizeof(array_type) * array_size
-
数组名可看作指向数组第一个元素的常量指针,但数组名绝不是指针
-
a + 1
的意义#include <stdio.h> int main() { int a[5] = {0}; int* p = NULL; //看作是常量指针的数组名运算 printf("a = 0x%X\n", (unsigned int)(a)); // a = 0xBFF51B48 printf("a + 1 = 0x%X\n", (unsigned int)(a + 1)); // a + 1 = 0xBFF51B4c //指针运算 printf("p = 0x%X\n", (unsigned int)(p)); // p = 0x0 printf("p + 1 = 0x%X\n", (unsigned int)(p + 1)); // p + 1 = 0x4 return 0; }
2 指针的运算
-
指针是一种特殊的变量,与整数的运算规则为:
p + n; => (unsigned int)p + n * sizeof(*p);
-
当指针
p
指向一个同类型的数组的元素时:p + 1
将指向当前元素的下一个元素;p - 1
将指向当前元素的上一个元素 -
指针之间只支持减法运算,参与减法运算的指针类型必须相同:
p1 - p2; => ((unsigned int)p1 - (unsigned int)p2) / sizeof(type);
-
只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差;当两个指针指向的元素不在同一个数组中时,结果未定义
3 指针的比较
-
指针也可以进行关系运算(<,<=,>,>=)
-
指针关系运算的前提是同时指向同一个数组中的元素
-
任意两个指针之间的比较运算(==,!=)无限制
-
参与比较运算的指针类型必须相同
-
示例1
#include <stdio.h> int main() { char s1[] = {'H', 'e', 'l', 'l', 'o'}; int i = 0; char s2[] = {'W', 'o', 'r', 'l', 'd'}; char* p0 = s1; char* p1 = &s1[3]; char* p2 = s2; int* p = &i; printf("%d\n", p0 - p1); //-3 printf("%d\n", p0 + p2); //error printf("%d\n", p0 - p2); //error printf("%d\n", p0 - p); //error printf("%d\n", p0 * p2); //error printf("%d\n", p0 / p2); //error return 0; }
-
示例2
#include <stdio.h> #define DIM(a) (sizeof(a) / sizeof(*a)) int main() { char s[] = {'H', 'e', 'l', 'l', 'o'}; char* pBegin = s; char* pEnd = s + DIM(s); // Key point char* p = NULL; printf("pBegin = %p\n", pBegin); printf("pEnd = %p\n", pEnd); printf("Size: %d\n", pEnd - pBegin); for(p=pBegin; p<pEnd; p++) { printf("%c", *p); } printf("\n"); return 0; } //输出结果 pBegin = 0xbfac155f pEnd = 0xbfac1564 size = 5 Hello
4 数组的访问方式
-
数组名可以当作常量指针使用,那么指针是否也可以当作数组名来使用?
- 可以!
-
以下标的形式访问数组中的元素
int main() { int a[5] = {0}; a[1] = 3; a[2] = 5; return 0; }
-
以指针的形式访问数组中的元素
int main() { int a[5] = {0}; //数组名看作常量指针 *(a + 1) = 3; *(a + 2) = 5; return 0; }
-
下标形式 VS 指针形式
- 指针以固定增量在数组中移动时,效率高于下标形式
- 指针增量为 1 且硬件具有硬件增量模型时,效率更高
- 下标形式与指针形式的转换:
a[n] <=> *(a + n) <=> *(n + a) <=> n[a]
- 现代编译器的生成代码优化率已经大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可读性和代码维护的角度来看,下标形式更优
-
数组的访问方式
#include <stdio.h> int main() { int a[5] = {0}; int* p = a; int i = 0; for(i=0; i<5; i++){ //将指针当作数组名使用 p[i] = i + 1; } for(i=0; i<5; i++){ printf("a[%d] = %d\n", i, *(a + i)); } printf("\n"); for(i=0; i<5; i++){ //等价代换公式 i[a] = i + 10; } for(i=0; i<5; i++){ printf("p[%d] = %d\n", i, p[i]); } return 0; } //输出结果 a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 4 a[4] = 5 p[0] = 10 p[1] = 11 p[2] = 12 p[3] = 13 p[4] = 14
-
数组和指针不同
//ext.c int a[] = {1, 2, 3, 4, 5}; //test.c #include <stdio.h> int main() { extern int a[]; printf("&a = %p\n", &a);//数组地址 printf("a = %p\n", a);//数组第一个元素的地址 printf("*a = %d\n", *a);//取“数组第一个元素的地址”上的值 return 0; }
-
运行结果
&a = 0x804a014 a = 0x804a014 *a = 1
-
修改:验证数组名与指针是否一样。运行结果分析:
- ext.c 中定义了一个数组,在内存为:
1000 2000 ... 5000
(Linux为小端系统) ,一共 20 个字节 - 该数组在内存中的地址为:
0x804a014
,也就是说在编译后,标识符a
的意义为一个地址:0x804a014
- 在编译 test.c 时,当编译到
extern int* a;
时,发现标识符a
在别处定义 - 当编译到
printf("&a = %p\n", &a);
时,打印标识符a
的地址,即为:0x804a014
- 当编译到
printf("a = %p\n", a);
时,打印的是一个指针变量a
,其值保存的是一个地址,那么取 4 个字节的值即为:0x1
- 当编译到
printf("*a = %d\n", *a);
时,*a
是到地址0x1
取值,而此地址为操作系统所使用,产生段错误
//test.c #include <stdio.h> int main() { //数组名改为指针 extern int* a; printf("&a = %p\n", &a); printf("a = %p\n", a); printf("*a = %d\n", *a); return 0; } //运行结果 &a = 0x804a014 a = 0x1 段错误
- ext.c 中定义了一个数组,在内存为:
-
4 a 和 &a 的区别
-
a 为数组首元素的地址
-
&a 为整个数组的地址
-
a 和 &a 的区别在于指针运算
a + 1 => (unsigned int)a + sizeof(*a) =>(unsigned int)a + sizeof(*a[0])
&a + 1 => (unsigned int)(&a) + sizeof(*&a) => (unsigned int)(&a) + sizeof(a)
- 二者加 1 增加的步长不一样
-
指针运算示例
#include <stdio.h> int main() { int a[5] = {1, 2, 3, 4, 5}; printf("a = %p\n",a); // a = 0xbfa71990 int* p1 = (int*)(&a + 1); // p1指向数组a最后一个元素5后的下一个位置 int* p2 = (int*)((int)a + 1); // 整数加1是数学运算,结果为整数,p2指向一个地址:0xbfa71990 + 1 = 0xbfa71991,=> p2 = 0x0200 0000 => 十进制数:33554432 int* p3 = (int*)(a + 1); //p3指向第2个元素 printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]); // 5,33554432,3 return 0; }
5 数组参数
-
数组作为函数参数时,编译器将其编译成对应的指针
void f(int a[]); <=> void f(int* a);
void f(int a[5]); <=> void f(int* a);
-
一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小
#include <stdio.h> void func1(char a[5]) { printf("In func1: sizeof(a) = %d\n", sizeof(a)); *a = 'a'; //如果a是数组名的话会报错:数组名不可以赋值 a = NULL; } void func2(char b[]) { printf("In func2: sizeof(b) = %d\n", sizeof(b)); *b = 'b'; b = NULL; } int main() { char array[10] = {0}; func1(array); printf("array[0] = %c\n", array[0]); func2(array); printf("array[0] = %c\n", array[0]); return 0; } // 运行结果 In func2: sizeof(a) = 4 array[0] = a In func2: sizeof(b) = 4 array[0] = b