数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:不允许拷贝数组以及使用数组时(通常)会将其转换成指针。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式:
//尽管形式不同,但这三个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函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。
因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息。管理指针形参有三种常用的技术。
使用标记指定数组长度
管理数组实参的第一种方法是要求数组本身包含一个结束标记,使用这种方法的典型示例是C风格字符串。C风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符。函数在处理C风格字符串时遇到空字符停止:
void print(const char * cp)
{
if(cp)
while(*cp)
cout<<*cp++;
}
这种方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况,但是对于像int这样所有取值都是合法值得数据就不太高效了。
使用标准库规范
管理数组实参的第二种技术是传递指向数组首元素和尾后元素的指针,这种方法受到了标准库技术的启发。使用该方法,我们可以按照如下形式输出元素的内容:
void print(const int* beg,const int *end)
{
//输出beg到end之间(不含end)的所有元素
while(beg!=end)
cout<<*beg++<<endl;
}
while循环使用解引用运算符和后置递增运算符输出当前元素并在数组内将beg向前移动一个元素,当beg和end相等时结束循环。
为了调用这个函数,我们需要传入两个指针:一个指向要输出的首元素,另一个指向尾元素的下一个位置:
int j[2]={0,1};
//j转换成指向它首元素的指针
//第二个实参是指向j的尾后元素的指针
print(begin(j),end(j)); //begin和end函数
只要调用者能正确地计算指针所指的位置,那么上述代码就是安全的。在这里,我们使用标准库begin和end函数。
显示传递一个表示数组大小的形参
第三种管理数组实参的方法是专门定义一个表示数组大小的形参,在C程序和过去的C++程序中常常使用这种方法。使用该方法,可以讲print函数重写成如下形式:
//const int ia[]等价于const int* ia
//size表示数组的大小,将它显式地传给函数用于控制对ia元素的访问
void print(const int ia[],size_t size)
{
for(size_t i=0;i!=size;++i)
cout<<ia[i]<<endl;
}
这个版本的程序通过形参size的值确定要输出多少个元素,调用print函数时必须传入这个表示数组大小的值:
int j[]={0,1};
print(j,end(j)-begin(j));
只要传递给函数的size值不超过数组实际的大小,函数就是安全的。
数组引用形参
C++语言允许将变量定义成数组的引用,基于同样的到道理,形参也可以使数组的引用。此时,引用参数绑定到对应的实参上,也就是绑定到数组上:
//正确:形参是数组的引用,维度是类型的一部分
void print(int (&arr)[10])
{
for(auto elem:arr)
cout<<elem<<endl;
}
&arr两端的括号必不可少:
f(int &arr[10]) //错误:将arr声明成了引用的数组
f(int (&arr)[10]) //正确:arr是具有10个整数的整型数组的引用
因为数组的大小是构成数组类型的一部分,所以只要不超过维度,在函数体内就可以放心地使用数组。但是,这一用法也无形中限制了print函数的可用性,我们只能将函数作用于大小为10的数组:
int i=0,j[2]={0,1};
int k[10]={0,1,2,3,4,5,6,7,8,9};
print(&i); //错误:实参不是含有10个整数的数组
print(j); //错误:实参不是含有10个整数的数组
print(k); //正确:实参是含有10个整数的数组
传递多维数组
我们曾经介绍过,C++语言中实际上没有真正的多维数组,所谓多维数组其实是数组的数组。
和所有数组一样,当将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为我们处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。数组第二维大大小都是数组类型的一部分,不能省略:
//matrix指向数组的元素,该数组的元素是由10个整数构成的数组
void print(int (*matrix)[10],int rowSize){/*..*/}
上述语句将matrix声明成指向含有10个整数的数组的指针。
再一次强调,*matrix两端的括号必不可少
int *matrix[10]; //10个指针构成的数组
int (*matrix)[10]; //指向含有10个整数的数组的指针
我们也可以使用数组的语法定义函数,此时编译器会一如既往地忽略掉第一个维度,所以最不要把它包括在形参列表内:
//等价定义
void print(int matrix[][10],int rowSize) {/*...*/}
matrix的声明看起来就像一个二维数组,实际上形参是指向含有10个整数的数组的指针。