栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > C/C++/C#

【C++ 实战】概论 | 代码风格 | 类 |生命周期 |

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C++ 实战】概论 | 代码风格 | 类 |生命周期 |

文章目录
  • C++ 程序的生命周期
  • 编程范式
  • 代码风格
    • 注释
  • 预处理阶段 :宏定义和条件编译
    • #include
    • 宏定义(#define / #undef)
    • 条件编译(#if/#else/#endif)
  • 编译阶段 : 属性和 静态断言
    • 编译阶段编程
    • 属性(attribute)
    • 静态断言(static_assert)
  • 面向对象编程
    • 设计思想
    • 实现原则
    • 编码准则
    • 常用技巧
      • 委托构造
      • “成员变量初始化”(In-class member initializer)。
    • “类型别名”(Type Alias)。
    • 小结

C++ 程序的生命周期
  • 编码(Coding)、预处理(Pre-processing)、编译(Compiling)和运行 (Running)
  • 编码阶段是 C++ 程序生命周期的起点,也是最重要的阶段,是后续阶段的基础,直接决定 了 C++ 程序的“生存质量, 最基本的要求是遵循语 言规范和设计文档,再高级一点的话,还有代码规范、注释规范、设计模式、编程惯用法, 等等
  • 预处理:发挥作用的是预处理器(Pre-processor)。它的输入是编码阶段产生的源码 文件,输出是经过“预处理”的源码文件。“预处理”的目的是文字替换,用到的就是我们 熟悉的各种预处理指令,比如 #include、#define、#if 等,实现**“预处理编程”**。
  • 编译:(编译 & 链接): C++ 程序——经过预处理的源码——再经过编译器和链接器的“锤 炼”,生成可以在计算机上运行的二进制机器码。编译器还会根据 C++ 语言规则检查程序的语法、语义是否正确,发现错 误就会产生“编译失败”。这就是最基本的 C++“静态检查”
  • 运行阶段:GDB 调试、日志追踪、性能分 析等,然后收集动态的数据、调整设计思路,再返回编码阶段,重走这个“瀑布模型”,实 现“螺旋上升式”的开发.
  • 软件工程里的“蝴蝶效应”“混沌理论”: 一个 Bug 在越早的 阶段发现并解决,它的价值就越高;一个 Bug 在越晚的阶段发现并解决,它的成本就越高。
  • 我们应该在“编码”“预处理”“编译”这前面三个阶段多 下功夫,消灭 Bug,优化代码,尽量不要让 Bug 在“运行”阶段才暴露出来
编程范式

  • 面向对象:核心思想是“抽象”和“封装” , 它强调对象之间的关系和接口,而不是完成任务的具体步骤。
  • 泛型编程:自 STL(标准模板库)纳入到 C++ 标准以后才逐渐流行起来的新范式,核心思想是“一切皆为类型”,使用模板而不是继承的方式来复用代码,运行效率更高,代码简洁。eg.template,vector …
  • 模板元编程: 它的核心思想是“类型运算”,操作的数据是编译时可见的“类型”,所以也比较特殊,代码只能由编译器执行,而不能被运行时 的 CPU 执行。更多的是以库的方式来使用.
  • 函数式: 核心思想是“一切皆可调用”,通过一系列连续或者嵌套的函数调用实现对数据的处
  • 所以常用的范式是“过程 + 对象 + 泛型”,再加上少量的“函数式”,慎用“模板元”。认识、理解这些范式的优势和劣势,在程序里适当混用,取长补短才 是“王道
代码风格

代码风格

  • 留白的艺术 : 恰当地运用空格和空行
  • 起名字: 在于平时的词汇和经验积累,for循环的i/j/k、用于计数的 count、表示指针的 p/ptr、表示缓冲区的 buf/buffer、表示变化量的 delta、表示总和的 sum.
  • snake_case命名:用的是全小写,单词之间用下划线连接。这是 C 和 C++ 主要采用的命名方式。
  • 命名四条规则:
  1. 变量、函数名和名字空间用 snake_case,全局变量加“g_”前缀;
  2. 自定义类名用 CamelCase,成员函数用 snake_case,成员变量加“m_”前缀;
  3. 宏和常量应当全大写,单词之间用下划线连接;
  4. 尽量不要用下划线作为变量的前缀或者后缀(比如 local、name),很难识别。
注释
  • 给代码配上额外的文字,起到注解、补充说明的作用。
  • 注释必须要正确、清晰、有效,尽量言简意赅、点到为止,不要画蛇添足,更不 能写出含糊、错误的注释。
  • 你就可以给它加上作者、功能说明、调用注意事项、可能的返回值, 尽量用英文, 避免乱码.最基本的是不要出现低级的语法、拼写错误
  • 最好在文件的开头写上本文件的注释,里面有文件 的版权声明、更新历史、功能描述,等等。
预处理阶段 :宏定义和条件编译
  • 预处理阶段编程的操作目标是“源码”,用各种指令控制预处理器,把源码改造成另一种形式,就像是捏橡皮泥一样。
  • 预处理指令都以符号“#”开头,不属于 C++ 语言,它走的是预处理器,不受 C++ 语法规则的 约束。
  • 预处理指令不应该受 C++ 代码缩进层次的影响,永远是顶格写.
  • 预处理程序无法调试,得使用 gcc ‘-E’ 选项 ,略过后面的编译链接,只输出预处理后的源码。
  • # 开头 or 顶格写
#include
  • 可以包含任意的文件,不做什么检查,就是“死脑筋”把数据合并进源文件。
  • ** 在写头文件的时候,为了防止代码被重复包含,通常要加上“Include Guard” :用“#ifndef/#define/#endif”来保护整个头文件 。**

  • #include”的特点玩些“小花样”,编写一些 代码片段,存进“*.inc”文件里,然后有选择地加载
宏定义(#define / #undef)
  • #define ;文本替换”, “宏定义”

  • 使用宏的时候一定要谨慎,时刻记着以简化代码、清晰易懂为目标,不要“滥用”, 避免导致源码混乱不堪,降低可读性。

  • 宏是没有作用域概念的,永远是全局生效。

  • 最好是用完后尽快用“#undef”取消定义,避免冲突的风险。

  • 另一种做法是宏定义前先检查,如果之前有定义就先 undef,然后再重新定义:

条件编译(#if/#else/#endif)
  • #if 判断依据
  • 通常编译环境都会有一些预定义宏.
  • CPU 支持的特殊指令集、操作系统 / 编译器 / 程 序库的版本、语言特性等,使用它们就可以早于运行阶段
  • 宏是“__cplusplus”,它标记了 C++ 语言的版本号。
  • g++ -E -dM - < /dev/null 查看C++ 里面其他的预定义的宏

  • 区别是若 #include “” 查找成功,则遮蔽 #include <> 所能找到的同名文件;否则再按照 #include <> 的方式查找文件。另外标准库头文件都放在 #include <> 所查找的位置。一般来说 #include <> 的查找位置是标准库头文件所在目录, #include “” 的查找位置是当前源文件所在目录。不过这些都可由编译器调用参数等配置更改。
编译阶段 : 属性和 静态断言 编译阶段编程
  • 编译阶段 :生成计算机可识别的机器码(machine instruction code)。
  • 编译:输入(经过预处理的)C++ 源码,输出是二进制可执行 文件(也可能是汇编文件、动态库或者静态库)
  • 把编译器想象成是一种特殊的“虚拟机”,在上面跑的是只有编译器才能识别、 处理的代码.
属性(attribute)
  • C11 属性”:你可以把它理解为给变量、函数、类 等“贴”上一个编译阶段的“标签”,方便编译器识别处理。
  • “属性”没有新增关键字,而是用两对方括号的形式“[[…]]”,方括号的中间就是属性标 签
  • 在 C++11 里只定义了两个属性:“noreturn”和“carries_dependency”,它们 基本上没什么大用处。
  • C++14 增加了一个比较实用的属性“deprecated”(弃用的),用来标记不推 荐使用的变量、函数或者类。 可以用在C++11中
[[deprecated("deadline:2020-12-31")]] 
int old_func();

用到这个函数的程序都会报错
 warning: ‘int old_func()’ is deprecated: deadline:2020-12-31 [-Wdeprecated-decl]
  • 几个有用的属性:
    • deprecated:与 C++14 相同,但可以用在 C++11 里。
    • unused:用于变量、类型、函数等,表示虽然暂时不用,但最好保留着,因为将来可能会用。
    • constructor:函数会在 main() 函数之前执行,效果有点像是全局对象的构造函数
    • destructor:函数会在 main() 函数结束之后执行,有点像是全局对象的析构函数。
    • always_inline:要求编译器强制内联函数,作用比 inline 关键字更强。
    • hot:标记“热点”函数,要求编译器更积极地优化。
[[gnu::unused]] int nouse;
// 声明下面的变量暂不使用,不是错误
静态断言(static_assert)
  • assert 吧,它用来断言一个表达式必定为真。比如说,数字必须是正数,指针必须非空、函数必须返回 true:
assert(i > 0 && "i must be greater than zero"); 
assert(p != nullptr); 
assert(!str.empty());
  • 当程序(也就是CPU)运行到 assert 语句时,就会计算表达式的值,如果是 false,就会输出错误消息,然后调用 abort() 终止程序的执行。
  • abort:立刻terminate程序,没有任何清理工作. 相同的exit在调用之前一定要释放应该释放的资源.
  • assert 虽然是一个宏,但在预处理阶段不生效,而是在运行阶段才起作用,所以又 叫“动态断言”。
  • 静态断言:“static_assert”,一个专门的关键字,不是宏。因为它只在编译时生效, 运行阶段看不见,所以是“静态”的。
  • 它是编译阶段里检测各种条件的“断言”,编译器看 到 static_assert 也会计算表达式的值,如果值是 false,就会报错,导致编译失败。
  • 保证我们的程序只在 64 位系统上运行,可以用静态断言在编译阶段检查 long 的大小,必须是 8 个字节
  • static_assert 运行在编译阶段,只能看到编译时的常数和类型
  • 由于变量只能在运行阶段出现,而在编译阶段不存在,所 以静态断言无法处理。
  • 想要更好地发挥静态断言的威力,还要配合标准库里 的“type_traits” (模板元编程)
// 假设T是一个模板参数,即template
static_assert( is_integral::value, "int");
static_assert( is_pointer::value, "ptr");
static_assert( is_default_constructible::value, "constructible");
  • 编译阶段靠的是编译器,当我们可以使用属性 or 宏 进行控制 ,
  • 受语言的限制,编译阶段编程就只能“魔改”那些传统的语法要素了:把类当成函数,把模板参数当成函数参数,把“::”当成 return 返回值
  • 预处理阶段可以自定义宏,但编译阶段不能自定义属性标签,这是为什么呢? 2. 你觉得,怎么用“静态断言”,才能更好地改善代码质量?

实际上是因为属性标签必须要由编译器解释,而自定义标签编译器是不认识的,所以只能等编译器开发者去加,而不能是自己加。静态断言是一种对编译环境的“前提”“假设”,要求在编译阶段必须如何如何。

面向对象编程 设计思想
  • 主要就是 : 要点是抽象(Abstraction)和封装(Encapsulation)。
  • 面向对象编程的基本出发点是“对现实世界的模拟”,把问题中的实体抽象出来,封装为程 序里的类和对象,这样就在计算机里为现实问题建立了一个“虚拟模型”。
  • “继承”的本意是重用代码,表述类型的从属关系(Is-A),但它却不能与现实完全对应, 所以用起来就会出现很多意外情况。
  • “面向对象编程”是一种设计思想,要点是“抽象”和“封装”,“继承”“多态”是 衍生出的特性,不完全符合现实世界。
实现原则
  • 在 C++ 里应当少用继承和虚函数,降低对象的成本,减少冗余代码,提高代码的健壮性,绕过那些难懂易错的陷阱。
  • 如果使用继承的话, 要控制好层次 , 不能超过三层(过分设计).
  • 正确的做法应该是, 定义一个新的名字空间,把内部类都“提”到外面,降低原来类的耦合度和复杂度
编码准则
  • final :关键字 加在函数名后面 , 意思是显示地禁用继承,防止其他人有意或者无意地产生派生类。无论是对人还是对编译器, 效果都非常好,我建议你一定要积极使用.
  • 必须使用继承的场合,建议你只使用 public 继承,避免使用 virtual、protected。 也要加上final 终止继承关系。
  • 在现代 C++ 里,一个类总是会有六大基本函数:三个构造(普通构造、转移构造函数和转移赋值函数)、两个赋值、一个析构.
  • 对于比较重要的构造函数和析构函数,应该用“= default”的形式,明确 地告诉编译器(和代码阅读者):“应该实现这个函数,但我不想自己写。”这样编译器就 得到了明确的指示,可以做更好的优化
  • = delete”的形式。它表示明确地禁用某个函数形式,而且不限于构造 / 析构,可以 用于任何函数(成员函数、自由函数)。
  • 为了防止意外的类型转换,保证安全,就要使用“explicit”将这些函数标记 为“显式”
常用技巧 委托构造

“成员变量初始化”(In-class member initializer)。
  • 在类里声明变量的同时给它赋值,实现初始化,这样不但简单清 晰,也消除了隐患.

“类型别名”(Type Alias)。
  • 名字通常都很长(特别是带上名字空间、模板参数),书 写起来很不方便,简化并且增强了可读性。
小结

1.“面向对象编程”是一种设计思想,要点是“抽象”和“封装”,“继承”“多态”是 衍生出的特性,不完全符合现实世界。
2. 在 C++ 里应当少用继承和虚函数,降低对象的成本,绕过那些难懂易错的陷阱。
3. 使用特殊标识符“final”可以禁止类被继承,简化类的层次关系。
4.类有六大基本函数,对于重要的构造 / 析构函数,可以使用“= default”来显式要求编 译器使用默认实现
4. “委托构造”和“成员变量初始化”特性可以让创建对象的工作更加轻松。
5. 使用 using 或 typedef 可以为类型起别名,既能够简化代码,还能够适应将来的变化。

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1037241.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号