【Effective C++详细总结】第四章 设计与声明

news/2024/4/28 7:36:42/文章来源:https://blog.csdn.net/Newin2020/article/details/129702112

✍个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343
📚专栏地址:C/C++知识点
📣专栏定位:整理一下 C++ 相关的知识点,供大家学习参考~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
🎏唠叨唠叨:在这个专栏里我会整理一些琐碎的 C++ 知识点,方便大家作为字典查询~

四、设计与声明

条款18:让接口容易被正确使用,不易被误用

1、保证参数一致性:

如果直接传入三个 int 值的参数代表年月日,那么很有可能会因为将其中传入参数位置写反从而发生不可预料的错误。因此,我们可以通过将年月日设计成类,将类作为参数传入就能让编译器帮我们检错是否写反。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ME1eb3A-1679446885398)(《Effective C++》笔记.assets/image-20230308122517394.png)]

更进一步的可以在年月日类中再细分采用静态函数,进一步保证可靠性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HlhzOXx1-1679446885402)(《Effective C++》笔记.assets/image-20230308122806701.png)]

2、保证接口行为一致性:

内置数据类型(ints, double…)可以进行加减乘除的操作,STL 中不同容器也有相同函数(比如 size,都是返回其有多少对象),所以尽量保证用户自定义接口的行为一致性。

3、如果一个接口必须有什么操作,那么在它外面套一个新类型:

employee* createmp();//其创建的堆对象要求用户必须删除

如果用户忘记使用资源管理类,就有错误使用这个接口的可能,所以必须先下手为强,直接将 createmp() 返回一个资源管理对象,比如智能指针 share_ptr 等等:

tr1::share_ptr<employee> createmp();

如此就避免了误用的可能性。

4、有些接口可以定制删除器,就像 STL 容器可以自定义排序,比较函数一样:

tr1::share_ptr<employee> p(0, my_delete());//error! 0 不是指针tr1::share_ptr<employee> p(static_cast<employee*>(0), my_delete());//定义一个 null 指针

第一个参数是被管理的指针,第二个是自定义删除器。

条款19:12问帮你高效设计class

对于每一个 class 都要精心设计,要考虑其构造析构函数,初始化和赋值,继承,类型转换,运算符重载,值传递等问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sdb88qBt-1679446885403)(《Effective C++》笔记.assets/image-20230308123614658.png)]

条款20:宁以 pass-by-reference-to-const 替换 pass-by-value

释义:用 const 引用传递替换值传递

值传递是新创建一个对象,将这个对象和原对象相等,如果用在类里面,当类中成员变量数目较少的时候,也许问题不大(在类里值传递先调用构造,再调用析构)。但当类中成员变量数目过大时,每一次值传递就会造成时间浪费。

引用传递是生成一个别名指向这个地址,其本身是个指针,无论原对象有多少个成员变量,都能在一瞬间找到某一个。用上 const 令其成为常量指针,即“只读”。

class Number
{
public:int m_a;...int m_n;//有 n 个变量...
};void print1_num(Number num);
void print1_num(const Number& num);Number num1;
print1_num(num1);//构造,析构一个Number对象
print2_num(num1);//传地址

此外,值传递在类里还有一个问题:容易造成切割问题。

比如一个子类继续父类,传递子类对象的时候,可能只创建了一个父类的对象,子类部分缺失了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7U7VMUFK-1679446885405)(《Effective C++》笔记.assets/image-20230308124636024.png)]

如果不传入引用,则可能无法表现出多态性。

但是这条规则并不适用于所有类型,比如内置类型、STL 迭代器和函数对象。

举个例子,内置类型 int 在 64 位下占 4 个字节,但改成指针就占了 8 个字节。

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

class number{
public:number(int a);const number operator+(const number& n1, const number& n2);    //创建一个新对象,返回它//const number& operator+(const number& n1, const number& n2);private:int m_value;
};number n1(1);//n1 = 1
number n2(2);//n2 = 2number n3 = n1 + n2;

周所周知,return 返回的是一个浅拷贝副本,返回一个对象是没有问题的,但如果返回一个引用,原对象被销毁之后,引用的指向也被销毁了,也就是引用指空,出错。

我们当然可以用 new 解决这个问题,但是当变量数目多的时候,程序员往往不知道怎么使用 delete:

n3 = n1 + n2 + n4 + ...

或许有人想到创建一个 static 对象,但这也是有问题的,我们每次调用都是对同一个 static 操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnCaMZ8F-1679446885407)(《Effective C++》笔记.assets/image-20230308125604626.png)]

既然调用的都是同一个 result,那么 if 判断语句就一定是对的,这就出现了逻辑上的错误。

正确的方法就是一开始我们看到的,直接返回一个值即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fe0OOj0s-1679446885414)(《Effective C++》笔记.assets/image-20230308125737176.png)]

但并不是所有场景都不能返回引用,比如在 vector 中用索引操作时就可以返回引用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vcNO4nm-1679446885420)(《Effective C++》笔记.assets/image-20230308125906149.png)]

因为此时我们定义的 v 一直存在,需要通过返回引用对 v 中的值进行修改。

总结:虽然返回一个对象需要构造,析构等操作而产生一些代价,但是如果我们不想改变已有的值,就最好不要返回一个引用,而是支付这些代价。(这在时间上会多一点,但创建的对象会随运算符的结束而被销毁。这比“未定义行为(返回一个新建对象的引用)”,“资源泄漏”,“结果出错”要好得多了。)

条款22:将成员变量声明为 private

声明类型:

  • public:所有都能访问

  • protected:类对象不可以访问

  • private:只有类成员函数可以访问

private 优点:

1、使成员变量的控制更为精准:

class person
{
public:void setage(int id);void getage();void setname(string name);void getid();
private:int m_age;string m_name;int m_id;
};

用户通过某一函数控制一个私有变量,防止被误用。

2、使类更有封装性:

我们可以通过将方法封装到类中,以 public 的方式暴露给外部使用。比如将计算平均值的函数封装到类中,这样外部就不用在每个需要用到平均值的地方都进行一次计算操作,这样当有变动时修改起来很麻烦,每添加一个变量我都要去所有地方改。封装成函数后直接向外提供一个接口,每次要用到平均值就调用这个函数计算一次即可。

protected 不比 private 有封装性,因为 protected 子类也可以实现上述代码的操作。

条款23:宁以 non-member, non-friend 替换 member 函数

释义:如果一个成员函数调用了其他的成员函数,那么就要用一个非成员函数替换这个成员函数。

根据条款 22,对类变量的操作只能通过类成员函数实现(因为它是私有变量),那么如果一个成员函数内部实现是调用其他的成员函数,则一个非成员函数也可以做到这样的效果:

class preson
{
public:void func1();void func2();void func3(){func1();func2();}
};void use_all(const person& p)
{p.func1();p.func2();
}

func3() 和 use_all() 的效果是一样的,但这时候我们倾向于选择 use_all 函数,因为 func3() 作为一个成员函数,其本身也是个可以访问私有变量的函数。use_all() 函数其本身不可以访问私有变量。所以 use_all() 比 func3() 更有封装性。(能够访问私有变量的函数越少越好)

在了解这点之后,我们做一些更深层次的探讨:

我们称 use_all()(func3() 的非成员函数版本)为便利函数。假设一个类有多个诸如 func1() 的函数,根据排列组合,也就有很多便利函数。为了让这些便利函数和它的类看上去更像一个整体,我们把便利函数和类放在一个 namespace 中。于是,我们可以更为轻松地拓展这些便利函数 —— 多做一些排列组合。

再举个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5sAEgJr-1679446885427)(《Effective C++》笔记.assets/image-20230309122942489.png)]

总结:若一个成员函数调用其他成员函数,那么这个成员函数的非成员函数版本比之拥有更多的封装性,和机能扩充性。

条款24:若所有参数皆需类型转换,请为此采用 non-member 函数

举例:有理数类和整数的运算

class Rational
{
public:Rational(int numerator = 0, int denominator = 1)//分子与分母...const Rational operator*(const Rational& right_number) const;...
};Rational oneEighth(1, 8);
Rational onehalf(1, 2);
Rational result1 = onehalf * oneEighth;
Rational result2 = onehalf * 2;
Rational result3 = 2 * onehalf;//error!

onehalf*2 相当于 onehalf.operator*(2)

首先创建了一个临时对象 const Rational temp(2);

再让两个 Rational 对象运算。

2*onehalf 是 2 调用了 operator*。因为 2 是需要被转换的参数,而 2 的位置和 this(调用 operator *) 对象的位置是一样的,所以无法将 2 转换成 Rational 类型,也就无法调用 operator* 函数。

解决办法:使用 non-member 函数,让左右参数的地位平等:

const Rational operator*(const Rational& left_number, const Rational& right_number)
{...}

总结:如果所有参数(运算符左边或者右边的参数)都需要类型转换,用 non-member 函数。

条款25:考虑写一个不抛异常的 swap 函数

周所周知,swap 可以交换两个数的值,标准库的 swap 函数是通过拷贝完成这种运算的。想想,如果是交换两个类对象的值,如果类中变量的个数很少,那么 swap 是有一定效率的,但如果变量个数很多呢?

你一定联想到了之前提过的,引用传递替换值传递。没错,交换两个类对象的地址就可以很有效率地完成大量变量的 swap 操作。不幸的是,标准库的 swap 并无交换对象地址的行为,所以我们需要自己写 swap 函数。

class person{...};
void my_swap(person& p1, person& p2)
{swap(p1.ptr, p2.ptr);
}

这个函数无法通过编译,因为类变量是 private,无法通过对象访问。所以要把它变成成员函数。

class person
{
public:void my_swap(person& p){swap(this->ptr, p.ptr);}...
};

如果你觉得 p1.my_swap(p2) 的调用形式太 low 了,你可以设计一个 non-member 函数(如果是在同一个命名空间那就再好不过了),实现 swap(p1, p2),这里不做演示。你还可以特化 std 里的 swap 函数:

namespace std
{template<>void swap<person> (person& p1, person& p2){p1.my_swap(p2);}
}

值得注意的是,如果你设计的是类模板,而尝试对 swap 特化,那么会在 std 里发生重载,这是不允许的,因为用户可以特化 std 的模板,但不可以添加新的东西到 std 里。

还有一点:在上面工作全部完成后,如果想使用 swap ,请确定包含一个 using 声明式,一边让 std::swap 可见,然后直接使用 swap。

template<class T>
void do_something(T& a, T& b)
{using std::swap;...swap(a, b);...
}

其中过程:

如果 T 在其命名空间有专属的 swap 则调用,否则调用 std 的 swap。

如果在 std 有特化的 swap 则调用,否则调用一般的 swap。(也即是拷贝)

再举个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A132VxpJ-1679446885429)(《Effective C++》笔记.assets/image-20230309125427787.png)]

也可以利用 swap 函数去除重复代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kBCn4Fc-1679446885430)(《Effective C++》笔记.assets/image-20230309125831608.png)]

总结:

1、当 std::swap 效率不高时,考虑自定义一个成员函数 swap

2、为成员函数提供非成员函数版本

3、类模板不要特化 swap,类特化 swap

4、使用 swap 前要写 std::swap,以便在更多的语境下使用

参考资料:
《Effective C++》侯捷:https://book.douban.com/subject/1842426/
EFFECTIVE C++ (万字详解)(一)_c++ effective:https://blog.csdn.net/qq_62674741/article/details/124896986
一文整理Effective C++ 55条款内容(全):https://blog.csdn.net/weixin_45926547/article/details/121276226?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-1-121276226-blog-124896986.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-1-121276226-blog-124896986.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=2

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

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

相关文章

Flink-转换算子

基本转换算子 map(映射) filter&#xff08;过滤&#xff09; flatMap&#xff08;扁平映射&#xff09; 聚合算子 keyBy&#xff08;按键分区&#xff09; 简单聚合 reduce&#xff08;归约聚合&#xff09; UDF介绍 函数类 富函数类 数据源读入数据之后&#xff0c;我们就可…

Neodynamic EPLPrinter SDK 2.0 for .NET Crack

Neodynamic EPLPrinter Emulator SDK for .NET Standard V2.0 添加对 FK&#xff08;删除表单&#xff09;、FR&#xff08;检索表单&#xff09;和 FS&#xff08;存储表单&#xff09;表单相关命令的支持。 21月 2023&#xff0c; 10 - 34&#xff1a;<>新版本 特征…

如何在24小时内让你的网站跻身谷歌前列?

在当今互联网时代&#xff0c;拥有一个排名靠前的网站对于企业来说非常重要&#xff0c;因为这意味着更多的流量和更高的曝光率。 而谷歌&#xff08;Google&#xff09;是全球最受欢迎的搜索引擎之一&#xff0c;因此在谷歌的搜索结果中排名靠前非常重要。 那么如何在24小时…

tomcat服务器前端部署【Tomcat Manager、思路分析】

问题描述 当前需要我进行前端代码的部署&#xff0c;但是我忘记了这个系统对应的部署位置&#xff0c;但是隐约记得好像是通过tomcat部署的。 然后当时为了方便部署&#xff0c;我们打开了Tomcat Manager 以下是基于Tomcat Manager的&#xff0c;没有打开的需要前往tomcat下载…

详解:企业知识管理的制作步骤!

随着信息技术的快速发展&#xff0c;企业面临着海量的信息和知识&#xff0c;如何管理和利用这些信息和知识&#xff0c;已经成为企业发展的重要问题。知识管理是一种管理方法和技术&#xff0c;旨在帮助企业有效地管理和利用知识资产&#xff0c;提高企业的创新能力和竞争力。…

【CSS】浮动 ② ( 浮动语法简介 | 文字环绕效果 | 左浮动 | 右浮动 )

文章目录一、浮动语法简介1、语法说明2、没有浮动的效果3、左浮动的效果4、右浮动的效果5、右浮动 外边距效果二、完整代码示例一、浮动语法简介 1、语法说明 为 元素 设置了 浮动 CSS 属性 , 可以实现 : 元素标签 不再受 标准流 控制 ; ( 块级元素 , 行内元素 , 行内块元素 …

【嵌入式Linux学习笔记】platform设备驱动和input子系统

对于Linux这种庞大的操作系统&#xff0c;代码重用性非常重要&#xff0c;所以需要有相关的机制来提升效率&#xff0c;去除重复无意义的代码&#xff0c;尤其是对于驱动程序&#xff0c;所以就有了platform和INPUT子系统这两种工作机制。 学习视频地址&#xff1a;【正点原子…

【JavaSE】泛型中的通配符

文章目录1. 概述2. 上界通配符 < ? extends E>3. 下界通配符 < ? super E>3. &#xff1f;和 T 的区别1. 概述 Java 泛型&#xff08;generics&#xff09;是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制&#xff0c;该机制允许开发者在编译时…

【QT神奇Bug】中文乱码、括号乱码、冒号乱码【2023.03.22】

&#x1f60d;Qt乱码疑难杂症解决方案 Solved by Yang Naifen. &#x1f4fa;视频讲解地址&#xff1a;【Qt疑难杂症之乱码-哔哩哔哩】 https://b23.tv/83MmXru 附言&#xff1a;解决这个bug按照我当前的薪资&#xff0c;至少四百RMB。都是工农阶级的工友&#xff0c;有bug一…

本地调试Java程序时只对部分接口忽略代理

场景 今天有位朋友问了个问题&#xff0c;在本地IDE开发工具调试代码的时候&#xff0c;怎么不动代码的情况只针对部分API走proxy&#xff0c;因为他们的代码只需要在本地调试的时候才要用到Proxy&#xff0c;而平时都是部署在云上&#xff0c;是用不到Proxy的&#xff0c;所以…

JDBC基础,介绍了简单的连接数据库,并通过在后端写SQL语句对数据库进行基本的增删查改操作

一、JDBC基础 跟数据库连接&#xff0c;并且可以对数据库里面的数据通过SQL语句进行处理等操作。 1.1 JDBC JDBC是SUN公司的&#xff0c;所以要按照他们的规范来&#xff0c;因为MYSQL和Oracle都是SUN公司的。三个产品都是一个公司的&#xff0c;一般不会出现兼容性不好的问…

智能手机2023:高端前攻、中端后守

配图来自Canva可画 沉寂许久的行业&#xff0c;终于在疫情之后迎来了久违的舞台&#xff0c;MWC线下展会三年来第一次召开。2月27日至3月2日&#xff0c;2023年世界移动通讯大会如期在巴塞罗那举行&#xff0c;国内一众手机厂商们纷纷登台亮相、大秀肌肉。与以往相比&#xff…

Rocketmq-Mqtt 开发实例

一、RocketMQ MQTT 概览传统的消息队列MQ主要应用于服务&#xff08;端&#xff09;之间的消息通信&#xff0c;比如电商领域的交易消息、支付消息、物流消息等等。然而在消息这个大类下&#xff0c;还有一个非常重要且常见的消息领域&#xff0c;即IoT类终端设备消息。近些年&…

Tomcat源码:启动类Bootstrap与Catalina的加载

参考资料&#xff1a; 《Tomcat源码解析系列&#xff08;一&#xff09;Bootstrap》 《Tomcat源码解析系列&#xff08;二&#xff09;Catalina》 《Tomcat - 启动过程&#xff1a;初始化和启动流程》 《Tomcat - 启动过程:类加载机制详解》 《Tomcat - 启动过程:Catalina…

不用科学上网,免费的GPT-4 IDE工具Cursor保姆级使用教程

大家好&#xff0c;我是可乐。 过去的一周&#xff0c;真是疯狂的一周。 GPT-4 震撼发布&#xff0c;拥有了多模态能力&#xff0c;不仅能和GPT3一样进行文字对话&#xff0c;还能读懂图片&#xff1b; 然后斯坦福大学发布 Alpaca 7 B&#xff0c;性能匹敌 GPT-3.5&#xff…

易基因:PIWI/piRNA在人癌症中的表观遗传调控机制(DNA甲基化+m6A+组蛋白修饰)|综述

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。2023年03月07日&#xff0c;南华大学衡阳医学院李二毛团队在《Molecular Cancer》杂志发表了题为“The epigenetic regulatory mechanism of PIWI/piRNAs in human cancers”的综述文章&am…

数据处理时代,绕不开的数据分析

数据分析的出现是因为人类难以理解海量数据所呈现出来的信息&#xff0c;不能从中找到相应的规律来对现实中的事物进行对应&#xff0c;我们都知道数据有很高的价值&#xff0c;但不能利用的价值&#xff0c;没有任何意义。 为了解决这一问题&#xff0c;数据分析在长期的数据…

Golang每日一练(leetDay0012)

目录 34. 查找元素首末位置 Find-first-and-last-position-of-element-in-sorted-array &#x1f31f;&#x1f31f; 35. 搜索插入位置 Search Insert Position &#x1f31f; 36. 有效的数独 Valid Sudoku &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 …

[vue问题]Uncaught SyntaxError: Not available in legacy mode

[vue问题]Uncaught SyntaxError: Not available in legacy mode问题描述问题分析解决方案直接回退vue-i18n的版本解决错误提示的问题问题描述 Uncaught SyntaxError: Not available in legacy modeat Object.createCompileError (message-compiler.cjs.js?af13:58:1)at creat…

GTC 2023 | 「皮衣刀客」黄仁勋畅谈 AI Top 5,科学计算、生成式 AI、Omniverse 榜上有名

内容一览&#xff1a;北京时间 3 月 21 日 23:00&#xff0c;英伟达创始人兼 CEO 黄仁勋在 GTC 2023 上发表主题演讲&#xff0c;介绍了生成式 AI、元宇宙、大语言模型、云计算等领域最新进展。 关键词&#xff1a;英伟达 黄仁勋 GTC 2023 「Don’t Miss This Defining Momen…