【Effective C++】【Accustoming yourself to C++】

文章目录

  • term1:View C++as a federation of languages
    • (1)C
    • (2)Object-Oriented C++
    • (3)Template C++
    • (4)STL
  • term2:Prefer const,enums,inlines to #defines
    • (1)const:常量替换
    • (2)enums :枚举量替换
    • (3)inlines : 内置函数替换
  • term3:Use const whenever possible
    • (1)const 修饰指针,迭代器,函数返回值。
    • (2)const修饰成员函数
  • term4:Make sure that objects are initialized before they're used
    • (1)内置数据类型
    • (2)自定义数据类型
    • (3)使用local_static对象替换none_local_static对象
  • 4、总结
  • 5、参考

term1:View C++as a federation of languages

将C++看成一个语言联邦,C++有一些强烈的特性,为了方便理解,作者将其总结为次语言

(1)C

C++是以C为基础的,区块,语句,预处理器,内置数据类型,指针等诸多概念都是来自C。C++可以用来处理大型的复杂项目;而当前的C语言,主要用来编写51,32的MCU开发,底层驱动。

(2)Object-Oriented C++

class,封装,继承,多态,虚函数(动态绑定等等),这一部分是面向对象的设计在C++上最直接的体现。

(3)Template C++

涉及泛型编程,内置数种可供套用的函数或者类。良好的编程守则“惟Templete适用”。

(4)STL

STL是个模板库,主要涉及容器,算法和迭代器
在不同情况下使用适合的部分,可以使 C++ 实现高效编程。

term2:Prefer const,enums,inlines to #defines

这个条款的本质:在编程过程中多用编译器少用预处理器。因为#define是宏定义,他不被视作语言的一部分。而const,enums,inlines对应着3种不同的情况。

(1)const:常量替换

举个栗子:

#define PI 3.14159

这段代码在程序预处理过程中就执行了,这个记号名称PI,可能没有进入记号表,也从未被编译器看见。这样的话有一个不好的后果和一个潜在的风险。不好的后果就是如果程序编译报错,因为记号表没有收录这个记号名称,他会抱3.14159出错,会给后期的排查带来很大的困难;潜在的风险就是,他会直接替换,而不会对类型进行检查。
针对记号名称没有出现在记号表有一个解决办法:

//以一个常量替换上述的宏
const double PI = 3.14159;

除了以上的常量替换,有2种情况相对来说特殊一些:
(1)定义常量指针
简而言之,有必要将指针申明为const;
(2)class专属常量
class相较于struct更加强调作用域,#defines不能满足这个要求,而且不能提供任何封装性,所以对于有些专属常量,谨慎使用#defines。

class GamePlayer{
private:static const int NumTurns = 5; //声明式int scores[NumTurns];//to do sth;//高级编译器支持
}

如果程序中需要对class内的专属常量进行取地址的操作,需要另外提供定义式

const int GamePlayer::NumTurns; //定义式

旧式的编译器不支持在变量声明时获得初始值,所以按照一贯的习惯(变量在头文件声明,在实现文件中定义)。

class CostEstimate {static const double PI;			//常量声明//to do sth;					//位于头文件};
const doubleCostEstimate::PI = 3.14159;      //常量定义//位于实现文件内

但是如果遇到上面的例子,你要声明一个数组,编译器坚持要知道数组的大小。这时候就可以使用“the enum hack"补偿做法。

(2)enums :枚举量替换

一个属于枚举类型的数值,可以权充ints使用。

class GamePlayer{
private:enum{NumTurns = 5};int scores[NumTurns];...
}

(3)inlines : 内置函数替换

另一种情况就是实现宏,使用宏的时候,尤其要注意,表达式外要加上足够多的括号,不然,会有一些意想不到的错误。
举个栗子:

//a,b中的较大值给到f
#define CALL_WITH_MAX(a,b) f((a) >(b) ?(a) :(b))使用这个宏定义
int a = 5,b = 0;
CALL_WITH_MAX(++a,b);   	//a累加2次
CALL_WITH_MAX(++a,b+10);	//a累加1次

++的次数取决与和谁比较,这样的宏定义,就有很多隐藏的风险。因为宏不会像函数调用那样产生额外的开销,但与此同时会有不可预料的行为以及类型不安全的情况发生。
C++给出的解决方法是:templete inline函数

templete<typename T>
inline void callwithMax(const T&a,const T&b){f(a > b ? a: b);
}

但是#define在现阶段是无法被代替的,要充分了解他,并知悉他潜在的风险。

term3:Use const whenever possible

const允许你定义一个语义约束,指定一个"不该被改动的”对象。

(1)const 修饰指针,迭代器,函数返回值。

const修饰指针

char greeting[] = "Hello";
char* p = greeting;
const char* p = greeting; 		  //指向字符常量的指针,p指向的字符不能修改
char* const p = greeting; 		  //指向字符的常量指针,p本身的值不能够被修改
const char* const p = greeting;   //指向字符常量的常量指针,p指向的字符,p本身都不能被修改

为了方便理解:
在第一个声明中,p 是一个指针,它指向一个 const char。
在第二个声明中,p 是一个 const 指针,它指向一个 char。
在第三个声明中,p 是一个const指针,它指向一个const char。
const修饰迭代器
举个栗子:

std::vector<int> vec //整数类型的std::vector
...
const std::vector<int>::iterator iter = vec.begin();
//定义一个常量迭代器,相当于T* const
//迭代器不得指向不同的东西,但他所指向的东西的值是可以改动的;
*iter = 10; //正确
++iter;		//错误
std::vector<int>::const_iterator cIter = vec.begin();
//声明一个常量迭代器,相当于const T*
//迭代器不能修改容器中的元素,但是可以修改迭代器的指向;
*citer = 10;	//错误
++citer;		//正确

const修饰函数返回值

const int max(int a,int b){return (a > b ? a:b) ;
}
int c = 7;
max(a,b) = c;
//将一个常量的值,赋值给一个函数调用的返回值是没有意义的;
//const关键字在这里确保了函数不会进行任何可能影响程序状态
//的修改,并且使得函数的输出具有确定性。

(2)const修饰成员函数

首先有这样一个情况,2个成员函数如果只是常量性不同,可以被重载。
我举个栗子:

class TextBlock{
public:
...
const char& operator[](std::size_t position)const
{return text[position];}
char& operator[](std::size_t position)
{return text[position];}
private:
std::string text;
};TextBlock tb("hello");
std::cout << tb[0]; //调用non-const TextBlock::operator[]
TextBlock ctb("world");
std::cout << ctb[0]; //调用const TextBlock::operator[]

在介绍之前先引入两个概念:Bitwise Constness和Logical Constness
Bitwise Constness:
定义: “Bitwise constness” 关注的是对象在二进制表示层面是否保持不变。在这个概念中,如果对象被声明为 const,那么其底层二进制表示不应该发生变化。
实现: 通常通过将成员函数声明为 const 来实现 “bitwise constness”,以确保在 const 对象上调用这些函数时,对象的二进制表示不会发生变化。这样的函数不应修改任何非 mutable 的成员。
Logical Constness:
定义: “Logical constness” 关注的是对象的逻辑状态是否保持不变。在这个概念中,即使对象的物理状态(内部数据成员的值)发生了改变,它在外部的视角下仍然是常量。
实现: 通常通过将成员函数声明为 const,并在这些函数中不修改对象的逻辑状态。这允许在 const 对象上调用这些函数,保持对象在外部视角下的常量性。
区别:
“Bitwise constness” 关注对象底层二进制表示的变化,主要通过成员函数的 const 修饰符来实现,限制对数据成员的修改。
“Logical constness” 关注对象的逻辑状态的变化,同样通过成员函数的 const 修饰符来实现,限制对逻辑状态的修改。
在很多情况下,这两个概念是相关的,因为 “logical constness” 的实现通常涉及不修改对象的物理状态,从而保持 “bitwise constness”。然而,它们的重点略有不同,前者侧重于二进制表示,而后者更关注对象在逻辑上的不变性。

const成员函数不能改变(除static)成员变量的值,同理const对象不可以调用non-const函数。但是若成员变量是一个指针,仅仅改变指针指向的值却不改变指针地址,则不算事const函数,但能通过bitwise测试。
解决方法:使用mutable可以消除non-static成员变量的bitwise constness约束。
举一个栗子:

#include <iostream>
using namespace std;
class MyClass{
public://带有mutable 关键字的非静态成员变量mutable int mutableData;//构造初始化函数 mutableDataMyclass(int data):mutableData(data){}//const成员函数,逻辑上保持不变性,但允许修改mutableDataint getData() const{//修改mutableData,不违反const成员函数的规定mutableData++;return mutableData}
}
int main(){Myclass obj(42);cout << "Initial data: " <<obj.getData()<<endl;const Myclass constobj(100);cout << "Const object data: "<<constObj.getData()<<endl;return 0;
}

这个函数运行后的输出结果是:

Initial data: 43
Const object data: 101

在const 和non-const成员函数中避免重复
但是很遗憾mutable不能解决所有问题,假设你要做若干操作,同时放进const和non-const的operator[]中,就会发生代码重复的问题。
解决方法:利用2次转型,另non-const调用const进而避免代码重复
举个栗子:

 #include <iostream>class MyClass {
public:void commonFunction() {std::cout << "Common functionality\n";}void nonConstFunction() {commonFunction();std::cout << "Non-const function\n";}void constFunction() const {commonFunction();std::cout << "Const function\n";}
};int main() {MyClass obj;const MyClass constObj;// 避免代码重复的方式:通过类型转换obj.nonConstFunction();  // 直接调用非 const 成员函数const_cast<MyClass*>(&constObj)->nonConstFunction();  // 将 const 对象转换为非 const,并调用非 const 成员函数return 0;
}

term4:Make sure that objects are initialized before they’re used

为什么对象使用之前要进行初始化,举个例子

int x;

在某些语境之下,x会被初始化为0。但在其他语境中x的值是不能被保证的。这就会导致不明确的行为,进行给你的程序带来未知的风险,这是需要避免的。未知的情况千变万化,但解决的办法显得“大道至简”——在你使用对象之前,将其初始化即可。

(1)内置数据类型

int x = 0;
const char* text = "A C-style string"; //手工初始化

(2)自定义数据类型

使用成员初始值列表来替换赋值操作:

class PhoneNumber{...};
class ABEntry{
public:ABEntry(const std::string &name,const std::string& address,const std::list<PhoneNumber>& phones);
private:std::string theName;std::string theAddress;std::list<PhoneNumber> thePhones;int num;
}//赋值操作
ABEntry(const std::string &name,const std::string& address,const std::list<PhoneNumber>& phones)
{theName = name;theAddress = address;thePhones = phones;num = 0;				//赋值操作
}
/*赋值操作其实实施了2个步骤:
(1)调用default构造函数设立初值(default constructor)
(2)再对变量予以赋值(copy assignment)*/
//成员初始化值列表
ABEntry(const std::string &name,const std::string& address,const std::list<PhoneNumber>& phones)
:theName(name);theAddress(address);thePhones(phones);num(0;				//初始化操作
{	}		//构造函数不必有任何的动作

2个操作的结果是相同的,但是成员初值列的操作效率更高。赋值操作首先调用了default构造函数为其中的变量设立了初始值,然后立刻再对他们赋予新的数值。而成员初始化列表操作,只调用了一次copy构造函数是比较高效的。
需要注意的是,成员初始化列表的顺序是相对固定的,为了避免不必要的麻烦,初始化顺序可以遵循声明的次序。

(3)使用local_static对象替换none_local_static对象

书中说的比较晦涩,不同编译单元内定义之non-local static 对象的初始化次序,光看着就很头疼。首先要知道什么是local_static对象,什么是none_local_static对象。
在classes内部,函数内,file作用域内被声明为staic的对象被称为local_static对象;其他static对象被称为none_local_static对象。
而编译单元,指的是单一源码文件加上其所包含的头文件。而不同的编译单元,说明至少涉及2个编译单元;C++对于不同编译单元内的none_local_static对象的初始化次序并没有明确的定义。所以在程序运行过程中,某个编译单元内的none_local_static对象在初始化动作中使用了另一个编译单元内的某个none_local_static对象,他所用到的这个对象还没有初始化,这就带来了问题。
举个栗子:

编译单元A内:
class FileSystem{
public:...std:size_t numdisks() const;  //成员函数...
};
extern FileSystem tfs;
//预留给用户使用对象编译单元B内:
class Directory
public:
{Directory(params);...
};
Directory::Directory(params){...std::size_t disks = tfs.numDisks(); //使用tfs对象
}

之前讲过C++对于不同编译单元内的none_local_static对象的初始化次序并没有明确的定义,所以上述程序运行时会出现不可预料的错误。既然无法保证初始化次序,能否明确初始化顺序来达到这一目的呢,C++目前没有机制可以保证这一点,无法做到。
解决方法:将none_local_static对象搬到一个专属函数内(该对象在函数内被声明为static),这些函数返回一个reference指向他所含的对象。类似于单例模式的手法。
在使用过程中,用户直接调用这些函数,而不是直接调用对象。

编译单元A内:
class FileSystem{...};
FileSystem& tfs(){static FileSystem fs;return fs;
}编译单元B内:
class Directory{...};
Directory::Directory(params){...std::size_t disks = tfs().numDisks(); ...
}
Directory& tempDir(){static Directory td;return td;
}

这在单线程中是ok的,如果是在多线程系统中仍然有一定的不确定性,因为他在调用对象的过程中,在等待他进行初始化。这种情况是比较危险的,会有意想不到的错误发生。

4、总结

书山有路勤为径,学海无涯苦作舟。

5、参考

5.1 《Effective C++》

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

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

相关文章

【Qt之QNetworkAccessManager】概述及示例

概述 QNetworkAccessManager类允许应用程序发送网络请求和接收应答 网络访问API是围绕一个QNetworkAccessManager对象构建的&#xff0c;该对象为它发送的请求保存通用配置和设置。它包含代理和缓存配置&#xff0c;以及与此类问题相关的信号&#xff0c;以及可用于监视网络操…

Antd Select 添加中框

默认antd 的 Select中间并没有竖框&#xff0c;但是ui design设计了&#xff0c;所以记录一下如何添加 默认&#xff1a; CSS&#xff1a; .custom-select-suffix-icon {display: flex;align-items: center; }.custom-select-suffix-icon::before {content: ;height: 31px; …

持续学习动态架构算法LwF(Learning without Forgetting )解读总结与代码注释

0.持续学习 持续学习相关文章汇总&#xff0c;包含论文地址、代码地址、具体分析解读地址 1.LwF算法相关链接 论文地址代码地址 2.基本想法 针对问题&#xff1a;在无法获得原始任务训练数据的情况下&#xff0c;适合使视觉系统适应新任务&#xff0c;并且保证其在旧任务上…

在Vue开发中v-if指令和v-show指令的使用介绍和区别及使用场景

一、条件渲染 v-if v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。 <h1 v-if"awesome">Vue is awesome!</h1>v-else 你也可以使用 v-else 为 v-if 添加一个“else 区块”。 <h1 v-if"awesome"&g…

什么品牌的猫粮比较好?主食冻干猫粮品牌十大排行

咱们养猫人每天最愁的就是咋给自家猫咪选一款优质的猫粮&#xff0c;让猫主子吃了健健康康的。早些年大多养猫人的标准就是盯着进口的买&#xff0c;所以之前进口猫粮的销量一直遥遥领先&#xff0c;感觉品控也严&#xff0c;也就放心大胆的冲进口猫粮了&#xff0c;但近期百利…

34.用过JavaConfig方式的spring配置吗?它是如何替代xml的?

用过JavaConfig方式的spring配置吗?它是如何替代xml的? 基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。 以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。 另一个例子是@Bean注解,它…

【开题报告】基于SpringBoot的艺术类家教平台的设计与实现

1.选题背景 随着人们生活水平的提高和文化教育的重视&#xff0c;越来越多的家长开始注重孩子的艺术教育&#xff0c;希望让孩子在绘画、音乐、舞蹈等方面得到更加专业的指导和培养。 然而&#xff0c;市场上现有的艺术类家教资源不够丰富和专业&#xff0c;家长们很难找到合…

flink中如何把DB大表的配置数据加载到内存中对数据流进行增强处理

背景 在处理flink的数据流时&#xff0c;比如处理商品流时&#xff0c;一般我们从kafka中只拿到了商品id&#xff0c;此时我们需要把商品的其他配置信息比如品牌品类等也拿到&#xff0c;此时就需要关联上外部配置表来达到丰富数据流的目的&#xff0c;如果外部配置表很大&…

我的隐私计算学习——隐私集合求交(1)

笔记内容来自多本书籍、学术资料、白皮书及ChatGPT等工具&#xff0c;经由自己阅读后整理而成。 &#xff08;一&#xff09;PSI的介绍 隐私计算关键技术&#xff1a;隐私集合求交&#xff08;PSI&#xff09;原理介绍 隐私计算关键技术&#xff1a;隐私集合求交&#xff08…

在系统中查找重复文件

说在前面 &#x1f388;不知道大家对于算法的学习是一个怎样的心态呢&#xff1f;为了面试还是因为兴趣&#xff1f;不管是出于什么原因&#xff0c;算法学习需要持续保持。 一、题目描述 给你一个目录信息列表 paths &#xff0c;包括目录路径&#xff0c;以及该目录中的所有…

事务--03---TCC空回滚、悬挂、幂等解决方案

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Seata TCC 模式设计思路TCC存在的问题1、空回滚以及解决方案解决方案&#xff1a; 2、幂等问题以及解决方案解决方案&#xff1a; 3、悬挂问题以及解决方案解决方案…

PCIe设备热插拔-理论篇

硬件层面理解热插拔 PRSNT1#和PRSNT2#信号与PCIe设备的热插拔相关。在基于PCIe总线的Add-in 卡中&#xff0c;PRSNT1# 和PRSNT2#信号直接相连&#xff0c;而在处理器主板中&#xff0c;PRSNT1#信号接地&#xff0c;而PRSNT2#信号通过上 拉电阻接为高。 不同的处理器系统处理PC…

【Mysql】InnoDB的表空间(九)

概述 表空间是一个在 InnoDB 中比较抽象的概念&#xff0c;对于系统表空间来说&#xff0c;对应着文件系统中一个或多个实际文件&#xff1b;而对于每个独立表空间来说&#xff0c;对应着文件系统中一个名为表名.ibd 的实际文件。可以把表空间想象成由很多个页组成的池子&…

【Unity 实用工具篇】| 游戏多语言解决方案,官方插件Localization 实现本地化及多种语言切换

前言 【Unity 实用工具篇】| 游戏多语言解决方案&#xff0c;官方插件Localization 实现本地化及多种语言切换一、多语言本地化插件 Localization1.1 介绍1.2 效果展示1.3 使用说明 二、 插件导入并配置2.1 安装 Localization2.2 全局配置 三、多语言映射表3.1 创建多语言文本配…

Python之面向对象程序设计

文章目录 1、类定义2、创建实例3、属性4、方法5、继承6、多态7、组合8、导入类 1、类定义 面向对象程序设计的一个关键性观念是将数据以及对数据的操作封装在一起&#xff0c;组成一个相互依存、不可分割的整体&#xff0c;即对象。对于相同类型的对象进行分类、抽象后&#x…

字符处理 C语言xdoj52

问题描述 从键盘输入一个字符&#xff0c;若为小写字母&#xff0c;则输出其对应的大写字母&#xff1b;若为大写字母&#xff0c;则输出对应的小写字母&#xff1b;其他字符原样输出。 输入说明 输入一个字符 输出说明 输出一个字符 输入样例 样例1输入 a 样例…

分布式块存储 ZBS 的自主研发之旅|元数据管理

重点内容 元数据管理十分重要&#xff0c;犹如整个存储系统的“大黄页”&#xff0c;如果元数据操作出现性能瓶颈&#xff0c;将严重影响存储系统的整体性能。如何提升元数据处理速度与高可用是元数据管理的挑战之一。SmartX 分布式存储 ZBS 采用 Log Replication 的机制&…

安装ingress-nginx

1、下载helm压缩包 wget https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz2、解压 [rootk8s-master-10 helm]# tar -zxvf helm-v3.2.3-linux-amd64.tar.gz linux-amd64/ linux-amd64/README.md linux-amd64/LICENSE linux-amd64/helm3、进入linux-amd64 [rootk8s-maste…

论文修改润色平台 PaperBERT

大家好&#xff0c;今天来聊聊论文修改润色平台&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 标题&#xff1a;论文修改润色平台――助力学术研究&#xff0c;提升论文质量 一、引言 在学术研究中&am…

复制粘贴——QT实现原理

复制粘贴——QT实现原理 QT 剪贴板相关类 QClipboard 对外通用的剪贴板类&#xff0c;一般通过QGuiApplication::clipboard() 来获取对应的剪贴板实例。 // qtbase/src/gui/kernel/qclipboard.h class Q_GUI_EXPORT QClipboard : public QObject {Q_OBJECT private:explici…