【C++学习点滴】关于参数传递的问题
关于函数参数:
注意:函数参数的传递方法对效率很有影响,特别是对于大型的对象参数,一般分按值传递,指针传递,引用传递,按值传递会有一个拷贝操作,所以会影响效率。
不过按值传递由于会有拷贝,所以不影响原来的值,比较安全,而引用和指针都有可能不小心修改原来的值,所以最好使用const。
一个问题?何时使用指针参数,何时使用引用参数?
引用必须被初始化为指向一个对象 一旦初始化了 它就不能再 指向其他对象 指针可以指向一系列不同的对象也可以什么都不指向
因为指针可能指向一个对象或没有任何对象 所以函数在确定指针实际指向一个有效的 对象之前不能安全地解引用 dereference
因为指针可能指向一个对象或没有任何对象 所以函数在确定指针实际指向一个有效的 对象之前不能安全地解引用 dereference
一个指针 例如
class X;
void manip( X *px )
{
// 在解引用指针之前确信它非 0
if ( px != 0 )
// 解引用指针
}
另一方面 对于引用参数 函数不需要保证它指向一个对象 引用必须指向一个对象,甚至在我们不希望这样时也是如此 例如
class Type { };
void operate( const Type& p1, const Type& p2 );
int main() {
Type obj1;
// 设置 obj1 为某个值
// 错误 : 引用参数的实参不能为 0
Type obj2 = operate( obj1, 0 );
}
如果一个参数可能在函数中指向不同的对象 或者这个参数可能不指向任何对象 则必须使用指针参数。
引用参数的一个重要用法是 它允许我们在有效地实现重载操作符的同时 还能保证用法的直观性 。 让我们从下面的例子开始 它使用了Matrix
类类型 我们想支持两个Matrix 类对象的加法和赋值操作符 使它们的用法同内置类型一样 自然
Matrix a, b, c;
c = a + b;
Matrix 类对象的加法和赋值操作符用重载操作符来实现 被重载的操作符是一个带有特殊名字的函数 在加法操作符的例子中 函数名是operator+
class X;
void manip( X *px )
{
// 在解引用指针之前确信它非 0
if ( px != 0 )
// 解引用指针
}
另一方面 对于引用参数 函数不需要保证它指向一个对象 引用必须指向一个对象,甚至在我们不希望这样时也是如此 例如
class Type { };
void operate( const Type& p1, const Type& p2 );
int main() {
Type obj1;
// 设置 obj1 为某个值
// 错误 : 引用参数的实参不能为 0
Type obj2 = operate( obj1, 0 );
}
如果一个参数可能在函数中指向不同的对象 或者这个参数可能不指向任何对象 则必须使用指针参数。
引用参数的一个重要用法是 它允许我们在有效地实现重载操作符的同时 还能保证用法的直观性 。 让我们从下面的例子开始 它使用了Matrix
类类型 我们想支持两个Matrix 类对象的加法和赋值操作符 使它们的用法同内置类型一样 自然
Matrix a, b, c;
c = a + b;
Matrix 类对象的加法和赋值操作符用重载操作符来实现 被重载的操作符是一个带有特殊名字的函数 在加法操作符的例子中 函数名是operator+
让我们为这个重载操作符提供一个定义
Matrix // 加法返回一个 Matrix对象
operator+( // 重载操作符的名字
Matrix m1, // 操作符左操作数的类型
Matrix m2 // 操作符右操作数的类型
)
{
Matrix result;
// do the computation in result
return result;
}
Matrix // 加法返回一个 Matrix对象
operator+( // 重载操作符的名字
Matrix m1, // 操作符左操作数的类型
Matrix m2 // 操作符右操作数的类型
)
{
Matrix result;
// do the computation in result
return result;
}
该实现支持两个Matrix 对象的加法 如
a + b
但不幸的是 它的效率低得让人难以接受 注意 operator+()的参数不是引用 这意味着 operator+()的实参是按值传递的 两个Matrix 对象a 和b 的内容被拷贝到opertor+()函数的参数区中 因为Matrix 类对象非常大 分配这样一个对象并把它拷贝到函数参数区中的时间和空间开销高得让人难以接受
为了提高我们的操作符函数的效率 假定我们决定把参数声明为指针 下面是对operator+()新的实现代码
// 使用指针参数重新实现
Matrix operator+( Matrix *m1, Matrix *m2 )
{
Matrix result;
// 在 result 中计算
return result;
}
但是 这个实现代码有这样的问题 虽然我们获得了效率 但是 它是以放弃加法操作符用法的直观性为代价的 现在指针参数要求我们传递地址作为实参 它们指向我们希望做加法操作的Matrix 对象 现在 我们的加法操作必须如下编程
&a + &b; // 不太好, 但也不是不可能
但是 这比较难看 而且可能引起一些程序员抱怨用户接口不友好 在一个复合表达式中加三个对象变得很困难
// 喔 !这无法工作
// &a + &b 的返回类型是 Matrix 对象
&a + &b + &c;
为了使在指针方案下三个对象的加法能够很好地实现 程序必须这样写
// ok: 这样能行, 但是
&( &a + &b ) + &c;
当然 没有人希望那样写 引用参数提供了我们需要的方案 当参数是引用时 函数接收到的是实参的左值而不是值的拷贝 因为函数知道实参在内存的什么位置 所以实参值没有被拷贝到函数的参数区 引用参数的实参是Matrix 对象本身 这允许我们像对内置数据类型的对象使用加法操作符一样自然地使用加法操作符
下面是Matrix 类的重载加法操作符的修订版本
// 使用引用参数的新实现
Matrix operator+( const Matrix &m1, const Matrix &m2 )
{
Matrix result;
// 在 result 中进行计算
return result;
}
该实现支持如下形式的Matrix 对象的加法
a + b + c
a + b
但不幸的是 它的效率低得让人难以接受 注意 operator+()的参数不是引用 这意味着 operator+()的实参是按值传递的 两个Matrix 对象a 和b 的内容被拷贝到opertor+()函数的参数区中 因为Matrix 类对象非常大 分配这样一个对象并把它拷贝到函数参数区中的时间和空间开销高得让人难以接受
为了提高我们的操作符函数的效率 假定我们决定把参数声明为指针 下面是对operator+()新的实现代码
// 使用指针参数重新实现
Matrix operator+( Matrix *m1, Matrix *m2 )
{
Matrix result;
// 在 result 中计算
return result;
}
但是 这个实现代码有这样的问题 虽然我们获得了效率 但是 它是以放弃加法操作符用法的直观性为代价的 现在指针参数要求我们传递地址作为实参 它们指向我们希望做加法操作的Matrix 对象 现在 我们的加法操作必须如下编程
&a + &b; // 不太好, 但也不是不可能
但是 这比较难看 而且可能引起一些程序员抱怨用户接口不友好 在一个复合表达式中加三个对象变得很困难
// 喔 !这无法工作
// &a + &b 的返回类型是 Matrix 对象
&a + &b + &c;
为了使在指针方案下三个对象的加法能够很好地实现 程序必须这样写
// ok: 这样能行, 但是
&( &a + &b ) + &c;
当然 没有人希望那样写 引用参数提供了我们需要的方案 当参数是引用时 函数接收到的是实参的左值而不是值的拷贝 因为函数知道实参在内存的什么位置 所以实参值没有被拷贝到函数的参数区 引用参数的实参是Matrix 对象本身 这允许我们像对内置数据类型的对象使用加法操作符一样自然地使用加法操作符
下面是Matrix 类的重载加法操作符的修订版本
// 使用引用参数的新实现
Matrix operator+( const Matrix &m1, const Matrix &m2 )
{
Matrix result;
// 在 result 中进行计算
return result;
}
该实现支持如下形式的Matrix 对象的加法
a + b + c
数组参数:
在C++中 数组永远不会按值传递 它是传递第一个元素 准确地说是第0 个 的指针
例如 如下声明
void putValues( int[ 10 ] );
被编译器视为
void putValues( int* );
数组的长度与参数声明无关 因此 下列三个声明是等价的
// 三个等价的 putValues()声明
void putValues( int* );
void putValues( int[] );
void putValues( int[ 10 ] );
例如 如下声明
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.数组长度不是参数类型的一部分 函数不知道传递给它的数组的实际长度 编泽器
也不知道 当编译器对实参类型进行参数类型检查时 并不检查数组的长度 例如
1.在被调函数内对参数数组的改变将被应用到数组实参上而不是本地拷贝上 当用作
实参的数组必须保持不变时 程序员需要保留原始数组的拷贝 函数可以通过把参
数类型声明为const 来表明不希望改变数组元素
void putValues( const int[ 10 ] );
2.数组长度不是参数类型的一部分 函数不知道传递给它的数组的实际长度 编泽器
也不知道 当编译器对实参类型进行参数类型检查时 并不检查数组的长度 例如
void putValues( int[ 10 ] ); // 视为 int*
所以:指针表示法:
void putValues( int *ia, int sz )
另外一种机制是将参数声明为数组的引用 当参数是一个数组类型的引用时 数组长度成为参数和实参类型的一部分 编译器检查数组实参的长度与在函数参数类型中指定的长度是否匹配
// 参数为 10个 int的数组
// parameter is a reference to an array of 10 ints
// 参数为 10个 int的数组
// parameter is a reference to an array of 10 ints
void putValues( int (&arr)[10] );