EN
/news/show.php/video/58661184.html

侯捷 C++ 课程学习笔记:Lethehong

2025-06-24 12:08:33 来源: 新华社
字号:默认 超大 | 打印 |

🌟 嗨,我是Lethehong!🌟

🌍 立志在坚不欲说,成功在久不在速🌍

🚀 欢迎关注:👍点赞⬆️留言收藏🚀

🍀欢迎使用:小智初学计算机网页AI🍀


目录

01|前言

内存管理和 std::allocator

使用迭代器简化输入处理

移动语义的重要性及其应用

初始化列表 (initializer_list) 及其特性

02|C++基础与进阶

1、C++概述

                C++的历史与发展

                C++与C的关系

                C++的语言特性

2、基本语法

                数据类型

                变量与常量

                运算符

                控制结构(条件、循环)

3、函数

                函数定义与声明

                函数参数传递(值传递、引用传递)

                函数重载

                默认参数

                内联函数

4、面向对象编程(OOP)

                类与对象

                构造函数与析构函数

                成员函数与友元函数

                继承与多态

                虚函数与纯虚函数

                抽象类与接口

5、模板

                函数模板

                类模板

                模板特化与偏特化

6、异常处理

                异常的抛出与捕获

                异常安全

                标准异常

7、STL标准模板库

                容器(顺序容器、关联容器)

                迭代器

                算法

                适配器

03|C++高级特性

1、内存管理

                内存分配与释放

                指针与引用

                动态内存分配(new/delete)

                智能指针(shared_ptr, unique_ptr)

2、对象模型

                对象内存布局

                虚函数表

                构造与析构的顺序

                拷贝构造与赋值操作

3、模板元编程

                类型推导

                SFINAE

                模板元编程实例

4、并发编程

                线程(std::thread)

                锁(互斥量、条件变量)

                原子操作

                任务并行库(TPL)

04|C++项目实践

1、设计模式

                创建型模式

                结构型模式

                行为型模式

2、代码规范与重构

                编码风格

                代码重构技巧

                设计原则(SOLID)

3、项目实践

                项目分析与设计

                代码实现

                测试与调试

                性能优化

05|附录

1、C++标准库参考

                标准库函数

                标准库类

2、常见问题解答

                编译器差异

                性能问题

                设计模式应用

3、学习资源推荐

                书籍

                在线课程

                论坛与社区

06|总结


01|前言

内存管理和 std::allocator

在探讨内存管理时,std::allocator 是一个重要的概念。它提供了一种通用的方式来进行动态内存分配和释放操作。通过使用 std::allocator,可以更灵活地控制容器内部对象的创建与销毁。

#include using namespace std;int main() {     allocator alloc;    const int n = 1000;    int* p = alloc.allocate(n);        // 构造元素    uninitialized_fill_n(p, n, 0);    // 销毁并回收内存    destroy_n(p, n);    alloc.deallocate(p, n);}

这段代码展示了如何利用 std::allocator 来手动管理一块整型数据的空间,并对其进行初始化以及最终清理工作。

使用迭代器简化输入处理

对于从标准输入流读取数值并将这些值存储至目标容器的操作,可以通过组合 istream_iterator 和算法库中的 copy() 函数来实现简洁高效的解决方案:

class MyClass { public:    MyClass(MyClass&& other) noexcept : member(std::move(other.member)) { }    MyClass& operator=(MyClass&& other) noexcept {         if (this != &other){             member = std::move(other.member);        }        return *this;    }private:    string member;};

此程序片段能够方便地接收来自用户的多个浮点数作为输入,并自动将其追加到指定的目标集合内而无需显式循环结构。

移动语义的重要性及其应用

当涉及到像 std::vector 这样的序列式容器时,确保自定义类型的移动构造函数和赋值运算符被声明为 noexcept 至关重要。这不仅有助于提高性能,还可能影响某些 STL 容器的行为逻辑,比如扩容机制会优先考虑调用 noexcept 的版本以减少异常风险。

class MyClass { public:    MyClass(MyClass&& other) noexcept : member(std::move(other.member)) { }    MyClass& operator=(MyClass&& other) noexcept {         if (this != &other){             member = std::move(other.member);        }        return *this;    }private:    string member;};

上述类实现了无抛出保证的右值引用重载方法,从而使得该类型更适合用于频繁变动大小的数据结构之中。

初始化列表 (initializer_list) 及其特性

最后,在介绍现代 C++ 特性的过程中不得不提到 initializer_list。这是一种特殊的模板参数形式,允许我们采用花括号语法传递一组同质化的初始值给构造函数或其他接受此类参数的地方4。值得注意的是,尽管看起来像是拥有自己的副本,实际上 initializer_list 并不持有任何实际的对象实例——它仅仅是指向原始数组的一个视图而已。

templatevoid print(const initializer_list& ilist){     for(auto elem : ilist)        cout << elem << " ";}// 调用方式如下所示:print({ 1, 2, 3});

思维导图

02|C++基础与进阶

1、C++概述

C++是一种通用编程语言,它支持面向对象、过程式以及泛型编程特性。C++在软件工程的多个领域都有广泛应用,包括系统软件、应用软件、设备驱动程序、嵌入式软件、高性能服务器和客户端应用程序、以及在视频游戏和金融领域的性能要求极高的软件。 

                C++的历史与发展

C++的历史可以追溯到20世纪70年代末,当时的编程语言主要是C语言。C++的前身是C with Classes,由Bjarne Stroustrup在1979年开始设计,目的是为了提供面向对象的特性,同时保持C语言的高效性和灵活性。以下是C++发展的重要里程碑:

  • 1983年,C++语言被正式命名为“C++”,这个名字意味着它是C语言的下一个版本('++'是C语言中的递增运算符)。
  • 1985年,第一个C++编译器Cfront发布,它将C++代码转换为C代码。
  • 1989年,C++的第一个国际标准被提出,即C++98。
  • 2003年,C++03标准发布,主要是对C++98的一些修订。
  • 2011年,C++11标准发布,这是一个重大的更新,引入了许多新特性,极大地提升了语言的表现力和易用性。
  • 之后,C++14、C++17、C++20等标准相继发布,每个新标准都增加了新的特性和改进。
                C++与C的关系

C++与C语言有着紧密的关系:

  • C++是C语言的超集,这意味着任何有效的C语言程序都是有效的C++程序,但反之不一定成立。
  • C++保留了C语言的基本语法和控制结构,同时也扩展了C语言,增加了面向对象编程、泛型编程等特性。
  • C++在兼容C的基础上,提供了更丰富的类型系统、更强大的错误处理机制、更高级的库支持等
                C++的语言特性

主要的五个类型下面都有讲解到,这里就不一一阐述,这里阐述的三个的其他特性。

  • 运算符重载(Operator Overloading):允许为类定义新的运算符行为。
  • 函数重载(Function Overloading):允许定义多个同名函数,但参数列表不同。
  • 构造函数和析构函数:用于对象的初始化和清理。

2、基本语法

C语言的基本语法包括数据类型、变量与常量、运算符以及控制结构。

                数据类型

数据类型定义了变量可以存储信息的种类。C语言提供了以下基本数据类型:

  • 整型(Integers)

    • int:用于存储整数。
    • short:比int小的整型。
    • long:比int大的整型。
    • long long:比long更大的整型。
  • 字符型(Characters)

    • char:用于存储单个字符。
  • 浮点型(Floating-point)

    • float:单精度浮点数。
    • double:双精度浮点数。
    • long double:比double精度更高的浮点数。
  • 布尔型(Boolean)(在C99标准中引入):

    • _Bool:用于存储真(1)或假(0)。
                变量与常量

变量(Variables):变量是存储数据的标识符。在C语言中,变量必须先声明后使用,并且可以在声明时初始化。

int age = 30; // 声明并初始化一个整型变量

 常量(Constants):常量是程序运行期间其值不变的量。常量可以是字面量,也可以通过const关键字来定义。

const float PI = 3.14159; // 定义一个浮点型常量
                运算符

C语言提供了丰富的运算符,用于执行数学计算、逻辑运算、赋值等操作。以下是一些常见的运算符:

  • 算术运算符:+(加)、-(减)、*(乘)、/(除)、%(取模)。
  • 关系运算符:==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)。
  • 逻辑运算符:&&(逻辑与)、||(逻辑或)、!(逻辑非)。
  • 赋值运算符:=(赋值)、+=-=*=/=%=等。
  • 位运算符:&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)。
                控制结构(条件、循环)

控制结构用于控制程序的执行流程。

条件控制

  • if语句:根据条件执行代码块。
if (condition) {     // 条件为真时执行的代码}
  • else ifelse:与if语句配合使用,处理多个条件。
if (condition1) {     // 条件1为真时执行的代码} else if (condition2) {     // 条件2为真时执行的代码} else {     // 以上条件都不为真时执行的代码}

 循环控制

  • for循环:用于重复执行代码块,直到指定的条件不再满足。
for (initialization; condition; increment) {     // 循环体}
  • while循环:在指定的条件为真的情况下,重复执行代码块。
while (condition) {     // 循环体}
  • do-while循环:至少执行一次循环体,然后根据条件决定是否继续执行。
do {     // 循环体} while (condition);

3、函数

函数是C语言中实现代码模块化和重用的基本单元。

                函数定义与声明
  • 函数定义:函数定义是指创建一个函数,包括函数名、返回类型、参数列表(可以为空)以及函数体。
返回类型 函数名(参数类型 参数名1, 参数类型 参数名2, ...) {     // 函数体    // 可以包含局部变量的声明和可执行语句}

例如:

int add(int a, int b) {     return a + b;}
  • 函数声明:函数声明告诉编译器函数的名称、返回类型和参数类型,但不包括函数体。函数声明通常用于在函数定义之前提供函数的信息。
返回类型 函数名(参数类型 参数名1, 参数类型 参数名2, ...);

 例如:

int add(int, int); // 函数声明
                函数参数传递(值传递、引用传递)
  • 值传递(Call by Value):在值传递中,函数内部的参数是调用参数的副本。对参数的任何修改都不会影响原始变量。
void increment(int x) {     x = x + 1; // 这里的x是局部副本,不会影响外部变量}
  • 引用传递(Call by Reference):在C语言中,引用传递通常通过指针实现。函数通过指针接收变量的地址,从而可以修改原始变量的值。
void increment(int *x) {     *x = *x + 1; // 通过指针修改外部变量的值}
                函数重载

C语言本身不支持函数重载,即不能有多个同名函数,即使它们的参数列表不同。这是因为在C语言中,函数重载需要编译器能够根据参数类型和数量来区分不同的函数,而C语言的编译器不会进行这种区分。不过,可以通过宏定义或者函数指针来实现类似的功能。

                默认参数

C语言不支持默认参数这一特性。在C++中,可以在函数声明中为参数提供默认值,但在C语言中,每个函数调用必须提供所有参数的值。

                内联函数

C语言不支持内联函数这一概念,这是C++的一个特性。内联函数是一种建议编译器在调用点展开函数体的函数,以减少函数调用的开销。在C语言中,可以通过宏定义来模拟内联函数的行为。

#define INLINE_ADD(a, b) ((a) + (b))

 在C99标准中,C语言引入了inline关键字,但它只是一个建议,编译器可以选择忽略这个建议。

inline int add(int a, int b) {     return a + b;}

总结来说,函数是C语言编程的核心组成部分,它们通过定义和声明来实现特定的功能,并通过参数传递来与调用者交互。虽然C语言在函数重载和默认参数方面有限制,但它提供了强大的功能,如通过指针实现引用传递和宏定义来模拟内联函数。

4、面向对象编程(OOP)

面向对象编程(OOP)是一种编程范式,它使用对象和类来设计软件。

                类与对象
  • 类(Class):类是创建对象的蓝图或模板。它定义了一组属性(数据成员)和行为(成员函数)。
class ClassName {  public: // 数据成员 // 成员函数 };
  • 对象(Object):对象是类的实例。它是由类定义创建的实体,具有类定义中的属性和行为。
ClassName obj;
                构造函数与析构函数
  • 构造函数(Constructor):构造函数是一种特殊的成员函数,它在创建对象时自动调用。它用于初始化对象的数据成员。
class ClassName {  public: ClassName() {  // 构造函数体 } };
  • 析构函数(Destructor):析构函数也是一种特殊的成员函数,它在对象生命周期结束时自动调用。它用于执行任何必要的清理工作。
class ClassName {  public: ~ClassName() {  // 析构函数体 } };
                成员函数与友元函数
  • 成员函数(Member Function):成员函数是类的一部分,它可以访问类的所有成员(包括私有和受保护的成员)。
class ClassName {  public: void memberFunction() {  // 成员函数体 } };
  • 友元函数(Friend Function):友元函数不是类的成员,但它可以访问类的所有私有和受保护的成员。友元函数是在类内部声明,但在类外部定义的。
class ClassName {  private: int privateData; public: friend void friendFunction(ClassName &obj); };
                继承与多态
  • 继承(Inheritance):继承允许创建一个新的类(子类)从现有的类(基类)继承属性和行为。子类是基类的特殊化。
class DerivedClass : public BaseClass {  // 新的或覆盖的成员 };
  • 多态(Polymorphism):多态允许使用一个接口来表示不同的数据类型。在C++中,多态通常通过虚函数实现。
                虚函数与纯虚函数
  • 虚函数(Virtual Function):虚函数是一个在基类中被声明为虚的成员函数,它可以在派生类中被重新定义(覆盖)。虚函数允许在运行时根据对象的实际类型来调用相应的函数。
class BaseClass {  public: virtual void virtualFunction() {  // 基类的实现 } };
  • 纯虚函数(Pure Virtual Function):纯虚函数是一个在基类中声明但没有实现的虚函数。它通过在函数声明后加上“= 0”来指定。包含纯虚函数的类是抽象类,不能直接实例化。
class BaseClass {  public: virtual void pureVirtualFunction() = 0; };
                抽象类与接口

抽象类(Abstract Class)

抽象类是一种不能被实例化的类,它至少包含一个纯虚函数。抽象类的主要目的是作为其他类的基类,为它们提供公共接口和部分实现。

  • 纯虚函数:在C++中,纯虚函数是在成员函数的声明后面加上= 0来指定的,如下所示:
class AbstractClass { public:    virtual void pureVirtualFunction() = 0; // 纯虚函数    // 其他成员函数和成员变量};
  • 不能实例化:由于包含至少一个纯虚函数,抽象类不能被直接实例化。

  • 可以有构造函数和析构函数:抽象类可以有构造函数和析构函数,这些函数会在派生类的对象创建和销毁时被调用。

  • 可以包含非虚函数和虚函数:抽象类可以包含实现了的成员函数,这些函数可以是虚函数也可以不是。

接口(Interface)

在C++中,接口通常是通过抽象类来实现的,因为它没有提供任何实现,只包含纯虚函数。接口是一种更为纯粹的抽象,它定义了一组函数签名,但不提供任何实现。

  • 纯虚函数:接口通常只包含纯虚函数。

  • 不包含成员变量:接口通常不包含任何数据成员。

  • 完全抽象:接口不提供任何函数的实现,只定义了必须由实现类实现的函数签名。

 在C++中,可以使用抽象类来模拟接口,如下所示:

class Interface { public:    virtual void function1() = 0;    virtual void function2() = 0;    // ... 更多纯虚函数};

 实现接口的类必须实现接口中所有的纯虚函数:

class ConcreteClass : public Interface { public:    void function1() override {         // 实现function1    }        void function2() override {         // 实现function2    }    // ... 实现所有纯虚函数};

 区别

  • 抽象类可以包含带有实现的成员函数,而接口通常只包含纯虚函数。
  • 抽象类可以包含成员变量,而接口通常不包含。
  • 抽象类可以提供某些成员函数的默认实现,而接口不提供任何实现。

5、模板

模板是C语言中用于编写通用代码的工具,它允许编写与数据类型无关的函数和类。模板通过模板参数来创建,这些参数在编译时会被具体的数据类型所替换。

                函数模板

函数模板提供了一种方法,使得函数可以处理不同数据类型的参数,而不需要为每种数据类型编写单独的函数。函数模板的定义如下:

template  T functionName(T param) // 函数体 // 使用T类型的参数 return something; }

 例如,一个简单的函数模板用于交换两个值:

cpp template  void swap(T& a, T& b) T temp = a; a = b; b = temp; }
                类模板

类模板允许创建与数据类型无关的类。这意味着可以定义一个类,其成员的类型在类创建时才确定。类模板的定义如下:

template  class ClassName private: T member; public: ClassName(T value) : member(value) { } T getMember() const return member; } };

例如,一个简单的类模板用于封装一个数据值: 

template  class Wrapper private: T value; public: Wrapper(T val) : value(val) { } T getValue() const return value; } };
                模板特化与偏特化

模板特化是指为特定的模板参数提供特定的实现。当模板的通用实现不适用于某些特定类型时,可以使用模板特化。

  • 模板特化:完全特化一个模板,为特定的类型提供特定的实现。
template <> class ClassName // 特化的实现 };
  • 模板偏特化:部分特化一个模板,为模板参数的特定子集提供实现。
template  class ClassName // 偏特化的实现 };

例如,偏特化一个类模板,以便它只处理指针类型:

template  class Wrapper private: T* ptr; public: Wrapper(T* p) : ptr(p) { } T* getPointer() const return ptr; } };

总结来说,模板是C语言中一个非常强大的特性,它允许程序员编写更通用、更灵活的代码。函数模板和类模板使得代码可以适用于不同的数据类型,而模板特化和偏特化则提供了对特定情况下的优化和定制。

6、异常处理

异常处理是C++语言中用于处理程序运行时错误和异常情况的一种机制。

                异常的抛出与捕获
  • 异常的抛出(Throwing Exceptions): 异常是在程序执行过程中遇到错误时抛出的对象。抛出异常使用throw关键字,后面跟着要抛出的异常对象。
throw ExceptionType(arguments);

例如,如果一个函数无法执行其任务,它可以抛出一个异常:

void doSomething() if (errorCondition) throw std::runtime_error(“An error occurred”);
  • 异常的捕获(Catching Exceptions): 异常的捕获是通过trycatch块来实现的。try块包含了可能抛出异常的代码,而catch块用于捕获并处理这些异常。
try // 尝试执行的代码 catch (ExceptionType1& e1) // 处理ExceptionType1类型的异常 catch (ExceptionType2& e2) // 处理ExceptionType2类型的异常可以有多个catch块 catch (…) // 处理所有未指定类型的异常
                异常安全

异常安全是指程序在发生异常时仍然保持一致性和完整性的能力。要实现异常安全,需要遵循以下原则:

  • 强烈保证:即使在异常抛出后,程序的状态也不会改变。
  • 基本保证:即使异常抛出,程序的状态也不会变坏,不会发生数据破坏或资源泄漏。
  • 无抛出保证:函数保证不抛出任何异常。

为了实现异常安全,可以使用如下的技术:

  • 使用智能指针(如std::unique_ptrstd::shared_ptr)来管理资源,以确保资源在异常发生时能够被正确释放。
  • 使用事务性操作,如拷贝和交换(Copy and Swap)技术。
  • 使用RAII(Resource Acquisition Is Initialization)原则来管理资源。
                标准异常

C++标准库定义了一系列标准异常类,它们都是std::exception的派生类。以下是一些常见的标准异常:

  • std::exception:所有标准异常的基类。
  • std::bad_alloc:在动态内存分配失败时抛出。
  • std::runtime_error:在运行时错误发生时抛出。
  • std::logic_error:在逻辑错误发生时抛出,如违反了程序的前提条件。
  • std::out_of_range:在访问超出有效范围的元素时抛出。
  • std::invalid_argument:在传递了无效参数时抛出。

7、STL标准模板库

STL(Standard Template Library,标准模板库)是C语言的一个重要组成部分,它提供了一系列模板化的数据结构和函数,用于处理数据集合。

                容器(顺序容器、关联容器)

容器是用于存储数据的数据结构。STL提供了多种容器,分为两大类:

  • 顺序容器(Sequential Containers):

    • vector:动态数组,可以高效地随机访问元素,但在末尾之外插入或删除元素较慢。
    • deque:双端队列,可以高效地在两端插入或删除元素。
    • list:双向链表,可以高效地在任何位置插入或删除元素,但随机访问较慢。
    • forwardlist:单向链表,与list类似,但更节省空间。
    • array:固定大小的数组,不支持动态扩展。
  • 关联容器(Associative Containers):

    • set:基于红黑树实现,存储唯一元素,元素自动排序。
    • multiset:与set类似,但允许存储重复元素。
    • map:存储键值对,键是唯一的,基于红黑树实现,自动按键排序。
    • multimap:与map类似,但允许键重复。
                迭代器

迭代器是一种检查容器内元素并遍历容器的对象。它们模拟了指针的一些功能,提供了对容器元素的访问方法。迭代器分为几种类型:

  • 输入迭代器:提供对数据的只读访问。
  • 输出迭代器:提供对数据的只写访问。
  • 前向迭代器:可以向前遍历数据。
  • 双向迭代器:可以向前和向后遍历数据。
  • 随机访问迭代器:提供了最完整的迭代器功能,包括随机访问。
                算法

算法是用于处理容器中数据的函数模板。STL提供了大量算法,包括但不限于:

  • 排序算法:如sort、stable_sort
  • 查找算法:如find、binary_search
  • 赋值算法:如copy、fill
  • 变换算法:如transform
  • 数值算法:如accumulate、partial_sum
                适配器

适配器是用于修改或适配其他组件行为的模板类。STL中的适配器包括:

  • 容器适配器:如stack、queue、priority_queue,它们是基于其他容器(通常是vector或deque)实现的,提供了特定的接口和行为。
  • 函数适配器:如bind1st、bind2nd,用于将函数对象的参数绑定到特定的值上。
  • 迭代器适配器:如reverse_iterator,用于反转容器的迭代顺序。

03|C++高级特性

1、内存管理

内存管理是编程中的一个关键方面,它涉及到如何在程序运行时有效地分配、使用和释放内存资源。

                内存分配与释放
  • 内存分配:在C语言中,内存分配通常指的是为变量或数据结构分配内存空间的过程。这可以通过静态分配(如全局变量、静态变量)或动态分配完成。
  • 内存释放:当不再需要内存时,应该将其释放回系统,以避免内存泄漏。在静态分配的情况下,内存通常在程序结束时自动释放。
                指针与引用
  • 指针(Pointers):指针是一个变量,其值是另一个变量的地址。指针可以用来直接访问和操作内存中的数据。
int var = 5; int *ptr = &var; // ptr指向var的地址
  • 引用(References):引用是另一个变量的别名,它必须在声明时初始化,并且一旦初始化就不能更改引用的对象。
int var = 5; int &ref = var; // ref是var的引用
                动态内存分配(new/delete)
  • new操作符:在C语言中,new操作符用于在堆上动态分配内存。它不仅分配内存,还可以调用构造函数来初始化对象。
int *dynamicInt = new int; // 分配一个int类型的内存 MyClass *myObject = new MyClass(); // 分配并构造一个MyClass对象
  • delete操作符:与new操作符对应,delete操作符用于释放动态分配的内存,并且可以调用析构函数来清理对象。
delete dynamicInt; // 释放int类型的内存 delete myObject; // 释放MyClass对象并调用析构函数
                智能指针(shared_ptr, unique_ptr)

智能指针是C语言中的一种特殊指针,它可以自动管理内存,从而减少内存泄漏的风险。以下是两种常见的智能指针:

  • shared_ptr:共享智能指针,允许多个shared_ptr实例共享同一块内存。当最后一个shared_ptr被销毁时,它所管理的内存会被自动释放。
#include  std::shared_ptr sharedInt = std::make_shared(10); // 创建一个shared_ptr
  • unique_ptr:独占智能指针,它保证同一时间只有一个指针拥有所管理的内存。unique_ptr不能复制,但可以移动。
#include  std::unique_ptr uniqueInt = std::make_unique(20); // 创建一个unique_ptr

2、对象模型

对象模型是C++语言中描述对象在内存中的布局和行为的一个概念。

                对象内存布局
  • 对象内存布局:在C++中,一个对象的内存布局包括其所有非静态数据成员的内存。布局遵循一定的规则,比如成员按照其声明的顺序进行布局,并且可能会有padding(填充)来满足对齐要求。
class MyClass {  public: int a; char b; double c; };

在大多数平台上,MyClass对象的内存布局可能如下:

  • int a:通常占用4字节
  • char b:占用1字节,但后面可能会有3字节的padding,以确保double c的对齐
  • double c:占用8字节
                虚函数表
  • 虚函数表(V-Table):在支持多态的C++对象模型中,每个包含虚函数的类都有一个与之关联的虚函数表。这个表是一个函数指针数组,每个指针对应一个虚函数的实现。当通过指针或引用调用虚函数时,实际调用的函数是通过虚函数表来确定的。
class Base {  public: virtual void func() { } };

Base的对象中,会有一个指向虚函数表的指针。

                构造与析构的顺序
  • 构造顺序:对象的构造是从基类到派生类依次进行的。如果类有多个基类,则按照它们在派生类声明中的顺序进行构造。
  • 析构顺序:与构造顺序相反,析构是从派生类到基类依次进行的。
class Base {  public: Base() {  std::cout << “Base constructor” << std::endl; } virtual ~Base() {  std::cout << “Base destructor” << std::endl; } };class Derived : public Base {  public: Derived() {  std::cout << “Derived constructor” << std::endl; } ~Derived() {  std::cout << “Derived destructor” << std::endl; } };Derived obj; // 构造顺序:Base -> Derived // 析构顺序:Derived -> Base
                拷贝构造与赋值操作
  • 拷贝构造函数:当一个对象需要通过已存在的同类型对象来进行初始化时,拷贝构造函数会被调用。它通常执行深度拷贝,即复制对象的所有成员。
class MyClass {  public: MyClass(const MyClass& other) {  // 拷贝other的成员到当前对象 } };
  • 赋值操作符:赋值操作符用于将一个对象的状态赋值给另一个同类型的对象。它通常返回一个指向赋值后对象的引用。
class MyClass {  public: MyClass& operator=(const MyClass& other) {  // 将other的成员赋值给当前对象 return *this; } };

在C++中,拷贝构造函数和赋值操作符通常需要一起定义,以确保对象的正确拷贝和赋值行为,特别是在涉及资源管理(如动态内存分配)时。

3、模板元编程

模板元编程(TMP)是C语言中一种利用模板来编写在编译时执行代码的技术。

                类型推导
  • 类型推导:模板类型推导是指编译器在实例化模板时自动推断模板参数类型的能力。这允许程序员编写更通用和灵活的代码。
template void print(T value) {  std::cout << value << std::endl; }

在这个例子中,当调用print(10);时,编译器会自动推导出Tint类型。

                SFINAE
  • SFINAE(Substitution Failure Is Not An Error):这是一种模板元编程技术,它利用了编译器在模板替换过程中的行为。如果模板替换导致一个无效的表达式或类型,编译器不会报错,而是会忽略这个替换。SFINAE可以用来在编译时选择函数重载或模板特化。
#include template class has_member_foo {  private: typedef char yes[1]; typedef char no[2];template static yes& test(decltype(&U::foo)); // 如果U::foo存在,这个重载被选中template static no& test(…); // 如果U::foo不存在,这个重载被选中public: static const bool value = sizeof(test(0)) == sizeof(yes); };

在这个例子中,has_member_foo类模板用来检测一个类型T是否有一个名为foo的成员。如果T::foo存在,第一个test函数模板会被选中,否则第二个会被选中。通过比较返回类型的大小,我们可以确定T::foo是否存在。

                模板元编程实例
  • 模板元编程实例:以下是一个简单的模板元编程实例,它使用递归模板来计算一个常量表达式的值。
template struct Factorial {  static const int value = N * Factorial::value; };template<> struct Factorial<0> {  static const int value = 1; };int main() {  std::cout << Factorial<5>::value << std::endl; // 输出120 return 0; }

在这个例子中,我们定义了一个递归模板Factorial来计算阶乘。基例Factorial<0>提供了递归的终止条件。当编译器实例化Factorial<5>时,它会递归地实例化Factorial<4>Factorial<3>Factorial<2>Factorial<1>, 和 Factorial<0>,最终计算出5的阶乘。

4、并发编程

并发编程是一种编程范式,它允许多个任务在同一时间段内执行,这些任务可能是同时运行,也可能是交替运行。

                线程(std::thread)
  • 线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。在C中,std::thread提供了创建和管理线程的接口。
 #include  #include void function() std::cout << “Thread is running…” << std::endl; }int main() std::thread t(function); t.join(); // 等待线程结束 return 0; }

在这个例子中,我们创建了一个新的线程t,它执行function函数。join函数用于等待线程执行完毕。

                锁(互斥量、条件变量)
  • 互斥量(Mutex):互斥量是一种同步机制,用于防止多个线程同时访问共享资源。在C中,std::mutex是最常用的互斥量类型。
#include  std::mutex mtx; // 创建互斥量void shared_print(const std::string& msg, int id) mtx.lock(); // 加锁 std::cout << msg << id << std::endl; mtx.unlock(); // 解锁 }
  • 条件变量(Condition Variable):条件变量是一种同步机制,它允许线程在某些条件不满足时挂起(等待),直到条件满足时被唤醒。
#include  std::condition_variable cv; bool ready = false;void thread_function() std::unique_lockstd::mutex lock(mtx); cv.wait(lock, []{ return ready;}); // 等待条件变量 std::cout << “Thread is processing data…” << std::endl; }void data_preparation_function() // 准备数据… ready = true; cv.notify_one(); // 唤醒一个等待的线程 }
                原子操作
  • 原子操作:原子操作是一种不可分割的操作,它在执行过程中不会被任何其他的操作中断。在C中,std::atomic模板类提供了原子操作的支持。
#include  std::atomic count(0);void increment() count.fetch_add(1, std::memory_order_relaxed); // 原子地增加count的值 }
                任务并行库(TPL)
  • 任务并行库(Task Parallel Library,TPL):TPL是C语言中的一个库,它提供了丰富的并行编程模型和算法,使得并行编程更加简单和高效。TPL通常指的是Microsoft的Parallel Patterns Library(PPL),它不是C标准库的一部分,而是Microsoft Visual C++的一部分。
#include  #include  #include int main() std::vector vec = { 1, 2, 3, 4, 5};concurrency::parallel_for_each(vec.begin(), vec.end(), [](int& n) n *= 2; });for (int n : vec) std::cout << n << ’ '; return 0; }

在这个例子中,parallel_for_each函数并行地对向量vec中的每个元素执行了乘以2的操作。

04|C++项目实践

1、设计模式

设计模式是软件工程中经过验证的解决方案,用于解决在软件设计中经常出现的问题。设计模式可以分为三大类:创建型模式、结构型模式和行为型模式。

                创建型模式

创建型模式关注对象的创建过程,提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是直接使用new运算符实例化对象。这类别包括以下几种模式:

  1. 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
  2. 工厂方法模式(Factory Method):定义一个接口用于创建对象,但让子类决定实例化的类是哪一个。
  3. 抽象工厂模式(Abstract Factory):提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
  4. 建造者模式(Builder):将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
  5. 原型模式(Prototype):通过复制现有的实例来创建新的实例,而不是通过构造函数。
                结构型模式

结构型模式关注类和对象之间的组合,用于实现类和对象的组合,以形成更大的结构。这类别包括以下几种模式:

  1. 适配器模式(Adapter):允许不兼容的接口一起工作,通过适配器转换接口。
  2. 桥接模式(Bridge):将抽象部分与实现部分分离,使它们可以独立变化。
  3. 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。
  4. 装饰器模式(Decorator):动态地给一个对象添加一些额外的职责,而不改变其接口。
  5. 门面模式(Facade):为一个复杂的子系统提供一个统一的接口,使得子系统更容易使用。
  6. 享元模式(Flyweight):通过共享尽可能多的对象来减少对象的数量。
  7. 代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。
                行为型模式

行为型模式关注对象之间的通信,以及如何分配职责,使得对象之间能够有效地通信。这类别包括以下几种模式:

  1. 责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免了请求发送者和接收者之间的耦合关系。
  2. 命令模式(Command):将请求封装为一个对象,从而允许用户使用不同的请求、队列或日志请求,并支持可撤销的操作。
  3. 解释器模式(Interpreter):为语言创建解释器,用于解释语言中的句子。
  4. 迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部的表示。
  5. 中介者模式(Mediator):定义一个对象来封装一组对象之间的交互,使得对象之间不需要显式地相互引用,从而降低它们之间的耦合。
  6. 备忘录模式(Memento):捕获一个对象的内部状态,并在该对象之外保存这个状态,以便稍后恢复它。
  7. 观察者模式(Observer):当对象的状态发生变化时,它的所有依赖者都会自动收到通知。
  8. 状态模式(State):允许对象在内部状态改变时改变其行为。
  9. 策略模式(Strategy):定义一系列算法,将每个算法封装起来,并使它们可以互换。
  10. 模板方法模式(Template Method):在一个方法中定义一个算法的骨架,将一些步骤延迟到子类中实现。
  11. 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作,它可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

2、代码规范与重构

编写高质量的代码不仅要求功能正确,还要求代码具有良好的可读性、可维护性和可扩展性。代码规范与重构是达到这些目标的重要手段。

                编码风格
  • 编码风格:编码风格是指编写代码时遵循的一系列约定,它包括变量命名、函数命名、代码缩进、注释习惯、文件组织等方面。一致的编码风格可以提升代码的可读性,使得团队成员之间更容易理解和交流。以下是一些常见的编码风格指南:

    • 使用有意义的变量和函数名。
    • 保持一致的缩进和空格,例如,使用4个空格而不是制表符进行缩进。
    • 使用注释来解释复杂的逻辑或重要的决策点。
    • 避免过长的函数和类,保持它们的功能单一。
    • 遵循模块化和封装的原则,隐藏内部实现细节。
                代码重构技巧
  • 代码重构技巧:重构是在不改变软件可见行为的前提下,对代码内部结构进行修改的过程。重构的目的是提高代码的质量,使其更加清晰、简单和健壮。以下是一些常见的重构技巧:

    • 提取方法(Extract Method):将大块代码提取成单独的方法。
    • 提取变量(Extract Variable):将复杂的表达式赋值给变量,以提升可读性。
    • 提取类(Extract Class):将过多的职责分离到不同的类中。
    • 重命名(Rename):使用更具描述性的名称来替换变量、函数或类的名称。
    • 移除重复代码(Remove Duplicate Code):通过抽象共同点来减少代码重复。
    • 引入参数对象(Introduce Parameter Object):当函数参数过多时,可以创建一个对象来封装这些参数。
    • 引入工厂方法(Introduce Factory Method):将对象创建逻辑移至单独的方法或类中。
                设计原则(SOLID)
  • 设计原则(SOLID):SOLID是一组五个设计原则的缩写,它们是面向对象设计和编程中的基本原则,有助于编写易于维护和扩展的代码。

    • 单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起变化的原因。
    • 开放封闭原则(Open/Closed Principle, OCP):软件实体应该对扩展开放,对修改封闭。
    • 里氏替换原则(Liskov Substitution Principle, LSP):子类应该能够替换它们的基类。
    • 接口隔离原则(Interface Segregation Principle, ISP):客户端不应该被迫依赖于它们不使用的接口。
    • 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

3、项目实践

项目实践是将理论知识应用到实际软件开发过程中的活动。

                项目分析与设计
  • 项目分析与设计:这是项目实践的第一步,它涉及对项目需求的理解、系统架构的设计以及详细设计的规划。这个过程通常包括以下几个子步骤:

    • 需求分析:通过与利益相关者沟通,明确项目的功能需求和非功能需求。
    • 系统设计:确定系统的整体结构,包括选择合适的技术栈、定义系统组件和它们之间的交互方式。
    • 模块设计:对系统中的每个模块进行详细设计,包括数据结构、接口和算法。
                代码实现
  • 代码实现:在项目分析与设计完成后,开发人员开始编写代码,将设计转化为实际运行的软件。代码实现需要注意以下几点:

    • 遵循编码规范:确保代码的可读性和一致性。
    • 模块化:将代码组织成模块,每个模块负责一个具体的功能。
    • 测试驱动开发(TDD):先编写测试用例,然后编写代码使其通过测试。
                测试与调试
  • 测试与调试:测试是验证代码是否按照预期工作的过程,而调试则是发现和修复代码中错误的过程。测试与调试包括:

    • 单元测试:对最小的可测试部分(通常是函数或方法)进行测试。
    • 集成测试:测试模块之间的交互和集成。
    • 系统测试:测试整个系统的行为是否符合需求。
    • 调试:使用调试工具和技巧来定位和修复代码中的错误。
                性能优化
  • 性能优化:在软件满足功能需求后,性能优化成为提升用户体验和系统效率的关键步骤。性能优化可能包括:

    • 性能分析:使用性能分析工具来识别瓶颈。
    • 代码优化:改进算法和数据结构,减少不必要的计算和内存使用。
    • 系统优化:调整系统配置,如数据库索引、缓存策略和并发设置。

05|附录

1、C++标准库参考

C++标准库是一套丰富的预编译库,它为C++程序员提供了一系列常用的功能,包括输入输出操作、数学计算、内存管理、数据结构、算法等。

                标准库函数

标准库函数是C++提供的一系列全局函数,它们可以直接使用,无需实例化任何类。以下是一些主要的C++标准库函数类别:

输入输出函数

std::cin、std::cout、std::cerr、std::clog:用于标准输入输出。std::getline(): 从输入流中读取一行数据。std::printf(), std::scanf(): 格式化输入输出,类似于C语言的函数。

 数值处理函数

std::abs(): 返回整数的绝对值。std::pow(): 计算幂次方。std::sqrt(): 计算平方根。std::sin(), std::cos(), std::tan(): 三角函数。

字符和字符串函数

std::strlen(): 返回字符串的长度。std::strcmp(): 比较两个字符串。std::strcpy(), std::strncpy(): 复制字符串。std::strcat(), std::strncat(): 连接字符串。

内存管理函数

new, delete: 动态分配和释放内存。std::malloc(), std::free(): 类似于C语言的内存分配和释放函数。std::memcpy(), std::memmove(): 内存复制。

其他函数

std::sort(): 排序算法。std::find(): 查找元素。std::swap(): 交换两个值。
                标准库类

C++标准库还提供了一系列预定义的类,这些类可以用来创建对象,以实现更复杂的功能。以下是一些重要的C++标准库类:

容器类

std::vector: 动态数组。std::list: 双向链表。std::forward_list: 单向链表。std::deque: 双端队列。std::queue, std::priority_queue: 队列和优先队列。std::stack: 栈。std::set, std::multiset: 排序集合和无序集合。std::map, std::multimap: 排序映射和无序映射。std::unordered_map, std::unordered_set: 哈希表实现的映射和集合。

迭代器类

std::iterator: 提供迭代器的基本接口。

算法类

std::algorithm: 包含各种通用算法,如排序和搜索。

数值类

std::complex: 复数类。std::valarray: 用于数值计算。

输入输出类

std::iostream: 基础的输入输出流类。std::fstream: 文件流类,用于文件操作。

智能指针类

std::unique_ptr: 独占所有权语义的智能指针。std::shared_ptr: 共享所有权语义的智能指针。std::weak_ptr: 弱引用智能指针,用于解决共享指针的循环引用问题。

其他类

std::exception: 异常处理的基础类。std::thread: 线程类,用于并发编程。

2、常见问题解答

                编译器差异
  • 问题描述:不同的编译器可能会对同一份代码产生不同的解释,导致编译后的程序行为不一致。

  • 详细说明

    • 语法支持:不同的编译器对C++标准的支持程度不同,有些编译器可能不支持最新的C++标准特性。
    • 优化级别:不同的编译器有不同的优化策略,这可能会影响程序的执行效率和性能。
    • 内置函数:某些编译器提供了特定的内置函数,这些函数在其他编译器中可能不可用或行为不同。
    • 诊断信息:编译器在编译过程中给出的错误和警告信息可能会有所不同,这影响到问题的诊断和修复。
  • 解决方法

    • 尽量使用标准C++代码,避免使用特定编译器的扩展特性。
    • 在多个编译器上测试代码,确保兼容性。
    • 针对不同的编译器编写条件编译代码。
                性能问题
  • 问题描述:程序可能存在性能瓶颈,导致运行缓慢或资源消耗过高。

  • 详细说明

    • 算法效率:算法的选择对性能有重大影响,低效的算法可能导致程序运行缓慢。
    • 内存管理:不当的内存分配和释放可能导致内存泄漏或频繁的内存碎片整理,影响性能。
    • 数据访问:频繁的跨越大内存块的访问可能导致缓存未命中,降低性能。
    • 并发处理:错误的并发设计可能导致线程竞争、死锁等问题,降低程序性能。
  • 解决方法

    • 使用性能分析工具(如gprof, Valgrind)来识别性能瓶颈。
    • 优化算法和数据结构,减少不必要的计算和内存使用。
    • 合理使用内存,避免内存泄漏和过度碎片化。
    • 正确使用并发编程技术,避免竞态条件和死锁。
                设计模式应用
  • 问题描述:设计模式是解决特定问题的通用可重用解决方案,但在实际应用中可能会遇到选择和实现上的困难。

  • 详细说明

    • 模式选择:根据具体问题选择合适的设计模式是关键。错误的选择可能导致代码复杂度增加而收益不大。
    • 模式实现:设计模式的实现需要深入理解其原理和意图,不当的实现可能无法达到预期的效果。
    • 模式滥用:过度使用设计模式或在不适当的情况下使用设计模式会导致代码过度复杂化。
  • 解决方法

    • 理解设计模式的原则和意图,而不是仅仅记住它们的结构。
    • 根据实际问题的需求选择合适的设计模式,避免盲目应用。
    • 保持代码的简洁性,只在必要时引入设计模式。
    • 通过代码审查和重构来确保设计模式的应用是合理和有效的。

3、学习资源推荐

                书籍
  1. “C++ Primer” by Stanley B. Lippman, Josée Lajoie, and Barbara E. Moo - 适合初学者和有经验的程序员,详细介绍了C++的基础和高级特性。
  2. “Effective Modern C++” by Scott Meyers - 专注于C++11和C++14的现代特性,提供了一系列实用的编程技巧和最佳实践。
  3. “The C++ Programming Language” by Bjarne Stroustrup - C++之父所著,详细介绍了C++语言的历史、设计和使用。
  4. “Accelerated C++” by Andrew Koenig and Barbara E. Moo - 以实用主义为导向,快速教授C++基础和编程技巧。
  5. “C++ Concurrency in Action” by Anthony Williams - 专注于C++的并发编程,包括线程、锁、原子操作等。
                在线课程
  1. Coursera - 提供了多个与C++相关的课程,包括斯坦福大学的"Programming Paradigms"和"Object-Oriented Programming in C++"。
  2. Udemy - 有许多不同级别的C++课程,从入门到高级,适合自学。
  3. edX - 由麻省理工学院(MIT)等知名大学提供的C++编程课程。
  4. Pluralsight - 提供了一系列C++教程,涵盖了从基础到高级的主题。
  5. Codecademy - 提供互动式的C++学习体验,适合编程新手。
                论坛与社区
  1. Stack Overflow - 一个广泛使用的问答网站,你可以在这里找到C++相关的各种问题和解答。
  2. Reddit - r/cpp - Reddit上的C++社区,讨论C++相关的新闻、问题和资源。
  3. C++ Forum - 一个专门的C++论坛,可以提问和参与讨论。
  4. ISO C++ Standard Committee - 官方的C++标准委员会网站,可以了解C++的最新标准和提案。
  5. cplusplus.com - 提供C++参考手册、论坛和一个强大的在线编译器。

这些资源可以帮助你建立坚实的C++知识基础,并提供实践和社区支持,以便你在学习过程中遇到问题时能够得到帮助。记住,实践是学习编程的关键,因此尝试编写自己的程序并与他人分享你的代码,以便从社区中获得反馈和建议。

06|总结

这篇文章详细介绍了C++编程语言的基础与进阶知识,涵盖了C++的历史与发展、语言特性、面向对象编程、模板、异常处理、标准模板库(STL)、并发编程、内存管理、对象模型、模板元编程、设计模式、代码规范与重构、项目实践以及C++标准库参考等内容。文章首先介绍了C++语言的概述,包括其通用编程特性以及在多个领域的广泛应用。接着,文章详细阐述了C++的历史与发展,从C语言的演变到C++11、C++14、C++17等新标准的发布。

文章还介绍了C++与C语言的关系,指出C++是C语言的超集,保留了C语言的基本语法和控制结构,同时扩展了C语言,增加了面向对象编程、泛型编程等特性。此外,文章还详细解释了C++的主要语言特性,包括运算符重载、函数重载、构造函数和析构函数等。

面向对象编程(OOP)是C++的核心特性之一,文章介绍了类与对象、构造函数与析构函数、成员函数与友元函数、继承与多态、虚函数与纯虚函数等概念。此外,文章还介绍了模板在C++中的作用,包括函数模板和类模板的使用,以及模板特化与偏特化的概念。

异常处理是C++中用于处理程序运行时错误和异常情况的一种机制,文章介绍了异常的抛出与捕获、异常安全、标准异常等概念。此外,文章还介绍了STL标准模板库,包括容器、迭代器、算法等概念。

文章还详细介绍了C++的高级特性,包括内存管理、对象模型、模板元编程等概念。此外,文章还介绍了并发编程的概念,包括线程、锁、原子操作、任务并行库等。

最后,文章总结了C++项目实践的重要性,包括需求分析、系统设计、模块设计、代码实现、测试与调试、性能优化等步骤。文章还附带了C++标准库参考和常见问题解答,为读者提供了更多的学习资源和解决问题的方法。

总的来说,这篇文章是一份非常全面的C++学习笔记,涵盖了C++语言的基础知识、进阶技巧、项目实践和标准库参考等内容,非常适合想要深入学习C++的读者。

【我要纠错】责任编辑:新华社