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;
}
};
在这个例子中:
accountNumber
、balance
和ownerName
是私有成员变量,外部代码无法直接访问和修改deposit
、withdraw
、getBalance
和getAccountInfo
是公共方法,提供了安全访问和操作内部数据的接口
访问修饰符
C++提供了三种访问修饰符:
private(私有):
- 只能被类内部的成员函数访问
- 是类的默认访问级别
- 派生类不能访问基类的私有成员
protected(受保护):
- 可以被类内部的成员函数访问
- 可以被派生类的成员函数访问
- 外部代码不能直接访问
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 - 不修改数据,通常声明为const
std::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,我们可以:
- 添加验证逻辑,确保数据的有效性
- 控制属性的读写权限(只读、只写或读写)
- 在不改变接口的情况下修改内部实现
- 添加额外逻辑(如日志记录、通知等)
封装的好处
- 数据保护:防止外部代码意外或恶意修改对象的内部状态
- 实现隐藏:可以改变内部实现而不影响外部代码
- 耦合度降低:对象之间通过定义清晰的接口进行交互,降低了依赖程度
- 提高可维护性:系统更易于维护和扩展
继承(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
继承了Animal
的name
、age
属性,以及eat
、sleep
、getName
和getAge
方法Dog
添加了自己的属性breed
和方法bark
和getBreed
使用示例:
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++支持三种继承方式:
公有继承(public):
- 基类的公有成员在派生类中仍是公有的
- 基类的保护成员在派生类中仍是保护的
- 基类的私有成员对派生类不可访问
- 表示"是一个"(is-a)关系
保护继承(protected):
- 基类的公有和保护成员在派生类中变为保护成员
- 基类的私有成员对派生类不可访问
- 表示"基于"(is-implemented-in-terms-of)关系,但派生类可能不想公开这种关系
私有继承(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 不可访问
};
构造函数和析构函数的调用顺序
在继承关系中,构造函数和析构函数的调用顺序非常重要:
构造顺序:
- 基类构造函数
- 派生类构造函数
析构顺序:
- 派生类析构函数
- 基类析构函数
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;
}
虚拟继承确保无论通过多少条路径,基类在派生类中只有一个实例。
继承的好处
- 代码重用:避免重复编写基类已经提供的功能
- 提高可扩展性:可以通过创建新的派生类来扩展系统功能
- 建立类层次:反映真实世界的"是一个"关系
- 多态性的基础:为运行时多态提供机制
多态(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;
}
多态的好处
- 灵活性:允许编写适用于不同类型对象的代码
- 可扩展性:可以添加新的派生类而不修改现有代码
- 解耦:代码依赖于抽象接口而非具体实现
- 设计模式:许多设计模式(如策略、观察者、工厂等)都依赖于多态
实际应用例子
图形编辑器
下面是一个简化的图形编辑器实现,展示了封装、继承和多态的应用:
#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;
}
这个例子展示了:
- 封装:每个类都隐藏了其实现细节,只暴露必要的接口
- 继承:
Circle
、Rectangle
和Triangle
都继承自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;
}
这个例子展示了:
- 封装:每个账户类封装了其数据和行为,隐藏了实现细节
- 继承:
SavingsAccount
、CheckingAccount
和CreditCardAccount
从Account
继承 - 多态:通过虚函数,
BankSystem
可以统一处理不同类型的账户
最佳实践
封装最佳实践
保持数据私有:
- 将成员变量声明为
private
- 通过公共方法提供对私有数据的访问
- 将成员变量声明为
设计清晰的接口:
- 提供完整但最小化的公共接口
- 使用有意义的命名和注释
验证输入:
- 在setter中验证输入数据的有效性
- 避免对象陷入无效状态
遵循"最少知识"原则:
- 类只应该与其直接合作者交互
- 避免"训练车"式的调用链(a.getB().getC().getD()…)
继承最佳实践
优先使用组合而非继承:
- 只在真正的"是一个"关系中使用继承
- 对于"有一个"关系,使用组合
遵循里氏替换原则:
- 派生类对象应该能够替换基类对象而不改变程序的正确性
- 派生类不应该违反基类的行为约定
避免过深的继承层次:
- 深度超过3层的继承会增加复杂性
- 考虑使用混合继承和组合
小心使用多重继承:
- 避免不必要的多重继承
- 使用接口(纯虚函数类)进行多重继承
基类析构函数应该是虚函数:
- 确保正确析构派生类对象
- 避免内存泄漏
多态最佳实践
使用纯虚函数定义接口:
- 明确类的责任和契约
- 强制派生类实现核心功能
使用
override
标记重写的虚函数:- 帮助编译器检查是否正确重写
- 提高代码可读性
避免在构造函数中调用虚函数:
- 在构造期间,对象尚未完全初始化
- 虚函数可能不会按预期行为
谨慎使用多态与
new
/delete
:- 确保通过基类指针删除时使用虚析构函数
- 优先使用智能指针管理动态对象
避免"神奇数字"虚函数:
- 不要根据类型标记调用不同的虚函数
- 让多态机制自动选择正确的实现
总结
封装、继承和多态是面向对象编程的三大核心特性:
封装将数据和操作数据的方法绑定在一起,隐藏实现细节,提供清晰的接口。它保护数据不被直接访问,增强了代码的模块化和安全性。
继承允许我们基于现有类创建新的类,重用代码,建立类层次结构。它体现了"是一个"关系,可以表示真实世界中的分类层次。
多态使我们能够通过统一的接口处理不同类型的对象。它增加了代码的灵活性和可扩展性,是许多设计模式的基础。
理解和正确应用这三大特性是成为一名优秀的面向对象程序设计师的关键。在实际编程中,应该遵循面向对象设计原则,平衡使用这些特性,以创建出高质量、可维护的代码。
在下一篇文章中,我们将详细探讨C++中的构造函数和析构函数,这是理解对象生命周期管理的重要组成部分。
参考资料
- Bjarne Stroustrup. The C++ Programming Language (4th Edition)
- Scott Meyers. Effective C++
- cppreference.com - Classes
- cppreference.com - Inheritance
- cppreference.com - Virtual functions
- C++ Core Guidelines - Classes and Class Hierarchies
- Robert C. Martin. Clean Code: A Handbook of Agile Software Craftsmanship
这是我C++学习之旅系列的第九篇技术文章。查看完整系列目录了解更多内容。