的确,这是非常简单也没有太大意义的问题,但是因为各种各样的原因,教学、传播、造谣、历史,等等。很多人已经无法明确这个问题了,所以我们单独讲一下。
注意,聊 C++。
数组不是指针,指针不是地址,(引用不是指针)。
多数人提起数组与指针的时候,默认的是 数组与指针对象,而不是数组与指针类型。 这造成了很多的问题,同时衍生出很多的莫名其妙的说法,比如:
- 数组名
并且甚至有人将,“数组名”,和“数组”区分,用来指代两种东西,我不清楚这是否是目前大学的考点,但我的猜测是,他们谈论起“数组名”的时候,应该要表达的是 数组对象,但是,萌新能区分对象和类型? 这太神奇了,不管。
这种说法就如同 “int 名” 一样愚蠢。
int a = 0; // 这是一个 int 类型的对象,它的名字是 a
int arr[10]{}; // 这是一个数组类型(int[10])的对象,它的名字是arr
如果你认可“数组名”这种愚蠢的说法,应该也认可“int 名”、“double 名”、“string 名”。
有很多人都有着错误的想法,诸如:
我知道数组不是指针,但是它用起来和指针一样啊,除了 sizeof 的时候,那何必纠结呢?
说出这种话的原因非常简单,学少了。
我们下面详细聊聊能有多少区别,由常见的,到稍微不那么常见的。
void test(int*){}
void test2(int**){}
int arr[10]{};
int arr2[100]{};
test(arr); //所以 int[10] == int*?int[10][10] == int**?
test2(arr2); //Error
有些人可能会想了,改成
void test2(int[10][10]){}
int arr2[100]{};
test2(arr2);
你真的觉得函数 test2
的形参类型是 int[10][10]
吗?不是的,只是表象,这里实际会退化为数组指针 void test2(int(*)[10])
,这是语言的基本规则。
形参列表中的每个函数形参的类型根据下列规则确定:
- 首先,以如同在任何声明中的方式,组合声明说明符序列 和声明符以确定它的类型。
- 如果类型是“T 的数组”或“T 的未知边界数组”,那么它会被替换成类型“指向 T 的指针”
- 如果类型是函数类型 F,那么它被替换成类型“指向 F 的指针”
- 从形参类型中丢弃顶层 cv 限定符(此调整只影响函数类型,但不改动形参的性质:int f(const int p, decltype(p)); 和 int f(int, const int); 声明同一函数)
参见文档。这里不想计较这些愚蠢的设计和模糊,只是为了简单告诉各位而已,就这么一个简简单单的小问题,也能注意到,数组和指针的不同。
- 存在从数组类型的左值和右值到指针类型的右值的隐式转换:它构造一个指向数组首元素的指针。凡在数组出现于不期待数组而期待指针的语境中时,均使用这个转换。
template<typename T,std::size_t N>
void print(T(&arr)[N]) {
for (const auto i : arr) {
std::cout << i << ' ';
}
std::cout << '\n';
}
int arr[]{ 1,2,3,4 };
int* p = arr;
print(arr); //OK
print(p); //Error
我希望各位不要忘记:
- 数组的长度,是数组类型的一部分,数组的类型信息包括长度。
其实上一个例子也是用了模板,不过无所谓,我们再聊点:数组类型在 C++类型系统本身的意义。
数组它是一类类型,它类型本身就有意义,天生的第一无二的意义,它是数组,数组就是数组。
我们可以写模板的时候对数组类型进行偏特化。没必要自己写,看标准库。
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
template <
class T,
class Deleter
> class unique_ptr<T[], Deleter>;
指针是指针,指针指代一类类型,比如:int*
、double*
,说类型是什么,本身很莫名其妙,如果要聊指针对象,也一样莫名奇妙,指针对象是指针对象,它还能是啥?
指针对象可以存储别的对象的地址,就这么简单。
我 std::vector
能存的东西多了去了,那它是什么?
聊的不算非常完全,但,也暂时够了,可以提 pr 修改。