C++核心知识卡片

基础概念

1. 面向过程与面向对象的区别

面向过程编程

  • 解决问题的步骤为中心
  • 程序分解为一组函数,每个函数负责特定任务
  • 数据与函数分离
  • 注重算法和流程控制

面向对象编程

  • 对象为核心
  • 数据和操作封装在对象内部
  • 通过定义类创建具体的对象实例
  • 注重抽象、封装、继承和多态

主要区别

特性面向过程面向对象
抽象层次关注步骤和算法关注对象和行为
数据封装性数据与函数分离数据与方法封装在一起
继承和多态无继承和多态概念核心特性
代码复用方式模块化设计类的继承和组合

📌 选择建议:大型项目适合面向对象(可维护性、扩展性更好),小型简单项目可选择面向过程(更直观高效)。

2. C和C++的区别

特性C语言C++
编程范式面向过程面向对象(同时支持面向过程)
对象支持不支持类和对象支持类、继承、多态等OOP特性
标准库基本的输入输出、字符串处理包含C标准库外,还有STL等更丰富的库
异常处理无内置异常处理机制支持try-catch异常处理
内存管理malloc/free除了malloc/free外,还有new/delete
编译器兼容性C编译器一般不支持C++代码C++编译器通常可编译C代码

💡 核心区别:C++在C的基础上增加了面向对象的特性,提供更强大的抽象和封装能力。

3. static关键字的作用

1️⃣ 静态局部变量

  • 生命周期与程序运行期间一致
  • 不随函数调用结束而销毁
  • 保留上次函数调用后的值
void increment() {
    static int counter = 0;  // 只在第一次调用初始化
    counter++;
    cout << "Counter: " << counter << endl;
}

int main() {
    increment();  // 输出: Counter: 1
    increment();  // 输出: Counter: 2
    increment();  // 输出: Counter: 3
    return 0;
}

2️⃣ 静态函数

  • 只在当前文件范围内可见
  • 限制函数作用域,避免命名冲突
// 在同一个文件中定义的静态函数
static void internalFunction() {
    cout << "This is an internal function." << endl;
}

3️⃣ 静态全局变量

  • 只能在声明它的源文件中访问
  • 防止不同源文件之间的命名冲突

4️⃣ 静态类成员

  • 属于类本身而非实例对象
  • 可通过类名直接访问
  • 所有实例共享同一个静态成员
class MyClass {
public:
    static int count;
    static void increaseCount() {
        count++;
        cout << "Count: " << count << endl;
    }
};

int MyClass::count = 0;  // 必须在类外初始化静态成员

int main() {
    MyClass::increaseCount();  // 输出: Count: 1
    MyClass::increaseCount();  // 输出: Count: 2
    return 0;
}

4. const关键字的作用

1️⃣ 声明常量变量

const int MAX_VALUE = 100;  // 声明一个不可修改的常量

2️⃣ 保护函数参数

void printMessage(const string& message) {
    // message不会被修改
    cout << message << endl;
}

3️⃣ 防止函数修改对象状态

class MyClass {
public:
    void printValue() const {  // const成员函数
        cout << value << endl;
        // 不能修改类的成员变量
    }
private:
    int value;
};

4️⃣ 限制返回值的修改

const int getValue() {
    return 42;  // 返回值不能被修改
}

🔑 const的价值:提高代码安全性并明确表达设计意图,帮助编译器优化。

5. synchronized关键字和volatile关键字区别

synchronized关键字

  • C++没有直接对应Java中synchronized关键字
  • C++使用互斥量(mutex)实现类似功能
  • 用于保证临界区代码的互斥访问

volatile关键字

  • 指示编译器不对变量进行优化
  • 确保每次访问该变量都从内存读取或写入
  • 用于处理多线程环境下共享数据、信号处理、硬件寄存器等场景
  • 与Java不同,C++的volatile不能保证原子性、可见性或禁止重排序
volatile int shared_counter; // 告诉编译器不要优化对此变量的访问

⚠️ 注意:volatile本身不能保证线程安全,通常需要配合互斥量使用。

6. C语言中struct和union的区别

结构体(struct)

  • 存储不同类型的数据成员
  • 每个成员独立占用内存空间
  • 结构体大小为所有成员大小之和(加上对齐填充)
  • 可同时访问所有成员

联合体(union)

  • 所有成员共享同一块内存空间
  • 一次只能存储一个成员的值
  • 大小等于最大成员的大小
  • 修改一个成员会影响其他成员
struct Point {
    int x;   // 4字节
    int y;   // 4字节
};  // 总大小: 8字节

union Data {
    int i;     // 4字节
    float f;   // 4字节
    char str[8]; // 8字节
};  // 总大小: 8字节(最大成员的大小)

💡 使用场景:union适用于需要在不同类型之间转换或节省内存空间的场景。

7. C++中struct和class的区别

特性structclass
默认访问控制publicprivate
成员函数默认修饰符publicprivate
继承方式默认public继承默认private继承

使用习惯

  • struct通常用于简单数据结构的定义
  • class更常用于封装复杂对象及其行为
struct DataPoint {
    int x, y;  // 默认public
};

class Entity {
    int id;  // 默认private
public:
    void setId(int value) { id = value; }
};

📝 编程风格:struct更适合POD(Plain Old Data)类型,class更适合需要封装、继承的场景。

8. 数组和指针的区别

特性数组指针
内存分配静态分配,固定大小可动态分配内存
数据访问使用下标使用解引用(*)
可修改性数组名不可修改指针可重新指向不同地址
函数参数传递退化为指针直接传递指针值
int arr[5] = {1, 2, 3, 4, 5};  // 静态数组
int* ptr = new int[5];         // 动态数组,通过指针访问

// 数组名是常量指针
// arr = ptr;  // 错误:不能修改数组名
ptr = arr;     // 正确:指针可以重新赋值

🔍 本质区别:数组名本质上是指向首元素的常量指针,而指针是变量,可以存储和修改内存地址。

9. 程序执行的过程

  1. 编译阶段

    • 源代码(.cpp/.c)经过预处理、编译,生成目标文件(.o/.obj)
  2. 链接阶段

    • 链接器将多个目标文件和库文件链接,生成可执行文件
  3. 加载阶段

    • 操作系统将可执行文件加载到内存
    • 为程序分配所需资源(内存空间、文件句柄等)
  4. 执行阶段

    • CPU按指令序列执行程序
    • 执行算术运算、逻辑判断、内存读写等操作
  5. 库调用阶段

    • 程序调用系统库函数和运行时库函数
  6. 结束阶段

    • 程序执行完毕,操作系统回收资源
    • 返回执行结果

🔄 完整流程:代码 → 编译 → 链接 → 加载 → 执行 → 结束

10. C++中指针和引用的区别

特性指针引用
定义方式使用* (int* p)使用& (int& r)
初始化可以延迟初始化必须在定义时初始化
空值可以为nullptr必须引用有效对象
可改变性可重新指向其他对象一旦绑定不能改变
内存占用占额外内存空间不占额外空间
访问方式需解引用(*p)直接访问®
int value = 10;
int* ptr = &value;  // 指针指向value
int& ref = value;   // 引用绑定到value

*ptr = 20;  // 通过指针修改
ref = 30;   // 通过引用修改

int another = 50;
ptr = &another;  // 指针可以重新指向
// ref = another;  // 错误理解:这不是重新绑定,而是赋值操作

🌟 使用建议:引用通常用于函数参数和返回值,使代码更清晰;指针则适用于需要改变指向或处理空值的场景。

面向对象与高级特性

21. 引用与指针的详细比较

特性引用(Reference)指针(Pointer)
本质对象的别名存储内存地址的变量
初始化必须初始化且不能为空可以延迟初始化,可为nullptr
重新赋值不能重新绑定到其他对象可以改变指向的对象
多重间接性不支持多级引用支持多级指针
语法使用使用更简洁需要解引用操作
空值检查不需要检查空值使用前需要检查nullptr
算术运算不支持引用算术支持指针算术(++、–等)
// 引用示例
int original = 5;
int& ref = original;  // 引用必须初始化
ref = 10;            // 修改original的值为10

// 指针示例
int* ptr = nullptr;  // 可以初始化为nullptr
ptr = &original;     // 可以重新指向
*ptr = 15;           // 通过解引用修改值
ptr++;               // 指针算术,移动到下一个int位置

🌟 最佳实践

  • 优先使用引用作为函数参数,特别是不需要修改指向的情况
  • 当需要表示"无对象"状态或需要改变指向时,使用指针
  • 使用引用可以使代码更清晰、安全

22. 函数重载(Function Overloading)

函数重载的定义

  • 允许在同一作用域中定义多个同名但参数不同的函数
  • 编译器根据调用时的参数类型和数量匹配合适的函数

重载的条件

  • 函数名相同
  • 参数列表不同(类型、数量或顺序)
  • 返回类型不同但参数相同的函数不构成重载
// 函数重载示例
int add(int a, int b) {
    return a + b;
}

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

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

// 调用示例
int sum1 = add(5, 3);        // 调用第一个函数
float sum2 = add(5.0f, 3.0f); // 调用第二个函数
int sum3 = add(5, 3, 2);     // 调用第三个函数

重载解析过程

  1. 编译器找到所有同名函数
  2. 寻找参数完全匹配的函数
  3. 如果没有完全匹配,尝试通过类型转换找到最佳匹配
  4. 如果有多个可能的匹配,则产生二义性错误

注意事项

  • 避免依赖隐式类型转换的重载
  • 小心默认参数与重载结合使用
  • C语言不支持函数重载

💡 优点:函数重载增强了代码可读性和易用性,使相似操作可以使用相同的函数名。

23. 类和对象

类(Class)的概念

  • 用户定义的数据类型
  • 描述具有相似特征和行为的对象的蓝图
  • 封装了数据(属性)和操作数据的方法(函数)

对象(Object)的概念

  • 类的实例
  • 具有具体状态和行为的实体
  • 占用内存空间

类的定义

class Student {
private:
    // 数据成员(属性)
    int id;
    string name;
    float score;

public:
    // 构造函数
    Student(int i, string n, float s) : id(i), name(n), score(s) {}

    // 成员函数(方法)
    void display() const {
        cout << "ID: " << id << ", Name: " << name
             << ", Score: " << score << endl;
    }

    float getScore() const {
        return score;
    }

    void setScore(float s) {
        score = s;
    }
};

对象的创建与使用

// 对象创建
Student alice(1001, "Alice", 92.5);  // 栈上分配
Student* bob = new Student(1002, "Bob", 85.0);  // 堆上分配

// 对象使用
alice.display();         // 使用点操作符访问
bob->display();          // 使用箭头操作符访问
alice.setScore(95.0);    // 修改属性
float bobScore = bob->getScore();  // 获取属性

// 释放堆对象
delete bob;

类与对象的关系

  • 类是对象的模板,对象是类的具体实例
  • 一个类可以创建多个对象
  • 每个对象有自己的数据成员副本,但共享成员函数

📝 面向对象的核心:类与对象是面向对象编程的基本单位,通过数据封装、继承和多态实现代码复用和模块化。

24. C++的访问修饰符

三种访问修饰符

  1. public

    • 可在任何地方访问
    • 定义类的接口
    • 通常用于成员函数和公共数据
  2. protected

    • 只能在当前类及其派生类中访问
    • 对外部隐藏,但允许继承使用
    • 通常用于被派生类需要的内部实现
  3. private

    • 只能在当前类内部访问
    • 对外部和派生类都隐藏
    • 通常用于内部实现细节
class Base {
public:
    int publicVar;       // 公有成员
    void publicMethod() {
        cout << "Public method" << endl;
        privateMethod();  // 可以访问私有方法
    }

protected:
    int protectedVar;    // 受保护成员
    void protectedMethod() {
        cout << "Protected method" << endl;
    }

private:
    int privateVar;      // 私有成员
    void privateMethod() {
        cout << "Private method" << endl;
    }
};

class Derived : public Base {
public:
    void testAccess() {
        publicVar = 1;        // 可以访问公有成员
        publicMethod();       // 可以访问公有方法
        protectedVar = 2;     // 可以访问受保护成员
        protectedMethod();    // 可以访问受保护方法
        // privateVar = 3;    // 错误:不能访问私有成员
        // privateMethod();   // 错误:不能访问私有方法
    }
};

int main() {
    Base b;
    b.publicVar = 10;     // 可以访问公有成员
    b.publicMethod();     // 可以访问公有方法
    // b.protectedVar = 20;  // 错误:不能访问受保护成员
    // b.protectedMethod(); // 错误:不能访问受保护方法
    // b.privateVar = 30;    // 错误:不能访问私有成员
    // b.privateMethod();    // 错误:不能访问私有方法
}

友元(friend)机制

  • 允许非成员函数或其他类访问私有和受保护成员
  • 破坏了封装性,应谨慎使用
class MyClass {
private:
    int secret;

    friend void friendFunction(MyClass& obj);  // 友元函数
    friend class FriendClass;                 // 友元类

public:
    MyClass(int s) : secret(s) {}
};

void friendFunction(MyClass& obj) {
    cout << "Secret: " << obj.secret << endl;  // 可以访问私有成员
}

class FriendClass {
public:
    void reveal(MyClass& obj) {
        cout << "Secret: " << obj.secret << endl;  // 可以访问私有成员
    }
};

🔐 封装原则:使用访问修饰符实现"数据隐藏",公有接口尽量简洁,私有实现尽可能多。

25. 虚函数和纯虚函数

虚函数(Virtual Function)

  • 基类中使用virtual关键字声明的函数
  • 派生类可以重写(override)虚函数
  • 实现运行时多态性
  • 通过虚函数表(vtable)和虚函数指针(vptr)实现
class Shape {
public:
    virtual void draw() {
        cout << "Drawing a shape" << endl;
    }

    virtual double area() {
        return 0.0;
    }
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    // 重写虚函数
    void draw() override {
        cout << "Drawing a circle" << endl;
    }

    double area() override {
        return 3.14159 * radius * radius;
    }
};

纯虚函数(Pure Virtual Function)

  • 在声明末尾使用= 0的虚函数
  • 没有函数实现
  • 含有纯虚函数的类称为抽象类,不能直接实例化
  • 派生类必须实现所有纯虚函数,否则也是抽象类
class AbstractShape {
public:
    // 纯虚函数
    virtual void draw() = 0;
    virtual double area() = 0;

    // 普通虚函数
    virtual void rotate(double angle) {
        cout << "Rotating shape by " << angle << " degrees" << endl;
    }
};

class Rectangle : public AbstractShape {
private:
    double width, height;

public:
    Rectangle(double w, double h) : width(w), height(h) {}

    // 实现纯虚函数
    void draw() override {
        cout << "Drawing a rectangle" << endl;
    }

    double area() override {
        return width * height;
    }

    // 可以选择重写普通虚函数
    void rotate(double angle) override {
        cout << "Rotating rectangle by " << angle << " degrees" << endl;
    }
};

虚函数与纯虚函数的区别

特性虚函数纯虚函数
声明方式virtual type name()virtual type name() = 0
函数体可以有默认实现没有实现(在接口中)
派生类重写可选必须
类的性质可以实例化不能实例化(抽象类)
主要用途提供多态行为和默认实现定义接口规范

🔄 设计模式:虚函数用于实现"模板方法"模式;纯虚函数用于实现"策略"模式和"接口"概念。

26. C++中的继承

继承的基本概念

  • 创建新类时复用现有类的属性和方法
  • 已存在的类称为基类/父类,新类称为派生类/子类
  • 子类继承父类的特性,并可添加新特性或修改现有特性

继承的语法

class Base {
    // 基类成员
};

class Derived : [访问修饰符] Base {
    // 派生类成员
};

继承类型

  1. 公有继承(public)

    • 基类的公有成员在派生类中仍是公有的
    • 基类的保护成员在派生类中仍是保护的
    • 基类的私有成员在派生类中不可访问
  2. 保护继承(protected)

    • 基类的公有和保护成员在派生类中变为保护成员
    • 基类的私有成员在派生类中不可访问
  3. 私有继承(private)

    • 基类的公有和保护成员在派生类中变为私有成员
    • 基类的私有成员在派生类中不可访问
class Vehicle {
public:
    void start() { cout << "Vehicle started" << endl; }
protected:
    int speed;
private:
    string engineType;
};

// 公有继承
class Car : public Vehicle {
public:
    void drive() {
        start();  // 可以访问基类公有方法
        speed = 60;  // 可以访问基类保护成员
        // engineType = "V8";  // 错误:不能访问基类私有成员
    }
};

// Car的对象可以直接调用Vehicle的公有方法
Car myCar;
myCar.start();  // 正确

多继承

  • 一个类可以同时继承多个基类
  • 可能导致菱形继承问题(Diamond Problem)
class A {
public:
    void display() { cout << "Class A" << endl; }
};

class B {
public:
    void display() { cout << "Class B" << endl; }
};

// 多继承
class C : public A, public B {
public:
    // 解决二义性
    void showDisplay() {
        A::display();  // 调用A的display
        B::display();  // 调用B的display
    }
};

虚继承

  • 解决菱形继承问题
  • 确保共同基类只有一个实例
class Animal {
public:
    int age;
};

class Mammal : virtual public Animal {
    // Mammal特有成员
};

class Bird : virtual public Animal {
    // Bird特有成员
};

class Bat : public Mammal, public Bird {
    // 因为虚继承,只有一份Animal::age
};

🧩 设计原则:优先使用组合而非继承;必要时使用公有继承表示"是一个"关系;避免多继承导致的复杂性。

27. 析构函数

析构函数的作用

  • 在对象销毁时自动调用
  • 释放对象占用的资源
  • 执行必要的清理操作
  • 防止内存泄漏

析构函数的特点

  • 名称为类名前加~
  • 不带参数
  • 没有返回类型
  • 一个类只能有一个析构函数
  • 如果未定义,编译器会生成默认析构函数
class Resource {
private:
    int* data;

public:
    // 构造函数
    Resource(int size) {
        cout << "Resource acquired" << endl;
        data = new int[size];
    }

    // 析构函数
    ~Resource() {
        cout << "Resource released" << endl;
        delete[] data;  // 释放动态分配的内存
    }
};

void useResource() {
    Resource r(10);  // 构造函数被调用
    // 使用资源...
} // 函数结束,r超出作用域,析构函数自动调用

虚析构函数

  • 基类的析构函数应声明为virtual
  • 确保使用基类指针删除派生类对象时调用正确的析构函数
  • 防止资源泄漏
class Base {
public:
    Base() { cout << "Base constructed" << endl; }
    virtual ~Base() { cout << "Base destructed" << endl; }
};

class Derived : public Base {
private:
    int* array;

public:
    Derived() : Base() {
        cout << "Derived constructed" << endl;
        array = new int[10];
    }

    ~Derived() {
        cout << "Derived destructed" << endl;
        delete[] array;
    }
};

int main() {
    Base* ptr = new Derived();
    // 使用对象...
    delete ptr;  // 如果~Base()不是virtual,只会调用Base析构函数
}

RAII原则

  • Resource Acquisition Is Initialization(资源获取即初始化)
  • 在构造函数中获取资源,在析构函数中释放资源
  • C++的核心资源管理策略

⚠️ 注意事项

  • 派生类的析构函数会自动调用基类的析构函数
  • 使用多态时,基类的析构函数必须是虚函数
  • 如果类包含动态分配的资源,必须定义析构函数

28. 友元函数(Friend Function)

友元函数的定义

  • 不属于类的成员,但可以访问类的所有成员(包括私有成员)
  • 在类内部使用friend关键字声明
  • 破坏了封装性,应谨慎使用

友元函数的类型

  1. 普通友元函数:独立函数被声明为友元
  2. 友元成员函数:另一个类的成员函数被声明为友元
  3. 友元类:整个类及其所有成员都是友元

普通友元函数

class Box {
private:
    double width, height, depth;

public:
    Box(double w, double h, double d) : width(w), height(h), depth(d) {}

    // 声明友元函数
    friend double getVolume(const Box& b);
};

// 友元函数定义
double getVolume(const Box& b) {
    // 可以直接访问私有成员
    return b.width * b.height * b.depth;
}

友元成员函数

class Screen;  // 前向声明

class ScreenManager {
public:
    void clearScreen(Screen& s);
};

class Screen {
private:
    string content;

public:
    Screen(string text) : content(text) {}

    // 将ScreenManager类的clearScreen方法声明为友元
    friend void ScreenManager::clearScreen(Screen& s);
};

// 友元成员函数定义
void ScreenManager::clearScreen(Screen& s) {
    s.content = "";  // 可以访问Screen的私有成员
}

友元类

class Node {
private:
    int data;
    Node* next;

    // 声明友元类
    friend class LinkedList;

public:
    Node(int d) : data(d), next(nullptr) {}
};

class LinkedList {
public:
    void addNode(Node* node, Node* newNode) {
        // 可以访问Node的私有成员
        newNode->next = node->next;
        node->next = newNode;
    }
};

友元的特性

  • 友元关系不具有传递性:A是B的友元,B是C的友元,不意味着A是C的友元
  • 友元关系不具有继承性:基类的友元不是派生类的友元
  • 友元关系不是相互的:A是B的友元,不意味着B是A的友元

🔑 最佳实践

  • 友元通常用于运算符重载或需要高效访问私有成员的场景
  • 尽量减少友元的使用,保持良好的封装性
  • 友元可以增强灵活性,但过度使用会降低代码可维护性

29. 命名空间(Namespace)

命名空间的作用

  • 避免命名冲突
  • 组织和分类代码
  • 控制名称可见性
  • 提供模块化支持

定义命名空间

// 定义命名空间
namespace Mathematics {
    const double PI = 3.14159;

    double square(double x) {
        return x * x;
    }

    class Complex {
        // 复数类定义
    };
}

使用命名空间

// 方法1:使用命名空间名称限定
double area = Mathematics::PI * radius * radius;
double squared = Mathematics::square(5.0);

// 方法2:using声明(导入特定名称)
using Mathematics::PI;
double area = PI * radius * radius;

// 方法3:using指令(导入整个命名空间)
using namespace Mathematics;
double area = PI * radius * radius;
double squared = square(5.0);

嵌套命名空间

namespace Outer {
    void outerFunction() {
        cout << "Outer function" << endl;
    }

    namespace Inner {
        void innerFunction() {
            cout << "Inner function" << endl;
        }
    }
}

// 访问嵌套命名空间
Outer::outerFunction();
Outer::Inner::innerFunction();

匿名命名空间

namespace {
    // 匿名命名空间中的内容仅在当前文件可见
    // 类似于static全局函数/变量
    int privateVar = 10;

    void privateFunction() {
        cout << "Private function" << endl;
    }
}

命名空间别名

namespace VeryLongNamespace {
    void function() {
        cout << "Function in long namespace" << endl;
    }
}

// 创建别名
namespace VLN = VeryLongNamespace;

// 使用别名
VLN::function();

标准命名空间

  • 标准库定义在std命名空间中
  • 应避免在自己的代码中使用std作为命名空间名

⚠️ 最佳实践

  • 避免在头文件中使用using namespace指令
  • 优先使用命名空间名称限定或using声明
  • 在大型项目中使用命名空间组织代码
  • 嵌套命名空间反映层次结构

30. 模板(Template)

模板的作用

  • 实现泛型编程
  • 支持类型无关的代码
  • 提高代码复用性
  • 在编译时生成特定类型的代码

函数模板

  • 创建可以处理不同类型参数的函数
  • 编译器根据调用时的参数类型生成具体函数
// 函数模板定义
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 使用函数模板
int maxInt = max<int>(10, 20);       // 显式指定类型
double maxDouble = max(3.14, 2.71);  // 类型自动推导

类模板

  • 创建可以适应不同数据类型的类
  • 必须在实例化时指定具体类型
// 类模板定义
template <typename T>
class Stack {
private:
    vector<T> elements;

public:
    void push(const T& item) {
        elements.push_back(item);
    }

    T pop() {
        if (elements.empty()) {
            throw runtime_error("Stack is empty");
        }
        T top = elements.back();
        elements.pop_back();
        return top;
    }

    bool isEmpty() const {
        return elements.empty();
    }
};

// 使用类模板
Stack<int> intStack;      // 整数栈
Stack<string> strStack;   // 字符串栈

intStack.push(10);
intStack.push(20);
cout << intStack.pop();   // 输出20

模板特化

  • 为特定类型提供不同实现
  • 分为全特化和偏特化
// 主模板
template <typename T>
class DataProcessor {
public:
    void process(T data) {
        cout << "Processing generic data: " << data << endl;
    }
};

// 全特化(完全特化)
template <>
class DataProcessor<string> {
public:
    void process(string data) {
        cout << "Processing string data: " << data << endl;
    }
};

// 偏特化(部分特化)
template <typename T>
class DataProcessor<T*> {
public:
    void process(T* data) {
        cout << "Processing pointer data pointing to: " << *data << endl;
    }
};

非类型模板参数

  • 除类型外,模板还可以接受常量值作为参数
// 非类型模板参数
template <typename T, int Size>
class Array {
private:
    T data[Size];

public:
    T& operator[](int index) {
        return data[index];
    }

    int size() const {
        return Size;
    }
};

// 使用带有非类型参数的模板
Array<int, 5> intArray;
Array<double, 10> doubleArray;

模板元编程

  • 在编译时执行的计算
  • 使用模板递归和特化
// 编译时阶乘计算示例
template <int N>
struct Factorial {
    static const int value = N * Factorial<N-1>::value;
};

// 特化终止条件
template <>
struct Factorial<0> {
    static const int value = 1;
};

// 使用
cout << "5! = " << Factorial<5>::value << endl;  // 编译时计算

🔍 模板优势

  • 类型安全:编译时检查类型
  • 性能:无运行时开销
  • 复用:同一代码适用于多种类型
  • 灵活:可通过特化处理特殊情况

STL与高级功能

31. C++标准模板库(STL)

常用容器类型

容器特点适用场景
vector动态数组,连续内存,尾部增删快随机访问,尾部操作频繁
list双向链表,任意位置增删快频繁在任意位置插入删除
deque双端队列,两端增删快两端频繁操作
stack后进先出(LIFO)栈需要LIFO操作顺序
queue先进先出(FIFO)队列需要FIFO操作顺序
priority_queue优先队列,自动排序需要维护元素优先级
set有序集合,不允许重复元素需要有序且唯一的元素集合
map键值对映射,按键排序需要通过键快速查找值

容器操作示例

// vector示例
vector<int> nums = {1, 2, 3, 4, 5};
nums.push_back(6);      // 添加元素
nums[2] = 10;           // 随机访问
for(int n : nums) {     // 范围for循环遍历
    cout << n << " ";
}

// map示例
map<string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 87;
for(const auto& pair : scores) {
    cout << pair.first << ": " << pair.second << endl;
}

STL算法

  • 包含在<algorithm>头文件中
  • 常用算法:sort, find, binary_search, transform等
vector<int> v = {5, 3, 8, 1, 7};
// 排序
sort(v.begin(), v.end());

// 查找
auto it = find(v.begin(), v.end(), 7);
if(it != v.end()) {
    cout << "Found: " << *it << endl;
}

🚀 核心优势:STL提供了高效、可复用、类型安全的数据结构和算法。

32. 异常处理(Exception Handling)

异常处理基本结构

  • try块:包含可能抛出异常的代码
  • catch块:捕获并处理异常
  • throw语句:抛出异常
try {
    // 可能抛出异常的代码
    int* arr = new int[1000000000]; // 可能导致std::bad_alloc
    if (!arr) {
        throw "Memory allocation failed";
    }
} catch (const std::bad_alloc& e) {
    cout << "内存分配失败: " << e.what() << endl;
} catch (const char* msg) {
    cout << "错误: " << msg << endl;
} catch (...) {
    cout << "未知异常" << endl;
}

异常类层次结构

  • std::exception:所有标准异常的基类
  • 常见派生类:std::bad_alloc, std::runtime_error等

自定义异常类

class DivideByZeroException : public std::exception {
public:
    const char* what() const noexcept override {
        return "除数不能为零";
    }
};

double divide(double a, double b) {
    if (b == 0) {
        throw DivideByZeroException();
    }
    return a / b;
}

⚠️ 注意:异常处理有一定性能开销,在性能关键路径上应谨慎使用。

33. 运算符重载(Operator Overloading)

运算符重载的作用

  • 允许自定义类型使用C++内置的运算符
  • 使代码更直观、易读
  • 提供与内置类型一致的使用体验

重载方式

  • 成员函数方式
  • 全局函数方式(友元)

二元运算符重载

class Complex {
private:
    double real, imag;

public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // 成员函数重载+
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

    // 友元函数重载*
    friend Complex operator*(const Complex& a, const Complex& b);
};

// 全局函数重载*
Complex operator*(const Complex& a, const Complex& b) {
    return Complex(a.real * b.real - a.imag * b.imag,
                  a.real * b.imag + a.imag * b.real);
}

一元运算符重载

// 前置++
Complex& operator++() {
    ++real;
    ++imag;
    return *this;
}

// 后置++
Complex operator++(int) {
    Complex temp = *this;
    ++(*this);
    return temp;
}

流运算符重载

// 输出流运算符
friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
    os << c.real;
    if (c.imag >= 0) {
        os << "+" << c.imag << "i";
    } else {
        os << c.imag << "i";
    }
    return os;
}

📌 限制:不能重载的运算符包括:., .*, ::, ?:, sizeof。应保持运算符的原始语义。

34. 虚拟继承(Virtual Inheritance)

菱形继承问题

  • 当一个类通过多条路径继承自同一个基类时产生
  • 导致派生类包含基类的多个副本
  • 访问基类成员时产生二义性
class A {
public:
    int data;
};

class B : public A { };
class C : public A { };

// 菱形继承
class D : public B, public C {
    // D包含两份A::data
};

D d;
// d.data; // 错误:二义性
d.B::data = 1; // 必须指定路径

虚拟继承解决方案

  • 使用virtual关键字指定虚拟继承
  • 虚拟继承确保共同基类只有一个实例
class A {
public:
    int data;
};

class B : virtual public A { }; // 虚拟继承
class C : virtual public A { }; // 虚拟继承

class D : public B, public C {
    // D只包含一份A::data
};

D d;
d.data = 1; // 可以直接访问,没有二义性

💡 设计建议:如果可能,使用组合而非多继承来避免菱形继承问题。

35. 类型转换操作符和显式类型转换

类型转换操作符

  • 允许自定义类型隐式转换为其他类型
  • 没有参数和返回类型(返回类型隐含在操作符名称中)
class Fraction {
private:
    int numerator;
    int denominator;

public:
    Fraction(int n, int d) : numerator(n), denominator(d) {}

    // 转换为double的操作符
    operator double() const {
        return static_cast<double>(numerator) / denominator;
    }
};

Fraction f(3, 4);
double d = f; // 隐式调用类型转换操作符

explicit关键字

  • 防止隐式类型转换
  • 只允许显式转换
class Integer {
private:
    int value;

public:
    // 显式构造函数
    explicit Integer(int v) : value(v) {}

    // 显式类型转换操作符
    explicit operator int() const {
        return value;
    }
};

Integer i(42);
// int n = i; // 错误:禁止隐式转换
int n = static_cast<int>(i); // 正确:显式转换

C++类型转换操作符

  1. static_cast

    • 基本类型转换
    • 继承层次间的上行转换
    • 无运行时类型检查
  2. dynamic_cast

    • 继承层次间的安全下行转换
    • 有运行时类型检查
    • 要求有虚函数(RTTI)
  3. const_cast

    • 添加或移除const/volatile限定符
    • 不能改变类型
  4. reinterpret_cast

    • 重新解释二进制位
    • 危险,但有时必要
    • 通常用于底层操作

⚠️ 最佳实践:优先使用static_cast,需要类型安全的多态转换时使用dynamic_cast,避免使用reinterpret_cast除非确实需要。

36. 智能指针(Smart Pointer)

智能指针的作用

  • 自动管理动态内存
  • 防止内存泄漏
  • 实现RAII原则

std::unique_ptr

  • 独占式所有权
  • 不可复制,只能移动
  • 资源有唯一的拥有者
std::unique_ptr<int> p1(new int(42));
// std::unique_ptr<int> p2 = p1; // 错误:不能复制
std::unique_ptr<int> p2 = std::move(p1); // 移动所有权,p1变为nullptr

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

std::shared_ptr

  • 共享所有权
  • 使用引用计数
  • 当最后一个shared_ptr销毁时,资源被释放
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1; // 共享所有权,引用计数增加

cout << "引用计数: " << p1.use_count() << endl; // 输出2

std::weak_ptr

  • 与shared_ptr协作使用
  • 不增加引用计数
  • 用于解决shared_ptr循环引用问题
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;

// 检查资源是否仍然存在
if (auto temp = wp.lock()) {
    cout << *temp << endl; // 资源仍然存在
} else {
    cout << "资源已被释放" << endl;
}

🔑 使用建议

  • 默认使用unique_ptr管理独占资源
  • 需要共享所有权时使用shared_ptr
  • 需要引用共享资源但不参与所有权时使用weak_ptr

37. C++11新特性

语言特性

  1. auto关键字

    • 自动类型推导
    auto i = 42;        // int
    auto d = 3.14;      // double
    auto str = "hello"; // const char*
    
  2. Lambda表达式

    • 匿名函数
    auto add = [](int a, int b) { return a + b; };
    cout << add(3, 4); // 输出7
    
    // 捕获变量
    int x = 10;
    auto addX = [x](int a) { return a + x; };
    
  3. Range-based for循环

    vector<int> nums = {1, 2, 3, 4, 5};
    for (auto num : nums) {
        cout << num << " ";
    }
    
  4. nullptr

    • 替代NULL,类型安全
    int* p = nullptr;
    
  5. 强类型枚举

    enum class Color { Red, Green, Blue };
    Color c = Color::Red;
    

库特性

  1. 智能指针

    • unique_ptr, shared_ptr, weak_ptr
  2. std::move和右值引用

    • 支持移动语义
    std::vector<int> v1 = {1, 2, 3};
    std::vector<int> v2 = std::move(v1); // 移动而非复制
    
  3. 并发支持

    • std::thread, std::mutex, std::future等

🆕 重要性:C++11是C++标准的重大更新,极大提高了开发效率和代码质量。

38. 静态断言与动态断言

动态断言(assert)

  • 在运行时检查条件
  • 位于<cassert>头文件
  • 在Debug模式下生效,Release模式通常被禁用
#include <cassert>

void divide(int a, int b) {
    assert(b != 0); // 运行时检查
    return a / b;
}

静态断言(static_assert)

  • 在编译时检查条件
  • C++11引入
  • 条件必须是编译期常量表达式
  • 无论Debug还是Release模式都有效
template <typename T>
void processData(T value) {
    static_assert(std::is_integral<T>::value,
                 "只允许整型数据!");
    // 处理数据...
}

// 编译期检查
static_assert(sizeof(int) == 4, "该平台上int不是4字节!");

区别

特性动态断言静态断言
检查时机运行时编译时
条件类型任何布尔表达式常量表达式
模式依赖Debug模式有效所有模式有效
错误消息固定格式自定义错误消息

💡 使用建议

  • 用静态断言检查编译期可确定的条件(类型特性、大小等)
  • 用动态断言检查运行时条件(用户输入、函数参数等)

39. 析构函数为何是虚函数而构造函数不能是

析构函数为虚函数的原因

  • 确保通过基类指针删除派生类对象时调用正确的析构函数
  • 防止资源泄漏
  • 支持多态删除
class Base {
public:
    virtual ~Base() {
        cout << "Base析构" << endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        cout << "Derived析构" << endl;
    }
};

// 正确的析构顺序
Base* ptr = new Derived();
delete ptr; // 先调用Derived析构,再调用Base析构

构造函数不能是虚函数的原因

  1. 概念矛盾

    • 虚函数调用依赖于vtable(虚函数表)
    • vtable指针在构造函数中初始化
    • 构造对象时,类型已确定,不需要虚机制
  2. 执行顺序

    • 构造时先构造基类部分,再构造派生类部分
    • 构造基类部分时,派生类部分尚未存在

🔍 核心理念:构造是自下而上的创建过程,析构是自上而下的销毁过程。

40. const与#define的区别

#define的特点

  • 预处理指令,在预处理阶段进行文本替换
  • 无类型检查,只是简单的文本替换
  • 不遵循作用域规则,全局生效
  • 没有内存分配,不会出现在符号表中
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))

const的特点

  • 编译期常量,拥有类型信息
  • 具有作用域,遵循变量的作用域规则
  • 可以进行类型检查
  • 可以调试,有内存分配(对于非内联常量)
const double PI = 3.14159;
const int MAX_ARRAY_SIZE = 100;

主要区别

特性const#define
处理阶段编译时预处理时
类型检查
作用域遵循块作用域从定义点到文件结束
内存分配可能分配内存无内存分配
可调试性可在调试器中查看不可在调试器中查看

使用建议

  • 一般情况下优先使用const
  • 需要类型安全的常量定义用const
  • 简单的文本替换可以用#define
  • 对于复杂的宏定义功能,现代C++更建议使用constexpr函数或内联函数

📘 现代C++:在C++11之后,使用constexpr可以定义编译期常量表达式,比简单的const更强大,比#define更安全。


📚 学习资源

📊 C++版本发展

版本年份主要特性
C++981998STL, 异常处理
C++112011auto, lambda, 智能指针
C++142014make_unique, 泛型lambda
C++172017结构化绑定, std::optional
C++202020概念(concepts), 协程, 模块
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J先生x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值