当前位置: 首页 > article >正文

C++学习:六个月从基础到就业——面向对象编程:封装、继承与多态

C++学习:六个月从基础到就业——面向对象编程:封装、继承与多态

本文是我C++学习之旅系列的第九篇技术文章,主要讨论C++中面向对象编程的三大核心特性:封装、继承与多态。这些概念是理解和应用面向对象设计的关键。查看完整系列目录了解更多内容。

引言

面向对象编程(Object-Oriented Programming,简称OOP)是现代程序设计中的主流编程范式,它将数据和操作数据的函数打包成被称为"对象"的单元,通过对象之间的交互来设计应用程序。C++作为一种多范式的编程语言,提供了强大的面向对象编程支持。

面向对象编程的三大基本特性是封装、继承和多态。这些特性使得代码更加模块化、可重用和易于维护。本文将详细介绍这三大特性在C++中的实现和应用。

封装(Encapsulation)

什么是封装?

封装是面向对象编程的首要特性,它指的是将数据(属性)和操作数据的方法(函数)绑定在一起,形成一个独立的单元,并对外部隐藏实现细节,仅暴露必要的接口。

封装的主要目的是:

  • 保护数据不被外部直接访问和修改
  • 隐藏实现细节,提供清晰的接口
  • 降低代码的耦合度,提高模块化程度

C++中的封装实现

在C++中,封装主要通过类(class)和访问修饰符(access specifiers)来实现:

class BankAccount {
private:// 私有成员,外部无法直接访问std::string accountNumber;double balance;std::string ownerName;public:// 公共接口,外部可以访问BankAccount(const std::string& number, const std::string& name, double initialBalance): accountNumber(number), ownerName(name), balance(initialBalance) {}void deposit(double amount) {if (amount > 0) {balance += amount;}}bool withdraw(double amount) {if (amount > 0 && balance >= amount) {balance -= amount;return true;}return false;}double getBalance() const {return balance;}std::string getAccountInfo() const {return "Account: " + accountNumber + ", Owner: " + ownerName;}
};

在这个例子中:

  • accountNumberbalanceownerName是私有成员变量,外部代码无法直接访问和修改
  • depositwithdrawgetBalancegetAccountInfo是公共方法,提供了安全访问和操作内部数据的接口

访问修饰符

C++提供了三种访问修饰符:

  1. private(私有)

    • 只能被类内部的成员函数访问
    • 是类的默认访问级别
    • 派生类不能访问基类的私有成员
  2. protected(受保护)

    • 可以被类内部的成员函数访问
    • 可以被派生类的成员函数访问
    • 外部代码不能直接访问
  3. public(公共)

    • 可以被任何代码访问
    • 通常用来定义类的接口
class Example {
private:int privateVar;    // 只有类内部可访问protected:int protectedVar;  // 类内部和派生类可访问public:int publicVar;     // 任何代码都可访问
};

getters与setters

为了遵循封装原则,通常使用getter和setter方法来控制对私有成员变量的访问:

class Person {
private:std::string name;int age;public:// Getter - 不修改数据,通常声明为conststd::string getName() const {return name;}int getAge() const {return age;}// Setter - 可以加入验证逻辑void setName(const std::string& newName) {if (!newName.empty()) {name = newName;}}void setAge(int newAge) {if (newAge >= 0 && newAge <= 120) {  // 基本合理性检查age = newAge;}}
};

通过getter和setter,我们可以:

  • 添加验证逻辑,确保数据的有效性
  • 控制属性的读写权限(只读、只写或读写)
  • 在不改变接口的情况下修改内部实现
  • 添加额外逻辑(如日志记录、通知等)

封装的好处

  1. 数据保护:防止外部代码意外或恶意修改对象的内部状态
  2. 实现隐藏:可以改变内部实现而不影响外部代码
  3. 耦合度降低:对象之间通过定义清晰的接口进行交互,降低了依赖程度
  4. 提高可维护性:系统更易于维护和扩展

继承(Inheritance)

什么是继承?

继承是面向对象编程中的第二个重要特性,它允许我们基于现有类(基类或父类)创建新的类(派生类或子类)。派生类继承基类的特性(属性和方法),可以重用基类的代码,同时可以添加新的特性或修改继承的行为。

继承建立了类之间的层次关系,体现了"is-a"(是一个)的关系,如"小轿车是一种车",“猫是一种动物”。

C++中的继承实现

在C++中,继承通过在类声明时指定基类来实现:

// 基类
class Animal {
protected:std::string name;int age;public:Animal(const std::string& n, int a) : name(n), age(a) {}void eat() {std::cout << name << " is eating." << std::endl;}void sleep() {std::cout << name << " is sleeping." << std::endl;}std::string getName() const {return name;}int getAge() const {return age;}
};// 派生类
class Dog : public Animal {
private:std::string breed;public:Dog(const std::string& n, int a, const std::string& b): Animal(n, a), breed(b) {}void bark() {std::cout << name << " is barking." << std::endl;}std::string getBreed() const {return breed;}
};

在这个例子中:

  • Dog是派生自Animal的子类
  • Dog继承了Animalnameage属性,以及eatsleepgetNamegetAge方法
  • Dog添加了自己的属性breed和方法barkgetBreed

使用示例:

int main() {Dog myDog("Rex", 3, "German Shepherd");// 使用继承自基类的方法std::cout << myDog.getName() << " is " << myDog.getAge() << " years old." << std::endl;myDog.eat();myDog.sleep();// 使用派生类特有的方法std::cout << "Breed: " << myDog.getBreed() << std::endl;myDog.bark();return 0;
}

继承的类型

C++支持三种继承方式:

  1. 公有继承(public)

    • 基类的公有成员在派生类中仍是公有的
    • 基类的保护成员在派生类中仍是保护的
    • 基类的私有成员对派生类不可访问
    • 表示"是一个"(is-a)关系
  2. 保护继承(protected)

    • 基类的公有和保护成员在派生类中变为保护成员
    • 基类的私有成员对派生类不可访问
    • 表示"基于"(is-implemented-in-terms-of)关系,但派生类可能不想公开这种关系
  3. 私有继承(private)

    • 基类的公有和保护成员在派生类中变为私有成员
    • 基类的私有成员对派生类不可访问
    • 表示"使用"(is-implemented-in-terms-of)关系,实现方式而非接口继承
class Base {
public:void publicFunc() {}
protected:void protectedFunc() {}
private:void privateFunc() {}
};class PublicDerived : public Base {// publicFunc 在这里是 public// protectedFunc 在这里是 protected// privateFunc 不可访问
};class ProtectedDerived : protected Base {// publicFunc 在这里是 protected// protectedFunc 在这里是 protected// privateFunc 不可访问
};class PrivateDerived : private Base {// publicFunc 在这里是 private// protectedFunc 在这里是 private// privateFunc 不可访问
};

构造函数和析构函数的调用顺序

在继承关系中,构造函数和析构函数的调用顺序非常重要:

构造顺序

  1. 基类构造函数
  2. 派生类构造函数

析构顺序

  1. 派生类析构函数
  2. 基类析构函数
class Base {
public:Base() { std::cout << "Base constructor" << std::endl; }~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; }
};int main() {Derived d;return 0;
}// 输出:
// Base constructor
// Derived constructor
// Derived destructor
// Base destructor

多重继承

C++支持多重继承,即一个类可以继承多个基类:

class Engine {
public:void start() { std::cout << "Engine started" << std::endl; }void stop() { std::cout << "Engine stopped" << std::endl; }
};class Vehicle {
public:void accelerate() { std::cout << "Accelerating" << std::endl; }void brake() { std::cout << "Braking" << std::endl; }
};class Car : public Engine, public Vehicle {
public:void drive() {start();        // 从Engine继承accelerate();   // 从Vehicle继承}void park() {brake();        // 从Vehicle继承stop();         // 从Engine继承}
};

虽然多重继承功能强大,但也容易导致设计复杂和歧义问题(如菱形继承)。一般建议谨慎使用,优先考虑组合而非多重继承。

菱形继承和虚拟继承

菱形继承是多重继承中的一个常见问题,发生在一个类通过多条继承路径继承同一个基类:

    A/ \B   C\ /D

这会导致D中有A的两份副本。为了解决这个问题,C++提供了虚拟继承:

class A {
public:int a;A() : a(10) {}
};class B : virtual public A {
public:int b;B() : b(20) {}
};class C : virtual public A {
public:int c;C() : c(30) {}
};class D : public B, public C {
public:int d;D() : d(40) {}
};int main() {D d;std::cout << d.a << std::endl;  // 如果不是虚拟继承,这里会有歧义return 0;
}

虚拟继承确保无论通过多少条路径,基类在派生类中只有一个实例。

继承的好处

  1. 代码重用:避免重复编写基类已经提供的功能
  2. 提高可扩展性:可以通过创建新的派生类来扩展系统功能
  3. 建立类层次:反映真实世界的"是一个"关系
  4. 多态性的基础:为运行时多态提供机制

多态(Polymorphism)

什么是多态?

多态是面向对象编程的第三个核心特性,它允许我们使用统一的接口处理不同类型的对象。多态使得我们可以编写依赖于抽象接口而非具体实现的代码,提高了代码的灵活性和可扩展性。

多态有两种主要形式:

  • 编译时多态(静态多态):通过函数重载和运算符重载实现
  • 运行时多态(动态多态):通过虚函数和继承实现

编译时多态(静态多态)

编译时多态是在编译阶段确定调用哪个函数,主要通过函数重载和运算符重载实现:

函数重载:同名函数但参数不同

class Math {
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;}
};

运算符重载:定义运算符的自定义行为

class Complex {
private:double real, imag;public:Complex(double r, double i) : real(r), imag(i) {}Complex operator+(const Complex& other) const {return Complex(real + other.real, imag + other.imag);}void display() const {std::cout << real << " + " << imag << "i" << std::endl;}
};

运行时多态(动态多态)

运行时多态是在程序运行时才确定调用哪个函数,主要通过虚函数机制实现:

class Shape {
public:// 虚函数,可以被派生类重写virtual double area() const {return 0;}virtual double perimeter() const {return 0;}// 虚析构函数,确保正确销毁派生类对象virtual ~Shape() {}
};class Circle : public Shape {
private:double radius;public:Circle(double r) : radius(r) {}// 重写基类的虚函数double area() const override {return 3.14159 * radius * radius;}double perimeter() const override {return 2 * 3.14159 * radius;}
};class Rectangle : public Shape {
private:double width, height;public:Rectangle(double w, double h) : width(w), height(h) {}double area() const override {return width * height;}double perimeter() const override {return 2 * (width + height);}
};

使用多态的示例:

void printDetails(const Shape& shape) {std::cout << "Area: " << shape.area() << std::endl;std::cout << "Perimeter: " << shape.perimeter() << std::endl;
}int main() {Circle circle(5);Rectangle rectangle(4, 6);printDetails(circle);     // 调用Circle的area和perimeter方法printDetails(rectangle);  // 调用Rectangle的area和perimeter方法// 使用基类指针Shape* shapes[2] = {&circle, &rectangle};for (int i = 0; i < 2; i++) {printDetails(*shapes[i]);}return 0;
}

虚函数

虚函数是C++实现运行时多态的关键机制:

class Base {
public:virtual void method() {std::cout << "Base method" << std::endl;}
};class Derived : public Base {
public:void method() override {  // override是C++11引入的关键字,帮助检查是否正确重写std::cout << "Derived method" << std::endl;}
};int main() {Base* ptr = new Derived();ptr->method();  // 输出"Derived method"delete ptr;return 0;
}

虚函数表机制

当一个类包含虚函数时,编译器会创建一个虚函数表(vtable),包含指向该类虚函数实现的指针。每个对象都包含一个指向虚函数表的指针(vptr)。这使得在运行时可以确定应该调用哪个函数的实现。

纯虚函数和抽象类

纯虚函数是声明但不定义的虚函数,使用= 0语法:

class AbstractShape {
public:// 纯虚函数virtual double area() const = 0;virtual double perimeter() const = 0;virtual ~AbstractShape() {}
};

包含至少一个纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为其他类的基类。

class Circle : public AbstractShape {
private:double radius;public:Circle(double r) : radius(r) {}double area() const override {return 3.14159 * radius * radius;}double perimeter() const override {return 2 * 3.14159 * radius;}
};// AbstractShape shape; // 错误:不能创建抽象类的实例
Circle circle(5);       // 正确:可以创建实现了所有纯虚函数的派生类实例

接口类

在C++中,接口类是一种特殊的抽象类,它只包含纯虚函数,没有成员变量和函数实现:

class Drawable {
public:virtual void draw() const = 0;virtual ~Drawable() {}
};class Movable {
public:virtual void move(double x, double y) = 0;virtual ~Movable() {}
};class Sprite : public Drawable, public Movable {
private:double x, y;std::string image;public:Sprite(double posX, double posY, const std::string& img): x(posX), y(posY), image(img) {}void draw() const override {std::cout << "Drawing sprite " << image << " at (" << x << "," << y << ")" << std::endl;}void move(double newX, double newY) override {x = newX;y = newY;std::cout << "Moved sprite to (" << x << "," << y << ")" << std::endl;}
};

接口类定义了一组功能的契约,但不提供实现,这增强了代码的灵活性和扩展性。

虚析构函数

当通过基类指针删除派生类对象时,如果基类没有虚析构函数,只会调用基类的析构函数,可能导致内存泄漏:

class Base {
public:~Base() {  // 非虚析构函数std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* ptr = new Derived();delete ptr;  // 只会调用Base的析构函数,不会调用Derived的析构函数return 0;
}

解决方法是声明虚析构函数:

class Base {
public:virtual ~Base() {  // 虚析构函数std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() override {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* ptr = new Derived();delete ptr;  // 先调用Derived的析构函数,再调用Base的析构函数return 0;
}

多态的好处

  1. 灵活性:允许编写适用于不同类型对象的代码
  2. 可扩展性:可以添加新的派生类而不修改现有代码
  3. 解耦:代码依赖于抽象接口而非具体实现
  4. 设计模式:许多设计模式(如策略、观察者、工厂等)都依赖于多态

实际应用例子

图形编辑器

下面是一个简化的图形编辑器实现,展示了封装、继承和多态的应用:

#include <iostream>
#include <vector>
#include <string>
#include <memory>// 抽象基类:形状
class Shape {
private:int id;std::string color;protected:double x, y;  // 中心坐标public:Shape(double posX, double posY, const std::string& c, int shapeId): x(posX), y(posY), color(c), id(shapeId) {}// 纯虚函数virtual void draw() const = 0;virtual void move(double newX, double newY) = 0;virtual double area() const = 0;virtual std::string getType() const = 0;// 普通虚函数virtual void scale(double factor) {std::cout << "Basic scaling for shape " << id << std::endl;}// 非虚函数int getId() const { return id; }std::string getColor() const { return color; }void setColor(const std::string& newColor) { color = newColor; }virtual ~Shape() = default;
};// 派生类:圆形
class Circle : public Shape {
private:double radius;public:Circle(double posX, double posY, double r, const std::string& c, int id): Shape(posX, posY, c, id), radius(r) {}void draw() const override {std::cout << "Drawing Circle #" << getId() << " at (" << x << "," << y << ") with radius " << radius << " and color " << getColor() << std::endl;}void move(double newX, double newY) override {x = newX;y = newY;std::cout << "Moved Circle #" << getId() << " to (" << x << "," << y << ")" << std::endl;}double area() const override {return 3.14159 * radius * radius;}std::string getType() const override {return "Circle";}void scale(double factor) override {radius *= factor;std::cout << "Scaled Circle #" << getId() << " to radius " << radius << std::endl;}
};// 派生类:矩形
class Rectangle : public Shape {
private:double width, height;public:Rectangle(double posX, double posY, double w, double h, const std::string& c, int id): Shape(posX, posY, c, id), width(w), height(h) {}void draw() const override {std::cout << "Drawing Rectangle #" << getId() << " at (" << x << "," << y << ") with width " << width << ", height " << height << " and color " << getColor() << std::endl;}void move(double newX, double newY) override {x = newX;y = newY;std::cout << "Moved Rectangle #" << getId() << " to (" << x << "," << y << ")" << std::endl;}double area() const override {return width * height;}std::string getType() const override {return "Rectangle";}void scale(double factor) override {width *= factor;height *= factor;std::cout << "Scaled Rectangle #" << getId() << " to width " << width << " and height " << height << std::endl;}
};// 派生类:三角形
class Triangle : public Shape {
private:double side1, side2, side3;public:Triangle(double posX, double posY, double s1, double s2, double s3, const std::string& c, int id): Shape(posX, posY, c, id), side1(s1), side2(s2), side3(s3) {}void draw() const override {std::cout << "Drawing Triangle #" << getId() << " at (" << x << "," << y << ") with sides " << side1 << ", " << side2 << ", " << side3 << " and color " << getColor() << std::endl;}void move(double newX, double newY) override {x = newX;y = newY;std::cout << "Moved Triangle #" << getId() << " to (" << x << "," << y << ")" << std::endl;}double area() const override {// 海伦公式double s = (side1 + side2 + side3) / 2;return std::sqrt(s * (s - side1) * (s - side2) * (s - side3));}std::string getType() const override {return "Triangle";}void scale(double factor) override {side1 *= factor;side2 *= factor;side3 *= factor;std::cout << "Scaled Triangle #" << getId() << " by factor " << factor << std::endl;}
};// 图形编辑器类
class GraphicsEditor {
private:std::vector<std::unique_ptr<Shape>> shapes;int nextId = 1;public:// 添加形状int addCircle(double x, double y, double radius, const std::string& color) {shapes.push_back(std::make_unique<Circle>(x, y, radius, color, nextId));return nextId++;}int addRectangle(double x, double y, double width, double height, const std::string& color) {shapes.push_back(std::make_unique<Rectangle>(x, y, width, height, color, nextId));return nextId++;}int addTriangle(double x, double y, double s1, double s2, double s3, const std::string& color) {shapes.push_back(std::make_unique<Triangle>(x, y, s1, s2, s3, color, nextId));return nextId++;}// 查找形状Shape* findShapeById(int id) {for (auto& shape : shapes) {if (shape->getId() == id) {return shape.get();}}return nullptr;}// 绘制所有形状void drawAll() const {std::cout << "Drawing all shapes:" << std::endl;for (const auto& shape : shapes) {shape->draw();}std::cout << std::endl;}// 移动指定形状bool moveShape(int id, double newX, double newY) {Shape* shape = findShapeById(id);if (shape) {shape->move(newX, newY);return true;}return false;}// 缩放指定形状bool scaleShape(int id, double factor) {Shape* shape = findShapeById(id);if (shape) {shape->scale(factor);return true;}return false;}// 删除指定形状bool deleteShape(int id) {for (auto it = shapes.begin(); it != shapes.end(); ++it) {if ((*it)->getId() == id) {shapes.erase(it);return true;}}return false;}// 计算所有形状的总面积double calculateTotalArea() const {double totalArea = 0;for (const auto& shape : shapes) {totalArea += shape->area();}return totalArea;}// 打印形状统计信息void printStatistics() const {int circleCount = 0, rectangleCount = 0, triangleCount = 0;for (const auto& shape : shapes) {std::string type = shape->getType();if (type == "Circle") circleCount++;else if (type == "Rectangle") rectangleCount++;else if (type == "Triangle") triangleCount++;}std::cout << "Shape Statistics:" << std::endl;std::cout << "Total shapes: " << shapes.size() << std::endl;std::cout << "Circles: " << circleCount << std::endl;std::cout << "Rectangles: " << rectangleCount << std::endl;std::cout << "Triangles: " << triangleCount << std::endl;std::cout << "Total area: " << calculateTotalArea() << std::endl;std::cout << std::endl;}
};int main() {GraphicsEditor editor;// 添加形状int circleId = editor.addCircle(100, 100, 50, "Red");int rectId = editor.addRectangle(200, 200, 80, 40, "Blue");int triangleId = editor.addTriangle(300, 300, 30, 40, 50, "Green");// 绘制所有形状editor.drawAll();// 打印统计信息editor.printStatistics();// 移动和缩放操作editor.moveShape(circleId, 150, 150);editor.scaleShape(rectId, 1.5);// 再次绘制所有形状editor.drawAll();// 删除一个形状editor.deleteShape(triangleId);std::cout << "After deleting the triangle:" << std::endl;editor.printStatistics();return 0;
}

这个例子展示了:

  • 封装:每个类都隐藏了其实现细节,只暴露必要的接口
  • 继承CircleRectangleTriangle都继承自Shape
  • 多态:通过虚函数实现,GraphicsEditor使用Shape的接口而不依赖具体实现

银行账户系统

下面是一个简单的银行账户系统,展示了另一个封装、继承和多态的应用:

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <iomanip>// 账户基类
class Account {
private:std::string accountNumber;std::string ownerName;protected:double balance;public:Account(const std::string& number, const std::string& name, double initialBalance): accountNumber(number), ownerName(name), balance(initialBalance) {}// 存款(所有账户都相同)void deposit(double amount) {if (amount > 0) {balance += amount;std::cout << "Deposited $" << amount << " into account " << accountNumber << std::endl;}}// 取款(可能在不同账户类型中有不同实现)virtual bool withdraw(double amount) {if (amount > 0 && balance >= amount) {balance -= amount;std::cout << "Withdrew $" << amount << " from account " << accountNumber << std::endl;return true;}std::cout << "Insufficient funds in account " << accountNumber << std::endl;return false;}// 获取余额double getBalance() const {return balance;}// 获取账户信息std::string getAccountNumber() const {return accountNumber;}std::string getOwnerName() const {return ownerName;}// 账户类型(在派生类中重写)virtual std::string getAccountType() const {return "Generic Account";}// 月末处理(在派生类中重写)virtual void monthEndUpdate() {// 基类中无操作}// 打印账户信息virtual void printDetails() const {std::cout << "Account Type: " << getAccountType() << std::endl;std::cout << "Account Number: " << accountNumber << std::endl;std::cout << "Owner: " << ownerName << std::endl;std::cout << "Balance: $" << std::fixed << std::setprecision(2) << balance << std::endl;}virtual ~Account() = default;
};// 储蓄账户
class SavingsAccount : public Account {
private:double interestRate;  // 年利率public:SavingsAccount(const std::string& number, const std::string& name, double initialBalance, double rate): Account(number, name, initialBalance), interestRate(rate) {}std::string getAccountType() const override {return "Savings Account";}// 月末处理 - 添加利息void monthEndUpdate() override {double interest = balance * (interestRate / 12);  // 月利息balance += interest;std::cout << "Added $" << std::fixed << std::setprecision(2) << interest << " interest to account " << getAccountNumber() << std::endl;}void printDetails() const override {Account::printDetails();std::cout << "Interest Rate: " << std::fixed << std::setprecision(2) << (interestRate * 100) << "%" << std::endl;}double getInterestRate() const {return interestRate;}void setInterestRate(double rate) {if (rate >= 0) {interestRate = rate;}}
};// 支票账户
class CheckingAccount : public Account {
private:double transactionFee;  // 交易费int freeTransactionsPerMonth;  // 每月免费交易次数int transactionCount;  // 本月交易计数public:CheckingAccount(const std::string& number, const std::string& name,double initialBalance, double fee, int freeTrans): Account(number, name, initialBalance), transactionFee(fee), freeTransactionsPerMonth(freeTrans), transactionCount(0) {}std::string getAccountType() const override {return "Checking Account";}bool withdraw(double amount) override {bool success = Account::withdraw(amount);if (success) {transactionCount++;if (transactionCount > freeTransactionsPerMonth) {balance -= transactionFee;std::cout << "Applied transaction fee of $" << transactionFee << " to account " << getAccountNumber() << std::endl;}}return success;}// 月末处理 - 重置交易计数void monthEndUpdate() override {transactionCount = 0;std::cout << "Reset transaction count for account " << getAccountNumber() << std::endl;}void printDetails() const override {Account::printDetails();std::cout << "Transaction Fee: $" << std::fixed << std::setprecision(2) << transactionFee << std::endl;std::cout << "Free Transactions: " << freeTransactionsPerMonth << " per month" << std::endl;std::cout << "Transactions this month: " << transactionCount << std::endl;}
};// 信用卡账户
class CreditCardAccount : public Account {
private:double creditLimit;double interestRate;  // 年利率public:CreditCardAccount(const std::string& number, const std::string& name,double initialBalance, double limit, double rate): Account(number, name, initialBalance), creditLimit(limit), interestRate(rate) {}std::string getAccountType() const override {return "Credit Card Account";}bool withdraw(double amount) override {if (amount > 0 && (balance - amount) >= -creditLimit) {balance -= amount;std::cout << "Charged $" << amount << " to credit card " << getAccountNumber() << std::endl;return true;}std::cout << "Credit limit exceeded for account " << getAccountNumber() << std::endl;return false;}// 月末处理 - 添加利息(仅对负余额)void monthEndUpdate() override {if (balance < 0) {double interest = -balance * (interestRate / 12);  // 月利息balance -= interest;std::cout << "Added $" << std::fixed << std::setprecision(2) << interest << " interest to credit card " << getAccountNumber() << std::endl;}}void printDetails() const override {Account::printDetails();std::cout << "Credit Limit: $" << std::fixed << std::setprecision(2) << creditLimit << std::endl;std::cout << "Interest Rate: " << std::fixed << std::setprecision(2) << (interestRate * 100) << "%" << std::endl;std::cout << "Available Credit: $" << std::fixed << std::setprecision(2) << (creditLimit + std::min(balance, 0.0)) << std::endl;}double getCreditLimit() const {return creditLimit;}void setCreditLimit(double limit) {if (limit >= 0) {creditLimit = limit;}}
};// 银行系统
class BankSystem {
private:std::vector<std::unique_ptr<Account>> accounts;public:// 创建储蓄账户void createSavingsAccount(const std::string& number, const std::string& name,double initialBalance, double interestRate) {accounts.push_back(std::make_unique<SavingsAccount>(number, name, initialBalance, interestRate));std::cout << "Created savings account " << number << " for " << name << std::endl;}// 创建支票账户void createCheckingAccount(const std::string& number, const std::string& name,double initialBalance, double fee, int freeTrans) {accounts.push_back(std::make_unique<CheckingAccount>(number, name, initialBalance, fee, freeTrans));std::cout << "Created checking account " << number << " for " << name << std::endl;}// 创建信用卡账户void createCreditCardAccount(const std::string& number, const std::string& name,double initialBalance, double limit, double rate) {accounts.push_back(std::make_unique<CreditCardAccount>(number, name, initialBalance, limit, rate));std::cout << "Created credit card account " << number << " for " << name << std::endl;}// 查找账户Account* findAccount(const std::string& accountNumber) {for (auto& account : accounts) {if (account->getAccountNumber() == accountNumber) {return account.get();}}return nullptr;}// 存款bool deposit(const std::string& accountNumber, double amount) {Account* account = findAccount(accountNumber);if (account) {account->deposit(amount);return true;}std::cout << "Account " << accountNumber << " not found" << std::endl;return false;}// 取款bool withdraw(const std::string& accountNumber, double amount) {Account* account = findAccount(accountNumber);if (account) {return account->withdraw(amount);}std::cout << "Account " << accountNumber << " not found" << std::endl;return false;}// 打印账户详情bool printAccountDetails(const std::string& accountNumber) {Account* account = findAccount(accountNumber);if (account) {account->printDetails();return true;}std::cout << "Account " << accountNumber << " not found" << std::endl;return false;}// 打印所有账户void printAllAccounts() const {std::cout << "All Bank Accounts:" << std::endl;std::cout << "==================" << std::endl;if (accounts.empty()) {std::cout << "No accounts found." << std::endl;} else {for (const auto& account : accounts) {account->printDetails();std::cout << "==================" << std::endl;}}}// 月末处理所有账户void monthEndProcessing() {std::cout << "Performing month-end processing..." << std::endl;for (auto& account : accounts) {account->monthEndUpdate();}std::cout << "Month-end processing complete." << std::endl;}// 账户总余额double getTotalBalance() const {double total = 0;for (const auto& account : accounts) {total += account->getBalance();}return total;}
};int main() {BankSystem bank;// 创建账户bank.createSavingsAccount("SAV001", "John Doe", 1000.0, 0.03);bank.createCheckingAccount("CHK001", "Jane Smith", 2000.0, 1.5, 5);bank.createCreditCardAccount("CC001", "Alice Brown", 0.0, 5000.0, 0.18);// 打印所有账户std::cout << std::endl;bank.printAllAccounts();// 进行一些交易std::cout << std::endl << "Performing transactions:" << std::endl;bank.deposit("SAV001", 500.0);bank.withdraw("CHK001", 100.0);bank.withdraw("CHK001", 200.0);bank.withdraw("CHK001", 300.0);bank.withdraw("CHK001", 400.0);bank.withdraw("CHK001", 500.0);  // 这将产生交易费bank.withdraw("CHK001", 600.0);  // 这将产生交易费bank.withdraw("CC001", 2000.0);// 打印账户详情std::cout << std::endl << "Updated Account Details:" << std::endl;bank.printAccountDetails("SAV001");std::cout << std::endl;bank.printAccountDetails("CHK001");std::cout << std::endl;bank.printAccountDetails("CC001");// 月末处理std::cout << std::endl;bank.monthEndProcessing();// 打印更新后的账户详情std::cout << std::endl << "Account Details After Month-End Processing:" << std::endl;bank.printAllAccounts();// 打印总余额std::cout << "Total balance of all accounts: $" << std::fixed << std::setprecision(2) << bank.getTotalBalance() << std::endl;return 0;
}

这个例子展示了:

  • 封装:每个账户类封装了其数据和行为,隐藏了实现细节
  • 继承SavingsAccountCheckingAccountCreditCardAccountAccount继承
  • 多态:通过虚函数,BankSystem可以统一处理不同类型的账户

最佳实践

封装最佳实践

  1. 保持数据私有

    • 将成员变量声明为private
    • 通过公共方法提供对私有数据的访问
  2. 设计清晰的接口

    • 提供完整但最小化的公共接口
    • 使用有意义的命名和注释
  3. 验证输入

    • 在setter中验证输入数据的有效性
    • 避免对象陷入无效状态
  4. 遵循"最少知识"原则

    • 类只应该与其直接合作者交互
    • 避免"训练车"式的调用链(a.getB().getC().getD()…)

继承最佳实践

  1. 优先使用组合而非继承

    • 只在真正的"是一个"关系中使用继承
    • 对于"有一个"关系,使用组合
  2. 遵循里氏替换原则

    • 派生类对象应该能够替换基类对象而不改变程序的正确性
    • 派生类不应该违反基类的行为约定
  3. 避免过深的继承层次

    • 深度超过3层的继承会增加复杂性
    • 考虑使用混合继承和组合
  4. 小心使用多重继承

    • 避免不必要的多重继承
    • 使用接口(纯虚函数类)进行多重继承
  5. 基类析构函数应该是虚函数

    • 确保正确析构派生类对象
    • 避免内存泄漏

多态最佳实践

  1. 使用纯虚函数定义接口

    • 明确类的责任和契约
    • 强制派生类实现核心功能
  2. 使用override标记重写的虚函数

    • 帮助编译器检查是否正确重写
    • 提高代码可读性
  3. 避免在构造函数中调用虚函数

    • 在构造期间,对象尚未完全初始化
    • 虚函数可能不会按预期行为
  4. 谨慎使用多态与new/delete

    • 确保通过基类指针删除时使用虚析构函数
    • 优先使用智能指针管理动态对象
  5. 避免"神奇数字"虚函数

    • 不要根据类型标记调用不同的虚函数
    • 让多态机制自动选择正确的实现

总结

封装、继承和多态是面向对象编程的三大核心特性:

  • 封装将数据和操作数据的方法绑定在一起,隐藏实现细节,提供清晰的接口。它保护数据不被直接访问,增强了代码的模块化和安全性。

  • 继承允许我们基于现有类创建新的类,重用代码,建立类层次结构。它体现了"是一个"关系,可以表示真实世界中的分类层次。

  • 多态使我们能够通过统一的接口处理不同类型的对象。它增加了代码的灵活性和可扩展性,是许多设计模式的基础。

理解和正确应用这三大特性是成为一名优秀的面向对象程序设计师的关键。在实际编程中,应该遵循面向对象设计原则,平衡使用这些特性,以创建出高质量、可维护的代码。

在下一篇文章中,我们将详细探讨C++中的构造函数和析构函数,这是理解对象生命周期管理的重要组成部分。

参考资料

  1. Bjarne Stroustrup. The C++ Programming Language (4th Edition)
  2. Scott Meyers. Effective C++
  3. cppreference.com - Classes
  4. cppreference.com - Inheritance
  5. cppreference.com - Virtual functions
  6. C++ Core Guidelines - Classes and Class Hierarchies
  7. Robert C. Martin. Clean Code: A Handbook of Agile Software Craftsmanship

这是我C++学习之旅系列的第九篇技术文章。查看完整系列目录了解更多内容。

相关文章:

C++学习:六个月从基础到就业——面向对象编程:封装、继承与多态

C学习&#xff1a;六个月从基础到就业——面向对象编程&#xff1a;封装、继承与多态 本文是我C学习之旅系列的第九篇技术文章&#xff0c;主要讨论C中面向对象编程的三大核心特性&#xff1a;封装、继承与多态。这些概念是理解和应用面向对象设计的关键。查看完整系列目录了解…...

Golang Event Bus 最佳实践:使用 NSQite 实现松耦合架构

Go Event Bus 最佳实践&#xff1a;使用 NSQite 实现松耦合架构 什么是 Event Bus&#xff1f; Event Bus&#xff08;事件总线&#xff09;是一种消息传递模式&#xff0c;它允许应用程序的不同组件通过发布/订阅机制进行通信&#xff0c;而不需要直接相互依赖。这种模式特别…...

独家!美团2025校招大数据题库

推荐阅读文章列表 2025最新大数据开发面试笔记V6.0——试读 我的大数据学习之路 面试聊数仓第一季 题库目录 Java 1.写一个多线程代码 2.写一个单例代码 3.LinkedBlockingQueue原理 4.模板设计模式 5.如何设计一个 生产者-消费者队列 6.堆内存和栈内存 7.ThreadLo…...

用 C++ 模拟客户端渲染中的分步数据加载

用 C++ 模拟客户端渲染中的分步数据加载 引言 在前端开发中,客户端渲染是一种常见的技术,它允许页面在加载后动态地更新内容。通常,页面会先展示一个基本的骨架,然后再逐步加载和渲染具体的数据。本文将介绍如何使用 C++ 编写一个简单的程序来模拟客户端渲染中的这种分步…...

Dify智能体平台源码二次开发笔记(5) - 多租户的SAAS版实现(2)

目录 前言 用户的查询 controller层 添加路由 service层 用户的添加 controller层 添加路由 service层-添加用户 service层-添加用户和租户关系 验证结果 结果 前言 完成租户添加功能后&#xff0c;下一步需要实现租户下的用户管理。基础功能包括&#xff1a;查询租…...

Linux的目录结构(介绍,具体目录结构)

目录 介绍 具体目录结构 简洁的目录解释 详细的目录解释 介绍 Linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构的最上层是根目录“/”。Linux的世界中&#xff0c;一切皆文件&#xff08;比如&#xff1a;Linux会把硬件映射成文件来管理&#xff09; 具体目…...

如何用 esProc 补充数据库 SQL 的缺失能力

某些数据库 SQL 缺失必要的能力&#xff0c;通常要编写大段的代码&#xff0c;才能间接实现类似的功能&#xff0c;有些情况甚至要改用存储过程&#xff0c;连结构都变了。常见的比如&#xff1a;生成时间序列、保持分组子集、动态行列转换、自然序号、相对位置、按序列和集合生…...

晶晨线刷工具下载及易错点说明:Key文件配置错误/mac剩余数为0解决方法

晶晨线刷工具下载及易错点说明&#xff1a;Key文件配置错误&#xff0f;mac剩余数为0解决方法 各种版本晶晨线刷工具下载&#xff1a; 晶晨线刷工具易出错点故障解决方法&#xff1a; 1、晶晨线刷工具加载固件的时候提示mac红字且剩余数为0的解决办法 很多同学可能会与遇到加…...

论文阅读:Invertible Grayscale

这是一篇 ACM Transactions on Graphic 上的文章&#xff0c;这篇文章中介绍的应用还挺有意思的&#xff0c;关于可逆的图像灰度化。 Abstract 一旦彩色图像被转换为灰度图像&#xff0c;人们普遍认为&#xff0c;即使采用最先进的彩色化方法&#xff0c;原始颜色也无法完全恢…...

linux下使用php修改php.ini的session.save_path无效的解决办法

linux下安装php的组合还是php-fpm和nginx&#xff0c;其实已经安装好了&#xff0c;网站已经能够跑起来了&#xff0c;但是遇到后台登录的时候验证码一直不对&#xff0c;看了下报错&#xff0c;session无法存储&#xff0c;于是新增了一个phpinfo文件&#xff0c;使用web查看下…...

关于ResNet和FPN的一份介绍

在这篇文章中我将介绍ResNet和FPN这两个深度学习中重要的技术。 一、ResNet-50/101 首先我们先来看ResNet技术&#xff1a; 1.1 概述 ResNet技术是基于残差学习&#xff0c;引入Bottleneck技术以及Shortcut Connection技术&#xff0c;而去解决神经网络中的退化问题。 1.2…...

以技术的形式实现发票真伪的快速查验-Android发票查验接口

对于企业而言&#xff0c;假票入账不仅可能导致企业财务损失&#xff0c;更会引发一系列法律风险&#xff0c;因此精准、高效的发票查验服务成为了企业运营不可或缺的支持。发票验真服务接口&#xff0c;正是一款能满足这些需求&#xff0c;助力企业摆脱繁琐流程、提升工作效率…...

Python实现贪吃蛇三

上篇文章Python实现贪吃蛇一&#xff0c;实现了一个贪吃蛇的基础版本。后面第二篇文章Python实现贪吃蛇二修改了一些不足&#xff0c;但最近发现还有两点需要优化&#xff1a; 1、生成食物的时候有概率和记分牌重合 2、游戏缺少暂停功能 先看生成食物的时候有概率和记分牌重合的…...

Docker 中多个容器之间的通信

在 Docker 中&#xff0c;多个容器之间的通信可以通过以下几种主要方式实现&#xff0c;具体选择取决于网络需求、隔离性及管理复杂度&#xff1a; 一、自定义 Bridge 网络&#xff08;推荐&#xff09; 通过创建自定义的 Docker 网络&#xff0c;容器可以加入同一网络并通过容…...

AI大模型学习九:‌Sealos cloud+k8s云操作系统私有化一键安装脚本部署完美教程

一、说明 ‌Sealos‌是一款基于Kubernetes&#xff08;K8s&#xff09;的云操作系统发行版&#xff0c;它将K8s以及常见的分布式应用如Docker、Dashboard、Ingress等进行了集成和封装&#xff0c;使得用户可以在不深入了解复杂的K8s底层原理的情况下&#xff0c;快速搭建起一个…...

详解关于VS配置好Qt环境之后但无法打开ui界面

目录 找到Qt安装目录中designer.exe的路径 找到vs中的解决方案资源管理器 右键ui文件&#xff0c;找到打开方式 点击添加 然后把前面designer.exe的路径填到程序栏中&#xff0c;点击确定 然后设置为默认值&#xff0c;并点击确定 当在vs中配置好Qt环境之后&#xff0c;但…...

Python Cookbook-6.5 继承的替代方案——自动托管

任务 你需要从某个类或者类型继承&#xff0c;但是需要对继承做一些调整。比如&#xff0c;需要选择性地隐藏某些基类的方法&#xff0c;而继承并不能做到这一点。 解决方案 继承是很方便的&#xff0c;但它并不是万用良药。比如&#xff0c;它无法让你隐藏基类的方法或者属…...

【深度学习与大模型基础】第9章-条件概率以及条件概率的链式法则

简单理解条件概率 条件概率就是在已知某件事发生的情况下&#xff0c;另一件事发生的概率。用数学符号表示就是&#xff1a; P(A|B) 在B发生的前提下&#xff0c;A发生的概率。 计算机例子&#xff1a;垃圾邮件过滤 假设你写了一个程序来自动判断邮件是否是垃圾邮件&#xf…...

STM32-FreeRTOS的详细配置

配置FreeRTOS 原文链接&#xff1a;https://ydamooc.github.io/posts/c9defcd/ 1.1 下载FreeRTOS 打开FreeRTOS官网&#xff1a;https://www.freertos.org/ 点击下载&#xff0c;并且选择"FreeRTOS 202212.01"版本&#xff0c;再点击Download按钮下载官方的资源包…...

行为检测技术指南

以下是行为检测技术的全面技术指南,涵盖核心技术原理、应用场景及发展趋势: 一、核心行为检测技术分类 1. 基于计算机视觉的行为检测 目标检测算法 通过目标定位与分类识别行为,典型算法包括: YOLO系列(YOLOv5/v8):实时性强,适用于视频流中的动作识别(如摔倒、抽烟检…...

视觉自回归图像生成:基于多模态大模型的万字深度梳理

目前利用多模态大模型进行图像生成主要有以下两种形式&#xff1a; LLM作为condtioner&#xff1a;利用MLLM依据用户输入的text prompt来生成条件信息&#xff0c;条件信息被注入到下游生成模型进行更精细化的生成控制。这种形式通常需要外接一个额外专门的多模态生成模型&…...

openssh离线一键升级脚本分享(含安装包)

查看当前的版本 [rootmyoracle ~]#ssh -V相关安装包下载地址 openssh下载地址&#xff1a;http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssl下载地址&#xff1a;https://www.openssl.org/source/zlib下载地址&#xff1a;http://www.zlib.net/今天演示从7.4升级…...

音视频之H.265/HEVC预测编码

H.265/HEVC系列文章&#xff1a; 1、音视频之H.265/HEVC编码框架及编码视频格式 2、音视频之H.265码流分析及解析 3、音视频之H.265/HEVC预测编码 预测编码是视频编码中的核心技术之一。对于视频信号来说&#xff0c;一幅图像内邻近像素之间有着较强的空间相关性,相邻图像之…...

Python异步编程入门:Async/Await实战详解

引言 在当今高并发的应用场景下&#xff0c;传统的同步编程模式逐渐暴露出性能瓶颈。Python通过asyncio模块和async/await语法为开发者提供了原生的异步编程支持。本文将手把手带你理解异步编程的核心概念&#xff0c;并通过实际代码案例演示如何用异步爬虫提升10倍效率&#…...

设计模式每日硬核训练 Day 13:桥接模式(Bridge Pattern)完整讲解与实战应用

&#x1f504; 回顾 Day 12&#xff1a;装饰器模式小结 在 Day 12 中&#xff0c;我们学习了装饰器模式&#xff08;Decorator Pattern&#xff09;&#xff1a; 强调在不改变原类结构的前提下&#xff0c;动态为对象增强功能。通过“包装对象”实现运行时组合&#xff0c;支…...

库洛游戏一面+二面

目录 一面 1. ArrayList和LinkedList的区别&#xff0c;就是我在插入和删除的时候他们在时间复杂度上有什么区别 2. hashmap在java的底层是怎么实现的 3. 红黑树的实现原理 4. 红黑树的特点 5. 为什么红黑树比链表查询速度快 6. 在java中字符串的操作方式有几种 7. Stri…...

前端面试-Vue篇

核心概念 Vue 3的响应式原理与Vue 2有何本质区别&#xff1f;Vue中虚拟DOM的diff算法优化策略有哪些&#xff1f;Vue组件间通信方式有哪些&#xff1f;适用场景分别是什么&#xff1f;Vue的生命周期钩子在Composition API中如何替代&#xff1f;Vue的模板编译过程是怎样的&…...

XSS攻击(反射型、存储型、dom型、PDF、SWF、SVG)

一、XSS攻击是什么 XSS是恶意攻击者往 Web 页面里插入恶意可执行网页脚本代码&#xff0c;当用户浏览该页之时&#xff0c;嵌入其中 Web 里面的脚本代码会被执行&#xff0c;从而可以达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。 二、XSS分类 反射型XSS 常见情况是…...

C复习(主要复习)

指针和数组 指针数组是一个数组&#xff0c;数组的每个元素都是指针。它适用于需要存储多个指针的场景&#xff0c;如字符串数组。数组指针是一个指针&#xff0c;指向一个数组。它适用于需要传递整个数组给函数或处理多维数组的场景。 函数指针&#xff1a;函数指针的定义需要…...

Python及C++中的集合

1. Python 中的集合&#xff08;set&#xff09; 1.1 特性 无序性&#xff1a;集合中的元素没有顺序&#xff0c;不能通过索引访问。唯一性&#xff1a;集合中的元素不能重复&#xff0c;如果尝试添加重复的元素&#xff0c;集合会自动忽略。可变性&#xff1a;集合是可变的&…...