前置知识: C++

C++ 面向对象基础

6 minIntermediate

类与对象、封装、继承、多态与虚函数。

1. 与对象 (Class & Object)

1.1 的定义

是面向对象编程的基本单位,它封装了数据和操作数据的方法。

 class Person {
 private:
  // 私有成员变量
  std::string name;
  int age;
 public:
  // 构造函数
  Person() : name(""), age(0) {}
  Person(std::string n, int a) : name(n), age(a) {}
  // 成员方法
  void setName(std::string n) { name = n; }
  void setAge(int a) { age = a; }
  std::string getName() const { return name; }
  int getAge() const { return age; }
  // 成员方法
  void introduce() const {
  std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
  }
 }

1.2 对象的创建与使用

 int main() {
  // 栈上创建对象
  Person person1; // 默认构造函数
  person1.setName("Alice");
  person1.setAge(25);
  person1.introduce();
  // 栈上创建对象(带参数)
  Person person2("Bob", 30);
  person2.introduce();
  // 堆上创建对象
  Person* person3 = new Person("Charlie", 35);
  person3->introduce();
  delete person3; // 释放堆内存
  return 0;
 }

1.3 的成员

成员描述访问权限
成员变量存储对象状态public, private, protected
成员函数操作对象状态public, private, protected
构造函数初始化对象public
析构函数清理对象资源public
静态成员属于,而非对象public, private, protected
友元允许外部访问私有成员-

2. 封装 (Encapsulation)

封装是将数据和操作数据的方法捆绑在一起,对外部隐藏实现细节。

2.1 访问修饰符

修饰符访问权限描述
public公共外部可访问
private私有内访问
protected保护及派生访问

2.2 封装示例

 class BankAccount {
 private:
  // 私有成员变量,外部不可直接访问
  std::string accountNumber;
  double balance;
 public:
  // 构造函数
  BankAccount(std::string accNum, double initialBalance) :
  accountNumber(accNum), balance(initialBalance) {}
  // 公共接口
  void deposit(double amount) {
  if (amount > 0) {
  balance += amount;
  std::cout << "Deposited: $" << amount << std::endl;
  }
  }
  void withdraw(double amount) {
  if (amount > 0 && amount <= balance) {
  balance -= amount;
  std::cout << "Withdrawn: $" << amount << std::endl;
  } else {
  std::cout << "Insufficient funds" << std::endl;
  }
  }
  double getBalance() const {
  return balance;
  }
  std::string getAccountNumber() const {
  return accountNumber;
  }
 }
 // 使用示例
 int main() {
  BankAccount acc("123456", 1000.0);
  acc.deposit(500.0);
  acc.withdraw(200.0);
  std::cout << "Balance: $" << acc.getBalance() << std::endl;
  return 0;
 }

3. 继承 (Inheritance)

继承是一种创建新的方式,新继承现有的属性和方法。

3.1 继承的基本语法

 // 基类
 class Animal {
 protected:
  std::string name;
 public:
  Animal(std::string n) : name(n) {}
  virtual void makeSound() {
  std::cout << "Generic animal sound" << std::endl;
  }
  virtual ~Animal() {}
 }
 // 派生类
 class Dog : public Animal {
 public:
  Dog(std::string n) : Animal(n) {}
  // 覆盖基类方法
  void makeSound() override {
  std::cout << name << " barks: Woof! Woof!" << std::endl;
  }
 }
 // 派生类
 class Cat : public Animal {
 public:
  Cat(std::string n) : Animal(n) {}
  // 覆盖基类方法
  void makeSound() override {
  std::cout << name << " meows: Meow! Meow!" << std::endl;
  }
 }

3.2 继承

继承成员在派生中的访问权限
public保持基成员的访问权限
protected的 public 和 protected 成员变为 protected
private的所有成员变为 private

3.3 多继承

C++ 支持多继承,一个可以从多个基继承。

 // 基类 1
 class Printable {
 public:
  virtual void print() const = 0; // 纯虚函数
 }
 // 基类 2
 class Serializable {
 public:
  virtual std::string serialize() const = 0; // 纯虚函数
 }
 // 派生类,多继承
 class Person : public Printable, public Serializable {
 private:
  std::string name;
  int age;
 public:
  Person(std::string n, int a) : name(n), age(a) {}
  // 实现 Printable 接口
  void print() const override {
  std::cout << "Name: " << name << ", Age: " << age << std::endl;
  }
  // 实现 Serializable 接口
  std::string serialize() const override {
  return "{\"name\": \"" + name + "\", \"age\": " + std::to_string(age) + "}";
  }
 }

3.4 菱形继承问题

菱形继承是多继承中的一个问题,当两个派生继承自同一个基,而另一个又同时继承这两个派生时,会导致基成员的重复。

 // 基类
 class Animal {
 public:
  Animal() { std::cout << "Animal constructor" << std::endl; }
  ~Animal() { std::cout << "Animal destructor" << std::endl; }
 }
 // 派生类 1
 class Mammal : public Animal {
 public:
  Mammal() { std::cout << "Mammal constructor" << std::endl; }
  ~Mammal() { std::cout << "Mammal destructor" << std::endl; }
 }
 // 派生类 2
 class Bird : public Animal {
 public:
  Bird() { std::cout << "Bird constructor" << std::endl; }
  ~Bird() { std::cout << "Bird destructor" << std::endl; }
 }
 // 派生类 3,多继承
 class Bat : public Mammal, public Bird {
 public:
  Bat() { std::cout << "Bat constructor" << std::endl; }
  ~Bat() { std::cout << "Bat destructor" << std::endl; }
 }
 // 问题:Bat 会有两个 Animal 子对象
 // 解决方案:使用虚继承

3.5 虚继承

虚继承可以解决菱形继承问题,确保基只被继承一次。

 // 基类
 class Animal {
 public:
  Animal() { std::cout << "Animal constructor" << std::endl; }
  ~Animal() { std::cout << "Animal destructor" << std::endl; }
 }
 // 派生类 1,虚继承
 class Mammal : virtual public Animal {
 public:
  Mammal() { std::cout << "Mammal constructor" << std::endl; }
  ~Mammal() { std::cout << "Mammal destructor" << std::endl; }
 }
 // 派生类 2,虚继承
 class Bird : virtual public Animal {
 public:
  Bird() { std::cout << "Bird constructor" << std::endl; }
  ~Bird() { std::cout << "Bird destructor" << std::endl; }
 }
 // 派生类 3,多继承
 class Bat : public Mammal, public Bird {
 public:
  Bat() { std::cout << "Bat constructor" << std::endl; }
  ~Bat() { std::cout << "Bat destructor" << std::endl; }
 }
 // 现在 Bat 只有一个 Animal 子对象

4. 多态 (Polymorphism)

多态是指同一操作作用于不同的对象时,会产生不同的行为。C++ 支持两种型的多态:静态多态和动态多态。

4.1 静态多态

静态多态是在编译时确定的,通过函数重载和模板实现。

4.1.1 函数重载

函数重载是指在同一作用域内定义多个同名函数,但它们的参数列表不同(参数型、参数个数或参数顺序不同)。

 class Calculator {
 public:
  // 重载:参数类型不同
  int add(int a, int b) {
  return a + b;
  }
  double add(double a, double b) {
  return a + b;
  }
  // 重载:参数个数不同
  int add(int a, int b, int c) {
  return a + b + c;
  }
  // 重载:参数顺序不同
  void print(int a, double b) {
  std::cout << "int: " << a << ", double: " << b << std::endl;
  }
  void print(double a, int b) {
  std::cout << "double: " << a << ", int: " << b << std::endl;
  }
 }
 // 使用示例
 int main() {
  Calculator calc;
  std::cout << "add(int, int): " << calc.add(1, 2) << std::endl;
  std::cout << "add(double, double): " << calc.add(1.5, 2.5) << std::endl;
  std::cout << "add(int, int, int): " << calc.add(1, 2, 3) << std::endl;
  calc.print(1, 2.5);
  calc.print(1.5, 2);
  return 0;
 }

4.1.2 模板

模板是 C++ 支持泛型编程的核心机制,通过模板可以编写通用的函数和

 // 函数模板
 template <typename T>
 T add(T a, T b) {
  return a + b;
 }
 // 类模板
 template <typename T>
 class Stack {
 private:
  std::vector<T> elements;
 public:
  void push(T element) {
  elements.push_back(element);
  }
  T pop() {
  if (elements.empty()) {
  throw std::runtime_error("Stack is empty");
  }
  T top = elements.back();
  elements.pop_back();
  return top;
  }
  bool empty() const {
  return elements.empty();
  }
  size_t size() const {
  return elements.size();
  }
 }
 // 使用示例
 int main() {
  // 使用函数模板
  int i = add(10, 20);
  double d = add(3.14, 2.71);
  std::string s = add(std::string("Hello"), std::string(" World"));
  std::cout << "add(int, int): " << i << std::endl;
  std::cout << "add(double, double): " << d << std::endl;
  std::cout << "add(string, string): " << s << std::endl;
  // 使用类模板
  Stack<int> intStack;
  intStack.push(1);
  intStack.push(2);
  intStack.push(3);
  while (!intStack.empty()) {
  std::cout << intStack.pop() << " ";
  }
  std::cout << std::endl;
  Stack<std::string> stringStack;
  stringStack.push("Hello");
  stringStack.push("World");
  while (!stringStack.empty()) {
  std::cout << stringStack.pop() << " ";
  }
  std::cout << std::endl;
  return 0;
 }

4.2 动态多态

动态多态是在运行时确定的,通过虚函数实现。

 // 基类
 class Shape {
 public:
  virtual void draw() const {
  std::cout << "Drawing a shape" << std::endl;
  }
  virtual double area() const = 0; // 纯虚函数
  virtual ~Shape() {}
 }
 // 派生类
 class Circle : public Shape {
 private:
  double radius;
 public:
  Circle(double r) : radius(r) {}
  void draw() const override {
  std::cout << "Drawing a circle" << std::endl;
  }
  double area() const override {
  return M_PI * radius * radius;
  }
 }
 // 派生类
 class Rectangle : public Shape {
 private:
  double width;
  double height;
 public:
  Rectangle(double w, double h) : width(w), height(h) {}
  void draw() const override {
  std::cout << "Drawing a rectangle" << std::endl;
  }
  double area() const override {
  return width * height;
  }
 }
 // 派生类
 class Triangle : public Shape {
 private:
  double base;
  double height;
 public:
  Triangle(double b, double h) : base(b), height(h) {}
  void draw() const override {
  std::cout << "Drawing a triangle" << std::endl;
  }
  double area() const override {
  return 0.5 * base * height;
  }
 }
 // 使用多态
 void printShapeInfo(const Shape& shape) {
  shape.draw();
  std::cout << "Area: " << shape.area() << std::endl;
 }
 int main() {
  Circle circle(5.0);
  Rectangle rectangle(4.0, 6.0);
  Triangle triangle(3.0, 8.0);
  printShapeInfo(circle); // 传递 Circle 对象
  printShapeInfo(rectangle); // 传递 Rectangle 对象
  printShapeInfo(triangle); // 传递 Triangle 对象
  // 使用基类指针数组
  Shape* shapes[3];
  shapes[0] = new Circle(2.0);
  shapes[1] = new Rectangle(3.0, 4.0);
  shapes[2] = new Triangle(5.0, 6.0);
  for (int i = 0; i < 3; i++) {
  printShapeInfo(*shapes[i]);
  delete shapes[i];
  }
  return 0;
 }

4.3 虚函数纯虚函数

4.3.1 虚函数

虚函数是在基中声明的,允许派生覆盖的函数。

 class Base {
 public:
  virtual void func() {
  std::cout << "Base::func()" << std::endl;
  }
  virtual ~Base() {}
 }
 class Derived : public Base {
 public:
  void func() override {
  std::cout << "Derived::func()" << std::endl;
  }
 }
 int main() {
  Base* b = new Derived();
  b->func(); // 调用 Derived::func()
  delete b;
  return 0;
 }

4.3.2 纯虚函数

纯虚函数是在基中声明的,没有实现的虚函数,派生必须实现它。

 class AbstractShape {
 public:
  virtual void draw() const = 0; // 纯虚函数
  virtual double area() const = 0; // 纯虚函数
  virtual ~AbstractShape() {}
 }
 class Square : public AbstractShape {
 private:
  double side;
 public:
  Square(double s) : side(s) {}
  void draw() const override {
  std::cout << "Drawing a square" << std::endl;
  }
  double area() const override {
  return side * side;
  }
 }
 int main() {
  // AbstractShape shape; // 错误:不能实例化抽象类
  Square square(5.0);
  square.draw();
  std::cout << "Area: " << square.area() << std::endl;
  return 0;
 }

4.4 多态的实现原理

多态的实现依赖于虚函数表(VTable)和虚指针(vptr)。

  1. 虚函数表(VTable):每个包含虚函数都有一个虚函数表,存储了该所有虚函数的地址。
  2. 虚指针(vptr):每个对象都有一个虚指针,指向该虚函数表。
  3. 运行时绑定:当通过基指针引用调用虚函数时,会通过虚指针找到虚函数表,然后调用相应的函数。
 // 基类
 class Base {
 public:
  virtual void func1() { std::cout << "Base::func1()" << std::endl; }
  virtual void func2() { std::cout << "Base::func2()" << std::endl; }
  void nonVirtual() { std::cout << "Base::nonVirtual()" << std::endl; }
 }
 // 派生类
 class Derived : public Base {
 public:
  void func1() override { std::cout << "Derived::func1()" << std::endl; }
  // func2() 继承自 Base
  void nonVirtual() { std::cout << "Derived::nonVirtual()" << std::endl; }
 }
 int main() {
  Base* b = new Derived();
  b->func1(); // 调用 Derived::func1()(多态,运行时绑定)
  b->func2(); // 调用 Base::func2()(多态,运行时绑定)
  b->nonVirtual(); // 调用 Base::nonVirtual()(非虚函数,编译时绑定)
  delete b;
  return 0;
 }

5. 虚函数虚函数表 (VTable)

5.1 虚函数

虚函数是在基中声明的,允许派生覆盖的函数。虚函数是实现动态多态的核心机制。

 class Base {
 public:
  virtual void func() {
  std::cout << "Base::func()" << std::endl;
  }
  virtual void anotherFunc() {
  std::cout << "Base::anotherFunc()" << std::endl;
  }
  virtual ~Base() {}
 }
 class Derived : public Base {
 public:
  void func() override {
  std::cout << "Derived::func()" << std::endl;
  }
  // anotherFunc() 继承自 Base
 }
 class Derived2 : public Base {
 public:
  void func() override {
  std::cout << "Derived2::func()" << std::endl;
  }
  void anotherFunc() override {
  std::cout << "Derived2::anotherFunc()" << std::endl;
  }
 }
 // 使用示例
 int main() {
  Base* b1 = new Base();
  Base* b2 = new Derived();
  Base* b3 = new Derived2();
  b1->func(); // Base::func()
  b1->anotherFunc(); // Base::anotherFunc()
  b2->func(); // Derived::func()
  b2->anotherFunc(); // Base::anotherFunc()
  b3->func(); // Derived2::func()
  b3->anotherFunc(); // Derived2::anotherFunc()
  delete b1;
  delete b2;
  delete b3;
  return 0;
 }

5.2 虚函数

虚函数表(VTable)是实现动态多态的关键机制。

  • 虚函数表:每个包含虚函数都有一个虚函数表,存储了该所有虚函数的地址。
  • 虚指针(vptr):每个对象都有一个虚指针,指向该虚函数表。
  • 运行时绑定:当通过基指针引用调用虚函数时,会通过虚指针找到虚函数表,然后调用相应的函数。

5.3 虚函数表原理

 // 基类
 class Base {
 public:
  virtual void func1() { std::cout << "Base::func1()" << std::endl; }
  virtual void func2() { std::cout << "Base::func2()" << std::endl; }
  void nonVirtual() { std::cout << "Base::nonVirtual()" << std::endl; }
 }
 // 派生类
 class Derived : public Base {
 public:
  void func1() override { std::cout << "Derived::func1()" << std::endl; }
  // func2() 继承自 Base
  void nonVirtual() { std::cout << "Derived::nonVirtual()" << std::endl; }
 }
 int main() {
  Base* b = new Derived();
  b->func1(); // 调用 Derived::func1()(多态,运行时绑定)
  b->func2(); // 调用 Base::func2()(多态,运行时绑定)
  b->nonVirtual(); // 调用 Base::nonVirtual()(非虚函数,编译时绑定)
  delete b;
  return 0;
 }

5.4 虚函数表的结构

虚函数表是一个函数指针数组,存储了虚函数地址。当派生覆盖基虚函数时,会在自己的虚函数表中替换对应的函数指针

 // 基类虚函数表
 // Base VTable:
 // [0] -> Base::func1()
 // [1] -> Base::func2()
 // 派生类虚函数表
 // Derived VTable:
 // [0] -> Derived::func1() // 覆盖基类的func1()
 // [1] -> Base::func2() // 继承基类的func2()

5.5 虚函数的性能影响

虚函数调用比普通函数调用慢,因为需要通过虚指针虚函数表进行间接调用。但这种开销通常很小,对于大多数应用来说可以忽略不计。

5.6 虚函数的使用注意事项

  1. 虚析构函数:基析构函数应该声明为虚函数,以确保派生析构函数被正确调用。
  2. 虚函数与构造函数构造函数不能是虚函数,因为在构造对象时,虚函数表还未完全建立。
  3. 虚函数与内联函数虚函数通常不能被内联,因为需要运行时绑定。
  4. 虚函数与静态函数:静态函数不能是虚函数,因为静态函数属于而不是对象。
  5. 虚函数与私有成员:私有虚函数也可以被派生覆盖,但派生无法直接调用基的私有虚函数
 class Base {
 private:
  virtual void privateFunc() {
  std::cout << "Base::privateFunc()" << std::endl;
  }
 public:
  void publicFunc() {
  privateFunc(); // 可以调用私有虚函数
  }
 }
 class Derived : public Base {
 private:
  void privateFunc() override {
  std::cout << "Derived::privateFunc()" << std::endl;
  }
 }
 int main() {
  Base* b = new Derived();
  b->publicFunc(); // 调用 Derived::privateFunc()
  delete b;
  return 0;
 }

更新日志 (Changelog)

  • 2026-05-27: 从 C13_104 拆分,专注于面向对象基础(与对象、封装、继承、多态、虚函数)。