乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

C++ 语言概述与编译模型

定义与作用

C++ 是一种基于 C 的通用编程语言,由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计,最初被称为 "C with Classes"。在 C 语言的坚实基础上,C++ 增加了类、模板、异常处理、命名空间、运算符重载、函数重载、引用、自由存储管理以及丰富的标准库设施。

C++ 的设计哲学可概括为:

  • 零开销抽象:你不为不使用的特性付出成本
  • 直接映射硬件:C++ 提供与 C 同等的底层控制能力
  • 多范式融合:面向过程 + 面向对象 + 泛型编程 + 函数式风格共存

理解 C++ 的编译模型是掌握语言的前提——它不是解释型语言,也不是纯 JIT 编译型,而是经过复杂翻译阶段的原生编译型语言。

核心原理

ISO 标准演进

C++ 的标准化始于 1998 年,每个新标准在扩展语言的同时修正前代缺陷:

C++11 是分水岭——引入了右值引用、移动语义、lambda、智能指针、并发库,标志着"现代 C++"的诞生。本教程以 C++11 为核心,适当延伸至 C++14/17/20。

翻译阶段(9 阶段模型)

从 .cpp 源文件到可执行文件,C++ 经历 9 个翻译阶段(phases of translation):

阶段核心动作可观察结果
阶段 1源文件字符映射到基本源字符集三字符组替换
阶段 2反斜杠换行符拼接物理行合并为逻辑行
阶段 3分解为预处理记号与注释注释替换为空格
阶段 4执行预处理指令#include 展开、宏替换、条件编译
阶段 5字符串字面量字符转换转义序列解析
阶段 6相邻字符串字面量拼接"hello " "world" → "hello world"
阶段 7语法分析与语义检查生成目标代码
阶段 8模板实例化每个实例化单元生成代码
阶段 9链接外部引用解析,生成可执行文件

翻译单元与 ODR

翻译单元(Translation Unit) 是编译的基本单位:

翻译单元 = 一个源文件 + 所有 #include 头文件 - 条件排除代码

单一定义规则(ODR,One Definition Rule) 分两个层面:

  1. 翻译单元内:变量、函数、类、枚举、模板最多只能有一个定义
  2. 整个程序中:非内联函数和非内联变量在整个程序中只能有一个定义,但类、枚举、内联函数等可在多个翻译单元中各有定义(定义必须完全相同)

违反 ODR 是非良构、无需诊断(no diagnostic required) 的行为——编译器可能不报错,但导致未定义行为。

完整示例

示例一:多文件项目的编译流程

场景说明:一个简单学生管理系统,包含头文件声明、源文件实现和主函数,演示翻译单元与链接。

student.hpp

#ifndef STUDENT_HPP
#define STUDENT_HPP

#include <string>

class Student {
public:
    Student(const std::string& name, int score);
    void display() const;

private:
    std::string name_;
    int score_;
};

#endif

student.cpp

#include "student.hpp"
#include <iostream>

Student::Student(const std::string& name, int score)
    : name_(name), score_(score) {}

void Student::display() const {
    std::cout << "姓名: " << name_
              << ", 分数: " << score_ << std::endl;
}

main.cpp

#include "student.hpp"

int main() {
    Student s("张三", 95);
    s.display();
    return 0;
}

编译与链接(GCC 示例):

g++ -std=c++11 -c student.cpp -o student.o    # 编译为对象文件
g++ -std=c++11 -c main.cpp -o main.o          # 编译为对象文件
g++ student.o main.o -o student_app           # 链接为可执行文件

预期输出:

姓名: 张三, 分数: 95

逐段分析:

  • #ifndef/#define/#endif 是包含守卫,防止同一头文件在一个翻译单元中被多次包含,从而违反 ODR
  • student.cpp 和 main.cpp 各自形成一个翻译单元,分别编译为 student.o 和 main.o
  • 链接阶段(阶段 9)将两个目标文件中的符号解析,main.o 中对 Student::display() 的引用被解析到 student.o 中的定义
  • 如果 Student 类在头文件中没有包含守卫,且被多次间接包含,就会触发 ODR 违规

示例二:ODR 违规的典型场景

场景说明:演示内联函数与非内联函数在多翻译单元中的 ODR 差异。

config.hpp(在多个 .cpp 中包含)

#ifndef CONFIG_HPP
#define CONFIG_HPP

// 情况 A: 内联函数——每个翻译单元可以各有一份定义
inline int get_max_connections() {
    return 500;
}

// 情况 B: 非内联函数——整个程序只能有一份定义
int get_port_number();  // 这里只有声明

#endif

config.cpp

#include "config.hpp"

// 定义出现在一个翻译单元中
int get_port_number() {
    return 8080;
}

server.cpp

#include "config.hpp"
#include <iostream>

int main() {
    std::cout << "最大连接数: " << get_max_connections() << std::endl;
    std::cout << "端口号: " << get_port_number() << std::endl;
    return 0;
}

预期输出:

最大连接数: 500
端口号: 8080

逐段分析:

  • get_max_connections 标记为 inline,允许在每个包含 config.hpp 的翻译单元中各有一份定义——链接器会自动去重
  • get_port_number 是普通函数,只能在一个翻译单元中有定义。如果在 config.hpp 中直接写函数体(非 inline),那么每个包含它的翻译单元都会生成定义,导致链接器报 multiple definition 错误
  • 这是初学者最常见的编译链接错误之一:在头文件中定义了非内联函数

错误实践:在头文件中定义非内联函数导致链接错误

// ---- 错误示例: types.hpp ----
#ifndef TYPES_HPP
#define TYPES_HPP

// 错误:非 inline、非模板的普通函数定义在头文件中
int compute(int x) {  
    return x * 2;
}

#endif

// ---- a.cpp ----
#include "types.hpp"

// ---- b.cpp ----
#include "types.hpp"

// 链接阶段报错:
// multiple definition of `compute(int)'

正确做法:要么加 inline,要么声明与定义分离,要么用 static 限制为内部链接。

易错场景与面试考点

易错场景

场景错误表现根因
头文件缺少包含守卫redefinition of 'class X'同一翻译单元中多次包含同类定义
头文件中定义非内联非模板函数multiple definition of 'func'违反整个程序的 ODR
静态变量在头文件中定义多个翻译单元各自拥有独立的静态变量副本不符合"全局唯一"的预期
忘记 inline 关键字链接错误如上C++17 前 inline 变量不被允许

常见面试问题

  1. C 和 C++ 的核心区别是什么?——C++ 在 C 基础上增加了类、模板、异常、重载、引用、命名空间以及标准库(STL),且拥有完全不同的编译期范型能力。

  2. 什么是翻译单元?——一个源文件经过预处理后(包含头文件展开、宏替换、条件编译排除)形成的完整代码块,是编译的基本单位。

  3. ODR(单一定义规则)的两层含义是什么?——翻译单元内实体最多一个定义;整个程序内非内联实体最多一个定义。类、模板、内联函数等可在多翻译单元有定义但必须完全相同。

  4. inline 函数为什么可以定义在头文件中?——因为 C++ 标准规定内联函数在每个使用它的翻译单元中都必须有定义,且链接器会合并相同定义而非报重定义错误。

  5. 翻译的 9 个阶段分别是什么?请按顺序简述。

  6. "良构程序(well-formed)"与"非良构程序(ill-formed)"有何不同?为什么 ODR 违规标为"无需诊断"?——ODR 违规的检测需要跨翻译单元分析,编译器单文件编译时无法完整检测,因此标准允许"无需诊断",但程序已是非良构的。

  7. C++11 相较于 C++98/03 最重要的三大变革是什么?——右值引用与移动语义、lambda 表达式、并发内存模型与线程库。

小结

  • C++ 是原生编译型多范式编程语言,ISO 标准每 3 年迭代
  • 编译经历 9 个翻译阶段,核心概念是翻译单元和 ODR
  • 内联函数与模板可在头文件中定义;普通函数必须声明与定义分离
  • 理解编译模型是解决编译/链接错误的基础
下一页
第一个 C++ 程序与规范