详谈C++函数参数传递
说明:
- 在C++中,依旧可以使用C语言的Pass By Pointer(指针传递)方式,但是在C++建议使用Pass By Reference替代Pass By Pointer,所以没有考虑Pass By pointer;
- 有关三种传递方式的介绍,可以参照C++中的值传递、引用传递和指针传递;
- 本文只探讨Pass By Value , Pass By Reference两种传递方式。
0x01 参数传递
首先我们要知道的是每次函数调用时都会重新创建它的形参,并用传入的实参对形参进行初始化:
// 定义函数
int great(int a, int b) {
return a > b ? a : b;
}
// 函数调用
great(3, 5); // 调用1
great(10, 20); // 调用2
上面的函数有两个形参a
和b
,在函数的调用1
中a
被初始化为3
, b
被初始化为5
;在函数的调用2
中a
被初始化为10
, b
被初始化为20
。
0x02 Pass By Value
当函数形参的传递方式为Pass By Value时,初始值被拷贝给变量,此时对形参的改动不会影响实参:
#include <iostream>
using std::cout;
using std:: endl;
void plus(int a) {
a++;
}
int main() {
int value = 10;
plus(value);
cout << value << endl; // 10
return 0;
}
上面的程序在plus
函数中更改a
的值不会影响value
的值。
0x03 Pass By Reference
当函数形参的传递方式为Pass By Reference时 ,对形参的更改会影响实参:
#include <iostream>
using std::cout;
using std:: endl;
void plus(int &a) {
a++;
}
int main() {
int value = 10;
plus(value);
cout << value << endl; // 11
return 0;
}
使用引用可以避免拷贝,提升效率
拷贝很大的对象时性能可能会很低,甚至某些类型(包括IO类型在内)根本不支持拷贝操作,对于不支持拷贝或者性能很低时,函数只能通过Pass By Reference形式进行传递:
// 比较两个字符串
bool isLonger(const std::string& s1, const std::string& s2) {
return s1.size() > s2.size();
}
上面的函数是为了比较两个string对象的长度,因为string的长度可能非常长(可能是一本书),所以拷贝过程可能狠低效,所以使用Pass By Reference避免拷贝动作的发生。
0x04 const形参(常量引用)
当const
和形参结合时,也有一些需要注意的点:
- 形参的顶层const会被忽略,例如下面的函数会发生重定义报错;
void func(int a) {...}
void func(const int a) {...}
虽然C++支持函数重载,但是当使用func(5)
调用时,编译器调用哪个函数都是可以的,会引发二义性,所以顶层const
并不属于函数的重载条件。
- 引用形参与const
形参的初始化方式与变量的初始化方式是一样的,我们可以使用非常量初始化一个底层const对象,但是反过来却不行,例如对于下面的函数:
bool isLess(const int& a, const int& b) {
return a < b;
}
有两个常量a
和b
;两个变量c
和d
:
const int a = 1;
const int b = 2;
int c = 3;
int d = 4;
下面的函数调用都是合法的:
isLess(a, b);
isLess(c, d);
但是当函数定义为
bool isLess(int& a, int& b) {
return a < b;
}
下面的调用是合法的
isLess(c, d);
但是下面的调用就会发生错误
isLess(a, b);
所以在设计函数时,尽量使用常量引用。
0x05 类中的成员函数
假设我们设计一个复数(Complex)类,下面是类图:
下面是实现代码:
class Complex {
private:
double re;
double im;
public:
Complex() {}
Complex(double re, double im) : re(re), im(im) {}
double getRe() const {
return re;
}
void setRe(double re) {
Complex::re = re;
}
double getIm() const {
return im;
}
void setIm(double im) {
Complex::im = im;
}
};
上面代码的两个函数getRe()
和getIm()
设定为const
,这样做主要是由两个原因:
-
第一个是因为这两个函数没有更改对象的属性,所以在函数的参数后面加上
const
关键字,但是要注意,这种写法只能应用于类中的成员函数,在外部的函数不能这样书写。 -
第二个也是为了避免常量对象调用这个函数,例如:
声明一个复数对象,实部为
3
,虚部为2
,因为不打算更改这个复数的实部和虚部,所以将其声明为常量对象,所以在一个常量对象调用上面的getIm()
和getRe()
函数时,如果这两个函数不加const
会引发错误,也就是说,这个类设计的并不理想。const Complex c(3, 2); // 如果类中的成员函数不声明为const,会导致错误 c.getRe(); c.getIm();
总结
- Pass By Value 形参的改变不会影响实参
- Pass By Reference形参的改变会影响实参,并且会避免拷贝的发生,提高效率
- 在设计函数时,尽量使用常量引用
- 类中的成员函数,不更改对象属性的函数建议定义为
const