C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(4)

news/2024/9/19 14:49:54 标签: c++, 学习, 笔记, 开发语言

7、拷贝构造函数

        在C++中有一种叫做拷贝构造函数的特殊的构造函数,它允许生成另一个对象的拷贝的对象。下面是在SpreadsheetCell类中的拷贝构造函数的声明:

export class SpreadsheetCell
{
public:
    SpreadsheetCell(const SpreadsheetCell& src);
    // Remainder of the class definition omitted for brevity
};

        拷贝构造函数使用一个对源对象的常量引用。与其他构造函数类似,它并不返回值。拷贝构造函数从源对象中拷贝所有的数据成员。当然了,从技术上来说,在拷贝构造函数中可以做你想做的任何事,但是,执行期待的行为并初始化新的对象为原来对象的一个拷贝是个不错的主意。下面是一个实现SpreadsheetCell拷贝构造函数的例子。注意构造函数初始化器的使用。

SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src)
: m_value { src.m_value }
{
}

        如果你自己不定拷贝构造函数,c++就会为你生成一个,初始化每个从与其等价的源对象中的数据成员的新的对象的数据成员。对于对象的数据成员,这种初始化意味着调用拷贝构造函数。给定一些叫做m1,m2,...mn的数据成员,编译器生成的拷贝构造函数可以表示如下:

classname::classname(const classname& src)
: m1 { src.m1 }, m2 { src.m2 }, ... mn { src.mn } { }

        因此,在大部分情况下,没有必要指定拷贝构造函数!

        注意:SpreadsheetCell拷贝构造函数只是为了演示的需要。实际上,在这种情况下,拷贝构造函数可以省略,因为缺省的编译器生成的已足够好。然而,在有些情况下,编译器生成的拷贝构造函数是不够的。我们以后再讨论。

7.1、什么时候调用拷贝构造函数

        在c++中缺省的传递给函数的参数语法是传值。这就意味着函数接收到的是值或者对象的一个拷贝。这样的话,每当传递对象给函数的时候,编译器就会调用新对象的拷贝构造函数来进行初始化。例如,假设你有下面的printString()函数,接收std::string参数的值:

void printString(string value)
{
    println("{}", value);
}

        回忆一下,std::string实际上是一个类,而不是一个内建的类型。当代码传递string参数调用printString()是,string参数的值是用调用其拷贝构造函数来进行初始化的。拷贝构造函数的参数就是传递给printString()的string。在下面的例子中,string的拷贝构造函数对于printString()的value对象以name作为其参数执行:

string name { "heading one" };
printString(name); // Copies name

        当printString()成员函数执行完毕时,value就被破坏掉了。因为它只是name的一个拷贝,而name保持不变。当然了,你可以通过传递一个指向常量的引用作为参数来避免掉拷贝构造函数的开销,这个我们下面讨论。

        当从函数中返回对象的值时,拷贝构造函数也可能被调用。这个也比较复杂,我们专门讨论吧。

7.2、显式调用拷贝构造函数

        你也可以显式使用拷贝构造函数。能够构造一个对象完全是另一个对象的拷贝通常是很有用的。例如,你可能会想像这样生成一个SpreadsheetCell对象:

SpreadsheetCell myCell1 { 4 };
SpreadsheetCell myCell2 { myCell1 }; // myCell2 has the same values as myCell1

7.3、传递对象引用

        为了避免在传递对象给函数时发生拷贝,应该声明函数接收对象的引用。传递对象引用通常比传值更高效,因为只有对象的地址被拷贝,而不是整个对象。另外,引用传递避免了对象动态内存分配的问题。这个也留待后面讨论吧。

        当传递对象引用时,使用对象引用的函数可能会改变原来的对象。如果使用引用传递只是为了效率,也可以通过声明对象为常量来排除这种可能性。这就是有名的通过常量引用传递对象,会贯穿在我们整个的博客中。

        注意:基于性能的原因,最好是传递常量引用对象而不是值。在介绍了move的语法后会稍稍改动一下这个规则,允许在特定情况下传递对象的值。

        注意SpreadsheetCell类有许多接收std::string_view作为参数的成员函数。我们以前讨论过,string_view只是一个指针与长度,所以对其拷贝开销不会太大,通常都是传值。

        还有对于像int,double等等的原始类型也应该传值。对这样类型的传递常量引用也得不到什么额外的好处。

        SpreadsheetCell类的doubleToString()成员函数总是返回string值,因为该成员函数的实现生成了一个本地的string对象,在返回给调用者的成员函数的结尾。返回一个对该string的引用是不灵的,因为当函数退出时,它指向的string已经被破坏掉了。

7.4、显式缺省与删除拷贝构造函数

就像可以显式缺省与删除编译器生成类的缺省构造函数一样,也可以显式缺省与删除编译器生成的拷贝构造函数,如下:

SpreadsheetCell(const SpreadsheetCell& src) = default;
SpreadsheetCell(const SpreadsheetCell& src) = delete;

        删除了拷贝构造函数,对象就不能再被拷贝了。这可以被用于禁止传值给对象,这个我们以后讨论。

        注意:如果拥有数据成员的类有一个删除了的或者私有的拷贝构造函数,该类的拷贝构造函数也自动删除了,即使你显式地给了一个缺省的情况下也不行。


http://www.niftyadmin.cn/n/5665732.html

相关文章

【Java】多态性【主线学习笔记】

文章目录 前言多态性1、编译时多态性(静态多态性):2、运行时多态性(动态多态性)3、多态性的实用性4、多态性的优点与弊端 下一篇点击跳转《【Java】多态性-向下转型【主线学习笔记】》 前言 Java是一门功能强大且广泛应…

功能测试干了三年,快要废了。。。

8年前刚进入到IT行业,到现在学习软件测试的人越来越多,所以在这我想结合自己的一些看法给大家提一些建议。 最近聊到软件测试的行业内卷,越来越多的转行和大学生进入测试行业,导致软件测试已经饱和了,想要获得更好的待…

JAVA与Python谁更适合后端?

Java和Python各有优势,选择哪种语言应根据项目的具体需求和团队的实际情况来决定。 小型团队或原型开发:如果项目规模小、需求变动频繁,或者需要快速开发产品原型,建议使用Python。大型项目或企业级应用:如果项目规模…

【毕设】基于Java的超市管理系统

基于Java的超市管理系统是一个用于管理超市日常运营的软件解决方案,它可以包括库存管理、销售管理、客户管理等多个模块。以下是一个简化的系统设计方案,以及一些关键组件和技术选型的建议。 系统架构 前端: HTML/CSS/JavaScript&#xf…

状态估计算法

目录 前言一、贝叶斯滤波二、卡尔曼滤波2.1 KF简介2.2 基本线性模型2.3 KF公式推导2.3.1 预测值2.3.2 先验误差协方差矩阵2.3.3 卡尔曼增益2.3.4 最优估计值2.3.5 后验误差协方差矩阵 2.4 KF算法使用2.5 MATLAB验证2.5 Python验证 三、扩展卡尔曼滤波3.1 EKF原理3.2 MATLAB实现…

(java+Seleniums3)自动化测试实战2

1.环境问题点 此时,可以成功打开浏览器 此时,selenium可以控制浏览器 get--就是访问的意思 将驱动复制在当前项目之下 复制驱动的路径 2.基本元素定位 使用id来定位: 使用Name来定位: 成功: 使用id是唯一的&#xff0c…

python画图|3D bar进阶探索

前述学习过程只能怪,已经探究了3D直方图的基础教程,详见下述链接: python画图|3D直方图基础教程-CSDN博客 实际上,基础文章直接进入了堆叠教程,相对来说基础的程度不够,因此有必要再次探索。 【1】官网教…

软件工程测试

1. 软件测试概述 通俗地说,软件测试是为了发现错误而执行程序的过程。 软件测试:根据软件开发各阶段的规格说明和程序的内部结构而精心设计一批测试用例(即输入数据及其预期的输出结果),并利用这些测试用例去运行程序…