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中进行接口设计的原则、技术和最佳实践。查看完整系列目录了解更多内容。 引言 在面向对象的软件开发中,良好的接口设计…...
花园灌溉问题
#include <bits/stdc.h> using namespace std;// 设置最大行列数(题目限制 n, m ≤ 100) const int N 104;// 标记某个格子是否已经被水浇灌 bool used[N][N];// 队列,用于 BFS,存储当前水源的位置 queue<pair<int,i…...
《AI大模型应知应会100篇》第22篇:系统提示词(System Prompt)设计与优化
第22篇:系统提示词(System Prompt)设计与优化 摘要 在大语言模型(LLM)应用中,系统提示词(System Prompt)是控制模型行为的核心工具之一。它不仅定义了模型的身份、角色和行为规范,还直接影响输…...
Jsp技术入门指南【六】jsp脚本原理及隐式对象
Jsp技术入门指南【六】jsp脚本原理及隐式对象 前言一、JSP 脚本元素1.1 声明1.2 表达式1.3 脚本标签 二、JSP 的隐式对象是什么三、隐式对象详解outrequestsessionapplicationconfigexception 前言 在之前的博客中,我们已经介绍了JSP的环境搭建、编译文件查找以及生…...
transient关键字深度解析
Java transient 关键字深度解析 1. 核心概念 (1) 基本定义 作用:标记字段不参与序列化 适用场景: 敏感数据(如密码、密钥) 临时计算字段 依赖运行时环境的字段(如Thread对象) (2) 语法示例 java public class User implements Serializable {private String username…...
Jsp技术入门指南【五】详细讲解jsp结构页面
Jsp技术入门指南【五】详细讲解jsp结构页面 前言一、JSP页面的结构二、JSP页面的部件1. 指令(核心控制部件)2. 动作(页面交互部件,了解即可)3. 脚本(Java逻辑嵌入部件) 三、JSP指令详解1.1 JSP指…...
Beyond Compare 30天评估到期 解决方法
Beyond Compare 30天评估到期 解决方法 一、问题二、解决办法2.1 第一步:打开注册表2.2 第二步:删除cacheID 三、效果 一、问题 Beyond Compare提示评估到期,重装也无效,只需简单两步,轻轻松松出困境。 二、解决办法…...
探索蓝桥杯:嵌入式开发技巧分享与实践
在信息技术飞速发展的今天,嵌入式系统作为物联网和智能设备的核心技术之一,正扮演着愈发重要的角色。蓝桥杯作为国内知名的科技竞赛平台,为广大学生和科技爱好者提供了展示自己嵌入式开发能力的舞台。在这场竞赛中,参赛者不仅需要…...
Arduino无线体感机器手——问题汇总
文章不介绍具体参数,有需求可去网上搜索。 特别声明:不论年龄,不看学历。既然你对这个领域的东西感兴趣,就应该不断培养自己提出问题、思考问题、探索答案的能力。 提出问题:提出问题时,应说明是哪款产品&a…...
学习设计模式《一》——简单工厂
一、基础概念 1.1、接口 简单的说:接口是【用来实现类的行为定义、约束类的行为】(即:定义可以做什么);接口可以包含【实例方法】、【属性】、【事件】、【索引器】或这四种成员类型的任意组合。 接口的优点࿱…...
python有序列表
您的代码整体结构良好,但存在一些关键错误和优化点。以下是对代码的详细评价及改进建议:---### 主要问题1. **add方法中的链表断裂问题**- **问题描述**:当向链表中间插入节点时,未正确设置新节点的next,导致后续节点丢…...
使用Lombok @Builder 收参报错提示没有无参构造方法的原因与解决办法
使用Lombok Builder 收参报错提示没有无参构造方法的原因与解决办法 类上加了Builder之后接口接收前端传来的参数报错:(no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) 1.解决办法…...
010数论——算法备赛
数论 模运算 一般求余都是对正整数的操作,如果对负数,不同编程语言结果可能不同。 C/javapythona>m,0<a%m<m-1 a<m,a%ma~5%32~-5%3 -21(-5)%(-3) -2~5%(-3)2-1正数:(ab)%m((a%m)(b%m))%m~正数ÿ…...
NAT、代理服务、内网穿透
NAT、代理服务、内网穿透 1、NAT1.1、NAT过程1.2、NAPT2、内网穿透3、内网打洞3、代理服务器3.1、正向代理3.2、反向代理1、NAT 1.1、NAT过程 之前我们讨论了IPv4协议中IP地址数量不充足的问题。NAT技术是当前解决IP地址不够用的主要手段,是路由器的一个重要功能。 NAT能够将…...
C# 点击导入,将需要的参数传递到弹窗的页面
点击导入按钮,获取本页面的datagridview标题的结构,并传递到导入界面。 新增一个datatable用于存储datagridview的caption和name,这里用的是devexpress组件中的gridview。 DataTable dt new DataTable(); DataColumn CAPTION …...
Linux 文件查找终极指南:find, locate, grep 等命令详解
在 Linux 系统管理和日常使用中,文件查找是一项不可或缺的基本技能。无论是寻找配置文件、查找日志文件中的特定错误,还是清理旧的临时文件,掌握高效的文件查找工具都能让你事半功倍。Linux 提供了多种强大的命令行工具来满足不同的查找需求。本文将详细介绍几个最常用、最强…...
嵌入式硬件常用总线接口知识体系总结和对比
0.前言 在嵌入式工程实现中,多多少少我们都使用过总线,各种各样的总线应用于不同场合,不同场景有不同的优势,但是我们在作为工程师过程中在如何选择项目合适的总线,根据什么来选?需要我们对项目全局和总线特征有所了解,本文目的就是对比多种总线的关键特征 我们在聊到…...
【unity实战】Unity动画层级(Animation Layer)的Sync同步和Timing定时参数使用介绍,同步动画层制作角色的受伤状态
文章目录 前言方案一:复制粘贴原有层级的状态机1、实现2、问题 方法二:勾选Sync同步动画层1、简单实现同步2、同步blend tree的问题3、动画状态的播放时长4、下层状态覆盖了上层状态 专栏推荐完结 前言 如何制作角色的受伤状态? 玩家角色在…...
Uniapp调用native.js使用经典蓝牙串口通讯方法及问题解决
本人尝试在uniapp环境下开发一款安卓应用,需要与使用经典蓝牙协议的设备进行串口通讯,而uniapp官方给出的蓝牙操作接口目前只支持BLE(低功耗蓝牙),用该接口无法正常获取到我想要连接的设备。 通过大量搜索,…...
C++23 新特性:行拼接前去除空白符 (P2223R2)
文章目录 1\. 什么是行拼接前去除空白符2\. 为什么需要这一特性3\. 示例代码输出结果 4\. 编译器支持5\. 优势与应用场景5.1 提高代码可读性5.2 减少潜在错误5.3 适用于多行字符串 6\. 其他相关特性7\. 总结 C 语言一直在不断进化,以满足现代软件开发的需求。C23 标…...
Windows 11设置开机自动运行 .jar 文件
Windows 11设置开机自动运行 .jar 文件 打开启动文件夹: 按下 Win R,输入 shell:startup,回车。 此路径为当前用户的启动文件夹: C:\Users\<用户名>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup创…...
【通过Zadig给鼠标适配器安装驱动后,鼠标动不了,无法恢复的解决办法】
【通过Zadig给鼠标适配器安装驱动后,鼠标动不了,无法恢复的解决办法 问题产生缘由感谢这位大佬提供的解决办法解决办法 问题产生缘由 通过Zadig给鼠标适配器安装USB GAMING MOUSE这个驱动后,鼠标动不了,无法恢复(重启电脑,卸载鼠标驱动再重装也不可以), 不过还好,我用的是笔记…...
GoogleCodeUtil.java
Google动态验证码实现 GoogleCodeUtil.java package zwf;import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.SecureRandom;/** https://mvnrepository.com/artifact/commons-codec/…...
Maven 简介(图文)
Maven 简介 Maven 是一个Java 项目管理和构建的工具。可以定义项目结构、项目依赖,并使用统一的方式进行自动化构建,是Java 项目不可缺少的工具。 Maven 的作用 提供标准化的项目结构:以前不同的开发工具创建的项目结构是不一样的…...
JESD204B标准及其在高速AD采集系统中的应用详解
一、JESD204B协议的本质与核心价值 JESD204B是由JEDEC制定的第三代高速串行接口标准(2011年发布),专为解决高速ADC/DAC与FPGA/ASIC间数据传输瓶颈而设计。其核心突破体现在: 速率革命性提升 支持每通道最高12.5Gbps(通…...
天梯赛数据结构合集
1.集合操作:PTA | 程序设计类实验辅助教学平台 主要是注意set的取交集操作,AC代码: #include<bits/stdc.h> using namespace std; int n,m,k; set<int> a[60]; int main(){cin>>n;for(int i1;i<n;i){cin>>m;for…...
2025Github介绍与注册(有图片讲解,保姆级)
为什么要注册Github账号 利于团队协作,特别是打比赛的队友 版本控制强大,代码安全 开源项目多,方便个人模仿或抄袭 方便托管,形成自动化工具链 教育福利,教育参与者暂时免费 讲解完了优势,下面讲注册 Gith…...
决战浏览器渲染:减少重绘(Repaint)与重排(Reflow)的性能优化策略
在现代Web开发中,流畅的用户体验是衡量应用质量的关键指标之一。用户与界面的每一次交互,背后都牵动着浏览器复杂而精密的渲染过程。当这个过程不够高效时,用户就会感受到卡顿、延迟,甚至页面“掉帧”。在众多影响渲染性能的因素中…...
好数对的数目
题目描述 给你一个整数数组 nums。 如果一组数字 (i, j) 满足 nums[i] nums[j] 且 i < j,就可以认为这是一组 好数对。 返回 好数对 的数目。 示例 示例 1: 输入:nums [1,2,3,1,1,3] 输出:4 解释: 有 4 组好…...
C++ STL编程-vector概念、对象创建
vector 概念:是常见的一种容器,被称为“柔性数组”。 在vector中,front()是数组中的第一个元素,back()是数组的最后一个元素。begin()是是指向第一个元素,end()是指向back()的后一个元素 vector的对象创建࿰…...
