Qt C++11/14/17 新特性大全详解

C++11:现代C++的革命

一、核心语言特性

1. 自动类型推导(auto)
复制代码
// 旧方式
std::vector<QString>::iterator it = vec.begin();
std::map<int, QString>::const_iterator cit = map.begin();

// C++11
auto it = vec.begin();           // 推导为 std::vector<QString>::iterator
auto cit = map.begin();          // 推导为 std::map<int, QString>::const_iterator
auto name = QString("Alice");    // 推导为 QString
auto count = 42;                 // 推导为 int
auto pi = 3.14159;               // 推导为 double

优势

  • 简化复杂类型声明

  • 减少输入错误

  • 提高模板代码的可读性

2. 基于范围的for循环
复制代码
QList<QString> names = {"Alice", "Bob", "Charlie"};

// 旧方式
for (int i = 0; i < names.size(); ++i) {
    qDebug() << names[i];
}

// C++11
for (const QString& name : names) {
    qDebug() << name;
}

// 配合auto
for (const auto& name : names) {
    qDebug() << name;
}

// 修改元素
for (auto& name : names) {
    name = name.toUpper();
}
3. Lambda表达式
复制代码
// 基本语法:[捕获列表](参数列表) -> 返回类型 { 函数体 }

// 简单Lambda
auto print = []() { qDebug() << "Hello"; };
print();

// 带参数
auto add = [](int a, int b) -> int { return a + b; };
auto sum = add(3, 4);

// 捕获局部变量
int factor = 2;
auto multiply = [factor](int x) { return x * factor; };

// 引用捕获
QString message = "Hello";
auto modify = [&message]() { message.append(" World!"); };

// 在Qt中的应用
QTimer::singleShot(1000, []() { qDebug() << "1秒后执行"; });

QList<int> numbers = {1, 5, 3, 4, 2};
std::sort(numbers.begin(), numbers.end(), 
          [](int a, int b) { return a > b; }); // 降序排序
4. 右值引用和移动语义
复制代码
class MyString {
    char* data;
public:
    // 移动构造函数
    MyString(MyString&& other) noexcept 
        : data(other.data) {
        other.data = nullptr;  // 转移所有权
    }
    
    // 移动赋值运算符
    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
};

// std::move将左值转为右值
QString createString() {
    QString str("大量数据");
    return str;  // 触发移动构造而不是拷贝
}

QString s1 = createString();  // 移动构造
QString s2 = std::move(s1);   // s1现在为空,内容移动到s2
5. 智能指针
复制代码
#include <memory>

// unique_ptr:独占所有权
std::unique_ptr<QWidget> widget(new QWidget());
auto dialog = std::unique_ptr<QDialog>(new QDialog());

// 转移所有权
auto widget2 = std::move(widget);  // widget变为nullptr

// shared_ptr:共享所有权
std::shared_ptr<QObject> obj1 = std::make_shared<QObject>();
std::shared_ptr<QObject> obj2 = obj1;  // 引用计数+1

// weak_ptr:弱引用,不增加引用计数
std::weak_ptr<QObject> weakObj = obj1;
if (auto sharedObj = weakObj.lock()) {
    // 使用sharedObj
}
6. nullptr
复制代码
// 旧方式的问题
void func(int) { qDebug() << "int version"; }
void func(char*) { qDebug() << "pointer version"; }

func(NULL);    // 可能调用int版本(如果NULL是0)
func(nullptr); // 明确调用指针版本

// 在Qt中
QWidget* widget = nullptr;  // 替代NULL
if (widget != nullptr) {
    widget->show();
}
7. 强类型枚举
复制代码
// 旧枚举的问题
enum Color { Red, Green, Blue };
enum TrafficLight { Red, Yellow, Green };  // 冲突!

// C++11枚举类
enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green };

Color c = Color::Red;
TrafficLight t = TrafficLight::Red;  // 无冲突

// 指定底层类型
enum class Status : uint8_t {
    Ok = 0,
    Error = 1,
    Warning = 2
};
8. 委托构造函数和继承构造函数
复制代码
class Widget : public QWidget {
public:
    // 委托构造函数
    Widget(QWidget* parent = nullptr) 
        : Widget("Default", parent) {}  // 委托给下面的构造函数
    
    Widget(const QString& name, QWidget* parent = nullptr)
        : QWidget(parent), name(name) {}
    
private:
    QString name;
};

// 继承构造函数
class MyLineEdit : public QLineEdit {
public:
    using QLineEdit::QLineEdit;  // 继承所有父类构造函数
};
9. override和final
复制代码
class Base {
public:
    virtual void show() { qDebug() << "Base"; }
    virtual void display() final {}  // 禁止派生类重写
};

class Derived : public Base {
public:
    void show() override {  // 明确表示重写
        qDebug() << "Derived";
    }
    // void display() {}  // 错误!final函数不能重写
};
10. 变长参数模板
复制代码
template<typename... Args>
void log(Args... args) {
    // 可以使用递归或折叠表达式(C++17)处理参数
    (qDebug() << ... << args);  // C++17折叠表达式
}

// 使用
log("Error:", 404, "at line", __LINE__);

C++14:完善和精炼

一、核心语言特性

1. 泛型Lambda
复制代码
// C++11:参数类型必须明确
auto lambda11 = [](int x, double y) { return x + y; };

// C++14:参数类型可以用auto
auto lambda14 = [](auto x, auto y) { return x + y; };

// 使用示例
auto adder = [](auto a, auto b) { return a + b; };
qDebug() << adder(5, 3);           // 8
qDebug() << adder(2.5, 3.7);       // 6.2
qDebug() << adder(QString("A"), QString("B"));  // "AB"

// 更复杂的例子
auto processor = [](auto container) {
    for (const auto& item : container) {
        qDebug() << item;
    }
};
processor(QList<int>{1, 2, 3});
processor(QVector<QString>{"a", "b", "c"});
2. Lambda捕获表达式
复制代码
// C++11只能捕获现有变量
int x = 10;
auto lambda11 = [x]() { return x * 2; };

// C++14可以初始化捕获
int y = 5;
auto lambda14_1 = [value = x + y]() { return value; };  // value = 15

// 移动捕获(重要!)
auto data = std::make_unique<QByteArray>(1000, 'A');
auto lambda14_2 = [ptr = std::move(data)]() {
    // ptr拥有数据所有权,data现在为空
    return ptr->size();
};

// 引用重命名
QString str = "Hello";
auto lambda14_3 = [&s = str]() {
    s.append(" World!");
    return s;
};
3. 函数返回类型推导
复制代码
// 普通函数的auto返回类型
auto add(int a, int b) {  // 编译器推导返回int
    return a + b;
}

auto createWidget() {     // 返回QWidget*
    return new QWidget();
}

auto getConfig() -> std::map<QString, QVariant> {
    return {{"width", 800}, {"height", 600}};
}

// 配合decltype(auto)保留引用和const
QString globalName = "Global";
decltype(auto) getNameRef() {
    return (globalName);  // 返回QString&,注意括号!
}

const QString& getConstName() {
    static QString name = "Static";
    return name;
}
decltype(auto) nameRef = getConstName();  // 推导为const QString&
4. 放宽constexpr限制
复制代码
// C++11: constexpr函数只能有一条return语句
constexpr int factorial11(int n) {
    return n <= 1 ? 1 : n * factorial11(n - 1);
}

// C++14: constexpr函数可以有局部变量、循环等
constexpr int factorial14(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int arraySize(int multiplier) {
    int size = 10;
    if (multiplier > 1) {
        size *= multiplier;
    }
    return size;
}

// 编译期计算数组大小
std::array<int, factorial14(5)> arr1;  // 120个元素的数组
std::array<double, arraySize(3)> arr2; // 30个元素的数组
5. 二进制字面量和数字分隔符
复制代码
// 二进制字面量
uint8_t flags = 0b10101010;      // 0xAA
uint16_t mask = 0b1111000011110000; // 0xF0F0

// 数字分隔符(提高可读性)
int million = 1'000'000;
double pi = 3.14159'26535'89793;
long bigNumber = 9'223'372'036'854'775'807LL;

// 在Qt中的实用示例
enum Permissions : uint8_t {
    None    = 0b0000'0000,
    Read    = 0b0000'0001,
    Write   = 0b0000'0010,
    Execute = 0b0000'0100,
    All     = 0b0000'0111
};

const uint32_t ColorARGB = 0xFF'88'00'44;  // ARGB颜色值
const int MaxBufferSize = 64'000'000;      // 64MB

二、标准库增强

1. std::make_unique
复制代码
// C++11只有std::make_shared
std::unique_ptr<QWidget> p1(new QWidget());  // 旧方式

// C++14引入make_unique
auto p2 = std::make_unique<QWidget>();
auto list = std::make_unique<QList<int>>(10, 0);  // 10个0

// 带参数构造
auto button = std::make_unique<QPushButton>("Click Me");
auto dialog = std::make_unique<QFileDialog>(
    nullptr, 
    "Select File",
    QDir::homePath(),
    "Images (*.png *.jpg)"
);

// 数组版本
auto arr = std::make_unique<int[]>(100);  // 100个int的数组
2. std::exchange - 交换并返回旧值
复制代码
#include <utility>

class ResourceHolder {
    std::unique_ptr<QWidget> resource;
public:
    // 设置新资源,返回旧资源
    std::unique_ptr<QWidget> replaceResource(
        std::unique_ptr<QWidget> newResource) {
        
        // 交换并返回旧的
        return std::exchange(resource, std::move(newResource));
    }
    
    // 清空资源并返回
    std::unique_ptr<QWidget> releaseResource() {
        return std::exchange(resource, nullptr);
    }
};

// 使用示例
ResourceHolder holder;
auto oldWidget = holder.replaceResource(
    std::make_unique<QWidget>());  // 设置新widget,得到旧widget
3. std::integer_sequence(编译期整数序列)
复制代码
#include <utility>

// 展开参数包的辅助工具
template<typename T, T... Ints>
void printSequence(std::integer_sequence<T, Ints...>) {
    ((std::cout << Ints << ' '), ...);  // C++17折叠表达式
}

// 生成索引序列用于访问tuple
template<typename Tuple, size_t... Is>
void printTuple(const Tuple& t, std::index_sequence<Is...>) {
    ((std::cout << std::get<Is>(t) << ' '), ...);
}

// 使用
printSequence(std::integer_sequence<int, 1, 2, 3, 4>{});
auto t = std::make_tuple(1, 2.0, "three");
printTuple(t, std::index_sequence<0, 1, 2>{});

C++17:重大功能增强

一、核心语言特性

1. 结构化绑定
复制代码
// 从pair/tuple解包
std::pair<int, QString> result = {42, "Answer"};
auto [code, message] = result;  // code=42, message="Answer"

// 从结构体解包
struct Point { int x; int y; };
Point p{10, 20};
auto [x, y] = p;  // x=10, y=20

// 从map遍历
std::map<int, QString> m{{1, "one"}, {2, "two"}};
for (const auto& [key, value] : m) {
    qDebug() << key << "->" << value;
}

// 引用绑定
auto& [refX, refY] = p;  // refX和refY是p.x和p.y的引用
refX = 100;  // 修改p.x

// 在Qt中的应用
QMap<QString, QVariant> config;
if (auto it = config.find("timeout"); it != config.end()) {
    auto& [key, value] = *it;  // key是QString, value是QVariant
    int timeout = value.toInt();
}
2. if/switch初始化语句
复制代码
// 旧方式
auto it = container.find(key);
if (it != container.end()) {
    use(*it);
}

// C++17:将初始化限制在if作用域内
if (auto it = container.find(key); it != container.end()) {
    use(*it);  // it的作用域仅限于if和else
} else {
    // 这里也可以访问it
    qDebug() << "Key not found";
}
// it在这里已经销毁

// 多个初始化
if (auto x = getValue(); x > 0 && x < 100) {
    qDebug() << "Valid range:" << x;
}

// switch语句同样适用
switch (auto code = getErrorCode(); code) {
    case 0: qDebug() << "Success"; break;
    case 1: qDebug() << "Error:" << code; break;
    default: qDebug() << "Unknown code"; break;
}

// 实用示例:文件操作
if (std::ifstream file("data.txt"); file.is_open()) {
    std::string line;
    while (std::getline(file, line)) {
        process(line);
    }
}  // file自动关闭
3. 内联变量
复制代码
// 头文件 widgetconstants.h
#pragma once

// C++11/14需要在cpp文件中定义
// C++17可以直接在头文件中定义
inline const QString AppName = "MyApp";
inline constexpr int DefaultWidth = 800;
inline constexpr int DefaultHeight = 600;

namespace Colors {
    inline const QColor Background = QColor(240, 240, 240);
    inline const QColor Text = QColor(0, 0, 0);
    inline const QColor Highlight = QColor(255, 200, 0);
}

// 类中的静态成员
class Config {
public:
    static inline int MaxConnections = 100;
    static inline QString LogPath = "/var/log/myapp/";
};

// 使用:包含头文件即可,无需链接定义
4. 折叠表达式
复制代码
// 变参模板的参数包展开
template<typename... Args>
auto sum(Args... args) {
    return (... + args);  // 左折叠:((arg1 + arg2) + arg3)...
}

template<typename... Args>
auto sumRight(Args... args) {
    return (args + ...);  // 右折叠:arg1 + (arg2 + arg3)...
}

// 使用
qDebug() << sum(1, 2, 3, 4, 5);  // 15
qDebug() << sumRight(1, 2, 3, 4, 5);  // 15

// 逻辑操作
template<typename... Args>
bool allTrue(Args... args) {
    return (... && args);  // 所有参数为true
}

template<typename... Args>
bool anyTrue(Args... args) {
    return (... || args);  // 任一参数为true
}

// 输出所有参数
template<typename... Args>
void printAll(Args... args) {
    (qDebug() << ... << args);  // 输出所有参数
}

// 调用函数
template<typename... Args>
void callAll(Args... args) {
    (args(), ...);  // 依次调用每个函数
}
5. constexpr if(编译期if)
复制代码
// 根据类型在编译期选择代码路径
template<typename T>
auto process(T value) {
    if constexpr (std::is_pointer_v<T>) {
        qDebug() << "Pointer:" << *value;
        return *value;
    } else if constexpr (std::is_integral_v<T>) {
        qDebug() << "Integer:" << value;
        return value * 2;
    } else if constexpr (std::is_same_v<T, QString>) {
        qDebug() << "QString:" << value;
        return value.toUpper();
    } else {
        qDebug() << "Unknown type";
        return value;
    }
}

// 使用
int x = 42;
process(x);              // 调用整数版本
process(new int(100));   // 调用指针版本
process(QString("hello")); // 调用QString版本

// 编译期递归的终止条件
template<typename T, typename... Args>
void printArgs(T first, Args... rest) {
    qDebug() << first;
    if constexpr (sizeof...(rest) > 0) {
        printArgs(rest...);  // 只在有剩余参数时递归
    }
}
6. 嵌套命名空间简化
复制代码
// 旧方式
namespace Company {
    namespace Project {
        namespace Module {
            class Widget {};
        }
    }
}

// C++17简化
namespace Company::Project::Module {
    class Widget {};
}

// 配合内联命名空间
namespace MyLib::v1::Utils {  // 简洁明了
    void helper() {}
}

二、标准库新容器和工具

1. std::optional
复制代码
#include <optional>

// 表示"可能有值"的对象
std::optional<QString> findUserName(int id) {
    if (id > 0 && id < 1000) {
        return QString("User%1").arg(id);
    }
    return std::nullopt;  // 无值
}

// 使用
auto name = findUserName(42);
if (name.has_value()) {      // 或 if (name)
    qDebug() << "Found:" << name.value();
    qDebug() << "Found:" << *name;  // 解引用
} else {
    qDebug() << "Not found";
}

// 提供默认值
QString userName = findUserName(9999).value_or("Guest");

// 就地构造
std::optional<QByteArray> data;
data.emplace(1024, 'A');  // 构造QByteArray(1024, 'A')

// 与指针的区别:明确表达意图,避免空指针歧义
2. std::variant
复制代码
#include <variant>
#include <string>

// 类型安全的联合体
using Value = std::variant<int, double, QString, bool>;

Value v1 = 42;               // 存储int
Value v2 = 3.14;             // 存储double
Value v3 = QString("Hello"); // 存储QString
Value v4 = true;             // 存储bool

// 访问值
qDebug() << std::get<int>(v1);          // 获取int值
qDebug() << std::get<QString>(v3);      // 获取QString值

// 安全访问
if (auto intPtr = std::get_if<int>(&v1)) {
    qDebug() << "Is int:" << *intPtr;
}

// std::visit访问(需要访问者模式)
struct Visitor {
    void operator()(int i) const { qDebug() << "int:" << i; }
    void operator()(double d) const { qDebug() << "double:" << d; }
    void operator()(const QString& s) const { qDebug() << "QString:" << s; }
    void operator()(bool b) const { qDebug() << "bool:" << b; }
};

std::visit(Visitor{}, v1);  // 根据实际类型调用对应函数

// C++17配合泛型Lambda
std::visit([](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>) {
        qDebug() << "Integer:" << arg;
    } else if constexpr (std::is_same_v<T, QString>) {
        qDebug() << "String:" << arg;
    }
}, v1);
3. std::any
复制代码
#include <any>

// 可以存储任意类型的容器
std::any anything;
anything = 42;                      // 存储int
anything = QString("Hello");        // 存储QString
anything = std::vector<int>{1,2,3}; // 存储vector

// 检查类型
if (anything.type() == typeid(int)) {
    qDebug() << "Contains int";
}

// 获取值
try {
    int value = std::any_cast<int>(anything);
    qDebug() << "Value:" << value;
} catch (const std::bad_any_cast& e) {
    qDebug() << "Wrong type!";
}

// 指针版本(不抛出异常)
if (auto ptr = std::any_cast<QString>(&anything)) {
    qDebug() << "QString:" << *ptr;
}

// 重置
anything.reset();  // 变为空
if (!anything.has_value()) {
    qDebug() << "Empty";
}
4. std::string_view
复制代码
#include <string_view>

// 字符串的只读视图,不拥有数据
void printString(std::string_view sv) {
    qDebug() << sv.data() << "(length:" << sv.length() << ")";
}

// 可以从多种类型构造
QString qstr = "Hello Qt";
std::string stdstr = "Hello STL";
char cstr[] = "Hello C";

printString("Literal");      // 字符串字面量
printString(qstr.toStdString());  // std::string
printString(cstr);           // C字符串
printString(stdstr);         // std::string

// 高效,避免拷贝
std::string_view view = qstr.toStdString();  // 不复制数据
qDebug() << view.substr(0, 5);  // "Hello" - 子串也不复制

// 注意:要确保原字符串的生命周期
5. std::filesystem
复制代码
#include <filesystem>
namespace fs = std::filesystem;

// 路径操作
fs::path p = "/home/user/docs/report.txt";
qDebug() << p.filename();     // "report.txt"
qDebug() << p.extension();    // ".txt"
qDebug() << p.parent_path();  // "/home/user/docs"

// 文件操作
if (fs::exists(p)) {
    qDebug() << "File exists, size:" << fs::file_size(p);
    
    // 拷贝
    fs::copy(p, "/tmp/backup.txt");
    
    // 重命名
    fs::rename("/tmp/backup.txt", "/tmp/report_backup.txt");
    
    // 删除
    fs::remove("/tmp/report_backup.txt");
}

// 目录遍历
for (const auto& entry : fs::directory_iterator("/home/user")) {
    qDebug() << entry.path();
    if (entry.is_regular_file()) {
        qDebug() << "File size:" << entry.file_size();
    }
}

// 创建目录
fs::create_directories("/tmp/myapp/logs");  // 创建多级目录
6. 并行算法
复制代码
#include <algorithm>
#include <execution>

std::vector<int> data(1000000);

// 顺序执行(传统方式)
std::sort(data.begin(), data.end());

// 并行执行
std::sort(std::execution::par, data.begin(), data.end());

// 更多并行算法
std::for_each(std::execution::par, data.begin(), data.end(),
              [](int& n) { n *= 2; });

std::transform(std::execution::par,
               data.begin(), data.end(), data.begin(),
               [](int n) { return n * n; });

auto sum = std::reduce(std::execution::par,
                       data.begin(), data.end(), 0);

// 执行策略:
// - seq: 顺序执行(默认)
// - par: 并行执行
// - par_unseq: 并行+向量化

三、在Qt开发中的实用组合

模式1:安全配置读取
复制代码
#include <optional>

class Config {
    std::map<QString, QVariant> settings;
    
public:
    template<typename T>
    std::optional<T> get(const QString& key) const {
        if (auto it = settings.find(key); it != settings.end()) {
            if (auto value = it->second.value<T>(); value) {
                return *value;
            }
        }
        return std::nullopt;
    }
    
    template<typename T>
    T getOr(const QString& key, T defaultValue) const {
        return get<T>(key).value_or(defaultValue);
    }
};

// 使用
Config config;
auto timeout = config.get<int>("timeout");  // optional<int>
if (timeout) {
    qDebug() << "Timeout:" << *timeout;
}

int width = config.getOr("width", 800);    // 默认800
QString title = config.getOr("title", QString("Default"));
模式2:类型安全的消息处理
复制代码
#include <variant>

using Message = std::variant<
    struct ConnectMessage { QString address; int port; },
    struct DataMessage { QByteArray data; },
    struct CommandMessage { QString command; QVariant args; },
    struct ErrorMessage { int code; QString description; }
>;

class MessageProcessor {
public:
    void handle(const Message& msg) {
        std::visit([this](auto&& arg) {
            this->process(std::forward<decltype(arg)>(arg));
        }, msg);
    }
    
private:
    void process(const ConnectMessage& msg) {
        qDebug() << "Connect to" << msg.address << ":" << msg.port;
    }
    
    void process(const DataMessage& msg) {
        qDebug() << "Data size:" << msg.data.size();
    }
    
    void process(const CommandMessage& msg) {
        qDebug() << "Command:" << msg.command;
    }
    
    void process(const ErrorMessage& msg) {
        qDebug() << "Error" << msg.code << ":" << msg.description;
    }
};
模式3:现代工厂模式
复制代码
#include <memory>
#include <functional>

class WidgetFactory {
    std::map<QString, std::function<std::unique_ptr<QWidget>()>> creators;
    
public:
    template<typename T, typename... Args>
    void registerType(const QString& name, Args&&... args) {
        creators[name] = [args...]() {
            return std::make_unique<T>(args...);
        };
    }
    
    std::unique_ptr<QWidget> create(const QString& name) {
        if (auto it = creators.find(name); it != creators.end()) {
            return it->second();
        }
        return nullptr;
    }
};

// 使用
WidgetFactory factory;
factory.registerType<QPushButton>("button", "Click Me");
factory.registerType<QLabel>("label", "Hello World");
factory.registerType<QLineEdit>("edit");

auto widget = factory.create("button");
if (widget) widget->show();

总结对比

特性类别 C++11 C++14 C++17
自动类型 auto变量 auto函数返回类型 结构化绑定
Lambda 基本Lambda 泛型Lambda, 初始化捕获 常量求值Lambda
智能指针 shared_ptr, unique_ptr make_unique -
模板 变参模板 变量模板 折叠表达式, if constexpr
编译期计算 受限constexpr 放宽constexpr constexpr if, Lambda
新类型 - - optional, variant, any
字符串 - - string_view
文件系统 - - filesystem
并发 线程库 - 并行算法
语法糖 范围for, nullptr 数字分隔符 嵌套命名空间, if初始化

学习建议

  1. C++11是基础,必须完全掌握

  2. C++14是优化,让代码更简洁

  3. C++17是生产力飞跃,提供全新编程范式

在Qt 6开发中,充分利用这些特性可以写出更安全、更高效、更易维护的代码。

相关推荐
Pacify_The_North1 小时前
【C++11(二)】可变参数模板和 lambda表达式
java·开发语言·c++
顺顺 尼1 小时前
包装器c++11
开发语言·c++
阿里嘎多学长1 小时前
2025-12-05 GitHub 热点项目精选
开发语言·程序员·github·代码托管
王光环1 小时前
C语言写exe脚本
c语言·开发语言
8278209371 小时前
python scp 备份
开发语言·python
獭.獭.1 小时前
C++ -- 二叉搜索树
数据结构·c++·算法·二叉搜索树
leoufung1 小时前
图解除法查询问题:用 C 语言和 DFS 实现带权有向图的路径乘积
c语言·开发语言·深度优先
poggioxay1 小时前
JAVA零基础入门知识3(持续更新中)
java·开发语言·python
鹤归时起雾.1 小时前
Vue3响应式编程核心指南
开发语言·vue3