设计模式学习

1、单例模式

适用场景: 配置管理器、日志系统、连接池------真正只需要一个实例的地方。

单例是我踩坑最多的模式,没有之一。

现在我写单例,只有这一种写法:

复制代码
class Database {
public:
    static Database& instance() {
        static Database db;  // C++11保证线程安全
        return db;
    }
    Database(const Database&) = delete;
    Database& operator=(const Database&) = delete;
    Database(Database&&) = delete;
    Database& operator=(Database&&) = delete;

private:
    Database() = default;
    ~Database() = default;
};

C++11 §6.7规定:static局部变量的初始化是线程安全的,编译器生成guard变量,保证只有一个线程执行初始化。这就是Meyers' Singleton,比double-checked locking简洁、正确、零锁开销。

= delete禁用拷贝和移动,不然有人写auto db = Database::instance()就创建副本了,单例就破了。

2、工厂模式

适用场景: 运行时决定创建哪种对象。

2008 年我写过一个对象工厂,返回裸指针。调用方忘记delete,内存泄漏。另一个调用方delete了两次,double free,程序崩溃。

后来我改成了unique_ptr:

复制代码
class Button {
public:
    virtual ~Button() = default;
    virtual void render() const = 0;
};

class WinButton :public Button {
public:
    void render() const override {
        std::cout << "Rendering Windows button\n";
    }
};

class MacButton :public Button {
public:
    void render() const override {
        std::cout << "Rendering macOS button\n";
    }
};

// 工厂函数:返回unique_ptr
std::unique_ptr<Button> createButton(const std::string& platform) {
    if (platform == "windows") returnstd::make_unique<WinButton>();
    if (platform == "linux") returnstd::make_unique<LinuxButton>();
    throwstd::invalid_argument("Unknown platform");
}

// 使用
auto btn = createButton("windows");
btn->render();

unique_ptr明确所有权:调用者拿到独占所有权,析构时自动释放。没有裸指针泄漏,没有double free。

简单工厂 vs 工厂方法: 类型只有2-3种,用简单工厂(if/switch)就行。需要开闭原则、需要运行时扩展,才用工厂方法。

3、抽象工厂

适用场景: 创建一族相关对象。

复制代码
class Button {
public:
    virtual ~Button() = default;
    virtual std::string render() const = 0;
};

class Checkbox {
public:
    virtual ~Checkbox() = default;
    virtual std::string render() const = 0;
};

// 抽象工厂:一组产品的创建接口
class GUIFactory {
public:
    virtual ~GUIFactory() = default;
    virtual std::unique_ptr<Button> createButton() const = 0;
    virtual std::unique_ptr<Checkbox> createCheckbox() const = 0;
};

class WinFactory :public GUIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        returnstd::make_unique<WinButton>();
    }
    std::unique_ptr<Checkbox> createCheckbox() const override {
        returnstd::make_unique<WinCheckbox>();
    }
};

class MacFactory :public GUIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        returnstd::make_unique<MacButton>();
    }
    std::unique_ptr<Checkbox> createCheckbox() const override {
        returnstd::make_unique<MacCheckbox>();
    }
};

// 使用:保证一族产品的风格一致
void createUI(const GUIFactory& factory) {
    auto btn = factory.createButton();
    auto cb  = factory.createCheckbox();
    // 两者风格一致:要么都是Windows,要么都是macOS
}

和工厂方法的区别: 工厂方法创建一种产品,抽象工厂创建一族产品。产品只有一种,用工厂方法;产品一族需要风格一致,用抽象工厂。

4、观察者模式

适用场景: 事件通知、GUI控件、消息总线。

复制代码
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& event) = 0;
};

class EventChannel {
    std::vector<std::weak_ptr<Observer>> observers_;
public:
    void subscribe(std::shared_ptr<Observer> obs) {
        observers_.push_back(obs);
    }

    void publish(const std::string& event) {
        for (auto it = observers_.begin(); it != observers_.end(); ) {
            if (auto obs = it->lock()) {
                obs->update(event);
                ++it;
            } else {
                it = observers_.erase(it);  // 观察者已销毁,自动清理
            }
        }
    }
};

weak_ptr是关键: Subject用weak_ptr持有Observer,不参与引用计数。Observer销毁后weak_ptr::lock()返回空,自动清理。如果用shared_ptr双向持有,永远不析构。

5、策略模式

适用场景: 算法有多种变体,需要运行时切换。

2012年我写排序模块,最初是 if-else:

复制代码
void sort(std::vector<int>& data, const std::string& type) {
    if (type == "quick") { /* quick sort */ }
    else if (type == "merge") { /* merge sort */ }
}

后来发现每次加新算法都要改这个函数,耦合太紧。重构的时候自然就想到了策略模式:

复制代码
class Sorter {
    std::function<void(std::vector<int>&)> strategy_;
public:
    explicit Sorter(std::function<void(std::vector<int>&)> s)
        : strategy_(std::move(s)) {}
    void sort(std::vector<int>& data) { strategy_(data); }
    void setStrategy(std::function<void(std::vector<int>&)> s) {
        strategy_ = std::move(s);
    }
};

// 直接传lambda,不需要定义接口
Sorter sorter([](auto& v) { std::sort(v.begin(), v.end()); });

std::function和lambda就是策略模式的原生实现。std::sort本身就是策略模式。 无状态的策略,用lambda就够了;有状态的策略,才用类封装。

6、建造者模式

适用场景: 参数超过3个,或者对象有多个可选配置项。

复制代码
class HttpRequest {
    std::string method_;
    std::string url_;
    std::map<std::string, std::string> headers_;
    std::string body_;

public:
    class Builder {
        HttpRequest req_;
    public:
        Builder& method(std::string m) { req_.method_ = std::move(m); return *this; }
        Builder& url(std::string u) { req_.url_ = std::move(u); return *this; }
        Builder& header(std::string key, std::string val) {
            req_.headers_[std::move(key)] = std::move(val);
            return *this;
        }
        Builder& body(std::string b) { req_.body_ = std::move(b); return *this; }
        HttpRequest build() { returnstd::move(req_); }
    };

    static Builder create() { return Builder{}; }
};

auto req = HttpRequest::create()
    .method("POST")
    .url("https://api.example.com/data")
    .header("Content-Type", "application/json")
    .body(R"({"name":"Alice"})")
    .build();

三个要点:

  1. std::move在setter里 ------链式调用中参数通常是临时对象,std::move直接转移内部buffer,零拷贝。

  2. build()返回值而非引用------调用者拿到独立对象,Builder不能复用。

  3. C++20简化------简单结构体连Builder都不用写:

    struct Point { double x = 0; double y = 0; double z = 0; };
    Point p{.x = 1.0, .z = 3.0}; // designated initializers

参数大于3个且有不少可选参数时用Builder,否则构造函数+默认值更直接。

7、原型模式

适用场景: 创建成本高,需要基于已有对象复制。

复制代码
class Shape {
public:
    virtual ~Shape() = default;
    virtual std::unique_ptr<Shape> clone() const = 0;
    virtual std::string describe() const = 0;
};

class Circle :public Shape {
    double radius_;
public:
    explicit Circle(double r) : radius_(r) {}
    std::string describe() const override {
        return"Circle(r=" + std::to_string(radius_) + ")";
    }
    std::unique_ptr<Shape> clone() const override {
        returnstd::make_unique<Circle>(*this);  // 利用拷贝构造
    }
};

拷贝构造函数天然支持原型make_unique<Circle>(*this)调用Circle的拷贝构造函数。

切片问题: Shape s = *this只拷贝了Shape部分,派生类数据丢失。必须通过虚函数clone返回正确的类型。

原型本质上是一种工厂。

8、适配器模式

适用场景: 新老接口对接,接口不兼容但功能等价。

复制代码
// 旧接口
class LegacyLogger {
public:
    void logMessage(const std::string& msg, int level) {
        printf("[Legacy][%d] %s\n", level, msg.c_str());
    }
};

// 新接口
class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void info(const std::string& msg) = 0;
    virtual void warn(const std::string& msg) = 0;
    virtual void error(const std::string& msg) = 0;
};

// 对象适配器(组合方式,推荐)
class LoggerAdapter :public ILogger {
    LegacyLogger legacy_;
public:
    void info(const std::string& msg) override { legacy_.logMessage(msg, 0); }
    void warn(const std::string& msg) override { legacy_.logMessage(msg, 1); }
    void error(const std::string& msg) override { legacy_.logMessage(msg, 2); }
};

对象适配器 vs 类适配器: 对象适配器用组合,更灵活,可以运行时替换被适配对象。类适配器用继承,能访问protected成员,但耦合度高。

适配逻辑简单时,std::function+lambda就能搞定,不需要写类。

9、装饰器模式

适用场景: 运行时动态添加职责。

复制代码
class Coffee {
public:
    virtual ~Coffee() = default;
    virtual double cost() const = 0;
    virtual std::string description() const = 0;
};

class Espresso :public Coffee {
public:
    double cost() const override { return1.99; }
    std::string description() const override { return"Espresso"; }
};

class CoffeeDecorator :public Coffee {
protected:
    std::unique_ptr<Coffee> coffee_;
public:
    explicit CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee_(std::move(c)) {}
};

class MilkDecorator :public CoffeeDecorator {
public:
    using CoffeeDecorator::CoffeeDecorator;
    double cost() const override { return coffee_->cost() + 0.50; }
    std::string description() const override {
        return coffee_->description() + " + Milk";
    }
};

// 使用:链式装饰
auto order = std::make_unique<MilkDecorator>(
    std::make_unique<MochaDecorator>(
        std::make_unique<Espresso>()
    )
);
// cost = 1.99 + 0.70 + 0.50 = 3.19

装饰器 vs 继承: 继承是静态的,编译时确定;装饰器是动态的,运行时组合。3种咖啡×4种配料=12种组合,用继承要写12个子类,用装饰器只需4个装饰器类。

unique_ptr持有被装饰对象,所有权沿装饰链传递,最外层销毁时整条链自动释放。装饰链三层以内,太长调试困难。

10、代理模式

适用场景: 懒加载、访问控制、缓存。

复制代码
class Image {
public:
    virtual ~Image() = default;
    virtual void display() const = 0;
};

class RealImage :public Image {
    std::string filename_;
public:
    explicit RealImage(std::string filename) : filename_(std::move(filename)) {
        std::cout << "  Loading " << filename_ << " from disk...\n";
    }
    void display() const override {
        std::cout << "  Displaying " << filename_ << "\n";
    }
};

class ImageProxy :public Image {
    mutablestd::unique_ptr<RealImage> real_;  // mutable关键!
    std::string filename_;
public:
    explicit ImageProxy(std::string filename) : filename_(std::move(filename)) {}

    void display() const override {
        if (!real_) {
            real_ = std::make_unique<RealImage>(filename_);
        }
        real_->display();
    }
};

mutable关键字display()const方法,但需要懒加载修改real_mutable允许在const函数中修改标记为mutable的成员。

智能指针本身就是代理std::shared_ptr是引用计数代理,std::unique_ptr是独占所有权代理。

对象创建成本低则没必要用代理,懒加载才值得。

11、桥接模式

适用场景: 两个维度独立变化。

复制代码
// 实现接口
class Renderer {
public:
    virtual ~Renderer() = default;
    virtual void renderCircle(float x, float y, float radius) = 0;
};

class OpenGLRenderer :public Renderer {
public:
    void renderCircle(float x, float y, float radius) override {
        std::cout << "OpenGL: circle(" << x << "," << y << ") r=" << radius << "\n";
    }
};

// 抽象层
class Shape {
protected:
    std::unique_ptr<Renderer> renderer_;
public:
    explicit Shape(std::unique_ptr<Renderer> r) : renderer_(std::move(r)) {}
    virtual ~Shape() = default;
    virtual void draw() = 0;
};

class Circle :public Shape {
    float x_, y_, radius_;
public:
    Circle(float x, float y, float r, std::unique_ptr<Renderer> renderer)
        : Shape(std::move(renderer)), x_(x), y_(y), radius_(r) {}
    void draw() override { renderer_->renderCircle(x_, y_, radius_); }
};

// 使用:形状和渲染器独立变化
auto c1 = std::make_unique<Circle>(10, 20, 5.0, std::make_unique<OpenGLRenderer>());
auto c2 = std::make_unique<Circle>(30, 40, 8.0, std::make_unique<VulkanRenderer>());

桥接解决什么: 两个维度独立变化时,用继承需要M×N个子类。用桥接只需要M+N个类。

unique_ptr要求虚析构 :如果Renderer没有virtual ~Renderer()unique_ptr销毁时子类资源泄漏。这是C++最常见的UB之一。

桥接 vs 策略: 结构类似,意图不同。桥接分离两个维度,策略替换算法。

12、外观模式

适用场景: 简化复杂子系统的接口---。

复制代码
class VideoEncoder {
public:
    std::string encode(const std::string& file, const std::string& format) {
        return file + "." + format;
    }
};

class AudioEncoder {
public:
    std::string encode(const std::string& file, const std::string& format) {
        return file + "_audio." + format;
    }
};

// Facade:一个入口搞定所有
class VideoConverter {
    VideoEncoder videoEncoder_;
    AudioEncoder audioEncoder_;
public:
    std::string convert(const std::string& input, const std::string& output,
                        const std::string& subtitleFile = "") {
        auto video = videoEncoder_.encode(input, "mp4");
        audioEncoder_.encode(input, "aac");
        std::cout << "Converted to " << output << "\n";
        return video;
    }
};

VideoConverter converter;
converter.convert("input.avi", "output.mp4");

外观不是封装: 底层类仍然可以独立使用。外观只是提供了一个简化入口。

FFmpeg封装为例: avformat_open_input+avcodec_find_decoder+av_read_frame这一串,对外就一个convert(input, output)

外观 vs 适配器: 外观简化复杂接口(多对一),适配器转换不兼容接口(一对一)。

13、命令模式

适用场景: 需要撤销/重做、命令排队、事务。

复制代码
class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
    virtual void undo() = 0;
};

class Document {
    std::string content_;
public:
    void insert(size_t pos, const std::string& text) {
        content_.insert(std::min(pos, content_.size()), text);
    }
    void erase(size_t pos, size_t len) {
        content_.erase(pos, std::min(len, content_.size() - pos));
    }
    std::string content() const { return content_; }
};

class InsertCommand :public Command {
    Document& doc_;
    size_t pos_;
    std::string text_;
public:
    InsertCommand(Document& doc, size_t pos, std::string text)
        : doc_(doc), pos_(pos), text_(std::move(text)) {}
    void execute() override { doc_.insert(pos_, text_); }
    void undo() override { doc_.erase(pos_, text_.length()); }
};

class CommandHistory {
    std::vector<std::unique_ptr<Command>> history_;
public:
    void execute(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history_.push_back(std::move(cmd));
    }
    void undo() {
        if (!history_.empty()) {
            history_.back()->undo();
            history_.pop_back();
        }
    }
};

// 使用
Document doc;
CommandHistory history;
history.execute(std::make_unique<InsertCommand>(doc, 0, "Hello"));
history.execute(std::make_unique<InsertCommand>(doc, 5, " World"));
history.undo();  // 撤销

undo的关键: 每个命令在execute时记录足够的状态,undo时能完全恢复。

不需要undo时更简单: std::function<void()>队列就够,不需要完整继承体系。

14、模板方法模式

适用场景: 算法骨架固定,部分步骤子类定制。

复制代码
class DataProcessor {
public:
    // 模板方法:定义算法骨架
    void process(const std::string& input) {
        auto raw = readData(input);
        auto validated = validate(raw);
        auto transformed = transform(validated);
        writeData(transformed);
    }

protected:
    virtual std::string validate(const std::string& data) = 0;
    virtual std::string transform(const std::string& data) = 0;

    std::string readData(const std::string& input) { return input; }
    void writeData(const std::string& data) { std::cout << "Output: " << data << "\n"; }
};

class CsvProcessor :public DataProcessor {
protected:
    std::string validate(const std::string& data) override {
        if (data.empty()) return"name,value\n" + data;
        return data;
    }
    std::string transform(const std::string& data) override {
        std::string r;
        for (char c : data) if (c != ' ') r += c;
        return r;
    }
};

**骨架方法标记为final**:子类不应该能覆盖process()这个骨架方法。

复制代码
template<typename Derived>
class DataProcessorBase {
public:
    void process(const std::string& input) {
        auto v = derived().validate(input);
        auto t = derived().transform(v);
        std::cout << "Output: " << t << "\n";
    }
private:
    Derived& derived() { returnstatic_cast<Derived&>(*this); }
};

class MyProcessor :public DataProcessorBase<MyProcessor> {
public:
    std::string validate(const std::string& data) { return data; }
    std::string transform(const std::string& data) { return data; }
};

CRTP的好处:编译期分发,没有虚函数表,没有运行时开销。坏处:代码更复杂,错误信息更难读。

15、状态模式

适用场景: 状态转换逻辑复杂,用if/else难以维护。

复制代码
class State {
public:
    virtual ~State() = default;
    virtual void insertCoin(class VendingMachine& m) = 0;
    virtual void dispense(class VendingMachine& m) = 0;
};

class NoCoinState :public State {
public:
    void insertCoin(VendingMachine& m) override;
    void dispense(VendingMachine& m) override;
};

class HasCoinState :public State {
public:
    void insertCoin(VendingMachine& m) override;
    void dispense(VendingMachine& m) override;
};

class VendingMachine {
    std::unique_ptr<State> state_;
    int count_ = 5;
public:
    VendingMachine() : state_(std::make_unique<NoCoinState>()) {}
    void setState(std::unique_ptr<State> s) { state_ = std::move(s); }
    void insertCoin() { state_->insertCoin(*this); }
    void dispense() { state_->dispense(*this); }
};

void NoCoinState::insertCoin(VendingMachine& m) {
    m.setState(std::make_unique<HasCoinState>());
}
void HasCoinState::dispense(VendingMachine& m) {
    m.setState(std::make_unique<NoCoinState>());
}

状态模式 vs 策略模式: 结构相同,意图不同。

std::variant实现无堆分配的状态机:

复制代码
#include <variant>

struct NoCoin {};
struct HasCoin {};

class VendingMachine {
    std::variant<NoCoin, HasCoin> state_{NoCoin{}};
public:
    void insertCoin() {
        std::visit(overloaded{
            [&](NoCoin)  { state_ = HasCoin{}; },
            [&](HasCoin) { /* already has coin */ }
        }, state_);
    }
};

std::variant的优势:编译期穷举所有状态,遗漏状态编译器报错。零堆分配,性能更好。但写法更复杂,适合状态数量少且固定的场景。

16、责任链模式

适用场景: 请求经过多个处理器,每个决定处理或传递。

复制代码
class Handler {
    std::unique_ptr<Handler> next_;
public:
    virtual ~Handler() = default;
    void setNext(std::unique_ptr<Handler> h) { next_ = std::move(h); }
    void handleRequest(int request) {
        if (canHandle(request)) {
            process(request);
        } elseif (next_) {
            next_->handleRequest(request);
        }
    }
protected:
    virtual bool canHandle(int request) const = 0;
    virtual void process(int request) = 0;
};

class LowHandler :public Handler {
protected:
    bool canHandle(int request) const override { return request < 10; }
    void process(int request) override { std::cout << "Low: " << request << "\n"; }
};

// 使用
auto h3 = std::make_unique<HighHandler>();
auto h2 = std::make_unique<MidHandler>();
h2->setNext(std::move(h3));
auto h1 = std::make_unique<LowHandler>();
h1->setNext(std::move(h2));

h1->handleRequest(5);   // Low处理
h1->handleRequest(15);  // Mid处理
h1->handleRequest(25);  // High处理

std::function链:

复制代码
class Pipeline {
    std::vector<std::function<bool(int&)>> handlers_;
public:
    Pipeline& add(std::function<bool(int&)> h) {
        handlers_.push_back(std::move(h)); return *this;
    }
    void process(int& request) {
        for (auto& h : handlers_) {
            if (h(request)) return;
        }
    }
};

链太长考虑哈希分发或表驱动。

17、RAII

不属于GoF,但最重要。

适用场景: 所有资源管理------内存、文件、锁、连接。

2013年我接手一个老项目,代码里到处是new/delete、fopen/fclose、lock/unlock。内存泄漏、文件句柄泄漏、死锁,应有尽有。

我花了三个月才把这些全改成RAII。改完之后,代码行数少了30%,bug少了80%。

复制代码
class FileHandle {
    std::FILE* file_;
public:
    explicit FileHandle(const char* path, const char* mode)
        : file_(std::fopen(path, mode)) {
        if (!file_) throw std::runtime_error("Cannot open file");
    }
    ~FileHandle() { if (file_) std::fclose(file_); }
    FileHandle(const FileHandle&) = delete;
    FileHandle(FileHandle&& other) noexcept : file_(other.file_) {
        other.file_ = nullptr;
    }
};

构造时获取资源,析构时释放资源。 函数结束自动关闭,抛异常也会关闭。

不会RAII,不算会C++。这话我说了十年,不接受反驳。

18、Pimpl惯用法(C++特有)

适用场景: 头文件频繁变动,不想触发大量重编译。

复制代码
class Widget {
public:
    Widget();
    ~Widget();
    Widget(const Widget&);
    Widget& operator=(const Widget&);
    Widget(Widget&&) noexcept;
    Widget& operator=(Widget&&) noexcept;
    void doSomething();
private:
    struct Impl;
    std::unique_ptr<Impl> pimpl_;  // 隐藏所有实现细节
};

struct Widget::Impl {
    std::string name_;
    int value_ = 0;
    std::vector<int> data_;
};

Widget::Widget() : pimpl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default;  // 必须在.cpp中定义
Widget::Widget(const Widget& other) : pimpl_(std::make_unique<Impl>(*other.pimpl_)) {}

要点: 修改Impl结构体不需要重新编译所有包含Widget.h的文件。大型项目中,一个头文件的改动可能触发几千个文件重编译,Pimpl把这个隔离到.cpp里。

析构函数必须在.cpp中定义 :如果析构函数在.h中默认(= default),编译器在所有包含.h的文件中都需要Impl的完整定义来生成unique_ptr的析构代码。

19、类型擦除(C++特有)

适用场景: 不同类型放同一容器,且无法/不愿共享基类。

复制代码
class Drawable {
    struct Concept {
        virtual ~Concept() = default;
        virtual void draw() const = 0;
        virtual std::unique_ptr<Concept> clone() const = 0;
    };

    template<typename T>
    struct Model : Concept {
        T obj_;
        explicit Model(T obj) : obj_(std::move(obj)) {}
        void draw() const override { obj_.draw(); }
        std::unique_ptr<Concept> clone() const override {
            returnstd::make_unique<Model<T>>(obj_);
        }
    };

    std::unique_ptr<Concept> pimpl_;
public:
    template<typename T>
    Drawable(T obj) : pimpl_(std::make_unique<Model<T>>(std::move(obj))) {}
    Drawable(const Drawable& other) : pimpl_(other.pimpl_->clone()) {}
    Drawable(Drawable&&) noexcept = default;
    void draw() const { pimpl_->draw(); }
};

// 任意类型,只要有draw()方法就行
class Circle {public: void draw() const { std::cout << "Circle\n"; } };
class Square {public: void draw() const { std::cout << "Square\n"; } };

// 使用:不同类型放进同一个容器
std::vector<Drawable> shapes;
shapes.emplace_back(Circle{});
shapes.emplace_back(Square{});
for (constauto& s : shapes) s.draw();

std::function本身就是类型擦除std::function<void()>能存lambda、函数指针、函数对象,调用方不需要知道具体类型。

类型擦除 vs 虚函数继承: 类型擦除不需要被包装类型继承任何基类,只要是鸭子类型就行

相关推荐
小小编程路10 小时前
C++ 常用逻辑运算符
开发语言·c++·算法
乐观的山里娃11 小时前
【后编码时代 06】Vibe Coding + Superpowers 完全不够
设计模式·软件工程·ai编程
‎ദ്ദിᵔ.˛.ᵔ₎11 小时前
C++ 智能指针
开发语言·c++
likerhood12 小时前
设计模式 · 责任链模式(Chain of Responsibility Pattern)
设计模式·责任链模式
Lumbrologist12 小时前
【C++】零基础入门 · 第 4 节:循环结构(while、for、do-while)
开发语言·c++
我命由我1234512 小时前
Android Framework P4 - ServiceManager 进程
android·c语言·c++·visualstudio·android studio·android-studio·android runtime
叶子野格12 小时前
《C语言学习:编程例题》B
c语言·开发语言·c++·学习
郝学胜-神的一滴13 小时前
Qt 高级开发014 :信号槽connect函数精讲
开发语言·c++·qt·开源软件·用户界面
Shadow(⊙o⊙)13 小时前
文件-语言-系统:基础IO-2.0——IO重定向接口,语言层缓冲区,系统级缓冲区。内核级分析!
linux·运维·服务器·开发语言·c++