一、一个例子引发的思考
运行以下代码,请问输出是什么?例子来源于《剑指Offer》,我对书上例子做了修改,让其可以运行。
#include<iostream>
using namespace std;
int GetSize(int data[]) {
return sizeof(data);
}
int main() {
int data1[] = {1,2,3,4,5};
int size1 = sizeof(data1);
int *data2 = data1;
int size2 = sizeof(data2);
int size3 = GetSize(data1);
cout<<size1<<" "<<size2<<" "<<size3<<endl;
return 0;
}
运行结果如下:
data1是一个数组,sizeof(data1)是求数组的大小。这个数组包含5个整数,每个整数占4个字节,因为总共是20个字节。data2声明为指针,尽管它指向了数组data1,对认真指针求sizeof,得到的结果都是4。在C/C++中,当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针。因此尽管函数GetSize的参数data被声明为数组,但它会退化为指针,size3的结果仍然是4.
二、数组的两个特殊性质
(1)不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
int a[] = {0,1,2}; // 含有三个整数的数组
int s2[] = a; // 错误:不允许使用一个数组初始化另一个数组
a2 = a; // 错误:不能把一个数组直接赋值给另一个数组
(2)使用数组是通常将其转化成指针
在C++语言中,指针和数组有非常紧密的联系。使用数组的时候编译器一般会把它转换成指针。
通常情况下,使用取地址符来获取指向某个对象的指针,取地址符也可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能的搭配指向该元素的指针:
string nums[] = {"one", "two", "three"}; // 数组元素是string对象
string *p = &nums[0]; // p指向nums的第一个元素
然而,数组还有一个特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针:
string *p2 = nums; // 等价于p2 = &nums[0]
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
三、数组形参
数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
尽管不能以值传递的方式传毒数组,但是我们可以把形参写成类似数组的形式:
//尽管形式不同,但这三个print函数是等价的
//每个函数都有一个const int*类型的形参
void print(const int*);
void print(const int[]);
//这里的维度表示我们期望数组含有多少元素,实际不一定
void print(const int[10]);
尽管表现形式不同,但上面的三个函数是等价的:每个函数的唯一形式都是const int*类型的。当编译器处理对print函数的调用时,只检查传入的参数是否是const int*类型:
int i=0,j[2] = {0,1};
print(&i); //正确:&i的类型是int*
print(j); //正确:j转船成int* 并指向j[0]
如果我们传给print函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。
和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界。