数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别 是:不允许拷贝数组以及使用数组时(通常)会将其转换成 指针。因为不能拷贝数组,所以我们无法以值传递的方式使用 数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的 是指向数组首元素的指针。
尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式:
尽管表现形式不同,但上面的三个函数是等价的:每个函数的唯一形参都是const int* 类型的。当编译器处理对print函数的调用时,只检查传入的参数是否是const int* 类型://尽管形式不同,但这三个print函数是等价的
//每个函数都有一个const int*类型的形参 void print(const int*);
void print (const int []) ; //可以看出来,函数的意图是作用于一个教组
void print (const int [10]); //这里的维度表示我们期望数组含有多少元素,实际不一定
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) //若cp不是一个空指针
while (*cp) //只要指针所指的字符不是空字符
cout << *cp++; //输出当前字符并将指针向前移动一个位置
这种方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况,但是对于像 int这样所有取值都是合法值的数据就不太有效了。
使用标准库规范
管理数组实参的第二种技术是传递指向数组首元素和尾后元素的指针,这种方法受到 了标准库技术的启发,关于其细节将在第II部分详细介绍。使用该方法,我们可以按照如 下形式输出元素内容:
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 }; //大小为2的整型数组
print(j, end(j) - begin(j));
只要传递给函数的size值不超过数组实际的大小,函数就是安全的。
数组形参和const
我们的三个print函数都把数组形参定义成了指向const的指针关于引用的讨论同样适用于指针。当函数不需要对数组元素执行写操作的时候,数组形参应该是指向const的指针。只有当函数确实要改变元素值 的时候,才把形参定义成指向非常量的指针。
数组引用形参
C++语言允许将变量定义成数组的引用,基于N样的道理, 形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,也就是绑定到数组上:
//正确:形参是數组的引用,维度是类型的一部分
void print(int (&arr)[10])
{
for (auto elem : arr)//范围for语句
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,l,2,3,4,5,6,7,8,9};
print (&i) ; //错误:实参不是含有10个整教的数组
print (j ) ; //错误:实参不是含有10个整数的数组
print (k) ; //正确:实参是含有10个整数的数组