类的生命周期

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++ 主要采用的命名方式。
命名四条规则:
变量、函数名和名字空间用 snake_case,全局变量加“g_”前缀;
自定义类名用 CamelCase,成员函数用 snake_case,成员变量加“m_”前缀;
宏和常量应当全大写,单词之间用下划线连接;
尽量不要用下划线作为变量的前缀或者后缀(比如 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<typename T>
static_assert( is_integral<T>::value, "int");
static_assert( is_pointer<T>::value, "ptr");
static_assert( is_default_constructible<T>::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 可以为类型起别名,既能够简化代码,还能够适应将来的变化。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值