1. 背景
数组作为函数形参传递的是数组首元素的地址本来是很简单的知识点,但是在具体使用中还会有一些坑需要注意。
2. 数组做函数形参
C++中数组有两个特殊的性质:
- 不允许拷贝数组:我们不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值,所以我们不能以值传递的方式使用数组参数;
- 使用数组时通常会将其转换成指针,所以我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
数组做形参的三种写法:
void action(const int *); void action(const int[]); void action(const int[100]);
在C和C++中因为数组是以指针方式传递给函数,所以函数中没办法确定数组的实际大小,为了让函数体知道数组大小有以下三种方式:
- 要求数组本身包含一个结束标记,类似于C风格的字符串,用空字符标识数据结尾。但是局限性是数组元素类型和结束标记有明显区分,像int数组就不太好把一个数字作为数组的结束标记;
- 使用标准库规范。这种方法类似于C++标准库中的vector等数据结构的迭代器,传入输入的首元素指针和尾元素指针。
- 增加一个标记数组大小的int型形参,这种最简单直观。
3. 数组引用做函数形参
C++还允许我们将变量定义成数组的引用,同理,形参也可以是数组的引用:
void action(int (&arr)[100]){ for(auto i : arr) std::cout << i << std::endl; }
这里涉及到数组的指针和数组的引用的定义:
int *p[10];//p是含有10个整形指针的数组 int *r[10];//不存在引用的数组 int(*p2)[10] = &arr;// p2是指向一个含有10个整数的数组 int (&r2)[20] = arr;//r2引用一个含有10个整数的数组
这几个概念容易混淆。我们这里理解一下:
- 对于p,我们把类型修饰符从右向左依次绑定来理解,那么p定义的是一个大小为10的数组,名字是p,数组中存放的是指向int的指针;
- 对于p2,从右向左解释不太合理,这里就要从内向外理解。圆括号括起来的
*p
意味着p是一个指针,再看右边我们知道p是指向大小为10的数组的指针,再看左边,我们知道数组中的元素是int。
C++这种语法特别绕,有时候感觉勉强解释通了很容易就又混了,倒不如通过熟能生巧来“死记硬背”。
4. 多维数组作为函数的形参
C++本身没有多维数组,我们看到的多维数组其实是数组的数组。
将多维数组传递给函数时,真正传递的是指向数组首元素的指针,而首元素本身又是一个数组,所以形参实际上是一个指向数组的指针,数组的第二维的大小都是数组类型的一部分,不能省略:
void action(int (*p)[10], int rowSize){ }
p指向数组的首元素,而这个数组的元素是10个整数构成的数组。p实际是指向含有10个整数的数组的指针,注意是p是指向指针,指向含有10个整数数组的指针,特别绕。
如果我们使用数组的语法定义函数,编译器会忽略掉第一个维度,所以我们最好不要把它包括在形参列表内:
void action(int p[][10], int rowSize){ }
p看起来是一个二维数组,实际上形参是指向含有10个整数的数组的指针,相当于是以为数组。
我们回忆一下我们的main函数参数:
int main(int argc, char **argv){ }
或者
int main(int argc, char *argv[]){ }
第二个参数argv是含有指向C风格字符串的指针元素的数组。
5. 可变形参
其实数组我们就可以理解成是一种可变形参,具体数量可以通过实参来指定,但是这样有个局限性,就是所有的参数类型必须一致。
C++11新标准提供了两种可变形参,一种类似于数组,是标准库提供的initializer_list;还有一种可以类型不同的省略符形参,这种主要是用于C++和C函数的交互接口程序。
6. 总结
本文总结了数组作为函数形参的问题,以及数组指针,数组引用做形参的理解、多维数组作为函数的形参可能遇到的坑。