Effective-C++阅读解析条款(条款二:尽量以const,enum,inline替换#define)

个人主页:Lei宝啊 

愿所有美好如期而遇


书中说这个条款或许改为“宁可以编译器替换预处理器”比较好,这句话在我看来原因是这样的:

如果我们有这样一个宏(假设写这个宏的人比较粗心):#define Add(x, y)  x + y

我们本意是想得到x+y的值,但是如果我们在这样一个表达式中:int ret = 3 * Add(4 + 5);  我们预期的结果应该是27,但是实际上我们得到的结果是17,这就是由于在预处理阶段进行了宏替换,表达式就成了这样:ret  =  3 * 4 + 5; 

又或者是这样:#define NUM 1.653,如果我们使用这个常量但是获得一个编译错误信息时,错误信息可能会提到1.653而不是NUM,因为在预处理阶段就已经进行了宏替换,所以在编译阶段已经没有NUM了。如果这个宏甚至不是我们写的,那么如果报错1.653,那我们一定对这个值没有概念,不知道他是哪里来的,所以我们不推荐使用宏,而是使用const,enum,inline等,因为他们都是在编译阶段的,在编译后会进入符号表。所以上面的宏我们可以替换成这样:const double NUM = 1.653; 

当我们使用常量替换宏,有两种特殊情况需要我们注意:

第一就是定义常量指针,由于常量定义通常被放在头文件内,因此有必要将指针和他所指的值都声明为const,假设我们定义一个常量的字符串,我们使用指针指向,我们通常这样写:

const char* const authorname = "Scot Meyers";

并且书中提到,string对象通常比char*更加合适,因为我们是可以使用char*来构造string对象,并且string对象使用起来更加方便,所以他推荐这样定义上面的authorname变量:

const std::string authorname("Scot Meyers");这样authorname这个变量也就同样不能对这个字符串进行修改了。

第二个需要注意的就是class的专属常量。为了将常量的作用域限制在class内,所以必须让其成为class内的一个成员:而常量我们没有必要让每个对象都拥有一份,所以最好是让所有对象都能够共享他,所以我们让他成为一个static成员,就像这样:

class GamePlayer
{
private:static const int Num = 5;int scores[Num];
}

这里博主要提醒一下,静态成员变量只能在类外进行初始化,而上面这个初始化是一个特殊的例子,仅仅只有const int这样的静态成员变量可以在类内这样进行声明。

并且我们书中提到,通常C++要求我们对所使用的任何一个东西提供定义式,但如果他是个class专属常量并且是static,并且还是整型,那么只要不取他们的地址,我们就可以使用他们并且无需提供他们的定义式,就像这样:

class Gameplayer
{
public:static const int Num = 5;int scores[Num];
};int main()
{cout << Gameplayer::Num << endl;return 0;
}

但是如果我们需要取他的地址,就需要提供定义式(书中这样写的,但是博主经过测试,发现即使不提供定义式,似乎也是可以的,但是我们仍然还是按照书中的去写代码,不要依赖于编译器的各种骚操作):

并且我们这里要说,如果这样的一个常量在声明时获得初值,那么定义时不可以再给初值!

顺带一提,我们无法利用#define创建一个class专属常量,因为#define并不注重作用域,也就是说,一但宏被定义,那么他在其后的编译过程中都是有效的,除非在某处被#undef。

也就是说,没有private : #define这样的东西,他不能够用来定义class专属常量,也就没有任何封装性。

书中提到,如果你的编译器不支持静态整型常量在类内给初值,那么就将初值放在定义式。

唯一例外的是,如果有成员变量,也就是我们上面的scores数组需要这个常量值,那么在编译期间,这个常量值就必须让编译器知道,也就是说,这个常量需要在类内给一个初值,在定义式给初值是不可以的:

这也是唯一例外。

如果你的编译器不允许在类内给初值,那么可以使用enum这种补偿做法。这种做法的理论基础是:“一个枚举类型的数值可以被充作ints使用”,所以Gameplayer可以定义成这样:

书中说到enum值得我们去认识,那么我们就去认识一下,第一点,enum的行为的某方面比较像#define而不是const,他举了这样一个例子:如果我们想要取一个const的地址是合法的,而取enum的地址就不合法,同样的,取#define的地址通常也不合法。

如果我们不想让别人获得一个指针或引用指向我们的某个整型常量,enum就可以帮助我们实现这个约束,因为取他的地址是不合法的。

同时书中提到空间上的问题,说到优秀的编译器不会为整数型const对象设定另外的空间(除非我们创建一个指针或引用指向这个对象),但不够优秀的编译器可能不会这么做,而这可能不是我们想要的结果。enum就和#define一样绝不会导致非必要的内存分配,这里其实博主理解的也不是很清楚,所以也就不多解释。

书中说到认识enum的第二个理由纯粹是为了使用主义。许多代码用了他,所以我们见到他时必须认识他,事实上,enum是模板编程的基础(事实上,博主不清楚这点,解释不清)

现在我们继续谈预处理器的问题。也就是说,我们使用宏的时候,需要加小括号,否则就会像我们开始Add那样得出我们不想要的结果,而这些小括号往往令人抓狂。

首先有一个这样的函数:

int f(int a)
{return a;
}

 我们使用宏,有这样一个例子:

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

书中使用这样的函数来进行替代:

template<class T>
inline int callWIthMax(const T& a, const T& b)
{return f(a > b ? a : b);
}

我们对他们进行举例,看结果: 

我们可以看见宏中的++a,a是否要++竟然取决于比较的先后顺序!所幸我们不需要为这种无聊的方式浪费时间,所以就有了上面替代的函数。(其实只要使用函数,这些值都将是确定的,而不是像宏那样,烦且存在许多不确定性)。并且这样的函数可以成为在类内的private inline函数,一般而言宏无法完成此时。

有了const, enum, inline,我们对于预处理器的需求(特别是#define)降低了,但是#include仍然是必需品,并且#ifdef/#ifndef对于控制编译也是很重要的,博主这里对于#if 和 #endif也是常用,博主常常这样使用:

#include <iostream>
using namespace std;#if 1
int main()
{//using...return 0;
}
#endif#if 0
int main()
{//...return 0;
}
#endif

其实相当于一个变相的注释了,而且想释放使用时将0改成1即可。

本篇重点,请记住:

  • 对于单纯常量,最好使用const对象或enum替换#define
  • 对于形似函数的宏,我们最好改用inline函数替换#define

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

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

相关文章

全栈的自我修养 ———— redux入门(看这么一篇就够了!!!)

redux时react中负责状态管理的工具 一、下载二、配置1、目录2、store配置3、redux中index.js配置4、启动类中index.js配置 三、使用1、调用store的数据2、调用store里面的方法3、改变store里面的值 一、下载 npm I reduxjs/toolkit react-redux二、配置 1、目录 modules里面…

uniapp-打包IOS的APP流程

打包前所需配置 在manifest文件内配置 1. APP图标 2. 启动界面 有三种启动界面配置 第一种是 HBuilderX 官方给的通用启动界面&#xff0c;页面单一&#xff0c;屏幕中间就一个圆框图标 第二种是自定义的启动图&#xff0c;无法通过AppStore的审核 第三种是自定义storyboard启动…

ios应用内支付

用uniapp开发iOS应用内支付 准备前端代码服务器端处理如果iOS支付遇到问题实在解决不了&#xff0c;可以联系我帮忙解决&#xff0c;前端后端都可以解决&#xff08;添加的时候一定要备注咨询iOS支付问题&#xff09; 准备前端代码 获取支付通道 (uni.getProvider) uni.getPr…

240330-大模型资源-使用教程-部署方式-部分笔记

A. 大模型资源 Models - Hugging FaceHF-Mirror - Huggingface 镜像站模型库首页 魔搭社区 B. 使用教程 HuggingFace HuggingFace 10分钟快速入门&#xff08;一&#xff09;&#xff0c;利用Transformers&#xff0c;Pipeline探索AI。_哔哩哔哩_bilibiliHuggingFace快速入…

CA根证书——https安全保障的基石

HTTPS通信中&#xff0c;服务器端使用数字证书来证明自己的身份。客户端需要验证服务器发送的证书的真实性。这就需要一个可信的第三方机构&#xff0c;即CA&#xff0c;来颁发和管理证书。CA根证书是证书颁发机构层次结构的顶级证书&#xff0c;客户端信任的所有证书都可以追溯…

提质增效|大型汽车制造业运维精细化管理建设实战

项目背景 某大型汽车制造企业随着数字化技术的深入应用&#xff0c;对运维在“质量与效率”方面的精细化管理有了更高的要求。借助云智慧运维指标体系实现了 IT 架构的智能化与可视化&#xff0c;高效解决系统显性问题&#xff0c;积极处理系统隐性问题&#xff0c;提升系统稳…

差分与前缀和

目录 差分法 例题&#xff1a;大学里的树木要打药 前缀和 例题&#xff1a;大学里的树木要维护 差分法 差分法的应用主要是用于处理区间问题&#xff0c;当一个数组要在很多不确定的区间&#xff0c;加上相同的一个数&#xff0c;我们如果每个数都进行加法操作的话&#x…

C++初学者:如何优雅地写程序

我喜欢C语言的功能强大&#xff0c;简洁&#xff0c;我也喜欢C#的语法简单&#xff0c;清晰&#xff0c;写起来又方便好用。 一、为什么不用C语言写程序。 C语言用来做题目&#xff0c;考试研究是很方便的&#xff0c;但是用来写程序做软件&#xff0c;你就会发现&#xff0c…

低噪声、轨至轨运算放大器芯片—— D721、D722、D724,适合用于音频领域

应用领域 D721、D722、D724是我们推荐的三款低噪声、轨至轨运算放大器芯片&#xff0c;其中D721为单运放&#xff0c;D722为双运放&#xff0c;D724为四运放。适合用于音频领域、传感器等的信号放大处理&#xff0c;比如K歌宝、音响、测距、滤波器、AD转换器前级信号处理等等。…

MES系统主要功能有哪些?

本文将为大家讲解&#xff1a;1、MES系统是什么&#xff1f;2、MES系统主要功能有哪些&#xff1f;3、MES系统的价值何在&#xff1f;4、使用MES系统的案例。 一、MES系统是什么&#xff1f; 正所谓“磨刀不误砍柴工”&#xff0c;咱们先来了解一下什么是MES系统。MES系统&am…

蓝桥杯第1593题——二进制问题

题目描述 小蓝最近在学习二进制。他想知道 1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。你能帮助他吗&#xff1f; 输入描述 输入一行包含两个整数 N 和 K。 输出描述 输出一个整数表示答案。 输入输出样例 示例 输入 7 2输出 3评测用例规模与约定 对于 30% …

OpenAI官宣,ChatGPT免登录使用

昨天&#xff0c;就在愚人节当天&#xff0c;OpenAI突然宣布ChatGPT3.5向所有人开放&#xff0c;意思是即使没有注册OpenAI的账号&#xff0c;也能体验ChatGPT&#xff0c;就像浏览器一样&#xff0c;联接即可使用。 消息一出&#xff0c;OpenAI的官网直接被大量的用户挤挂了。…

Python中的全栈开发前端与后端的完美融合【第160篇—全栈开发】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python中的全栈开发&#xff1a;前端与后端的完美融合 全栈开发已成为当今软件开发领域中的…

c语言:预处理详解

1. 预定义符号 C语⾔设置了⼀些预定义符号&#xff0c;可以直接使⽤&#xff0c;预定义符号也是在预处理期间处理的。 __FILE__ //进⾏编译的源⽂件 __LINE__ //⽂件当前的⾏号 __DATE__ //⽂件被编译的⽇期 __TIME__ //⽂件被编译的时间 __STDC__ //如果编译器遵循ANSI …

SpringBoot登录校验(三)JWT令牌

SpringBoot 登录认证&#xff08;一&#xff09;-CSDN博客 SpringBoot 登录认证&#xff08;二&#xff09;-CSDN博客 SpringBoot登录校验&#xff08;三&#xff09;-CSDN博客 前面我们介绍了传统的会话跟踪技术cookie和sesstion&#xff0c;本节讲解令牌技术。这里所提到的…

golang语言系列:Authentication、OAuth、JWT 认证策略

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 golang语言系列 文章&#xff0c;主要对编程通用技能 Authentication、OAuth、JWT 认证策略 进行学习 1.Basic Authentication认证 每个请求都需要将 用户名密码 进行base64编码后&#xff0c;放在请求头的Aut…

uniapp 微信小程序 输入框跟随手机键盘弹起

需求&#xff1a;手机键盘弹起后&#xff0c;页面底部的输入框跟随弹起&#xff0c;且页面不被顶上去 html: <textareaclass"textinput"placeholder-class"input-place"auto-height:maxlength"2000"v-model"text"placeholder"…

每天五分钟计算机视觉:使用神经网络完成人脸的特征点检测

本文重点 我们上一节课程中学习了如何利用神经网络对图片中的对象进行定位,也就是通过输出四个参数值bx、by、bℎ和bw给出图片中对象的边界框。 本节课程我们学习特征点的检测,神经网络可以通过输出图片中对象的特征点的(x,y)坐标来实现对目标特征的识别,我们看几个例子。…

关于v114之后的chromedriver及存放路径

使用selenium调用浏览器时&#xff0c;我一直调用谷歌浏览器&#xff0c;可浏览器升级后&#xff0c;就会再次遇到以前遇到过的各种问题&#xff0c;诸如&#xff1a;1、怎么关闭浏览器更新&#xff1b;2、去哪儿下载chromedriver&#xff1b;3、114版本之后的驱动去哪儿下载&a…

鸿蒙原生应用开发-网络管理HTTP数据请求

一、场景介绍 应用通过HTTP发起一个数据请求&#xff0c;支持常见的GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT方法。 二、接口说明 HTTP数据请求功能主要由http模块提供。 使用该功能需要申请ohos.permission.INTERNET权限。 涉及的接口如下表&#xff0c;具体的…