1. 程式人生 > >Qt:QSqlDatabase的進一步封裝(多執行緒支援+更加簡單的操作)

Qt:QSqlDatabase的進一步封裝(多執行緒支援+更加簡單的操作)

開發背景:

1.直接用QSqlDatabase我覺得太麻煩了;

2.對於某些資料庫,多個執行緒同時使用一個QSqlDatabase的時候會崩潰;

3.這段時間沒什麼乾貨放出來覺得渾身不舒服,就想寫一個。

於是,我就封裝了一下

只要簡單的例項化,然後通過query()就可以拿到QSqlQuery的例項化物件。

還自帶計時,一段時間不用可自動關閉,既保證效率也不佔用無用資源。

注:需要C++11的支援

不多說,上程式碼:

JasonQt_Database.h

#ifndef __JasonQt_Database_h__
#define __JasonQt_Database_h__

// C++ lib import
#include <functional>

// Qt lib import
#include <QtCore>
#include <QtSql>

#define PropertyDeclare(Type, Name, setName, ...)                   \
    private:                                                        \
    Type m_ ## Name __VA_ARGS__;                                    \
    public:                                                         \
    inline const Type &Name(void) const { return m_ ## Name; }      \
    inline void setName(const Type &Name) { m_ ## Name = Name; }    \
    private:

namespace JasonQt_Database
{

enum DatabaseModeEnum{ DatabaseNameMode, DatabaseHostMode };

enum QueryMode { QueryAutoMode, QueryMultiMode, QuerySingleMode };

class DatabaseSettings
{
private:
    PropertyDeclare(DatabaseModeEnum, databaseMode, setDatabaseMode)

    PropertyDeclare(QString, databaseType, setDatabaseType)
    PropertyDeclare(QString, connectionName, setConnectionName)

    // File mode
    PropertyDeclare(QString, nameModeName, setNameModeName)

    // Host mode
    PropertyDeclare(QString, hostModeHostName, setHostModeHostName)
    PropertyDeclare(QString, hostModeDatabaseName, setHostModeDatabaseName)
    PropertyDeclare(QString, hostModeUserName, setHostModeUserName)
    PropertyDeclare(QString, hostModePassword, setHostModePassword)

private:
    DatabaseSettings(const DatabaseModeEnum &databastMode, const QString &databaseType, const QString &connectionName);

public:
    DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &nameModeName);

    DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &hostModeHostName, const QString &hostModeDatabaseName, const QString &hostModeUserName, const QString &hostModePassword);
};

class ConnectSettings
{
private:
    PropertyDeclare(int, maxOpenTime, setMaxOpenTime)
    PropertyDeclare(QueryMode, queryMode, setQueryMode)
    PropertyDeclare(int, minWaitTime, setMinWaitTime)

public:
    ConnectSettings(const int &maxOpenTime = 60 * 1000, const QueryMode &queryMode = QueryAutoMode, const int &minWaitTime = -1);
};

class Query
{
private:
    QSqlQuery *m_query;
    QMutex *m_mutex = NULL;

public:
    Query(QSqlDatabase &dataBase, QMutex *mutex = NULL);

    Query(Query &&other);

    ~Query(void);

    inline QSqlQuery *operator->(void) { return m_query; }

    inline QSqlQuery *operator*(void) { return m_query; }

    QSqlQuery *takeQuery(void);

    QMutex *takeMutex(void);
};

class ConnectNode: public QObject
{
     Q_OBJECT

private:
    QSqlDatabase *m_database = NULL;
    QString m_connectionName;

    DatabaseSettings m_dataBaseSettings;
    ConnectSettings m_connectSettings;

    QTimer *m_autoClose = NULL;
    QMutex *m_mutex = NULL;

public:
    ConnectNode(const DatabaseSettings &dataBaseSettings, const ConnectSettings &connectSettings);

    ~ConnectNode(void);

public:
    Query query(void);

public slots:
    bool addDataBase(void);

    void removeDataBase(void);

    bool open(void);

    void close(void);

signals:
    void controlStartAutoClose(void);

    void controlStopAutoClose(void);
};

class Control
{
private:
    DatabaseSettings m_databaseSettings;
    ConnectSettings m_connectSettings;

    QMap<qint64, ConnectNode *> m_node;

    QMutex m_mutex;
    QTime *m_wait = NULL;

public:
    Control(const DatabaseSettings &databaseSettings, const ConnectSettings &connectSettings = ConnectSettings());

    Control(const Control &) = delete;

    ~Control(void);

public:
    void removeAll(void);

    Query query(void);

private:
    void insert(const qint64 &key);
};

}

#endif//__JasonQt_Database_h__


JasonQt_Database.cpp

#include "JasonQt_Database.h"

using namespace JasonQt_Database;

// DatabaseSettings
DatabaseSettings::DatabaseSettings(const DatabaseModeEnum &databastMode, const QString &databaseType, const QString &connectionName)
{
    m_databaseMode = databastMode;
    m_databaseType = databaseType;
    m_connectionName = connectionName;
}

DatabaseSettings::DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &nameModeName):
    DatabaseSettings(DatabaseNameMode, databaseType, connectionName)
{
    m_nameModeName = nameModeName;
}

DatabaseSettings::DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &hostModeHostName, const QString &hostModeDatabaseName, const QString &hostModeUserName, const QString &hostModePassword):
    DatabaseSettings(DatabaseHostMode, databaseType, connectionName)
{
    m_hostModeHostName = hostModeHostName;
    m_hostModeDatabaseName = hostModeDatabaseName;
    m_hostModeUserName = hostModeUserName;
    m_hostModePassword = hostModePassword;
}

// ConnectSettings
ConnectSettings::ConnectSettings(const int &maxOpenTime, const QueryMode &queryMode, const int &minWaitTime)
{
    m_maxOpenTime = maxOpenTime;
    m_queryMode = queryMode;
    m_minWaitTime = minWaitTime;
}

// Query
Query::Query(QSqlDatabase &dataBase, QMutex *mutex):
    m_query(new QSqlQuery(dataBase))
{
    m_mutex = mutex;
}

Query::Query(Query &&other)
{
    m_query = other.takeQuery();
    m_mutex = other.takeMutex();
}

Query::~Query(void)
{
    if(m_query)
    {
        delete m_query;
    }
    if(m_mutex)
    {
        m_mutex->unlock();
    }
}

QSqlQuery *Query::takeQuery(void)
{
    auto buf = m_query;
    m_query = NULL;
    return buf;
}

QMutex *Query::takeMutex(void)
{
    auto buf = m_mutex;
    m_mutex = NULL;
    return buf;
}

// ConnectNode
ConnectNode::ConnectNode(const DatabaseSettings &dataBaseSettings, const ConnectSettings &connectSettings):
    m_dataBaseSettings(dataBaseSettings),
    m_connectSettings(connectSettings)
{
    m_connectionName = QString("%1(%2)").arg(m_dataBaseSettings.connectionName()).arg(QString::number(qint64(QThread::currentThread()), 16));

    m_mutex = new QMutex(QMutex::Recursive);

    if(m_connectSettings.maxOpenTime())
    {
        m_autoClose = new QTimer;
        m_autoClose->setSingleShot(true);
        m_autoClose->setInterval(m_connectSettings.maxOpenTime());
        m_autoClose->moveToThread(qApp->thread());
        m_autoClose->setParent(qApp);

        connect(m_autoClose, SIGNAL(timeout()), this, SLOT(close()), Qt::DirectConnection);
        connect(this, SIGNAL(controlStartAutoClose()), m_autoClose, SLOT(start()));
        connect(this, SIGNAL(controlStopAutoClose()), m_autoClose, SLOT(stop()));
    }

    this->addDataBase();
}

ConnectNode::~ConnectNode(void)
{
    if(m_mutex){ m_mutex->lock(); }

    if(m_autoClose)
    {
        m_autoClose->deleteLater();
    }

    this->removeDataBase();

    if(m_mutex){ m_mutex->unlock(); }
    if(m_mutex){ delete m_mutex; }
}

Query ConnectNode::query(void)
{
    if(!m_database)
    {
        this->addDataBase();
    }

    if(!m_database->isOpen())
    {
        m_database->open();
    }

    if(m_mutex){ m_mutex->lock(); }

    Query buf(*m_database, m_mutex);
    emit controlStartAutoClose();
    return buf;
}

bool ConnectNode::addDataBase(void)
{
    if(m_mutex){ m_mutex->lock(); }

    if(m_database)
    {
        this->removeDataBase();
    }

    m_database = new QSqlDatabase(QSqlDatabase::addDatabase(m_dataBaseSettings.databaseType(), m_connectionName));

    switch(m_dataBaseSettings.databaseMode())
    {
        case DatabaseNameMode:
        {
            m_database->setDatabaseName(m_dataBaseSettings.nameModeName());
            break;
        }
        case DatabaseHostMode:
        {
            m_database->setHostName(m_dataBaseSettings.hostModeHostName());
            m_database->setDatabaseName(m_dataBaseSettings.hostModeDatabaseName());
            m_database->setUserName(m_dataBaseSettings.hostModeUserName());
            m_database->setPassword(m_dataBaseSettings.hostModePassword());
            break;
        }
        default:
        {
            if(m_mutex){ m_mutex->unlock(); }
            return false;
        }
    }

    const auto &&flag = this->open();

    if(m_mutex){ m_mutex->unlock(); }

    return flag;
}

void ConnectNode::removeDataBase(void)
{
    if(m_mutex){ m_mutex->lock(); }

    delete m_database;
    m_database = NULL;
    QSqlDatabase::removeDatabase(m_connectionName);

    if(m_mutex){ m_mutex->unlock(); }
}

bool ConnectNode::open(void)
{
    if(!m_database)
    {
        this->addDataBase();
    }

    if(m_mutex){ m_mutex->lock(); }

    emit controlStartAutoClose();
    const auto &&Flag = m_database->open();

    if(m_mutex){ m_mutex->unlock(); }

    return Flag;
}

void ConnectNode::close(void)
{
    if(m_mutex)
    {
        if(m_mutex->tryLock())
        {
            m_mutex->unlock();
            emit controlStopAutoClose();
            m_database->close();
        }
        else
        {
            emit controlStartAutoClose();
        }
    }
    else
    {
        emit controlStopAutoClose();
        m_database->close();
    }
}

// Control
Control::Control(const DatabaseSettings &databaseSettings, const ConnectSettings &connectSettings):
    m_databaseSettings(databaseSettings),
    m_connectSettings(connectSettings)
{
    if(m_connectSettings.queryMode() == QueryAutoMode)
    {
        if(databaseSettings.databaseType() == "QMYSQL")
        {
            m_connectSettings.setQueryMode(QueryMultiMode);
        }
        else if(databaseSettings.databaseType() == "QODBC")
        {
            m_connectSettings.setQueryMode(QueryMultiMode);
        }
        else
        {
            m_connectSettings.setQueryMode(QuerySingleMode);
        }
    }
    if(m_connectSettings.queryMode() == QuerySingleMode)
    {
        this->insert(qint64(QThread::currentThread()));
    }

    if(m_connectSettings.minWaitTime() == -1)
    {
        if(databaseSettings.databaseType() == "QMYSQL")
        {
            m_connectSettings.setMinWaitTime(0);
        }
        else if(databaseSettings.databaseType() == "QODBC")
        {
            m_connectSettings.setMinWaitTime(0);
        }
        else
        {
            m_connectSettings.setMinWaitTime(5);
            m_wait = new QTime;
            m_wait->start();
        }
    }
    else
    {
        m_wait = new QTime;
        m_wait->start();
    }
}

Control::~Control(void)
{
    for(auto &now: m_node)
    {
        now->deleteLater();
    }
    if(m_wait)
    {
        delete m_wait;
    }
}

void Control::removeAll(void)
{
    m_mutex.lock();

    for(auto &Now: m_node)
    {
        Now->removeDataBase();
    }

    m_mutex.unlock();
}

Query Control::query(void)
{
    if(m_wait)
    {
        const auto &&flag = m_connectSettings.minWaitTime() - m_wait->elapsed();
        m_wait->restart();
        if(flag > 0)
        {
            QThread::msleep(flag);
        }
    }

    if(m_connectSettings.queryMode() == QueryMultiMode)
    {
        m_mutex.lock();

        const auto &¤tThread = qint64(QThread::currentThread());
        const auto &&now = m_node.find(currentThread);
        if(now != m_node.end())
        {
            auto buf((*now)->query());

            m_mutex.unlock();
            return buf;
        }
        else
        {
            this->insert(qint64(QThread::currentThread()));

            m_mutex.unlock();
            return this->query();
        }
    }
    else
    {
        return (*m_node.begin())->query();
    }
}

void Control::insert(const qint64 &key)
{
    m_node[key] = new ConnectNode(m_databaseSettings, m_connectSettings);
}

使用:

// Qt lib import
#include <QCoreApplication>
#include <QtConcurrent>
#include <QSqlError>

// JasonQt lib import
#include "JasonQt/JasonQt_Database.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    /*
     * 注:關於附加引數
     * 這是可以不寫的,如果要寫的話,可以參考這個:
     *
     * 單次開啟資料庫最大時間:也就是最大open的時間,對於某些資料庫,長時間open但不使用,不僅會造成資源浪費還會意外斷開。在設定了60 * 1000後,且60秒內未進行查詢,就自動斷開。
     * 多執行緒支援:簡單的說就是高階點的資料庫,比如 MySql 寫 JasonQt_Database::QueryMultiMode ;低階的,比如 Sqlite ,寫 JasonQt_Database::QuerySingleMode ,就可以了。
     * 最小等待時間:對於某些資料庫,比如Sqlite,密集查詢時可能出錯,此時可以適當的提升兩次查詢之間的最小等待時間,比如10ms
     */

    // Sqlite的連線方式                    型別        連線名        Sqlite檔案路徑      單次開啟資料庫最大時間                多執行緒支援           最小等待時間
    JasonQt_Database::Control control({ "QSQLITE", "TestDB", "/Users/Jason/test.db" }, { 60 * 1000,        JasonQt_Database::QuerySingleMode, 10});

    // MySql的連線方式                      型別      連線名        IP        資料庫    使用者名稱        密碼
//  JasonQt_Database::Control control({ "QMYSQL", "TestDB", "localhost", "JasonDB", "root", "YourPassword" });


    // SqlServer的連線方式                  型別      連線名                                    資料庫              資料庫名   使用者名稱         密碼
//  JasonQt_Database::Control control({ "QODBC", "TestDB", "Driver={SQL SERVER};server=iZ23kn6vmZ\\TEST;database=test;uid=sa;pwd=YourPassword;" });

    auto insert = [&]()
    {
        auto query(control.query()); // 這裡的query在解引用( -> 或者 * )後返回的是 QSqlQuery ,直接用就可以了,不需要單獨開啟資料庫或者其他的初始化

        query->prepare("insert into Test1 values(?)"); // 模擬插入操作

        query->addBindValue(rand() % 1280);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }

        query->clear();

        query->prepare("insert into Test2 values(NULL, ?, ?)");

        query->addBindValue(rand() % 1280);
        QString buf;
        for(int now = 0; now < 50; now++)
        {
            buf.append('a' + (rand() % 26));
        }
        query->addBindValue(buf);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }
    };
    auto delete_ = [&]()
    {
        auto query(control.query());

        query->prepare("delete from Test1 where data = ?");

        query->addBindValue(rand() % 1280);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }

        query->clear();

        query->prepare("delete from Test2 where data1 = ?");

        query->addBindValue(rand() % 1280);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }
    };
    auto update = [&]()
    {
        auto query(control.query());

        query->prepare("update Test1 set data = ? where data = ?");

        query->addBindValue(rand() % 1280);
        query->addBindValue(rand() % 1280);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }

        query->clear();

        query->prepare("update Test2 set data1 = ?, data2 = ? where data1 = ?");

        query->addBindValue(rand() % 1280 + 1);
        QString buf;
        for(int now = 0; now < 50; now++)
        {
            buf.append('a' + (rand() % 26));
        }
        query->addBindValue(buf);
        query->addBindValue(rand() % 1280 + 1);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }
    };
    auto select = [&]()
    {
        auto query(control.query());

        query->prepare("select * from Test1 where data = ?");

        query->addBindValue(rand() % 1280);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }

        query->clear();

        query->prepare("select * from Test2 where data1 = ?");

        query->addBindValue(rand() % 1280);

        if(!query->exec())
        {
            qDebug() << "Error" << __LINE__;
        }
    };

    volatile int count = 0, last = 0;

    QTime time;
    time.start();

    QThreadPool::globalInstance()->setMaxThreadCount(10);

    for(int now = 0; now < 3; now++) // 開啟3個執行緒測試
    {
        QtConcurrent::run([&]()
        {
            while(true)
            {
                count++;
                if(!(count % 1000))
                {
                    const auto &&now = time.elapsed();
                    qDebug() << now - last; // 列印每完成1000語句的時間
                    last = now;
                }

                switch(rand() % 20)
                {
                    case 0:  { insert(); break; }
                    case 1:  { delete_(); break; }
                    case 2:  { update(); break; }
                    default: { select(); break; }
                }
            }
            QThread::msleep(10);
        });
    }

    return a.exec();
}

我也寫了一個示例工程,可以前往這裡下載

相關推薦

QtQSqlDatabase進一步封裝執行支援+更加簡單操作

開發背景: 1.直接用QSqlDatabase我覺得太麻煩了; 2.對於某些資料庫,多個執行緒同時使用一個QSqlDatabase的時候會崩潰; 3.這段時間沒什麼乾貨放出來覺得渾身不舒服,就想寫一個。 於是,我就封裝了一下 只要簡單的例項化,然後通過query()就可以

java個人學習筆記16執行+extends Thread+implements Runnable

1.多執行緒 程序:正在執行的應用程式在記憶體中分配的空間 執行緒:是程序中負責程式執行的執行單元,也稱執行路徑 一個程序中至少有一個執行緒在負責該程序的執行 多執行緒技術:解決多部分程式碼同時執行的需求,合理使用cpu資源,提高使用者體驗。(微觀上序列,並未實際上提高效率

python程式設計執行c回撥python

【 宣告:版權所有,歡迎轉載,請勿用於商業用途。 聯絡信箱:feixiaoxing @163.com】     python下面的GIL決定了每次thread執行的時候不能實現完全的併發執行。所以如果多執行緒c呼叫python程式碼的時候,有很多地方需要注意一

刨根問底系列3——關於socket api的原子操作性和執行安全性的探究和實驗測試執行同時send,write

多個執行緒對同一socket同時進行send操作的結果 1. 概覽 1.1 起因 自己寫的專案裡,為了保證連線不中斷,我起一個執行緒專門傳送心跳包保持連線,那這個執行緒在send傳送資料時,可能會與主執行緒中的send衝突,因此我就想探討一下socket api是否具有執行緒安全性。網上很多說法,但多是推測,

QtQt實現Winsock網路程式設計—TCP服務端和客戶端通訊執行

Qt實現Winsock網路程式設計—TCP服務端和客戶端通訊(多執行緒) 前言 感覺Winsock網路程式設計的api其實和Linux下網路程式設計的api非常像,其實和其他程式語言的網路程式設計都差不太多。博主用Qt實現的,當然不想用黑視窗唄,有介面可以看到,由於GUI程式設計

機器學習筆記十九TensorFlow實戰十一執行輸入資料

1 - 引言 為了加速模型訓練的時間,TensorFlow提供了一套多執行緒處理輸入資料的框架。 下面我們來詳細的介紹如何使用多執行緒來加速我們的模型訓練速度 2 - 佇列與多執行緒 在TensorFlow中,佇列和變數類似,我們可以修改它們的狀態。下面給出一個示例來展示如

執行學習4三種實現Java執行的方法Thread、Callable和Runable 的比較與區別

2018年10月03日 目錄 前言 前言 JVM允許應用程式併發執行多執行緒:最常用的是兩個方法:(1)基礎Thread類,重寫run()方法;(2)或實現Runnable 介面,實現介面的run()方法;(3)另外一種方法是:實現callable 介面

QT實現哈夫曼壓縮執行

本人菜雞程式設計師一枚,最近剛剛學習的資料結構中的哈夫曼樹,就用QT寫了一個哈夫曼壓縮,話不多說先上步驟,再上程式碼。(如果有更好的想法,歡迎指點) 1.先寫出建最小堆和建哈夫曼樹程式碼(建最小堆的程式碼可以通過STL中的堆代替) 2.寫出壓縮類的程式碼,類中

【Java併發程式設計】之六Runnable和Thread實現執行的區別含程式碼

    Java中實現多執行緒有兩種方法:繼承Thread類、實現Runnable介面,在程式開發中只要是多執行緒,肯定永遠以實現Runnable介面為主,因為實現Runnable介面相比繼承Th

java執行5、Java對執行支援執行優先順序

一、執行緒優先順序 在java當中,每一個執行緒都有一個優先順序,我們可以通過Thread當中的getPriority()方法、setPriority方法去得到一個執行緒的優先順序和設定一個執行緒的優先順序。  設定執行緒優先順序,它的引數是一個整形。最小為1(Thread.M

java執行4、Java對執行支援後臺執行setDaemon、暫停執行yield

文章目錄 一、設定後臺執行緒 二、yield方法,暫停執行緒讓別的執行緒執行 上篇文章介紹了執行緒實現方式、執行緒的隨機性,大家如需瞭解可參考 java多執行緒:3、Java對多執行緒的支援(一)執行緒實現方式、執行緒的隨機性 這篇我們來看看後臺執行緒setD

TensorFlow學習系列如何使用佇列和執行優化輸入管道

這篇教程是翻譯Morgan寫的TensorFlow教程,作者已經授權翻譯,這是原文。 目錄 TensorFlow 1.0版本已經出來了,隨著這次更新,一些不錯的指導建議出現在官網上面。其中一個我比較關心的是 f

【原創】中文分詞系統 ICTCLAS2015 的JAVA封裝執行執行附程式碼

  本文針對的問題是 ICTCLAS2015 的多執行緒分詞,為了實現多執行緒做了簡單的JAVA封裝。如果有需要可以自行進一步封裝其它介面。   首先ICTCLAS2015的傳送門(http://ictclas.nlpir.org/),其對中文分詞做的比較透徹,而且有一定的可調式性。但是應用到實際開發中的話

JAVA執行入門JAVA中如何寫執行

第一種方式:繼承Thread 步驟: 1.建立執行緒類,繼承自Thread + 重寫run,run中寫執行緒體,執行緒體就是mian()函式裡面的寫法 2.使用執行緒: 2.1 建立執行緒物件 2.2 執行緒物件.start() 步驟展示: 1. public

qt匯出、操作excel執行

使用QAxObject在多執行緒下進行excel操作,將原來固定格式的文字檔案,通過解析之後寫入到excel中。 效果圖 多執行緒使用 Worker *pWorker = new Worker(); connect(pWorker, SIGNAL(err

Qt實用技巧QPainterPath繪圖路徑次畫同樣的圖形集合

需求         根據配置檔案,可不改變程式只調整配置檔案可調整主頁面上的字串。 原理         1.讀取檔案,固定格式(檔案在本文章中省略)         2.寫一串字元,使用QPainterPath         3.注意QPainter的時候,需

C#執行基礎執行的優先順序、狀態、同步

一、關於多執行緒的優先順序、狀態、同步指令碼如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System

iOS - 知識梳理執行

多執行緒:一個程序裡面開啟多條執行緒,每條執行緒可以單獨的執行不同的任務。 iOS實現多執行緒的方式: 1、pthread(C寫的、基本不用) 2、NSThread 3、gcd 4、NSOperation 下面分別介紹下後三個常用的多執行緒方式 NSThread: 使用方式

c++封裝win32執行簡介

  /* 執行緒函式如果宣告在類中, 則需要靜態函式. 否則普通函式中將壓入this引數 . 所以在類中先宣告一個靜態函式,用於執行緒的啟動. 再寫一個虛擬函式 run , 線上程中呼叫, 也可以用於子類的重寫 */ class ThreadObj

購物表動態連結串列+鬧鐘提醒執行

基本連結串列的應用 增 刪 查 找 排 模糊查詢 核心程式碼如下: #include <stdio.h> #include <math.h> #include <string.h> #include <malloc.h> #incl