当前位置: 首页 > news >正文

C++编程实践--表达式与语句

文章目录

  • 声明与定义
    • 合理使用auto
    • 使用using定义类型别名
    • 禁止通过声明的方式引用外部函数接口和变量
    • 禁止依赖不同编译单元内全局对象的初始化顺序
    • 变量初始化
  • 类型转换
    • 使用由C++提供的类型转换操作
    • 禁止使用std::move操作const对象
  • 运算
    • 整数运算
      • 整数提升
      • 整数类型间的转换
      • 位移表达式
      • 位运算表达式
      • 乘法表达式
      • 整数常量表达式
      • 整数溢出问题
      • 整数回绕问题
    • 浮点运算
  • 表达式
    • 不要依赖未计算的操作数中的副作用
    • &&和||操作符的右侧操作数不要包含副作用
    • 编译期可确定的断言检查使用static_assert
    • 使用nullptr作为空指针常量

声明与定义

合理使用auto

使用auto可以避免编写冗长、重复的类型名,也可以保证定义变量时初始化。但是,auto类型推导规则复杂,需要仔细理解,在使用时应当确保推导的类型符合预期。

  1. 声明变量具有与函数调用的返回类型相同的类型
    std::map<std::string, int>::iterator iter = m.find(val);
    auto iter = m.find(val);    // 避免冗长的类型名
    
  2. 声明变量与非基础对象具有相同的类型
  • auto类型推导时忽略引用,可能引入难以发现的性能问题:
  • auto初始化的变量,普通变量的const修饰会忽略,而指针和引用的const修饰会保留。

不要在接口头文件中用auto定义对外接口,例如:如果在头文件中使用auto定义了常量,可能在代码演进时因修改其值,而导致类型发生变化。

使用using定义类型别名

类型的别名实际是对类型的封装。而通过封装,可以让代码更清晰,同时在很大程度上避免类型变化带来的散弹式修改。

C++11之前,可以通过typedef定义类型的别名:

typedef Type Alias; // Type 在前,还是 Alias 在前,不易理解

C++11之后,可以通过using定义类型别名:

using Alias = Type; // 符合'赋值'的用法,容易理解,不易出错

使用using定义模板的别名更简洁:

// 定义模板的别名
template <typename T>
using SpecialVector = std::vector<T, SpecialAllocator<T>>;
SpecialVector<int> data;                                   // 使用别名
template <typename T>
class SomeClass {...
private:SpecialVector<int> data;                               // 模板类中使用别名
};

而typedef不支持带模板参数的别名,只能"曲线救国":

// 通过模板包装 typedef,需要实现一个模板类
template <typename T>
struct SpecialVector {typedef std::vector<T, SpecialAllocator<T>> type;
};
SpecialVector<int>::type data;                             // 使用别名时,额外多写 ::type
template <typename T>
class SomeClass {...
private:typename SpecialVector<int>::type data;                // 需要加上 typename
};

禁止通过声明的方式引用外部函数接口和变量

只能通过包含头文件的方式使用其他模块或文件提供的接口。

通过声明的方式使用外部函数接口和变量,容易在外部接口改变时可能导致声明和定义不一致。同时,这种隐式依赖容易导致架构腐化。

【例外】
有些场景需要引用其内部函数,但并不想侵入代码时,可以通过 extern 声明方式引用。如:

针对某一内部函数进行单元测试时,可以通过 extern 声明来引用被测函数
当需要对某一函数进行打桩、打补丁处理时,允许 extern 声明该函数

禁止依赖不同编译单元内全局对象的初始化顺序

不同编译单元内全局对象的初始化顺序没有被严格定义,因此编写代码时不要依赖它们的初始化顺序,否则程序可能产生未定义行为。

变量初始化

确保对象在使用之前已被初始化。

遵循变量作用域最小化原则与就近声明原则,在变量被使用时才声明并初始化,使得代码更容易阅读,方便了解变量的类型和初始值。特别地,尽量使用初始化的方式对变量赋初值。

在函数开始位置声明所有变量,后面才使用变量,作用域覆盖整个函数实现,容易导致如下问题:

  • 程序难以理解和维护:变量的定义与使用分离。
  • 变量难以合理初始化:在函数开始时,经常没有足够的信息进行变量初始化,往往用某个默认的空值(比如零)来初始化,使程序低效,如果变量在被赋于有效值以前被使用,还会导致错误。

【反例】
声明与初始化分离,降低了代码的可读性。

std::string name;           // 声明时未初始化:调用默认构造函数
...                         // 在此期间未使用name变量
name = "nobody";            // 再次调用赋值操作符函数
...

【正例】
推迟变量定义,直到被使用时才定义并初始化。

std::string name{"nobody"}; // 调用构造函数初始化
...

优先使用{}初始化语法。
为了消除多种初始化语法带来的混乱,同时也覆盖到所有的初始化场景,C++11提供了{}初始化语法。当确定不会进行窄化转换或者明确希望支持隐式转换时,可以使用=初始化;当需要调用某个版本的构造函数时可以使用()初始化;其他情况优先使用{}初始化。

  1. 使用大括号{}初始化可以避免内置(built-in)类型之间的隐式窄化转换(narrowing conversions),而使用括号()或等号=初始化时不检查窄化转换
int a{1.2};        // 编译错误: 发生窄化
int b = 1.2;       // 不符合:非预期的隐式类型转换
int a{5};          // 符合
int b = a;         // 符合:确定不会发生窄化转换时使用赋值初始化
ArithmeticT c = 0; // 符合:明确需要该类型支持隐式转换
  1. {}初始化避免了C++语法解析引起的歧义

在C++中,任何可以被解释为声明语法的语句都会被解释为声明语句,这会导致调用默认构造函数创建对象的时候解析错误。
例如:

Foo a(); // 实际上声明了一个名字为 a 并且返回类型为 Foo 的函数
Foo b{}; // 可以用大括号默认构造对象
  1. 相对于C++提供的=()初始化,只有大括号{}是可以在任何地方使用的,语法上简单通用
// 初始化结构体
struct A {int i;int j;
}
http://www.xdnf.cn/news/1331875.html

相关文章:

  • 第一章:认识 CAD 图形文件 —— DXF 格式
  • 单抗免疫原选型指南|抗体制备方案设计——常用抗原类型及制备方法
  • Spring事务源码
  • c语言多任务处理(并发程序设计)
  • 挑战极限:在256MB内存的机器上构建MySQL极简安装方案
  • 基于SpringBoot的旅游攻略系统网站【2026最新】
  • mysql-8.0.37-linux-glibc2.12-x86_64安装
  • 【shell脚本编程】-7 寻找到在5分钟内改动的文件
  • 【C++】基础:C++11-14-17常用新特性介绍
  • 【Obsidian插件】HiNote
  • ansible playbook 实战案例roles | 实现db2自动安装
  • spring第9课,spring对DAO的支持
  • 【C++】模版(初阶)
  • 【STM32】HAL库中的实现(六):DAC (数模转换)
  • wpf之ComboBox
  • uniapp学习【上手篇】
  • Ubuntu 重连usb设备(断电和不断电方案)亲测可行
  • 【科研绘图系列】R语言绘制平滑曲线折线图
  • SQL面试题及详细答案150道(41-60) --- 条件查询与分组篇
  • 【报错】Please do not run this script with sudo bash
  • 开源大模型如何选择?GPT-OSS综合评估
  • IDEA切换分支时,提示:Git Checkout Problem
  • 4位量化:常规的线性层被替换成了4位线性层(48)
  • 服务器硬件电路设计之 SPI 问答(二):SPI 与 I2C 的特性博弈及多从机设计之道
  • 基于单片机环境火灾安全检测
  • 27.语言模型
  • 3D max制作蝴蝶结详细步骤(新手可跟)♥️
  • Angular入门教程
  • Angular由一个bug说起之十八:伴随框架升级而升级ESLint遇到的问题与思考
  • 【机器学习】什么是损失景观(Loss Landscape)?