C++ Primer Plus 第6版
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编译器等
- GCC (GNU Compiler Collection):开源编译器,支持多种平台和语言
开发工具:
- 编辑器: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 |
|
代码解析:
#include <iostream>:包含输入输出流头文件,引入std::cout等对象的声明int main():主函数,程序的入口点,int表示返回类型为整数std::cout << "Hello, World!" << std::endl:使用输出流对象cout输出字符串到控制台,<<是流插入运算符,std::endl表示换行并刷新缓冲区return 0:返回值0,表示程序正常结束,非零值表示错误或异常情况std:::命名空间前缀,避免命名冲突,指明使用的是标准库中的对象
编译与执行过程:
- 预处理:处理#include指令,将iostream的内容插入到源文件中
- 编译:将预处理后的源代码编译为目标代码(.o文件)
- 链接:将目标代码与标准库链接,生成可执行文件
- 执行:操作系统加载可执行文件到内存,开始执行main函数
内存布局:
- 代码段:存储可执行指令,包括main函数和cout的实现
- 数据段:存储全局变量和静态变量
- 栈:存储局部变量和函数调用信息,包括main函数的栈帧
- 堆:动态内存分配区域,本程序未使用
执行流程:
- 操作系统调用main函数
- main函数创建std::cout对象(或使用全局对象)
- std::cout输出字符串”Hello, World!”到标准输出
- std::cout输出std::endl,刷新缓冲区并换行
- main函数执行return 0,返回操作系统
- 操作系统接收返回值,结束程序执行
现代C++风格的改进:
1 |
|
性能考虑:
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'; } - 控制语句:
if、for、while、do-while、switch、break、continue、goto、return - 声明语句:用于声明变量或函数,如
int x; - 空语句:只有一个分号的语句,如
;
- 表达式语句:由表达式加分号组成,如
注释:
- 块注释:
/* 注释内容 */,可跨行 - 行注释:
// 注释内容,仅单行 - 文档注释:用于生成文档的特殊注释格式,如
/** 文档注释 */
- 块注释:
2.2 C++的基本语法
标识符:变量、函数、常量、类型等的名称
- 命名规则:只能由字母、数字和下划线组成,不能以数字开头,区分大小写
- 命名约定:
- 变量和函数:小写字母加下划线(snake_case),如
int user_id; - 常量:全部大写加下划线,如
const int MAX_BUFFER_SIZE = 1024; - 类型名:首字母大写(PascalCase),如
class Person {}; - 命名空间:全部小写或首字母大写,如
namespace my_namespace {}
- 变量和函数:小写字母加下划线(snake_case),如
- 保留标识符:以下划线开头的标识符通常为系统保留,应避免使用
关键字:C++保留的特殊单词,具有特定的语法含义
- 存储类别关键字:
auto、register、static、extern、typedef、mutable - 类型关键字:
void、char、short、int、long、float、double、bool、wchar_t、char16_t、char32_t、auto、decltype、typename - 控制流关键字:
if、else、switch、case、default、for、while、do、break、continue、goto、return - 面向对象关键字:
class、struct、union、enum、public、private、protected、virtual、override、final、explicit、friend、this、namespace、using - 模板关键字:
template、typename、class、constexpr、concept - 异常处理关键字:
try、catch、throw、noexcept - 其他关键字:
const、constexpr、volatile、restrict、alignas、alignof、sizeof、typeid、new、delete
- 存储类别关键字:
运算符:
- 算术运算符:
+、-、*、/、%、++、-- - 关系运算符:
==、!=、<、>、<=、>= - 逻辑运算符:
&&、||、! - 位运算符:
&、|、^、~、<<、>> - 赋值运算符:
=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>= - 条件运算符:
?: - 逗号运算符:
, - 指针运算符:
*、& - 下标运算符:
[] - 成员运算符:
.、-> - 括号运算符:
() - 大小运算符:
sizeof、alignof - 类型转换运算符:
static_cast、dynamic_cast、const_cast、reinterpret_cast
- 算术运算符:
分隔符:用于分隔语法元素
{}:用于定义块和复合语句():用于函数调用和表达式分组[]:用于数组下标,:用于分隔参数和表达式;:用于结束语句::用于标签、case语句、基类初始化列表:::作用域解析运算符.*、->*:指向成员的指针运算符
2.3 数据类型
基本数据类型:
- 整数类型:
char:通常为 1 字节,可表示字符或小整数signed char:有符号字符,范围通常为 -128 到 127unsigned char:无符号字符,范围通常为 0 到 255short(或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:布尔类型,只能存储true或false
- 字符类型:
char:单字节字符wchar_t:宽字符,用于存储 Unicode 字符char16_t:16 位 Unicode 字符(C++11)char32_t:32 位 Unicode 字符(C++11)
- 整数类型:
复合数据类型:
- 数组:相同类型元素的集合,如
int arr[10]; - 字符串:
- C风格字符串:以空字符
'\0'结尾的字符数组 - C++字符串:使用
std::string类
- C风格字符串:以空字符
- 结构体:不同类型成员的集合,如
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 };中的RED、GREEN、BLUE - 宏常量:用
#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::setw、std::setprecision、std::fixed、std::scientific - 状态控制:
std::boolalpha、std::noboolalpha、std::hex、std::dec、std::oct - 其他:
std::endl(换行并刷新)、std::flush(刷新缓冲区)、std::ends(添加空字符)
- 格式控制:
输入输出的性能考虑:
- 缓冲策略:使用缓冲区减少I/O操作次数
- 避免频繁刷新:减少使用
std::endl,改用'\n' - 批量操作:尽量一次性读取或写入大量数据
2.6 程序示例:计算圆的面积
1 |
|
代码分析:
- 包含了必要的头文件:
<iostream>用于输入输出,<cmath>用于数学函数,<iomanip>用于输出格式控制 - 使用了
M_PI宏获取更精确的圆周率值 - 添加了函数注释,遵循文档注释规范
- 增加了输入验证,确保输入有效
- 使用
std::cerr输出错误信息,区分标准输出和标准错误 - 函数化设计,提高代码复用性和可维护性
- 使用
std::fixed和std::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,647short(或short int):通常为 2 字节,范围通常为 -32,768 到 32,767long(或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,295unsigned short(或unsigned short int):通常为 2 字节,范围为 0 到 65,535unsigned long(或unsigned long int):至少为 4 字节unsigned long long(或unsigned long long int):至少为 8 字节,范围为 0 到 18,446,744,073,709,551,615
特殊整型:
size_t:无符号整型,用于表示大小,通常为unsigned long或unsigned long longptrdiff_t:有符号整型,用于表示指针差值intptr_t、uintptr_t:用于存储指针值的整型(C++11)
整型的取值范围:
- 有符号整型:
-2^(n-1) ~ 2^(n-1)-1,其中 n 为位数 - 无符号整型:
0 ~ 2^n-1,其中 n 为位数
- 有符号整型:
确定整型范围的方法:
- 使用
<climits>头文件中的宏,如INT_MIN、INT_MAX、UINT_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_t、char16_t或char32_t处理 Unicode 文本 - 字节操作:使用
unsigned char处理原始字节数据,避免符号扩展问题 - 位操作:
unsigned char也适合进行位操作
- 文本处理:使用
3.4 布尔类型
bool:布尔类型,只能存储
true(1)或false(0)布尔值的转换:
- 其他类型到 bool 的转换:
- 零值(0、0.0、空指针、空字符串等)转换为
false - 非零值转换为
true
- 零值(0、0.0、空指针、空字符串等)转换为
- bool 到其他类型的转换:
true转换为 1(整型)或 1.0(浮点型)false转换为 0(整型)或 0.0(浮点型)
- 其他类型到 bool 的转换:
布尔型的底层实现:
- 通常占用 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
- IEEE 754 标准:
浮点型的特殊值:
- 正无穷:
+infinity,由正数除以零或溢出产生 - 负无穷:
-infinity,由负数除以零或溢出产生 - NaN(Not a Number):非数值,由无效操作产生,如 0/0、无穷减无穷等
- 正零和负零:表示零的两种形式
- 正无穷:
浮点型的比较:
- 精度问题:由于浮点表示的有限精度,应避免直接使用
==和!=比较浮点数 - 推荐方法:比较两个浮点数的差值是否小于一个很小的阈值(epsilon)
- 特殊值比较:NaN 与任何值(包括自身)比较都返回 false
- 精度问题:由于浮点表示的有限精度,应避免直接使用
3.6 类型转换
隐式转换:编译器自动进行的转换
- 算术转换:小类型转换为大类型(如
int到double) - 赋值转换:赋值时,右侧转换为左侧类型
- 初始化转换:初始值转换为变量类型
- 函数调用转换:实参转换为形参类型
- 返回值转换:函数返回值转换为函数声明的返回类型
- 布尔转换:任何类型转换为
bool - 指针转换:如
nullptr转换为任何指针类型
- 算术转换:小类型转换为大类型(如
显式转换(强制类型转换):
- C 风格转换:
(type)expression,功能强大但不安全 - C++ 风格转换:
- static_cast:用于相关类型之间的转换,如
int到double、基类指针到派生类指针(无运行时检查) - dynamic_cast:用于多态类型之间的转换,如派生类指针到基类指针(有运行时检查)
- const_cast:用于移除
const限定符,如const int *到int * - reinterpret_cast:用于不相关类型之间的转换,如
int到void *、char *到int *
- static_cast:用于相关类型之间的转换,如
- C 风格转换:
类型转换的底层原理:
- 位模式转换:如
int到float的转换会改变位模式 - 指针转换:通常只是改变指针的类型,不改变其值
- 引用转换:与指针转换类似,只是语法不同
- 位模式转换:如
类型转换的风险:
- 精度丢失:如
double到int的转换 - 溢出:如大整数到小整数的转换
- 未定义行为:如无效的指针转换
- 内存访问错误:如错误的指针类型转换导致的内存访问
- 精度丢失:如
3.7 sizeof 运算符
sizeof:返回操作数的大小(以字节为单位)
1
2std::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
2std::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
2int 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
2char name[20] = "John"; // 自动添加空字符
char name[] = "John"; // 编译器自动计算大小为5(包含空字符)C++字符串:使用
std::string类(位于<string>头文件中)1
2
3
4
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:
- 优点:安全,提供丰富的操作,自动管理内存
- 缺点:空间开销较大,某些操作可能较慢
- C风格字符串:
4.3 结构体
结构体:是一种复合数据类型,可以包含不同类型的成员
结构体的声明:
1
2
3
4
5struct 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 pack或alignas关键字控制对齐方式
结构体的操作:
- 成员访问:使用
.运算符(对于对象)或->运算符(对于指针) - 复制:可以直接赋值
Person p5 = p1; - 比较:默认情况下,结构体不支持比较运算符,需要手动定义
- 成员访问:使用
结构体的高级特性:
- 成员函数:结构体可以包含成员函数
- 访问控制:可以使用
public、private、protected控制成员访问 - 继承:结构体可以继承自其他结构体或类(默认是
public继承) - 模板结构体:可以定义模板结构体
4.4 共用体
共用体:是一种复合数据类型,所有成员共享同一块内存
共用体的声明:
1
2
3
4
5union Data {
int i;
float f;
char c;
};共用体的特点:
- 内存共享:所有成员共享同一块内存,共用体的大小至少是最大成员的大小
- 活跃成员:在任何时刻,只有一个成员是活跃的
- 类型双关:可以通过不同类型的成员访问同一块内存,实现类型双关
- 未定义行为:读取非活跃成员的值是未定义行为(C++11之前)
- 类型安全:C++11引入了受限联合,可以包含具有非平凡构造函数的类型
共用体的使用场景:
- 节省内存:当不同类型的数据不会同时使用时
- 类型双关:在不同类型之间转换数据
- 解析二进制数据:如网络协议、文件格式等
- 变体类型:实现简单的变体类型(现代C++中推荐使用
std::variant)
共用体的内存布局:
- 起始地址:所有成员的起始地址相同
- 大小:至少是最大成员的大小,可能更大(由于对齐要求)
- 对齐:共用体的对齐要求是其成员中最大的对齐要求
4.5 枚举
枚举:是一种整数类型,其值为一组命名的常量
传统枚举的声明:
1
2
3
4
5
6
7
8
9enum 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
7enum 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推荐,之前使用NULL或0 - 野指针:未初始化的指针,指向未知内存区域
- 指向变量:
指针的操作:
- 解引用:
*p// 获取指针指向的值 - 取地址:
&x// 获取变量的地址 - 指针算术:
p++、p--、p + n、p - n - 指针比较:
p1 == p2、p1 < p2等
- 解引用:
指针的类型:
- 普通指针:
int *p - 常量指针:
int *const p// 指针本身不可修改 - 指向常量的指针:
const int *p或int const *p// 指针指向的值不可修改 - 指向常量的常量指针:
const int *const p// 指针本身和指向的值都不可修改 - void指针:
void *p// 可以指向任何类型的对象,但不能直接解引用 - ** nullptr**:C++11引入的空指针字面量,类型为
std::nullptr_t
- 普通指针:
引用:是变量的别名,必须在初始化时绑定到一个变量
引用的声明:
1
2int x = 10;
int &ref = x; // 声明一个引用,绑定到x引用的特点:
- 必须初始化:引用必须在声明时初始化
- 不能重新绑定:一旦绑定到一个变量,就不能再绑定到其他变量
- 不能为空:引用不能绑定到空值
- 不能建立引用的引用:不能声明引用的引用
- 作为函数参数:可以避免值拷贝,修改实参
- 作为函数返回值:可以返回左值
引用的类型:
- 左值引用:
int &ref = x;// 绑定到左值 - 右值引用:
int &&ref = 10;// 绑定到右值(C++11) - 常量引用:
const int &ref = x;// 可以绑定到左值或右值
- 左值引用:
指针与引用的区别:
- 空值:指针可以为空,引用不能为空
- 重新绑定:指针可以重新指向其他对象,引用不能
- 内存开销:指针占用内存空间,引用不占用额外内存空间
- 解引用:指针需要显式解引用,引用不需要
- 多级间接:可以有指针的指针,不能有引用的引用
- 类型转换:指针可以进行各种类型转换,引用的类型转换受到更多限制
4.7 程序示例:结构体和指针
1 |
|
代码分析:
- 结构体定义:定义了包含姓名、年龄和身高的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
3while (条件) {
// 循环体
} - 执行流程:
- 检查条件是否为真
- 如果为真,执行循环体
- 重复步骤 1 和 2
- 如果为假,退出循环
5.3 for 循环
- 基本语法:
1
2
3for (初始化; 条件; 更新) {
// 循环体
} - 执行流程:
- 执行初始化
- 检查条件是否为真
- 如果为真,执行循环体
- 执行更新
- 重复步骤 2 到 4
- 如果为假,退出循环
5.4 do-while 循环
- 基本语法:
1
2
3do {
// 循环体
} while (条件); - 执行流程:
- 执行循环体
- 检查条件是否为真
- 如果为真,重复步骤 1 和 2
- 如果为假,退出循环
- 特点:循环体至少执行一次
5.5 循环的嵌套
- 嵌套循环:一个循环包含另一个循环
1
2
3
4
5
6for (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
6for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 当 i=5 时跳出循环
}
std::cout << i << " ";
} - continue:跳过当前循环的剩余部分,进入下一次循环
1
2
3
4
5
6for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
std::cout << i << " ";
} - goto:跳转到指定标签(尽量避免使用)
1
2
3
4
5
6
7int i = 1;
loop:
std::cout << i << " ";
i++;
if (i <= 5) {
goto loop;
}
5.7 关系表达式
- 关系运算符:
==、!=、<、>、<=、>= - 逻辑运算符:
&&(与)、||(或)、!(非) - 条件运算符:
?:1
2
3int a = 10, b = 20;
int max = (a > b) ? a : b;
std::cout << "最大值:" << max << std::endl;
5.8 程序示例:猜数字游戏
1 |
|
第6章 分支语句和逻辑运算符
6.1 if 语句
- 基本语法:
1
2
3if (条件) {
// 条件为真时执行
} - if-else 语句:
1
2
3
4
5if (条件) {
// 条件为真时执行
} else {
// 条件为假时执行
} - 嵌套 if 语句:
1
2
3
4
5
6
7if (条件1) {
// 条件1为真时执行
} else if (条件2) {
// 条件2为真时执行
} else {
// 所有条件都为假时执行
}
6.2 switch 语句
- 基本语法:
1
2
3
4
5
6
7
8
9
10
11
12switch (表达式) {
case 常量1:
// 代码
break;
case 常量2:
// 代码
break;
// 更多 case
default:
// 代码
break;
} - 注意事项:
- 表达式的值必须是整数类型或枚举类型
- 每个 case 后必须有 break,否则会继续执行下一个 case
- default 是可选的
6.3 逻辑运算符
- &&(逻辑与):当且仅当两个操作数都为真时,结果为真
- ||(逻辑或):当至少一个操作数为真时,结果为真
- !(逻辑非):取反操作数的逻辑值
6.4 程序示例:成绩等级评定
1 |
|
第7章 函数——C++的编程模块
7.1 函数的概念
- 函数:是完成特定任务的代码块
- 函数的优点:
- 模块化:将复杂任务分解为小任务
- 代码重用:避免重复代码
- 可维护性:便于修改和调试
- 可读性:使代码结构更清晰
7.2 函数的声明和定义
- 函数声明:告诉编译器函数的存在和签名
1
int add(int, int); // 函数声明
- 函数定义:实现函数的具体功能
1
2
3int 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
7int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
7.6 函数的默认参数
- 默认参数:是指在函数声明时为参数指定默认值
1
2
3void print_info(std::string name, int age = 18) {
std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
}
7.7 函数的作用域
- 局部变量:在函数内部定义的变量,只在函数内部可见
- 全局变量:在函数外部定义的变量,在整个程序中可见
7.8 递归函数
- 递归:函数调用自身的过程
- 递归的条件:
- 终止条件:避免无限递归
- 递归步骤:将问题分解为更小的子问题
- 示例:计算阶乘
1
2
3
4
5
6int factorial(int n) {
if (n <= 1) {
return 1; // 终止条件
}
return n * factorial(n - 1); // 递归调用
}
7.9 程序示例:函数重载
1 |
|
第8章 函数探幽
8.1 内联函数
- 内联函数:是指在编译时将函数调用替换为函数体的函数,减少函数调用的开销
- 声明方式:使用
inline关键字1
2
3inline int max(int a, int b) {
return (a > b) ? a : b;
}
8.2 引用变量
- 引用:是变量的别名,必须在初始化时绑定到一个变量
- 引用的声明:
1
2int x = 10;
int &ref = x; - 引用作为函数参数:
1
2
3
4
5void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
} - 常量引用:是指向常量的引用,不能通过引用修改所指对象的值
1
const int &ref = x;
8.3 函数模板
- 函数模板:是一种通用函数,可以处理不同类型的参数
- 模板的声明:
1
2
3
4template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
} - 模板的使用:
1
2
3
4
5int 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 |
|
第9章 内存模型和名称空间
9.1 内存模型
- 内存的分配方式:
- 静态存储区:存储全局变量、静态变量
- 栈:存储局部变量、函数参数
- 堆:动态分配的内存
9.2 存储类别
- auto:自动存储类别,默认存储类别
- static:静态存储类别
- extern:外部存储类别
- register:寄存器存储类别
9.3 名称空间
- 名称空间:是一种将全局作用域划分为多个子作用域的机制,避免命名冲突
- 名称空间的声明:
1
2
3
4
5namespace 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 |
|
第10章 对象和类
10.1 面向对象编程的基本概念
- 面向对象编程(OOP):是一种编程范式,以对象为中心,通过封装、继承、多态等特性来组织代码
- 对象:是类的实例,具有状态(属性)和行为(方法)
- 类:是对象的蓝图,定义了对象的属性和方法
10.2 类的定义
- 类的声明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class 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
4Person::Person(std::string n, int a) {
name = n;
age = a;
} - 析构函数:是一种特殊的成员函数,在对象销毁时自动调用,用于清理资源
1
2
3Person::~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
21void 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
2Person p1("John", 30); // 栈上创建对象
Person *p2 = new Person("Mary", 25); // 堆上创建对象 - 对象的使用:
1
2
3
4
5p1.print_info();
p2->print_info();
// 释放堆上的对象
delete p2;
10.7 程序示例:简单的类
1 |
|
第11章 使用类
11.1 运算符重载
- 运算符重载:是指为类定义运算符的行为,使运算符能够处理类的对象
- 运算符重载的声明:
1
2
3
4
5
6
7
8
9
10
11class 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
7class Vector {
private:
double x, y;
public:
// 声明友元函数
friend Vector operator*(double scalar, const Vector &v);
};
11.3 类的自动转换和强制转换
- 类型转换:
- 构造函数转换:使用单参数构造函数进行转换
- 类型转换运算符:定义从类类型到其他类型的转换
1
2
3
4
5
6
7class MyInt {
private:
int value;
public:
MyInt(int v = 0) : value(v) {}
operator int() const { return value; }
};
11.4 程序示例:运算符重载
1 |
|
第12章 类和动态内存分配
12.1 动态内存分配
- 动态内存分配:是指在运行时分配内存,使用
new和delete运算符 new运算符:分配内存并返回指向该内存的指针1
2int *p = new int(10); // 分配单个整数
int *arr = new int[5]; // 分配整数数组delete运算符:释放动态分配的内存1
2delete p; // 释放单个整数
delete[] arr; // 释放整数数组
12.2 类中的动态内存管理
- 类中的动态内存:当类需要管理动态分配的内存时,需要正确实现构造函数、析构函数、复制构造函数和赋值运算符
- 复制构造函数:用于创建一个新对象,作为现有对象的副本
1
2
3
4
5
6
7
8
9
10class 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 |
|
第13章 类继承
13.1 继承的基本概念
- 继承:是指一个类(派生类)从另一个类(基类)继承属性和方法
- 继承的目的:代码重用、扩展功能
- 继承的类型:
- 公有继承(public):基类的公有成员在派生类中仍然是公有的,保护成员仍然是保护的
- 保护继承(protected):基类的公有和保护成员在派生类中都是保护的
- 私有继承(private):基类的公有和保护成员在派生类中都是私有的
13.2 派生类的定义
- 派生类的声明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class 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
4class Person {
public:
virtual void print_info() const;
}; - 纯虚函数:是指在基类中声明但没有实现的虚函数,派生类必须实现
1
2
3
4class Shape {
public:
virtual double area() const = 0; // 纯虚函数
};
13.5 程序示例:继承和多态
1 |
|
第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
23class 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
14class 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
17class 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
20class 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 |
|
第15章 友元、异常和其他
15.1 友元
- 友元函数:可以访问类的私有成员的非成员函数
- 友元类:可以访问另一个类的私有成员的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14class 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 异常处理
- 异常:是指程序运行时发生的错误,如除以零、数组越界等
- 异常处理:使用
try、catch、throw关键字来处理异常1
2
3
4
5
6
7
8
9
10
11try {
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
class Base { virtual void f() {} };
class Derived : public Base {};
Base *b = new Derived;
std::cout << typeid(*b).name() << std::endl; // 输出 Derived 的类型信息
15.4 程序示例:异常处理
1 |
|
第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 |
|
第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
// 写入文件
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
// 字符串输出流
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 |
|
第18章 模板和泛型编程
18.1 模板的基本概念
- 模板:是一种代码重用机制,允许创建通用的函数和类
- 函数模板:创建通用函数
- 类模板:创建通用类
18.2 函数模板
- 函数模板的声明:
1
2
3
4template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
} - 函数模板的使用:
1
2
3
4
5int 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
10template <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
24template <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
13Stack<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 |
|
第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
13std::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 |
|
第20章 输入/输出流迭代器
20.1 流迭代器概述
- 流迭代器:是一种特殊的迭代器,用于处理输入/输出流
- 流迭代器的类型:
std::istream_iterator:输入流迭代器std::ostream_iterator:输出流迭代器
20.2 输入流迭代器
- 输入流迭代器:从输入流中读取数据
1
2
3
4
5
6
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
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 |
|
第21章 智能指针
21.1 智能指针概述
- 智能指针:是一种管理动态内存的工具,自动处理内存的分配和释放,避免内存泄漏
- C++11 智能指针:
std::unique_ptr:独占所有权的智能指针std::shared_ptr:共享所有权的智能指针std::weak_ptr:不增加引用计数的智能指针,用于解决循环引用问题
21.2 unique_ptr
- unique_ptr:独占所有权,不能复制,只能移动
1
2
3
4
5std::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
5std::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
6std::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 |
|
第22章 异常处理
22.1 异常处理概述
- 异常:是指程序运行时发生的错误,如除以零、数组越界等
- 异常处理:使用
try、catch、throw关键字来处理异常
22.2 异常的抛出和捕获
- 抛出异常:使用
throw关键字1
2
3if (b == 0) {
throw std::runtime_error("除数不能为零");
} - 捕获异常:使用
try和catch块1
2
3
4
5
6try {
// 可能抛出异常的代码
} 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
9class 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 |
|
第23章 并发编程
23.1 并发编程概述
- 并发:是指多个任务同时执行
- C++11 并发支持:
- 线程库:
std::thread - 互斥量:
std::mutex - 锁:
std::lock_guard、std::unique_lock - 条件变量:
std::condition_variable - 原子操作:
std::atomic - 异步任务:
std::async、std::future
- 线程库:
23.2 线程
- 线程:是程序执行的最小单位
- 创建线程:
1
2
3
4
5
6
7
8
9
10
11
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
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
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 |
|
总结
《C++ Primer Plus 第6版》是一本全面、系统的C++入门教材,涵盖了从基础概念到高级特性的所有内容。通过学习本书,读者可以掌握C++的核心概念和编程技巧,为进一步学习其他编程语言和技术打下坚实的基础。
本学习笔记总结了《C++ Primer Plus 第6版》的主要内容,包括:
- C++基础:数据类型、变量、常量、运算符、表达式
- 控制结构:条件语句、循环语句、跳转语句
- 函数:函数的声明、定义、调用、重载、模板
- 复合类型:数组、字符串、结构体、联合体、枚举
- 面向对象编程:类、对象、封装、继承、多态
- 运算符重载:为类定义运算符的行为
- 动态内存管理:new、delete、智能指针
- 模板和泛型编程:函数模板、类模板、模板特化
- 标准库:STL容器、迭代器、算法
- 输入/输出:流、文件操作、字符串流
- 异常处理:try、catch、throw
- 并发编程:线程、互斥量、条件变量
学习C++需要不断练习,通过编写实际程序来巩固所学知识。建议读者在学习过程中多动手编程,尝试解决实际问题,这样才能真正掌握C++的精髓。
参考资料
- 《C++ Primer Plus 第6版》- Stephen Prata
- C++标准文档(C++11/C++14/C++17)
- GCC编译器文档
- 各种C++在线教程和资源
学习建议:
- 按章节顺序学习,不要跳过基础内容
- 每学完一章,编写相应的练习题
- 尝试解决实际问题,如小型工具、游戏等
- 阅读优秀的C++代码,学习编程风格和技巧
- 参加编程社区,与其他学习者交流经验
注意事项:
- 注意内存管理,避免内存泄漏
- 注意异常处理,提高程序的健壮性
- 注意代码风格,保持代码的可读性和可维护性
- 注意性能优化,编写高效的代码
- 注意安全性,避免常见的安全漏洞
希望本学习笔记对您的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 |
|
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;



