• 嵌入式养成计划-46----QT--简易版网络聊天室实现--QT如何连接数据库


    一百一十九、简易版网络聊天室实现

    119.1 QT实现连接TCP协议

    119.1.1 基于TCP的通信流程

    在这里插入图片描述
    在这里插入图片描述

    119.1.2 QT中实现服务器过程

    1. 使用QTcpServer实例化一个服务器对象
    2. 设置监听状态,通过listen()函数,可以监听特定的主机,也可以监听所有客户端,端口号可以是系统自动分配的,也可以是指定端口号。
    3. 如果有客户端发来连接请求,那么服务器就自动发射一个newConnection信号,我们就可以将该信号连接到自定义的槽函数中处理该客户端的操作。
    4. 此时服务器和客户端已经建起了连接,可以调用nextPandingConnection获取最新连接的客户端套接字,可以将该套接字存放在服务器的客户端容器中。
    5. 当客户端发来数据时,该客户端就会自动发射一个readyRead信号,我们可以将该信号连接到自定义的槽函数中读取客户端数据。
    6. 通过read(),readLine(),readAll()读取套接字里的数据,可以通过write()往套接字中写入数据
    7. 关闭服务器使用close即可

    119.1.3 QT中实现客户端过程

    1. 使用QTcpSocket实例化一个客户端对象
    2. 将客户端连接到服务器,使用connectToHost, 给定主机地址,端口号
    3. 如果连接成功,该客户端会自动发射connected信号,我们可以将该信号连接到自定义的槽函数中处理相关逻辑代码。
    4. 此时,客户端和服务器已经建立了连接,如果服务端发来数据,那么该客户端会自定发射readyRead信号,可以将该信号连接到自定义的槽函数中读取服务端中数据
    5. 可以使用read() readLine() readAll()读取套接字中数据,使用write往套接字中写入数据
    6. 客户端断开与服务器的连接,使用disConnectFromHost, 如果断开成功,客户端会自动发射disconnected信号,我们可以将该信号连接到自定义的槽函数中处理相关逻辑代码。

    119.2 服务器端

    119.2.1 UI 界面

    在这里插入图片描述

    119.2.2 qt_server.h

    #ifndef QT_SERVER_H
    #define QT_SERVER_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class Qt_Server; }
    QT_END_NAMESPACE
    
    class Qt_Server : public QWidget
    {
        Q_OBJECT
    
    public:
        Qt_Server(QWidget *parent = nullptr);
        ~Qt_Server();
    
    private slots:
        void on_startBtn_clicked();
    
    public slots:
        //  自定义的 处理连接的槽函数声明
        void newConnection_slot();
        //  自定义的 处理接收数据的槽函数声明
        void readyRead_slot();
    
    private:
        Ui::Qt_Server *ui;
    
        QTcpServer *server;
    
        QList<QTcpSocket *> socketList;
    };
    #endif // QT_SERVER_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    119.2.3 qt_server.cpp

    #include "qt_server.h"
    #include "ui_qt_server.h"
    
    Qt_Server::Qt_Server(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Qt_Server)
    {
        ui->setupUi(this);
    
        //  给服务器指针创建空间
        server = new QTcpServer(this);
    //    socketList = new QList<>();
    }
    
    Qt_Server::~Qt_Server()
    {
        delete ui;
    }
    
    //  点击启动服务器按钮对应的槽函数实现
    void Qt_Server::on_startBtn_clicked()
    {
        if(ui->startBtn->text() == "启动服务器"){
            //  获取UI界面上输入的端口号
            quint16 port = ui->portEdit->text().toUInt();
            //  使服务器进入监听状态,返回值是bool类型
            if(server->listen(QHostAddress::Any,port)){
                QMessageBox::information(this,"提示","服务器启动成功");
            }else{
                QMessageBox::critical(this,"错误","服务器启动失败");
            }
            //  此时服务器已经进入监听状态,如果有客户端发来链接请求,
            //  则服务器会自动发射一个newConnection信号,需要将该信号连接到自定义的槽函数中处理连接的套接字
            connect(server, &QTcpServer::newConnection, this, &Qt_Server::newConnection_slot);
            ui->startBtn->setText("已启动服务器");
            ui->startBtn->setStyleSheet("background-color:green");
        }else{
            server->close();
            disconnect(server, &QTcpServer::newConnection, this, &Qt_Server::newConnection_slot);
            ui->startBtn->setStyleSheet("background-color:white");
            ui->startBtn->setText("启动服务器");
        }
    
    }
    
    //  自定义的 处理连接的槽函数声明
    void Qt_Server::newConnection_slot()
    {
        qDebug() << "有新客户的连接";
        //  获取最新连接的客户端套接字,并放入容器中,此时客户端与服务器已经建立起连接
        QTcpSocket *s = server->nextPendingConnection();
        socketList.push_back(s);
    
        //  如果有客户端发送数据,那么此客户端就会自动发射readyRead信号
        //  需要将该信号与自定义的处理接收数据的槽函数连接
        connect(s, &QTcpSocket::readyRead, this, &Qt_Server::readyRead_slot);
    }
    
    //  自定义的 处理接收数据的槽函数实现
    void Qt_Server::readyRead_slot()
    {
        //  移除无效的客户端
        //  遍历所有的客户端
        for(int i=0; i<socketList.count(); i++){
            //  判断每个客户端的状态,返回值是枚举类型
            //  socketList.at(i)->state();
            if(0 == socketList.at(i)->state()){
                //  移除当前客户端,通过下标删除
                socketList.removeAt(i);
            }
        }
        //  遍历容器,找到有需要读取数据的客户端
        for(int i=0; i<socketList.count(); i++){
            //  如果当前客户端的 有效字节数 不为0,代表当前客户端有需要读取的数据
            if(0 != socketList.at(i)->bytesAvailable()){
                //  读取客户端中的数据
                QByteArray msg = socketList.at(i)->readAll();
                //  将读取的数据放到UI界面上
                ui->listWidget->addItem(QString::fromLocal8Bit(msg));
    
                //  将数据发送给所有客户端
                for (int j=0; j<socketList.count(); j++) {
                    socketList.at(i)->write(msg);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    119.2.4 main.cpp

    #include "qt_server.h"
    
    #include 
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        Qt_Server w;
        w.show();
        return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    119.3 客户端

    119.3.1 UI 界面

    在这里插入图片描述

    119.3.2 qt_client.h

    #ifndef QT_CLIENT_H
    #define QT_CLIENT_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class Qt_Client; }
    QT_END_NAMESPACE
    
    class Qt_Client : public QWidget
    {
        Q_OBJECT
    
    public:
        Qt_Client(QWidget *parent = nullptr);
        ~Qt_Client();
    
    public slots:
        void connected_slot();
        void readyRead_slot();
    
    private slots:
        void on_connectBtn_clicked();
    
        void on_msgBtn_clicked();
    
        void on_disconnectBtn_clicked();
    
    private:
        Ui::Qt_Client *ui;
    
        //  定一个客户端对象
        QTcpSocket *socket;
        //  定义一个用户名变量
        QString uname;
    };
    #endif // QT_CLIENT_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    119.3.3 qt_client.cpp

    #include "qt_client.h"
    #include "ui_qt_client.h"
    
    Qt_Client::Qt_Client(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Qt_Client)
    {
        ui->setupUi(this);
        //  给客户端对象创建空间
        socket = new QTcpSocket(this);
    
        //  初始化UI界面的组件状态
        ui->msgEdit->setEnabled(false);
        ui->msgBtn->setEnabled(false);
        ui->disconnectBtn->setEnabled(false);
    
    }
    
    Qt_Client::~Qt_Client()
    {
        delete ui;
    }
    
    void Qt_Client::connected_slot()
    {
        //  告诉服务器我进来了
        QString msg = uname + " 来了,快接驾";
        //  将这个消息发送给服务器
        socket->write(msg.toLocal8Bit());
        //  此时说明服务器与客户端已经建立连接
    
        //  现在将UI界面的组件状态修改一下
        ui->msgEdit->setEnabled(true);
        ui->msgBtn->setEnabled(true);
        ui->disconnectBtn->setEnabled(true);
        ui->unameEdit->setEnabled(false);
        ui->ipEdit->setEnabled(false);
        ui->portEdit->setEnabled(false);
        ui->connectBtn->setEnabled(false);
    
        //  如果服务器发来数据,客户端会自动发射readyRead信号
        //  因此需要将readyRead信号连接到自定义的槽函数
        //  因为只需要连接一次,所以也是该在构造函数中写连接函数
        connect(socket, &QTcpSocket::readyRead, this, &Qt_Client::readyRead_slot);
    }
    
    void Qt_Client::readyRead_slot()
    {
        //  走到了这一步,说明服务器给客户端发送了消息,现在需要进行读取
        QByteArray msg = socket->readAll();
        //  将这个数据放到UI界面的消息显示框中
        ui->listWidget->addItem(QString::fromLocal8Bit(msg));
    }
    
    //  连接服务器按钮 对应的槽函数
    void Qt_Client::on_connectBtn_clicked()
    {
        //  获取UI界面的IP和PORT,还有uname
        QString ip = ui->ipEdit->text();
        quint16 port = ui->portEdit->text().toUInt();
        uname = ui->unameEdit->text();
        //  使客户端连接服务器
        socket->connectToHost(ip, port);
    
        //  判断客户端是否成功连接服务器,成功则客户端会自动发射connected信号
        //  将该信号连接到自定义的槽函数中
        //  因为只需要连接一次,所以连接函数应该写在构造函数中
        connect(socket, &QTcpSocket::connected, this, &Qt_Client::connected_slot);
    }
    
    void Qt_Client::on_msgBtn_clicked()
    {
        //  获取UI界面上输入的内容
        QString msg = uname + " : " + ui->msgEdit->toPlainText();
        //  将消息发送给服务器
        socket->write(msg.toLocal8Bit());
    
    }
    
    void Qt_Client::on_disconnectBtn_clicked()
    {
        QString msg = uname + " 走咯,我还会再回来的";
        //  将消息发送给服务器
        socket->write(msg.toLocal8Bit());
        //  断开链接
        socket->close();
        disconnect(socket, &QTcpSocket::readyRead, this, &Qt_Client::readyRead_slot);
        disconnect(socket, &QTcpSocket::connected, this, &Qt_Client::connected_slot);
    
        //  更改UI界面的组件状态
        ui->msgEdit->setEnabled(false);
        ui->msgBtn->setEnabled(false);
        ui->disconnectBtn->setEnabled(false);
        ui->unameEdit->setEnabled(true);
        ui->ipEdit->setEnabled(true);
        ui->portEdit->setEnabled(true);
        ui->connectBtn->setEnabled(true);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    119.3.4 main.cpp

    #include "qt_client.h"
    
    #include 
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        Qt_Client w;
        w.show();
        return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    一百二十、QT连接数据库

    120.1 QT将数据库分为三个层次

    1. 数据库驱动层:QSqlDriver、QSqlDriverCreator、QSqlDriverCreatorBase、QSqlDriverPlugin
    2. sql接口层:QSqlDatabase、QSqlQuery、QSqlRecord、QSqlError
    3. 用户接口层:提供一些模型QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel

    120.2 实现数据库操作的相关方法

    1.	添加数据库:[static] QSqlDatabase QSqlDatabase::addDatabase(QSqlDriver *driver, const QString &connectionName = QLatin1String(defaultConnection))                                                                
    		QSQLITE SQLite version 3 or above
    2.	设置数据库名称:void QSqlDatabase::setDatabaseName(const QString &name)
    3.	包含数据库:bool QSqlDatabase::contains(const QString &connectionName = QLatin1String(defaultConnection))
    4.	打开数据库:bool QSqlDriver::open(const QString &db)
    5.	关闭数据库:void QSqlDatabase::close()
    6.	错误信息:QSqlError QSqlDatabase::lastError()
    
    7.	sql语句执行:构造一个QSqlQuery类对象,调用其成员函数exec,执行sql语句:bool QSqlQuery::exec(const QString &query)
    8.	bool QSqlQuery::next():遍历查询结果的函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    120.3 示例 :

    120.3.1 UI 界面

    120.3.2 database.h

    #ifndef DATABASE_H
    #define DATABASE_H
    
    #include 
    #include  //  数据库管理类
    #include     //  数据库操作类
    #include    //  数据库记录类
    #include     //  数据库错误类
    #include   //  消息对话框类
    #include 
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class DataBase; }
    QT_END_NAMESPACE
    
    class DataBase : public QWidget
    {
        Q_OBJECT
    
    public:
        DataBase(QWidget *parent = nullptr);
        ~DataBase();
    
    private slots:
        void on_addBtn_clicked();
    
        void on_showAllEdit_clicked();
    
        void on_showEdit_clicked();
    
        void on_deleteBtn_clicked();
    
        void on_deleteAllBtn_clicked();
    
    private:
        Ui::DataBase *ui;
    
        //  实例化一个数据库对象
        QSqlDatabase db;
    };
    #endif // DATABASE_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    120.3.3 database.cpp

    #include "database.h"
    #include "ui_database.h"
    
    DataBase::DataBase(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::DataBase)
    {
        ui->setupUi(this);
    
        //  判断数据库是否存在
        if(!db.contains())
        {
            //  不存在,则创建数据库
            db = QSqlDatabase::addDatabase("QSQLITE");   //  表示数据库驱动为sqlite3
            //  给刚创建的数据库起名
            db.setDatabaseName("stuInfo.db");
    
            //  提示用户:数据库创建成功
            QMessageBox::information(this,"提示","数据库创建成功");
        }
        //  打开数据库
        if(!db.open()){
            QMessageBox::critical(this,"错误","数据库无法打开");
            return;
        }
        //  创建数据库表
        QSqlQuery table;
        //  Sql语句
        QString sql = "create table if not exists stu_info_table("
                      "id integer primary key autoincrement,"
                      "numb integer,"
                      "name varchar(20),"
                      "sex varchar(4),"
                      "score integer);";
        if(table.exec(sql)){
            QMessageBox::information(this,"提示","数据库学生表创建成功");
        }else{
            QMessageBox::critical(this,"错误","数据库学生表创建失败");
        }
    }
    
    DataBase::~DataBase()
    {
        delete ui;
    }
    
    //  添加
    void DataBase::on_addBtn_clicked()
    {
        //  获取UI界面上的信息
        int num = ui->numEdit->text().toUInt();
        QString name = ui->nameEdit->text();
        QString sex = ui->sexEdit->text();
        int score = ui->scoreEdit->text().toUInt();
    
        //  保证用户输入完整信息
        if(num == 0 || name.isEmpty() || sex.isEmpty() || score == 0){
            QMessageBox::warning(this,"警告","请完善信息");
            return;
        }
    
        //  将信息存放到数据库学生表中
        QSqlQuery query;
        //  SQL语句
        QString sql = QString("insert into stu_info_table (numb, name, sex, score) "
                              "values(%1,'%2','%3',%4)")
                .arg(num).arg(name).arg(sex).arg(score);
    
        //  执行SQL语句
        if(query.exec(sql)){
            QMessageBox::information(this,"提示","数据添加成功");
        }else{
            QMessageBox::critical(this,"错误","数据添加失败");
        }
        DataBase::on_showAllEdit_clicked();
    }
    
    //  查看所有
    void DataBase::on_showAllEdit_clicked()
    {
        ui->tableWidget->clear();
        //  将信息存放到数据库学生表中
        QSqlQuery query;
        //  SQL语句
        QString sql = QString("select * from stu_info_table");
        //  执行SQL语句
        if(query.exec(sql)){
            //  将数据库的内容放到UI界面上
            int i = 0;  //  记录行号
            //  用next()遍历
            while (query.next()) {
                for(int j=0; j<query.record().count(); j++){
                    ui->tableWidget->setItem(i,j,new QTableWidgetItem(query.value(j+1).toString()));
                }
                i++;
            }
        }
    }
    
    //  查询某个学生
    void DataBase::on_showEdit_clicked()
    {
        QString name = ui->nameEdit_2->text();
        if(name.isEmpty()){
            QMessageBox::information(this, "提示", "请输入要查询的学生姓名");
            return;
        }
        ui->tableWidget->clear();
        QSqlQuery query;
        QString sql = QString("select * from stu_info_table where name='%1';").arg(name);
        if(query.exec(sql)){
            int i=0;
            while (query.next()) {
                //            qDebug() << i;
                for(int j=0; j<query.record().count(); j++){
                    ui->tableWidget->setItem(i, j, new QTableWidgetItem(query.value(j+1).toString()));
                }
                i++;
            }
        }else{
            QMessageBox::warning(this, "警告", "查询错误");
            return;
        }
    }
    
    void DataBase::on_deleteBtn_clicked()
    {
        QString name = ui->nameEdit_2->text();
        if(name.isEmpty()){
            QMessageBox::information(this, "提示", "请输入要删除的学生姓名");
            return ;
        }
        QString sql = QString("delete from stu_info_table where name='%1';").arg(name);
        QSqlQuery query;
        if(query.exec(sql)){
            QMessageBox::information(this, "提示", "删除成功");
            DataBase::on_showAllEdit_clicked();
        }else{
            QMessageBox::warning(this, "警告", "删除失败");
            DataBase::on_showAllEdit_clicked();
        }
        return ;
    }
    
    void DataBase::on_deleteAllBtn_clicked()
    {
        QString sql = QString("delete from stu_info_table");
        QSqlQuery query;
        if(query.exec(sql)){
            QMessageBox::information(this, "提示", "删除成功");
            DataBase::on_showAllEdit_clicked();
        }else{
            QMessageBox::warning(this, "警告", "删除失败");
            DataBase::on_showAllEdit_clicked();
        }
        ui->tableWidget->clear();
        return ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158

    120.3.4 main.cpp

    #include "database.h"
    
    #include 
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        DataBase w;
        w.show();
        return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    Redis专题----2
    网络安全(黑客技术)—2024自学
    【双指针】移动零
    测试日常工作中需要具备哪些知识和能力,在需求评审时需要考虑哪些方面,在技术方面评审时需要考虑哪些方面,从什么方面进行设计测试用例
    PY32F003F18之独立看门狗
    科技云报道:走入商业化拐点,大模型“开箱即用”或突破行业困局
    EntityManagerFactory和EntityManager的一个用法探究
    Redis 哈希( Hash )
    详解K-Means算法
    【C# Programming】类、构造器、静态成员
  • 原文地址:https://blog.csdn.net/qq_52625576/article/details/133963950