-
传值调用为什么会造成代码执行效率低?因为传值调用的时候,是传递对象的一个副本,因此是调用对象的拷贝构造函数将对象复制一份,然后传递给函数。效率比较低
- 下面来看一个例子:有一个基类(Person)与一个派生类(Student),并且定义一个函数(validateStudent),参数接受一个Student对象(传值调用)
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);
- 现在我们有如下的代码:
int main()
{
Student plato; //定义Student对象
validateStudent(plato); //传值调用
return 0;
}
- 现在我们来考虑一下这个传值调用的过程一共发生了哪些事情:
- ①执行6次构造函数:plato传入进函数的时候,需要调用1次Student的构造函数,Student构造函数执行前需要先构造Person ,因此还会调用1次Person类的构造函数。Person和Student两个类中共有4个string成员变量,因此还需要调用string的构造函数4次
- ②执行6次析构函数:当函数执行完成之后,传入validateStudent函数中的plato副本还需要执行析构函数,那么执行析构函数的时候分别要对应执行6次析构函数(Student+Person+4个string成员变量)
二、传值调用会造成对象切割/截断问题替代方案:以const引用传递调用
- 一种替代传值调用的方式是将对象以const引用的方式传递给函数:
- const:如果不想对象在函数被修改,那么就以const修饰
- 引用:引用在编译器底层为指针形式。因此使用引用把对象传递给函数,是直接将对象传递给函数,而不是将对象的副本传递给函数(避免了构造函数的调用以及析构函数的调用)
- 现在我们修改上面的validateStudent函数:
bool validateStudent(const Student& s); int main() { Student plato; //定义Student对象 validateStudent(plato); //const引用调用 return 0; }
- 对象切割/截断:如果将对象直接以传值方式调用,会造成对象的切割/截断问题。这种现象一般发生在函数的参数为基类类型,但是却将派生类对象传递给函数
- 关于对象截断的例子还可以参阅这篇文章中的标题七:h6ttps://blog.csdn.net/qq_41453285/article/details/10310043
- 下面来看一个例子:有一个基类(Window)与一个派生类(WindowWithScrollBars),并且定义一个函数(printNameAndDisplay),参数接受一个Window对象(传值调用)
class Window
{
public:
std::string name()const;
virtual void display()const;
};
class WindowsWithScrollBars :public Window
{
public:
virtual void display()const;
};
void printNameAndDisplay(Window w)
{
std::cout << w.name();
w.display();
}
- 现在我们有如下的代码:
int main()
{
WindowsWithScrollBars wwsb;
printNameAndDisplay(wwsb); //WindowsWithScrollBars对象会被截断
return 0;
}
- 现在我们来考虑一下这个传值调用的过程一共发生了哪些事情:
- 因为printNameAndDisplay函数的参数为Window类型,所以即使我们传入的是WindowsWithScrollBars类型的对象,那么这个对象会被截断,只取WindowsWithScrollBars对象中属于基类(Window)的内容,然后传递给函数
- 因此被截断之后,不论如何调用,printNameAndDisplay函数中调用的display函数总是Window的display虚函数,不会是WindowsWithScrollBars中的display虚函数。因为多态只会发生在基类指针/引用指向于派生类的情况下,此处没有指针/引用
三、内置类型建议传值调用替代方案:以const引用传递调用
- 如果我们将上述函数的参数改为const引用方式的话,那么传入函数的对象将与传入的对象类型有关
- 现在我们修改上面的validateStudent函数:
void printNameAndDisplay(const Window& w) { std::cout << w.name(); w.display(); } int main() { WindowsWithScrollBars wwsb; printNameAndDisplay(wwsb); //传入的就是WindowsWithScrollBars类型的对象 return 0; }
- C++编译器的底层,是把引用以指针的形式实现,因此引用传递就是指针的传递
- 如果你使用的对象属于内置类型(例如int),内置类型都相当小,传值调用往往比引用传递的效率高些
- 这条规则在STL的迭代器和函数对象中都被广泛引用
- 尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题
- 以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,传值调用往往比较合适