读书笔记——《高质量C++/C编程指南》(5)

目录

前言

类的构造函数、析构函数与赋值函数

构造函数与析构函数的起源

构造函数的初始化表

构造和析构的次序

示例:类String 的构造函数与析构函数

不要轻视拷贝构造函数与赋值函数

示例:类String 的拷贝构造函数与赋值函数

偷懒的办法处理拷贝构造函数与赋值函数

如何在派生类中实现类的基本函数


前言

前两篇笔记对这本书里面的文件结构、代码风格、命名规则、表达式和基本语句的良好编程习惯,将记录常量与函数设计做了记录。本篇读书笔记(5)将记录类的构造函数、析构函数与赋值函数。

类的构造函数、析构函数与赋值函数

类的构造函数析构函数赋值函数构造函数、析构函数与赋值函数是每个类最基本的函数。

每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。

对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A 产生四个缺省的函数,如

A(void); // 缺省的无参数构造函数
A(const A &a); // 缺省的拷贝构造函数
~A(void); // 缺省的析构函数
A & operate =(const A &a); // 缺省的赋值函数



默认的“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错

String的结构如下:

class String
{
public:String(const char *str = NULL); // 普通构造函数String(const String &other); // 拷贝构造函数~ String(void); // 析构函数String & operate =(const String &other); // 赋值函数
private:char *m_data; // 用于保存字符串
};

构造函数与析构函数的起源

C++提供了更好的机制来增强程序的安全性。C++编译器具有严格的类型安全检查功能,它几乎能找出程序中所有的语法问题。

但是程序通过了编译检查并不表示错误已经不存在了,仍然存在难以察觉的错误:由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人遗忘。因此创造了构造函数和析构函数!!!

当对象被创建时,构造函数被自动执行。

当对象消亡时,析构函数被自动执行。

这下就不用担心忘了对象的初始化和清除工作。

构造函数的初始化表

构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)。

初始化表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前
构造函数初始化表的使用规则:如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数
例如

class A
{…A(int x); // A 的构造函数
};class B : public A
{…B(int x, int y);// B 的构造函数
};B::B(int x, int y)
: A(x) // 在初始化表里调用A 的构造函数
{
…
}

类的 const 常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。

类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,

class A
{…A(void); // 无参数构造函数A(const A &other); // 拷贝构造函数A & operate =( const A &other); // 赋值函数
};
class B
{
public:B(const A &a); // B 的构造函数
private:A m_a; // 成员对象
};

这两种方式的效率不完全相同。非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。例如

B::B(const A &a): m_a(a)
{//…
}
B::B(const A &a)
{m_a = a;
…
}

将成员对象m_a 初始化。


类B 的构造函数在函数体内用赋值的方式将成员对象m_a 初始化。

我们看到的只是一条赋值语句,但实际上B 的构造函数干了两件事:

先暗地里创建m_a对象(调用了A 的无参数构造函数),

再调用类A 的赋值函数,将参数a 赋给m_a。

对于内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但。若类F 的声明如下:

class F
{
public:F(int x, int y); // 构造函数
private:int m_x, m_y;int m_i, m_j;
}

后者的程序版式似乎更清晰些 

F::F(int x, int y)
: m_x(x), m_y(y)
{m_i = 0;m_j = 0;
}
F::F(int x, int y)
{m_x = x;m_y = y;m_i = 0;m_j = 0;
}

构造和析构的次序

构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。

析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。

示例:类String 的构造函数与析构函数

// String 的普通构造函数
String::String(const char *str)
{if(str==NULL){m_data = new char[1];*m_data = ‘\0’;}else{int length = strlen(str);m_data = new char[length+1];strcpy(m_data, str);}
}// String 的析构函数
String::~String(void)
{delete [] m_data;// 由于m_data 是内部数据类型,也可以写成 delete m_data;
}

不要轻视拷贝构造函数与赋值函数

如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数

倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误

class String
{
public:String(const char *str = NULL); // 普通构造函数String(const String &other); // 拷贝构造函数~ String(void); // 析构函数String & operate =(const String &other); // 赋值函数
private:char *m_data; // 用于保存字符串
};

以类String 的两个对象a,b 为例,假设a.m_data 的内容为“hello”,b.m_data 的内容为“world”。

现将 a 赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。

这将造成三个错误:

一是b.m_data 原有的内存没被释放,造成内存泄露

二是b.m_data 和a.m_data 指向同一块内存,a 或b 任何一方变动都会影响另一方;

三是在对象被析构时,m_data 被释放了两次

拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。

拷贝构造函数是在对象被创建时调用的

赋值函数只能被已经存在了的对象调用。

String a(“hello”);
String b(“world”);
String c = a; // 调用了拷贝构造函数,最好写成 c(a);
c = b; // 调用了赋值函数

示例:类String 的拷贝构造函数与赋值函数

// 拷贝构造函数
String::String(const String &other)
{// 允许操作other 的私有成员m_dataint length = strlen(other.m_data);m_data = new char[length+1];strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operate =(const String &other)
{// (1) 检查自赋值if(this == &other)return *this;// (2) 释放原有的内存资源delete [] m_data;// (3)分配新的内存资源,并复制内容int length = strlen(other.m_data);m_data = new char[length+1];strcpy(m_data, other.m_data);// (4)返回本对象的引用return *this;
}

类 String 拷贝构造函数与普通构造函数的区别是:

在函数入口处无需与NULL 进行比较,这是因为“引用”不可能是NULL,

而“指针”可以为NULL。

类 String 的赋值函数比构造函数复杂得多,分四步实现:
(1)第一步,检查自赋值(a=a)。需要注意的是间接的自赋值,例如

// 内容自赋值
b = a;
…
c = b;
…
a = c;// 地址自赋值
b = &a;
…
a = *b;

自赋值为了防止多次释放同一块内存,第二步的delete,自杀后就不能复制自己

注意不要将检查自赋值的if 语句
if ( this  ==  & other )
错写成为
if (  * this  ==  other )

(2)第二步,用delete 释放原有的内存资源。如果现在不释放,以后就没机会了,将造成内存泄露。
(3)第三步,分配新的内存资源并复制字符串

注意函数strlen 返回的是有效字符串长度,不包含结束符‘\0’。函数strcpy 则连‘\0’一起复制。

(4)第四步,返回本对象的引用,目的是为了实现象 a = b = c 这样的链式表达。

注意不要将 return *this 错写成 return this 。

偷懒的办法处理拷贝构造函数与赋值函数

如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,
偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码
例如:

class A
{ …
private:A(const A &a); // 私有的拷贝构造函数A & operate =(const A &a); // 私有的赋值函数
};

如果有人试图编写如下程序:
A  b ( a ) ; // 调用了私有的拷贝构造函数
b  =  a ; // 调用了私有的赋值函数
编译器将指出错误,因为外界不可以操作A 的私有函数。

如何在派生类中实现类的基本函数

基类的构造函数、析构函数、赋值函数都不能被派生类继承

如果类之间存在继承关系,在编写上述基本函数时应注意以下事项:

派生类的构造函数应在其初始化表里调用基类的构造函数。

//基类与派生类的析构函数应该为虚(即加virtual 关键字)。例如
#include <iostream.h>class Base
{
public:virtual ~Base() { cout<< "~Base" << endl ; }
};class Derived : public Base
{
public:virtual ~Derived() { cout<< "~Derived" << endl ; }
};void main(void)
{Base * pB = new Derived; // upcastdelete pB;
}

输出结果为:
~Derived
~Base
如果析构函数不为虚,那么输出结果为
~Base

编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。例如: 


class Base
{
public:…Base & operate =(const Base &other); // 类Base 的赋值函数
private:int m_i, m_j, m_k;
};class Derived : public Base
{
public:Derived & operate =(const Derived &other); // 类Derived 的赋值函数
private:int m_x, m_y, m_z;
};Derived & Derived::operate =(const Derived &other)
{
//(1)检查自赋值if(this == &other)return *this;
//(2)对基类的数据成员重新赋值Base::operate =(other); // 因为不能直接操作私有数据成员
//(3)对派生类的数据成员赋值m_x = other.m_x;m_y = other.m_y;m_z = other.m_z;
//(4)返回本对象的引用return *this;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/10908.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode怎么设置背景图片?

vscode背景图片是可以自己设置的&#xff0c;软件安装后默认背景的颜色是黑色的&#xff0c;这是默认的设计&#xff0c;如果要修改背景为指定的图片&#xff0c;那么我们需要安装插件&#xff0c;然后再通过代码来设置背景图片的样式&#xff0c;下面我们就来看看详细的教程。…

代数结构:5、格与布尔代数

16.1 偏序与格 偏序集&#xff1a;设P是集合&#xff0c;P上的二元关系“≤”满足以下三个条件&#xff0c;则称“≤”是P上的偏序关系&#xff08;或部分序关系&#xff09; &#xff08;1&#xff09;自反性&#xff1a;a≤a&#xff0c;∀a∈P&#xff1b; &#xff08;2…

旅游推荐管理系统(小组项目)

文章目录 前言 一、项目介绍 1. 项目目的 2. 项目意义 2.1 提升旅游体验 2.2 促进旅游业发展 2.3 数据积累与分析 2.4 提升服务品质 2.5 优化资源配置 二、项目结构 1. 主要使用的技术 1.1 若依&#xff08;Ruoyi&#xff09;框架 1.2 Vue.js框架 1.3 Ajax 1.4 …

OpenCV 阈值法

1.概述 在深度学习出现之前&#xff0c;图像中的阈值法处理主要有二值阈值法、自适应阈值法、Ostu阈值法。 2.理论对比 3.代码实现 #include <iostream> #include <opencv2/opencv.hpp>int main(int argc, char** argv) {if(argc ! 2) {std::cerr << "…

【进程通信】了解信号以及信号的产生

文章目录 0.前言1.信号的基本概念1.1中断1.1.1 软中断1.1.2硬中断 1.2异步1.2.1异步和同步的比较 2.信号的主要用途3.信号的特点4.查看信号4.1Core和Term的区别4.2生成Core文件 5.初识捕捉信号5.1signal函数 6.产生信号的方式6.1.通过终端按键产生信号6.2.调用系统函数向进程发…

隆重庆贺中华人民共和国成立七十五周年,中国科学技术大学全体师生祝福祖国强盛

隆重庆贺中华人民共和国成立七十五周年中国科学技术大学全体师生祝福祖国强盛卡西莫多 华夏曾经炮声隆 亿万黎民饥寒重 列强瓜分举杯庆 条约割肉斟血贺 饕餮盛宴沃野中 地大物博多浮华 曾多膏脂送肉人 我辈悲苦先祖民 曾经如此患难共 皆因内乱又失和 才致如此覆巢国 领袖聚沙垒…

使用 TensorFlow.js 和 OffscreenCanvas 实现实时防挡脸弹幕

首先&#xff0c;要理解我们的目标&#xff0c;我们将实时获取视频中的面部区域并将其周围的内容转为不透明以制造出弹幕的“遮挡效应”。 步骤一&#xff1a;环境准备 我们将使用 TensorFlow.js 的 Body-segmentation 库来完成面部识别部分&#xff0c;并使用 OffscreenCanv…

tvm.frontend.from_pytorch详细介绍(1)

文章目录 一、pytorch前端整体转化流程&#xff08;部分&#xff09;1.脚本化的pytorch模型2.内联优化(_run_jit_passes)2.1、内联优化2.2 什么是内联函数 3.graph中的所有op(get_all_op_names)3.1 各个变量的值1 .graph2 .nodes3 .p nodes4、返回结果 二、from_pytorch完整代码…

国内智能搜索工具实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

3、Qt--配置文件的使用

开发平台&#xff1a;Win10 64位 开发环境&#xff1a;Qt Creator 13.0.0 构建环境&#xff1a;Qt 5.15.2 MSVC2019 64位 一、需求及方案 实际开发过程中&#xff0c;我们需要根据本地的配置文件&#xff0c;去配置我们的程序&#xff0c;比如数据库地址、网络地址等信息&…

分享10类正规的网上赚钱平台,让你摆脱单一收入

在这个互联网飞速发展的时代&#xff0c;你是否还在为单一的收入来源而焦虑&#xff1f;别担心&#xff0c;今天带你解锁10种网上赚钱的新姿势&#xff0c;让你的收入不再单一&#xff0c;甚至可能翻倍&#xff01; 1. 文库类&#xff1a;知识的变现 你知道吗&#xff1f;你的…

k8s 数据流向 与 核心概念详细介绍

目录 一 k8s 数据流向 1&#xff0c;超级详细版 2&#xff0c;核心主键及含义 3&#xff0c;K8S 创建Pod 流程 4&#xff0c;用户访问流程 二 Kubernetes 核心概念 1&#xff0c;Pod 1.1 Pod 是什么 1.2 pod 与容器的关系 1.3 pod中容器 的通信 2&#xff0c; …

imx91的uboot编译

一、准备操作 下载半导体厂家的uboot源码 如这里我要下载的是imx91的恩智浦linux芯片bootloader 进入半导体厂家官网 下载源码&#xff0c;略 更新linux源&#xff0c;这里我是替换成清华源 vi /etc/apt/sources.list deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ fo…

【江科大STM32学习笔记】新建工程

1.建立工程文件夹&#xff0c;Keil中新建工程&#xff0c;选择型号 2.工程文件夹里建立Start、Library、User等文件夹&#xff0c;复制固件库里面的文件到工程文件夹 为添加工程文件准备&#xff0c;建文件夹是因为文件比较多需要分类管理&#xff0c;需要用到的文件一定要复…

Web UI自动化测试--PO模式

没有PO实现的测试用例的问题: 重用性低:登录功能重复可维护性差:数据和代码混合可读性差:元素定位方法杂乱(id、xpath、css混杂)可读性差:不易识别操作的含义(特别是css和xpath语法)可维护性差:如果某个元素的属性改了,你要更改多次PO(Page Object Model)页面对象模型…

完全背包问题(c++)

完全背包问题 当前有 N 种物品&#xff0c;第 i 种物品的体积是 ci​&#xff0c;价值是 wi​。 每种物品的数量都是无限的&#xff0c;可以选择任意数量放入背包。 现有容量为 V 的背包&#xff0c;请你放入若干物品&#xff0c;使总体积不超过 V&#xff0c;并且总价值尽可…

el-table被点击行添加背景颜色

在 Element UI 的 el-table 组件中&#xff0c;你可以通过使用行样式 row-style 属性来为被点击的行添加颜色。首先&#xff0c;你需要在数据中定义一个对象来存储被点击行的索引&#xff0c;然后在 row-style 函数中根据这个索引来返回不同的样式。 以下是一个示例&#xff1…

YOLOv8+CLIP实现图文特征匹配

本文通过结合YOLOv8s的高效物体检测能力与CLIP的先进图像-文本匹配技术&#xff0c;展示了深度学习在处理和分析复杂多模态数据中的潜力。这种技术的应用不仅限于学术研究&#xff0c;还能广泛应用于工业、商业和日常技术产品中&#xff0c;以实现更智能的人机交互和信息处理。…

新年首站 | 宝兰德教育行业信创新动力发展研讨会顺利召开

近日&#xff0c;宝兰德携手慧点数码、安超云共同举办了教育行业信创新动力发展研讨会。会议邀请了中国人民公安大学、中国戏曲学院、北京航空航天大学、北京理工大学、华北电力大学、中国矿业大学、北京服装学院、北京城市学院等数十所高校信息中心负责人、专家出席了本次会议…

LeetCode 题目 120:三角形最小路径和

作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任字节跳动数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python&#xff0c;欢迎探讨交流 欢迎加入社区&#xff1a;码上找工作 作者专栏每日更新&#xff1a; LeetCode解锁1000题…