"数组引用"以避免"数组降阶"(本文曾贴于VCKBASE\C++论坛)
受[hpho]的一段模板函数的启发,特写此文,如有雷同,实在遗憾。
数组降阶是个讨厌的事,这在C语言中是个无法解决的问题,先看一段代码,了解什么是"数组降阶"
#include <IOSTREAM>
using namespace std;
void Test( char array[20] )
{
cout << sizeof(array) << endl; // 输出 4
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl; // 输出 20
Test( array );
}
为什么同样申明的array一个输出20一个输出4?这是因为void Test( char array[20] )中的array被降阶处理了,void Test( char array[20] )等同于void Test( char array[] ),也等同于void Test( char* const array ),如果你BT(开玩笑),它也等同于void Test( char array[999] )。
就是说
void Test( char array[20] )
{
cout << sizeof(array) << endl;
}
被降成
void Test( char* const array )
{
cout << sizeof(array) << endl; // 既然是char*,当然输出4
}
这样一来问题大了,你完全可以定义一个不足20个元素的数组,然后传给Test,坐等程序崩溃。在一些要求较高的场合就不能使用数组做参数,真TMD心有不甘。
那么在C语言中怎样解决这个问题?
没办法,应该说没有好办法。a:做个结构,其中仅一个char array[20],然后用这个结构指针代替char array[20]。可见这是个很繁琐的办法,且不直观;b:在Test内部使用_msize来计算array长度。这更不行,首先它使得错误的发现被推迟到运行期,而不是编译期,其次_msize长度/元素大小>=array长度,也就是说就是new char[19]和new array[20]分配的大小是一样的,这样一来,虽不至于导致程序崩溃,但运算结果却不正确。
感谢[hpho],受其启发,C++中有C所没有的"引用",但数组引用是怎样申明的呢?经过几番试验,Look
#include <IOSTREAM>
using namespace std;
void Test( char (&array)[20] ) // 是不是很像 char *p[20] 和 char (*p)[20] 的区别?
{
cout << sizeof(array) << endl;
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl;
Test( array );
}
在 C++中,数组永远不会按值传递,它是传递第一个元素,准确地说是第 0个 的指针。
例如,如下声明 :
void putValues( int[ 10 ] );
被编译器视为
void putValues( int* );
数组的长度与参数声明无关,因此,下列三个声明是等价的:
// 三个等价的 putValues()声明
void putValues( int* );
void putValues( int[] );
void putValues( int[ 10 ] );
因为数组被传递为指针 所以这对程序员有两个含义:
1. 在被调函数内对参数数组的改变将被应用到数组实参上而不是本地拷贝上,当用作实参的数组必须保持不变时,程序员需要保留原始数组的拷贝函数可以通过把参数类型声明为 const 来表明不希望改变数组元素。
void putValues( const int[ 10 ] );
2. 数组长度不是参数类型的一部分,函数不知道传递给它的数组的实际长度,编泽器也不知道,当编译器对实参类型进行参数类型检查时,并不检查数组的长度。例如:
void putValues( int[ 10 ] ); // 视为 int*
int main() {
int i, j[ 2 ];
putValues( &i ); // ok: &i 是 int*; 潜在的运行错误
putValues( j ); // ok: j 被转换成第 0 个元素的指针
// 实参类型为 int*: 潜在的运行错误
return 0;
}
参数的类型检查只能保证putValues()的两次调用都提供了int*型的实参,类型检查不能检验实参是一个 10元素的数组 。习惯上, C风格字符串是字符的数组,它用一个空字符编码作为结尾。但是所有其他类型,包括希望处理内含空字符的字符数组必须以某种方式在向函数传递实参时使其知道它的长度。
一种常见的机制是提供一个含有数组长度的额外参数。例如:
void putValues( int[], int size );
int main() {
int i, j[ 2 ];
putValues( &i, 1 );
putValues( j, 2 );
return 0;
}
另外一种机制是将参数声明为数组的引用
当参数是一个数组类型的引用时,数组长度成为参数和实参类型的一部分,编译器检查数组实参的长度与在函数参数类型中指定的长度是否匹配。
// 参数为 10 个 int 的数组
// parameter is a reference to an array of 10 ints
void putValues( int (&arr)[10] );//不能写成&arr[10],因为下标操作符的优先级较高
int main() {
int i, j[ 2 ];
putValues( i ); // 错误: 实参不是 10 个 int 的数组
putValues( j ); // 错误: 实参不是 10 个 int 的数组
return 0;
}
"数组引用"以避免"数组降阶"(本文:http://blog.vckbase.com/bruceteen/archive/2004/05/20/232.aspx)
受[hpho]的一段模板函数的启发,特写此文,如有雷同,实在遗憾。
数组降阶是个讨厌的事,这在C语言中是个无法解决的问题,先看一段代码,了解什么是"数组降阶"
#include <IOSTREAM>
using namespace std;
void Test( char array[20] )
{
cout << sizeof(array) << endl; // 输出 4
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl; // 输出 20
Test( array );
}
为什么同样申明的array一个输出20一个输出4?这是因为void Test( char array[20] )中的array被降阶处理了,void Test( char array[20] )等同于void Test( char array[] ),也等同于void Test( char* const array ),如果你BT(开玩笑),它也等同于void Test( char array[999] )。
就是说
void Test( char array[20] )
{
cout << sizeof(array) << endl;
}
被降成
void Test( char* const array )
{
cout << sizeof(array) << endl; // 既然是char*,当然输出4
}
这样一来问题大了,你完全可以定义一个不足20个元素的数组,然后传给Test,坐等程序崩溃。在一些要求较高的场合就不能使用数组做参数,真TMD心有不甘。
那么在C语言中怎样解决这个问题?
没办法,应该说没有好办法。a:做个结构,其中仅一个char array[20],然后用这个结构指针代替char array[20]。可见这是个很繁琐的办法,且不直观;b:在Test内部使用_msize来计算array长度。这更不行,首先它使得错误的发现被推迟到运行期,而不是编译期,其次_msize长度/元素大小>=array长度,也就是说就是new char[19]和new array[20]分配的大小是一样的,这样一来,虽不至于导致程序崩溃,但运算结果却不正确。
感谢[hpho],受其启发,C++中有C所没有的"引用",但数组引用是怎样申明的呢?经过几番试验,Look
#include <IOSTREAM>
using namespace std;
void Test( char (&array)[20] ) // 是不是很像 char *p[20] 和 char (*p)[20] 的区别?
{
cout << sizeof(array) << endl;
}
int main( void )
{
char array[20] = { 0 };
cout << sizeof(array) << endl;
Test( array );
}
本文来自:http://blog.vckbase.com/smileonce/archive/2004/11/05/1295.aspx
去年,周星星大哥曾经在VCKBASE/C++论坛发表过一篇文章《"数组引用"以避免"数组降阶"》*1,当时我不能深入理解这种用法的含义;时隔一年,我的知识有几经锤炼,终于对此文章渐有所悟,所以把吾所知作想详细道来,竟也成了一篇文章。希望本文能对新手有所启迪,同时也希望大家发现本文中的疏漏之处后不吝留言指教。
故事起源于周星星大哥给出的两个Demo,为了节省地方,我把两个Demo合二为一,也能说明同样的问题:
#include using namespace std;void Foo1(int arr[100]){ cout << "pass by pointer: " << sizeof(arr) << endl;}void Foo2(int (&arr)[100]){ cout << "pass by reference: " << sizeof(arr) << endl;}void main(){ int a[100]; cout << "In main function : " << sizeof(a) << endl; Foo1(a); Foo2(a); }
其运行结果如下:
In main function : 400
pass by pointer: 4
pass by reference: 400
这段代码说明了,如果数组形参是数组名形式(或者指针形式,下文讨论)时,使用sizeof运算符,将得不到原来数组的长度;如果用传递原数组引用的方法,则没有问题。
这段代码的确很难理解,因为这短短的十几行涉及到了形参与实参的关系、数组名和指针的关系、引用的意义、声名和表达式的关系这4大类问题,只要有1条理解不透、或者理解不正确,就理解不透上面的这段代码。本文也就从这4个问题入手,把这4个问题首先解决掉,然后再探讨上面的这段代码。虽然这样看来很是繁复,但是我认为从根上入手来理解、学习,是条似远实近的道路。
一、函数形参和实参的关系
void Foo(int a);Foo(10);
这里的a叫做形式参数(