详细介绍 C++ 基础知识,涵盖核心概念、语法、特性,并提供学习意见,帮助你系统地掌握 C++

详细介绍 C++ 基础知识,涵盖核心概念、语法、特性,并提供学习意见,帮助你系统地掌握 C++。同时,我会结合前文的 多设备调试工具 上下文,扩展一个简化的 Demo 项目,展示 C++ 基础知识在 Qt 项目中的实际应用。该 Demo 将是一个串口调试工具,支持时间戳记录(结合前文的日期和时间处理),使用 C++17,集成模板、右值引用、线程安全,确保跨平台(Windows 和 Linux)并支持中文(UTF-8)。以下是详细内容。


C++ 基础知识详解与学习意见

1. C++ 基础知识概述

C++ 是一种功能强大的通用编程语言,支持过程式、面向对象和泛型编程。它广泛应用于系统编程、游戏开发、嵌入式系统和高性能应用。C++ 继承自 C,增加了类、模板、异常处理等现代特性。

1.1 核心特性

  • 编译型:代码编译为机器码,运行效率高。

  • 多范式:支持过程式、面向对象、泛型编程。

  • 内存管理:手动管理(如 new/delete)或智能指针。

  • 标准库:提供容器(vector、map)、算法(sort)、I/O 等。

  • 跨平台:通过条件编译支持不同操作系统。

1.2 基础知识点

  1. 基本语法:

    • 数据类型:int、double、char、bool。

    • 变量和常量:const、constexpr。

    • 运算符:算术、逻辑、位运算。

    • 控制结构:if、switch、for、while。

  2. 函数:

    • 定义和声明。

    • 参数传递:值、引用、指针。

    • 默认参数、函数重载。

  3. 指针和引用:

    • 指针:int* ptr。

    • 引用:int& ref。

    • 动态内存:new、delete。

  4. 面向对象:

    • 类和对象:class、struct。

    • 构造函数、析构函数。

    • 继承、多态、虚函数。

    • 封装、访问控制(public、private)。

  5. 标准模板库 (STL):

    • 容器:vector、map、set。

    • 迭代器:遍历容器。

    • 算法:sort、find。

  6. C++11 及以上特性:

    • 自动类型推导:auto。

    • 智能指针:unique_ptr、shared_ptr。

    • 右值引用和移动语义:&&、std::move。

    • 模板增强:变参模板、constexpr。

    • Lambda 表达式。

    • 线程支持:<thread>、<mutex>。

    • Chrono 库:时间处理。

  7. 异常处理:

    • try、catch、throw。

  8. 文件 I/O:

    • <fstream>:ifstream、ofstream。

2. 学习意见

2.1 学习路径

  1. 入门(1-2 个月):

    • 掌握基本语法:变量、控制结构、函数。

    • 练习简单程序:计算器、猜数字游戏。

    • 资源:在线教程(如 LearnCpp.com)、书籍(如《C++ Primer》)。

  2. 进阶(3-4 个月):

    • 学习面向对象:类、继承、多态。

    • 掌握指针、引用、动态内存。

    • 使用 STL:vector、map、算法。

    • 项目:实现简单学生管理系统。

  3. 高级(5-6 个月):

    • 深入 C++11/17:智能指针、右值引用、Lambda。

    • 学习模板和泛型编程。

    • 掌握多线程和线程安全。

    • 项目:实现网络聊天程序或简单游戏。

  4. 实践(持续):

    • 参与开源项目或竞赛(如 LeetCode、Codeforces)。

    • 开发实际应用:结合 Qt 的 GUI 工具、嵌入式系统。

    • 学习框架:Qt、Boost。

2.2 学习建议

  • 实践驱动:每学一个知识点,写小 Demo 验证。

  • 调试技能:使用 IDE(如 Qt Creator、Visual Studio)设置断点,观察变量。

  • 阅读代码:分析开源项目(如 Qt 源码)。

  • 版本选择:优先学习 C++17,兼容现代特性。

  • 工具:

    • 编译器:GCC、Clang、MSVC。

    • IDE:Qt Creator、Visual Studio Code。

    • 调试器:GDB、LLDB。

  • 资源:

    • 书籍:《C++ Primer》、《Effective C++》。

    • 在线:CppReference.com、GeeksforGeeks。

    • 课程:Udemy、Coursera 的 C++ 课程。

2.3 常见问题与解决

  1. 指针错误:

    • 问题:野指针、空指针解引用。

    • 解决:初始化指针,使用智能指针。

  2. 内存泄漏:

    • 问题:new 后未 delete。

    • 解决:使用 unique_ptr 或 RAII。

  3. 编译错误:

    • 问题:模板语法复杂。

    • 解决:逐步调试,参考 CppReference。

  4. 性能瓶颈:

    • 问题:容器选择不当。

    • 解决:根据需求选择 vector(连续内存)或 map(键值对)。

3. Demo 项目:简化的串口调试工具

3.1 项目概述

基于前文的 多设备调试工具,简化实现一个 Qt GUI 串口调试工具,展示 C++ 基础知识:

  • 功能:

    • 打开/关闭串口(COM3 或 /dev/ttyUSB0)。

    • 异步读取串口数据,记录时间戳。

    • 显示数据和响应时间(表格和日志)。

    • 支持周期性读取(每秒)。

  • C++ 基础知识:

    • 基本语法:变量、函数、控制结构。

    • 指针和引用:管理串口句柄。

    • 面向对象:类封装。

    • STL:vector、map、stringstream。

    • C++11/17:auto、智能指针、右值引用、<chrono>、<mutex>。

    • 异常处理:捕获串口错误。

    • 文件 I/O:记录日志到文件。

  • Qt 集成:

    • QMainWindow:GUI。

    • QTableWidget:显示数据。

    • QTimer:定时读取。

    • QDateTime:时间戳。

  • 技术:

    • C++17,跨平台,UTF-8。

    • 线程安全:std::mutex。

    • 模板:泛型数据记录。

3.2 项目文件结构

SerialDebugger/
├── main.cpp
├── mainwindow.h
├── mainwindow.cpp
├── mainwindow.ui
├── serialport.h
├── serialport.cpp
├── datamanager.h
├── datamanager.cpp
├── timeutils.h
├── timeutils.cpp
├── SerialDebugger.pro

3.3 项目配置文件

pro

# SerialDebugger.pro
QT += core gui widgets
CONFIG += c++17
TARGET = SerialDebugger
TEMPLATE = app

SOURCES += \
    main.cpp \
    mainwindow.cpp \
    serialport.cpp \
    datamanager.cpp \
    timeutils.cpp

HEADERS += \
    mainwindow.h \
    serialport.h \
    datamanager.h \
    timeutils.h

FORMS += mainwindow.ui

win32 {
    LIBS += -lsetupapi -lws2_32
    QMAKE_CXXFLAGS += -D_WIN32_WINNT=0x0600
}
unix {
    LIBS += -lgthread-2.0
    QMAKE_CXXFLAGS += -pthread
}

CODECFORTR = UTF-8
CODECFORSRC = UTF-8
QMAKE_CXXFLAGS += -Wall -Wextra -O2

DESTDIR = bin
OBJECTS_DIR = build
MOC_DIR = build
RCC_DIR = build
UI_DIR = build

3.4 代码实现

3.4.1 时间工具类(timeutils.h/cpp)

复用前文的 TimeUtils,简化:

cpp

// timeutils.h
#ifndef TIMEUTILS_H
#define TIMEUTILS_H

#include <chrono>
#include <string>
#include <QString>

class TimeUtils {
public:
    static QString getCurrentTimeQString(const QString& format = "yyyy-MM-dd hh:mm:ss");
    static std::string getCurrentTimeChrono(const std::string& format = "%Y-%m-%d %H:%M:%S");
    static int64_t getDurationMs(const std::chrono::system_clock::time_point& start,
                                 const std::chrono::system_clock::time_point& end);
};

#endif

cpp

// timeutils.cpp
#include "timeutils.h"
#include <QDateTime>
#include <iomanip>
#include <sstream>

QString TimeUtils::getCurrentTimeQString(const QString& format) {
    return QDateTime::currentDateTime().toString(format);
}

std::string TimeUtils::getCurrentTimeChrono(const std::string& format) {
    auto now = std::chrono::system_clock::now();
    time_t rawtime = std::chrono::system_clock::to_time_t(now);
    std::stringstream ss;
    ss << std::put_time(std::localtime(&rawtime), format.c_str());
    return ss.str();
}

int64_t TimeUtils::getDurationMs(const std::chrono::system_clock::time_point& start,
                                 const std::chrono::system_clock::time_point& end) {
    return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}

3.4.2 串口类(serialport.h/cpp)

cpp

// serialport.h
#ifndef SERIALPORT_H
#define SERIALPORT_H

#include <string>
#include <vector>
#include <functional>
#ifdef _WIN32
#include <windows.h>
#else
#include <termios.h>
#endif

class SerialPort {
private:
    std::string portName; // 串口名称
    int baudRate;         // 波特率
#ifdef _WIN32
    HANDLE hSerial;       // Windows 句柄
#else
    int fd;               // Linux 文件描述符
    struct termios oldtio;
#endif
public:
    SerialPort(std::string port, int baud); // 构造函数
    ~SerialPort();                          // 析构函数
    void asyncRead(std::function<void(std::vector<uint8_t>, std::chrono::system_clock::time_point)> callback);
    void write(const std::vector<uint8_t>& data);
    bool isOpen() const;
};

#endif

cpp

// serialport.cpp
#include "serialport.h"
#include <QThread>
#include <stdexcept>

SerialPort::SerialPort(std::string port, int baud) : portName(std::move(port)), baudRate(baud) {
#ifdef _WIN32
    hSerial = CreateFileA(portName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr,
                          OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (hSerial == INVALID_HANDLE_VALUE) throw std::runtime_error("打开串口失败");
    DCB dcb = {0};
    dcb.DCBlength = sizeof(dcb);
    dcb.BaudRate = baudRate;
    dcb.ByteSize = 8;
    dcb.StopBits = ONESTOPBIT;
    dcb.Parity = NOPARITY;
    SetCommState(hSerial, &dcb);
#else
    fd = open(portName.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0) throw std::runtime_error("打开串口失败");
    tcgetattr(fd, &oldtio);
    struct termios newtio = {0};
    cfsetospeed(&newtio, B9600);
    newtio.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
    tcsetattr(fd, TCSANOW, &newtio);
#endif
}

SerialPort::~SerialPort() {
#ifdef _WIN32
    if (hSerial != INVALID_HANDLE_VALUE) CloseHandle(hSerial);
#else
    if (fd >= 0) {
        tcsetattr(fd, TCSANOW, &oldtio);
        close(fd);
    }
#endif
}

bool SerialPort::isOpen() const {
#ifdef _WIN32
    return hSerial != INVALID_HANDLE_VALUE;
#else
    return fd >= 0;
#endif
}

void SerialPort::asyncRead(std::function<void(std::vector<uint8_t>, std::chrono::system_clock::time_point)> callback) {
    QThread* thread = new QThread;
    QObject::connect(thread, &QThread::started, [=]() {
        std::vector<uint8_t> buffer(1024);
        auto start = std::chrono::system_clock::now();
#ifdef _WIN32
        DWORD bytesRead;
        ReadFile(hSerial, buffer.data(), buffer.size(), &bytesRead, nullptr);
        buffer.resize(bytesRead);
#else
        ssize_t bytesRead = read(fd, buffer.data(), buffer.size());
        if (bytesRead > 0) buffer.resize(bytesRead);
#endif
        callback(std::move(buffer), start);
        thread->quit();
    });
    QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
    thread->start();
}

void SerialPort::write(const std::vector<uint8_t>& data) {
#ifdef _WIN32
    DWORD bytesWritten;
    WriteFile(hSerial, data.data(), data.size(), &bytesWritten, nullptr);
#else
    write(fd, data.data(), data.size());
#endif
}

3.4.3 数据管理类(datamanager.h/cpp)

cpp

// datamanager.h
#ifndef DATAMANAGER_H
#define DATAMANAGER_H

#include <QObject>
#include <QTimer>
#include <mutex>
#include <vector>
#include <memory>
#include <fstream>
#include "serialport.h"
#include "timeutils.h"

// 数据记录
struct SerialData {
    std::string timestamp; // 时间戳
    std::vector<uint8_t> data; // 原始数据
    int64_t responseTimeMs; // 响应时间
    SerialData(std::string ts, std::vector<uint8_t> d, int64_t rt)
        : timestamp(std::move(ts)), data(std::move(d)), responseTimeMs(rt) {}
    std::string toString() const {
        std::stringstream ss;
        ss << "时间: " << timestamp << ", 数据: ";
        for (auto v : data) ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(v) << " ";
        ss << ", 响应时间: " << std::dec << responseTimeMs << "ms";
        return ss.str();
    }
};

// 模板记录容器
template<typename T>
class DataRecord {
    std::vector<std::unique_ptr<T>> records;
public:
    void addRecord(std::unique_ptr<T>&& record) { records.push_back(std::move(record)); }
    const std::vector<std::unique_ptr<T>>& getRecords() const { return records; }
};

class DataManager : public QObject {
    Q_OBJECT
private:
    std::unique_ptr<SerialPort> serialPort; // 串口对象
    DataRecord<SerialData> records;         // 数据记录
    std::mutex recordsMutex;                // 线程安全
    QTimer* readTimer;                      // 定时器
    std::ofstream logFile;                  // 日志文件
    std::chrono::system_clock::time_point requestTime; // 请求时间

public:
    DataManager(QObject* parent = nullptr);
    ~DataManager();
    bool openSerial(const std::string& port, int baud);
    void closeSerial();
    void startRead(bool periodic = false, int intervalMs = 1000);
    void writeData(const std::vector<uint8_t>& data);
    const std::vector<std::unique_ptr<SerialData>>& getRecords() const;

signals:
    void dataUpdated();
    void errorOccurred(const QString& error);
    void serialStateChanged(bool isOpen);

private slots:
    void onReadTimeout();
};

#endif

cpp

// datamanager.cpp
#include "datamanager.h"
#include <QDateTime>

DataManager::DataManager(QObject* parent) : QObject(parent) {
    readTimer = new QTimer(this);
    connect(readTimer, &QTimer::timeout, this, &DataManager::onReadTimeout);
    logFile.open("serial_log.txt", std::ios::app);
    if (!logFile.is_open()) {
        emit errorOccurred("无法打开日志文件");
    }
}

DataManager::~DataManager() {
    closeSerial();
    readTimer->stop();
    if (logFile.is_open()) logFile.close();
}

bool DataManager::openSerial(const std::string& port, int baud) {
    try {
        serialPort = std::make_unique<SerialPort>(port, baud);
        emit serialStateChanged(true);
        return true;
    } catch (const std::exception& e) {
        emit errorOccurred(QString::fromStdString("打开串口失败: " + std::string(e.what())));
        return false;
    }
}

void DataManager::closeSerial() {
    readTimer->stop();
    serialPort.reset();
    emit serialStateChanged(false);
}

void DataManager::startRead(bool periodic, int intervalMs) {
    if (!serialPort || !serialPort->isOpen()) {
        emit errorOccurred("串口未打开");
        return;
    }
    requestTime = std::chrono::system_clock::now();
    serialPort->asyncRead([this](std::vector<uint8_t> data, std::chrono::system_clock::time_point start) {
        auto end = std::chrono::system_clock::now();
        int64_t responseTimeMs = TimeUtils::getDurationMs(start, end);
        auto record = std::make_unique<SerialData>(
            TimeUtils::getCurrentTimeChrono(), std::move(data), responseTimeMs);
        {
            std::lock_guard<std::mutex> lock(recordsMutex);
            records.addRecord(std::move(record));
            if (logFile.is_open()) {
                logFile << record->toString() << "\n";
                logFile.flush();
            }
        }
        emit dataUpdated();
    });
    if (periodic) {
        readTimer->start(intervalMs);
    }
}

void DataManager::onReadTimeout() {
    startRead(false);
}

void DataManager::writeData(const std::vector<uint8_t>& data) {
    if (!serialPort || !serialPort->isOpen()) {
        emit errorOccurred("串口未打开");
        return;
    }
    try {
        serialPort->write(data);
    } catch (const std::exception& e) {
        emit errorOccurred(QString::fromStdString("写数据失败: " + std::string(e.what())));
    }
}

const std::vector<std::unique_ptr<SerialData>>& DataManager::getRecords() const {
    return records.getRecords();
}

3.4.4 主窗口(mainwindow.h/cpp)

cpp

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTableWidget>
#include "datamanager.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget* parent = nullptr);
    ~MainWindow();

private slots:
    void openSerial();
    void closeSerial();
    void sendData();
    void updateTable();
    void togglePeriodicRead();
    void showError(const QString& error);
    void updateSerialStatus(bool isOpen);

private:
    Ui::MainWindow* ui;
    DataManager* manager;
};

#endif

cpp

// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QInputDialog>
#include <sstream>

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    setWindowTitle("串口调试工具");

    manager = new DataManager(this);

    ui->dataTable->setColumnCount(3);
    ui->dataTable->setHorizontalHeaderLabels({"时间", "数据", "响应时间(ms)"});
    ui->dataTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

    connect(ui->openButton, &QPushButton::clicked, this, &MainWindow::openSerial);
    connect(ui->closeButton, &QPushButton::clicked, this, &MainWindow::closeSerial);
    connect(ui->sendButton, &QPushButton::clicked, this, &MainWindow::sendData);
    connect(ui->periodicCheck, &QCheckBox::toggled, this, &MainWindow::togglePeriodicRead);
    connect(manager, &DataManager::dataUpdated, this, &MainWindow::updateTable);
    connect(manager, &DataManager::errorOccurred, this, &MainWindow::showError);
    connect(manager, &DataManager::serialStateChanged, this, &MainWindow::updateSerialStatus);
}

MainWindow::~MainWindow() {
    delete ui;
}

void MainWindow::openSerial() {
    bool ok;
    QString port = QInputDialog::getText(this, "打开串口", "端口 (如 COM3 或 /dev/ttyUSB0):",
                                         QLineEdit::Normal, "COM3", &ok);
    if (ok && manager->openSerial(port.toStdString(), CBR_9600)) {
        ui->logText->append("串口已打开: " + port);
        manager->startRead(ui->periodicCheck->isChecked(), 1000);
    }
}

void MainWindow::closeSerial() {
    manager->closeSerial();
    ui->logText->append("串口已关闭");
}

void MainWindow::sendData() {
    std::string input = ui->dataInput->text().toStdString();
    std::stringstream ss(input);
    std::vector<uint8_t> data;
    int value;
    while (ss >> std::hex >> value) {
        data.push_back(static_cast<uint8_t>(value));
    }
    manager->writeData(data);
    ui->logText->append("发送数据: " + QString::fromStdString(input));
}

void MainWindow::togglePeriodicRead() {
    manager->startRead(ui->periodicCheck->isChecked(), 1000);
}

void MainWindow::updateTable() {
    const auto& records = manager->getRecords();
    ui->dataTable->setRowCount(records.size());
    for (size_t i = 0; i < records.size(); ++i) {
        const auto& record = records[i];
        ui->dataTable->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(record->timestamp)));
        ui->dataTable->setItem(i, 1, new QTableWidgetItem(QString::fromStdString(record->toString())));
        ui->dataTable->setItem(i, 2, new QTableWidgetItem(QString::number(record->responseTimeMs)));
    }
}

void MainWindow::showError(const QString& error) {
    ui->logText->append("错误: " + error);
}

void MainWindow::updateSerialStatus(bool isOpen) {
    ui->statusLabel->setText(isOpen ? "串口: 已打开" : "串口: 已关闭");
    ui->statusLabel->setStyleSheet(isOpen ? "color: green" : "color: red");
}

3.4.5 UI 文件(mainwindow.ui)

在 Qt Designer 中:

  • 布局:QVBoxLayout:

    • QHBoxLayout:

      • QLabel(statusLabel):串口状态。

      • QPushButton(openButton):文本“打开串口”。

      • QPushButton(closeButton):文本“关闭串口”。

    • QHBoxLayout:

      • QLineEdit(dataInput):输入数据(如 01 02 03)。

      • QPushButton(sendButton):文本“发送”。

      • QCheckBox(periodicCheck):文本“周期性读取”。

    • QTableWidget(dataTable):显示时间、数据、响应时间。

    • QTextEdit(logText):日志。

3.4.6 Main 函数

cpp

// main.cpp
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
    MainWindow window;
    window.show();
    return app.exec();
}

4. C++ 基础知识在 Demo 中的应用

  • 基本语法:

    • 变量:portName、baudRate。

    • 控制结构:if 检查串口状态。

  • 函数:

    • SerialPort::asyncRead 使用回调。

    • 参数传递:std::move 优化 vector。

  • 指针和引用:

    • std::unique_ptr<SerialPort> 管理串口。

    • 引用:回调函数中的 data。

  • 面向对象:

    • SerialPort、DataManager:封装串口和数据逻辑。

    • 构造函数:初始化串口句柄。

  • STL:

    • vector<uint8_t>:存储串口数据。

    • stringstream:解析输入。

  • C++11/17:

    • auto:简化迭代器类型。

    • unique_ptr:内存安全。

    • std::move:优化数据转移。

    • Lambda:异步读取回调。

    • <chrono>:时间戳和响应时间。

    • <mutex>:线程安全。

  • 异常处理:

    • try/catch:捕获串口错误。

  • 文件 I/O:

    • ofstream:记录日志到 serial_log.txt。

5. 输出示例

  • 打开串口 COM3,发送 01 02 03,周期性读取返回 04 05 06。

  • 日志:

    串口已打开: COM3
    发送数据: 01 02 03
    收到数据: 04 05 06 (2025-06-04 15:01:00)
    收到数据: 04 05 06 (2025-06-04 15:01:01)
  • 表格:

    时间                  | 数据                                    | 响应时间(ms)
    2025-06-04 15:01:00  | 时间: 2025-06-04 15:01:00, 数据: 04 05 06 | 10
    2025-06-04 15:01:01  | 时间: 2025-06-04 15:01:01, 数据: 04 05 06 | 12
  • 日志文件(serial_log.txt):

    时间: 2025-06-04 15:01:00, 数据: 04 05 06 , 响应时间: 10ms
    时间: 2025-06-04 15:01:01, 数据: 04 05 06 , 响应时间: 12ms

6. 编译与运行

  • Linux:

    bash

    sudo apt install qtbase5-dev
    mkdir build
    cd build
    qmake ../SerialDebugger.pro
    make
    ./bin/SerialDebugger
  • Windows:

    • 使用 Qt Creator 打开 .pro,构建运行。

    • 确保串口为 COM3。

7. 扩展建议

  • STL:使用 map 管理多串口。

  • 面向对象:添加继承支持 Modbus。

  • C++17:使用 std::optional 处理无效数据。

  • 文件 I/O:支持 CSV 导出。

8. 总结

本教程详细介绍了 C++ 基础知识,提供了系统学习建议,并通过 串口调试工具 Demo 展示了:

  • 基本语法、函数、指针、面向对象。

  • STL、C++11/17 特性、异常处理、文件 I/O。

  • 结合 Qt 的 GUI 和时间处理。

如果你需要深入某个知识点(如模板、多线程)或更复杂的 Demo(如 Modbus 支持),请提供细节,我可以进一步定制!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhxup606

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值