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

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;封装、继承与多态。这些概念是理解和应用面向对象设计的关键。查看完整系列目录了解…...

光谱相机的成像方式

光谱相机的成像方式决定了其如何获取物体的空间与光谱信息&#xff0c;核心在于分光技术与扫描模式的结合。以下是主要成像方式的分类解析&#xff1a; ‌一、滤光片切换型‌ ‌1. 滤光片轮&#xff08;Filter Wheel&#xff09;‌ ‌原理‌&#xff1a;通过旋转装有多个窄带…...

Excel 中让表格内容自适应列宽和行高

Excel 中让表格内容自适应列宽和行高 目录 Excel 中让表格内容自适应列宽和行高自适应列宽自适应行高在Excel中让表格内容自适应列宽和行高,可参考以下操作: 自适应列宽 方法一:手动调整 选中需要调整列宽的列(如果是整个表格,可点击表格左上角行号和列号交叉处的三角形全…...

android rtsp 拉流h264 h265,解码nv12转码nv21耗时卡顿问题及ffmpeg优化

一、 背景介绍及问题概述 项目需求需要在rk3568开发板上面&#xff0c;通过rtsp协议拉流的形式获取摄像头预览&#xff0c;然后进行人脸识别 姿态识别等后续其它操作。由于rtsp协议一般使用h.264 h265视频编码格式&#xff08;也叫 AVC 和 HEVC&#xff09;是不能直接用于后续处…...

Day(21)--网络编程

网络编程 在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输 应用场景&#xff1a;即使通信、网友对战、金融证券等等&#xff0c;不管是什么场景&#xff0c;都是计算机和计算机之间通过网络进行的数据传输 java.net 常见的软件架构 C/S&am…...

Android主流播放器功能详解

Android主流播放器功能详解 前言 本文将深入介绍Android三大主流播放框架(ijkplayer、ExoPlayer和MediaPlayer)的功能特性和实战应用,帮助你选择合适的播放框架并掌握其使用方法。 三大播放框架概述 播放框架开发方特点适用场景MediaPlayerAndroid官方简单易用,系统内置…...

牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记2

目录 一、ArcGIS Engine概述 1、 定义 2、 核心功能 3、 与ArcObjects&#xff08;AO&#xff09;的关系 二、开发环境搭建 1、 开发工具要求 2、 关键步骤 三、 ArcGIS Engine核心组件 1、 对象模型 2、 类库分类 四、 第一个AE应用程序&#xff08;C#示例&#xf…...

语音合成(TTS)从零搭建一个完整的TTS系统-第一节-效果演示

一、概述 语音合成又叫文字转语音&#xff08;TTS-text to speech &#xff09;&#xff0c;本专题我们记录从零搭建一个完整的语音合成系统&#xff0c;包括文本前端、声学模型和声码器&#xff0c;从模型训练到系统的工程化实现&#xff0c;模型可以部署在手机等嵌入式设备上…...

文章记单词 | 第35篇(六级)

一&#xff0c;单词释义 across [əˈkrɒs] prep. 从一边到另一边&#xff1b;横过&#xff1b;在… 对面&#xff1b;遍及&#xff1b;在… 上&#xff1b;跨越&#xff1b;adv. 从一边到另一边&#xff1b;横过&#xff1b;宽&#xff1b;从… 的一边到另一边&#xff1b;在…...

MySQL Binlog 数据恢复总结

&#x1f332; 总入口&#xff1a;你想恢复什么&#xff1f; 恢复类型 ├── 表结构 表数据&#xff08;整张表被 DROP&#xff09; │ ├── Binlog 中包含 CREATE TABLE │ │ └── ✅ 直接用 mysqlbinlog 提取建表 数据语句&#xff0c;回放即可 │ └── B…...

【Linux】进程基础入门指南(下)

> &#x1f343; 本系列为Linux的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:【小编的个人主页】 >小编将在这里分享学习Linux的心路历程✨和知识分享&#x1f50d; >如果本篇文章有不足&#xff0c;还请多多包涵&a…...

NoETL×大模型:Aloudata重构数据智能新范式,开启Chat BI新落地之道

在当今数据驱动的时代&#xff0c;企业对于高效、智能的数据处理与分析需求日益增长。随着大模型的兴起&#xff0c;如DeepSeek等&#xff0c;数据智能领域正经历着前所未有的变革。 Aloudata大应科技创始人&CEO周卫林表示&#xff0c;企业的核心竞争力包括人才壁垒、技术…...

算法题(126):前缀和

审题&#xff1a; 本题需要我们将题目给出的数组的数据的[l,r]范围内的数据和打印 思路&#xff1a; 方法一&#xff1a;前缀和 前缀和的思想就是预处理数据&#xff0c;通过空间换时间的方式提高代码效率 第一步&#xff1a;利用数组f将前缀和记录下来&#xff0c;f[i]表示索引…...

选择排序(简单选择排序、堆排序)

简单选择排序&#xff08;Selection Sort&#xff09; 1. 算法思想 它通过多次遍历数组&#xff0c;每次从未排序部分中选择最小&#xff08;或最大&#xff09;的元素&#xff0c;将其放到已排序部分的末尾&#xff08;或开头&#xff09;&#xff0c;直到整个数组有序。 2.…...

【JavaEE】Spring AOP的注解实现

目录 一、AOP 与 Spring AOP二、Spring AOP简单实现三、详解Spring AOP3.1 Spring AOP 核心概念3.1.1 切点&#xff08;Pointcut&#xff09;3.1.2 连接点&#xff08;Join Point&#xff09;3.1.3 通知&#xff08;Advice&#xff09;3.1.4 切面&#xff08;Aspect&#xff09…...

【天外之物】加速度与速度的单位向量的内积得到加速度在切向向量上的值

切向加速度的标量值 a T a_T aT​ 正是加速度矢量 a \mathbf{a} a 与单位切矢量 T ^ \mathbf{\hat{T}} T^ 的内积&#xff08;点积&#xff09;。 1. 数学定义 设物体的速度为 v \mathbf{v} v&#xff0c;加速度为 a \mathbf{a} a&#xff0c;单位切矢量为 T ^ \mathbf{…...

​​eBay 2025春季财报揭示跨境电商新蓝海:五大隐秘品类引爆增长密码​

核心数据速览​​ 2024年第一季度&#xff0c;eBay全球商品交易总额&#xff08;GMV&#xff09;达255亿美元&#xff0c;同比增长5%。这一增长不仅源于季节性消费回暖&#xff0c;更折射出跨境电商行业在能源转型、供应链重构及消费需求升级中的结构性变革。透过数据&#xff…...

兔子桌面tv版下载-兔子桌面tv版官方app免费下载安装

兔子桌面 TV 版是一款专为智能电视和机顶盒设计的轻量化桌面应用&#xff0c;其界面采用大图标、大字体设计&#xff0c;支持自由调整应用顺序&#xff0c;将常用的影视、游戏 App 置顶&#xff0c;还可通过主题市场下载动态背景&#xff0c;满足用户对电视界面的个性化需求。 …...

绿算轻舟系列FPGA加速卡:驱动数字化转型的核心动力【2】

工业与医疗&#xff1a;精准化的幕后推手 在工业4.0与智慧医疗领域&#xff0c;绿算轻舟FPGA加速卡通过实时信号处理与高精度控制&#xff0c;推动关键场景的技术升级。 工业自动化&#xff1a;在机器视觉质检中&#xff0c;实现亚像素级缺陷检测&#xff0c;产线检测速度大幅…...

ubuntu1804服务器开启ftp,局域网共享特定文件给匿名用户

要在 Ubuntu 18.04 上设置一个 FTP 服务器&#xff0c;满足以下要求&#xff1a; 允许匿名登录&#xff08;无需账号密码&#xff09;。指定分享特定目录下的文件。只允许只读下载。 可以使用 vsftpd&#xff08;Very Secure FTP Daemon&#xff09;来实现。以下是详细步骤&a…...

k8s中pod报错 FailedCreatePodSandBox

问题现象&#xff1a; 创建容器时出现一下情况 而且删掉控制器的时候pod还会卡住 解决&#xff1a; 将calico的pod重新删掉。其中有1个控制器pod以及3个node pod 删掉后&#xff0c;大概10来秒就重新创建完成了。 然后现在在使用kubectl apply -f 文件.yaml 就可以正常创…...

请详细说明下面训练阶段的差别: Supervised Fine-Tuning、Reward Modeling、PPO、DPO、KTO、Pre-Training

目录 &#x1f527; 一、训练阶段总体流程&#xff08;从底层到上层&#xff09; &#x1f9e0; 1. Pre-Training&#xff08;预训练&#xff09; &#x1f4cc; 目的&#xff1a; &#x1f4da; 数据&#xff1a; ⚙️ 方法&#xff1a; &#x1f4a1; 举个例子&#xf…...

Go语言入门到入土——一、安装和Hello World

Go语言入门到精通——安装和Hello World 文章目录 Go语言入门到精通——安装和Hello World下载并安装让Go跑起来为你的代码启动依赖跟踪调用外部包总结 下载并安装 下载地址&#xff1a;https://go.dev/dl/ 下载后傻瓜式安装 查看是否安装完成 go version让Go跑起来 创建一个…...

React 入门完全指南:从零开始构建现代 Web 应用

在当今快速发展的前端开发领域&#xff0c;React 凭借其高效的组件化架构和强大的生态系统&#xff0c;已成为最受欢迎的 JavaScript 库之一。根据 2023 年 Stack Overflow 开发者调查&#xff0c;React 连续七年成为最常用的 Web 框架。无论是初创公司还是科技巨头&#xff0c…...

0701表单组件-react-仿低代码平台项目

文章目录 1 react表单组件1.1 受控组件 (Controlled Components)示例代码&#xff1a; 1.2 非受控组件 (Uncontrolled Components)示例代码&#xff1a; 2 AntD表单组件实战2.1 开发搜索功能2.2 开发注册页2.3 开发登录页2.4 表单组件校验 结语 1 react表单组件 input表单组件…...

Android ViewStub显示VISIBLE与消失GONE,Kotlin(2)

Android ViewStub显示VISIBLE与消失GONE&#xff0c;Kotlin&#xff08;2&#xff09; 在 Android ViewStub显示VISIBLE与消失GONE&#xff0c;Kotlin-CSDN博客 基础上完善。 import android.os.Bundle import android.util.Log import android.view.View import android.view…...

跨站脚本(XSS) 的详细分类、对比及解决方案

以下是 跨站脚本&#xff08;XSS&#xff09; 的详细分类、对比及解决方案&#xff1a; 一、XSS的分类与详解 1. 反射型XSS&#xff08;非持久型XSS&#xff09; 定义&#xff1a;攻击载荷通过URL参数传递&#xff0c;服务器直接返回到页面中&#xff0c;需用户主动触发。 工…...

JVM:程序计数器、虚拟机栈、本地方法栈

一、程序计数器 &#xff08;1&#xff09;程序计数器介绍 作用&#xff1a;当线程执行 Java 方法时&#xff0c;程序计数器记录该线程下一条要执行的字节码指令的地址&#xff1b;当线程执行本地方法时&#xff0c;程序计数器的值为未指定&#xff08;undefined&#xff09;…...

适配器模式在Java开发中的应用

适配器模式&#xff08;Adapter Pattern&#xff09;是设计模式中的一种结构型模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口。通过这种方式&#xff0c;原本因接口不兼容而无法协同工作的类能够一起工作。适配器模式在Java开发中非常常见&#xff0c;尤…...

(三)谷歌Code as Policies复现(操作记录)

目录 《复现的项目来源》 一、创建虚拟环境 二、下载原项目并修改&#xff08;非必须&#xff09; 二、可直接下载修改后的项目 三、配置环境 &#xff08;1&#xff09;安装jupyterlab以及内核 &#xff08;2&#xff09;安装ffmpeg &#xff08;3&#xff09;配置环境…...

驱动学习专栏--字符设备驱动篇--2_字符设备注册与注销

对于字符设备驱动而言&#xff0c;当驱动模块加载成功以后需要注册字符设备&#xff0c;同样&#xff0c;卸载驱动模 块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示 : static inline int register_chrdev(unsigned int major, const char *name, const…...

奥创中心卸载工具Armoury Crate Uninstall Tool官网下载

为了应对用户对 Armoury Crate 占用资源大、卸载困难等问题的普遍反馈&#xff0c;ASUS 官方提供了一个专门的卸载工具&#xff0c;即 Armoury Crate Uninstall Tool(奥创中心卸载工具)。该工具的主要作用是帮助用户彻底从系统中移除 Armoury Crate 相关的所有组件&#xff0c;…...

【Linux网络】网络基础概念深度解析

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

【NLP 61、大模型应用 —— RAG方法】

生活打不败一个大口吃饭的人&#xff01; —— 25.4.13 一、模型幻觉问题 模型幻觉&#xff08;AI Hallucination&#xff09;是指人工智能模型&#xff08;尤其是大语言模型&#xff09;生成看似合理但实际不准确、虚构或与事实不符内容的现象。其本质是模型基于统计概…...

UV工具——小试牛刀

背景 MCP开发使用到 为什么MCP更推荐使用uv进行环境管理&#xff1f; MCP 依赖的 Python 环境可能包含多个模块&#xff0c;uv 通过 pyproject.toml 提供更高效的管理方式&#xff0c;并且可以避免 pip 的一些依赖冲突问题。此外&#xff0c;uv 的包管理速度远超 pip…...

vue3+vite 多个环境配置

同一套代码 再也不用在不同的环境里来回切换请求地址了 然后踩了一个坑 就是env的文件路径是在当前项目下 不是在views内 因为公司项目需求只有dev和pro两个环境 虽然我新增了3个 但是只在这两个里面配置了 .env是可以配置一些公共配置的 目前需求来说不需要 所以我也懒得配了。…...

《分布式软总线架构下,设备虚拟化技术的深度剖析与优化策略》

设备之间的互联互通和协同工作已成为一种趋势。分布式软总线架构作为实现这一目标的关键技术&#xff0c;为不同设备之间的通信和协作提供了基础。而设备虚拟化技术则是在分布式软总线架构下&#xff0c;进一步提升设备资源利用效率的重要手段。本文将深入探讨在分布式软总线架…...

MCP 正当时:FunctionAI MCP 开发平台来了!

作者&#xff1a;封崇 MCP&#xff1a;AI 时代的“操作系统接口” 2024 年 11 月&#xff0c;Anthropic 发布模型上下文协议&#xff08;MCP&#xff09;&#xff0c;这一开放标准迅速引发开发者社区的"协议觉醒"。其本质是通过标准化接口实现 LLM 与外部世界的双向…...

AI Agents系列之AI代理的类型

在本文中,我们将探讨不同类型的 AI 代理,包括它们的实现、实际应用、优势和局限性。从简单反射代理到多代理系统,我们将了解这些模型如何推动自动化、决策制定和智能问题解决。 文章目录 1. AI代理的类型1.1 简单反射代理1.1.1 实现**1.1.2 优势****1.1.3 局限性**1.2 基于…...

Go RabbitMQ基础教程:入门与实践指南,实战代码讲解

简介&#xff1a; RabbitMQ是一款实现高级消息队列协议&#xff08;AMQP&#xff09;的消息代理软件&#xff0c;也称为消息队列或消息中间件。它通过解耦应用程序之间的直接通信&#xff0c;支持异步数据交换&#xff0c;增强了系统的可扩展性和灵活性。RabbitMQ能够跨平台运…...

LeetCode详解之如何一步步优化到最佳解法:27. 移除元素

LeetCode详解系列的总目录&#xff08;持续更新中&#xff09;&#xff1a; LeetCode详解之如何一步步优化到最佳解法&#xff1a;前100题目录&#xff08;更新中...&#xff09;-CSDN博客 LeetCode详解系列的上一题链接&#xff1a; LeetCode详解之如何一步步优化到最佳解法…...

c++原子操作

原子操作&#xff0c;顾名思义&#xff0c;该操作不可分割。多线程环境也能保证读写数据不错乱。百度搜索了下&#xff0c;其核心概念如下&#xff1a; 1、不可分割性。原子操作是指一系列不可被CPU上下文交换的机器指令&#xff0c;操作要么完全执行&#xff0c;要么完全不执…...

在 Redis Lua 脚本中,keyCount 参数的作用是明确区分脚本参数中的 KEYS 和 ARGV,具体关系如下:

在 Redis Lua 脚本中&#xff0c;keyCount 参数的作用是**明确区分脚本参数中的 KEYS 和 ARGV**&#xff0c;具体关系如下&#xff1a; --- ### 核心作用 1. **参数分类标识** - keyCount 表示脚本中使用的 Redis KEY 的数量&#xff08;即 KEYS 数组的长度&#xff09;…...

小白如何从0学习CSS

以下是针对小白从零开始系统学习 CSS 的完整路径和实用指南&#xff0c;结合核心概念、实践技巧和项目经验&#xff0c;助你掌握网页样式的精髓&#xff1a; 1. 理解 CSS 是什么&#xff1f; 定义&#xff1a;CSS&#xff08;层叠样式表&#xff09;用于控制网页的视觉表现&…...

一文掌握RK3568开发板Android13挂载Windows共享目录

在物联网和边缘计算场景中&#xff0c;开发板与PC端的高效文件交互尤为重要。现以iTOP-RK3568开发板为例&#xff0c;详细演示Android13系统如何通过CIFS协议挂载Windows共享目录&#xff0c;实现开发板与PC的无缝文件共享。 RK3568开发板优势 iTOP-3568开发板采用瑞芯微RK3…...

UE5烘培后->为什么C磁盘满了

烘培会产生ddc 需要把路径切换一下&#xff0c;比如切换到游戏空间下。 如何修改&#xff0c;修改如下&#xff1a; 使用记事本打开BaseEngine.ini文件。 将以下内容&#xff1a; textCopy Code Path\"%ENGINEVERSIONAGNOSTICUSERDIR%DerivedDataCache\" 替换为&…...

本地搭建全网可访问的开源音乐服务器Melody结合内网穿透随时听歌

文章目录 前言1. 添加镜像源2. 本地部署Melody3. 本地访问与使用演示4. 安装内网穿透5. 配置Melody公网地址6. 配置固定公网地址 前言 嗨&#xff0c;各位音乐发烧友们&#xff01;今天我要带你们解锁一个超酷的新技能——在香橙派Zero3上搭建自己的在线音乐平台&#xff0c;并…...

深度学习Y5周:yolo.py文件解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前言 文件位置&#xff1a;./models/yolo.py 此文件是实现YOLOv5网络模型的搭建文件&#xff0c;如果想改进YOLOv5&#xff0c;这个文件是必须进行修改的…...

Qt实现文件传输服务器端(图文详解+代码详细注释)

Qt实现文件传输服务器 1、前言2、服务器2.1 服务器UI界面2.2添加网络模块和头文件2.3、创建服务器对象2.4 连接有新连接的信号与槽2.5实现有新连接处理的槽函数2.6 选择文件按钮实现2.6.1 连接按钮点击的信号与槽2.6.2 添加头文件2.6.3 创建所需对象2.6.3 选择文件按钮实现 2.7…...

记录学习的第二十七天

今天效率低下&#xff0c;只做了一道力扣的每日一题。 看了题解才懂的。下面复述一遍吧。 算法就是越长越合法型滑动窗口。 核心&#xff1a; 如果此时窗口中有c个元素x&#xff0c;此时再进一个x&#xff0c;那么相同数对就增加c个。 如果此时窗口有c个元素x&#xff0c;此…...