《effective c++》总结

2023-11-08

文章目录


前言

读《effective c++》内容的部分总结


条款02:尽量以const,enum,inline替换 #define

原因如下:

  1. 宏变量可能没有进入符合表(symbol table)内(映射关系),导致运行此宏常量时获得一个编译错误信息
    例如:#define ASN 1.653。当使用ASN时会报错。
    解决方法是以一个常量替换上述的宏(#define):
    const double ASN=1.653;

  2. 声明class专属常量(将常量的作用域限制于class内),不能用#define,因为#define并不重视作用域,不提供任何封装特性。
    解决方法如下:静态常量

class  Solution {
	private:
		static const double fu; //static class 声明常量位于头文件内
};
const double Solution::fu=1.35; //static class 常量定义位于实现文件内
  1. 编译器不允许“static整数型class常量”完成“in class”初值设定(即不允许在类内初始化静态变量)

如下面的:

class Solution {
	private:
		static const int num=5; //错误的,旧的编译器不允许在类内初始化静态变量
		int socre[num]; //会报错,无法在编译期间知道数组的大小
};

解决方法如下:用枚举来代替

class Solution {
	private:
		enum {num=5}; //令num成为5的一个记号名称
		int socre[num];
};
  1. 用#define实现简单函数功能,不推荐

解决方法:用内联函数代替
c++inline关键字详解

条款03:尽可能使用const

  1. const类型定义
char *p=greeting;  //non-const pointer,non-const data
const char *p=greeting;  //non-const pointer,const data
char* const p=greeting;  //const pointer,non-const data
const char*  const p=greeting;  //const pointer,const data

如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量

  1. 函数的返回值和参数尽量用const,避免一些不必要的错误

  2. const实施于成员函数
    目的:(1)使class接口容易理解,容易看出哪个函数可以改动对象内容而哪个函数不行
    (2)便于操作const对象

  3. 两个成员函数如果只是常量性不同,可以被重载

class TextBlock {
	public:
		const char& operator[](std::size_t position) const
			{	return text[position];	}	//const对象
		
		char& operator[](std::size_t position)
			{	return text[position];	}	//non-const对象 
	private:
		std::string text;
};

TextBlock  tb("hello");
std::cout<<tb[0]; //调用non-const

const TextBlock  ctb("world");
std::cout<<ctb[0]; //调用const
  1. bitwise错误
    const成员函数内更改成员变量(static除外)报错
    如下代码:
class CTextBlock {
	public:
		std::size_t length() const;
	private:
		char *pText;
		std::size_t textLength;
		bool lengthIsValid;
};
std::size_t CTextBlock::length() const
{
	if(!lengthIsValid) {
		textLength=std::strlen(pText);
		lengthIsValid=true;	//错误,在const成员函数内不能赋值给textLength和lengthIsValid
	}
	return textLength;
}

上述代码报错,const成员函数内改变了变量的值。

解决方法: 利用c++的一个与const相关的摆动场:mutable(可变的)。mutable释放掉non-static成员变量的bitwise-constness约束

class CTextBlock {
	public:
		std::size_t length() const;
	private:
		char* pText;
		mutable std::size_t textLength;
		mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const
{
	if(!lengthIsValid) 
	{
		textLength=std::strlen(pText);
		lengthIsValid=true;
	}
	return textLength;
}

关于mutable的学习参考这篇文章:【C++】Mutable关键字

  1. “const成员函数调用non-const成员函数”是一种错误的行为,因为对象有可能因此被改动

  2. 令non-const版本调用const版本可避免代码重复

6-7:非const函数可以调用const函数,const函数不可以调用非const函数

条款04:确定对象被使用前已先被初始化

  1. 确保每一个构造函数都将对象的每一个成员初始化
    c++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前

如下代码:

class Solution {
	private:
		int ax,bx;
	public:
		Solution(int a,int b);
};
Solution::Solution(int a,int b) {
	ax=a;
	bx=b;
}

在Solution构造函数内,ax和bx都不是被初始化,而是被赋值
初始化的发生时间更早,发生于这些成员的dafault构造函数被自动调用之时(比进入Solution构造函数本体的时间更早)

最佳的写法是用成员初始化列表

Solution::Solution(int a,int b):ax(a),bx(b)
{	}

成员初始化列表避免了调用default设初值,因此效率更高

c++有十分固定的“成员初始化次序”。次序总是相同的:base classes更早于其derived classes被初始化。并且class的成员变量总是以其声明次序被初始化

  1. “不同编译单元内定义之non-local static对象”的初始化次序
    对于static对象,它们的析构函数会在main()结束时被自动调用

条款05:了解c++默默编写并调用哪些函数

  1. 空类的默认生成哪些

如果写下:

class Empty {  };

编辑器就会默认写下如下:

class Empty {
	public:
		Empty()	{...} //default构造函数
		Empty(const Empty& rhs)  {...} //copy构造函数
		~Empty() {...} //析构函数
		Empty& operator=(const Empty& rhs) {...} //操作符函数
};

当这些函数被需要(被调用),它们才会被编译器创建出来 (只有被调用时,才会创建)

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

具体做法:所有编译器产出的函数都是public。为阻止这些函数被创建出来,可以自行声明它们为private。这样阻止了编译器暗自创建其专属版本,并且类对象也不能调用它们。(只有声明,不予实现)

条款07:为多态基类声明virtual析构函数

此原因的详细讲解,参考博客:c++多态与虚函数从根上解决中的为什么要把析构函数声明成虚函数;

记住:任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数

条款08:别让异常逃离析构函数

在析构函数中添加了一些处理函数,可能会出现异常

  1. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常(try),然后吞下它们不传播(catch)或结束程序(abort)

条款09:绝不在构造函数和析构过程中调用virtual函数

基类的构造函数和析构函数中不要有virtual函数。
例如,如下代码:

class Transaction {
	public:
		Transaction();
		virtual void log() const =0;
};
Transaction::Transaction()
{
	log();
}
class BuyTransaction:public Transaction {
	public:
		virtual void log() const;
};
BuyTansaction b;

上述代码,创建对象b时,首先会调用Transaction构造函数,但Transaction构造函数的最后一行调用log函数。这时候被调用的log函数是Transaction内的版本,不是BuyTransaction内的版本。(base class构造期间virtual函数绝不会下降到derived classes阶层)

记住:在derived class 对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息,也会把对象视为base class类型。(析构函数也一样)

条款10:令operator=返回一个reference to *this

为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参
x=y=z=15;

class Widget {
	public:
		Widget& operator=(const Widget& rhs) {
			...
			return* this;
		}
};

条款11:在operator=中处理“自我赋值”

w=w;
a[i]=a[j]; //潜在的自我赋值i=j

条款12:复制对象时勿忘其每一个成分

  1. 为derived class自己撰写copying函数时,要记得也要copy其base class的成员成分,让derived class的copy函数调用相应base class的copying函数 。即编写一个copying函数时,应确保(1)复制所有local成员变量(2)调用所有的base classes内的适当的copying函数

  2. 拷贝赋值函数(copy assignment 操作符)和拷贝构造函数(copy构造函数)不能互相调用。
    两种区别请看这篇博客:copy构造函数与copy assignment操作符区别
    如果发现两个函数之间有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用,这样的函数往往是private而且常被命名为init。这样便可以消除两者之间的代码重复了

条款13:以对象管理资源

为防止资源泄露,分配资源时,建议使用智能指针,在构造函数中获得资源并在析构函数中释放资源。
避免自己忘了delete对象

条款14:在资源管理类中小心copy行为

动态分配的资源进行复制时:引用计数,深拷贝

条款15:在资源管理类中提供对原始资源的访问

显示转换和隐式转换

条款16:成对使用new和delete时要采取相同形式

new -------------> delete
new [ ] ----------------> delete [ ]
要彼此搭配。

内存有一个(或更多)构造函数被调用,针对此内存就会有一个(或更多)析构函数被 调用

(注意底层实现)

条款17:以独立语句将newed对象置入智能指针

本条款要学习的知识核心是注意编译器在同一语句中,调用次序具有不确定性,不同语句中,调用次序确定。
上面的话什么意思?
请看下面代码:

int priority();
int processWidget(shared_ptr<Widget> pw, int priority);
processWidget(shared_prt<Widget> pw(new Widget), priority());

以上代码运行三个行为,各自是
1、运行priority()函数
2、运行new Widget
3、运行shared_ptr构造函数
大家知道这三个行为顺序吗?
我想没人敢非常自信的说顺序是什么,由于编译器在运行时,对以上三个行为的运行次序是不确定的。唯一确定的次序就是2行为在3行为之后。

假设,运行次序是2、1、3.那么当函数priority()调用出现异常。new Widget返回的指针还没来得及放入shared_ptr中。这样会造成内存泄露。

所以,我们在编程的时候,最好将紧密行为单独编写为单一语句

例如以下:

shared_prt<Widget> pw(new Widget);
processWidget(pw,priority());

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《effective c++》总结 的相关文章

  • 使用链表进行堆排序

    我想知道是否有人曾经使用链表进行堆排序 如果他们可以提供代码 我已经能够使用数组进行堆排序 但尝试在链表中进行排序似乎不切实际 而且在你知道的地方很痛苦 我必须为我正在做的项目实现链接列表 任何帮助将不胜感激 我也用C 答案是 你不想在链表
  • 格式说明符%02x

    我有一个简单的程序 include
  • 静态构造函数和 BeforeFieldInit?

    如果类型没有静态构造函数 则将执行字段初始值设定项 就在使用该类型之前 或者在某个时间点突发奇想 运行时 为什么这段代码 void Main start Dump Test EchoAndReturn Hello end Dump clas
  • 关于逻辑/算法的想法以及如何防止线程写入 Sql Server 中的竞争

    我有以下逻辑 public void InQueueTable DataTable Table int incomingRows Table Rows Count if incomingRows gt RowsThreshold async
  • EntityHydrate 任务失败

    我最近安装了 Visual Studio 11 Beta 和 Visual Studio 2010 之后 我无法在 Visual Studio 2010 中构建依赖于 PostSharp 的项目 因此我卸载了 Visual Studio 1
  • 为什么 C 程序使用 Scanf 给出奇怪的输出?

    我目前正在学习 C 编程 并且遇到了这个奇怪的输出 Program will try functionalities of the scanf function include
  • C# 中的 Stack<> 实现

    我最近一直在实现递归目录搜索实现 并且使用堆栈来跟踪路径元素 当我使用 string Join 连接路径元素时 我发现它们被颠倒了 当我调试该方法时 我查看了堆栈 发现堆栈内部数组中的元素本身是相反的 即最近 Push 的元素位于内部数组的
  • 2个对象,完全相同(除了命名空间)c#

    我正在使用第三方的一组网络服务 但遇到了一个小障碍 在我手动创建将每个属性从源复制到目标的方法之前 我想我应该在这里寻求更好的解决方案 我有 2 个对象 一个是 Customer CustomerParty 类型 另一个是 Appointm
  • 如何修复错误:“检测到无法访问的代码”

    我有以下代码 private string GetAnswer private int CountLeapYears DateTime startDate return count String answer GetAnswer Respo
  • C# 根据当前日期传递日期时间值

    我正在尝试根据 sql server 中的两个日期获取记录 Select from table where CreatedDate between StartDate and EndDate我通过了5 12 2010 and 5 12 20
  • OpenGL:如何检查用户是否支持glGenBuffers()?

    我检查了文档 它说 OpenGL 版本必须至少为 1 5 才能制作glGenBuffers 工作 用户使用的是1 5版本但是函数调用会导致崩溃 这是文档中的错误 还是用户的驱动程序问题 我正在用这个glGenBuffers 对于VBO 我如
  • 测量进程消耗的 CPU 时钟

    我用 C 语言编写了一个程序 它是作为研究结果创建的程序 我想计算程序消耗的确切 CPU 周期 精确的循环次数 知道我怎样才能找到它吗 The valgrind tool cachegrind valgrind tool cachegrin
  • 我们可以通过指针来改变const定义的对象的值吗?

    include
  • 调用 .ToArray() 时出现 ArgumentException

    我有一个经常被清除的列表 代码完全是这样的 VisitorAgent toPersist List
  • C:设置变量范围内所有位的最有效方法

    让我们来int举个例子 int SetBitWithinRange const unsigned from const unsigned to To be implemented SetBitWithinRange应该返回一个int其中所有
  • 如何编写一个接受 int 或 float 的 C 函数?

    我想用 C 语言创建一个扩展 Python 的函数 该函数可以接受 float 或 int 类型的输入 所以基本上 我想要f 5 and f 5 5 成为可接受的输入 我认为我不能使用if PyArg ParseTuple args i v
  • 如何高效计算连续数的数字积?

    我正在尝试计算数字序列中每个数字的数字乘积 例如 21 22 23 98 99 将会 2 4 6 72 81 为了降低复杂性 我只会考虑 连续的数字 http simple wikipedia org wiki Consecutive in
  • Streamwriter 覆盖 txt 文件中的文本

    有没有什么方法可以重新打开流写入器而不创建新的写入对象 因为此时 当调用 WriteOdd 时 streamwriter 正在覆盖在它之前调用的 WriteEven public void WriteEven StreamWriter wr
  • 如何在 C# 中获取 CMD/控制台编码

    我需要指定正确的代码页来使用 zip 库打包文件 正如我所见 我需要指定控制台编码 在我的例子中为 866 C Users User gt mode Status for device CON Lines 300 Columns 130 K
  • 嵌入式linux编写AT命令

    我在向 GSM 模块写入 AT 命令时遇到问题 当我使用 minicom b 115200 D dev ttySP0 term vt100 时它工作完美 但我不知道如何在 C 代码中做同样的事情 我没有收到任何错误 但模块对命令没有反应 有

随机推荐

  • 如何调试R程序(转载)

    R语言的调试重要性不言而喻 这段时间准备改进一个R的包 但由于接触R时间不长 中间的很多东西不懂 需要重新打包调试 以对里面的很多程序有深入了解 下面从几个方面分享一下我的收获 1 准备工作 a R软件的下载http cran r proj
  • 看完这篇Vue-element-admin,跟面试官聊骚没问题

    Vue element admin vue element admin 是一个后台前端解决方案 它基于 vue 和 element ui实现 它使用了最新的前端技术栈 内置了 i18 国际化解决方案 动态路由 权限验证 提炼了典型的业务模型
  • leetcode算法题--回文数

    原文链接 https leetcode cn com problems palindrome number bool isPalindrome int x if x lt 0 x 10 0 x 0 如何x为负 或者如果x的最后一个数为0 那
  • Go-获取今天、昨天、前天以及指定时间范围的格式化日期的开始和结束的时间戳

    func GetHourTime timeType start end string startTime endTime int64 switch timeType case 1 今天 dateNow time Now startTime
  • Python文件路径操作汇总,获取文件夹下的所有文件路径

    在Python编程过程中 我们常常有这样的需求 需要获取某一个文件目录下的所有文件 或获取文件目录下的所有指定后缀名的文件 亦或要求获取该文件所在的文件夹 还有可能需要替换文件名 删除文件夹 针对这些问题 本文系统总结了这些方法 以供大家参
  • 如何在React中写出有趣的数字滚动动画

    前段时间公司项目需要迭代更新 在这个更新的过程中就添加了一个大数据的数字滚动效果 这让我发现了一个既有趣上手又快的React插件 下面我们看看效果如何 官网的demo https inorganik github io countUp js
  • java中类名一定要和java文件名一致吗?

    结论 首先java中类名不一定要和java文件名一致 是否一致需要分情况 其次java中可以有多个类 但是最多只有一个类的类名和文件名相同 如果一个类被public修饰 那该类的类名必须和文件名相同 并且一个java文件中最多只有一个类被p
  • 学习matlab(十八)——小波分析

    小波分析克服了短时傅里叶变换在单分辨率上的缺陷 具有多分辨率分析的特点 下面对小波分析的基本理论进行介绍 包括连续小波变换 离散小波变换 多分辨分析和小波包分析 最后介绍在小波分析中常用的小波 小波变换采用随频率的时间 频率窗口 是进行信号
  • VC++ 设定Windows程序自启动(非管理员权限)

    Windows程序自启动方式有很多种 任务计划 注册表 启动项等等 创建任务计划和启动项 都需要程序具有管理员权限才能写入 在此略过 只介绍启动项方式 要实现启动项方式 必须要知道当前用户下启动项方式的目录 我们才好将程序或程序的快捷方式存
  • react 首页加载loading

    首页加载loading 放在 root里面 代码如下
  • C语言学习7——文件操作 文件的打开、读写、关闭 文件指针的使用

    关于C语言文件操作方面的知识 主要涉及到以下几个方面 文件的打开和关闭 文件的读写 文件的定位 下面我将详细讲解每个方面 文件的打开和关闭 在C语言中 需要使用fopen 函数来打开一个文件 该函数的原型如下 FILE fopen cons
  • secureCRT设置按钮栏

    secureCRT通过设置按钮栏可以把许多繁琐的命令简化成一个按钮 一步到位提高工作效率 详情如下 1 img http dl2 iteye com upload attachment 0094 3355 9805c794 f8e6 376
  • Day 5 - 元组字典习题

    1 声明一个字典保存一个学生的信息 学生信息中包括 姓名 年龄 成绩 单科 电话 性别 男 女 不明 stu name xxx age 18 grade 60 tel 12345678 sex 男 女 不明 2 声明一个列表 在列表中保存6
  • java-collection中的null,isEmpty

    只使用java utils包的isEmpty 第一种情况 实例化list 但是size为空 List
  • 【华为机试真题 JAVA】求满足条件的最长子串的长度-100

    编程题目 100分 求满足条件的最长子串的长度 2022 Q1 Q2考试题 时间限制 C C 1000ms 其他语言 2000ms 空间限制 C C 128MB 其他语言 256MB 64bit IO Format lld 本题可使用本地I
  • 一键排序Linux内存占用

    top c 大写M M
  • 华为鸿蒙os系统服务器炸了,华为鸿蒙OS短时间内威胁不到麒麟操作系统和统信UOS系统...

    麒麟操作系统和统信UOS系统是当前国产Linux操作系统的两座大山 它们鼎足而立 麒麟操作系统多用于高端部署 而统信UOS有更强大的Deepin社区支撑 比如麒麟操作系统可用于大型计算机 各类政企终端 而统信UOS受到华为笔记本的青睐 被预
  • 【教程】DGL中的子图分区函数partition_graph讲解

    转载请注明出处 小锋学长生活大爆炸 xfxuezhang cn 目录 函数形式 函数作用 函数内容 函数入参 函数返参 使用示例 实际上官方的函数解释中就已经非常详细了 函数形式 def partition graph g graph na
  • uniapp---- 微信小程序中获取当前地理位置(高德地图)

    uniapp 微信小程序中获取当前地理位置 高德地图 1 在manifest json中选择微信小程序配置 勾选上位置接口 2 在manifest json中选择源码视图 添加permission和requiredPrivateInfos
  • 《effective c++》总结

    文章目录 前言 条款02 尽量以const enum inline替换 define 条款03 尽可能使用const 条款04 确定对象被使用前已先被初始化 条款05 了解c 默默编写并调用哪些函数 条款06 若不想使用编译器自动生成的函数