四、数组形参
1、数组形参的定义
如果要编写一个函数,输出int型数组的内容,可用下面三种方式指定数组形参:
void printValues(int *){ /*...........*/ }
void printValues(int [ ]){ /*........*/ }
void printValues( int [10]) { /*..........*/ }
虽然不能直接传递数组,但是函数的形参可以写成数组的形式。上面的三种定义是等价的,形参类型都是int *。
将数组形参直接定义为指针要比使用数组语法定义更好,定义为指针明确的表示 :函数操纵的是指向数组元素的指针,而不是数组本身,因此函数忽略数组长度,但形参定义中如果包含了数组的长度则特别容易引起误解。
2、形参的长度会引起误解
编译器忽略为任何数组形参指定指定的长度。根据数组的长度(权且这样说),可将函数printValues编写为:
void printValues( const int a[10])
{
//this code assumes array has 10 elements
//disaster if argument has fewer than 10 elements
for( size_t i =0 ; i != 10 ; ++i)
{
cout<<ia[ i ]<< endl ;
}
}
尽管上述代码假定所传递的数组至少含有10个元素,但C++语言没有任何机制强制实现这个假设。下面的调用都是合法的:
int main()
{
int i =0 , j[ 2 ] ={0,1};
printValues(&i); // ok : &i is int * ; probable run-time error
printValues( j ); // ok : j is converted to pointer to 0th
// element ; argument has type int * ;
// probable run-time error
return 0;
}
虽然编译没有问题,但是这两个调用都是错误的,可能导致运行失败。在这两个调用中,由于函数printValues假设传递进来的数组至少含有10个元素,因此造成数组内存的越界访问。程序的执行可能产生错误的输出,也可能崩溃。
注解:当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针类型和数组元素的类型是否匹配,而不会检查数组的长度。
3、数组实参
和其他类型一样,数组形参可定义为引用或非引用类型。
在传递数组时,实参是指向数组第一个元素的指针,形参复制的是这个指针的值,而不是数组元素本身。函数操纵的是指针的副本因此不会修改实参指针的值,然而,函数可通过指针改变他所指向的数组元素的值。
通过指针形参做的任何改变都在修改数组元素本身。(不需要修改数组形参的元素时,函数应该将形参定义为指向const对象的指针(const type *valuename))
void f( const int *){ /*......*/ }
3、通过引用传递数组((&arr)[ N ])
数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分,编译器检查数组实参的大小与形参的大小是否匹配:
void printValues( int (&arr)[10]) { /*......*/ } // &arr两边的圆括号是必需的,因为下标操作符具有更高的优先级
int main()
{
int k[10] = {0,1,2,3,4,5,6,7,8,9};
printValues(k); //ok : argument is an array of 10 ints
int i=0, j[2]={1,2};
int a[12]={1,2,3,4,5,6,7,8,9,0,11,12};
printValues( &i ); //error :argument is not an array of 10 ints
printValues( j ); //error : argument is not an array of 10 ints
printValues( a ); //error : argument is not an array of 10 ints
}
这个版本的printValues函数只严格接受含有10个int型数值的数组,由于形参是引用,在函数体中依赖数组的大小的安全的。
4、多维数组的传递(略)
5、传递给函数的数组的处理
任何处理数组的程序都要确保程序程序停留在数组的边界内。
有三种常见的编程技巧确保函数的操作不超出数组实参的边界。第一种方法在数组本身放置一个标记来检测数组的结束。C风格字符串就是采用这种方法的一个例子,它是一种字符数组,并且以空字符null作为结束的标记。
1.使用标准库规范
第二种方法是传递指向数组第一个和最后一个元素的的下一个位置 的指针:
void printValues( const int *beg ,const int *end )
{
while ( beg != end ){
cout<< *beg++ << endl;
}
}
int main()
{
int j[2] = { 0,1};
// ok: j is converted to pointer to 0th element in j
// j + 2 refers one past the end of j
printValues( j, j + 2 );
return 0;
}
调用这个版本的函数需要传递两个指针:一个指向要输出的第一个元素,另一个则指向最后一个元素的下一个位置,要正确计算指针,使它们标记一段有效的元素范围,程序就会安全。
2.显示传递表示数组大小的形参
第三种方法是将第二个形参定义为表示数组的大小,这种用法在C程序和标准化之前的程序中十分普遍。
void printValues( const int ia[], size_t size)
{
for ( size_t i = 0; i != size; ++i){
cout<< ia[ i ]<< endl;
}
}
int main( )
{
int j[ ] = {0,1} ; // int array of size 2
printValues( j , sizeof( j )/sizeof( *j ));
return 0;
}