传参时,使用引用替换变量
第一个原因。。。
C++默认使用变量的方式传递对象。假如没有另外指定,函数的参数都是以实参的副本为初值,而函数的返回值也是一个复件。这些复件由对象的copy构造函数产出,这会让值传递成为昂贵的操作。来一个简单的例子
class Person
{
public:
Person();
virtual ~Person();
private:
std::string name;
std::string address;
};
class Student:public Person
{
public:
Student();
~Student();
private:
std::string schoolName;
std::string schoolAddress;
};
bool validateStudent(Student s); //以值的方式接受学生
Student plato; //柏拉图
bool platoIsOK = validateStudent(plato); //调用函数
简单地看,Student的复制构造函数在传入参数的时候会被调用,产生plato的副本初始化s,同样,返回的时候将s销毁,调用一次析构函数。也就是参数的传递成本是“一次Student的复制构造函数与一次student的析构函数”。但是,让我们解剖一下Student。
一个Student中有4个string对象,2个继承自Person,2个Student自己定义。而每个string也是一个实例,意味着每个Student初始化的时候string会被初始化时,就会初始化4个string,一个Person,每个Student销毁时,就会销毁四个string,一个Person,所以,真正的调用这个函数的时候,参数传递的成本是“六次构造函数与六次析构函数”!!!
想到这些,顿时间十分堪忧我们程序的性能。解决的方法就是,使用引用代替普通的值传递。当然,假如需要保护形参,我们还可以加上const限制。
bool validateStudent(const Student& s);
因为引用实际上是通过指针来实现的。引用一句话,"变量可以看成变量的别名”,也就是,使用引用是不会创建新的实例的。所以使用这种传递方式,效率高了很多,没有任何构造函数与析构函数被调用,因为没有创建任何新的实例。
第二个原因。。。
当然,使用引用的好处不仅仅是这些。使用引用的方式传递参数,还可以避免对象切割的问题。当派生类的对象传递给基类的对象时,调用的是基类的构造函数,派生类的那些特性将会被全部切割掉。试想一下有如下代码。
class Window
{
pubic:
virtual void Display()const; //显示窗口的名字
};
class WindowWithScrollBars:public Window
{
public:
virtual void Display()const; //显示窗口和内容
};
当我们用一个WindowWithScrollBars类型的对象初始化一个Window类型的对象后,在Window对象后调用Display,一切不值得惊讶又让人懊恼,我想显示一个窗口和内容但实际上它只给了我一个窗口的名字。学习了虚函数之后,我们知道这可以通过指针来解决,同样的,使用引用也能解决这个问题。
第三个原因。。。
编译器对内置类型与用户自定义类型的态度不太一致。比如,有些编译器会将一个double放入缓冲器中,但是却拒绝将一个只有double的class放入缓冲器中。而当我们使用引用的时候,引用是指针实现的,编译器当然会将指针放入缓冲器中。
第四个原因。。。
你认为你的class很小,直接使用变量传递与使用引用传递没有区别。但是,没有人能够保证class一直很小。就比如,class里面只有一个string,看起来真的很小,但不同的库的string大小相差非常大,当你认为class很小的时候,它实际已经无限膨胀了。再比如,假如没有使用引用,每次向class中增加内容的时候,你得小心翼翼增加的内容是否对你的性能会造成很大的影响。而假如你不希望有这种心理阴影,还是使用引用吧。
但是话说回来,又不是要求我们每时每刻都要使用引用来代替变量,比如,当我们使用STL库的时候。STL库习惯被设计成以变量的方式传递。比如传递其迭代器。所以,当我们使用STL库的时候,我们直接使用变量传递参数会优于引用。
总结一下
- 使用引用传递参数会比变量更高效
- 使用引用传递参数可以避免对象切割的问题
- 不是所有的时候都使用引用比时候变量好,比如当使用STL库或者函数对象的时候。