【C++复习】四种类型转换 + 常见设计模式 + Redis/MySQL(后端面试复习完整版)

【C++入门精讲22】四种类型转换 + 常见设计模式 + Redis/MySQL(后端面试复习完整版)

前言 :本篇在原《【C++入门精讲22】常见设计模式》的基础上做了大幅扩充:新增 C++ 四种显式类型转换 、代理模式 ,并补入后端面试高频的 Redis / MySQL 复习要点。原有章节的知识点与代码全部保留,代码中的明显笔误已顺手修正(sever→server、intance→instance)。


一、知识点总回顾(移动语义 + 智能指针)

核心知识点

  1. 移动语义

    • 作用:转移对象资源所有权,避免低效深拷贝,提升性能
    • 触发方式:std::move() 将对象转为右值引用
    • 核心函数:移动构造函数 类名(类名&&)、移动赋值重载 返回值 operator=(类名&&)
    • 编译器默认生成函数:无参构造、拷贝构造、移动构造、拷贝赋值、移动赋值、析构函数
  2. 智能指针(RAII 资源管理)

    • 核心思想:利用类生命周期自动管理堆裸指针,构造获取资源,析构释放资源,防止内存泄漏
    • 自定义智能指针必备接口:构造函数、析构函数、operator->(指针访问)、operator*(解引用)、get()(获取原生指针)、reset()(手动释放资源)
    • C++11 标准智能指针
      • unique_ptr:独占式智能指针,同一资源仅能被一个指针管理
      • shared_ptr:共享式智能指针,内部引用计数,多指针共管同一资源
      • weak_ptr:弱引用指针,不增加引用计数 ,专门解决 shared_ptr 循环引用问题,必须依托 shared_ptr 使用

对应代码

cpp 复制代码
// 综合应用
/*
 * 回顾知识点:
 * 1. 移动语义: 将某一个对象的数据转移到另一个对象中,避免不必要的拷贝,
 *    1) std::move() 触发
 *    2) 移动构造函数:类名(类名&&)
 *    3) 移动赋值重载:返回类型 operator=(类名&&)
 *
 *    一个类编译器默认提供无参构造,拷贝构造,移动构造,拷贝赋值,移动赋值,析构函数
 *
 * 2. 智能指针:依据类的生命周期(构造函数,析构函数)去自动管理原生指针,也叫做裸指针
 *    1) 自定义智能指针:
 *       构造函数(数据类型 *):RAII(资源获取即初始化)
 *       析构函数:回收原始的指针
 *       operator->():指针访问
 *       operator*():指针解引用
 *       数据类型 * get():获取原始指针,返回原始指针,但原始指针的生命周期仍然受智能指针控制。
 *                        使用 get() 时需要小心,避免与智能指针的自动管理机制冲突。
 *       void reset():手动释放原始指针
 *
 *    2) C++11 提供的智能指针:类模板
 *       auto_ptr<数据类型>:放弃
 *       unique_ptr<数据类型>:独占
 *       shared_ptr<数据类型>:共享
 *       weak_ptr<数据类型>:弱引用
 *          必须指向 shared_ptr,但是不增加引用计数
 *          用于解决 shared_ptr 的循环引用问题
 */

二、C++ 四种显式类型转换

C 语言风格的 (type)expr 强制转换几乎什么都能转,编译器很难帮我们检查错误。C++ 引入了四种显式类型转换运算符,各自职责明确,见名知义、便于排查。

1. static_cast:编译期可检查的常规转换

常见用途:

  • 基本数据类型之间转换(如 double → int)
  • void* 与其他指针类型之间转换
  • 有继承关系的指针或引用之间转换(含向上转型与向下转型)
  • 调用显式转换构造函数
cpp 复制代码
double value = 3.14;
int number = static_cast<int>(value);  // 结果为 3

⚠️ 向下转型时不会进行运行时安全检查:基类指针实际不指向派生类对象时,转出来就是野指针。

cpp 复制代码
Derived* derived = static_cast<Derived*>(base);  // 不检查,后果自负

2. dynamic_cast:多态类型之间的运行时安全转换

主要用于具有多态性的类之间进行安全转换,尤其是从基类向派生类转换(安全向下转型)。

cpp 复制代码
class Base {
public:
    virtual ~Base() = default;
};

class Derived : public Base {};

Base* base = new Derived;
Derived* derived = dynamic_cast<Derived*>(base);

if (derived != nullptr) {
    // 转换成功
}

特点:

  • 基类通常必须是多态类,即至少有一个虚函数(靠 RTTI 运行时类型信息实现)
  • 指针转换失败时返回 nullptr
  • 引用转换失败时抛出 std::bad_cast
  • 会进行运行时类型检查,因此比 static_cast 更安全,但有一定运行时开销

3. const_cast:添加或移除 const / volatile 属性

cpp 复制代码
int value = 10;
const int* p1 = &value;

int* p2 = const_cast<int*>(p1);
*p2 = 20;  // 合法,因为原对象 value 本身不是 const

⚠️ 特别注意 :如果对象本身定义时就是 const,移除 const 后再修改它会产生未定义行为(UB)。

cpp 复制代码
const int value = 10;
int* p = const_cast<int*>(&value);
*p = 20;  // 未定义行为!

4. reinterpret_cast:底层位模式的重新解释

用于进行底层、位模式层面的类型重新解释,不改变二进制数据,只换一种"看法"。

常见用途:

  • 不相关的指针类型之间转换
  • 指针与足够大的整数类型之间转换
  • 底层系统编程和硬件操作
cpp 复制代码
int value = 10;
int* p = &value;

std::uintptr_t address = reinterpret_cast<std::uintptr_t>(p);

它非常危险,因为基本不提供类型安全保证,通常只应该在底层编程中使用。

总结对比

转换方式 主要作用 安全性
static_cast 常规、编译期可检查的转换 较高
dynamic_cast 多态类之间的运行时安全转换 高
const_cast 添加或移除 const/volatile 谨慎使用
reinterpret_cast 底层、不相关类型之间的重新解释 最低

简单记忆:

  • 普通转换 :static_cast
  • 多态安全转换 :dynamic_cast
  • 修改常量属性 :const_cast
  • 底层强制解释 :reinterpret_cast

三、设计模式基础介绍

核心知识点

  1. 设计模式 :共 23 种经典设计模式,用于规范类与代码结构,简化设计、提升代码扩展性、可维护性。设计模式是针对常见软件设计问题总结出来的可复用解决方案。
  2. 三大分类:创建型模式 (单例、工厂、构建器......)、结构型模式 (代理、装饰器......)、行为型模式(观察者......)。
cpp 复制代码
// 1.设计模式:类的设计模式(23种),分三大类型(创建类型,结构类型,行为类型)
// 简化类的设计,提高程序的扩展性等

四、单例设计模式

核心知识点

  1. 单例模式 :整个程序生命周期内,类有且仅有一个实例对象,并提供一个全局访问入口。
  2. 两大实现方式
    • 懒汉式:第一次使用对象时才创建实例,延迟加载。
    • 饿汉式:程序启动、类加载时就提前创建实例,无论是否使用。
  3. 单例实现步骤
    1. 私有化构造函数,禁止外部创建对象;
    2. 删除拷贝构造、移动构造、拷贝赋值,禁止克隆对象;
    3. 提供 public 静态成员函数,对外获取唯一实例;
    4. 饿汉式:定义静态类对象作为唯一实例;懒汉式:在静态函数内定义局部静态对象。

常见使用场景

  • 配置管理器、日志系统、数据库连接池、线程池、Redis 客户端管理器

对应代码(WebServer 完整示例)

cpp 复制代码
#if 0
#include <iostream>
using namespace std;

// 定义web服务器:只创建一个类的对象
// 单例设计过程:1)私有化构造函数 2)删除拷贝构造和移动构造(整个程序只能有一个,那么就不能被克隆或移动)
// 3)提供一个public静态函数,返回一个类的对象
// 4) 懒汉式:静态成员函数中创建局部静态对象
// 5) 饿汉式:定义静态成员类对象,在静态成员函数中返回这个类的对象
class WebServer {
    int port;
    string root; // web资源文件的根目录

    WebServer(int port = 443, string root = "/webroot") : port(port), root(root) {}

    WebServer(const WebServer&) = delete;
    WebServer& operator=(const WebServer&) = delete;
    WebServer(WebServer&&) = delete; // 删除移动构造

public:
    void start() {
        cout << "WebServer Running localhost:" << port << endl;
    }

    void shutdown() {
        cout << "WebServer Stop" << endl;
    }

    // 静态的成员函数的参数表尽量同构造函数的参数表相同
    static WebServer& getInstance(int port, string root) {
        // 懒汉式:局部的static对象,在程序运行期间,只会定义一次
        // static WebServer server(port, root);
        // return server;

        return instance;
    }

    // 饿汉模式
    static WebServer instance; // 只是声明
};

void runServer() {
    WebServer::getInstance(8080, "/webroot").start();
}

void stopServer() {
    WebServer::getInstance(8080, "/webroot").shutdown();
}

WebServer WebServer::instance(8080); // 定义

int main() {
    WebServer::getInstance(8080, "/webroot").start();
}
#endif

面试补充:Meyers Singleton(最推荐的写法)

cpp 复制代码
class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;  // 局部静态对象
        return instance;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
};
cpp 复制代码
Singleton& instance = Singleton::getInstance();

关键点 :从 C++11 开始,函数内静态变量的初始化是线程安全 的,因此这种写法一般称为 Meyers Singleton,既天然线程安全又不用手写双检锁,面试首选答案。

优缺点

优点:

  • 保证实例唯一;节省资源;方便全局访问。

缺点:

  • 相当于引入全局状态;类之间容易产生强耦合;不利于单元测试;使用不当可能违背单一职责原则。

五、工厂设计模式

5.1 模式分类 & 核心知识点

  1. 简单工厂
    • 特点:一个工厂类生产所有产品,产品类型固定,新增产品需要修改工厂代码,违反开闭原则。
    • 开闭原则:对扩展开放,对原有代码修改关闭。
  2. 工厂方法
    • 特点:一个工厂只负责生产一种产品,不同产品对应专属工厂,遵循开闭原则;但类的数量会增加。
  3. 抽象工厂
    • 特点:一个工厂可以生产同系列 / 多类组合产品(如手机、平板、电脑),解决工厂方法单一产品的局限性。也叫创建"产品族",例如:Windows 风格按钮、文本框一套;Linux 风格按钮、文本框一套。
  4. 通用结构:抽象产品接口 → 具体产品类 → 抽象工厂接口 → 具体工厂类。
  5. 核心思想 :把对象的创建过程 封装起来,调用者只关心"我要什么",不关心"具体怎么创建",不需要直接 new 具体对象。

优点:创建和使用解耦;隐藏复杂的对象创建过程;便于扩展和统一管理对象。

5.2 对应代码

cpp 复制代码
#if 0
#include <iostream>
#include <memory>
#include <string>
using namespace std;

// 3.工厂设计模式:思考产品如何通过工厂生产出来的
// 简单工厂:工厂只需要知道用户要什么产品类型,即可以生产出来,生产的东西只能是固定的,
//           不能生产它没有的,违反了开闭原则。
// 开闭原则:在不改现有代码的基础上,对扩展开放,对修改关闭
// 工厂方法:不同的产品可以由不同的工厂生产出来,例如:苹果手机由苹果工厂生产(只能生产手机),
//           小米手机由小米工厂生产
// 抽象工厂:不同的工厂可以生成相同类型或组合的产品,即一个工厂可以生产多个不同类型的产品,
//           例如:苹果工厂可以生产苹果手机、苹果电脑等,可以生产组合或套装产品(或同系列)

// 产品类:抽象类或接口
class IPhone {
public:
    // 产品的功能
    virtual void tell(string num) = 0;
};

// 定义具体的产品:某一种类型
class HWPhone : public IPhone {
public:
    void tell(string num) {
        cout << "HWPhone tell: " << num << endl;
    }
};

class VivoPhone : public IPhone {
public:
    void tell(string num) {
        cout << "VivoPhone tell: " << num << endl;
    }
};

class MIPhone : public IPhone {
public:
    void tell(string num) {
        cout << "MIPhone tell: " << num << endl;
    }
};

// 简单工厂:产品类型固定,定义好之后无法扩展
class PhoneFactory {
public:
    enum Type {
        HW,
        VIVO,
        MI
    };

    // 依据用户提供的产品类型来生产
    static shared_ptr<IPhone> create(Type type = HW) {
        switch (type) {
        case HW:   return shared_ptr<IPhone>(new HWPhone());
        case VIVO: return shared_ptr<IPhone>(new VivoPhone());
        default:   return shared_ptr<IPhone>(new MIPhone());
        }
    }
};

#if 0
int main() {
    shared_ptr<IPhone> phone = PhoneFactory::create();
    phone->tell("110");

    shared_ptr<IPhone> phone2 = PhoneFactory::create(PhoneFactory::VIVO);
    phone2->tell("120");
    return 0;
}
#endif

// 工厂方法设计模式:每一个工厂只负责一种产品的生产
class IFactory {
public:
    virtual shared_ptr<IPhone> create() = 0;
};

class HWFactory : public IFactory {
public:
    shared_ptr<IPhone> create() {
        return shared_ptr<IPhone>(new HWPhone());
    }
};

#if 0
int main() {
    // 需要HWPhone,只需要通过它的工厂生产即可。
    shared_ptr<IPhone> phone = HWFactory().create();
    phone->tell("110");
    return 0;
}
#endif

#if 1
// 抽象工厂:工厂可以生产同类型或品牌的系列产品
class IPad {
public:
    virtual void playGame() = 0;
};

class IComputer { // 电脑产品接口
public:
    virtual void softDev() = 0;
};

class HWPad : public IPad {
public:
    void playGame() {
        cout << "HWPad playGame()" << endl;
    }
};

class XMPad : public IPad {
public:
    void playGame() {
        cout << "XMPad playGame()" << endl;
    }
};

class VIvoPad : public IPad {
public:
    void playGame() {
        cout << "VIvoPad playGame()" << endl;
    }
};

class HWComputer : public IComputer {
public:
    void softDev() {
        cout << "HW" << endl;
    }
};

class XMComputer : public IComputer {
public:
    void softDev() {
        cout << "XM" << endl;
    }
};

class VIvoComputer : public IComputer {
public:
    void softDev() {
        cout << "VIvo" << endl;
    }
};

// 抽象工厂的接口类
class IFactory {
public:
    virtual shared_ptr<IPhone>    createPhone()    = 0;
    virtual shared_ptr<IPad>      createPad()      = 0;
    virtual shared_ptr<IComputer> createComputer() = 0;
};

class HWFactory : public IFactory {
public:
    shared_ptr<IPhone> createPhone() {
        return shared_ptr<IPhone>(new HWPhone());
    }
    shared_ptr<IPad> createPad() {
        return shared_ptr<IPad>(new HWPad());
    }
    shared_ptr<IComputer> createComputer() {
        return shared_ptr<IComputer>(new HWComputer());
    }
};

class XMFactory : public IFactory {
public:
    shared_ptr<IPhone> createPhone() {
        return shared_ptr<IPhone>(new MIPhone());
    }
    shared_ptr<IPad> createPad() {
        return shared_ptr<IPad>(new XMPad());
    }
    shared_ptr<IComputer> createComputer() {
        return shared_ptr<IComputer>(new XMComputer());
    }
};

class VIvoFactory : public IFactory {
public:
    shared_ptr<IPhone> createPhone() {
        return shared_ptr<IPhone>(new VivoPhone());
    }
    shared_ptr<IPad> createPad() {
        return shared_ptr<IPad>(new VIvoPad());
    }
    shared_ptr<IComputer> createComputer() {
        return shared_ptr<IComputer>(new VIvoComputer());
    }
};
#endif
#endif

5.3 面试补充:三种工厂一句话总结

模式 一句话总结 开闭原则
简单工厂 一个工厂根据参数生产固定产品 ❌ 违反
工厂方法 一个工厂只生产一种产品,一物一厂 ✅ 遵循
抽象工厂 一个工厂生产一族(组合)产品 ✅ 遵循

六、代理模式 Proxy(新增)

核心知识点

  1. 代理模式 :为真实对象提供一个代理对象 ,客户端通过代理间接访问 真实对象;代理可以在调用真实对象之前或之后增加额外逻辑(权限检查、日志等)。
  2. 属于结构型设计模式。

常见场景

  • 权限检查
  • 日志记录
  • 缓存
  • 远程调用 RPC
  • 延迟加载
  • 事务控制
  • Spring AOP(动态代理的经典应用)

示例代码

cpp 复制代码
#include <iostream>
#include <memory>

class Service {
public:
    virtual void request() = 0;
    virtual ~Service() = default;
};

class RealService : public Service {
public:
    void request() override {
        std::cout << "执行真实业务\n";
    }
};

class ServiceProxy : public Service {
private:
    std::unique_ptr<RealService> realService;

public:
    ServiceProxy() : realService(std::make_unique<RealService>()) {}

    void request() override {
        std::cout << "权限检查\n";       // 调用前的额外逻辑

        realService->request();          // 调用真实对象

        std::cout << "记录日志\n";       // 调用后的额外逻辑
    }
};

常见代理类型

  • 静态代理:代理类在编译前已经写好;
  • 动态代理:程序运行时动态生成代理对象;
  • 远程代理:代理远程服务,例如 RPC 客户端;
  • 虚拟代理:需要时才创建资源较大的真实对象;
  • 保护代理:控制访问权限。

面试高频:代理模式 vs 装饰器模式

  • 代理模式 侧重控制对象的访问(权限、延迟、远程);
  • 装饰器模式 侧重动态增强对象的功能(套一层加行为)。
  • 结构几乎一样,区别在意图。

七、观察者模式

核心知识点

  1. 观察者模式 :俗称发布 - 订阅模式,属于行为型设计模式。
  2. 两大核心角色
    • 主题(发布者):维护核心数据、观察者列表;提供注册 / 注销观察者、数据更新、批量通知接口。
    • 观察者(订阅者):实现统一更新接口,接收主题推送的消息并响应。
  3. 运行逻辑:主题数据发生变化 → 主动通知所有已注册观察者 → 观察者执行自身更新逻辑。观察者是被动的:关注主题的数据变化时,被主题通知,然后执行自己的更新函数。

对应代码

cpp 复制代码
#if 0
#include <iostream>
#include <vector>
#include <thread>
#include <iomanip>
#include <chrono>
#include <sstream>
using namespace std;

// 4.观察者模式:简化版的发布与订阅模式
//   组成:主题,观察者
//   主题:核心数据(天气,GPS位置,网络时间)
//   主题必须有:添加或删除观察者,更新数据函数,通知接口
//   观察者:必须存在接收主题通知的函数(如 update)
//   观察者是被动性的:当我们关注的主题数据发生变化时,会被主题通知,然后执行自己的更新函数

// 设计观察者接口类
class INTObserver {
public:
    virtual void update(string timeStr) = 0;
};

// 设计主题类
class NetworkTimerServer {
    string current_time;  // 数据
    vector<shared_ptr<INTObserver>> allOb; // 所有注册的观察者

public:
    NetworkTimerServer() : current_time("") {}

    // 注册观察者
    void regist(shared_ptr<INTObserver> ob) {
        allOb.push_back(ob);
    }

    // 取消注册观察者
    void unregist(shared_ptr<INTObserver> ob) {
        auto it = allOb.begin();
        while (it != allOb.end()) {
            if (it->get() == ob.get()) {
                allOb.erase(it);
                break;
            }
        }
    }

    void setCurrentTime(const string& timeStr) {
        this->current_time = timeStr;
        notify();
    }

    string getCurrentTime() {
        return this->current_time;
    }

    void notify() {
        // 通知所有观察者数据发生了变化
        for (auto ob : allOb) {
            ob->update(current_time);
        }
    }
};

// 设计具体的观察者
class PhoneObserver : public INTObserver {
public:
    void update(string timeStr) override {
        cout << "PhoneObserver Current Time: " << timeStr << endl;
    }
};

class PadObserver : public INTObserver {
public:
    void update(string timeStr) override {
        cout << "PadObserver Current Time: " << timeStr << endl;
    }
};

class WatchObserver : public INTObserver {
    int i;
public:
    WatchObserver() : i(0) {}
    void update(string timeStr) override {
        if (i % 4 == 0)
            cout << "WatchObserver Current Time: " << timeStr << endl;
        i++;
    }
};

int main() {
    unique_ptr<NetworkTimerServer> nts(new NetworkTimerServer());

    shared_ptr<INTObserver> ob1 = shared_ptr<INTObserver>(new PhoneObserver());
    shared_ptr<INTObserver> ob2 = shared_ptr<INTObserver>(new PadObserver());
    shared_ptr<INTObserver> ob3 = shared_ptr<INTObserver>(new WatchObserver());

    nts->regist(ob1);
    nts->regist(ob2);
    nts->regist(ob3);

    // 模拟时钟
    for (int i = 0; i < 10; i++) {
        auto ct = chrono::system_clock::now();
        time_t tt = chrono::system_clock::to_time_t(ct);
        ostringstream oss;
        oss << put_time(localtime(&tt), "%Y-%m-%d %H:%M:%S");

        // 更新时间
        nts->setCurrentTime(oss.str());

        // 休息1s
        this_thread::sleep_for(chrono::seconds(1));
    }
    return 0;
}
#endif

八、构建器模式(建造者模式)

核心知识点

  1. 构建器模式 :用于成员属性繁多、构造函数重载复杂的类,拆分对象创建步骤,实现流式构建。
  2. 实现步骤
    1. 私有化外部类构造函数,禁止外部直接实例化;
    2. 定义静态内部构建器类 Builder(内部类可以访问外部类的私有成员);
    3. Builder 中提供链式成员函数,逐个设置对象属性;
    4. 最终通过 build() 方法生成并返回完整对象。
  3. 流式构建:成员函数返回构建器自身引用,支持连续调用。

对应原版代码

cpp 复制代码
#if 0
#include <iostream>
#include <memory>
using namespace std;

// 1. 构建器模式
//    将复杂性创建对象的类,设计为分层次的创建过程。
//    复杂性创建,如:类的成员属性较多,构造函数重载也非常多
//   构建器模式的设计过程:【流式构建器】
//     1) 私有化构造函数, 隐藏类对象的创建过程
//     2) 在类内设计静态公开的成员类(内部类)为构建器类 Builder
//     3) 在Builder 类内,设计公开的成员函数,提供属性的设置为构建过程(层次)
//        最后提供一个 build() 函数返回外部类的对象。

class Computer {
    string vendor;   // 厂家
    string cpuName;  // 处理器名称
    int cpuNum;      // 内核数
    int memorySize;  // 内存大小
    int ssdSize;     // 硬盘大小
    // ... 其他大量的属性

private:
    Computer() : vendor("中国"), cpuName("Intel"), cpuNum(2), memorySize(4), ssdSize(40) {}
    Computer(string vendor) : vendor(vendor), cpuName("Intel"), cpuNum(2), memorySize(4), ssdSize(40) {}
    Computer(string vendor, string cpuName) : vendor(vendor), cpuName(cpuName), cpuNum(2), memorySize(4), ssdSize(40) {}
    Computer(string vendor, string cpuName, int cpuNum)
        : vendor(vendor), cpuName(cpuName), cpuNum(cpuNum), memorySize(4), ssdSize(40) {}
    Computer(string vendor, string cpuName, int cpuNum, int memorySize)
        : vendor(vendor), cpuName(cpuName), cpuNum(cpuNum), memorySize(memorySize), ssdSize(40) {}
    Computer(string vendor, string cpuName, int cpuNum, int memorySize, int ssdSize)
        : vendor(vendor), cpuName(cpuName), cpuNum(cpuNum), memorySize(memorySize), ssdSize(ssdSize) {}

public:
    void show() {
        cout << vendor << " 生产 " << cpuName << "(" << cpuNum << ") 内存 " << memorySize << "G";
        cout << " ssd " << ssdSize << " G" << endl;
    }

    // 静态内部类:可以访问外部类的私有成员
    static class Builder {
        shared_ptr<Computer> computer;
    public:
        Builder() {
            computer = shared_ptr<Computer>(new Computer());
        }
        // 设计构建过程成员函数:如果返回构建器类的引用,称之为流式构建
        Builder& vendor(string v) {
            computer->vendor = v;
            return *this;
        }
        Builder& cpuNum(int n) {
            computer->cpuNum = n;
            return *this;
        }
        Builder& memSize(int n) {
            computer->memorySize = n;
            return *this;
        }
        Builder& ssdSize(int n) {
            computer->ssdSize = n;
            return *this;
        }
        shared_ptr<Computer> build() {
            return computer;
        }
    };
};

int main() {
    shared_ptr<Computer> c = Computer::Builder()
        .cpuNum(8).memSize(32).ssdSize(1024).build();
    c->show();
    return 0;
}
#endif

九、后端面试扩展:Redis 核心要点(新增)

简历上写了 Redis,面试官一定会问。以下按"五种基本类型 → 持久化 → 锁 → 主从 → 缓存三兄弟"的顺序复习。

9.1 Redis 五种基本数据类型

Redis 是基于内存的键值数据库,速度快、支持持久化、支持多种数据结构。经典的五种基本类型:String、Hash、List、Set、Sorted Set(ZSet)。

1)String 字符串

可以保存字符串、整数、浮点数、JSON、二进制数据。

redis 复制代码
SET name "Tom"
GET name
DEL name
EXISTS name

# 设置过期时间
SET code "123456" EX 60
EXPIRE code 60
TTL code

# 计数
SET count 0
INCR count
INCRBY count 10
DECR count

使用场景:缓存对象、分布式锁、验证码、计数器、Session。

2)Hash 哈希

一个 Key 对应多个字段和值,类似 Map<String, Map<String, String>>,适合保存对象(用户信息、商品信息)。

redis 复制代码
HSET user:1 name "Tom" age 20
HGET user:1 name
HGETALL user:1
HDEL user:1 age
HEXISTS user:1 name
HINCRBY user:1 age 1
3)List 列表

按插入顺序排列的字符串集合,底层支持从两端添加和删除元素。

redis 复制代码
LPUSH messages "A"
RPUSH messages "C"
LRANGE messages 0 -1
LPOP messages
RPOP messages

# 阻塞获取
BLPOP messages 10
BRPOP messages 10

使用场景:消息队列、最新消息列表、时间线、栈和队列。不过正式消息队列场景通常优先考虑 Redis Stream、RabbitMQ 或 Kafka。

4)Set 集合

无序、元素不重复。

redis 复制代码
SADD tags "Java" "C++" "Redis"
SMEMBERS tags
SISMEMBER tags "Redis"
SREM tags "Java"
SCARD tags

# 集合运算
SINTER set1 set2   # 交集
SUNION set1 set2   # 并集
SDIFF set1 set2    # 差集

使用场景:用户标签、共同好友、点赞用户集合、去重、抽奖。

5)Sorted Set 有序集合(ZSet)

元素不重复,每个元素带 score,按分数排序。

redis 复制代码
ZADD ranking 100 "Tom"
ZADD ranking 90 "Jack"
ZRANGE ranking 0 -1 WITHSCORES
ZREVRANGE ranking 0 -1 WITHSCORES
ZSCORE ranking "Tom"
ZRANK ranking "Tom"
ZINCRBY ranking 10 "Tom"
ZREM ranking "Jack"

使用场景:排行榜、延迟队列、按时间排序、带权重的任务管理。

9.2 Redis 持久化策略:RDB 与 AOF

RDB(快照)

在某个时间点生成整个 Redis 数据集的快照,可以理解为定期全量备份。

  • 优点:文件紧凑、适合备份、恢复速度快、对性能影响较小
  • 缺点:两次快照之间的数据可能丢失;数据量大时 fork 子进程可能造成短暂停顿
  • 适合:可接受分钟级数据丢失、需要快速恢复、定期全量备份
AOF(写命令日志)

记录每条写命令(SET / INCR / DEL......),重启后重新执行这些命令恢复数据。

三种刷盘策略:

策略 行为 特点
always 每次写操作都同步到磁盘 安全性最高,性能较低
everysec 每秒同步一次(最常用) 性能和安全的折中,极端情况丢约 1 秒数据
no 由操作系统决定何时刷盘 性能较高,安全性较低

AOF 重写 :文件会越来越大,Redis 根据当前数据状态生成更精简的命令集合。例如 SET count 1 + INCR count + INCR count 可压缩为 SET count 3。

RDB vs AOF 对比
对比项 RDB AOF
保存方式 数据快照 写命令日志
文件大小 较小 较大
恢复速度 较快 较慢
数据安全性 可能丢失较多数据 通常最多丢失约 1 秒
适合场景 备份、快速恢复 对数据完整性要求较高

实际项目中经常同时开启 RDB 和 AOF 。Redis 新版本还支持混合持久化:文件开头保存 RDB 数据,后面追加 AOF 增量命令,兼顾恢复速度和数据安全性。

9.3 悲观锁与乐观锁

悲观锁

认为数据很可能被其他事务修改,所以操作之前先加锁。

MySQL 示例(FOR UPDATE 加排他锁):

sql 复制代码
BEGIN;
SELECT stock FROM product WHERE id = 1 FOR UPDATE;
UPDATE product SET stock = stock - 1 WHERE id = 1;
COMMIT;
  • 优点:冲突发生时处理简单,适合写冲突频繁的场景
  • 缺点:阻塞其他事务、并发能力较低、加锁顺序不当可能死锁
乐观锁

认为大多数情况下不会冲突,更新时再检查数据是否被修改(通常用版本号)。

sql 复制代码
SELECT id, stock, version FROM product WHERE id = 1;  -- 假设 version = 5

UPDATE product
SET stock = stock - 1,
    version = version + 1
WHERE id = 1
  AND version = 5
  AND stock > 0;

受影响行数为 0,说明数据已被其他事务修改或库存不足,程序可重试。

  • 优点:不长期持有数据库锁,并发性能好,适合读多写少、冲突较少
  • 缺点:冲突时需要重试,冲突频繁时性能下降,业务代码更复杂
Redis 中的乐观锁(WATCH)
redis 复制代码
WATCH stock
GET stock
MULTI
DECR stock
EXEC

如果 WATCH 到 EXEC 期间 stock 被其他客户端修改,EXEC 会执行失败。注意:Redis 事务主要保证命令按顺序执行,不等同于 MySQL 的 ACID 事务,不支持传统意义上的事务回滚。

9.4 Redis 主从复制

主节点 Master 负责写,从节点 Replica 复制主节点数据、通常负责读。

大致过程:从节点连接主节点 → 主节点全量或部分同步 → 全量同步时主节点生成 RDB 并发送 → 从节点加载 RDB → 后续写命令持续复制。

  • 全量复制:第一次建立复制关系或无法增量复制时,完整复制全部数据
  • 增量复制:断开时间较短时,主节点把断开期间缺失的命令发给从节点(依赖复制积压缓冲区)

优点 :读写分离、提升读性能、数据冗余、为高可用打基础。

问题 :复制是异步的,可能短暂不一致;存在复制延迟;单纯主从不能自动故障转移 → 需要自动故障转移时用 Redis Sentinel(哨兵) 或 Redis Cluster。

9.5 缓存穿透、击穿、雪崩(面试必问三兄弟)

1)缓存穿透

查询数据库中根本不存在 的数据:缓存没有、数据库也没有,每次请求都打到数据库(例如恶意请求 GET /user?id=-100000)。

解决方法:

  • 缓存空值 :查不到也把空结果写入缓存(user:-100000 → null),但设较短过期时间
  • 布隆过滤器 :请求先经过布隆过滤器再查缓存和数据库。特点:判断"不存在"时一定不存在;判断"可能存在"时不一定真存在;不会漏判已有元素
  • 参数校验:如用户 ID 必须大于零、长度合法等
2)缓存击穿

某个访问量非常大的热点 Key 突然过期 ,大量请求同时访问数据库。特点是单个热点 Key 失效。

解决方法:

  • 互斥锁:只允许一个线程查询数据库并重建缓存,其他线程等待
  • 逻辑过期:缓存不直接删除数据,而是保存逻辑过期时间;发现过期后一个线程异步重建,其他请求暂时返回旧数据(牺牲短时一致性保可用性)
  • 热点数据永不过期:不设物理过期时间,由后台任务主动更新
3)缓存雪崩

大量缓存 Key 在同一时间失效 ,或者 Redis 服务整体不可用,导致大量请求进入数据库。

解决方法:

  • 过期时间加随机值(如基础 30 分钟 + 随机 0~5 分钟),不让大量 Key 同时过期
  • Redis 主从、哨兵或集群(高可用)
  • 限流、熔断和降级
  • 多级缓存、热点数据提前预热
  • 数据库做好容量保护
三者对比
问题 核心原因 典型解决方案
缓存穿透 查询根本不存在的数据 缓存空值、布隆过滤器
缓存击穿 单个热点 Key 失效 互斥锁、逻辑过期
缓存雪崩 大量 Key 同时失效或 Redis 故障 随机过期、高可用、限流降级

十、后端面试扩展:MySQL 核心要点(新增)

10.1 增删改查

示例表:

sql 复制代码
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    age INT,
    email VARCHAR(100) UNIQUE,
    status TINYINT DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

INSERT:

sql 复制代码
INSERT INTO users (name, age, email) VALUES ('Tom', 20, 'tom@example.com');

INSERT INTO users (name, age, email) VALUES
    ('Tom', 20, 'tom@example.com'),
    ('Jack', 22, 'jack@example.com');

DELETE (执行删除语句时必须特别检查 WHERE 条件!):

sql 复制代码
DELETE FROM users WHERE id = 1;
DELETE FROM users WHERE age < 18;
TRUNCATE TABLE users;   -- 清空整张表

DELETE 与 TRUNCATE 都能删除所有数据,但:DELETE 逐行删除、可带 WHERE;TRUNCATE 不能带 WHERE、更快、通常重置自增计数;两者在事务、日志和触发器方面行为不同。

UPDATE (不写 WHERE 会修改所有记录):

sql 复制代码
UPDATE users SET age = 21 WHERE id = 1;
UPDATE users SET age = 21, email = 'new@example.com' WHERE id = 1;

SELECT:

sql 复制代码
SELECT * FROM users;
SELECT id, name, age FROM users;
SELECT id, name FROM users WHERE age >= 18 AND status = 1;
SELECT * FROM users WHERE name LIKE 'T%';        -- 模糊查询
SELECT * FROM users WHERE age BETWEEN 18 AND 30; -- 范围查询
SELECT * FROM users WHERE status IN (1, 2);      -- 集合查询
SELECT * FROM users WHERE email IS NULL;         -- 空值判断(不能写 = NULL)

排序、分页、分组:

sql 复制代码
SELECT * FROM users ORDER BY age DESC, id ASC;
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20;  -- 等价于 LIMIT 20, 10
SELECT status, COUNT(*) AS user_count FROM users GROUP BY status;
SELECT status, COUNT(*) AS user_count FROM users GROUP BY status HAVING COUNT(*) >= 10;

WHERE 在分组前 过滤行;HAVING 在分组后过滤分组结果。

10.2 多表连接

sql 复制代码
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    amount DECIMAL(10, 2) NOT NULL
);
sql 复制代码
-- INNER JOIN:只返回两表能匹配的数据
SELECT u.name, o.amount
FROM users AS u
INNER JOIN orders AS o ON u.id = o.user_id;

-- LEFT JOIN:返回左表全部,右表匹配不上的为 NULL
SELECT u.name, o.amount
FROM users AS u
LEFT JOIN orders AS o ON u.id = o.user_id;

-- 经典应用:查询没有订单的用户
SELECT u.id, u.name
FROM users AS u
LEFT JOIN orders AS o ON u.id = o.user_id
WHERE o.id IS NULL;

-- RIGHT JOIN:返回右表全部(实际开发中 LEFT JOIN 更常见,交换表位置即可代替)

10.3 事务与 ACID

事务是数据库中作为一个整体执行的一组操作,例如转账的两步必须同时成功或同时失败。

sql 复制代码
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
COMMIT;     -- 发生异常时 ROLLBACK;
特性 含义 InnoDB 实现机制
原子性 Atomicity 操作要么全部成功,要么全部失败 Undo Log
一致性 Consistency 事务前后数据库从一个合法状态到另一个合法状态 目标,靠机制 + 正确业务逻辑共同保证
隔离性 Isolation 并发事务互不干扰 锁、MVCC、Undo Log
持久性 Durability 提交后的修改永久保存,崩溃不丢失 Redo Log

10.4 事务并发问题

  1. 脏读 :读到了另一个事务尚未提交的数据(对方回滚后,读到的就是无效数据)
  2. 不可重复读 :同一事务内两次读取同一条记录,结果不同(别人改了并提交)
  3. 幻读 :同一事务内两次执行相同条件的范围查询 ,返回的记录数量不同(别人插入/删除了记录)
    • 简化理解:不可重复读重点是某一行的内容 变化;幻读重点是符合条件的记录集合变化
  4. 丢失更新:两个事务同时读取并更新同一条数据,其中一个的结果覆盖另一个。避免方式:悲观锁、乐观锁、原子更新语句、合适的隔离级别

10.5 四种事务隔离级别

从低到高:读未提交 → 读已提交 → 可重复读 → 串行化。

  1. READ UNCOMMITTED 读未提交:可以读其他事务未提交的数据;脏读、不可重复读、幻读都可能出现;实际很少使用
  2. READ COMMITTED 读已提交:只能读已提交的数据;避免脏读,但可能有不可重复读、幻读;Oracle、PostgreSQL 默认
  3. REPEATABLE READ 可重复读 :同一事务多次读取同一数据结果一致;避免脏读、不可重复读;MySQL InnoDB 的默认级别 。InnoDB 通过 MVCC、Next-Key Lock 在很多情况下还能避免幻读,但需要区分快照读和当前读,不能绝对化理解
  4. SERIALIZABLE 串行化:最高隔离级别,事务近似串行执行;避免所有并发问题,但并发性能最低
隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 避免 可能 可能
可重复读 避免 避免 标准定义下可能,InnoDB 有额外处理
串行化 避免 避免 避免
sql 复制代码
SELECT @@transaction_isolation;                          -- 查看当前隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;  -- 设置当前会话隔离级别

10.6 索引

索引是帮助数据库快速查找数据的数据结构,可以理解为书的目录 :没有目录逐页查找,有目录快速定位。InnoDB 常用索引结构是 B+ 树。

优点 :提高查询速度;提高排序分组效率;唯一索引保证数据唯一性;覆盖索引可减少回表。

缺点:占用磁盘空间;插入/删除/更新需维护索引,写成本变高;不合理的索引可能不被使用。

常见索引类型:

sql 复制代码
PRIMARY KEY (id)                                       -- 主键索引:唯一、非空,InnoDB 用它组织表数据
CREATE UNIQUE INDEX idx_users_email ON users(email);   -- 唯一索引(注意 MySQL 对 NULL 的处理规则)
CREATE INDEX idx_users_age ON users(age);              -- 普通索引
CREATE INDEX idx_users_status_age ON users(status, age); -- 联合索引
-- 全文索引:用于文本搜索,复杂搜索场景常用 Elasticsearch

聚簇索引与二级索引:

  • 聚簇索引 :InnoDB 表数据直接存储在主键索引的叶子节点中(叶子节点保存完整行数据),一张表通常只有一个
  • 二级索引 :叶子节点保存索引列 + 对应记录的主键值 ;用二级索引查完整数据时,先在二级索引找到主键,再到主键索引查完整记录------这个过程叫回表

覆盖索引:查询需要的字段全部在索引中,直接从索引获得结果,不需要回表:

sql 复制代码
CREATE INDEX idx_users_status_name ON users(status, name);
SELECT status, name FROM users WHERE status = 1;  -- 只读索引即可

联合索引与最左前缀原则(可以把联合索引想象成:先按 name 排序,name 相同时按 age 排序,age 相同时按 status 排序):

sql 复制代码
CREATE INDEX idx_users_name_age_status ON users(name, age, status);

-- 能有效利用索引:
WHERE name = 'Tom'
WHERE name = 'Tom' AND age = 20
WHERE name = 'Tom' AND age = 20 AND status = 1

-- 一般无法有效利用这个联合索引的最左部分:
WHERE age = 20

索引设计时字段顺序非常重要 。MySQL 优化器可能有索引扫描、跳跃扫描等方式,实际情况应通过 EXPLAIN 判断,不要把最左前缀理解成绝对规则。

常见索引失效或效果不佳的情况:

sql 复制代码
-- 1. 对索引列做函数操作
WHERE YEAR(created_at) = 2025                     -- ❌
WHERE created_at >= '2025-01-01'
  AND created_at < '2026-01-01'                   -- ✅ 范围

-- 2. 隐式类型转换:字段是字符串,却用数字比较
WHERE phone = 13800138000                         -- ❌
WHERE phone = '13800138000'                       -- ✅

-- 3. 前置模糊查询
WHERE name LIKE '%Tom'                            -- ❌ B+ 树难以利用前缀定位
WHERE name LIKE 'Tom%'                            -- ✅ 可用索引范围查询

-- 4. 联合索引不满足合适的字段顺序:索引(name, age) 却只查 WHERE age = 20

-- 5. 数据量太小或返回数据太多:优化器可能判断全表扫描成本更低

查看执行计划:

sql 复制代码
EXPLAIN SELECT * FROM users WHERE email = 'tom@example.com';

重点关注:type(访问类型)、key(实际使用的索引)、rows(预计扫描行数)、Extra(额外信息),以及是否出现 Using filesort、Using temporary、Using index。

10.7 数据库连接与连接池

应用程序访问 MySQL 的基本过程:加载数据库驱动 → 建立 TCP 连接 → 身份认证 → 发送 SQL → 执行 → 返回结果 → 释放连接。

每次请求都新建连接成本高,所以实际项目使用数据库连接池 :提前建立一定数量的连接,请求到达时借 一个连接,执行 SQL 后归还。

主要优点:减少频繁创建连接的开销;限制数据库连接总数;提高系统吞吐量;便于管理连接超时、检测和回收。

常见参数:初始连接数、最小空闲连接数、最大连接数、获取连接超时时间、空闲连接回收时间、连接最大生命周期、连接有效性检测。

使用注意:

  • 用完连接必须归还;事务结束后再归还
  • 使用参数化查询,避免 SQL 注入
  • 不要长时间占用连接;最大连接数不是越大越好
  • 连接池容量应结合应用并发量和数据库承载能力设置

参数化查询(防 SQL 注入):

cpp 复制代码
// 错误方式:用户可能输入恶意内容,产生 SQL 注入
std::string sql = "SELECT * FROM users WHERE name = '" + input + "'";

正确思路是使用预编译和参数绑定:

sql 复制代码
SELECT * FROM users WHERE name = ?;

除了更安全,预编译语句通常也更便于复用。

10.8 视图

视图是基于查询结果创建的虚拟表。

sql 复制代码
CREATE VIEW active_users AS
SELECT id, name, email FROM users WHERE status = 1;

SELECT * FROM active_users;

CREATE OR REPLACE VIEW active_users AS
SELECT id, name, email, age FROM users WHERE status = 1;  -- 修改视图

DROP VIEW active_users;                                    -- 删除视图

优点:

  • 简化复杂查询:把复杂的多表查询封装成一个视图
  • 控制数据访问:只暴露允许查看的字段(隐藏密码、身份证号等敏感字段)
  • 提供逻辑抽象:底层表部分变化时,向上层提供相对稳定的查询接口

注意事项:

  • 普通视图通常不单独存储实际数据,查询视图本质仍查询底层表
  • 复杂视图可能影响性能
  • 含聚合、分组、多表连接的视图通常不能直接更新
  • 视图不能代替合理的表结构和索引设计;不要无限嵌套视图

十一、面试速记总结

C++ 四种类型转换

  • static_cast:常规编译期转换;dynamic_cast:多态安全向下转型(失败返回 nullptr / 抛 bad_cast);const_cast:增删 const;reinterpret_cast:底层位模式重解释(最危险)。

设计模式

  • 单例:保证一个类只有一个实例(Meyers Singleton 线程安全);
  • 工厂:封装对象创建过程(简单工厂违反开闭、工厂方法一厂一物、抽象工厂一厂多产品族);
  • 代理:控制对真实对象的访问,添加权限、日志等功能(与装饰器的区别在意图);
  • 观察者:发布订阅,主题推送、观察者被动响应;
  • 构建器:多参数复杂对象分步流式构建。

Redis

  • String:缓存、计数器、分布式锁;Hash:对象;List:队列、列表;Set:去重、交并差集;ZSet:排行榜、延迟任务。
  • RDB:定期快照,恢复快,可能丢较多数据;AOF:写命令日志,更安全,文件更大恢复更慢;实际项目可以同时用(新版还有混合持久化)。

缓存问题

  • 穿透:查询不存在的数据(缓存空值、布隆过滤器);
  • 击穿:单个热点 Key 失效(互斥锁、逻辑过期);
  • 雪崩:大量 Key 同时失效或 Redis 故障(随机过期、高可用、限流降级)。

锁

  • 悲观锁:先加锁再操作,适合冲突频繁;乐观锁:更新时检查版本号,适合冲突较少。

MySQL

  • ACID:原子性、一致性、隔离性、持久性;
  • 隔离级别:读未提交、读已提交、可重复读、串行化;InnoDB 默认可重复读;
  • 索引提高读性能,但增加存储和写入成本;InnoDB 常用 B+ 树索引;
  • 联合索引重点掌握最左前缀原则;用 EXPLAIN 分析 SQL;
  • 使用连接池复用数据库连接;使用预编译和参数绑定避免 SQL 注入;
  • 视图是基于查询定义的虚拟表。

十二、附加练习代码

12.1 图形面积计算(多态 + 智能指针)

cpp 复制代码
#if 0
#include <iostream>
#include <memory>
using namespace std;
#define pai 3.14

class Shape {
public:
    virtual void alcArea() = 0;
};

class Circle : public Shape {
private:
    double t;
    double area;
public:
    Circle(double r, double area = 0) : t(r), area(area) {}
    void alcArea() override {
        area = pai * t * t;
        cout << "圆的面积是" << area << endl;
    }
};

int main() {
    unique_ptr<Shape> s(new Circle(5.0));
    s.get()->alcArea();
}
#endif

抽象基类定义纯虚函数实现多态;unique_ptr 管理堆对象,自动释放资源。

12.2 vector + for_each + Lambda 练习

cpp 复制代码
#if 0
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v;
    v.emplace_back(1);   // emplace_back 原位构造,效率高于 push_back
    v.emplace_back(3);
    v.emplace_back(5);
    v.emplace_back(7);
    v.emplace_back(9);

    for (auto v : v) {
        cout << v << endl;
    }
    for_each(v.begin(), v.end(), [](int v) { cout << v * 2 << endl; });
    return 0;
}
#endif

全文总结

  1. 四种类型转换 :static_cast(常规)、dynamic_cast(多态安全)、const_cast(常量)、reinterpret_cast(底层),各司其职,别再用 C 风格强转。
  2. 前置复习:移动语义优化拷贝效率,各类智能指针基于 RAII 实现内存自动管理。
  3. 单例模式:全局唯一实例,懒汉 / 饿汉,面试报 Meyers Singleton(C++11 静态局部变量线程安全)。
  4. 工厂模式:简单工厂(违反开闭)、工厂方法(一厂一物)、抽象工厂(一厂多系列产品)。
  5. 代理模式:控制访问 + 前置后置增强,与装饰器的区别在意图。
  6. 观察者模式:发布订阅模型,主题推送消息,观察者被动响应。
  7. 构建器模式:解决多参数复杂对象创建,支持流式调用。
  8. Redis:五种基本类型、RDB/AOF 持久化、乐观悲观锁、主从复制、缓存穿透击穿雪崩。
  9. MySQL:CRUD 与多表连接、事务 ACID、并发问题与四种隔离级别、B+ 树索引与最左前缀、连接池与视图。
  10. 附加练习巩固:C++ 多态、智能指针、STL 容器与算法综合使用。

如果这篇对你有帮助,点个赞 + 收藏再走吧~有疑问欢迎评论区交流。

相关推荐
rhett. li1 小时前
用 AI 写 C++ 桌面 UI:TRAE + nim_duilib 实战指南
c++·人工智能·ui
91刘仁德1 小时前
RAG实战-Embedding模型(BGE)
笔记·embedding·rag
JosieBook1 小时前
【数据库】MySQL 实战精通系列 · 第4篇:索引原理与执行计划实战
android·数据库·mysql
ShineWinsu1 小时前
对于Redis:Redis特性以及应用场景的解析
linux·c++·redis·缓存·高并发·分布式系统·key-value
李游Leo1 小时前
HarmonyOS 7 + ArkTS + Image Kit 学习笔记:图像超分处理链路与 PixelMap 数据流实践【鸿蒙心迹】
笔记·学习·harmonyos
m0_547486661 小时前
《数据库原理、技术与应用:MySQL》全套PPT课件2026
数据库·mysql
-森屿安年-1 小时前
买卖股票的最佳时机
c++·贪心算法
JosieBook1 小时前
【数据库】MySQL 实战精通系列 · 第1篇:MySQL 全局观与实战环境搭建
数据库·mysql·adb
驭渊的小故事1 小时前
牛客网算法刷题笔记:哈希表、贪心与动态规划实战
笔记·算法·散列表