C++ Primer Plus 第6版

前言

《C++ Primer Plus》是一本经典的C++入门教材,由Stephen Prata编写,第6版于2012年出版。这本书以清晰、系统的方式介绍了C++的各个方面,从基础概念到高级特性,适合初学者和有一定编程经验的开发者。

本学习笔记基于《C++ Primer Plus 第6版》,但在原有基础上进行了深度扩展,融入了更多底层原理、高级编程技巧和工程实践经验。笔记内容不仅包括各章节的主要知识点、代码示例、学习重点和注意事项,还增加了底层实现细节、性能优化策略、内存管理深入分析、实际项目案例以及C++标准的最新发展和特性。

适用人群

  • 高级C++开发者:希望深入理解C++底层原理和实现细节的专业人士
  • 系统软件工程师:需要编写高性能、高可靠性系统代码的工程师
  • 嵌入式系统开发者:在资源受限环境中工作的嵌入式开发人员
  • 游戏引擎开发者:需要编写高性能游戏引擎核心代码的工程师
  • 金融系统开发者:需要编写低延迟、高可靠性金融交易系统的工程师
  • 计算机科学专业学生:希望从理论到实践全面掌握C++的学生
  • 代码审查人员:需要快速识别C++代码中潜在问题的专业人士

核心价值

  • 深度解析:从编译器实现、内存模型等底层视角解析C++特性
  • 实战导向:提供大量实际项目中的代码示例和优化案例
  • 性能优化:详细介绍C++性能优化的原理和实践方法
  • 内存管理:深入探讨C++内存管理的最佳实践和常见陷阱
  • 标准演进:涵盖C++98、C++03、C++11、C++14、C++17、C++20等标准的重要变化
  • 现代C++:重点介绍现代C++(C++11及以后)的特性和最佳实践
  • 代码质量:详细讲解C++代码质量保证的方法和工具

本笔记的目标是帮助读者不仅掌握C++的语法和基本用法,更能理解其设计原理和实现机制,从而能够编写出更高效、更可靠、更易于维护的C++代码。

第1章 预备知识

1.1 C++的起源与发展

  • 起源:C++由Bjarne Stroustrup于1979年在贝尔实验室开发,最初名为”C with Classes”,旨在为C语言添加面向对象编程特性,同时保持C语言的高效性和灵活性

  • 设计目标

    • 与C语言兼容,允许C代码无缝集成
    • 支持面向对象编程,包括封装、继承、多态
    • 支持泛型编程,通过模板机制
    • 保持与C语言相近的执行效率
    • 提供丰富的编程范式选择
  • 标准演进

    • C++98:第一个官方标准,奠定了C++的基本语法和特性
    • C++03:对C++98的小修改,主要是修复漏洞和澄清标准
    • C++11:重大更新,引入了许多现代C++特性,如auto类型推导、lambda表达式、右值引用、移动语义、智能指针等
    • C++14:对C++11的扩展,进一步完善了C++11的特性
    • C++17:进一步的改进,引入了结构化绑定、内联变量、fold表达式等
    • C++20:引入更多现代特性,如概念(Concepts)、协程(Coroutines)、模块(Modules)、范围库(Ranges)等
    • C++23:计划中的标准,将进一步完善C++20的特性
  • 技术特点

    • 兼容性:兼容C语言,可直接包含C代码
    • 面向对象:支持封装、继承、多态三大特性
    • 泛型编程:通过模板机制支持编译时多态
    • 元编程:通过模板元编程实现编译时计算
    • 高效性:接近C语言的执行效率,支持直接内存操作
    • 灵活性:支持多种编程范式,如过程式、面向对象、泛型、函数式等
    • 扩展性:支持操作符重载、自定义类型转换等

1.2 编程的本质

  • 编程:是将人类的意图转换为计算机可执行的指令序列的过程,涉及问题分析、算法设计、代码实现、测试调试等多个环节

  • 算法:解决问题的步骤集合,具有确定性、有限性、可行性、输入和输出等特征

    • 时间复杂度:衡量算法执行时间随输入规模增长的变化趋势
    • 空间复杂度:衡量算法所需内存空间随输入规模增长的变化趋势
    • 算法设计范式:贪心、动态规划、分治、回溯、分支限界等
  • 程序:用编程语言表达的算法,是算法在计算机上的具体实现

  • 编译器:将源代码转换为机器语言的工具,是连接人类思维和计算机执行的桥梁

    • 编译过程:预处理、词法分析、语法分析、语义分析、中间代码生成、优化、目标代码生成、链接
    • 编译优化:常量折叠、死代码消除、循环展开、内联函数、寄存器分配、指令重排序等
    • 编译器前端:负责词法分析、语法分析、语义分析等
    • 编译器后端:负责代码生成、优化等
  • 程序执行模型

    • 存储模型:代码段、数据段、堆、栈
    • 执行流程:从main函数开始,顺序执行语句,处理函数调用,直到main函数返回
    • 异常处理模型:try-catch机制,用于处理运行时错误

1.3 C++的应用领域

  • 系统软件:操作系统内核、编译器、汇编器、链接器、数据库管理系统

    • 案例:Linux内核的部分组件、GCC编译器、LLVM、MySQL、PostgreSQL
    • 要求:高性能、高可靠性、直接硬件访问
  • 游戏开发:游戏引擎、物理模拟、图形渲染、AI逻辑

    • 案例:Unity引擎的C++核心、Unreal Engine、CryEngine
    • 要求:高性能、实时性、跨平台
  • 嵌入式系统:微控制器、智能家居设备、汽车电子系统、医疗设备

    • 案例:Arduino核心库、汽车ECU固件、医疗设备控制系统
    • 要求:资源受限、实时性、可靠性
  • 金融领域:高频交易系统、风险管理、量化分析

    • 案例:高频交易平台、风险评估系统
    • 要求:低延迟、高可靠性、安全性
  • 科学计算:数值分析、模拟仿真、信号处理、图像处理

    • 案例:计算流体力学软件、有限元分析软件
    • 要求:高性能、数值精度、并行计算
  • 图形界面:桌面应用、移动应用、图形工具

    • 案例:Qt框架、Adobe软件、AutoCAD
    • 要求:跨平台、用户体验、性能
  • 网络编程:服务器、网络协议实现、网络安全工具

    • 案例:Web服务器、网络协议栈、防火墙
    • 要求:高性能、安全性、可扩展性
  • 区块链:加密货币、智能合约

    • 案例:比特币核心、以太坊虚拟机
    • 要求:安全性、性能、可靠性

1.4 学习C++的准备

  • 编译器工具链

    • GCC (GNU Compiler Collection):开源编译器,支持多种平台和语言
      • 版本演进:从GCC 3.x到GCC 13.x,支持C++标准的逐步完善
      • 优化级别:-O0到-O3,以及-Os(优化代码大小)
    • Clang/LLVM:现代化编译器,具有良好的错误提示和静态分析能力
      • 特点:模块化设计、丰富的诊断信息、强大的静态分析
    • Visual Studio:集成开发环境,提供丰富的调试和分析工具
      • MSVC:Microsoft C++编译器,对Windows平台支持良好
    • Intel C++ Compiler:针对Intel处理器优化的编译器
    • 其他编译器:ICC、PGI、ARM编译器等
  • 开发工具

    • 编辑器:VS Code、Sublime Text、Vim、Emacs、CLion
    • 调试器:GDB、LLDB、Visual Studio Debugger、WinDbg
    • 性能分析工具:Valgrind、Perf、Intel VTune、Visual Studio Performance Profiler
    • 静态分析工具:Clang Static Analyzer、Coverity、PVS-Studio、Cppcheck
    • 代码格式化工具:ClangFormat、AStyle、Uncrustify
    • 构建系统:Make、CMake、Ninja、MSBuild、Bazel
    • 包管理器:vcpkg、Conan、CMake FetchContent
  • 学习资源

    • 官方文档:C++标准文档、编译器文档
    • 书籍:《C++ Primer》、《Effective C++》、《STL源码剖析》、《C++模板元编程》等
    • 在线资源:cppreference.com、Stack Overflow、C++ Core Guidelines
    • 开源项目:Linux内核、LLVM、Qt、Boost库等
  • 操作系统:Windows、Linux、macOS 均可,但Linux平台更适合深入学习C++的底层特性

1.5 第一个C++程序

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

代码解析

  • #include <iostream>:包含输入输出流头文件,引入std::cout等对象的声明
  • int main():主函数,程序的入口点,int表示返回类型为整数
  • std::cout << "Hello, World!" << std::endl:使用输出流对象cout输出字符串到控制台,<<是流插入运算符,std::endl表示换行并刷新缓冲区
  • return 0:返回值0,表示程序正常结束,非零值表示错误或异常情况
  • std:::命名空间前缀,避免命名冲突,指明使用的是标准库中的对象

编译与执行过程

  1. 预处理:处理#include指令,将iostream的内容插入到源文件中
  2. 编译:将预处理后的源代码编译为目标代码(.o文件)
  3. 链接:将目标代码与标准库链接,生成可执行文件
  4. 执行:操作系统加载可执行文件到内存,开始执行main函数

内存布局

  • 代码段:存储可执行指令,包括main函数和cout的实现
  • 数据段:存储全局变量和静态变量
  • :存储局部变量和函数调用信息,包括main函数的栈帧
  • :动态内存分配区域,本程序未使用

执行流程

  1. 操作系统调用main函数
  2. main函数创建std::cout对象(或使用全局对象)
  3. std::cout输出字符串”Hello, World!”到标准输出
  4. std::cout输出std::endl,刷新缓冲区并换行
  5. main函数执行return 0,返回操作系统
  6. 操作系统接收返回值,结束程序执行

现代C++风格的改进

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello, World!\n"; // 使用\n代替std::endl,避免不必要的缓冲区刷新
return 0;
}

性能考虑

  • std::endl会强制刷新缓冲区,可能影响性能
  • 对于简单的输出,使用\n更高效
  • 只有在需要立即看到输出时才使用std::endl

第2章 开始学习C++

2.1 C++程序的结构

  • 预处理指令:以 # 开头的指令,在编译前由预处理器处理

    • 文件包含#include <header.h>#include "header.h"
    • 宏定义#define MACRO value,可带参数
    • 条件编译#if#ifdef#ifndef#else#elif#endif
    • 其他指令#undef#line#error#pragma
  • 函数:程序的基本执行单元,由函数声明、函数定义和函数调用组成

    • 主函数int main(void)int main(int argc, char *argv[]),程序的入口点
    • 用户定义函数:由返回类型、函数名、参数列表和函数体组成
    • 库函数:由标准库或第三方库提供的函数
    • 内联函数:使用 inline 关键字声明,编译时将函数调用替换为函数体
    • ** constexpr 函数**:使用 constexpr 关键字声明,可在编译时计算
  • 语句

    • 表达式语句:由表达式加分号组成,如 x = 5;
    • 复合语句:用花括号括起来的语句块,如 { int x = 5; std::cout << x << '\n'; }
    • 控制语句ifforwhiledo-whileswitchbreakcontinuegotoreturn
    • 声明语句:用于声明变量或函数,如 int x;
    • 空语句:只有一个分号的语句,如 ;
  • 注释

    • 块注释/* 注释内容 */,可跨行
    • 行注释// 注释内容,仅单行
    • 文档注释:用于生成文档的特殊注释格式,如 /** 文档注释 */

2.2 C++的基本语法

  • 标识符:变量、函数、常量、类型等的名称

    • 命名规则:只能由字母、数字和下划线组成,不能以数字开头,区分大小写
    • 命名约定
      • 变量和函数:小写字母加下划线(snake_case),如 int user_id;
      • 常量:全部大写加下划线,如 const int MAX_BUFFER_SIZE = 1024;
      • 类型名:首字母大写(PascalCase),如 class Person {};
      • 命名空间:全部小写或首字母大写,如 namespace my_namespace {}
    • 保留标识符:以下划线开头的标识符通常为系统保留,应避免使用
  • 关键字:C++保留的特殊单词,具有特定的语法含义

    • 存储类别关键字autoregisterstaticexterntypedefmutable
    • 类型关键字voidcharshortintlongfloatdoubleboolwchar_tchar16_tchar32_tautodecltypetypename
    • 控制流关键字ifelseswitchcasedefaultforwhiledobreakcontinuegotoreturn
    • 面向对象关键字classstructunionenumpublicprivateprotectedvirtualoverridefinalexplicitfriendthisnamespaceusing
    • 模板关键字templatetypenameclassconstexprconcept
    • 异常处理关键字trycatchthrownoexcept
    • 其他关键字constconstexprvolatilerestrictalignasalignofsizeoftypeidnewdelete
  • 运算符

    • 算术运算符+-*/%++--
    • 关系运算符==!=<><=>=
    • 逻辑运算符&&||!
    • 位运算符&|^~<<>>
    • 赋值运算符=+=-=*=/=%=&=|=^=<<=>>=
    • 条件运算符?:
    • 逗号运算符,
    • 指针运算符*&
    • 下标运算符[]
    • 成员运算符.->
    • 括号运算符()
    • 大小运算符sizeofalignof
    • 类型转换运算符static_castdynamic_castconst_castreinterpret_cast
  • 分隔符:用于分隔语法元素

    • {}:用于定义块和复合语句
    • ():用于函数调用和表达式分组
    • []:用于数组下标
    • ,:用于分隔参数和表达式
    • ;:用于结束语句
    • ::用于标签、case语句、基类初始化列表
    • :::作用域解析运算符
    • .*->*:指向成员的指针运算符

2.3 数据类型

  • 基本数据类型

    • 整数类型
      • char:通常为 1 字节,可表示字符或小整数
      • signed char:有符号字符,范围通常为 -128 到 127
      • unsigned char:无符号字符,范围通常为 0 到 255
      • short(或 short int):通常为 2 字节
      • unsigned short(或 unsigned short int):无符号短整数
      • int:通常为 4 字节
      • unsigned int:无符号整数
      • long(或 long int):至少 4 字节
      • unsigned long(或 unsigned long int):无符号长整数
      • long long(或 long long int):至少 8 字节(C++11)
      • unsigned long long(或 unsigned long long int):无符号长长整数
    • 浮点类型
      • float:单精度浮点数,通常为 4 字节,精度约为 7 位小数
      • double:双精度浮点数,通常为 8 字节,精度约为 15-17 位小数
      • long double:扩展精度浮点数,通常为 12 或 16 字节
    • 布尔类型
      • bool:布尔类型,只能存储 truefalse
    • 字符类型
      • char:单字节字符
      • wchar_t:宽字符,用于存储 Unicode 字符
      • char16_t:16 位 Unicode 字符(C++11)
      • char32_t:32 位 Unicode 字符(C++11)
  • 复合数据类型

    • 数组:相同类型元素的集合,如 int arr[10];
    • 字符串
      • C风格字符串:以空字符 '\0' 结尾的字符数组
      • C++字符串:使用 std::string
    • 结构体:不同类型成员的集合,如 struct Person { std::string name; int age; };
    • 联合体:所有成员共享同一块内存的类型,如 union Data { int i; float f; char c; };
    • 枚举
      • 传统枚举:enum Color { RED, GREEN, BLUE };
      • 枚举类(C++11):enum class Color { RED, GREEN, BLUE };
    • 指针:存储内存地址的变量,如 int *p;
    • 引用:变量的别名,必须在初始化时绑定到一个变量,如 int &ref = x;
    • 函数类型:由返回类型和参数列表决定的类型
  • 类型修饰符

    • const:修饰的对象不可修改
    • constexpr:修饰的表达式可在编译时计算
    • volatile:修饰的对象可能被外部因素修改,禁止编译器优化
    • restrict:修饰的指针是访问其指向对象的唯一方式(C++11)
    • mutable:修饰的成员变量,即使在 const 成员函数中也可修改
    • alignas:指定类型或变量的对齐方式

2.4 变量和常量

  • 变量

    • 声明int age;,告诉编译器变量的类型和名称
    • 定义:为变量分配内存空间,如 int age;(声明并定义)或 extern int age;(仅声明)
    • 初始化
      • 默认初始化:int x;(未初始化,值不确定)
      • 值初始化:int x = 0;int x{};
      • 直接初始化:int x(5);
      • 列表初始化(C++11):int x{5};int x = {5};
    • 存储类别
      • auto:自动存储类别,默认,存储在栈中
      • register:寄存器存储类别,建议编译器将变量存储在寄存器中
      • static:静态存储类别,存储在静态存储区,生命周期为整个程序
      • extern:外部存储类别,用于声明在其他文件中定义的变量
      • thread_local:线程本地存储类别,每个线程有自己的副本(C++11)
  • 常量

    • 字面常量:直接出现在代码中的值,如 100(整型)、3.14(浮点型)、'A'(字符型)、"Hello"(字符串型)
    • const 常量:用 const 修饰的变量,如 const int MAX_AGE = 100;
    • constexpr 常量表达式:用 constexpr 修饰的表达式,如 constexpr int MAX_AGE = 100;
    • 枚举常量:枚举类型的成员,如 enum Color { RED, GREEN, BLUE }; 中的 REDGREENBLUE
    • 宏常量:用 #define 定义的常量,如 #define PI 3.14159
    • 常量表达式 vs 常量
      • 常量表达式:可在编译时计算的值,如 constexpr int N = 5;
      • 常量:运行时不可修改的值,如 const int N = get_value();

2.5 输入输出

  • 标准输入输出

    • 输出流
      • std::cout:标准输出流,默认指向控制台
      • std::cerr:标准错误流,默认指向控制台,无缓冲
      • std::clog:标准日志流,默认指向控制台,有缓冲
    • 输入流
      • std::cin:标准输入流,默认来自控制台
      • std::cin.get():读取单个字符
      • std::cin.getline():读取一行字符
    • 文件流
      • std::ifstream:输入文件流
      • std::ofstream:输出文件流
      • std::fstream:输入输出文件流
  • 流操作符

    • 插入运算符<<,用于输出,如 std::cout << "Hello" << '\n';
    • 提取运算符>>,用于输入,如 std::cin >> x;
  • 流操纵符

    • 格式控制std::setwstd::setprecisionstd::fixedstd::scientific
    • 状态控制std::boolalphastd::noboolalphastd::hexstd::decstd::oct
    • 其他std::endl(换行并刷新)、std::flush(刷新缓冲区)、std::ends(添加空字符)
  • 输入输出的性能考虑

    • 缓冲策略:使用缓冲区减少I/O操作次数
    • 避免频繁刷新:减少使用 std::endl,改用 '\n'
    • 批量操作:尽量一次性读取或写入大量数据

2.6 程序示例:计算圆的面积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cmath> // 用于数学函数
#include <iomanip> // 用于输出格式控制

/**
* 计算圆的面积
* @param radius 圆的半径
* @return 圆的面积
*/
double calculate_circle_area(double radius) {
// 使用M_PI宏获取更精确的圆周率值
return M_PI * radius * radius;
}

int main() {
double radius, area;

// 提示用户输入
std::cout << "请输入圆的半径:";

// 读取用户输入并验证
if (!(std::cin >> radius)) {
std::cerr << "错误:输入无效!\n";
return 1;
}

// 验证半径是否为正数
if (radius < 0) {
std::cerr << "错误:半径不能为负数!\n";
return 1;
}

// 计算面积
area = calculate_circle_area(radius);

// 输出结果,保留6位小数
std::cout << "圆的面积是:" << std::fixed << std::setprecision(6) << area << '\n';

return 0;
}

代码分析

  • 包含了必要的头文件:<iostream> 用于输入输出,<cmath> 用于数学函数,<iomanip> 用于输出格式控制
  • 使用了 M_PI 宏获取更精确的圆周率值
  • 添加了函数注释,遵循文档注释规范
  • 增加了输入验证,确保输入有效
  • 使用 std::cerr 输出错误信息,区分标准输出和标准错误
  • 函数化设计,提高代码复用性和可维护性
  • 使用 std::fixedstd::setprecision(6) 控制输出格式,保留6位小数
  • 使用 '\n' 代替 std::endl,避免不必要的缓冲区刷新

性能优化

  • 函数 calculate_circle_area 简洁高效,适合被编译器内联
  • 避免了不必要的变量和计算,减少内存使用和执行时间
  • 使用了标准库提供的常量和函数,确保精度和性能

可移植性考虑

  • 使用标准 C++ 语法和库函数,确保在不同平台上的兼容性
  • 避免了平台特定的特性和依赖
  • 错误处理方式符合标准 C++ 规范

第3章 处理数据

3.1 数据类型概述

  • 数据类型的作用

    • 决定变量占用的内存空间大小和对齐方式
    • 决定变量可以存储的值的范围
    • 决定变量可以参与的操作和运算符
    • 提供编译时类型检查,减少运行时错误
    • 帮助编译器进行类型相关的优化
  • 基本存储单位

    • 位(bit):最小的存储单位,只能存储 0 或 1
    • 字节(byte):通常由 8 位组成,是计算机存储的基本单位
    • 字(word):计算机一次处理的位数,与处理器架构有关(如 32 位或 64 位)
    • 双字(double word):通常为字长的两倍
    • 四字(quad word):通常为字长的四倍
  • C++的数据类型分类

    • 基本类型:整数、浮点、字符、布尔
    • 复合类型:数组、字符串、结构体、联合体、枚举、指针、引用、函数类型
    • 自定义类型:类、模板、别名类型(typedef、using)
    • 依赖类型: decltype 推导的类型、auto 推导的类型

3.2 整数类型

  • 有符号整数:可以表示正数、负数和零

    • int:通常为 4 字节,范围通常为 -2,147,483,648 到 2,147,483,647
    • short(或 short int):通常为 2 字节,范围通常为 -32,768 到 32,767
    • long(或 long int):至少为 4 字节,在 32 位系统通常为 4 字节,64 位系统通常为 8 字节
    • long long(或 long long int):至少为 8 字节,范围通常为 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
  • 无符号整数:只能表示非负数

    • unsigned int:通常为 4 字节,范围为 0 到 4,294,967,295
    • unsigned short(或 unsigned short int):通常为 2 字节,范围为 0 到 65,535
    • unsigned long(或 unsigned long int):至少为 4 字节
    • unsigned long long(或 unsigned long long int):至少为 8 字节,范围为 0 到 18,446,744,073,709,551,615
  • 特殊整型

    • size_t:无符号整型,用于表示大小,通常为 unsigned longunsigned long long
    • ptrdiff_t:有符号整型,用于表示指针差值
    • intptr_tuintptr_t:用于存储指针值的整型(C++11)
  • 整型的取值范围

    • 有符号整型:-2^(n-1) ~ 2^(n-1)-1,其中 n 为位数
    • 无符号整型:0 ~ 2^n-1,其中 n 为位数
  • 确定整型范围的方法

    • 使用 <climits> 头文件中的宏,如 INT_MININT_MAXUINT_MAX
    • 使用 std::numeric_limits<T>::min()std::numeric_limits<T>::max()(需要包含 <limits> 头文件)
  • 整型的底层实现

    • 二进制表示:使用补码表示有符号整数,原码表示无符号整数
    • 位运算:支持按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移(<<)、右移(>>)
    • 字节序:小端序(低位字节在前)或大端序(高位字节在前),取决于硬件平台

3.3 字符类型

  • char:通常为 1 字节,用于存储字符,可表示 ASCII 码

  • signed char:有符号字符,范围通常为 -128 到 127

  • unsigned char:无符号字符,范围为 0 到 255

  • wchar_t:宽字符类型,用于存储 Unicode 字符,通常为 2 或 4 字节

  • char16_t:16 位 Unicode 字符(C++11),用于存储 UTF-16 编码的字符

  • char32_t:32 位 Unicode 字符(C++11),用于存储 UTF-32 编码的字符

  • 字符的编码

    • ASCII码:0-127,包含基本拉丁字母、数字和控制字符
    • 扩展ASCII:128-255,包含更多字符,如重音符号
    • Unicode:支持全球所有字符的编码标准
      • UTF-8:可变长度编码,1-4字节,兼容ASCII
      • UTF-16:可变长度编码,2或4字节
      • UTF-32:固定长度编码,4字节
  • 字符类型的使用

    • 文本处理:使用 char 处理 ASCII 或 UTF-8 文本,使用 wchar_tchar16_tchar32_t 处理 Unicode 文本
    • 字节操作:使用 unsigned char 处理原始字节数据,避免符号扩展问题
    • 位操作unsigned char 也适合进行位操作

3.4 布尔类型

  • bool:布尔类型,只能存储 true(1)或 false(0)

  • 布尔值的转换

    • 其他类型到 bool 的转换
      • 零值(0、0.0、空指针、空字符串等)转换为 false
      • 非零值转换为 true
    • bool 到其他类型的转换
      • true 转换为 1(整型)或 1.0(浮点型)
      • false 转换为 0(整型)或 0.0(浮点型)
  • 布尔型的底层实现

    • 通常占用 1 字节内存,但在某些情况下(如结构体中的位域)可能只占用 1 位
    • 在表达式中,布尔值通常以整型形式处理
  • 布尔型的性能考虑

    • 条件分支:布尔值常用于条件分支,影响程序的控制流
    • 位操作:对于大量布尔值,可以使用位域或位集来节省内存

3.5 浮点类型

  • float:单精度浮点数,通常为 4 字节,精度约为 7 位小数

  • double:双精度浮点数,通常为 8 字节,精度约为 15-17 位小数

  • long double:扩展精度浮点数,通常为 12 或 16 字节,精度更高

  • 浮点型的表示

    • IEEE 754 标准
      • 单精度(float):1 位符号位,8 位指数位,23 位尾数位
      • 双精度(double):1 位符号位,11 位指数位,52 位尾数位
      • 扩展精度(long double):因平台而异,通常为 1 位符号位,15 位指数位,64 位尾数位
    • 表示范围
      • 单精度:约 ±3.4×10^38
      • 双精度:约 ±1.7×10^308
      • 扩展精度:约 ±1.1×10^4932
  • 浮点型的特殊值

    • 正无穷+infinity,由正数除以零或溢出产生
    • 负无穷-infinity,由负数除以零或溢出产生
    • NaN(Not a Number):非数值,由无效操作产生,如 0/0、无穷减无穷等
    • 正零负零:表示零的两种形式
  • 浮点型的比较

    • 精度问题:由于浮点表示的有限精度,应避免直接使用 ==!= 比较浮点数
    • 推荐方法:比较两个浮点数的差值是否小于一个很小的阈值(epsilon)
    • 特殊值比较:NaN 与任何值(包括自身)比较都返回 false

3.6 类型转换

  • 隐式转换:编译器自动进行的转换

    • 算术转换:小类型转换为大类型(如 intdouble
    • 赋值转换:赋值时,右侧转换为左侧类型
    • 初始化转换:初始值转换为变量类型
    • 函数调用转换:实参转换为形参类型
    • 返回值转换:函数返回值转换为函数声明的返回类型
    • 布尔转换:任何类型转换为 bool
    • 指针转换:如 nullptr 转换为任何指针类型
  • 显式转换(强制类型转换):

    • C 风格转换(type)expression,功能强大但不安全
    • C++ 风格转换
      • static_cast:用于相关类型之间的转换,如 intdouble、基类指针到派生类指针(无运行时检查)
      • dynamic_cast:用于多态类型之间的转换,如派生类指针到基类指针(有运行时检查)
      • const_cast:用于移除 const 限定符,如 const int *int *
      • reinterpret_cast:用于不相关类型之间的转换,如 intvoid *char *int *
  • 类型转换的底层原理

    • 位模式转换:如 intfloat 的转换会改变位模式
    • 指针转换:通常只是改变指针的类型,不改变其值
    • 引用转换:与指针转换类似,只是语法不同
  • 类型转换的风险

    • 精度丢失:如 doubleint 的转换
    • 溢出:如大整数到小整数的转换
    • 未定义行为:如无效的指针转换
    • 内存访问错误:如错误的指针类型转换导致的内存访问

3.7 sizeof 运算符

  • sizeof:返回操作数的大小(以字节为单位)

    1
    2
    std::cout << "int 的大小:" << sizeof(int) << " 字节" << std::endl;
    std::cout << "double 的大小:" << sizeof(double) << " 字节" << std::endl;
  • sizeof 的使用场景

    • 内存分配:计算需要分配的内存大小
    • 数组长度:计算数组元素个数(sizeof(array) / sizeof(array[0])
    • 类型信息:获取类型的大小,用于序列化/反序列化
    • 对齐计算:计算类型的对齐要求
  • sizeof 的特点

    • 编译时计算sizeof 表达式在编译时计算,不产生运行时开销
    • 操作数不求值sizeof(expr) 中的表达式不会被执行
    • 对数组:返回整个数组的大小(字节),而不是指针的大小
    • 对指针:返回指针本身的大小,与指针指向的类型无关
    • 对函数:返回函数返回类型的大小
  • alignof 运算符

    • 返回类型的对齐要求(以字节为单位)
    1
    2
    std::cout << "int 的对齐要求:" << alignof(int) << " 字节" << std::endl;
    std::cout << "double 的对齐要求:" << alignof(double) << " 字节" << std::endl;

第4章 复合类型

4.1 数组

  • 数组:是相同类型元素的集合,在内存中连续存储

  • 数组的声明

    1
    int numbers[5]; // 声明一个包含 5 个整数的数组
  • 数组的初始化

    • 完整初始化int numbers[5] = {1, 2, 3, 4, 5};
    • 部分初始化int numbers[5] = {1, 2, 3}; // 剩余元素初始化为0
    • 自动计算大小int numbers[] = {1, 2, 3, 4, 5}; // 编译器自动计算大小为5
    • 列表初始化(C++11):int numbers[5] {1, 2, 3, 4, 5}; // 不允许窄化转换
    • 零初始化int numbers[5] = {}; // 所有元素初始化为0
  • 数组的访问

    1
    2
    int numbers[] = {1, 2, 3, 4, 5};
    std::cout << numbers[0] << std::endl; // 输出第一个元素,索引从0开始
  • 数组的底层实现

    • 内存布局:数组元素在内存中连续存储,每个元素占据相同大小的内存空间
    • 地址计算numbers[i] 等价于 *(numbers + i),其中 numbers 是指向第一个元素的指针
    • 边界检查:C++ 不进行数组边界检查,越界访问是未定义行为
  • 数组的性能考虑

    • 访问速度:数组元素的访问时间是常数时间(O(1)),因为可以通过指针算术直接计算地址
    • 缓存友好:连续存储的特性使得数组访问对缓存更友好
    • 空间开销:数组的空间开销很小,只需要存储元素本身
  • 数组的局限性

    • 固定大小:数组大小在编译时确定,不能动态调整
    • 缺乏边界检查:可能导致越界访问错误
    • 作为函数参数:数组作为函数参数时会退化为指针,丢失大小信息
  • 数组与指针的关系

    • 数组名:在大多数情况下,数组名会隐式转换为指向第一个元素的指针
    • 指针算术:可以使用指针算术来访问数组元素
    • 数组衰减:数组作为函数参数时会衰减为指针

4.2 字符串

  • C风格字符串:以空字符 '\0' 结尾的字符数组

    1
    2
    char name[20] = "John"; // 自动添加空字符
    char name[] = "John"; // 编译器自动计算大小为5(包含空字符)
  • C++字符串:使用 std::string 类(位于 <string> 头文件中)

    1
    2
    3
    4
    #include <string>

    std::string name = "John"; // 构造字符串对象
    std::cout << name << std::endl; // 输出字符串
  • C风格字符串的操作

    • 长度计算strlen(name) // 不包含空字符
    • 复制strcpy(dest, src) // 可能导致缓冲区溢出
    • 安全复制strncpy(dest, src, size) // 限制复制长度
    • 连接strcat(dest, src) // 可能导致缓冲区溢出
    • 安全连接strncat(dest, src, size) // 限制连接长度
    • 比较strcmp(s1, s2) // 返回0表示相等
  • std::string 的操作

    • 长度计算name.size()name.length()
    • 访问元素name[0]name.at(0) // at() 会进行边界检查
    • 添加字符name.push_back('!')
    • 添加字符串name += " Doe"name.append(" Doe")
    • 插入name.insert(4, " ")
    • 删除name.erase(4, 1) // 从位置4开始删除1个字符
    • 查找name.find("Doe") // 返回第一次出现的位置
    • 替换name.replace(4, 3, "Smith") // 从位置4开始替换3个字符
    • 子串name.substr(0, 4) // 从位置0开始提取4个字符
    • 转换为C风格字符串name.c_str()name.data()
  • 字符串的底层实现

    • C风格字符串:简单的字符数组,以空字符结尾
    • std::string:通常使用小字符串优化(SSO),短字符串存储在栈上,长字符串存储在堆上
  • 字符串的性能考虑

    • C风格字符串
      • 优点:空间开销小,访问速度快
      • 缺点:不安全,容易导致缓冲区溢出
    • std::string
      • 优点:安全,提供丰富的操作,自动管理内存
      • 缺点:空间开销较大,某些操作可能较慢

4.3 结构体

  • 结构体:是一种复合数据类型,可以包含不同类型的成员

  • 结构体的声明

    1
    2
    3
    4
    5
    struct Person {
    std::string name;
    int age;
    double height;
    };
  • 结构体变量的定义和初始化

    • 聚合初始化Person p1 = {"John", 30, 1.75};
    • 默认初始化Person p2; // 成员变量默认初始化
    • 列表初始化(C++11):Person p3 {"John", 30, 1.75};
    • 指定初始化(C++20):Person p4 {.name = "John", .age = 30};
  • 结构体的内存布局

    • 成员对齐:结构体成员通常按照其类型的对齐要求进行对齐
    • 内存填充:为了满足对齐要求,编译器可能会在成员之间插入填充字节
    • 结构体大小:至少是所有成员大小的总和,可能更大(由于填充)
    • 计算大小:使用 sizeof(Person) 获取结构体大小
    • 控制对齐:使用 #pragma packalignas 关键字控制对齐方式
  • 结构体的操作

    • 成员访问:使用 . 运算符(对于对象)或 -> 运算符(对于指针)
    • 复制:可以直接赋值 Person p5 = p1;
    • 比较:默认情况下,结构体不支持比较运算符,需要手动定义
  • 结构体的高级特性

    • 成员函数:结构体可以包含成员函数
    • 访问控制:可以使用 publicprivateprotected 控制成员访问
    • 继承:结构体可以继承自其他结构体或类(默认是 public 继承)
    • 模板结构体:可以定义模板结构体

4.4 共用体

  • 共用体:是一种复合数据类型,所有成员共享同一块内存

  • 共用体的声明

    1
    2
    3
    4
    5
    union Data {
    int i;
    float f;
    char c;
    };
  • 共用体的特点

    • 内存共享:所有成员共享同一块内存,共用体的大小至少是最大成员的大小
    • 活跃成员:在任何时刻,只有一个成员是活跃的
    • 类型双关:可以通过不同类型的成员访问同一块内存,实现类型双关
    • 未定义行为:读取非活跃成员的值是未定义行为(C++11之前)
    • 类型安全:C++11引入了受限联合,可以包含具有非平凡构造函数的类型
  • 共用体的使用场景

    • 节省内存:当不同类型的数据不会同时使用时
    • 类型双关:在不同类型之间转换数据
    • 解析二进制数据:如网络协议、文件格式等
    • 变体类型:实现简单的变体类型(现代C++中推荐使用 std::variant
  • 共用体的内存布局

    • 起始地址:所有成员的起始地址相同
    • 大小:至少是最大成员的大小,可能更大(由于对齐要求)
    • 对齐:共用体的对齐要求是其成员中最大的对齐要求

4.5 枚举

  • 枚举:是一种整数类型,其值为一组命名的常量

  • 传统枚举的声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum Weekday {
    MONDAY, // 默认值为0
    TUESDAY, // 默认值为1
    WEDNESDAY, // 默认值为2
    THURSDAY, // 默认值为3
    FRIDAY, // 默认值为4
    SATURDAY, // 默认值为5
    SUNDAY // 默认值为6
    };
  • 枚举的特点

    • 默认值:第一个枚举常量默认值为0,后续常量默认值依次递增1
    • 显式赋值:可以为枚举常量显式赋值,如 MONDAY = 1
    • 类型转换:枚举值可以隐式转换为整数
    • 作用域:传统枚举的作用域与枚举名所在的作用域相同,可能导致名称冲突
  • 枚举类(C++11):提供更安全的枚举类型

    1
    2
    3
    4
    5
    6
    7
    enum class Color {
    RED, // 值为0
    GREEN, // 值为1
    BLUE // 值为2
    };

    Color c = Color::RED; // 必须使用作用域解析运算符
  • 枚举类的特点

    • 作用域限定:枚举常量的作用域限定在枚举类内部,避免名称冲突
    • 类型安全:枚举类的值不会隐式转换为整数,需要显式转换
    • 底层类型:默认底层类型为 int,可以显式指定,如 enum class Color : char { ... }
    • 前向声明:可以前向声明枚举类,如 enum class Color;
  • 枚举的使用场景

    • 状态表示:表示对象的不同状态
    • 选项标志:表示一组相关的选项
    • 错误代码:表示不同类型的错误
    • 有限集合:表示有限的、命名的值集合

4.6 指针和引用

  • 指针:是存储内存地址的变量

  • 指针的声明

    1
    int *p; // 声明一个指向int的指针
  • 指针的初始化

    • 指向变量int x = 10; int *p = &x;
    • 空指针int *p = nullptr; // C++11推荐,之前使用 NULL0
    • 野指针:未初始化的指针,指向未知内存区域
  • 指针的操作

    • 解引用*p // 获取指针指向的值
    • 取地址&x // 获取变量的地址
    • 指针算术p++p--p + np - n
    • 指针比较p1 == p2p1 < p2
  • 指针的类型

    • 普通指针int *p
    • 常量指针int *const p // 指针本身不可修改
    • 指向常量的指针const int *pint const *p // 指针指向的值不可修改
    • 指向常量的常量指针const int *const p // 指针本身和指向的值都不可修改
    • void指针void *p // 可以指向任何类型的对象,但不能直接解引用
    • ** nullptr**:C++11引入的空指针字面量,类型为 std::nullptr_t
  • 引用:是变量的别名,必须在初始化时绑定到一个变量

  • 引用的声明

    1
    2
    int x = 10;
    int &ref = x; // 声明一个引用,绑定到x
  • 引用的特点

    • 必须初始化:引用必须在声明时初始化
    • 不能重新绑定:一旦绑定到一个变量,就不能再绑定到其他变量
    • 不能为空:引用不能绑定到空值
    • 不能建立引用的引用:不能声明引用的引用
    • 作为函数参数:可以避免值拷贝,修改实参
    • 作为函数返回值:可以返回左值
  • 引用的类型

    • 左值引用int &ref = x; // 绑定到左值
    • 右值引用int &&ref = 10; // 绑定到右值(C++11)
    • 常量引用const int &ref = x; // 可以绑定到左值或右值
  • 指针与引用的区别

    • 空值:指针可以为空,引用不能为空
    • 重新绑定:指针可以重新指向其他对象,引用不能
    • 内存开销:指针占用内存空间,引用不占用额外内存空间
    • 解引用:指针需要显式解引用,引用不需要
    • 多级间接:可以有指针的指针,不能有引用的引用
    • 类型转换:指针可以进行各种类型转换,引用的类型转换受到更多限制

4.7 程序示例:结构体和指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <string>
#include <iomanip> // 用于输出格式控制

/**
* 人员结构体
* @note 包含姓名、年龄和身高信息
*/
struct Person {
std::string name; ///< 姓名
int age; ///< 年龄
double height; ///< 身高(米)
};

/**
* 打印人员信息
* @param p 指向Person对象的指针
* @note 使用const指针,确保不会修改传入的对象
*/
void print_person(const Person *p) {
if (p == nullptr) {
std::cerr << "错误:空指针!\n";
return;
}

std::cout << std::left; // 左对齐输出
std::cout << "姓名:" << std::setw(10) << p->name << " ";
std::cout << "年龄:" << std::setw(5) << p->age << " ";
std::cout << "身高:" << std::fixed << std::setprecision(2) << p->height << " 米" << std::endl;
}

/**
* 主函数
*/
int main() {
// 使用列表初始化创建Person对象
Person p1 {"John", 30, 1.75};

// 创建指向p1的指针
Person *pp = &p1;

// 打印人员信息
std::cout << "人员信息:\n";
print_person(pp);

// 修改指针指向的对象的成员
pp->age = 31;
pp->height = 1.76;

// 再次打印人员信息,验证修改
std::cout << "修改后的人员信息:\n";
print_person(pp);

return 0;
}

代码分析

  • 结构体定义:定义了包含姓名、年龄和身高的Person结构体
  • 函数参数:使用const指针作为函数参数,确保函数不会修改传入的对象
  • 空指针检查:在print_person函数中添加了空指针检查,提高代码的健壮性
  • 输出格式:使用iomanip头文件中的函数控制输出格式,使输出更加整齐
  • 错误处理:对于空指针情况,使用cerr输出错误信息
  • 代码注释:添加了详细的注释,包括函数说明、参数说明和注意事项

内存分析

  • Person对象:在栈上分配内存,包含name(std::string对象)、age(int)和height(double)
  • pp指针:在栈上分配内存,存储p1的地址
  • 内存布局:p1的内存布局取决于编译器的对齐策略,通常age和height会按照其类型的对齐要求进行对齐

性能考虑

  • 传参方式:使用指针传递大型结构体,避免值拷贝的开销
  • 空指针检查:添加空指针检查会增加少量运行时开销,但提高了代码的健壮性
  • 输出操作:使用std::left和std::setw等格式化操作会增加少量运行时开销,但提高了输出的可读性

第5章 循环和关系表达式

5.1 循环的概念

  • 循环:重复执行一段代码的结构
  • 循环三要素:初始化、条件判断、更新

5.2 while 循环

  • 基本语法
    1
    2
    3
    while (条件) {
    // 循环体
    }
  • 执行流程
    1. 检查条件是否为真
    2. 如果为真,执行循环体
    3. 重复步骤 1 和 2
    4. 如果为假,退出循环

5.3 for 循环

  • 基本语法
    1
    2
    3
    for (初始化; 条件; 更新) {
    // 循环体
    }
  • 执行流程
    1. 执行初始化
    2. 检查条件是否为真
    3. 如果为真,执行循环体
    4. 执行更新
    5. 重复步骤 2 到 4
    6. 如果为假,退出循环

5.4 do-while 循环

  • 基本语法
    1
    2
    3
    do {
    // 循环体
    } while (条件);
  • 执行流程
    1. 执行循环体
    2. 检查条件是否为真
    3. 如果为真,重复步骤 1 和 2
    4. 如果为假,退出循环
  • 特点:循环体至少执行一次

5.5 循环的嵌套

  • 嵌套循环:一个循环包含另一个循环
    1
    2
    3
    4
    5
    6
    for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
    std::cout << "* ";
    }
    std::cout << std::endl;
    }

5.6 循环控制语句

  • break:跳出当前循环
    1
    2
    3
    4
    5
    6
    for (int i = 1; i <= 10; i++) {
    if (i == 5) {
    break; // 当 i=5 时跳出循环
    }
    std::cout << i << " ";
    }
  • continue:跳过当前循环的剩余部分,进入下一次循环
    1
    2
    3
    4
    5
    6
    for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
    continue; // 跳过偶数
    }
    std::cout << i << " ";
    }
  • goto:跳转到指定标签(尽量避免使用)
    1
    2
    3
    4
    5
    6
    7
    int i = 1;
    loop:
    std::cout << i << " ";
    i++;
    if (i <= 5) {
    goto loop;
    }

5.7 关系表达式

  • 关系运算符==!=<><=>=
  • 逻辑运算符&&(与)、||(或)、!(非)
  • 条件运算符?:
    1
    2
    3
    int a = 10, b = 20;
    int max = (a > b) ? a : b;
    std::cout << "最大值:" << max << std::endl;

5.8 程序示例:猜数字游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <cstdlib>
#include <ctime>

int main() {
int secret_number, guess, attempts = 0;

// 初始化随机数生成器
std::srand(std::time(nullptr));

// 生成 1-100 之间的随机数
secret_number = std::rand() % 100 + 1;

std::cout << "猜数字游戏!\n";
std::cout << "我想了一个 1-100 之间的数字,你能猜出来吗?\n\n";

do {
std::cout << "请输入你的猜测:";
std::cin >> guess;
attempts++;

if (guess < secret_number) {
std::cout << "太小了!再试试。\n\n";
} else if (guess > secret_number) {
std::cout << "太大了!再试试。\n\n";
} else {
std::cout << "恭喜你猜对了!答案是 " << secret_number << "。\n";
std::cout << "你用了 " << attempts << " 次尝试。\n";
}
} while (guess != secret_number);

return 0;
}

第6章 分支语句和逻辑运算符

6.1 if 语句

  • 基本语法
    1
    2
    3
    if (条件) {
    // 条件为真时执行
    }
  • if-else 语句
    1
    2
    3
    4
    5
    if (条件) {
    // 条件为真时执行
    } else {
    // 条件为假时执行
    }
  • 嵌套 if 语句
    1
    2
    3
    4
    5
    6
    7
    if (条件1) {
    // 条件1为真时执行
    } else if (条件2) {
    // 条件2为真时执行
    } else {
    // 所有条件都为假时执行
    }

6.2 switch 语句

  • 基本语法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    switch (表达式) {
    case 常量1:
    // 代码
    break;
    case 常量2:
    // 代码
    break;
    // 更多 case
    default:
    // 代码
    break;
    }
  • 注意事项
    • 表达式的值必须是整数类型或枚举类型
    • 每个 case 后必须有 break,否则会继续执行下一个 case
    • default 是可选的

6.3 逻辑运算符

  • &&(逻辑与):当且仅当两个操作数都为真时,结果为真
  • ||(逻辑或):当至少一个操作数为真时,结果为真
  • !(逻辑非):取反操作数的逻辑值

6.4 程序示例:成绩等级评定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

int main() {
int score;
char grade;

std::cout << "请输入分数(0-100):";
std::cin >> score;

if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else if (score >= 60) {
grade = 'D';
} else {
grade = 'F';
}

std::cout << "等级:" << grade << std::endl;

return 0;
}

第7章 函数——C++的编程模块

7.1 函数的概念

  • 函数:是完成特定任务的代码块
  • 函数的优点
    • 模块化:将复杂任务分解为小任务
    • 代码重用:避免重复代码
    • 可维护性:便于修改和调试
    • 可读性:使代码结构更清晰

7.2 函数的声明和定义

  • 函数声明:告诉编译器函数的存在和签名
    1
    int add(int, int); // 函数声明
  • 函数定义:实现函数的具体功能
    1
    2
    3
    int add(int a, int b) { // 函数定义
    return a + b;
    }

7.3 函数的参数和返回值

  • 参数:函数接收的输入值
    • 形式参数(形参):函数定义中的参数
    • 实际参数(实参):函数调用时传递的参数
  • 返回值:函数执行后返回的结果
    • 使用 return 语句返回值
    • 如果函数没有返回值,使用 void 类型

7.4 函数调用

  • 函数调用:执行函数的代码
    1
    int result = add(5, 3); // 调用 add 函数,传入实参 5 和 3

7.5 函数的重载

  • 函数重载:是指在同一作用域内,可以有多个同名函数,只要它们的参数列表不同
    1
    2
    3
    4
    5
    6
    7
    int add(int a, int b) {
    return a + b;
    }

    double add(double a, double b) {
    return a + b;
    }

7.6 函数的默认参数

  • 默认参数:是指在函数声明时为参数指定默认值
    1
    2
    3
    void print_info(std::string name, int age = 18) {
    std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
    }

7.7 函数的作用域

  • 局部变量:在函数内部定义的变量,只在函数内部可见
  • 全局变量:在函数外部定义的变量,在整个程序中可见

7.8 递归函数

  • 递归:函数调用自身的过程
  • 递归的条件
    • 终止条件:避免无限递归
    • 递归步骤:将问题分解为更小的子问题
  • 示例:计算阶乘
    1
    2
    3
    4
    5
    6
    int factorial(int n) {
    if (n <= 1) {
    return 1; // 终止条件
    }
    return n * factorial(n - 1); // 递归调用
    }

7.9 程序示例:函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

// 重载 add 函数
int add(int a, int b) {
std::cout << "调用 int 版本的 add 函数" << std::endl;
return a + b;
}

double add(double a, double b) {
std::cout << "调用 double 版本的 add 函数" << std::endl;
return a + b;
}

int add(int a, int b, int c) {
std::cout << "调用三个参数的 add 函数" << std::endl;
return a + b + c;
}

int main() {
std::cout << "add(1, 2) = " << add(1, 2) << std::endl;
std::cout << "add(1.5, 2.5) = " << add(1.5, 2.5) << std::endl;
std::cout << "add(1, 2, 3) = " << add(1, 2, 3) << std::endl;

return 0;
}

第8章 函数探幽

8.1 内联函数

  • 内联函数:是指在编译时将函数调用替换为函数体的函数,减少函数调用的开销
  • 声明方式:使用 inline 关键字
    1
    2
    3
    inline int max(int a, int b) {
    return (a > b) ? a : b;
    }

8.2 引用变量

  • 引用:是变量的别名,必须在初始化时绑定到一个变量
  • 引用的声明
    1
    2
    int x = 10;
    int &ref = x;
  • 引用作为函数参数
    1
    2
    3
    4
    5
    void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
    }
  • 常量引用:是指向常量的引用,不能通过引用修改所指对象的值
    1
    const int &ref = x;

8.3 函数模板

  • 函数模板:是一种通用函数,可以处理不同类型的参数
  • 模板的声明
    1
    2
    3
    4
    template <typename T>
    T max(T a, T b) {
    return (a > b) ? a : b;
    }
  • 模板的使用
    1
    2
    3
    4
    5
    int a = 10, b = 20;
    std::cout << max(a, b) << std::endl;

    double c = 1.5, d = 2.5;
    std::cout << max(c, d) << std::endl;

8.4 程序示例:引用和函数模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

// 引用作为参数
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}

// 函数模板
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}

int main() {
// 测试引用
int x = 10, y = 20;
std::cout << "交换前:x = " << x << ", y = " << y << std::endl;
swap(x, y);
std::cout << "交换后:x = " << x << ", y = " << y << std::endl;

// 测试函数模板
std::cout << "max(10, 20) = " << max(10, 20) << std::endl;
std::cout << "max(1.5, 2.5) = " << max(1.5, 2.5) << std::endl;
std::cout << "max('a', 'b') = " << max('a', 'b') << std::endl;

return 0;
}

第9章 内存模型和名称空间

9.1 内存模型

  • 内存的分配方式
    • 静态存储区:存储全局变量、静态变量
    • 栈:存储局部变量、函数参数
    • 堆:动态分配的内存

9.2 存储类别

  • auto:自动存储类别,默认存储类别
  • static:静态存储类别
  • extern:外部存储类别
  • register:寄存器存储类别

9.3 名称空间

  • 名称空间:是一种将全局作用域划分为多个子作用域的机制,避免命名冲突
  • 名称空间的声明
    1
    2
    3
    4
    5
    namespace MyNamespace {
    int add(int a, int b) {
    return a + b;
    }
    }
  • 名称空间的使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 使用作用域解析运算符
    int result = MyNamespace::add(5, 3);

    // 使用 using 声明
    using MyNamespace::add;
    int result = add(5, 3);

    // 使用 using 指令
    using namespace MyNamespace;
    int result = add(5, 3);

9.4 程序示例:名称空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>

// 声明名称空间
namespace Math {
int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}
}

namespace Geometry {
const double PI = 3.14159;

double area(double radius) {
return PI * radius * radius;
}
}

int main() {
// 使用作用域解析运算符
std::cout << "Math::add(5, 3) = " << Math::add(5, 3) << std::endl;
std::cout << "Math::subtract(5, 3) = " << Math::subtract(5, 3) << std::endl;
std::cout << "Geometry::area(2) = " << Geometry::area(2) << std::endl;

// 使用 using 声明
using Math::add;
std::cout << "add(10, 20) = " << add(10, 20) << std::endl;

// 使用 using 指令
using namespace Geometry;
std::cout << "area(3) = " << area(3) << std::endl;
std::cout << "PI = " << PI << std::endl;

return 0;
}

第10章 对象和类

10.1 面向对象编程的基本概念

  • 面向对象编程(OOP):是一种编程范式,以对象为中心,通过封装、继承、多态等特性来组织代码
  • 对象:是类的实例,具有状态(属性)和行为(方法)
  • :是对象的蓝图,定义了对象的属性和方法

10.2 类的定义

  • 类的声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Person {
    private:
    std::string name;
    int age;
    public:
    // 构造函数
    Person(std::string n, int a);

    // 方法
    void set_name(std::string n);
    std::string get_name() const;
    void set_age(int a);
    int get_age() const;
    void print_info() const;
    };

10.3 构造函数和析构函数

  • 构造函数:是一种特殊的成员函数,在创建对象时自动调用,用于初始化对象
    1
    2
    3
    4
    Person::Person(std::string n, int a) {
    name = n;
    age = a;
    }
  • 析构函数:是一种特殊的成员函数,在对象销毁时自动调用,用于清理资源
    1
    2
    3
    Person::~Person() {
    // 清理资源
    }

10.4 访问控制

  • 访问控制符
    • public:公有成员,可以在任何地方访问
    • private:私有成员,只能在类内部访问
    • protected:保护成员,可以在类内部和派生类中访问

10.5 类的实现

  • 类的实现:在类外部定义成员函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    void Person::set_name(std::string n) {
    name = n;
    }

    std::string Person::get_name() const {
    return name;
    }

    void Person::set_age(int a) {
    if (a >= 0) {
    age = a;
    }
    }

    int Person::get_age() const {
    return age;
    }

    void Person::print_info() const {
    std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
    }

10.6 对象的创建和使用

  • 对象的创建
    1
    2
    Person p1("John", 30); // 栈上创建对象
    Person *p2 = new Person("Mary", 25); // 堆上创建对象
  • 对象的使用
    1
    2
    3
    4
    5
    p1.print_info();
    p2->print_info();

    // 释放堆上的对象
    delete p2;

10.7 程序示例:简单的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <string>

class Person {
private:
std::string name;
int age;
public:
// 构造函数
Person(std::string n, int a);

// 析构函数
~Person();

// 方法
void set_name(std::string n);
std::string get_name() const;
void set_age(int a);
int get_age() const;
void print_info() const;
};

// 构造函数的实现
Person::Person(std::string n, int a) {
name = n;
age = a;
std::cout << "构造函数被调用:" << name << std::endl;
}

// 析构函数的实现
Person::~Person() {
std::cout << "析构函数被调用:" << name << std::endl;
}

// 方法的实现
void Person::set_name(std::string n) {
name = n;
}

std::string Person::get_name() const {
return name;
}

void Person::set_age(int a) {
if (a >= 0) {
age = a;
}
}

int Person::get_age() const {
return age;
}

void Person::print_info() const {
std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
}

int main() {
// 栈上创建对象
Person p1("John", 30);
p1.print_info();

// 堆上创建对象
Person *p2 = new Person("Mary", 25);
p2->print_info();

// 修改对象属性
p1.set_name("Tom");
p1.set_age(35);
p1.print_info();

p2->set_name("Lisa");
p2->set_age(28);
p2->print_info();

// 释放堆上的对象
delete p2;

return 0;
}

第11章 使用类

11.1 运算符重载

  • 运算符重载:是指为类定义运算符的行为,使运算符能够处理类的对象
  • 运算符重载的声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Vector {
    private:
    double x, y;
    public:
    Vector(double x = 0, double y = 0);
    Vector operator+(const Vector &v) const;
    Vector operator-(const Vector &v) const;
    Vector operator*(double scalar) const;
    friend Vector operator*(double scalar, const Vector &v);
    void print() const;
    };

11.2 友元函数

  • 友元函数:是指可以访问类的私有成员的非成员函数
  • 友元函数的声明
    1
    2
    3
    4
    5
    6
    7
    class Vector {
    private:
    double x, y;
    public:
    // 声明友元函数
    friend Vector operator*(double scalar, const Vector &v);
    };

11.3 类的自动转换和强制转换

  • 类型转换
    • 构造函数转换:使用单参数构造函数进行转换
    • 类型转换运算符:定义从类类型到其他类型的转换
    1
    2
    3
    4
    5
    6
    7
    class MyInt {
    private:
    int value;
    public:
    MyInt(int v = 0) : value(v) {}
    operator int() const { return value; }
    };

11.4 程序示例:运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>

class Vector {
private:
double x, y;
public:
// 构造函数
Vector(double x = 0, double y = 0) : x(x), y(y) {}

// 运算符重载
Vector operator+(const Vector &v) const {
return Vector(x + v.x, y + v.y);
}

Vector operator-(const Vector &v) const {
return Vector(x - v.x, y - v.y);
}

Vector operator*(double scalar) const {
return Vector(x * scalar, y * scalar);
}

// 友元函数
friend Vector operator*(double scalar, const Vector &v);

// 方法
void print() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};

// 友元函数的实现
Vector operator*(double scalar, const Vector &v) {
return Vector(v.x * scalar, v.y * scalar);
}

int main() {
Vector v1(1, 2);
Vector v2(3, 4);

std::cout << "v1 = ";
v1.print();

std::cout << "v2 = ";
v2.print();

Vector v3 = v1 + v2;
std::cout << "v1 + v2 = ";
v3.print();

Vector v4 = v1 - v2;
std::cout << "v1 - v2 = ";
v4.print();

Vector v5 = v1 * 2;
std::cout << "v1 * 2 = ";
v5.print();

Vector v6 = 3 * v2;
std::cout << "3 * v2 = ";
v6.print();

return 0;
}

第12章 类和动态内存分配

12.1 动态内存分配

  • 动态内存分配:是指在运行时分配内存,使用 newdelete 运算符
  • new 运算符:分配内存并返回指向该内存的指针
    1
    2
    int *p = new int(10); // 分配单个整数
    int *arr = new int[5]; // 分配整数数组
  • delete 运算符:释放动态分配的内存
    1
    2
    delete p; // 释放单个整数
    delete[] arr; // 释放整数数组

12.2 类中的动态内存管理

  • 类中的动态内存:当类需要管理动态分配的内存时,需要正确实现构造函数、析构函数、复制构造函数和赋值运算符
  • 复制构造函数:用于创建一个新对象,作为现有对象的副本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class String {
    private:
    char *str;
    public:
    String(const char *s = "");
    String(const String &other); // 复制构造函数
    ~String();
    String &operator=(const String &other); // 赋值运算符
    void print() const;
    };

12.3 深拷贝和浅拷贝

  • 浅拷贝:只复制指针,不复制指针指向的内容,会导致多个对象共享同一块内存
  • 深拷贝:复制指针指向的内容,每个对象拥有自己的内存

12.4 移动语义(C++11)

  • 移动语义:是指将资源从一个对象转移到另一个对象,而不是复制资源
  • 移动构造函数
    1
    String(String &&other) noexcept;
  • 移动赋值运算符
    1
    String &operator=(String &&other) noexcept;

12.5 程序示例:动态内存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <cstring>

class String {
private:
char *str;
public:
// 构造函数
String(const char *s = "") {
str = new char[strlen(s) + 1];
strcpy(str, s);
std::cout << "构造函数:" << str << std::endl;
}

// 复制构造函数
String(const String &other) {
str = new char[strlen(other.str) + 1];
strcpy(str, other.str);
std::cout << "复制构造函数:" << str << std::endl;
}

// 移动构造函数(C++11)
String(String &&other) noexcept : str(other.str) {
other.str = nullptr;
std::cout << "移动构造函数:" << str << std::endl;
}

// 赋值运算符
String &operator=(const String &other) {
if (this != &other) {
delete[] str;
str = new char[strlen(other.str) + 1];
strcpy(str, other.str);
std::cout << "赋值运算符:" << str << std::endl;
}
return *this;
}

// 移动赋值运算符(C++11)
String &operator=(String &&other) noexcept {
if (this != &other) {
delete[] str;
str = other.str;
other.str = nullptr;
std::cout << "移动赋值运算符:" << str << std::endl;
}
return *this;
}

// 析构函数
~String() {
std::cout << "析构函数:" << (str ? str : "nullptr") << std::endl;
delete[] str;
}

// 方法
void print() const {
std::cout << str << std::endl;
}
};

int main() {
// 测试构造函数
String s1("Hello");
s1.print();

// 测试复制构造函数
String s2 = s1;
s2.print();

// 测试赋值运算符
String s3;
s3 = s1;
s3.print();

// 测试移动构造函数
String s4 = std::move(s1);
s4.print();

// 测试移动赋值运算符
String s5;
s5 = std::move(s4);
s5.print();

return 0;
}

第13章 类继承

13.1 继承的基本概念

  • 继承:是指一个类(派生类)从另一个类(基类)继承属性和方法
  • 继承的目的:代码重用、扩展功能
  • 继承的类型
    • 公有继承(public):基类的公有成员在派生类中仍然是公有的,保护成员仍然是保护的
    • 保护继承(protected):基类的公有和保护成员在派生类中都是保护的
    • 私有继承(private):基类的公有和保护成员在派生类中都是私有的

13.2 派生类的定义

  • 派生类的声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Student : public Person {
    private:
    std::string student_id;
    double gpa;
    public:
    // 构造函数
    Student(std::string name, int age, std::string id, double g);

    // 方法
    void set_student_id(std::string id);
    std::string get_student_id() const;
    void set_gpa(double g);
    double get_gpa() const;
    void print_info() const override;
    };

13.3 构造函数和析构函数的调用顺序

  • 构造函数的调用顺序:基类构造函数 → 派生类构造函数
  • 析构函数的调用顺序:派生类析构函数 → 基类析构函数

13.4 虚函数和多态

  • 虚函数:是指在基类中声明为 virtual 的成员函数,派生类可以重写
  • 多态:是指通过基类指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数
  • 虚函数的声明
    1
    2
    3
    4
    class Person {
    public:
    virtual void print_info() const;
    };
  • 纯虚函数:是指在基类中声明但没有实现的虚函数,派生类必须实现
    1
    2
    3
    4
    class Shape {
    public:
    virtual double area() const = 0; // 纯虚函数
    };

13.5 程序示例:继承和多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <string>

// 基类
class Person {
protected:
std::string name;
int age;
public:
// 构造函数
Person(std::string n, int a) : name(n), age(a) {}

// 虚函数
virtual void print_info() const {
std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
}

// 虚析构函数
virtual ~Person() {}
};

// 派生类
class Student : public Person {
private:
std::string student_id;
double gpa;
public:
// 构造函数
Student(std::string n, int a, std::string id, double g)
: Person(n, a), student_id(id), gpa(g) {}

// 重写虚函数
void print_info() const override {
std::cout << "姓名:" << name << ",年龄:" << age
<< ",学号:" << student_id << ",GPA:" << gpa << std::endl;
}
};

// 派生类
class Teacher : public Person {
private:
std::string teacher_id;
std::string department;
public:
// 构造函数
Teacher(std::string n, int a, std::string id, std::string dept)
: Person(n, a), teacher_id(id), department(dept) {}

// 重写虚函数
void print_info() const override {
std::cout << "姓名:" << name << ",年龄:" << age
<< ",工号:" << teacher_id << ",部门:" << department << std::endl;
}
};

int main() {
// 创建对象
Person p("John", 30);
Student s("Mary", 20, "S12345", 3.8);
Teacher t("Tom", 40, "T67890", "Computer Science");

// 直接调用
std::cout << "直接调用:" << std::endl;
p.print_info();
s.print_info();
t.print_info();

// 通过基类指针调用(多态)
std::cout << "\n通过基类指针调用:" << std::endl;
Person *ptr[] = {&p, &s, &t};
for (int i = 0; i < 3; i++) {
ptr[i]->print_info();
}

// 通过基类引用调用(多态)
std::cout << "\n通过基类引用调用:" << std::endl;
Person &ref1 = p;
Person &ref2 = s;
Person &ref3 = t;
ref1.print_info();
ref2.print_info();
ref3.print_info();

return 0;
}

第14章 C++中的代码重用

14.1 组合

  • 组合:是指一个类包含另一个类的对象作为成员,实现代码重用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Address {
    private:
    std::string street;
    std::string city;
    public:
    Address(std::string s, std::string c) : street(s), city(c) {}
    void print() const {
    std::cout << street << ", " << city << std::endl;
    }
    };

    class Person {
    private:
    std::string name;
    Address address; // 组合
    public:
    Person(std::string n, std::string s, std::string c)
    : name(n), address(s, c) {}
    void print() const {
    std::cout << "姓名:" << name << ",地址:";
    address.print();
    }
    };

14.2 私有继承

  • 私有继承:是指派生类从基类私有继承,基类的公有和保护成员在派生类中都是私有的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Base {
    public:
    void public_method() {}
    protected:
    void protected_method() {}
    };

    class Derived : private Base {
    public:
    void use_base_methods() {
    public_method(); // 可以访问
    protected_method(); // 可以访问
    }
    };

14.3 多重继承

  • 多重继承:是指一个类从多个基类继承属性和方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class A {
    public:
    void methodA() {}
    };

    class B {
    public:
    void methodB() {}
    };

    class C : public A, public B {
    public:
    void methodC() {
    methodA();
    methodB();
    }
    };

14.4 虚继承

  • 虚继承:是指解决多重继承中的菱形继承问题,确保派生类只包含基类的一个实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Base {
    public:
    int value;
    Base(int v) : value(v) {}
    };

    class Derived1 : virtual public Base {
    public:
    Derived1(int v) : Base(v) {}
    };

    class Derived2 : virtual public Base {
    public:
    Derived2(int v) : Base(v) {}
    };

    class Derived3 : public Derived1, public Derived2 {
    public:
    Derived3(int v) : Base(v), Derived1(v), Derived2(v) {}
    };

14.5 程序示例:组合和继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <string>

// 地址类
class Address {
private:
std::string street;
std::string city;
public:
Address(std::string s, std::string c) : street(s), city(c) {}
void print() const {
std::cout << street << ", " << city;
}
};

// 基类:人
class Person {
protected:
std::string name;
int age;
Address address; // 组合
public:
Person(std::string n, int a, std::string s, std::string c)
: name(n), age(a), address(s, c) {}

virtual void print_info() const {
std::cout << "姓名:" << name << ",年龄:" << age << ",地址:";
address.print();
std::cout << std::endl;
}

virtual ~Person() {}
};

// 派生类:学生
class Student : public Person {
private:
std::string student_id;
double gpa;
public:
Student(std::string n, int a, std::string s, std::string c,
std::string id, double g)
: Person(n, a, s, c), student_id(id), gpa(g) {}

void print_info() const override {
std::cout << "学生信息:" << std::endl;
Person::print_info();
std::cout << "学号:" << student_id << ",GPA:" << gpa << std::endl;
}
};

// 派生类:教师
class Teacher : public Person {
private:
std::string teacher_id;
std::string department;
public:
Teacher(std::string n, int a, std::string s, std::string c,
std::string id, std::string dept)
: Person(n, a, s, c), teacher_id(id), department(dept) {}

void print_info() const override {
std::cout << "教师信息:" << std::endl;
Person::print_info();
std::cout << "工号:" << teacher_id << ",部门:" << department << std::endl;
}
};

int main() {
// 创建学生对象
Student s("Mary", 20, "123 Main St", "New York", "S12345", 3.8);
s.print_info();
std::cout << std::endl;

// 创建教师对象
Teacher t("Tom", 40, "456 Oak Ave", "Boston", "T67890", "Computer Science");
t.print_info();

return 0;
}

第15章 友元、异常和其他

15.1 友元

  • 友元函数:可以访问类的私有成员的非成员函数
  • 友元类:可以访问另一个类的私有成员的类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A {
    private:
    int value;
    public:
    A(int v) : value(v) {}
    friend class B; // 声明 B 为友元类
    };

    class B {
    public:
    void access_A(A &a) {
    std::cout << a.value << std::endl; // 可以访问
    }
    };

15.2 异常处理

  • 异常:是指程序运行时发生的错误,如除以零、数组越界等
  • 异常处理:使用 trycatchthrow 关键字来处理异常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try {
    int a = 10;
    int b = 0;
    if (b == 0) {
    throw std::runtime_error("除数不能为零");
    }
    int result = a / b;
    std::cout << "结果:" << result << std::endl;
    } catch (const std::exception &e) {
    std::cerr << "错误:" << e.what() << std::endl;
    }

15.3 类型识别

  • 类型识别:使用 typeid 运算符来获取对象的类型信息
    1
    2
    3
    4
    5
    6
    7
    #include <typeinfo>

    class Base { virtual void f() {} };
    class Derived : public Base {};

    Base *b = new Derived;
    std::cout << typeid(*b).name() << std::endl; // 输出 Derived 的类型信息

15.4 程序示例:异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <stdexcept>

// 自定义异常类
class NegativeNumberException : public std::exception {
public:
const char *what() const noexcept override {
return "负数异常:不能使用负数";
}
};

// 函数:计算平方根
double square_root(double x) {
if (x < 0) {
throw NegativeNumberException();
}
if (x == 0) {
return 0;
}

// 简单的平方根计算
double guess = x / 2;
for (int i = 0; i < 10; i++) {
guess = (guess + x / guess) / 2;
}
return guess;
}

int main() {
try {
double x = -4;
double result = square_root(x);
std::cout << "平方根:" << result << std::endl;
} catch (const NegativeNumberException &e) {
std::cerr << "捕获到自定义异常:" << e.what() << std::endl;
} catch (const std::exception &e) {
std::cerr << "捕获到标准异常:" << e.what() << std::endl;
} catch (...) {
std::cerr << "捕获到未知异常" << std::endl;
}

try {
double x = 9;
double result = square_root(x);
std::cout << "平方根:" << result << std::endl;
} catch (const std::exception &e) {
std::cerr << "错误:" << e.what() << std::endl;
}

return 0;
}

第16章 字符串、向量和数组

16.1 标准字符串类(std::string)

  • std::string:是 C++ 标准库提供的字符串类,提供了丰富的字符串操作方法
  • 常用方法
    • size():返回字符串长度
    • empty():检查字符串是否为空
    • append():追加字符串
    • insert():插入字符串
    • erase():删除字符
    • substr():获取子字符串
    • find():查找子字符串
    • replace():替换子字符串

16.2 标准向量类(std::vector)

  • std::vector:是 C++ 标准库提供的动态数组类,支持自动扩容
  • 常用方法
    • size():返回元素个数
    • empty():检查是否为空
    • push_back():在末尾添加元素
    • pop_back():删除末尾元素
    • at():访问指定位置的元素(带边界检查)
    • front():访问第一个元素
    • back():访问最后一个元素
    • clear():清空向量
    • resize():调整向量大小

16.3 数组(std::array)

  • std::array:是 C++11 引入的固定大小数组类,提供了更安全、更方便的数组操作
  • 常用方法
    • size():返回元素个数
    • empty():检查是否为空
    • at():访问指定位置的元素(带边界检查)
    • front():访问第一个元素
    • back():访问最后一个元素

16.4 程序示例:字符串和向量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
#include <string>
#include <vector>

int main() {
// 测试字符串
std::string s1 = "Hello";
std::string s2 = "World";

// 字符串拼接
std::string s3 = s1 + " " + s2;
std::cout << "s3 = " << s3 << std::endl;

// 字符串长度
std::cout << "s3.size() = " << s3.size() << std::endl;

// 字符串查找
size_t pos = s3.find("World");
if (pos != std::string::npos) {
std::cout << "找到 'World' 在位置:" << pos << std::endl;
}

// 子字符串
std::string s4 = s3.substr(0, 5);
std::cout << "s4 = " << s4 << std::endl;

// 字符串替换
std::string s5 = s3;
s5.replace(6, 5, "C++");
std::cout << "s5 = " << s5 << std::endl;

// 测试向量
std::vector<int> v1;

// 添加元素
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);

// 遍历向量
std::cout << "v1 的元素:";
for (int i = 0; i < v1.size(); i++) {
std::cout << v1[i] << " ";
}
std::cout << std::endl;

// 使用范围 for 循环
std::cout << "v1 的元素(范围 for):";
for (int x : v1) {
std::cout << x << " ";
}
std::cout << std::endl;

// 访问元素
std::cout << "第一个元素:" << v1.front() << std::endl;
std::cout << "最后一个元素:" << v1.back() << std::endl;

// 删除元素
v1.pop_back();
std::cout << "删除最后一个元素后,v1 的元素:";
for (int x : v1) {
std::cout << x << " ";
}
std::cout << std::endl;

// 清空向量
v1.clear();
std::cout << "清空后,v1.size() = " << v1.size() << std::endl;

return 0;
}

第17章 输入、输出和文件

17.1 流的概念

  • :是指数据的流动,C++ 中使用流对象来处理输入和输出
  • 标准流
    • std::cin:标准输入流
    • std::cout:标准输出流
    • std::cerr:标准错误流
    • std::clog:标准日志流

17.2 文件输入/输出

  • 文件流
    • std::ifstream:文件输入流
    • std::ofstream:文件输出流
    • std::fstream:文件输入输出流
  • 文件的打开和关闭
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <fstream>

    // 写入文件
    std::ofstream outfile("example.txt");
    if (outfile.is_open()) {
    outfile << "Hello, File!" << std::endl;
    outfile.close();
    }

    // 读取文件
    std::ifstream infile("example.txt");
    if (infile.is_open()) {
    std::string line;
    while (std::getline(infile, line)) {
    std::cout << line << std::endl;
    }
    infile.close();
    }

17.3 字符串流

  • 字符串流
    • std::istringstream:字符串输入流
    • std::ostringstream:字符串输出流
    • std::stringstream:字符串输入输出流
  • 字符串流的使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <sstream>

    // 字符串输出流
    std::ostringstream oss;
    oss << "Name: " << "John" << ", Age: " << 30;
    std::string s = oss.str();
    std::cout << s << std::endl;

    // 字符串输入流
    std::string input = "10 20 30";
    std::istringstream iss(input);
    int a, b, c;
    iss >> a >> b >> c;
    std::cout << a << " " << b << " " << c << std::endl;

17.4 程序示例:文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <fstream>
#include <string>

int main() {
// 写入文件
std::ofstream outfile("students.txt");
if (outfile.is_open()) {
outfile << "Name,Age,GPA" << std::endl;
outfile << "John,20,3.5" << std::endl;
outfile << "Mary,19,3.8" << std::endl;
outfile << "Tom,21,3.2" << std::endl;
outfile.close();
std::cout << "文件写入成功!" << std::endl;
} else {
std::cerr << "无法打开文件进行写入!" << std::endl;
return 1;
}

// 读取文件
std::ifstream infile("students.txt");
if (infile.is_open()) {
std::string line;
std::cout << "\n文件内容:" << std::endl;
while (std::getline(infile, line)) {
std::cout << line << std::endl;
}
infile.close();
} else {
std::cerr << "无法打开文件进行读取!" << std::endl;
return 1;
}

return 0;
}

第18章 模板和泛型编程

18.1 模板的基本概念

  • 模板:是一种代码重用机制,允许创建通用的函数和类
  • 函数模板:创建通用函数
  • 类模板:创建通用类

18.2 函数模板

  • 函数模板的声明
    1
    2
    3
    4
    template <typename T>
    T max(T a, T b) {
    return (a > b) ? a : b;
    }
  • 函数模板的使用
    1
    2
    3
    4
    5
    int a = 10, b = 20;
    std::cout << max(a, b) << std::endl;

    double c = 1.5, d = 2.5;
    std::cout << max(c, d) << std::endl;

18.3 类模板

  • 类模板的声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename T>
    class Stack {
    private:
    std::vector<T> elements;
    public:
    void push(const T &item);
    void pop();
    T top() const;
    bool empty() const;
    };
  • 类模板的实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    template <typename T>
    void Stack<T>::push(const T &item) {
    elements.push_back(item);
    }

    template <typename T>
    void Stack<T>::pop() {
    if (!empty()) {
    elements.pop_back();
    }
    }

    template <typename T>
    T Stack<T>::top() const {
    if (!empty()) {
    return elements.back();
    }
    throw std::out_of_range("Stack is empty");
    }

    template <typename T>
    bool Stack<T>::empty() const {
    return elements.empty();
    }
  • 类模板的使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    std::cout << intStack.top() << std::endl;
    intStack.pop();
    std::cout << intStack.top() << std::endl;

    Stack<std::string> stringStack;
    stringStack.push("Hello");
    stringStack.push("World");
    std::cout << stringStack.top() << std::endl;
    stringStack.pop();
    std::cout << stringStack.top() << std::endl;

18.4 模板特化

  • 模板特化:为特定类型提供模板的特殊实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 主模板
    template <typename T>
    class MyClass {
    public:
    void print() {
    std::cout << "General template" << std::endl;
    }
    };

    // 特化版本
    template <>
    class MyClass<int> {
    public:
    void print() {
    std::cout << "Specialized for int" << std::endl;
    }
    };

18.5 程序示例:模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <iostream>
#include <vector>
#include <stdexcept>

// 函数模板
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}

// 类模板
template <typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T &item);
void pop();
T top() const;
bool empty() const;
size_t size() const;
};

// 类模板成员函数实现
template <typename T>
void Stack<T>::push(const T &item) {
elements.push_back(item);
}

template <typename T>
void Stack<T>::pop() {
if (empty()) {
throw std::out_of_range("Stack is empty");
}
elements.pop_back();
}

template <typename T>
T Stack<T>::top() const {
if (empty()) {
throw std::out_of_range("Stack is empty");
}
return elements.back();
}

template <typename T>
bool Stack<T>::empty() const {
return elements.empty();
}

template <typename T>
size_t Stack<T>::size() const {
return elements.size();
}

// 模板特化
template <>
class Stack<bool> {
private:
std::vector<char> elements; // 使用 char 节省空间
public:
void push(bool item) {
elements.push_back(item ? 1 : 0);
}
void pop() {
if (empty()) {
throw std::out_of_range("Stack is empty");
}
elements.pop_back();
}
bool top() const {
if (empty()) {
throw std::out_of_range("Stack is empty");
}
return elements.back() != 0;
}
bool empty() const {
return elements.empty();
}
size_t size() const {
return elements.size();
}
};

int main() {
// 测试函数模板
std::cout << "max(10, 20) = " << max(10, 20) << std::endl;
std::cout << "max(1.5, 2.5) = " << max(1.5, 2.5) << std::endl;
std::cout << "max('a', 'b') = " << max('a', 'b') << std::endl;

// 测试类模板
std::cout << "\n测试 int Stack:" << std::endl;
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
intStack.push(30);
std::cout << "栈大小:" << intStack.size() << std::endl;
std::cout << "栈顶元素:" << intStack.top() << std::endl;
intStack.pop();
std::cout << "弹出后栈顶元素:" << intStack.top() << std::endl;

std::cout << "\n测试 string Stack:" << std::endl;
Stack<std::string> stringStack;
stringStack.push("Hello");
stringStack.push("World");
std::cout << "栈大小:" << stringStack.size() << std::endl;
std::cout << "栈顶元素:" << stringStack.top() << std::endl;
stringStack.pop();
std::cout << "弹出后栈顶元素:" << stringStack.top() << std::endl;

std::cout << "\n测试 bool Stack:" << std::endl;
Stack<bool> boolStack;
boolStack.push(true);
boolStack.push(false);
std::cout << "栈大小:" << boolStack.size() << std::endl;
std::cout << "栈顶元素:" << (boolStack.top() ? "true" : "false") << std::endl;
boolStack.pop();
std::cout << "弹出后栈顶元素:" << (boolStack.top() ? "true" : "false") << std::endl;

return 0;
}

第19章 标准模板库

19.1 STL 概述

  • 标准模板库(STL):是 C++ 标准库的一部分,提供了一系列通用数据结构和算法
  • STL 的组件
    • 容器(Containers):存储数据的对象
    • 迭代器(Iterators):用于遍历容器中的元素
    • 算法(Algorithms):操作容器中元素的函数
    • 函数对象(Functors):行为类似函数的对象
    • 适配器(Adapters):修改其他组件接口的组件
    • 分配器(Allocators):负责内存分配和释放

19.2 容器

  • 序列容器
    • std::vector:动态数组
    • std::list:双向链表
    • std::deque:双端队列
    • std::array:固定大小数组
    • std::forward_list:单向链表
  • 关联容器
    • std::set:有序集合
    • std::map:有序键值对
    • std::multiset:有序多重集合
    • std::multimap:有序多重键值对
  • 无序容器(C++11):
    • std::unordered_set:无序集合
    • std::unordered_map:无序键值对
    • std::unordered_multiset:无序多重集合
    • std::unordered_multimap:无序多重键值对
  • 容器适配器
    • std::stack:栈
    • std::queue:队列
    • std::priority_queue:优先队列

19.3 迭代器

  • 迭代器:是一种抽象的指针,用于遍历容器中的元素
  • 迭代器的类型
    • 输入迭代器:只读,只能向前移动
    • 输出迭代器:只写,只能向前移动
    • 前向迭代器:可读可写,只能向前移动
    • 双向迭代器:可读可写,可以向前和向后移动
    • 随机访问迭代器:可读可写,可以随机访问
  • 迭代器的使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    std::vector<int> v = {1, 2, 3, 4, 5};

    // 使用迭代器遍历
    for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用范围 for 循环(C++11)
    for (int x : v) {
    std::cout << x << " ";
    }
    std::cout << std::endl;

19.4 算法

  • STL 算法:是一系列操作容器中元素的函数,定义在 <algorithm> 头文件中
  • 常用算法
    • std::sort:排序
    • std::find:查找
    • std::copy:复制
    • std::for_each:对每个元素执行操作
    • std::count:计数
    • std::accumulate:累加
    • std::transform:转换
    • std::remove:移除

19.5 程序示例:STL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <algorithm>
#include <numeric>

int main() {
// 测试 vector
std::vector<int> v = {5, 2, 8, 1, 9};
std::cout << "原始 vector:";
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;

// 排序
std::sort(v.begin(), v.end());
std::cout << "排序后:";
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;

// 查找
auto it = std::find(v.begin(), v.end(), 5);
if (it != v.end()) {
std::cout << "找到 5 在位置:" << std::distance(v.begin(), it) << std::endl;
}

// 累加
int sum = std::accumulate(v.begin(), v.end(), 0);
std::cout << "总和:" << sum << std::endl;

// 测试 list
std::list<int> l = {5, 2, 8, 1, 9};
std::cout << "\n原始 list:";
for (int x : l) {
std::cout << x << " ";
}
std::cout << std::endl;

// 排序
l.sort();
std::cout << "排序后:";
for (int x : l) {
std::cout << x << " ";
}
std::cout << std::endl;

// 测试 map
std::map<std::string, int> m;
m["John"] = 30;
m["Mary"] = 25;
m["Tom"] = 35;

std::cout << "\nmap 内容:" << std::endl;
for (const auto &pair : m) {
std::cout << pair.first << ": " << pair.second << std::endl;
}

// 查找
auto map_it = m.find("Mary");
if (map_it != m.end()) {
std::cout << "找到 Mary,年龄:" << map_it->second << std::endl;
}

return 0;
}

第20章 输入/输出流迭代器

20.1 流迭代器概述

  • 流迭代器:是一种特殊的迭代器,用于处理输入/输出流
  • 流迭代器的类型
    • std::istream_iterator:输入流迭代器
    • std::ostream_iterator:输出流迭代器

20.2 输入流迭代器

  • 输入流迭代器:从输入流中读取数据
    1
    2
    3
    4
    5
    6
    #include <iterator>

    std::istream_iterator<int> in_iter(std::cin);
    std::istream_iterator<int> eof;

    std::vector<int> v(in_iter, eof);

20.3 输出流迭代器

  • 输出流迭代器:向输出流中写入数据
    1
    2
    3
    4
    5
    #include <iterator>

    std::ostream_iterator<int> out_iter(std::cout, " ");
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::copy(v.begin(), v.end(), out_iter);

20.4 程序示例:流迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
// 测试输入流迭代器
std::cout << "请输入一些整数,按 Ctrl+D(或 Ctrl+Z)结束:" << std::endl;
std::istream_iterator<int> in_iter(std::cin);
std::istream_iterator<int> eof;

std::vector<int> v(in_iter, eof);

std::cout << "\n输入的整数:";
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;

// 排序
std::sort(v.begin(), v.end());

std::cout << "排序后:";
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;

// 逆序
std::reverse(v.begin(), v.end());

std::cout << "逆序后:";
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;

return 0;
}

第21章 智能指针

21.1 智能指针概述

  • 智能指针:是一种管理动态内存的工具,自动处理内存的分配和释放,避免内存泄漏
  • C++11 智能指针
    • std::unique_ptr:独占所有权的智能指针
    • std::shared_ptr:共享所有权的智能指针
    • std::weak_ptr:不增加引用计数的智能指针,用于解决循环引用问题

21.2 unique_ptr

  • unique_ptr:独占所有权,不能复制,只能移动
    1
    2
    3
    4
    5
    std::unique_ptr<int> p1(new int(10));
    std::unique_ptr<int> p2 = std::move(p1); // 移动语义

    // 使用 make_unique(C++14)
    auto p3 = std::make_unique<int>(20);

21.3 shared_ptr

  • shared_ptr:共享所有权,使用引用计数管理内存
    1
    2
    3
    4
    5
    std::shared_ptr<int> p1(new int(10));
    std::shared_ptr<int> p2 = p1; // 引用计数增加

    // 使用 make_shared
    auto p3 = std::make_shared<int>(20);

21.4 weak_ptr

  • weak_ptr:不增加引用计数,用于观察 shared_ptr 管理的对象
    1
    2
    3
    4
    5
    6
    std::shared_ptr<int> p1 = std::make_shared<int>(10);
    std::weak_ptr<int> wp = p1; // 不增加引用计数

    if (auto p2 = wp.lock()) { // 检查对象是否存在
    std::cout << *p2 << std::endl;
    }

21.5 程序示例:智能指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <memory>

class Resource {
public:
Resource(int id) : id(id) {
std::cout << "Resource " << id << " 创建" << std::endl;
}
~Resource() {
std::cout << "Resource " << id << " 销毁" << std::endl;
}
void use() {
std::cout << "使用 Resource " << id << std::endl;
}
private:
int id;
};

int main() {
std::cout << "=== 测试 unique_ptr ===" << std::endl;
{
std::unique_ptr<Resource> p1(new Resource(1));
p1->use();

// 移动语义
std::unique_ptr<Resource> p2 = std::move(p1);
p2->use();

// p1 现在为空
if (!p1) {
std::cout << "p1 为空" << std::endl;
}
}

std::cout << "\n=== 测试 shared_ptr ===" << std::endl;
{
std::shared_ptr<Resource> p1(new Resource(2));
std::cout << "引用计数:" << p1.use_count() << std::endl;

{
std::shared_ptr<Resource> p2 = p1;
std::cout << "引用计数:" << p1.use_count() << std::endl;
p2->use();
}

std::cout << "引用计数:" << p1.use_count() << std::endl;
p1->use();
}

std::cout << "\n=== 测试 weak_ptr ===" << std::endl;
{
std::shared_ptr<Resource> p1 = std::make_shared<Resource>(3);
std::weak_ptr<Resource> wp = p1;

std::cout << "引用计数:" << p1.use_count() << std::endl;

// 检查对象是否存在
if (auto p2 = wp.lock()) {
std::cout << "对象存在" << std::endl;
p2->use();
} else {
std::cout << "对象不存在" << std::endl;
}

// 释放 shared_ptr
p1.reset();

// 再次检查对象是否存在
if (auto p2 = wp.lock()) {
std::cout << "对象存在" << std::endl;
p2->use();
} else {
std::cout << "对象不存在" << std::endl;
}
}

return 0;
}

第22章 异常处理

22.1 异常处理概述

  • 异常:是指程序运行时发生的错误,如除以零、数组越界等
  • 异常处理:使用 trycatchthrow 关键字来处理异常

22.2 异常的抛出和捕获

  • 抛出异常:使用 throw 关键字
    1
    2
    3
    if (b == 0) {
    throw std::runtime_error("除数不能为零");
    }
  • 捕获异常:使用 trycatch
    1
    2
    3
    4
    5
    6
    try {
    // 可能抛出异常的代码
    } catch (const std::exception &e) {
    // 处理异常
    std::cerr << "错误:" << e.what() << std::endl;
    }

22.3 异常的传播

  • 异常传播:如果异常在当前函数中没有被捕获,会向上传播到调用函数

22.4 自定义异常

  • 自定义异常:继承自 std::exception 或其派生类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyException : public std::exception {
    public:
    MyException(const std::string &msg) : msg(msg) {}
    const char *what() const noexcept override {
    return msg.c_str();
    }
    private:
    std::string msg;
    };

22.5 程序示例:异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <stdexcept>
#include <string>

// 自定义异常类
class NegativeValueException : public std::exception {
public:
NegativeValueException(const std::string &msg) : msg(msg) {}
const char *what() const noexcept override {
return msg.c_str();
}
private:
std::string msg;
};

// 函数:计算平方根
double square_root(double x) {
if (x < 0) {
throw NegativeValueException("不能计算负数的平方根");
}
if (x == 0) {
return 0;
}

// 简单的平方根计算
double guess = x / 2;
for (int i = 0; i < 10; i++) {
guess = (guess + x / guess) / 2;
}
return guess;
}

int main() {
try {
double x = -4;
double result = square_root(x);
std::cout << "平方根:" << result << std::endl;
} catch (const NegativeValueException &e) {
std::cerr << "捕获到自定义异常:" << e.what() << std::endl;
} catch (const std::exception &e) {
std::cerr << "捕获到标准异常:" << e.what() << std::endl;
} catch (...) {
std::cerr << "捕获到未知异常" << std::endl;
}

try {
double x = 9;
double result = square_root(x);
std::cout << "平方根:" << result << std::endl;
} catch (const std::exception &e) {
std::cerr << "错误:" << e.what() << std::endl;
}

return 0;
}

第23章 并发编程

23.1 并发编程概述

  • 并发:是指多个任务同时执行
  • C++11 并发支持
    • 线程库:std::thread
    • 互斥量:std::mutex
    • 锁:std::lock_guardstd::unique_lock
    • 条件变量:std::condition_variable
    • 原子操作:std::atomic
    • 异步任务:std::asyncstd::future

23.2 线程

  • 线程:是程序执行的最小单位
  • 创建线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <thread>

    void func() {
    std::cout << "线程函数" << std::endl;
    }

    int main() {
    std::thread t(func);
    t.join(); // 等待线程结束
    return 0;
    }

23.3 互斥量和锁

  • 互斥量:用于保护共享资源,防止多个线程同时访问
  • :管理互斥量的生命周期
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <mutex>

    std::mutex mtx;
    int shared_data = 0;

    void increment() {
    std::lock_guard<std::mutex> lock(mtx);
    shared_data++;
    }

23.4 条件变量

  • 条件变量:用于线程间的通信,等待某个条件满足
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <condition_variable>

    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;

    void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; }); // 等待条件满足
    std::cout << "工作线程开始工作" << std::endl;
    }

    void notify() {
    {
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
    }
    cv.notify_one(); // 通知一个等待的线程
    }

23.5 程序示例:并发编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;
int shared_counter = 0;
const int MAX_COUNT = 100000;

// 线程函数:增加计数器
void increment() {
for (int i = 0; i < MAX_COUNT; i++) {
std::lock_guard<std::mutex> lock(mtx);
shared_counter++;
}
}

// 线程函数:等待通知
void wait_for_notification() {
std::cout << "等待通知..." << std::endl;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
std::cout << "收到通知,开始工作!" << std::endl;
}

int main() {
std::cout << "=== 测试多线程计数 ===" << std::endl;

// 创建两个线程
std::thread t1(increment);
std::thread t2(increment);

// 等待线程结束
t1.join();
t2.join();

std::cout << "最终计数:" << shared_counter << std::endl;
std::cout << "预期计数:" << MAX_COUNT * 2 << std::endl;

std::cout << "\n=== 测试条件变量 ===" << std::endl;

// 创建等待线程
std::thread t3(wait_for_notification);

// 主线程睡眠一段时间
std::this_thread::sleep_for(std::chrono::seconds(2));

// 发送通知
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one();

// 等待线程结束
t3.join();

return 0;
}

总结

《C++ Primer Plus 第6版》是一本全面、系统的C++入门教材,涵盖了从基础概念到高级特性的所有内容。通过学习本书,读者可以掌握C++的核心概念和编程技巧,为进一步学习其他编程语言和技术打下坚实的基础。

本学习笔记总结了《C++ Primer Plus 第6版》的主要内容,包括:

  1. C++基础:数据类型、变量、常量、运算符、表达式
  2. 控制结构:条件语句、循环语句、跳转语句
  3. 函数:函数的声明、定义、调用、重载、模板
  4. 复合类型:数组、字符串、结构体、联合体、枚举
  5. 面向对象编程:类、对象、封装、继承、多态
  6. 运算符重载:为类定义运算符的行为
  7. 动态内存管理:new、delete、智能指针
  8. 模板和泛型编程:函数模板、类模板、模板特化
  9. 标准库:STL容器、迭代器、算法
  10. 输入/输出:流、文件操作、字符串流
  11. 异常处理:try、catch、throw
  12. 并发编程:线程、互斥量、条件变量

学习C++需要不断练习,通过编写实际程序来巩固所学知识。建议读者在学习过程中多动手编程,尝试解决实际问题,这样才能真正掌握C++的精髓。

参考资料

  • 《C++ Primer Plus 第6版》- Stephen Prata
  • C++标准文档(C++11/C++14/C++17)
  • GCC编译器文档
  • 各种C++在线教程和资源

学习建议

  1. 按章节顺序学习,不要跳过基础内容
  2. 每学完一章,编写相应的练习题
  3. 尝试解决实际问题,如小型工具、游戏等
  4. 阅读优秀的C++代码,学习编程风格和技巧
  5. 参加编程社区,与其他学习者交流经验

注意事项

  1. 注意内存管理,避免内存泄漏
  2. 注意异常处理,提高程序的健壮性
  3. 注意代码风格,保持代码的可读性和可维护性
  4. 注意性能优化,编写高效的代码
  5. 注意安全性,避免常见的安全漏洞

希望本学习笔记对您的C++学习有所帮助!

// 删除元素
v1.pop_back();
std::cout << "删除最后一个元素后,v1 的元素:";
for (int x : v1) {
    std::cout << x << " ";
}
std::cout << std::endl;

// 清空向量
v1.clear();
std::cout << "清空后,v1.size() = " << v1.size() << std::endl;

return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

## 第17章 输入、输出和文件

### 17.1 流的概念

- **流**:是指数据的流动,C++ 中使用流对象来处理输入和输出
- **标准流**:
- `std::cin`:标准输入流
- `std::cout`:标准输出流
- `std::cerr`:标准错误流
- `std::clog`:标准日志流

### 17.2 文件输入/输出

- **文件流**:
- `std::ifstream`:文件输入流
- `std::ofstream`:文件输出流
- `std::fstream`:文件输入输出流
- **文件的打开和关闭**:
```cpp
#include <fstream>

// 写入文件
std::ofstream outfile("example.txt");
if (outfile.is_open()) {
outfile << "Hello, File!" << std::endl;
outfile.close();
}

// 读取文件
std::ifstream infile("example.txt");
if (infile.is_open()) {
std::string line;
while (std::getline(infile, line)) {
std::cout << line << std::endl;
}
infile.close();
}

17.3 字符串流

  • 字符串流
    • std::istringstream:字符串输入流
    • std::ostringstream:字符串输出流
    • std::stringstream:字符串输入输出流
  • 字符串流的使用
    #include <sstream>
    
    // 字符串输出流
    std::ostringstream oss;
    oss << "Name: " << "John" << ", Age: " << 30;
    std::string s = oss.str();
    std::cout << s << std::endl;
    
    // 字符串输入流
    std::string input = "10 20 30";
    std::istringstream iss(input);
    int a, b, c;
    iss >> a >> b >> c;
    std::cout << a << " " << b << " " << c << std::endl;