目录
对于一些初学的小伙伴来说,最大的误区就是认为指针和数组是一样的。这是很严重的一个问题,数组本身是来顺序存储一系列同类型数据的,而指针是指向内存地址的,虽然两者在某些地方可以相互替代使用,但这并不代表两者相同。
为什么可以像指针一样使用数组?
举一个非常常见的例子,大家可能在定义函数的时候会写到如下:
void func(int arr[]);
这里写入的参数是一个 int 类型的数据,但是我们在实际使用的时候可能会既可以传入一个数组,又可以传入一个指针,如下:
int main() {
// 情况1:传递数组
int my_array[3] = {1, 2, 3};
func(my_array);
// 情况2:直接传递指针
int *ptr = my_array;
func(ptr);
return 0;
}
为什么会这样呢?难道数组和指针的类型相同,是同一个东西吗?
并不是这样的, 这是因为数组在表达式中使用的时候,编译器会有一个隐式的类型转换,将他转换成一个指针常量,这个指针常量指向的就是数组首元素的地址。要注意,转换的是一个指针常量,我们是不能去修改指针的指针指向的(如a、b都是一个数组,是不可以让 a = b 的)。
但是又两种特殊情况,数组名是不能用指针常量来代替的。当对一个数组名进行 sizeof 或 & 操作的时候,并不会转换成一个指针常量。sizeof() 返回的是一个数组的长度,而不是数组的指针的长度;& 取数组名返回的是一个指向数组的指针,而不是一个指向指针常量的指针。
下面举一个例子:
int a[10];
int b[10];
int *c;
c = &a[0];
表达式 &a[0] 是一个指向数组首元素的一个指针,但那正好是数组本身的值,所以使用 c = a 和上面那条语句执行的任务是完全一样的。
b = a 和 c = a 这两个操作都是非法的。都是去尝试修改指针常量的指向了。
为什么可以像数组一样使用指针?
下面举一个例子,定义一个指针,我们去用数组的方式对其进行访问。
int main()
{
char *p = "abcdefgh";
char arr[] = "abcdefgh";
char a = p[5];
char b = arr[5];
printf("a = %c,b = %c",a,b);
return 0;
}
/* 打印:a = f,b = f */
在上述两种情况下,我们都可以通过下标5拿到 'f',但是两者的途径完全不一样。
使用指针 p[5] 去引用元素的时候,编译器实际会进行以下操作:
- 取得符号表中 p 的地址,提取存储于此处的指针。
- 把下标所表示的偏移量与指针的值相加,产生一个地址。
- 访问上面这个地址,取得字符。
也就是说 p[5] 本质上就是进行了 *(p+5) 这样一个操作,也就是向后便宜了5个 char 的地址,去拿到了 'f' 。
我们使用数组 arr[5] 去引用元素的时候,我们上面已经说过了,数组在表达式中会转化成一个指针常量,所以同样是进行了 *(arr + 5) 这样一个操作。
虽然二者最终结果是一样的,但是两者的最终获取偏移量的一个过程并不相同。
数组和指针的区别总结
上述只是帮助大家分清了为什么有时候数组和指针可以相互替换。下面总结一下本质上的区别:
特性 | 数组 | 指针 |
---|---|---|
类型 | 连续内存块的别名 | 存储地址的变量 |
存储内容 | 直接存储数据元素 | 存储其他变量的地址 |
内存分配 | 编译期静态分配(栈/全局区) | 运行时动态分配(可指向堆/栈/全局区) |
大小(sizeof) | 返回整个数组的字节大小(如char[9] →9) |
返回指针本身的字节大小(4或8字节) |
地址性质 | 常量 | 变量 |
指针和数组都可以在定义中用字符串常量进行初始化,虽然看着一样,但底层的逻辑并不相同。
char *p = "abcdefgh";
定义指针的时候,编译器并不为指针所指向的对象分配空间,他只是分配指针本身的空间,除非我们在定义的时候就赋值给指针一个字符串常量进行初始化。初始化的时候,字符串常量会被设置为只读。如果通过指针修改这个字符串,就会造成未定义的行为。在有些编译器中,字符串常量存在于只允许读取的数据段中,防止被修改。
char [] = "abcdefg";
但是使用字符串常量初始化的数组是可以通过下标去修改字符串的。