题目主要参考:https://blog.csdn.net/songbijian/article/details/132507421 ,但是内容为自己写的。

1.封装、继承、多态

class BankAccount {
private:
    double balance;  // 封装在类内部,外界无法直接访问

public:
    BankAccount(double init) : balance(init) {}

    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }

    double getBalance() const {
        return balance;
    }
};

int main() {
    BankAccount acc(1000);
    acc.deposit(500);
    // acc.balance = -999; // ❌ 不允许直接访问
    std::cout << acc.getBalance(); // ✅ 通过接口访问
}

子类自动“拿到”父类的成员(变量、方法),从而实现代码复用。

但私有成员虽然继承了,但子类不能直接访问。

意义拓展:

让通用逻辑写在父类里,子类只管扩展差异部分。

形成层级关系(例如:动物 → 哺乳动物 → 狗)。

注意耦合性:继承太多层会导致修改父类影响所有子类。

举例(生活版):

“电动车”继承了“自行车”的特性(有轮子、能骑),但增加了“电池”和“充电功能”。

class Animal {
public:
    void eat() { std::cout << "Eating...\n"; }
};

class Dog : public Animal {
public:
    void bark() { std::cout << "Woof!\n"; }
};

int main() {
    Dog d;
    d.eat();  // 继承自 Animal
    d.bark(); // Dog 自己的功能
}

相同的接口,针对不同的对象有不同的表现。

多态常见于虚函数(C++),即“父类指针/引用调用子类重写的方法”。

C++ 在调用虚函数时的一种运行时机制

意义拓展:

可替代性:用父类指针就能操作所有子类对象。

可扩展性:增加新子类时,旧代码无需修改。

简化逻辑:避免写大量的 if-else 来区分不同对象。

举例(生活版):

“播放”这个动作,对不同对象表现不同:

播放 CD → 放音乐

播放 视频文件 → 放电影

播放 游戏 → 进入游戏场景

class Shape {
public:
    virtual void draw() { std::cout << "Drawing a shape\n"; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing a circle\n"; }
};

class Square : public Shape {
public:
    void draw() override { std::cout << "Drawing a square\n"; }
};

int main() {
    Shape* s1 = new Circle();
    Shape* s2 = new Square();

    s1->draw(); // 多态:输出 "Drawing a circle"
    s2->draw(); // 多态:输出 "Drawing a square"
}

Shape* s1 = new Circle(); 看上去 s1 是一个 Shape,但实际上它存的是 Circle 对象的地址。编译器在编译时只知道它是 Shape,但运行时 s1 真正指向的是一个 Circle。

如果没有 virtual,编译器会在编译阶段就确定调用 Shape::draw()(静态绑定)。

加了 virtual 后,编译器会在对象里存一张“虚函数表 vtable”,表里记录了当前对象该调用哪个版本的 draw()。

所以 s1->draw() 运行时会去查 vtable,发现 s1 指向的是 Circle,于是调用 Circle::draw()。


2.多态的实现原理是什么?以及多态的优点(特点)?

  1. 静态多态(编译期多态)

定义:编译器在编译阶段就能确定调用哪个函数,不需要运行时再查表。 实现方式

示例(函数重载)

class Printer {
public:
    void print(int x) { std::cout << "Printing int: " << x << "\n"; }
    void print(double y) { std::cout << "Printing double: " << y << "\n"; }
};
int main() {
    Printer p;
    p.print(5);    // 调用 int 版本
    p.print(3.14); // 调用 double 版本
}

示例(运算符重载(Operator overloading))

#include <iostream>
using namespace std;

class Complex {
public:
    double real, imag;

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

    // 重载 + 运算符
    Complex operator+(const Complex& other) {
        return Complex(real + other.real, imag + other.imag);
    }

    void display() { cout << real << " + " << imag << "i" << endl; }
};

int main() {
    Complex c1(1.0, 2.0), c2(3.5, 4.5);
    Complex c3 = c1 + c2;  // 调用重载的 +
    c3.display();          // 输出 4.5 + 6.5i
}

示例(Template,多见于泛型编程)

#include <iostream>
using namespace std;

// 函数模板
template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    cout << add(3, 5) << endl;         // int 版本 → 输出 8
    cout << add(3.14, 2.71) << endl;   // double 版本 → 输出 5.85
    cout << add(string("Hi "), string("there")) << endl; // string 版本 → "Hi there"
}

示例(函数隐藏(Redefinition / Name hiding))

#include <iostream>
using namespace std;

class Base {
public:
    void show(int x) { cout << "Base show(int): " << x << endl; }
    void show(double y) { cout << "Base show(double): " << y << endl; }
};

class Derived : public Base {
public:
    void show(string s) { cout << "Derived show(string): " << s << endl; }
};

int main() {
    Derived d;
    // d.show(10);       // ❌ 编译错误:Base::show 被隐藏
    // d.show(3.14);     // ❌ 也不行
    d.show("Hello");     // ✅ 调用子类版本
}

编译器在编译时就能决定调用哪个函数,所以叫“静态多态”。

  1. 动态多态(运行时多态)

定义:编译器在编译时只知道调用的是“基类接口”,但真正调用哪个版本要运行时才能决定实现方式

示例(动态多态)

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape\n"; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle\n"; }
};

int main() {
    Shape* s = new Circle(); // 基类指针指向派生类对象
    s->draw(); // 运行时决定:调用 Circle::draw()
}

背后原理(简化版) 假设 Shape 的虚函数表是:

vtable for Shape
\&Shape::draw

Circle 的虚函数表是:

vtable for Circle
\&Circle::draw

当你写 Shape* s = new Circle(); 时:

  1. 可替代性(Substitutability)

父类指针/引用可以指向任意子类对象,代码更通用。

void render(Shape* s) { s->draw(); } 
// 不管是 Circle 还是 Square,都能传进来
  1. 可扩展性(Extensibility)

新增一个子类,不需要修改现有代码,只要继承并重写虚函数即可。

→ 典型的“对扩展开放,对修改关闭”(开闭原则)。

  1. 灵活性(Flexibility)

同一接口调用,不同对象响应不同 → “一招多用”。

  1. 简化代码(Maintainability)

避免大量 if-else 判断类型:

// ❌ 没有多态时
if (type == "circle") drawCircle();
else if (type == "square") drawSquare();

// ✅ 有多态时
Shape* s = new Circle();
s->draw();

3.final标识符的作用是什么?

  1. final 用在类上

作用

示例

class Animal final {
public:
    void sound() { std::cout << "Some sound\n"; }
};

// ❌ 编译错误:final 类不能被继承
class Dog : public Animal {
};
  1. final 用在虚函数上

作用

示例

class Base {
public:
    virtual void show() final {  // 不能再被重写
        std::cout << "Base show\n";
    }
};

class Derived : public Base {
public:
    // ❌ 编译错误:final 函数不能再被重写
    void show() override {
        std::cout << "Derived show\n";
    }
};
  1. 为什么需要 final

4.虚函数是怎么实现的?它存放在哪里在内存的哪个区?什么时候生成的?

  1. 虚函数的实现核心:vtable(虚函数表)+ vptr(虚表指针)
  1. 内存布局示意

假设有代码:

class Base {
public:
    virtual void show() { std::cout << "Base show\n"; }
};

class Derived : public Base {
public:
    void show() override { std::cout << "Derived show\n"; }
};

int main() {
    Base* p = new Derived();
    p->show();
}

运行时,p->show() 调用流程:

1). p 是一个指向 Derived 对象的基类指针。

2). 该对象里有一个 vptr,指向 Derived 的虚函数表。

3). 在虚表里,show 的入口被填成 Derived::show

4). 所以执行的是 Derived 的版本

  1. 内存在哪儿?
  1. 什么时候生成?
  1. 直观例子(模拟打印 vtable 地址)
#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() { cout << "Base show\n"; }
};

class Derived : public Base {
public:
    void show() override { cout << "Derived show\n"; }
};

int main() {
    Base* p = new Derived();

    cout << "对象地址: " << p << endl;
    cout << "虚表指针(vptr)地址: " << *((void**)p) << endl;
    cout << "虚函数入口地址: " << *((void**)(*((void**)p))) << endl;

    p->show();  // 实际调用 Derived::show
}

5.智能指针的本质是什么,它们的实现原理是什么?

智能指针本质是一个封装了一个原始C++指针的类模板,为了确保动态内存的安全性而产生的。实现原理是通过一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。

  1. 智能指针的本质

一句话:智能指针就是带有自动回收功能的指针包装类

  1. 实现原理(核心思想)

(1) RAII

(2) 内部封装

(3) 常见实现方式

  1. 常见智能指针种类(C++11)

1). unique_ptr

2). shared_ptr

3). weak_ptr

  1. 简单模拟实现

(1) 模拟 unique_ptr

template <typename T>
class UniquePtr {
private:
    T* ptr;
public:
    explicit UniquePtr(T* p = nullptr) : ptr(p) {}
    ~UniquePtr() { delete ptr; }

    // 禁止拷贝
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;

    // 允许移动
    UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }
    UniquePtr& operator=(UniquePtr&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }

    T& operator*() { return *ptr; }
    T* operator->() { return ptr; }
};

(2) 模拟 shared_ptr(引用计数)

template <typename T>
class SharedPtr {
private:
    T* ptr;
    int* ref_count;

public:
    explicit SharedPtr(T* p = nullptr) : ptr(p), ref_count(new int(1)) {}

    ~SharedPtr() {
        if (--(*ref_count) == 0) {
            delete ptr;
            delete ref_count;
        }
    }

    SharedPtr(const SharedPtr& other) {
        ptr = other.ptr;
        ref_count = other.ref_count;
        ++(*ref_count);
    }

    SharedPtr& operator=(const SharedPtr& other) {
        if (this != &other) {
            if (--(*ref_count) == 0) {
                delete ptr;
                delete ref_count;
            }
            ptr = other.ptr;
            ref_count = other.ref_count;
            ++(*ref_count);
        }
        return *this;
    }

    T& operator*() { return *ptr; }
    T* operator->() { return ptr; }
};

6.匿名函数的本质是什么?他的优点是什么?