【阅读笔记】linux多线程服务端编程(2)

C/C++相关

C++编译链接模型

C++的三大约束:

  • 与C兼容
  • 零开销(zero overhead
    • 你不需要为你不使用的特性付出(时间或空间)开销;
    • 你使用的任何特性都应该尽可能地高效,至少要和你自己手写代码实现该特性的性能一致。
    • 不符合的:运行时类型识别(RTTI)和异常
  • 值语义
    • 现代更多语言是引用语义
      C++程序编译链接过程
      C++因为使用include机制,导致编译效率非常低,会将库代码重新parse一遍

c++20之后开始有模块化引用头,即import 包名

C语言是单遍编译的,只扫描一次,所以必须先定义才能访问,否则无法立即生成目标代码。
C语言采用了隐式函数声明,代码使用了前文未定义的函数时,编译器认为该函数是int xxx(int, ...)型(C语言一开始没有函数原型的说法,后续从C++借用,也不区分int和指针)
以上两点影响了C++的函数重载决议:当编译器读到一个函数调用时,只能从目前已看到的同名函数中选出最佳函数,而不是全局最佳函数例如:

void fun(int x){
    printf("fun int: %d",x);
}
int main(void){
    fun('a');
    return 0;
}
void fun(char c){
    printf("fun char:%c",c);
}

最终输出fun int: 97

前向声明:

函数的前向声明:
大多数情况下,我们把函数原型声明在.h文件中,而把定义写在.cpp文件中,头文件中的函数原型声明就是函数的前向声明。

类的前向声明:
如果只看到指针或者引用就行,可以使用前向声明,表示有这么个类。
如果仅声明一个返回值或者参数带有这个类的函数,可以使用前向声明。

永远不要重载&&、||、,(逗号)、operator&这几个操作符!!

链接

各个目标文件各自编译完之后,可能会有各种引用,比如一个模块引用了某个库的函数,编译时并不知道这个库的地址,所以只能先空着。链接的目的就在于将这些空填上。

链接思路:

  • 扫描两遍,第一遍记录每个符号地址,第二遍查符号表填补空白
  • 如果要求只在后面引用前面:那么只需扫描一遍,那么当遇到空白时,其所使用的符号都已经在前面出现,直接填即可
  • (one-pass)如果要求只在前面引用后面:那么也只需扫描一遍,不再记录符号地址,而是存储空白(待重填写项),那么后面一定到某个位置可以找到地址,将空白填上,就可以不再处理这个符号对应的空白了。

一般使用one-pass方式,这样相比于第二种方式内存消耗少(不需要存储库中所有符号,只需要存储需要填补的空白即可,而且一旦填上就可以忘掉)

C++增加了函数重载链接规则(name mangling)和弱定义vague linkage规则(同一个符号有多份互不冲突的定义)
vague linkage要求代码满足一次定义原则ODR

函数重载决议

返回值类型不参与函数重载决议

所以当声明和定义返回值类型并不一致的时候,编译器并不会报错。

头文件使用

尽量降低编译依赖
将定义式之间的依赖关系降至最小,避免依赖循环
总是写#include guard

库文件组织

  • 动态库
  • 静态库
  • 源码编译(推荐)

libstdc++是C++标准库,跟C++编译器直接相关
glibc是c标准库,跟Linux操作系统的版本直接相关
一般来说不会随意更改版本

面向对象的反思

朴实为贵,如果不用继承就可以做好事情,就不要通过继承来增加复杂度。只有使用继承(其他技术也一样)可以简化设计的时候,才去用它。
必要时可以动大手脚,避免积重难返。让代码保持清晰是王道!

值语义与对象(引用)语义

C++设计之初,就有一个重要的特性:抽象数据类型ADT,或者说数据抽象。数据抽象听起来似乎和基于对象的编程很像,但他们有本质不同。

数据抽象的目的是将一系列数据作为一个自定义数据类型,并可以提供操作该类型的函数(包括全局函数以及类型成员函数),通常是可拷贝的(拷贝是有意义的,比如将一个复数赋值给另一个复数)。也因此需要考虑拷贝控制(深拷贝)。STL中的大多容器都是值语义的。

基于对象的编程里的对象通常是不可拷贝的(拷贝无意义),这个对象通常代表了一个实体,比如一个线程对象,拷贝并没有什么意义,比如一个连接,拷贝过去之后socket是同一个吗?由谁释放?二者这样做也违背了设计连接对象的初衷。

数据抽象是值语义的,而基于对象的编程/面向对象编程中的对象是引用语义的。

C++没有垃圾回收,直接使用裸指针在面向对象编程中资源回收是个痛点,最好使用智能指针(虽然看起来代码更难看了,但没办法,这是为了避免更大的代价)
C++的数据抽象是0成本的,性能损失小

C++经验谈

代码逻辑应当直截了当,尽量减少依赖关系。

  1. 交换变量老老实实用tmp,不要用异或,异或并不能提高效率和减少内存占用
  2. 别重载new和delete
  3. 整数除法当操作数为负时,结果在各语言中不一定相同
  4. 版本控制友好的代码:
    作者是站在命令行diff的角度来说的,当前来说,图形化的diff当然效率更高,那么有一些规则其实也没必要太在意,下面是我认为需要注意的:
  • 多行注释也用//,这样在提交记录中能显示所有注释内容(嗯,现在大家好像都这样做,/**/都快没人用了,哈哈)
  • 使用static_cast之类的类型转换,原因是便于查找
  1. string的实现
  • 直接拷贝:eager copy
  • COW:copy on write
  • 短字符串优化SSO:有一段短缓冲区,超过限制则转为指针模式

问题

  1. Child和Parent class相互指涉(依赖)?前向声明用法
  2. 编译器如何处理inline函数中的static变量?
  3. 什么是一次定义原则ODR?
  4. P418如何利用g++找到某个头文件是如何引入的,比如只包含了iostream却可以使用std::string

参考书籍
《C++编程规范》陈硕-侯捷
《代码整洁之道》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值