C++代码优化(1):条款1~4

news2025/2/22 0:01:37

 "不要烟火不要星光,只要问问你内心的想法。"


        本栏仅仅 由对《Effictive C++》其中的一系列条款,掺杂着自己一些愚钝的理解而写的。

---前言

 条款01:

尽量以const、enum、inline 替换 #define

在谈及上述好几个关键字 与define宏定义的关系,我们不得不谈谈他们各自 在编码时的使用情况。

(1)小试牛刀地回顾

①const 与 enum 

#include<iostream>
using namespace std;

enum Color
{
	Red,
	Blow,
	Black
};

int main()
{
	const int a = 10;
	cout << "a:" << a << endl;
	cout << "Red:" << Red << endl;

	//Red = 3;
	//a = 3;

	return 0;
}

 ②inline 与 define

上述的enum、const 或许我们都在C中经常看到。 inline是个什么东西呢?

inline是一种“用于实现”的关键字,而不是一种“用于声明”的关键字。

想想一个场景:

        一个函数其代码量短小,但是很多地方都会调用,也就是会被反复调用的函数,必然带来的是 main函数为其 反复开辟、销毁的栈帧损耗。 那么如何避免开辟栈帧了? 

        在C中提供了一种宏函数调用的方式!

int Add(int x, int y)
{
	return x + y;
}

#define ADD(x,y) ((x)+(y))

int main()
{
	int x = 4;
	int y = 5;
	cout << "普通函数加法的总和为:" << Add(1, 2) << endl;
	cout << "宏函数加法的总和为:" << ADD(1, 2) << endl;
	return 0;
}

它们的结果完完全全一样的,但是宏函数可以避免 栈空间反复的 开辟、销毁。 

 那么 宏函数就可以肆无忌惮地乱使用了吗? 答案是肯定不是! 否则也不会有inline

宏函数的缺陷 

1.没有检查类型

2.不支持调试

3.使用复杂 

当然前两个也无需多说。宏函数不支持调试的根本原因就在于,宏函数在预处理完之后已经完成了文本替换。

其次,你也可以明显察觉到 当上述代码定义宏函数的时候 需要 加上很多括号。

举个简单的例子:

也许C++正是看到了define宏函数存在的缺点,因此设计了一个新的关键字 inline;

并且还 不如像使用宏函数那么拘谨! 就当做一个普通的函数即可!

 (2)开胃菜

①错误信息与数据冗余

上面的一节,仅仅做了一些要理解本条款的预备知识。

书上也说,老师也说,预处理阶段要经历的几个步骤; 但是没什么感性的认识!我们怎么知道这些是什么个东东???

去注释、宏替换、条件编译、头文件展开。

  

尽管const、enum、宏定义 都作为一个语言常量! 都保障其数值的 常量性。

但:

 ①如果 这个宏定义的数值(DEBUG) 在程序运行的 获得一个错误信息却得到一个"2"! 而非const 定义下的 DEBUG。如果这份代码不是你写的! 你肯定对这个数字"2"的来源无从下手! 因为其根本没有进入 记号表。

 ②此外,编译器看到 #define DEBUG 只会盲目地进行替换(它可不会太智能)。从而导致一份代码里 出现多份"2".相反 如果改用const int DEBUG 则只需要有一份记录即可。

②封装性

C++引入类这个概念,对封装的要求 一定比C语言高得多。

#define 与 const 

注:

枚举与const:

 

上述不懂? 那就记住!

1. 对于单纯常量,最好以const对象 或者 enums替换 define。

2.形似函数的宏,最好用inline 替代。


条款03:

尽可能使用const 

(1)小试牛刀地回顾

const是一个非常神奇 且用途十分广泛的关键字。它既可以告诉 编译器,你是会否应该让某个变量保持不变,也是告诉程序员,哪个变量由const 修饰 是不变的!

恐怕各位初学const的时候 尤其是到指针那章节! 一定会被这个const 弄得晕头转向。

char greetring[] = "Hello";             //non-const pointer,non-const data
char* p1 = greetring;                   //non-const pointer,const data
const char* p2 = greetring;             //const pointer,non-const data
char* const p3 = greetring;             //non-const pointer,const data
const char* const p4 = greetring;       //const pointer,const data
                                                                ---样例取自《Effective C++》

分清 const 到底是修饰的是 指针自身 还是指针所指向的物

        就是区分 const是在 " * " 的左边还是右边;

 (2)开胃菜

①const 与 迭代器 

 在C++中,STL大量使用迭代器(类似于指针一样,但不是真的指针)。

可能接触过STL容器的,一定更为头疼里面const的 “漫天使用”。

#include<vector>
#include<iostream>
using namespace std;
int main()
{
	vector<int> v1;
	v1.push_back(1);
	vector<int> v2;
	v1.push_back(1);

	//1.如果你希望得到一个迭代器
	//指向的对象是 可以被修改 但是自己不能指向不同的东西
	//你就需要一个 T* const
	vector<int>::iterator it = v1.begin();
	cout << ++(*it) << endl;

	//2.如果你希望得到一个迭代器
	// 指向的对象是 不能被修改的!
	//你就需要 const T*
	vector<int>::const_iterator cit = v1.cbegin(); //v1.begin()
	cout << ++(*cit) << endl;

	return 0;
}

注:

const迭代器 并不是给 迭代器+const!

②const 与 函数返回值

有些函数 的声明 会让函数 就返回一个const 对象! 但是可能 有人会觉得多此一举。 

多的也不用多说了。 很多时候 我们或许将“ == ” 写成了 " = " 很明显,如果你对该对象施加了const 那么很容易 编译器就会给我们立马 找出错误点! 而不是让我们盯着眼花缭乱的 代码胡乱翻找。 

③const对象、非const对象 与 成员函数 

我们先来看看以下代码;

#include<string>
#include<iostream>
using namepace std;
class TextBlock
{
public:
	TextBlock(const char* str)
		:_text(str)
	{}
	//...
	const char& operator[](size_t pos)const  //这两个是operator 函数重载 
	{
		return _text[pos];
	}

	char& operator[](size_t pos)
	{
		return _text[pos];
	}
private:
	string _text;
};

int main()
{
	TextBlock tb("Hello");
	cout << tb[1] << endl;


	const TextBlock ctb("World");
	cout << ctb[1] << endl;

	return 0;
}

        上述的境况 仅仅是operator[] 的返回类型以致,"由const 版 之 operator[] 返回的" const char& 进行赋值动作。

        同样,对于non-const成员 可以调用const成员函数 ,这显然是可以进容忍的! 上述问题的根本在于,const成员 去调用了 non-const成员函数。因此,const成员函数 

当然! operator[]的返回值 一定是char& (reference to char) 而非 'char' (这样就变成了右值),否则 也无法通过编译。

④如何定义成员函数 const属性? 

        1.对于成员变量的const属性 那即是 —— 不能修改const修饰的成员变量,反之 non-const的成员变量 可以轻易地更改。

        2.因此一派(bitwise const)认为: 成员函数的const属性 也和成员变量的const属性一样。

不能对 const成员函数里的 任何成员变量做任何修改! 显然,这很符合 毒地const常良性的定义。

但事实是这样吗? 我们看看下面代码;

#include<iostream>
using namespace std;
class TextBlock
{
public:
	TextBlock(char* str)
		:_ptext(str)
	{}
	//...
	char& operator[](size_t pos) const
	{
		return _ptext[pos];
	}
	void Print()const
	{
		cout << _ptext << endl;
	}
private:
	char* _ptext;
};

int main()
{
	char ch[] = "hello";
	const TextBlock ctb(ch);
	char* pc = &ctb[0];
	cout << "更改前-----------" << endl;
	ctb.Print();
	*pc = 'W';
	cout << "更改后-----------" << endl;
	ctb.Print();
	return 0;
}

不仅如此,难道const成员函数里的 成员变量任何改变都不能容忍吗?

logical constness就拥护:

一个const成员函数可以修改它 所处理对象内的某些bit!但 这种情况 只允许出现在 检测不出来才得以如此。

mutable(C++11 后引入的新宠)

class TextBlock
{
public:
	TextBlock(char* str)
				:_ptext(str)
			{}
	//...
	size_t lenth()const
	{
		if (!lenghValid)
		{
			_textlength = strlen(_ptext);
			lenghValid = true;
		}

		return _textlength;
	}
private:
	char* _ptext;
	mutable size_t _textlength; //释放 bitwise constness
	mutable bool lenghValid;
};

⑤const 和 non-const避免重复 

上面对于bitwise constness 一刀切的问题,mutable 可以解决。 但不能解决所有问题。

回到最初的问题;

C++11提供了一个新的方法,即:常量性转除(cast away constness);

然而事实上,使用 转型(casting) 是一个糟糕的想法! 

但是本例 中,仅仅是为了解决 代码冗余 给人带来的不快。

class TextBlocK
{
public:
	const char& operator[](size_t pos)const
	{
		//..

		return text[pos];
	}

	char& operator[](size_t pos)
	{
		return  //最外层是 去掉const属性
			const_cast<char&>(
				//加上const 属性 去调用 const版本的 operator[] 更加明确!
				static_cast<const TextBlocK&>(*this)[pos] //调用[]
				);
	}
private:
	string text;
};

        忽然,你拍一脑袋! 为什么不能让const const operator[] 版本去 复用 非const operator[]?

        显然! 你肯定不能被容忍这样干! 因为const成员函数 承诺不会改变 其所处理对象的任何状态,非const成员函数 则不会保证这样。

        同样,如果你非要这样干, 那不妨给const对象 const_cast 给它释放掉const 的属性。

当然 这等于 “脱了裤子 ,打屁”。

        想说的也就是,非const对象 可以 去调用非const成员函数 也可以 去调用 const成员函数,因为 非const对象可以对自己的 状态 选择不做任何修改。

上述不懂?那就记住!

 1.将某些变量声明为const 可以让编译器帮助你 检测错误。 其次 const可以施加于 作用域的任何对象,函数参数、函数返回的类型、成员函数自己身上。

 2.编译器多半强制实施 bitwise constness,mutable有时可为你提供一种解决方法。

 3.当const 与 non-const成员函数 有着实质性的等价实现时,令non-const成员函数 调用 const成员函数,未尝不是一个值得考虑的选择。


条款4:

确定对象被使用前 已先被初始化。 

(1)小试牛刀地回顾

class Point
{
	int x, y;
	//.....
};

int main()
{
	Point p;
	return 0;
}

因此,处理这些的最佳办法就是: 永远在使用这些对象、使用这些内置类型之前 进行初始化。 

(2)开胃菜 

①区分 初始化 和 赋值 

这里提个问题:类的构造函数 是赋值 还是 初始化?

         C++规定,对象的成员变量初始化的动作发生在,进入构造函数本体之前。所以在ABEntry进入构造函数之前 其中等待Name、Address(自定义类型)已经 初始化好了。构造函数调用时,已经是对ABEntry 进行赋值 动作了。  但可以看到, 构造函数对Age(内置类型)就并没有那么友好, 里面是随机值!并没对其进行初始化! 

        对上述ABEntry的最佳的写法时,利用初始化列表(member initialzation list);从而替换 赋值的那些 琐碎的操作。 

   

        显然,第二个版本避免了 由default对它进行重新赋值的多余动作。而那些实参 都被初始化列表拿去作为 实参 进行构造拷贝了。 当然Name \ Address 分别为 name\address的 初值copy。

        当然,也有无参的构造函数(默认构造)。都受便 于编译器对于自定义类型的处理方式:

②初始化列表 与 const、reference 

如果没有缺省参数,我们对于const 或者 reference 应该怎么初始化?  难道任让它们各自飞翔?

        对于内置类型,可能赋值、初始化的 代价不是很大! 但是 例如const 、 reference 它们一定就需要初值,而不是赋值!

         最简单的做法就是,所有成员变量的初始化 都交由初始化列表! "这样做有时候绝对必要,且往往比赋值要高 "

        但是,也有classes 拥有多个构造函数和多个成员变量,那样每个构造函数都 写初始化列表,未免有些 重复、无聊。 因此 ,对于 “初始化 与 赋值等价”的成员而言,也可以 给它们的赋值操作 抽象成一个 函数(通常为 private),往往使得 代码更加美观。 

       构造函数 无非只是一种 “伪初始化”,而不是初始化列表的“真初始化”那样 更可取。

③初始化列表 与  static

(这里讨论的问题,更加倾向于 工程性的情况,可以进行选读)

static声明的对象,其寿命是随程序的!

我们往往称 一个 在函数内的static 对象为 local static 其他static对象为 non-local  static 

 如何解决 “定义于不同编译单元内的 non-local static对象 无明确定义次序”?

简单来说,就是两个源码文件,各自内部都有一个 non-local static 的对象。 如果其中一个源码文件中的 non-local static的初始化 需要借助另外一个文件里的 non-local static对象, 但事实是,你根本不知道 要借助的该对象 是否已经初始化完毕了!  毕竟C++并没有该 明确的次序定义!

我们来看看下面代码;

class FileSystem
{
public:
	//...
	size_t numDisks() const; //统计Disks众多成员
	//...
};

extern FileSystem tfs; //这是一个全局变量 预备给客户使用的!


class Directory
{
public:
	Directory()
	{
		size_t disks = tfs.numDisks();
	}
};

Directory tempDir;

 这样不管是 tempDir依赖tfs  还是之后tempDir被其他依赖。 都会在用其被调用的地方,首先被初始化。并且,不会再经历、调用non-local static 对象的 “仿真函数”,也绝不会引发 构造、析构带来的性能损耗。

上述不懂?那就记住:
 1.为内置类型对对象进行手工初始化,因为C++不保证初始化他们。

 2.任何类的 成员变量初始化。最好都使用初始化列表。而不要在构造函数内就 进行赋值。并且,初始化列表里的排序,最好与变量声明的次序一致。

 3.为免除"跨编译单元之初始化次序"问题,请以local static 对象 替代 non-local static 对象。


本篇也就到此为止。 此外,“每个条款的最后模块都也是该本书最后的总结

希望对读者能有所帮助。感谢你的阅读~

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

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

相关文章

Intel i226芯片4端口千兆以太网卡 2.5GPoE工业相机图像采集卡介绍

PCIe-8634图像采集卡是一款基于 Intel i226芯片高性能千兆工业级 PCIe*4 POE网卡,具有传输速度高、兼容性强、性能稳定的特点&#xff0c;可广泛主要应用于网络高清监控、无线覆盖、工业自动化等领域。 RJ45千兆网络采用4 k Intel226千兆网络芯片,支持10/100/1000/2500Mbps传输…

microservices 简介

油鹳视频 Microservices explained - the What, Why and How? https://www.youtube.com/watch?vrv4LlmLmVWk&t2s microservices 是一种软件体系结构&#xff0c; microservices architecture(微服务架构) 是与传统的 monolithic architecture(整体式架构&#xff0c;一体…

微信转盘抽奖小程序如何制作?

微信转盘抽奖小程序如何制作&#xff1f;大概需要多少钱&#xff1f; 价格方面&#xff0c;平台按年收费&#xff0c;一年1498至2498元。 明码标价&#xff0c;7天退款制度&#xff0c;随时退。 微信转盘抽奖小程序如何制作步骤: 1.进入第三方微信转盘抽奖小程序制作平台官…

计算机结构体系:指令调度与循环展开题型 (非凭感觉的方法详解)

文章目录题目初始分析1.确定所使用的各个寄存器的作用2.将循环体内容语句和控制语句分开3.找出每一条循环体内容指令的代价并排序4.找出每一条循环体控制指令的代价并排序5.基于贪婪算法的最优循环展开体系结构这门课程中&#xff0c;指令调度和循环展开可以说是课程最困难的地…

负载均衡反向代理下的webshell

负载均衡(Load Balance) 是一种廉价的扩容的方案&#xff0c;它的概念不是本文的重点&#xff0c;不知道的可以去查资料学习。实现负载均衡的方式有很多种&#xff0c;比如 DNS 方式、HTTP 重定向方式、IP 负载均衡方式、反向代理方式等等。 其中像 HTTP 重定向方式、DNS方式等…

BioPython ② | 面向对象编程Object Oriented Programming

BioPython ② | Python面向对象编程 题目要求 定义分子类&#xff08;Molecule&#xff09;作为基类&#xff0c;包含集合elements和weight作为其属性&#xff0c;用初始化函数&#xff0c;将elements初始化为空集&#xff0c;weight初始化为None&#xff1b;定义show_weight…

进阶 - Git的远程仓库

本篇文章&#xff0c;是基于我自用Windows&#xff08;Win10&#xff09;系统当做示例演示 Git的远程仓库 之前我们一直在探讨 Git 的一些命令&#xff0c;也提及了仓库的概念。如果只是在一个仓库里管理文件历史&#xff0c;Git 和 SVN 真没啥区别。 Git 是分布式版本控制系…

02 stata入门【计量经济学及stata应用】

安装&#xff1a;建议直接在微信搜索&#xff0c;很多公众号有安装包资源及下载教程 不同版本在基本功能上无较大差异&#xff0c;一般为SE&#xff0c;更为专业MP&#xff0c;只是在处理变量个数或容量等存在不同 界面 历史命令&#xff1b;结果窗口&命令窗口&#xff1b…

字节跳动岗位薪酬体系曝光,看完感叹:不服不行,想高薪还得是学这个。。。。

目录&#xff1a;导读 前言 01岗位职级 02岗位薪酬 03绩效考核与晋升 大厂软件测试岗经验分享 一、软件测试基础篇&#xff1a;2022版 二、MySQL篇&#xff1a;2022版 三、 Linux篇&#xff1a;2022版 四、 Web测试 五、接口测试 六、APP测试 七、性能测试 八、Se…

Nacos一些理解

下载Mysql //下载mysql docker pull mysql:5.7 //运行容器 docker run -p 3306:3306 --name mysql -v /home/mysql/log:/var/log/mysql -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORDxx -d mysql:5.7 将导入数据库 1.新建数据库 nacos /*Navicat Premiu…

HyperMesh宝典 | 跟着老师学,你也可以做好二次开发

说到二次开发&#xff0c;你的脑海里是不是浮现出了“码农”两个字&#xff1f;有人可能会问&#xff0c;码农又是什么&#xff1f; 你是不是觉得二次开发这种码农干的事情感觉起来也太困难了吧&#xff1f; 其实有时候二次开发真的很简单&#xff0c;懂一点二次开发会让你的工…

机器学习中的数学原理——多项式回归

这个专栏主要是用来分享一下我在机器学习中的学习笔记及一些感悟&#xff0c;也希望对你的学习有帮助哦&#xff01;感兴趣的小伙伴欢迎私信或者评论区留言&#xff01;这一篇就更新一下《白话机器学习中的数学——多项式回归》&#xff01; 目录 一、什么多项式回归 二、算法…

Java#33(IO流)

目录 一.IO流 作用: (对于程序而言)用于读写数据(本地数据, 网络数据) 二.IO流体系 1.字节输出流 2.字节输入流 3.文件拷贝 3.字符集 字符流 字符输入流 字符输出流 缓冲流 转换流 序列化流 ​编辑反序列流 打印流 一.IO流 I: input O: output 流: 想流…

Linux下创建动态链接库与静态链接库

动态链接库 Linux下的动态链接库文件扩展名为so&#xff0c;可以用多个文件生成一个动态链接库。 在头文件中定义三个函数&#xff0c;三个函数分别于三个cpp文件中实现。 将三个cpp文件编译成动态库libdynamic.so -fPIC表示编译为位置独立的代码&#xff0c;如果不选择默…

使用YOLOv5练自己的数据集

说明 上次使用学习了如何运行yolov5检测自己的数据&#xff0c;这次学习yolov5如何训练自己的数据集 本次记录如何使用yolov5训练自己的数据集以及遇到报错解决方案 数据 使用数据&#xff1a;水果数据集 数据包含了png图片和相应的标注文件 切分数据 代码如下&#xff1…

[ vulhub漏洞复现篇 ] solr 远程命令执行(CVE-2019-0193)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Kubernetes的基础概念

一、Kubernetes的介绍 用于自动部署、扩展和管理"容器化&#xff08;containerized&#xff09;应用程序"的开源系统 可以理解成K8S是负责自动化运维管理多个容器化程序&#xff08;比如Docker&#xff09;的集群&#xff0c;是一个生态极其丰富的容器编排框架工具 …

【现场问题】zookeeper的集群,其中两台起来了,但是另外一台就是起不来,或者起来也是standalone

zookeeper问题现象集群的配置zookeeper的启动以及状态查看问题报错点问题现象 1、总共三台机器&#xff0c;node92&#xff0c;node93&#xff0c;node94 其中node92做了拉起&#xff0c;但是node93&#xff08;leader&#xff09;和node94&#xff08;follower&#xff09;&a…

浅谈MVVM ——Model-View-View-Model

浅谈MVVM ——Model-View-View-Model 前言&#xff1a; 笔者最近接到这样一个机器人项目&#xff0c;接入他们的机器人平台做二次开发&#xff0c;开发自己的opk移植到机器人上做医院展示使用。opk是必须使用机器人厂家提供的经过一定封装过的&#xff0c;该opk的架构是MVVM。…

48W字?GitHub上下载量破百万的阿里:图解Java、网络、算法笔记

Java基础这个东西&#xff0c;无论在哪个公司都被看得尤为重要&#xff0c;而面试中关于基础的问题也是层出不穷。所以基础可以说是重中之重&#xff0c;当你的基础打牢了&#xff0c;其他的也就没有那么重要了。 说到基础&#xff0c;无外乎操作系统&#xff0c;网络&#xf…