提供的内容深入探讨了C++编程中的一些关键概念,特别是如何编写清晰、易维护的代码,并展示了一些C++17的新特性。我将对这些内容做中文的解释和总结。

1. 良好的代码设计原则

什么是“良好的代码”?
  • 能工作:代码实现了预期功能。
  • 能在其他编译器或平台上运行:跨平台的代码很重要。
  • 能预先回答常见问题:处理好内存管理、边界情况等问题。
  • 富有表现力:代码应该易于理解,能清晰表达其目的。
  • 透明且具有沟通性:代码易于他人理解,代码意图清晰。
    这些内容强调了写清晰且可维护的代码的重要性,不仅仅是代码是否能正确工作。

2. Roger Orr 喜爱的代码片段

通过代码示例,可以对比如何通过一些小的改进让代码变得更加清晰和易于扩展。

原始版本:
class Holder
{
private:int number;
public:Holder(int i);Holder();void inc() { number++; }int getNumber() { return number; }std::string to_string();
};
  • 问题getNumber方法没有标记为constto_string方法也没有标记为const或者是override
  • 缺乏清晰的意图:没有明确表明哪些方法会修改类的状态。
改进版本:
class Holder
{
private:int number;
public:explicit Holder(int i);  // 防止隐式转换Holder();void inc() { number++; }int getNumber() const { return number; }  // 表明不会修改对象状态virtual std::string to_string() const;  // 用const和virtual标记,确保可以重写
};
  • 改进点
    • explicit: 防止构造函数进行隐式类型转换,从而避免一些难以发现的错误。
    • const: getNumberto_string方法标记为const,表示这些方法不会改变对象的状态。
    • virtual: to_string方法标记为virtual,确保子类能够重写该方法。

3. C++ 中的对立概念

C++中许多构造都成对出现或者互为对立。我们来看看一些常见的例子。

操作符和括号:
  • 操作符
    • 算术+(加法)与*(乘法)
    • 指针*(解引用)与&(取地址)
  • 括号
    • ()(函数调用)
    • {}(代码块/作用域)
    • [](数组下标)
    • <>(模板参数列表)
对立的关键字:
  • if / else: 条件执行。
  • noexcept / noexcept(false): noexcept用于声明一个函数不会抛出异常,noexcept(false)用于声明该函数可能抛出异常。
没有对立的部分

一些C++构造没有直接的“对立”或“反义词”,如:

  • breakcontinuereturnfoo(x)whileforswitch等控制流语句没有对立的概念。

4. C++ 17的新属性(Attributes)

C++17引入了一些新属性(attributes),比如[[fallthrough]][[maybe_unused]][[nodiscard]],它们有助于改善代码质量和清晰度。

[[fallthrough]]switch 语句中:

有时你希望故意跳过break语句,使代码从一个case顺利执行到下一个case

switch (i)
{
case 1:
case 2:msg += "case 1 or case 2. ";break;
case 3:msg += "case 3 or ";[[fallthrough]];  // 明确标记是故意的fallthrough
case 4:msg += "case 4.";
default:break;
}
  • 没有 [[fallthrough]],编译器可能会警告你关于不小心遗漏break的情况,而加上[[fallthrough]]则表明这是故意的。
[[maybe_unused]]

用于那些可能未被使用的变量或函数,但不希望编译器发出警告。

[[maybe_unused]] int j = FunctionWithSideEffects();
assert(j > 0);
  • 即使j可能未使用,编译器也不会对此发出警告。
[[nodiscard]]

此属性帮助确保函数的返回值不会被忽略。

[[nodiscard]] int getNumber() { return 42; }
auto num = getNumber();  // 正常使用
getNumber();  // 编译器会警告:返回值被丢弃
  • **[[nodiscard]]**会在函数的返回值被忽略时触发警告,提醒开发者该返回值可能很重要。

5. 其他重要概念

explicit:

explicit关键字用于防止构造函数进行隐式转换,这样可以避免一些潜在的错误。

constmutable:
  • const:表示方法不会修改对象的状态,常用于常成员函数或者常量变量。
  • mutable:即使在const对象中,某些成员变量可以被修改,通常用于缓存等需要在不改变对象状态的情况下修改的变量。
引用限定符(Ref-qualifiers):

引用限定符用于指定一个函数只能在特定的对象类型(左值或右值)上被调用。

void foo() & { /* 只能在左值上调用 */ }
void foo() && { /* 只能在右值上调用 */ }

总结

  1. explicit 与 隐式转换explicit防止隐式转换,从而避免一些潜在的隐式转换错误。
  2. constmutableconst确保方法不会修改对象状态,而mutable用于允许某些成员在const对象中被修改。
  3. [[fallthrough]][[maybe_unused]][[nodiscard]]:这些C++17属性帮助清晰地表达代码意图,并让编译器能给出有用的警告。
  4. virtualoverride:通过标记方法为virtual,确保子类能够重写该方法;而override则确保方法正确地重写了基类方法。
    这些特性和原则帮助我们写出表达性强可维护易于理解的代码。同时,C++17引入的属性增强了编译器的优化能力和错误检查功能,使得代码更健壮。
    你提供的内容主要是关于如何通过清晰地表达意图来编写更易理解和维护的C++代码。以下是对这些内容的中文解释和总结:

1. 如何更清楚地表达意图?

避免默认值(Avoid defaults)
  • 不使用默认参数值:在函数或者类的构造函数中,最好明确指定每个参数,不使用隐式的默认值。这样可以避免一些意外的行为,让代码更易理解。
在类或结构体中始终明确指定 public:private:
  • 即使是一个简单的两元素结构体(如 Point),也应明确标注 public:private: 区域。
    struct Point {int x, y;
    public:Point(int x, int y) : x(x), y(y) {}
    private:void privateMethod() {}
    };
    
    • 意图:明确告知其他开发者哪些成员是公开的,哪些是私有的。
void 函数中加上 return
  • 即便是 void 函数,最好在函数末尾显式添加 return,这表明你有意结束函数执行。
    void someFunction() {// Do somethingreturn;
    }
    
使用那些可选项(Use optional things)
  • 标记虚函数的重写:通过 override 明确标示函数是重写了基类的方法。
    class Base {virtual void foo() {}
    };
    class Derived : public Base {void foo() override {}  // 明确表明这是对基类方法的重写
    };
    
  • noexcept:如果你明确认为函数不会抛出异常,使用 noexcept 进行标注,增加可读性并避免误解。
    void someFunction() noexcept {// No exceptions expected
    }
    
  • 意义:虽然这些关键字(如 overridenoexcept)不一定是必须的,但它们提供了重要的意图信息,帮助读者理解代码的设计和限制。

2. 表达意图的极限

在表达意图时,有时我们需要在代码中做出平衡。虽然有很多关键字可以使用,但有些关键字并不一定出现在C++中,我们也不一定需要它们。

不常见的关键字
  • implicit:C++ 没有类似 implicit 的关键字来表示隐式转换。
  • const(false):没有这个关键字来表示“不可修改”。
  • nonvirtual:C++ 没有类似 nonvirtual 的关键字来禁止虚函数。
  • ByVal:C++ 中也没有 ByVal 关键字来表示按值传递。
    我们该如何处理这些情况?
  • 可以通过清晰的命名和注释来表达意图。例如,“我知道自己在做什么,请不要修改此部分”

3. 上下文的意义

缺少关键字意味着什么?
  • 第一种情况:表示“我已经考虑过这个问题,因此不需要使用关键字”。
  • 第二种情况:表示“我从未听说过这个关键字,或者至少没考虑过它是否应该在此使用”。
    如何传达给读者:如果你在代码库中始终如一地使用某些关键字,那么读者可以推测你已经考虑过它们的使用。例如,如果你在每个虚函数上都使用 override,那么读者可以很清楚地理解你有意标明这个函数是重写的。

4. 注释的使用

  • 注释:注释应仅用于那些可能让读者误解的地方,而不是在每个函数旁边都加上不必要的注释。例如,foo 方法看起来像是一个虚拟函数的重写,但它可能只是一个签名不同的函数。在这种情况下,你可以加上一些注释来澄清。
    // 我知道这看起来像是 foo 的重写,但实际上这是一个不同签名的函数
    
  • 注释不是表达意图的主要方式,而是用于澄清可能的误解。

5. 可选的返回语句

void 函数中,尽管返回值是 void 类型,但最好明确地加上 return 语句,以表明函数的结束。

void Thimbule(int robbit)
{robbit++;if (robbit)return;robbit--;
}
void Sprial(int oob, int boo)
{oob++;while (true){if (++oob > boo)return;}
}
  • 可选返回语句的好处:在一些复杂的条件分支中,显式的 return 会使代码更加清晰,避免不必要的复杂性。

6. 范围 for 循环(Ranged For)

  • 按值传递auto emp : department):
    for (auto emp : department) {// ...
    }
    
    • 适用于当你不需要修改元素时,复制元素的副本。
  • 按引用传递auto& emp : department):
    for (auto& emp : department) {// ...
    }
    
    • 用于避免不必要的复制,直接操作元素的引用。
  • 常量引用auto const & emp : department):
    for (auto const & emp : department) {// ...
    }
    
    • 如果不需要修改元素且希望避免复制,可以使用常量引用。

7. 参数传递

如何传递参数?
  • 按值传递Order createOrder(Customer c, OrderItem oi);
    • 适用于参数较小或者需要复制的情况。
  • 按引用传递Order createOrder(Customer& c, OrderItem oi);
    • 适用于对象较大,且希望避免复制的情况。
  • 按常量引用传递Order createOrder(Customer const& c, OrderItem oi);
    • 如果不需要修改对象,并且希望避免复制,这是最佳选择。

8. 省略参数名称

  • 在声明时,参数名称可以省略,编译器并不关心。但人类开发者会关心,因此最好不要省略参数名称。尤其是在定义函数时,如果某个参数未使用,可以在定义中省略其名称:
    int DetermineTotalTaxes(int, int, int);
    
    • 但是,在定义时,如果某个参数不使用,最好加上注释,说明这样做的原因,避免其他人误解。
    int DetermineTotalTaxes(int ProvRate, int FedRate, int) {// do somethingreturn 42;
    }
    

总结

  • 表达意图的清晰性是编写可维护代码的关键。通过使用适当的关键字(如 overridenoexcept 等),标明函数行为,帮助其他开发者更容易理解代码的设计。
  • 明确代码意图:尽量避免默认值,确保参数传递方式清晰,函数的返回值明确,并使用注释澄清潜在的误解。
  • 一致性:通过在整个代码库中一致使用关键字和模式,增强代码的可读性和可维护性。
    这部分内容探讨了如何通过代码中隐含的设计选择,传达更多的意图和信息,进而提高代码的可读性和可维护性。以下是对这些内容的中文解释:

1. 其他选择也能传达大量信息

原始指针是否总是非拥有的指针?
  • bool sendEmails(Employee* pe)Message* sendEmails(Employee* pe)
    • 这些代码片段的设计中,是否使用了智能指针?
    • 是否频繁使用了 newdelete
    • 这会涉及到“规则 3 或 5”(Rule of 3/5),即类如果有资源管理行为(如分配内存),它应该提供拷贝构造函数、赋值运算符和析构函数来管理这些资源。
    • 代码中是否有析构函数?
      传递原始指针或智能指针的选择,能够传达代码的资源管理策略。如果使用了智能指针,代码可以自动管理内存,减少资源泄漏的风险。如果是原始指针,则意味着可能需要手动管理内存,这就需要仔细考虑拷贝、赋值、析构等操作。
&* 的意义?
  • & 表示引用* 表示指针
    • 传递地址或引用是否总是会修改数据?
      • 传递引用T&)和传递指针T*)都可能导致数据修改,但是否会修改取决于是否是const类型。
      • 在一些编程习惯中,传递指针可以暗示转移所有权,即调用者不再需要负责对象的生命周期,而是交给被调用函数。
      • **“是否拥有”**并不是编译器关心的问题,但它能表达出代码设计中的意图。通过这些选择,你可以隐式地传达某个对象是否由当前函数或对象负责管理其生命周期。
传统的 for 循环是否总是在做一些奇怪的事?
  • 为什么选择这种循环?
    • 这个问题的重点是:是否需要使用传统的 for 循环?
      • for 循环在某些情况下非常有用,但它可能会比使用范围 forfor (auto& emp : department))更复杂且不易理解。选择传统的 for 循环可能意味着你在“手动”处理某些特定的逻辑,而不是依赖于标准库提供的算法。
  • 为什么不使用范围 for 循环?
    • for 循环能对每个元素进行逐一操作,而 范围 for 循环(Ranged for)能够更简洁地遍历容器。
  • 是否有算法可以完成这项工作?
    • C++ 标准库中有许多算法,如 findcountall_ofsort 等,它们提供了比传统循环更清晰、更简洁的解决方案。
      结论:使用标准算法而非传统循环能显得你对现有工具的理解更深入,表达了你对代码简洁性和可读性的重视。

2. 初始化

  • 构造函数没有初始化成员变量时的含义
    • 如果构造函数中没有在 : 后进行成员变量的初始化,可能有以下几种情况:
      1. 成员变量有非静态成员初始化器,即它们已经在类中指定了默认值。
      2. 在函数体内进行了初始化,可能是忘记在构造函数的初始化列表中进行初始化。
    • 为什么会这样?
      • 可能是忘记在构造函数中初始化某些成员。
      • 在多个构造函数中,可能有一个构造函数忘记了初始化某个成员。
        为什么将某个成员初始化为其默认值?
    • 例如,string s = "";vector<Employee> department(0);
    • 这种行为表示可能在某些情况下不需要特别设置成员的初始值,或者是某个“默认”状态。

3. 语言能否提供帮助?

  • 我们是否应该添加关键字或属性?你会使用它们吗?
    • implicitconst(false)nonvirtualByVal 等关键字,在当前 C++ 标准中没有,但它们可能能让代码更清晰。
    • 为什么不使用 fallthroughmaybe_unused
      • 在 C++17 中引入了 [[fallthrough]][[maybe_unused]] 属性,这能帮助更清晰地表达意图:
        • [[fallthrough]] 表示在 switch 语句中的 case 之间有意不加 break,表明是“故意跳过”。
        • [[maybe_unused]] 用来标记那些可能未使用的变量,避免编译器警告。

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

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

相关文章

C语言中的输入输出函数:构建程序交互的基石

在C语言的世界里&#xff0c;输入输出&#xff08;I/O&#xff09;操作是程序与用户或外部数据源进行交互的基本方式。无论是从键盘接收用户输入&#xff0c;还是将处理结果显示到屏幕上&#xff0c;亦或是读写文件&#xff0c;都离不开C语言提供的输入输出函数。本文将深入探讨…

高速信号眼图

横轴体系时域的抖动大小&#xff1b;纵轴体现电压的噪声。 噪声越大&#xff0c;眼高越小。 抖动越大&#xff0c;眼宽越窄。 眼图的模板是定义好的最大jitter和噪声的模板范围。就是信号的不可触碰区域。信号波形不能够触碰到模板或者进行模板中。也就是眼图中的线轨迹要在眼…

VisualSVN Server 禁止的特殊符号 导致的。具体分析如下:错误提示解读

是由于 文件夹名称中包含了 VisualSVN Server 禁止的特殊符号 导致的。具体分析如下&#xff1a; 错误提示解读 错误信息明确说明&#xff1a; Folder name cannot contain following symbols < > : " / | and start or end by period. 即 文件夹名称不能包含以下…

再见,WebSecurityConfigurerAdapter!你好,SecurityFilterChain

对于许多经验丰富的 Spring开发者来说&#xff0c;WebSecurityConfigurerAdapter 是一个再熟悉不过的名字。在很长一段时间里&#xff0c;它几乎是所有 Spring Security 配置的起点和核心。然而&#xff0c;随着 Spring Boot 3.x 和 Spring Security 6.x 的普及&#xff0c;这个…

web前端面试-- MVC、MVP、MVVM 架构模式对比

MVC、MVP、MVVM 架构模式对比 基本概念 这三种都是用于分离用户界面(UI)与业务逻辑的架构模式&#xff0c;旨在提高代码的可维护性、可测试性和可扩展性。 1. MVC (Model-View-Controller) 核心结构&#xff1a; Model&#xff1a;数据模型和业务逻辑View&#xff1a;用户界面展…

【C#】MVVM知识点汇总-2

在C#中实现MVVM&#xff08;Model-View-ViewModel&#xff09;架构时&#xff0c;可以总结以下几个关键知识点&#xff0c;并通过具体的代码示例来进行说明。 1. 模型 (Model) 模型包含应用程序中的数据和业务逻辑。通常与数据库交互。 public class User { public int Id {…

一文了解PMI、CSPM、软考、、IPMA、PeopleCert和华为项目管理认证

1 引言 常见的项目管理方面的认证有PMI、IPMA、PeopleCert、CSPM、软考和华为项目管理认证6个认证。本篇文章让你一文了解各认证的基本主要内容。 2 核心定位 目前全球范围内最具影响力的六大认证体系各有特色&#xff0c;源于不同的管理哲学和实践背景。六大认证体系的核心…

bean注入的过程中,Property of ‘java.util.ArrayList‘ type cannot be injected by ‘List‘

一、问题 在spring实践bean注入ArrayList属性的时候报错&#xff1a;Property of ‘java.util.ArrayList’ type cannot be injected by ‘List’二、原因分析 在尝试将 Spring 配置中的 注入到一个 ArrayList 类型的属性时出现了类型不匹配问题。核心问题在于&#xff1a;Spr…

自注意力机制原理: 向量矩阵案例进行说明

自注意力机制原理: 向量矩阵案例进行说明 目录 自注意力机制原理: 向量矩阵案例进行说明一个单词和所有单词进行乘法运算,提取特征一、场景设定:翻译句子“我喜欢深度学习”二、向量矩阵构建:以“我”为例计算自注意力三、矩阵视角:批量计算整个序列的自注意力四、向量矩…

D3 面试题100道之(61-80)

这里是D3的面试题,我们从第 61~80题 开始逐条解答。一共100道,陆续发布中。 🟨 面试题(第 61~80 题) 61. D3 中如何绘制饼图? 使用 d3.pie() 生成角度数据,再结合 d3.arc() 创建路径。 示例: const data = [10, 20, 30

flutter更改第三方库pub get的缓存目录;更改.gradle文件夹存放目录

1.在目标目录中新建文件夹flutter_pub_cache 2.在“用户变量“或“系统变量”中点击“新建” 变量名: PUB_CACHE 变量值: D:\flutter_pub_cache 3.打开新的终端运行或者从Android studio 控制台运行&#xff1a;flutter pub cache repair或者flutter pub clean pub读取新的变…

《Redis》哨兵模式

文章目录 为什么要有哨兵模式呢&#xff1f;哨兵自动恢复故障主节点使用docker搭建分布式系统查看哨兵节点工作哨兵选举新的主节点的流程 总结 为什么要有哨兵模式呢&#xff1f; 主从复制的问题 Redis 的主从复制模式可以将主节点的数据改变同步给从节点&#xff0c;这样从节…

零基础保姆级本地化部署文心大模型4.5开源系列

近两年随着大模型的迅猛崛起&#xff0c;吸引了各行各业的广泛关注&#xff0c;更对我们的工作方式与生活产生着显著积极影响。在这样一个技术范式转换的关键节点&#xff0c;百度文心大模型开源事件无疑具有里程碑意义——它不仅为中国自主研发的AI技术底座打开了通向世界的大…

【笔记】PyCharm 2025.2 EAP 创建 Poetry 和 Hatch 环境的踩坑实录与反馈

https://youtrack.jetbrains.com/issue/PY-82407/Incorrect-Python-Version-and-Virtual-Environment-Path-When-Creating-Poetry-and-Hatch-Environments-via-GUI-in-PyCharm-2025.2-EAP 在 Python 开发的道路上&#xff0c;PyCharm 一直是我们信赖的开发利器。然而&#xff0…

ASP.NET Web Pages 安装使用教程

一、ASP.NET Web Pages 简介 ASP.NET Web Pages 是微软推出的一种轻量级 Web 开发框架&#xff0c;适合快速开发动态网站。它使用 Razor 语法&#xff0c;可以将 HTML 与 C# 或 VB.NET 无缝融合&#xff0c;特别适合初学者和小型项目。 二、Web Pages 与 MVC 的区别 特性Web …

基于 ethers.js 的区块链事件处理与钱包管理

币圈工具箱 bqbot.cn 月访问量达90whttps://bqbot.cn/jms.html &#xff08;在线版地址&#xff09; Event事件 检索事件 const { ethers } require("hardhat"); async function SearchEvent() {try {const provider new ethers.JsonRpcProvider("http://1…

SpringBoot系列—入门

目录 1 第一个SpringBoot程序 1.1 创建SpringBoot项目 1.2 选择SpringBoot版本和必要依赖 1.3 项目目录结构 1.4 编写Hello World代码 1.5 运行程序 1.6 不需要IDEA也能创建SpringBoot程序 1.7 部署程序 1.8 pom.xml依赖问题 1.9 无Maven选项问题 1.10 SpringBoot版…

你的Prompt还有很大提升

与AI协作&#xff0c;Prompt&#xff08;提示词&#xff09;是沟通的桥梁。一个优秀的Prompt能让AI的输出事半功倍&#xff0c;而一个模糊的Prompt则可能导致南辕北辙的结果。如果你觉得AI的回答不够精准、缺乏深度&#xff0c;或者总带着一股“AI味”&#xff0c;那很可能是你…

3、Configuring Topics

如果您在应用程序上下文中定义了KafkaAdmin bean&#xff0c;它可以自动向代理添加主题。为此&#xff0c;您可以将每个主题的NewTopicBean添加到应用程序上下文中。2.3版本引入了一个新的类TopicBuilder&#xff0c;使创建此类bean更加方便。以下示例显示了如何执行此操作&…

FastAPI+React19开发ERP系统实战第04期

一、效果预览 1.1 首页 1.2 首页暗黑模式 1.3 登录页 1.4 登录页暗黑模式 二、搭建React开发环境 2.1 项目依赖 package.json {"name": "erp-web","version": "1.0.0","description": "ERP系统前端 - React 19&quo…