改善C++程序与设计的55个具体做法——2.尽量以const,enum,inline替换#define

const和#define

这个条款或许改为“宁可以编译器替换预处理器”比较好,因为或许#define不被视为语言的一部分。那正是它的问题所在。当你做出这样的事情:

#define ASPECT RATIO 1.653

记号名称ASPECT_RATIO也许从未被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称ASPECT RATIO有可能没进入记号表(symbol table)内。于是当你运用此常量但获得一个编译错误信息时,可能会带来困惑,因为这个错误信息也许会提到1.653而不是 ASPECT RATIO。如果ASPECT RATIO被定义在一个非你所写的头文件内,你肯定对1.653以及它来自何处毫无概念,于是你将因为追踪它而浪费时间。这个问题也可能出现在记号式调试器(symbolic debugger)中,原因相同:你所使用的名称可能并未进入记号表(symboltable)。
解决之道是以一个常量替换上述的宏(#define):

const double AspectRatio = 1.653; //大写名称通常用于宏,
//因此这里改变名称写法。


作为一个语言常量,AspectRatio肯定会被编译器看到,当然就会进入记号表内。此外对浮点常量(floating point constant,就像本例)而言,使用常量可能比使用#define导致较小量的码,因为预处理器“盲目地将宏名称ASPECT_RATIO替换为1.653”可能导致目标码(object code)出现多份1.653,若改用常量AspectRatio绝不会出现相同情况。

以常量替换#define的两种特殊情况

第一是定义常量指针

由于常量定义式通常被放在头文件内(以便被不同的源码含入),因此有必要将指针(而不只是指针所指之物)声明为const。

例如着要在文件内定义一个常量的(不变的)char*-based字符串,你必须写 const两次

const char* const authorName-"Scott Meyers";


关于const的意义和使用(特别是当它与指针结合时),条款3有完整的讨论。这里值得先提醒你的是,string对象通部比其前辈chart-based合宜,所以上选的authorName 往往定义成这样更好些:

const std::string authorName("Scott Meyers");


第二个是class专属常量

为了将常量的作用域(scope)限制于class内,你必须让它成为class的一个成员;而为确保此常量至多只有一份实体,你必须让它成为一个static成员:

class GamePlayer {
private:
static const int NumTuns =5; //常量声明式
int scores [NumTurns]; //使用该常量
};


然而你所看到的是NumTurns的声明式而非定义式。通常C++要求你对你所使用的任何东西提供一个定义式,但如果它是个class专属常量又是static且为整数类型(integral type,例如ints,chars,bools),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式但如果你取某个class专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,你就必须另外提供定义式如下:

const int GamePlayer::NumTurns; //NumTurns的定义;
//下面告诉你为什么没有给予数值


请把这个式子放进一个实现文件而非头文件由于class常量已在声明时获得初值(例如先前声明Numturns时为它设初值5),因此定义时不可以再设初值顺带一提,请注意,我们无法利用#define 创建一个class 专属常量,因为#defines并不重视作用域(scope)。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。这意味#defines不仅不能够用来定义class专属常量,也不能够提供任何封装性,也就是说没有所谓private #define这样的东西。而当然const成员变量是可以被封装的,NumTurns就是。
旧式编译器也许不支持上述语法,它们不允许static成员在其声明式上获得初值。此外所谓的“in-class 初值设定”也只允许对整数常量进行。如果你的编译器不支持上述语法,你可以将初值放在定义式:

class CostEstimate {
private:
static const double FudgeFactor; //static class 常量声明
//位于头文件内
};
const double CostEstimate::FudgeFactor =1.35;static class常量定义,位于实现文件内


这几乎是你在任何时候唯一需要做的事。

enum和#define

上面这例子几乎是你在任何时候唯一需要做的事。唯一例外是当你在class 编译期间需要一个class常量值,例如在上述的GamePlayer::scores的数组声明式中(是的,编译器坚持必须在编译期间知道数组的大小)。这时候万一你的编译器(错误地)不允许“static 整数型class 常量”完成“in class初值设定”,可改用所谓的"the enumhack”补偿做法。其理论基础是:“一个属于枚举类型(enumerated type)的数值可权充ints被使用”,于是GamePlayer可定义如下:

class GamePlayer {
private:
enum ( NumTurns=5 ); //"the enum hack"—令 NumTurns
//成为5的一个记号名称.
int scores [NumTurns]; //这就没问题了.
};


基于数个理由 enum back值得我们认识。第一,enum hack的行为某方面说比较像 #define而不像const,有时候这正是你想要的。例如取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。

如果你不想让别人获得一个pointer或referenee指向你的某个整数常量,enum可以帮助你实现这个约束。此外虽然优秀的编译器不会为“整数型const对象”设定另外的存物空间(除非你创建一个pointer或reference指向该对象),不够优秀的编译器却能如此,而这可能是你不想要的。enum 和define一样绝不会导致非必要的内有分配。
认识enum hack的第二个理由纯粹是为了实用主义。许多代码用了它,所以看到它时你必须认识它。事实上"enum hack”是template metaprogramming (模板元编程,见条款48)的基础技术。
把焦点拉回预处理器。

inline和#define

另一个常见的#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会招致函数调用(function call)带来的额外开销。下面这个宏夹带着宏实参,调用函数:

//以a和b的较大值调用f
#define CALL_WITH_MAX(a,b) f((a)> (b)? (a):(b))


这般长相的宏有着太多缺点,光是想到它们就让人痛苦不堪。
无论何时当你写出这种宏,你必须记住为宏中的所有实参加上小括号,否则某些人在表达式中调用这个宏时可能会遭遇麻烦。但纵使你为所有实参加上小括号,看看下面不可思议的事情:

int a=5,b=0;
CALL_WITH MAX(t+a, b); //a 被累加二次
CALL WITH MAX(++a,b+10); //a被累加一次


在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!
幸运的是你不需要对这种无聊事情提供温床。你可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(typesafety)——只要你写出template inline函数(见条款30):

template<typename r> //由于我们不知道
inline void callWithMax(const T& a, const T& b)//T是什么,所以采用( 一 f(a>b? a: b); //pass by reference-to-const.
//见条款 20.


这个template产出一整群函数,每个函数都接受两个同型对象,并以其中较大者调用f。这里不需要在函数本体中为参数加上括号,也不需要操心参数被核算(求值)多次……等等。此外由于cal1withMax是个真正的函数,它遵守作用域(scope)和访问规则。例如你绝对可以写出一个“class内的private inline函数”。一般而言宏无法完成此事。
Hlefine

有了consts、enums 和inlines,我们对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。目前还不到预处理器全面引退的时候,但你应该明确地给予它更长更频繁的假期。

总结 

对于单纯常量,最好以const对象或enums替换#defines。
对于形似函数的宏(macros),最好改用inline函数替换#defines。

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

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

相关文章

面试经典150题【31-40】

文章目录 面试经典150题【31-40】76.最小覆盖字串36.有效的数独54.螺旋矩阵48.旋转图像73.矩阵置零289.生命游戏383.赎金信205.同构字符串290.单词规律242.有效的字母异位词 面试经典150题【31-40】 76.最小覆盖字串 基本思路很简单&#xff0c;就是先移动右边到合适位置。再移…

SOP、SLA、SOA傻傻分不清?

SOP&#xff08;Standard Operating Procedure&#xff09; SOP 是 “Standard Operating Procedure” 的缩写&#xff0c;中文意思是“标准操作流程”。它是组织内部规定的一套标准化的工作流程或操作指南&#xff0c;目的是确保工作或任务以统一、高效、安全的方式完成。SOP…

面对耦合度过高的第三方对接

今天接到一个需求&#xff0c;原型图如下&#xff1a;抖音数据&#xff08;给的是数据占比&#xff09;&#xff0c;要求根据性别&#xff0c;年龄&#xff0c;类目&#xff0c;地域&#xff0c;消费偏好&#xff0c;客单价筛选后排序。 1、性别给的示例&#xff1a;{ "…

Android 9.0 禁用adb remount功能的实现

1.概述 在9.0的系统rom定制化开发中,在进行某些产品开发中,需要禁用掉adb remount功能,就是不能让系统remount,system分区 vendor分区等 只能有读权限没有写权限,所以接下来看下如何禁用remount功能 2. 禁用adb remount功能的实现的核心类 system/core/adb/remount_servic…

IPV6地址

技术背景&#xff1a;对IPV4做优化&#xff0c;比如地址长度128&#xff0c;简化了报文头部---快 ipv6地址 十六进制&#xff0c;简写前导0忽略&#xff0c;连续的0写成:: IPv6地址类型 1.单播 2.组播---接口有地址后&#xff0c;自动加入到一个组播里 3.任播---允许地址…

Rem 自适应原理与应用

前言 移动端适配有很多方案&#xff0c;这篇文章将根据 rem 自适应的原理进行讲解&#xff0c;接下来跟着作者的思路一起来看看吧&#xff01; 原理 在搞清楚 rem 适配之前&#xff0c;我们先来了解一下什么是 rem&#xff1f; rem 是一种相对长度单位&#xff0c;它相对于根…

51单片机学习(5)-----蜂鸣器的介绍与使用

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 目录 一. 蜂鸣器的介绍 1.蜂鸣器介绍 2.压电式蜂鸣器 &#xff08;无源…

LeetCode 第一题: 两数之和

文章目录 第一题: 两数之和题目描述示例 解题思路Go语言实现 - 一遍哈希表法C实现算法分析 排序和双指针法Go语言实现 - 排序和双指针法C算法分析 暴力法Go语言实现 - 暴力法C算法分析 二分搜索法Go语言实现 - 二分搜索法C算法分析 第一题: 两数之和 ‍ 题目描述 给定一个整…

6.2 认证授权模块 - 继承JWT、网关认证

认证授权模块 - 继承JWT、网关认证 文章目录 认证授权模块 - 继承JWT、网关认证一、JWT1.1 普通令牌1.2 JWT 令牌介绍1.3 测试生成JWT 令牌1.3.1 TokenConfig1.3.2 授权服务器配置 AuthorizationServer1.3.3 WebSecurityConfig 安全管理配置1.3.4 返回信息 1.4 资源服务集成 JW…

《乱弹篇(十九)围炉诗话》

闲来无事&#xff0c;近日浏览中国古代文学书藉&#xff0c;读罢《新嫁娘词》中的一首五言绝句诗&#xff0c;觉得妙趣横生&#xff0c;艺术造诣甚高。今天恰逢春潮寒意还浓&#xff0c;气温很低&#xff0c;笔者雅兴勃发&#xff0c;便开足暖气&#xff0c;也附庸风雅地来一番…

Linux FTP 服务搭建

vsftpd&#xff08;very secure FTP daemon&#xff09;是Linux下的一款小巧轻快、安全易用的FTP服务器软件&#xff0c;本次介绍如何在Linux上安装并配置vsftpd。 FTP&#xff08;File Transfer Protocol&#xff09;是一种文件传输协议&#xff0c;基于客户端/服务器架构&am…

jax可微分编程的笔记(4)

jax可微分编程的笔记(4) 第四章 JAX的微分运算 我们从最小二乘法说起,构建出深度学习的轮廓,并最终基于 MINST手写数据集训练了一个简单的全连接的神经网络. 4.1 微分操作的语法 本节给出复杂积分的运算和隐函数求导这两个例子. 4.1.1 JAX中的梯度操作 from jax imprt gr…

WSL2配置Linux、Docker、VS Code、zsh、oh my zsh(附Docker开机自启设置)

0. 写在前面 本篇笔记来自于UP主麦兜搞IT的合集视频Windows10开发环境搭建中的部分内容 1. 安装WSL2 按照微软官方文档进行操作&#xff0c;当然也可以直接wsl --install 也可以按照 旧版手动安装的步骤 来进行操作 选择安装的是Ubuntu 20.04 LTS 注&#xff1a;WSL默认安装…

抖音视频提取软件使用功能|抖音视频下载工具

我们的抖音视频提取软件是一款功能强大、易于操作的工具&#xff0c;旨在解决用户在获取抖音视频时需要逐个复制链接、下载的繁琐问题。我们的软件支持通过关键词搜索和分享链接两种方式获取抖音视频&#xff0c;方便用户快速找到自己感兴趣的内容。 主要功能模块&#xff1a;…

计算机设计大赛 深度学习图像风格迁移

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

pytorch -- CIFAR10 完整的模型训练套路

网络结构 代码 # CIFAR 10完整的模型训练套路&#xff1a; import torch.optim import torchvision from torch import nn from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriterfrom model import *# 1. 准备数据集 train_data torch…

【数据库】mybatis使用总结

文章目录 1. 批量插入、检索<foreach> 1. 批量插入、检索<foreach> <insert id"insertSystemService" >insert into SYSTEMINFO_SERVICE (system_code, service_id, add_user, add_time)values<foreach collection"serviceList" ite…

SSM框架学习笔记07 | Spring MVC入门

文章目录 1. HTTP协议2. Spring MVC2.1. 三层架构2.2. MVC&#xff08;解决表现层的问题&#xff09;2.3. 核心组件 3. Thymeleaf3.1. 模板引擎3.2. Thymeleaf3.3. 常用语法 代码 1. HTTP协议 网址&#xff1a;https://www.ietf.org/ &#xff08;官网网址&#xff09; https:…

C++标准头文件汇总及功能说明

文章目录 algorithmbitsetcctypecerrnoclocalecmathcstdioctimedequeiostreamexceptionfstreamfunctionallimitslistmapiosiosfwdsetsstreamstackstdexceptstreambufcstringutilityvectorcwcharcwctype algorithm algorithm头文件是C的标准算法库&#xff0c;它主要用在容器上。…

dolphinscheduler单机版部署教程

文章目录 前言一、安装准备1. 安装条件2. 安装jdk3. 安装MySQL 二、安装dolphinscheduler1. 下载并解压dolphinscheduler2. 修改配置文件2.1 修改 dolphinscheduler_env.sh 文件2.2 修改 application.yaml 文件 3. 配置mysql数据源3.1 修改MySQL安全策略3.2 查看数据库3.3 创建…