夯实C++基础:1.C++生命周期和编程范式、预处理、编译相关

一直告诉自己要保持学习;;但真的工作之后,反而不知道从哪里开始学起,就这么拖着光有想法没有行动。除了加班没有那么晚刷刷题之外,就从看课有人带着学开始夯实基础吧,反正学啥都比不学好。之后可以看设计模式,网络编程,STL深入学一学,也可以看书effectiveC++,操作系统内核之类//要保持学习呀!


C++ 程序的生命周期

四个阶段:编码、预处理、编译、运行

  1. 编码
  2. 预处理是C/C++独有的阶段,在这个阶段发挥作用的是预处理器Pre-process,输入是编码阶段产生的源码文件,输出是经过“预处理”的源码文件。“预处理”的目的是文字替换,用到的是各种预处理指令,入#define,#include,#if,实现预处理编程。
  3. 在经过预处理之后,还需要经过编译器和连接器去生成在计算机上可运行的二进制机器码,C++编译器要经过分词、语法解析,生成目标码并尽可能去优化。
  4. 编译之后有了可执行文件,C++程序就可以跑起来了,进入运行阶段,==这个时候,“静态的程序”被载入内存,由CPU逐条语句执行,就形成了“动态的进程”。==在这个阶段,我们常做GDB调试、日志追踪、性能分析等

C++编程范式Paradigm

“编程范式”是一种方法论,就是指导你编写代码的一些思路,规则,习惯,定式和常用语
C++是一种多范式的编程语言,支持“面向过程” “面向对象” “泛型” “模板元” “函数式”这五种主要的编程范式,前两种式基础,支撑着后三种范式,

  1. 面向过程:if/else/switch/for/while… 其核心思想是“命令”,通常就是顺序执行语句、子程序(函数),把任务分解成若干个步骤去执行,最终达成目标。
  2. 面向对象:class/public/private/friend…其核心思想是“抽象”和“封装”,倡导把任务分解成一些高内聚低耦合的对象,这些对象互相通信协作来完成任务。强调对象之间的关系和接口,而不是完成任务的具体步骤。
  3. 泛型:template/vector/set/map… 其核心思想是“一切皆为类型”,或者说是“参数化类型” “类型擦除”,使用模板而不是继承的方法来复用代码,所以运行效率更高,代码也更简洁。
  4. 模板元编程:template/struct/enable…if… 是一种高级的复杂的技术,C++对它的支持比较少,更多的是以库的方式来使用,比如type_traits,enable_if等
  5. 函数式编程:lambda 核心思想是“一切皆可调用”,通过一系列连续或者嵌套的函数调用来实现对数据的处理,在C++98的时候就有过函数式的尝试,如bind1st,bind2nd等

宏定义和条件编译

预处理阶段编程的操作目标是“源码”,用各种指令控制预处理器,把源码改造成另一种形式,常用的关键字是#include#define#if都是以#开头;虽然在一个源文件里,但预处理指令不属于C++语言,走的是预处理器,不受C++语法规则的约束。所以不管在函数,类里还是在if for 语句中,#都是顶格写

#include

#include很弱,不做检查,只是把数据合并到头文件里,所以在写头文件的时候,为了防止代码被重复包含,通常要加上“include Guard”,即用#ifndef/#define/#endif来保护整个头文件。(一般会在头文件中强制使用)

宏定义#define/#undef

宏定义,源码级别的”文本替换“,在预处理阶段可以无视C++的语法限制,替换任何文字,定义常量、变量 ,实现函数功能,为类型起别名,减少重复代码。

因为宏的展开和替换发生子啊预处理阶段,不涉及函数调用,参数传递,指针寻址,没有任何运行期的效率损失,所以对于一些调用频繁的小代码片段来说,用宏来封装的效果比inline要好

宏没有作用域概念,永远是全局生效。所以对于一些用于简化代码,起临时作用的宏,用完之后要#undef取消定义⭐

条件编译#if/else/#endif

可以在预处理阶段实现分支处理,通过判断宏的数值来产生不同的源码,改变源文件的形态,这就是“条件编译”
(编译环境

属性和静态断言

热知识:编码阶段和预处理阶段的主要工作还是“文本编辑”,生成的是人类可识别的源码,而==“编译阶段”就不一样了,这是生成计算机可识别的机器码==

编译是预处理之后的阶段,输入是经过预处理的C++源码,输出是二进制可执行文件,可能是汇编文件,动态库或者静态库

:两个编译阶段技巧:属性和静态断言
(让程序员手动执行编译器到这里该怎么做,那里该怎么做,就有可能会产生更高效的代码

属性attribute

在C++11之前,标准里没有这样的东西,在GCC\VC等编译器里有自己的“编译命令”,在GCC中式__attribute__,在VC里式__declspec;到C++11,标准委员会起了一个正式的名字叫属性,可以理解为给变量、函数、类等贴上一个编译阶段的标签,方便编译器识别处理

属性没有新增关键字,而是用两对方括号的形式“[[…]]”,中间的内容就是属性标签,在C++11里值定义了两个属性:“noreturn”和“carries_dependency”,基本没什么用,C++14加了“deprecated”,用来标记不推荐使用的变量,函数或者类,就是被废弃的。加上这个属性,程序会在编译的时候强制警告提醒用户所用的这个接口已经被废弃掉了,这会比文档或者注释有力很多。后面C++17、20又增加了几个属性;
属性支持非标准扩展,允许以类似名字空间的方式使用编译器自己的一个非官方的属性,如GCC的属性都在gnu::里,比较有用的几个有:

  • unused:用于变量、函数、类型等,表示虽然暂时不用,但最好保留,因为将来可能会用
  • constructor:函数会在main()函数之前执行,效果有点像全局对象的构造函数
  • destructor:函数会在main()函数之后执行,效果有点像全局对象的析构函数
  • always-inline:要求编译器强制内联函数,作用比inline更强(PS:内联函数是真正的函数,在调用点处直接展开,避免了函数的参数压栈操作,减少了函数调用的开销
  • hot:标记“热点”函数,要求编译器更积极优化
静态断言static_assert

assert虽然是一个宏,但在预处理阶段不生效,而是在运行阶段才起作用,所以又叫“动态断言
对应的,静态断言是一个专门的关键字static_assert,不是宏,只在编译时生效,运行阶段看不见,所以是静态的。
类比assert,静态断言是编译阶段里检测各种条件的“断言”,编译器看到static_assert就会计算表达式的值,如果false就报错,编译失败。
但是一定要注意,静态断言运行在编译阶段,只能看到编译时的常数和类型,看不到运行时的变量、指针、内存数据等
比图,下面的代码想检查空指针,**由于变量只能在运行阶段出现,而在编译阶段不存在,**所以静态断言无法处理。

char *p = nullptr;
static_assert(p == nullptr, "some errpr");//错误用法

在用静态断言时需要看看断言的表达式是不是能够在编译阶段算出结果
想要更好的发挥静态断言,还要配合标准库的type_traits,它提供了对应这些概念的各种编译器函数。再见后续。。。(关于模板元编程?

PS:使用assert要包含头文件,另外,如果在编译时定义了宏NDEBUG,就会令assert宏为空,完全禁用断言。不要依赖assert来检测或者预防错误,而是要把它作为一种“文档形式的代码”,显示地表示名前提条件和后续结果。

静态断言可以作为编译期地一种约定,配合错误提示能够更快发现编译期的错误

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值