IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《Effective C++》学习笔记(条款20:宁以 pass-by-reference-to-const 替换 pass-by-value) -> 正文阅读

[C++知识库]《Effective C++》学习笔记(条款20:宁以 pass-by-reference-to-const 替换 pass-by-value)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

通常情况下,C++是以值传递(pass-by-value)的方式传递对象至函数,除非你另外指定,否在函数参数都是以实参的复件为初值,而调用函数返回的对象也是函数返回值的一个复件。这些复件是由对象的拷贝构造函数产出的,这使得值传递的成本很高(费时)。

class Person{
public:
    Person();
    virtual ~Person();  //见条款7为什么使用虚函数
    ....
private:
    string name;
    string address;
};

class Student : public Person{
public:
    Student();
    virtual ~Student();
    ...
private:
    string schoolName;
    string schoolAddress;
};

bool validateStudent(Student s);  			//值传递

int main()
{
    Student stu;
    bool platoOK = validateStudent(stu);	//调用函数
    return 0;
}

当上述代码中的 validateStudent(stu) 函数被调用时,Student 的拷贝构造函数会被调用,以 stu 为参数将 s 初始化;而函数结束后,当 validateStudent() 返回 ,s会被销毁。因此,对函数而言,参数的传递成本时“一次 Student 拷贝构造函数调用,加上一次 Student 析构函数调用”。

但这还不是全部,Student 对象内还有两个 string 对象,所有每次构造一个 Student 对象也就构造了两个 string 对象。此外 Student 继承自 Person ,所以每次构造 Student 对象也必须构造出一个 Person 对象,而 Person 对象中又有两个 string 对象,因此每构造一个Person 对象又需要构造两个 string 对象。因此,以值传递方式传递一个 Student 对象会导致:StudentPerson 的拷贝构造函数各调用一次, string 的拷贝构造函数调用了四次。当函数结束时,又需要释放 Student对象,每一个构造函数调用都对应了一个析构函数的调用。因此,最终成本是:六次拷贝构造函数和六次析构函数

我们得保证函数的参数能够安全地初始化和销毁,但同样希望避免这些不必要的开销,那么就得使用常量引用传递(pass-by-reference-to-const)。

bool validateStudent(const Student& s); 

使用引用传递表示在实参本身上进行读写,从而不用进行拷贝复件,那么拷贝构造函数和析构函数的调用也就省略了。若我们不想实参本身被修改,就需要加上 const 关键字,意思是这个实参是只读的。

引用传递传递参数可以避免对象切割问题。当一个函数的传入参数是基类,而你以值传递的方式传入一个派生类,参数初始化调用基类的拷贝构造函数,派生类派生出来的特性全部被“切割”掉了,仅留下一个基类对象。

class Window{  						//定义一个图形操作界面的窗口类
public:
    ...
    std::string name() const;  		//返回当前窗口名称
    virtual void display() const; 	//显示窗口和内容,它是虚函数
};

class WindowWithScrollBar : public Window{
public:
    ...
    virtual void display() const;
};

display() 是虚函数,这就意味着两个类对于这个函数有不同的实现。现在你要实现一个函数:先打印出窗口的名字,然后显示窗口。下面是错误示范:

void printNameAndDisplay(Window w){  //值传递
	std::cout<<w.name();
  	w.display();
}

当你调用上述函数并传给它一个 WindowWithScrollBar 对象,会发生什么呢?

WindowWithScrollBar wwsb;
printNameAndDisplay(wwsb);  //调用的永远是Window::display()

参数 w 会被构造成一个 Window 对象,因为该函数是值传递的,所以导致了 wwsb 之所以是 WindowWithScrollBar 对象的所有特性都被切割掉了。在 printNameAndDisplay() 内不论传递过来的对象原本是什么类型,参数 w 永远只是 Window 对象,因此在函数内调用 display() 调用的总是 Window::display(),绝不会是 WindowWithScrollBar::display()

解决切割问题的方法就是使用引用传递。传进来的窗口是什么类型,w 就表现为那种类型。

void printNameAndDisplay(const Window& w){  //值传递
	std::cout<<w.name();
  	w.display();
}

在C++编译器的底层,引用是用指针来实现的,即引用传递的本指是指针传递。因此对于内置类型来说,值传递往往比引用传递的效率高。STL中的迭代器和函数对象也适合使用值传递,因为它们是根据值传递效率高的规则来设计的,并且它们不受切割问题的影响。

选择值传递还是引用传递,取决于你使用哪一部分的C++(见条款01)。

选择值传递还是引用传递与类型的大小无关。

Note:

  • 尽量以引用传递(若想参数只读,加 const)替换值传递,前者通常比较高效,并可避免切个问题
  • 以上规则并不适用于内置类型以及STL的迭代器和函数对象,对它们而言,值传递更高效

条款21:必须返回对象时,别妄想返回其reference

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-10 12:12:56  更:2021-11-10 12:13:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:58:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码