C++学习:六个月从基础到就业——面向对象编程:接口设计
C++学习:六个月从基础到就业——面向对象编程:接口设计
本文是我C++学习之旅系列的第十五篇技术文章,重点讨论在C++中进行接口设计的原则、技术和最佳实践。查看完整系列目录了解更多内容。
引言
在面向对象的软件开发中,良好的接口设计是创建可维护、可扩展和灵活的系统的关键。接口定义了组件之间的交互方式,将实现与使用分离,允许我们更改一个组件的内部工作方式而不影响依赖该组件的其他部分。
C++作为一种多范式编程语言,提供了多种方式来定义和实现接口。尽管C++不像Java或C#那样有专门的interface
关键字,但它通过抽象类、纯虚函数和其他机制提供了强大而灵活的接口设计能力。
本文将深入探讨C++中的接口设计技术、设计原则以及适用的设计模式,帮助您创建更为优雅和有效的面向对象系统。
C++中的接口概念
在C++中,接口通常有两层含义:
- 广义接口:指一个类或组件提供给外部使用者的所有公共方法和属性的集合,包括函数签名、参数类型、返回值等。
- 狭义接口:指一种只包含纯虚函数的抽象类,用于定义一组功能而不提供实现。
广义接口举例
任何类都有一个隐含的接口,即它的公共成员形成的集合:
class Vector {
public:void push_back(const int& value);int at(size_t index) const;size_t size() const;bool empty() const;void clear();// ...其他公共方法
};
在这个例子中,Vector
类的接口是由它的公共方法push_back
、at
、size
、empty
和clear
组成的。
狭义接口举例
更为专业和严格的接口定义是使用只包含纯虚函数的抽象类:
class Drawable {
public:virtual void draw() const = 0;virtual void resize(int width, int height) = 0;virtual ~Drawable() = default;
};
这个Drawable
接口定义了可绘制对象必须实现的操作,但不规定这些操作如何实现。
C++中实现接口的方法
1. 使用抽象类和纯虚函数
这是C++中最常见的接口实现方法,通过创建包含纯虚函数的抽象基类来定义接口:
class Logger {
public:virtual void log(const std::string& message) = 0;virtual void setLevel(int level) = 0;virtual int getLevel() const = 0;virtual ~Logger() = default;
};// 实现接口
class FileLogger : public Logger {
private:std::ofstream file;int logLevel;public:FileLogger(const std::string& filename) : logLevel(0) {file.open(filename);}void log(const std::string& message) override {file << message << std::endl;}void setLevel(int level) override {logLevel = level;}int getLevel() const override {return logLevel;}~FileLogger() {if (file.is_open()) {file.close();}}
};
2. 使用模板和CRTP(奇异递归模板模式)
CRTP提供了一种在编译时实现"接口"的方法,避免虚函数调用的运行时开销:
template <typename Derived>
class Printable {
public:void print() const {static_cast<const Derived*>(this)->printImpl();}// 可选的默认实现void printImpl() const {std::cout << "Default print implementation" << std::endl;}
};class Report : public Printable<Report> {
public:// 重写printImpl方法void printImpl() const {std::cout << "Printing report..." << std::endl;}
};class Invoice : public Printable<Invoice> {// 使用默认的printImpl实现
};
3. 使用概念(C++20)
C++20引入了概念(Concepts)特性,提供了一种更为现代和直接的方式来表达接口要求:
#include <concepts>
#include <iostream>
#include <string>// 定义一个Streamable概念
template<typename T>
concept Streamable = requires(T x, std::ostream& os) {{ os << x } -> std::convertible_to<std::ostream&>;
};// 使用概念的函数模板
template<Streamable T>
void print(const T& value) {std::cout << value << std::endl;
}// 满足Streamable概念的类
struct Person {std::string name;int age;friend std::ostream& operator<<(std::ostream& os, const Person& p) {return os << p.name << " (" << p.age << ")";}
};int main() {Person p{"Alice", 30};print(p); // OK,Person满足Streamable概念print("Hello"); // OK,字符串字面量满足Streamable概念print(42); // OK,整数满足Streamable概念return 0;
}
4. 使用鸭子类型和模板
C++的模板系统支持一种基于"鸭子类型"(duck typing)的接口检查,只要一个类提供了所需的方法,就可以被使用:
template<typename T>
void process(const T& obj) {// 只要T类型有process方法就可以调用obj.process();
}class Task {
public:void process() {std::cout << "Processing task..." << std::endl;}
};class Job {
public:void process() {std::cout << "Processing job..." << std::endl;}
};int main() {Task t;Job j;process(t); // OKprocess(j); // OKreturn 0;
}
接口设计原则
单一职责原则(SRP)
接口应该只有一个原因引起变化,也就是说,一个接口应该专注于一个特定的功能集:
// 不好的设计 - 接口承担了多个职责
class FileHandler {
public:virtual void open(const std::string& path) = 0;virtual void close() = 0;virtual void read(char* buffer, size_t size) = 0;virtual void write(const char* buffer, size_t size) = 0;virtual void compress() = 0; // 不相关的责任virtual void encrypt() = 0; // 不相关的责任virtual ~FileHandler() = default;
};// 好的设计 - 职责分离
class FileReader {
public:virtual void open(const std::string& path) = 0;virtual void close() = 0;virtual size_t read(char* buffer, size_t size) = 0;virtual ~FileReader() = default;
};class FileWriter {
public:virtual void open(const std::string& path) = 0;virtual void close() = 0;virtual void write(const char* buffer, size_t size) = 0;virtual ~FileWriter() = default;
};class DataProcessor {
public:virtual void compress(const char* input, char* output) = 0;virtual void encrypt(const char* input, char* output) = 0;virtual ~DataProcessor() = default;
};
接口隔离原则(ISP)
客户端不应该被迫依赖于它们不使用的接口。换句话说,接口应该小而专注:
// 不好的设计 - 一个大而全的接口
class Printer {
public:virtual void print() = 0;virtual void scan() = 0;virtual void fax() = 0;virtual void copy() = 0;virtual ~Printer() = default;
};// 好的设计 - 分离的接口
class Printable {
public:virtual void print() = 0;virtual ~Printable() = default;
};class Scannable {
public:virtual void scan() = 0;virtual ~Scannable() = default;
};class Faxable {
public:virtual void fax() = 0;virtual ~Faxable() = default;
};class Copyable {
public:virtual void copy() = 0;virtual ~Copyable() = default;
};// 多功能打印机实现多个接口
class AllInOnePrinter : public Printable, public Scannable, public Faxable, public Copyable {// 实现所有接口方法
};// 简单打印机只实现打印接口
class SimplePrinter : public Printable {// 只实现打印方法
};
依赖倒置原则(DIP)
高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象:
// 不好的设计 - 直接依赖具体实现
class EmailNotifier {
public:void sendNotification(const std::string& message) {std::cout << "Sending email: " << message << std::endl;}
};class NotificationService {
private:EmailNotifier emailNotifier; // 直接依赖具体实现public:void notify(const std::string& message) {emailNotifier.sendNotification(message);}
};// 好的设计 - 依赖抽象接口
class Notifier {
public:virtual void sendNotification(const std::string& message) = 0;virtual ~Notifier() = default;
};class EmailNotifier : public Notifier {
public:void sendNotification(const std::string& message) override {std::cout << "Sending email: " << message << std::endl;}
};class SMSNotifier : public Notifier {
public:void sendNotification(const std::string& message) override {std::cout << "Sending SMS: " << message << std::endl;}
};class NotificationService {
private:Notifier& notifier; // 依赖抽象接口public:NotificationService(Notifier& n) : notifier(n) {}void notify(const std::string& message) {notifier.sendNotification(message);}
};
最少知识原则(迪米特法则)
一个对象应该对其他对象有尽可能少的了解,只与直接的"朋友"通信:
// 不好的设计 - 违反迪米特法则
class Customer {
public:Wallet& getWallet() { return wallet; } // 暴露内部结构
private:Wallet wallet;
};class Cashier {
public:void checkout(Customer& customer, double amount) {// 直接访问客户的钱包(知道太多细节)if (customer.getWallet().getMoney() >= amount) {customer.getWallet().removeMoney(amount);}}
};// 好的设计 - 遵循迪米特法则
class Customer {
public:bool hasSufficientFunds(double amount) const {return wallet.getMoney() >= amount;}void pay(double amount) {if (hasSufficientFunds(amount)) {wallet.removeMoney(amount);}}private:Wallet wallet;
};class Cashier {
public:void checkout(Customer& customer, double amount) {// 只与Customer交互,不知道Wallet的存在if (customer.hasSufficientFunds(amount)) {customer.pay(amount);}}
};
开放/封闭原则(OCP)
软件实体(类、模块、函数等)应该对扩展开放,对修改封闭:
// 不好的设计 - 修改现有代码来添加新功能
class Shape {
public:enum Type { CIRCLE, RECTANGLE };Shape(Type t) : type(t) {}double area() const {switch (type) {case CIRCLE:return 3.14159 * radius * radius;case RECTANGLE:return width * height;// 添加新形状需要修改这里default:return 0;}}Type type;double radius; // 用于圆形double width; // 用于矩形double height; // 用于矩形
};// 好的设计 - 使用多态和接口
class Shape {
public:virtual double area() const = 0;virtual ~Shape() = default;
};class Circle : public Shape {
public:Circle(double r) : radius(r) {}double area() const override {return 3.14159 * radius * radius;}private:double radius;
};class Rectangle : public Shape {
public:Rectangle(double w, double h) : width(w), height(h) {}double area() const override {return width * height;}private:double width;double height;
};// 添加新形状不需要修改现有代码
class Triangle : public Shape {
public:Triangle(double b, double h) : base(b), height(h) {}double area() const override {return 0.5 * base * height;}private:double base;double height;
};
接口设计的高级技巧
1. 薄接口与胖接口
薄接口定义了最少的必要方法集,使实现更容易,但可能需要多个接口组合:
// 薄接口示例
class Movable {
public:virtual void move(double x, double y) = 0;virtual ~Movable() = default;
};class Resizable {
public:virtual void resize(double width, double height) = 0;virtual ~Resizable() = default;
};class Rotatable {
public:virtual void rotate(double angle) = 0;virtual ~Rotatable() = default;
};// 通过组合多个薄接口实现复杂功能
class GraphicalObject : public Movable, public Resizable, public Rotatable {// 实现所有接口方法
};
胖接口包含了更多的方法,减少了接口数量,但增加了实现的复杂性:
// 胖接口示例
class GraphicalObject {
public:virtual void move(double x, double y) = 0;virtual void resize(double width, double height) = 0;virtual void rotate(double angle) = 0;virtual void draw() = 0;virtual void highlight() = 0;virtual void hide() = 0;virtual void show() = 0;virtual ~GraphicalObject() = default;
};
通常,遵循接口隔离原则,薄接口是更好的选择。
2. 接口与抽象基类
纯接口(只有纯虚函数)和抽象基类(有一些实现的抽象类)的选择:
// 纯接口
class PureInterface {
public:virtual void method1() = 0;virtual void method2() = 0;virtual ~PureInterface() = default;
};// 抽象基类(带部分实现)
class AbstractBase {
public:virtual void method1() = 0; // 纯虚函数// 提供默认实现的虚函数virtual void method2() {// 默认实现method1(); // 可以调用纯虚函数}virtual ~AbstractBase() = default;
};
抽象基类可以通过提供公共代码来减少重复,同时仍然强制实现某些关键方法。
3. 接口继承与实现继承
区分接口继承(继承方法签名)和实现继承(继承方法实现):
class Interface {
public:virtual void method() = 0;virtual ~Interface() = default;
};// 接口继承 - 只继承接口,必须提供实现
class Pure : public Interface {
public:void method() override {// 提供完全独立的实现}
};class Base {
public:virtual void method() {// 默认实现}virtual ~Base() = default;
};// 实现继承 - 可以重用基类实现
class Derived : public Base {
public:void method() override {// 可以调用基类实现Base::method();// 添加额外的功能}
};
4. 接口组合与适配
将多个接口组合起来,形成更复杂的接口,或者通过适配器模式使不兼容的接口协同工作:
// 接口组合
class CompositeInterface : public Interface1, public Interface2, public Interface3 {// 实现所有接口方法
};// 接口适配
class LegacySystem {
public:void oldMethod(int value) {std::cout << "Legacy method called with " << value << std::endl;}
};class ModernInterface {
public:virtual void newMethod(const std::string& value) = 0;virtual ~ModernInterface() = default;
};// 适配器
class Adapter : public ModernInterface {
private:LegacySystem& legacy;public:Adapter(LegacySystem& sys) : legacy(sys) {}void newMethod(const std::string& value) override {// 将新接口调用转换为旧系统调用int intValue = std::stoi(value);legacy.oldMethod(intValue);}
};
5. 版本演化与接口扩展
接口需要随时间演化,但不应破坏现有实现。有多种方法可以安全地扩展接口:
// 方法1:使用默认参数
class Interface_v1 {
public:virtual void method(int a) = 0;virtual ~Interface_v1() = default;
};class Interface_v2 {
public:virtual void method(int a, int b = 0) = 0; // 添加带默认值的参数virtual ~Interface_v2() = default;
};// 方法2:创建扩展接口
class BasicInterface {
public:virtual void method1() = 0;virtual ~BasicInterface() = default;
};class ExtendedInterface : public BasicInterface {
public:virtual void method2() = 0; // 新方法
};// 方法3:使用可选接口查询(类似COM的QueryInterface)
class Interface1 {
public:virtual void method1() = 0;virtual ~Interface1() = default;
};class Interface2 {
public:virtual void method2() = 0;virtual ~Interface2() = default;
};class Implementation : public Interface1, public Interface2 {
public:void method1() override { /* 实现 */ }void method2() override { /* 实现 */ }template<typename T>T* queryInterface() {return dynamic_cast<T*>(this);}
};
实际应用案例
1. 数据访问接口
创建数据访问层的接口,允许轻松切换数据源或数据库:
// 数据访问接口
class UserRepository {
public:virtual bool save(const User& user) = 0;virtual bool remove(int userId) = 0;virtual std::optional<User> findById(int userId) = 0;virtual std::vector<User> findAll() = 0;virtual ~UserRepository() = default;
};// SQLite实现
class SQLiteUserRepository : public UserRepository {
private:sqlite3* db;public:SQLiteUserRepository(const std::string& dbPath);~SQLiteUserRepository();bool save(const User& user) override;bool remove(int userId) override;std::optional<User> findById(int userId) override;std::vector<User> findAll() override;
};// MongoDB实现
class MongoDBUserRepository : public UserRepository {
private:mongoc_client_t* client;public:MongoDBUserRepository(const std::string& connectionString);~MongoDBUserRepository();bool save(const User& user) override;bool remove(int userId) override;std::optional<User> findById(int userId) override;std::vector<User> findAll() override;
};// 内存实现(用于测试)
class InMemoryUserRepository : public UserRepository {
private:std::map<int, User> users;public:bool save(const User& user) override;bool remove(int userId) override;std::optional<User> findById(int userId) override;std::vector<User> findAll() override;
};// 使用示例
class UserService {
private:UserRepository& repository;public:UserService(UserRepository& repo) : repository(repo) {}void registerUser(const User& user) {// 业务逻辑...repository.save(user);}void deleteUser(int userId) {repository.remove(userId);}
};
2. 插件系统
设计插件系统接口,使应用可以动态加载和使用插件:
// 插件接口
class Plugin {
public:virtual std::string getName() const = 0;virtual std::string getVersion() const = 0;virtual void initialize() = 0;virtual void shutdown() = 0;virtual ~Plugin() = default;
};// 特定类型的插件接口
class ImageFilterPlugin : public Plugin {
public:virtual void applyFilter(Image& image) = 0;
};class AudioProcessorPlugin : public Plugin {
public:virtual void processAudio(AudioBuffer& buffer) = 0;
};// 插件加载器
class PluginLoader {
public:std::shared_ptr<Plugin> loadPlugin(const std::string& path) {void* handle = dlopen(path.c_str(), RTLD_LAZY);if (!handle) {throw std::runtime_error("Failed to load plugin: " + std::string(dlerror()));}// 获取创建插件的工厂函数using CreatePluginFunc = Plugin* (*)();auto createPlugin = reinterpret_cast<CreatePluginFunc>(dlsym(handle, "createPlugin"));if (!createPlugin) {dlclose(handle);throw std::runtime_error("Invalid plugin: createPlugin function not found");}return std::shared_ptr<Plugin>(createPlugin(), [handle](Plugin* p) {delete p;dlclose(handle);});}
};// 应用中使用插件
class Application {
private:std::vector<std::shared_ptr<Plugin>> plugins;public:void loadPlugins(const std::string& directory) {PluginLoader loader;// 加载目录中的所有插件文件...for (auto& plugin : plugins) {plugin->initialize();// 根据插件类型执行特定操作if (auto imageFilter = std::dynamic_pointer_cast<ImageFilterPlugin>(plugin)) {Image img;imageFilter->applyFilter(img);}}}void shutdown() {for (auto& plugin : plugins) {plugin->shutdown();}plugins.clear();}
};
3. GUI组件系统
为图形用户界面设计组件接口,支持各种UI控件:
// UI组件接口
class UIComponent {
public:virtual void render() = 0;virtual void handleEvent(const Event& event) = 0;virtual void setPosition(int x, int y) = 0;virtual void setSize(int width, int height) = 0;virtual bool isVisible() const = 0;virtual void setVisible(bool visible) = 0;virtual ~UIComponent() = default;
};// 容器接口
class Container : public UIComponent {
public:virtual void addComponent(std::shared_ptr<UIComponent> component) = 0;virtual void removeComponent(std::shared_ptr<UIComponent> component) = 0;virtual std::vector<std::shared_ptr<UIComponent>> getComponents() const = 0;
};// 按钮接口
class Button : public UIComponent {
public:virtual void setText(const std::string& text) = 0;virtual std::string getText() const = 0;virtual void setOnClickHandler(std::function<void()> handler) = 0;
};// 文本框接口
class TextField : public UIComponent {
public:virtual void setText(const std::string& text) = 0;virtual std::string getText() const = 0;virtual void setEditable(bool editable) = 0;virtual bool isEditable() const = 0;
};// 窗口类
class Window : public Container {// 窗口特有功能...
};// 面板类
class Panel : public Container {// 面板特有功能...
};// 各种UI组件的具体实现
class WindowsButton : public Button {// Windows风格的按钮实现
};class MacOSButton : public Button {// MacOS风格的按钮实现
};// UI工厂接口
class UIFactory {
public:virtual std::shared_ptr<Button> createButton() = 0;virtual std::shared_ptr<TextField> createTextField() = 0;virtual std::shared_ptr<Window> createWindow() = 0;virtual std::shared_ptr<Panel> createPanel() = 0;virtual ~UIFactory() = default;
};// 特定平台的工厂实现
class WindowsUIFactory : public UIFactory {
public:std::shared_ptr<Button> createButton() override {return std::make_shared<WindowsButton>();}// 其他组件的创建方法...
};class MacOSUIFactory : public UIFactory {
public:std::shared_ptr<Button> createButton() override {return std::make_shared<MacOSButton>();}// 其他组件的创建方法...
};
常见陷阱与解决方案
1. 接口膨胀
随着时间的推移,接口可能会因为新需求而不断膨胀,变得难以实现和维护:
// 问题 - 膨胀的接口
class UserService {
public:virtual User findById(int id) = 0;virtual std::vector<User> findAll() = 0;virtual void save(const User& user) = 0;virtual void delete(int id) = 0;virtual User findByUsername(const std::string& username) = 0;virtual std::vector<User> findByRole(Role role) = 0;virtual void sendWelcomeEmail(const User& user) = 0; // 不属于核心功能virtual void logLogin(int userId) = 0; // 不属于核心功能virtual bool validatePassword(const User& user, const std::string& password) = 0; // 不属于核心功能// 更多不相关的方法...
};
解决方案:应用接口隔离原则,将大接口分解为多个专注的小接口:
class UserRepository {
public:virtual User findById(int id) = 0;virtual std::vector<User> findAll() = 0;virtual void save(const User& user) = 0;virtual void delete(int id) = 0;virtual User findByUsername(const std::string& username) = 0;virtual std::vector<User> findByRole(Role role) = 0;
};class EmailService {
public:virtual void sendWelcomeEmail(const User& user) = 0;
};class AuthenticationService {
public:virtual bool validatePassword(const User& user, const std::string& password) = 0;
};class UserActivityLogger {
public:virtual void logLogin(int userId) = 0;
};
2. 接口不稳定
频繁更改接口会破坏现有的实现,导致大量代码需要更新:
// 版本1
class PaymentProcessor {
public:virtual bool processPayment(double amount) = 0;
};// 版本2 - 破坏性更改
class PaymentProcessor {
public:virtual bool processPayment(double amount, PaymentMethod method) = 0; // 添加了必需参数
};
解决方案:使用接口版本化或扩展接口,而不是修改现有接口:
// 原始接口保持不变
class PaymentProcessor_V1 {
public:virtual bool processPayment(double amount) = 0;virtual ~PaymentProcessor_V1() = default;
};// 新版本通过继承扩展
class PaymentProcessor_V2 : public PaymentProcessor_V1 {
public:virtual bool processPaymentWithMethod(double amount, PaymentMethod method) = 0;
};// 或者使用可选参数(如果适用)
class PaymentProcessor {
public:virtual bool processPayment(double amount, std::optional<PaymentMethod> method = std::nullopt) = 0;virtual ~PaymentProcessor() = default;
};
3. 过度使用继承
过度使用继承会导致脆弱的类层次结构:
// 问题 - 过度使用继承
class Animal { /* ... */ };
class Mammal : public Animal { /* ... */ };
class Bird : public Animal { /* ... */ };
class Bat : public Mammal, public Bird { /* ... */ }; // 多重继承带来的问题
解决方案:优先使用组合而非继承,或使用接口而非具体类继承:
// 定义能力接口
class Flyable {
public:virtual void fly() = 0;virtual ~Flyable() = default;
};class Walkable {
public:virtual void walk() = 0;virtual ~Walkable() = default;
};// 基于能力实现类
class Animal { /* 基本动物属性 */ };class Bird : public Animal, public Flyable, public Walkable {void fly() override { /* 实现飞行 */ }void walk() override { /* 实现行走 */ }
};class Bat : public Animal, public Flyable {void fly() override { /* 实现飞行 */ }
};
4. 接口依赖具体类型
在接口中直接使用具体类型会导致紧耦合:
// 问题 - 接口依赖具体类型
class ReportGenerator {
public:virtual void generateReport(SQLDatabase& db) = 0; // 依赖具体数据库类型
};
解决方案:接口应该依赖抽象,而非具体实现:
// 定义数据源接口
class DataSource {
public:virtual std::vector<Record> fetchData(const Query& query) = 0;virtual ~DataSource() = default;
};// 修改后的报告生成器接口
class ReportGenerator {
public:virtual void generateReport(DataSource& dataSource) = 0; // 依赖抽象接口
};// 具体数据库实现数据源接口
class SQLDatabase : public DataSource {
public:std::vector<Record> fetchData(const Query& query) override {// 从SQL数据库获取数据...}
};class MongoDBDatabase : public DataSource {
public:std::vector<Record> fetchData(const Query& query) override {// 从MongoDB获取数据...}
};
最佳实践总结
- 保持接口小巧单一:每个接口应该专注于单一功能集,避免"上帝接口"。
- 优先组合而非继承:通过组合多个小接口实现复杂功能,避免深层次继承。
- 依赖抽象而非具体:接口方法应该接收和返回抽象类型,避免依赖具体实现。
- 谨慎设计接口:接口一旦发布很难更改,要仔细考虑其稳定性和未来演化。
- 提供明确的文档:清晰说明接口的目的、前置条件和后置条件,以及使用约定。
- 考虑异常处理:明确定义接口可能抛出的异常,或使用返回值表示错误状态。
- 接口命名要明确:接口名称应该清晰地表达其功能或角色,如
Readable
、Logger
等。 - 避免暴露内部细节:接口应隐藏实现细节,只关注"做什么",而非"怎么做"。
- 提供默认实现:当有合理的默认行为时,考虑使用抽象基类提供部分实现。
- 关注性能影响:注意接口设计对性能的潜在影响,特别是虚函数调用和内存布局。
总结
在C++中设计良好的接口是创建可维护、灵活且可扩展软件系统的关键。虽然C++没有显式的接口关键字,但通过抽象类、纯虚函数和其他机制,我们可以实现强大而灵活的接口设计。
遵循单一职责原则、接口隔离原则、依赖倒置原则等设计原则,可以帮助我们创建更好的接口。同时,了解常见的接口设计陷阱和解决方案,对于避免创建难以维护的系统至关重要。
通过本文讨论的技术和最佳实践,您应该能够在C++项目中设计出更好的接口,创建出更加灵活、可维护和可扩展的软件系统。
在下一篇文章中,我们将探讨内存管理的基础知识,包括堆与栈的概念及其在C++中的应用。
这是我C++学习之旅系列的第十五篇技术文章。查看完整系列目录了解更多内容。
相关文章:
C++学习:六个月从基础到就业——面向对象编程:接口设计
C学习:六个月从基础到就业——面向对象编程:接口设计 本文是我C学习之旅系列的第十五篇技术文章,重点讨论在C中进行接口设计的原则、技术和最佳实践。查看完整系列目录了解更多内容。 引言 在面向对象的软件开发中,良好的接口设计…...
工作总结(十二)——迁移svn单项目到gitlab上,保留历史提交记录
文章目录 前言一、目的二、操作步骤1.创建项目库2.复制历史提交者账号3.复制待迁移项目以及历史记录4.push到gitlab远程仓库 总结 前言 本系列文章主要记录工作中一些需要记录的内容 一、目的 因为一些原因,我需要将svn库上的某个项目迁移到公司的gitlab库管理平台…...
PS中制作一张扣洞贴图
要在PS制作如下一张贴图,如下图所示 步骤: 1.首先复制一张图层 2.将最底层图层的透明度调整为0 3.选择画笔的模式为清除 4.设置画笔大小 5.选中需要清除的图层,然后就可以将图层的像素点清除了 6.导出成PNG文件即可 注࿱…...
STM32 HAL库 Freertos创建多任务
1. 引言 STM32F407 是 ST 公司推出的一款高性能微控制器,具有丰富的外设资源和强大的处理能力。HAL(Hardware Abstraction Layer)库是 ST 为其微控制器提供的硬件抽象层,它简化了硬件操作,提高了开发效率。FreeRTOS 是…...
android测试硬件工具 安卓硬件测试命令
Android开发常用ADB命令大全 在Android开发过程中,ADB(Android Debug Bridge)是一个非常重要的调试工具。掌握这些命令可以大大提高开发效率。如果你正在使用克魔开发助手(Keymob)这样的开发工具,你会发现它已经集成了很多ADB功能,让调试变得…...
第12篇:Linux程序访问控制FPGA端Switch<一>
Q:如何写.c代码访问读取FPGA端的滑动开关SW的值? A:DE1-SoC开发板上有10个滑动开关连接到DE1_SoC_Computer系统的并行输入端口,该端口只有一个10位只读Data寄存器映射到地址0xFF200040,对Data寄存器进行读操作并将读出…...
硬盘变废为宝!西部数据携微软等启动稀土回收 效率可达90%
快科技4月18日消息,西部数据(Western Digital)宣布,与微软、Critical Materials Recycling及PedalPoint Recycling携手,在美国启动一项跨产业前导计划-稀土回收。 目前,西部数据已经成功从报废硬盘&#x…...
元宇宙概念兴起,B 端数字孪生迎来哪些新机遇?
在科技飞速发展的当下,元宇宙概念如同一颗璀璨新星,迅速吸引了全球的目光。随着元宇宙的兴起,与之紧密相关的 B 端数字孪生技术也迎来了前所未有的发展机遇。元宇宙与 B 端数字孪生的融合,正悄然改变着多个行业的运作模式…...
用 NLP + Streamlit,把问卷变成能说话的反馈
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...
stl 容器 – map
stl 容器 – map 1. map 和 multimap的使用文档 参考文档 参考文档点这里哟 🌈 😘 2. map 类的介绍 map的声明如下 template < class Key, // map::key_type class T, // map::mapped_type class Compare less<Key>, // map::key_…...
20250417-vue-动态插槽名
动态指令参数在 v-slot 上也是有效的,即可以定义下面这样的动态插槽名: <base-layout><template v-slot:[dynamicSlotName]>...</template><!-- 缩写为 --><template #[dynamicSlotName]>...</template> </base…...
010301-cdn_waf-web扩展1-基础入门-网络安全
文章目录 1 WAF1.1WAF 的核心功能1.2 WAF 的部署类型1.3 WAF 的应用场景1.4 主流 WAF 产品1.5 如何选择 WAF?1.6 注意事项1.7 waf总结和演示 2 CDN2.1 核心原理2.2 关键功能2.3 典型应用场景2.4 优势2.5 主流CDN服务商2.6 技术实现2.7 注意事项2.8cdn安全测试和演示…...
CentOS7执行yum命令报错 Could not retrieve mirrorlist http://mirrorlist.centos.org
CentOS7执行yum命令报错 引更新yum源备份原有源创建新的源文件清理并重建缓存 引 CentOS 7 系统无法连接到 CentOS 的官方镜像站点。这通常是由于网络问题或 CentOS 7 已停止维护导致的(2024年6月30日后 CentOS 7 已进入 EOL) 报错明细: 已…...
在阿里云虚拟主机上启用WordPress伪静态
在阿里云虚拟主机上启用WordPress伪静态,需要根据虚拟主机的Web服务器类型(Nginx或Apache)进行相应的设置。以下是具体步骤: 1. 确认虚拟主机的Web服务器类型 登录阿里云虚拟主机管理控制台。 查看主机的配置信息,确认是使用Nginx还是Apac…...
【java 13天进阶Day06】Map集合,HashMapTreeMap,斗地主、图书管理系统,排序算法
Map集合 Collection是单值集合体系。 Map集合是另一个集合体系,是一种双列集合,每个元素包含两个值。 Map集合的每个元素的格式:keyvalue(键值对元素)。 Map集合也被称为“键值对集合”。 Map集合的完整格式:{key1value1 , ke…...
从代码学习深度学习 - 小批量随机梯度下降 PyTorch 版
文章目录 前言一、数据准备与处理1.1 数据集简介1.2 数据加载与预处理二、训练工具与辅助类三、可视化工具四、模型训练五、执行训练总结前言 深度学习是人工智能领域的核心技术之一,而小批量随机梯度下降(Mini-Batch Stochastic Gradient Descent, SGD)是训练神经网络的基…...
03、GPIO外设(三):标准库代码示例
标准库代码示例 1、点亮LED2、LED闪烁3、LED流水灯4、按键控制LED5、蜂鸣器 本章源代码链接: 链接: link 1、点亮LED 实验要求:点亮LED ①LED.c文件的代码如下: #include "LED.h"/*** LED引脚初始化*//* 定义数组,想要添加引脚…...
PyTorch 深度学习实战(37):分布式训练(DP/DDP/Deepspeed)实战
在上一篇文章中,我们探讨了混合精度训练与梯度缩放技术。本文将深入介绍分布式训练的三种主流方法:Data Parallel (DP)、Distributed Data Parallel (DDP) 和 DeepSpeed,帮助您掌握大规模模型训练的关键技术。我们将使用PyTorch在CIFAR-10分类…...
MCP系列之架构篇:深入理解MCP的设计架构
前言 在上一篇《MCP系列之基础篇》中,我们初步了解了MCP(模型上下文协议)的基本概念和价值。本篇文章将深入探讨MCP的技术架构,帮助开发者和技术爱好者更全面地理解这一协议的内部工作机制。我们将剖析MCP的核心组件、通信模型和工作流程,解析Host、Client和Server三者之…...
RT-Thread RTThread studio 初使用
RT-Thread Studio 下载 https://www.rt-thread.org/studio.html 安装使用 https://bbs.elecfans.com/jishu_2425653_1_1.html 4 编译问题解决 问题一:error: unknown type name clock_t 具体的类型值是在sys/_types.h中定义的,需要包含sys/_types.h 这个…...
设计模式 --- 外观模式
外观模式是一种结构型设计模式,为复杂子系统提供统一的高层接口,通过定义一个外观类来简化客户端与子系统的交互,降低系统耦合度。这种模式隐藏了子系统的复杂性,将客户端与子系统的实现细节隔离开来,…...
第十二节:原理深挖-React Fiber架构核心思想
链表结构、时间切片(Time Slicing) 优先级调度实现(如用户输入>网络请求) React Fiber架构深度解析:从链表到优先级调度的革命性升级 一、Fiber架构核心设计思想 React Fiber是React 16的底层协调算法重构&#x…...
利用DeepSeek设计一个HTML批量转换工具设计
需求词:需要设计一个能够批量转换HTML文件格式的网页在线工具,界面简洁易用 功能概述 设计一个网页在线工具,允许用户批量上传HTML文件并进行格式转换,包括: HTML美化/格式化 HTML压缩/最小化 HTML到XHTML转换 HT…...
TypeScript 从入门到精通:完整教程与实战应用(一)
1. TypeScript 简介 什么是 TypeScript? TypeScript 是 JavaScript 的超集,添加了静态类型系统,由微软开发并开源。它编译成纯 JavaScript 运行在任何 JavaScript 环境中。 为什么使用 TypeScript? 类型安全:在编译时…...
什么是Python单例模式
什么是Python单例模式 Python单例模式是一种创建型设计模式,目的是确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。以下从作用和示例进行介绍: 作用 控制资源使用:避免对系统资源的重复消耗,像数据库连接、文件句柄等稀缺资源,只创建一个实例来管理使用,防…...
PHP8.2.9NTS版本使用composer报错,扩展找不到的问题处理
使用composer install时报错: The openssl extension is required for SSL/TLS protection but is not available. If you can not enable the openssl extension, you can disable this error, at y our own risk, by setting the ‘disable-tls’ option to true.…...
memcache使用
Memcache 是一款高性能的分布式内存对象缓存系统,以下是其使用方法: 安装与配置 • 安装 Memcached :在 CentOS 7 系统中,可使用命令sudo yum install memcached进行安装,也可从源码编译安装,如下载 memca…...
旅游资源网站登录(jsp+ssm+mysql5.x)
旅游资源网站登录(jspssmmysql5.x) 旅游资源网站是一个为旅游爱好者提供全面服务的平台。网站登录界面简洁明了,用户可以选择以管理员或普通用户身份登录。成功登录后,用户可以访问个人中心,进行修改密码和个人信息管理。用户管理模块允许管…...
Nacos 中使用了哪些缓存?缓存的目的是什么?是如何实现的?
Nacos 在服务端和客户端都广泛的使用了缓存机制,下面着重介绍一下。 一、 Nacos 服务端缓存 (Server-Side Caching) Nacos 服务端缓存的主要目的是提高读取性能、降低对底层存储(数据库或磁盘文件)的压力,并加速对客户端请求的响…...
「GitHub热榜」AIGC系统源码:AI问答+绘画+PPT+音乐生成一站式
—零门槛搭建私有化AI内容工厂,源码开放商业落地指南 为什么全栈AIGC系统成为企业刚需? 1. 传统方案的致命缺陷 痛点 使用ChatGPTMidjourneyCanva 本全栈方案 工具割裂 需切换5平台 一个系统全搞定 成本 年费50万 一次部署永久免费 数据安全 …...
Dify LLM大模型参数(一)
深入了解大语言模型(LLM)的参数设置 模型的参数对模型的输出效果有着至关重要的影响。不同的模型会拥有不同的参数,而这些参数的设置将直接影响模型的生成结果。以下是 DeepSeek 模型参数的详细介绍: 温度(Tempera…...
快速使用工具Cursor
Cursor 是一款面向开发者的集成开发环境,用途如下: 代码编写:支持多语言,有语法高亮、智能补全与格式化功能。代码生成:能依据自然语言描述生成代码。协作开发:支持多人实时协作编辑项目。调试程序&#x…...
JVM之经典垃圾回收器
一、垃圾回收算法 1. 标记-清除(Mark-Sweep) 步骤: 标记:遍历对象图,标记所有存活对象。清除:回收未被标记的垃圾对象。 特点:简单,但会产生内存碎片。 2. 标记-复制(…...
【“星瑞” O6 评测】—NPU 部署 face parser 模型
前言 瑞莎星睿 O6 (Radxa Orion O6) 拥有高达 28.8TOPs NPU (Neural Processing Unit) 算力,支持 INT4 / INT8 / INT16 / FP16 / BF16 和 TF32 类型的加速。这里通过通过官方的工具链进行FaceParsingBiSeNet的部署 1. FaceParsingBiSeNet onnx 推理 首先从百度网盘…...
hadoop的三大结构及其各自的作用
Hadoop 主要有三大核心组件,分别是 HDFS(Hadoop Distributed File System)、MapReduce 和 YARN,以下是它们各自的作用: HDFS(Hadoop Distributed File System) 存储数据:HDFS 是一个…...
免费将静态网站部署到服务器方法(仅支持HTML,CSS,JS)
原视频链接:把HTML免费部署到网站上,实现别人也能访问的教程来啦QAQ_哔哩哔哩_bilibili 注意:仅支持HTML、CSS、JS。不支持Vue等框架。 1.打开网站www.wordpress.org 点击红框按钮 点击红框按钮下载wordpress模板文件并解压。 将自己编写的…...
2、SpringAI接入ChatGPT与微服务整合
2、SpringAI接入ChatGPT与微服务整合 小薛博客AI 大模型资料 1、SpringAI简介 https://spring.io/projects/spring-ai Spring AI是一个人工智能工程的应用框架。其目标是将Spring生态系统的设计原则(如可移植性和模块化设计)应用于人工智能领域&#…...
6.7.图的深度优先遍历(英文缩写DFS)
树是特殊的图,没有回路的图就是树 BFS与DFS的区别在于,BFS使用了队列,DFS使用了栈 一.深度优先遍历: 1.树的深度优先遍历: 树的深度优先遍历分为先根遍历和后根遍历。 以树的先根遍历为例: 上述图片里…...
若依同步企业微信架构及ACTIVITI
企业微信配置 1.进入企业微信后台管理应用管理,新建应用 2.配置网页授权及JS-SDK 将验证文件下载到项目的pubilc下 启动项目 npm run dev 然后验证域名归属 3.配置企业可信IP 公网IP地址即可,仅此IP的服务可调用企业微信接口信息 4.构造域名回调域&a…...
PyTorch分布式训练调试方法(跟踪调用过程)
PyTorch分布式训练调试方法(跟踪调用过程) 背景 在分布式深度学习训练场景中,通信操作(如AllReduce、Send/Recv)和CUDA操作的时序问题往往难以调试。本工具通过以下方式提供调试支持: 拦截所有PyTorch张量操作并记录调用栈监控分布式通信操作的完整生命周期自动生成带时间…...
跟我学C++中级篇——内存异常的分析
一、内存问题引发的现象 在实际使用电脑过程中,经常会遇到电脑卡住了,鼠标和键盘也没反应。这时候儿会怎么办?其实此时打开任务管理器,就可以发现,有几种情况:一是CPU占用过高;二是IO占用过高&…...
HTML新标签与核心 API 实战
HTML5 新标签与核心 API 实战 引言 今天咱们来看看HTML5 新标签以及一些核心 API。 HTML5 的引入彻底改变了 Web 前端开发格局,尤其是其新增的多媒体和交互能力标签。对于前端开发者而言,理解并掌握 <video>、<audio>、<canvas> 和…...
vscode 红色波浪线问题
VSCode 红色波浪线问题终极解决方案 问题描述 在编写 C 项目时,CMake 编译通过但代码出现红色波浪线,常见问题: #include 提示找不到头文件枚举或类型名未定义成员函数或变量无法识别 这些是 VSCode 的 IntelliSense 配置问题,…...
类型补充,scan 和数据库管理命令
Redis 数据类型 stream stream 就是一个队列(阻塞队列) Redis 作为一个消息队列的重要支撑,属于是 list 中 blpop 和 brpop 的升级版本 geospatial 主要用于存储地理位置信息,并对存储的信息进行操作 存储一些点后,…...
AI Agent系列(十) -Data Agent(数据分析智能体)开源资源汇总
AI Agent系列【十】 前言一、 Open Interpreter1.1 Open Interpreter的特点1.2 Open Interpreter的优势1.3 Open Interpreter的应用场景 二、DB-GPT2.1 核心能力2.2关键特性:2.3 系统架构 三、DeepBI3.1 特点 前言 DataAgent的能力本质上比较依赖大模型的自然语言转API/SQL/代…...
Vue3 + TypeScript中provide和inject的用法示例
基础写法(类型安全) typescript // parent.component.vue import { provide, ref } from vue import type { InjectionKey } from vue// 1. 定义类型化的 InjectionKey const COUNTER_KEY Symbol() as InjectionKey<number> const USER_KEY Sy…...
【国家能源集团生态协作平台-注册/登录安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
不确定与非单调推理的基本概念
前文我们讨论了建立在经典逻辑基础上的确定性推理,这是一种运用确定性知识进行的精确推理。同时,它又是一种单调性推理,即随着新知识的加人,推出的结论或证明了的命题将单调地增加。但是,人们通常是在信息不完善、不精确的情况下运用不确定性知识进行思维、求解问题的,推…...
系统架构设计师:计算机组成与体系结构(如CPU、存储系统、I/O系统)高效记忆要点、知识体系、考点详解、、练习题并提供答案与解析
计算机组成与体系结构高效记忆要点 从CPU、存储系统、I/O系统三大模块展开,结合高频考点与记忆技巧,有助于系统化掌握核心知识点。 一、CPU结构与工作原理 1. CPU的组成 核心组件: 运算器(ALU) :负责算术…...
FreeSWITCH中SIP网关(Gateway)操作
freeswitch是一款简单好用的VOIP开源软交换平台。 以下是一篇关于FreeSWITCH中SIP网关(Gateway)操作的技术指南,基于提供的官方文档内容整理: 一、网关生命周期管理 1. 创建新SIP Profile并启动 FreeSWITCH支持多SIP Profile&…...