# Effective C++读书笔记(20): 多用常量引用传递 **守则20: 多用常量引用传递,少用值传递** > "Prefer pass-by-reference-to-const to pass-by-value" ------ ***本章关键词: 常量引用传递,值传递,拷贝,对象切割\*** ------ C++继承了C默认传递方式为值传递的特性,那么对于一个值传递的函数,它的参数是被传进来变量的**拷贝**初始化的。拷贝是由拷贝构造函数生成的,而拷贝多是一个不经济的操作,至于为什么我们来看下面的例子。我们定义一个继承层次: ```text class Person{ public: Person(); virtual ~Person(); //见第7章为什么使用虚函数 .... private: std::string name; std::string address; }; class Student : public Person{ public: Student(); virtual ~Student(); ... private: std::string schoolName; std::string schoolAddress; }; ``` 现有如下函数使用值传递: ```text bool validateStudent(Student s); //值传递 Student plato; bool platoOK = validateStudent(plato); ``` 来看当我们使用值传递时会发生什么: - Student类的拷贝构造函数调用,用来初始化参数s - 本地参数s在函数返回时被销毁 现在我们再往细节分析一下,Student类含有两个std::string对象,它又是继承自Person类,又有两个std::string对象。这就意味着,Student对象使用值传递会带来: - 调用一次Student类的拷贝构造函数 - 调用一次Person类的拷贝构造函数 - 调用4次std::string类的拷贝构造函数 - 同样过程适用于返回时的析构过程 一共是6次拷贝构造函数,同时也对应了6次析构函数,成本果然很大吧! 我们当然希望函数的本地参数被安全地初始化和销毁,但我们同样希望避免这些不必要的开销,那么答案就是使用**常量引用传递**: ```text bool validateStudent(const Student& s); ``` 使用引用意味着直接在变量本身上进行读写,从而不用产生拷贝,那么如上繁杂的构造和析构过程也就省略掉了。如果我们不希望对它本身进行修改,就需要加const修饰符,让这个参数只读。 ------ 使用常量引用传递也巧妙避免了**对象切割问题**(object slicing problem)。如果有一个函数的参数类型是父类,这时一个子类对象被值传递进这个函数时,参数初始化所调用的构造函数是父类的,子类所衍生的特性就全部被"切割"掉了,在这个函数里你就只剩下一个父类对象,惊喜不惊喜? 我们举一个例子: ```text class Window{ //定义一个图形操作界面的窗口类 public: ... std::string name() const; //返回当前窗口名称 virtual void display() const; //显示窗口和内容 }; class WindowWithScrollBar : public Window{ public: ... virtual void display() const; }; ``` display()函数是虚函数,这就意味着两个类对于这个函数有不同的实现。如果你要写一个函数,先打印出窗口的名字,然后显示窗口,下面就是错误的写法: ```text void printNameAndDisplay(Window w){ //值传递 std::cout<