数组与指针
相信各位在初学的时候,指针和数组的概念还是比较泾渭分明的,但是学着学着,往往会感觉他们的界限越来越模糊,甚至产生“数组就是指针的”一种错觉。
但是很显然,这种想法是错误的,尽管数组名在值上与指针相同,但是如果我们采用VS去查看的,会发现他们的类型是不同:
int arr[3] = {1, 2, 3};
int *ptr = arr;
arr的类型是int[3]
,而ptr的类型是int*
,尽管大多数情况下,他们可以互相转化,但是还是有两个重要的操作符会给出不一样的结果:
-
sizeof运算符,对于arr,会给出12,对于ptr,在32位g++下,给出4.
-
&(取址运算符),我们可以看一下结果:
运算符 类型 结果 arr int[3] 0xffc0e994 &arr int[3]* 0xffc0e994 &arr[0] int* 0xffc0e994 ptr int* 0xffc0e994 &ptr int** 0xffc0e990 最为引人瞩目的是&arr和&ptr是不一致的
那么,为什么会有这样的不同呢?其实非常简单,什么是数组?数组是内存中一段连续的空间,他存储着一系列相同种类的元素。但是指针不同,他是一个存储着另一个对象的地址的对象。尽管我们在直接使用arr,也就是数组名的时候,编译器会解释为arr的地址,但是arr本身不是那个地址,而是那段连续的空间;ptr则不同,他本身就是地址。
另外,值得一提的是,数组类型是不支持拷贝初始化的,这也就决定了,数组不可能作为函数的参数(因为实参到形参必有一个拷贝初始化)。所以,我们看到的所有数组作为参数的函数调用,编译器会先帮助我们隐转为指针,然后进行调用,所以把数组作为参数传入函数之后,他的行为就与指针一致了。
那么,为什么数组不支持拷贝初始化呢?C++的数组继承自C,C是面向过程的语言,数组和数组的长度是分离的。这也就导致仅仅得知一个数组的情况下,无法得到它的长度。所以,自然无法拷贝。C++的vector类型内部具有长度,所以就可以拷贝初始化。
指针和引用
(下面讨论的,都是C++98里面经典的左值引用)
这也是一个非常让人迷惑的点,指针和引用看起来非常相似,但又如此不同,相信各位一定能把指针和引用的不同背的滚瓜烂熟:
- 最本质的不同:指针是一个对象(一块能存储数据并具有某种类型的内存空间),而引用仅仅是一个别名,这决定了引用与指针其他的不同
- 引用必须被初始化,但是指针不必。
- 存在空指针,但是没有空引用。
- 引用的创建和销毁不会调用构造与析构函数。
- 引用一旦与某个对象绑定便无法被绑定到其他变量上,但是指针是可以的。
但是,指针和引用有什么关系呢?答案是没有关系(笑)
在底层上看,引用和指针一样,都是通过变量的地址来进行访问的。但是编译器,也就是C++的语言层面对于引用做出了诸多限制。学习语言需要注意上层设计和底层设计(没有这种术语,我瞎说的),尽管底层上看引用和指针貌似一样,他肯定也得存在一个地址,但是我们的上层设计禁止了这一点,来保证“引用不是对象”的要求。所以,我们理解引用的时候,只需要知道,“引用不是对象,不是对象的地址,不是对象的拷贝,他仅仅是一个别名”