======C++ 介绍======
//第 0 章笔记//
----
====什么是C++====
* 一门比较流行的编程语言
* – https://www.tiobe.com/tiobe-index//
* C 语言的扩展
===关注性能===
==与底层硬件紧密结合==
* Big Endian vs Little Endian:多字节存储,大数存储高位为 Big Endian,反之是 Little Endian
* C++ 没有规定使用哪一种方式来存储数据(但 Java 规定了)
* 相当于把选择权给了硬件
==对象生命周期的精确控制==
* C++ 没有垃圾回收机制,需要处理资源
* 垃圾回收需要额外的系统资源运行
C++ 中的异常处理 只有 try-catch, C# 中有 try-catch-finally. 因为垃圾回收必须要使用 finally。
==Zero-overhead Abstraction==
* 不需要为没有使用的语言特性付出成本
* 虚函数:没有虚函数,就不是抽象类(对应 C#,即便没有派生,也需要付出派生类的成本,因为所有的类都继承于 Object)
* https://godbolt.org/z/fq66hM
* 不用 new 就不用堆 (C# 会)
* 使用了一些语言特性不等于付出运行期成本
* https://godbolt.org/z/Pv9bWj
* 编译期已经处理了函数逻辑
* ''constrval'' C++ 20 关键字,在编译器内部执行,运行期不执行函数,直接返回结果。
==引入大量特性,便于工程实践==
* 一系列不断衍进的标准集合
* C++98/03 , C++11 , C++14 , C++17 , C++20 , C++23 ?
* 三种编程范式:面向过程、面向对象、泛型
* 函数重载、异常处理、引用
* 语言本身的改进
* Memory Model(多线程角度 C++ 11)
* Lambda Expression(C++11)
* 标准库的改进
* type_traits / ranges(容器扩展)
* auto_ptr(C++11 中被智能指针替代)
==C++ 标准的工业界实现==
* MSVC / GCC / Clang
* 每个编译器可能并不完全遵照标准
* https://godbolt.org/z/cKMjK3
* 不同的实现存在差异
* https://godbolt.org/z/6hnPhY
==不能脱离具体的语境讨论 C++==
* 我使用什么样的标准
* 我使用什么样的工具
==编写程序时要注重==
* 性能
* 标准:尽量使用跨平台的库(符合标准的库),避免移植问题
====C++ 的开发环境与相关工具====
* 编译器:Visual C++ / GCC (G++) / Clang (Clang++)
===工具===
* time: 使用 linux 自带的 time 测试程序运行时间
/usr/bin/time
* valgrind:查内存泄漏
* Cpp reference
* Compiler explorer
* 可以查看程序对应的汇编代码
* 代码的分颜色:对应汇编和C++源码
* 可选不同编译器,方便做比较
* C++ Insights:解释代码(比如 for range 是怎么实现的)
* youtube
* cppcon
====C++ 的编译 / 链接模型====
通常情况下,处理程序的方式有两种:
- 简单加工
- 编译+链接
==简单加工模型==
将所有的内容都堆在一块,直接进行编译:
{{ :cs:programming:cpp:courses:cpp_basic_deep:cpp_ref_0_complie.jpg?400 |}}
* 加工时间长
* 少量修改也会导致全部重新加工
==分块处理==
{{ :cs:programming:cpp:courses:cpp_basic_deep:cpp_ref_0_complie_2.jpg?400 |}} \\ \\
每个文件单独编译,再进行链接
* 编译耗费资源,但一次输入少
* 链接输入多,但速度快
* 便于升级(只需要修改需要的文件即可)
===C++ 的编译 / 链接模型===
C++ 基于分块处理的概念来定义自己的编译链接模型。由此概念引申出了几个重要的概念:
==定义与声明==
* 变量的问题
* 如果是简单加工,那么只需要定义一个变量即可
* 如果是分块处理,那么多个文件中很可能都会使用到这个变量
* 处理的办法是:**分离变量的定义与声明**,定义只有一处;在**需要使用的地方进行声明**
* 该定义会在**链接期**进行查找
* 头文件与源文件
* 按需声明的做法,在文件较多的情况下也比较费时费力
* 解决的方法:**将所有的声明装进头文件**,在需要使用的地方**包含该头文件**即可
* 编译器会将头文件自动展开
* 翻译单元(编译器处理)
* 用于处理源文件和头文件的关系
* 将某个源文件,以及相关的头文件,除开应该忽略的预处理语句,构造出来的东西。
* 一处定义原则
* 要求**所有**的翻译单元里只能有**一个定义**(因为编译器必须要看到定义才能编译)
* 程序级:函数
* 翻译单元级:内联函数,类,模板
==编译链接模型==
* **预处理**(//preprocessor//)
* 将源文件变为翻译单元( ''.i'' 文件)
* 防止头文件被循环展开:嵌套的头文件会在预处理过程中反复展开
* 使用宏 ''#ifndef'' 解决:重复定义的 Header 会被当做可丢弃的预处理语句。
* 缺点:宏重名可能导致引入失败
* 使用 ''#pragma once'':对展开进行计数
g++ -E ./main.cpp o ./main.i
* **编译**(//Compiler//):将翻译单元转换为相应的汇编语言,并进行相应的优化(''-O3'')
* 优化的缺点:可能使 debug 的信息丢失, 因此会将程序编译分为 realease 编译(速度)和 debug 编译(调试)
* 增量编译:单独修改某个文件后进行编译,也就是根据源文件的最新时间来判断
* 如果修改了头文件,那么应该重新编译所有的源文件(某些老编译器不支持,此时需要全部编译)
* 全部编译(//rebuild//):改完头文件后没有 自动 rebuild,那么就需要手动 rebuild
g++ main.i -S - o main.s
* **汇编**(//assembler//):汇编代码生成为可链接文件
g++ main.s - c- o main.o
* **链接**(//Linker//):
* 整合所有的目标文件
* 关联声明和定义
* 生成可执行文件
* 链接的种类:
* 内部链接:如果变量只能存在翻译单元里面,那么是内部链接
* 外部链接:如果可以存在于翻译单元之间,那么是外部链接(''extern'')
* 无连接:都不可见,则无连接
* 常见链接错误:定义不可见(比如有声明没定义的情况)
* 查看当前程序的外部链接:''nm target.o - o''