主頁 > 後端開發 > 【QCustomPlot】性能提升之修改原始碼(版本 V2.x.x)

【QCustomPlot】性能提升之修改原始碼(版本 V2.x.x)

2023-05-30 07:44:27 後端開發

說明

QCustomPlot 是開源專案,原始碼撰寫十分規范,想要理解它的可視化思路不算特別困難,我在這篇隨筆中總結一下常用的原始碼修改技巧,下面的每一個技巧都是獨立的,不同技巧中添加的代碼無任何依賴關系,相互之間也不會引發任何沖突,不會影響 QCustomPlot 原生的介面,示例中使用的 QCustomPlot 版本號為 2.0.1,但在更高的 2.x.x 版本中也適用,

目錄
  • 說明
  • 1. 技巧一:啟用 GPU 加速
    • 1.1 下載并編譯 FreeGlut 庫
    • 1.2 在 qcustomplot.cpp 檔案中添加代碼
    • 1.3 在 pro 檔案中添加代碼
    • 1.4 啟用 GPU 加速
    • 1.5 加速效果
  • 2. 技巧二:添加曲線平滑功能
    • 2.1 在 qcustomplot.h 檔案中添加代碼
    • 2.2 在 qcustomplot.cpp 檔案中添加代碼
    • 2.3 啟用曲線平滑
    • 2.4 平滑效果
  • 3. 技巧三:匯出一維繪圖資料地址
    • 3.1 一維繪圖資料的記憶體結構
    • 3.2 在 qcustomplot.h 檔案中添加代碼
    • 3.3 使用繪圖資料地址來更新資料
  • 4. 技巧四:匯出 QCPColorMap 繪圖資料地址
    • 4.1 QCPColorMap 繪圖資料的記憶體結構
    • 4.2 在 qcustomplot.h 檔案中添加代碼
    • 4.3 使用繪圖資料地址來更新資料


1. 技巧一:啟用 GPU 加速

這里選用 FreeGlut 庫,

1.1 下載并編譯 FreeGlut 庫

去 https://freeglut.sourceforge.net/index.php 下載 freeglut 原始碼,編譯出 freeglut 庫,編譯程序不做介紹,然后將編譯出來的庫以及 GL 檔案夾下的五個頭檔案都包含進專案中,我使用的是 MSVC2015 64bit 靜態庫,因此在 pro/pri 檔案中添加以下代碼(因人而異):

HEADERS += \
    $$PWD/GL/freeglut.h \
    $$PWD/GL/freeglut_ext.h \
    $$PWD/GL/freeglut_std.h \
    $$PWD/GL/freeglut_ucall.h \
    $$PWD/GL/glut.h

CONFIG(debug, debug | release) {
    LIBS += -L$$PWD/lib64 -lfreeglut_staticd
    LIBS += -L$$PWD/lib64 -lfreeglutd
}

CONFIG(release, debug | release) {
    LIBS += -L$$PWD/lib64 -lfreeglut_static
    LIBS += -L$$PWD/lib64 -lfreeglut
}

1.2 在 qcustomplot.cpp 檔案中添加代碼

在檔案的前面幾行(比如 #include "qcustomplot.h" 的后面)添加以下代碼:

#define GLUT_DISABLE_ATEXIT_HACK
#include <GL/freeglut.h>

若同一個界面上有多個 QCustimPlot 視窗物件,且都開啟了 GPU 加速,則在視窗切換時圖形顯示可能會出現錯亂(被稱為背景關系例外),為了避免這種現象,需要在 QCPPaintBufferGlFbo::draw 函式里面添加以下代碼:

/* inherits documentation from base class */
void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const
{
    if (!painter || !painter->isActive())
    {
        qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
        return;
    }
    if (!mGlFrameBuffer)
    {
        qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
        return;
    }
    
    // 這個 if 陳述句是新添加的
    if(QOpenGLContext::currentContext() != mGlContext.data())
    {
        mGlContext.data()->makeCurrent(mGlContext.data()->surface());
    }
    
    painter->drawImage(0, 0, mGlFrameBuffer->toImage());
}

1.3 在 pro 檔案中添加代碼

pro 檔案中,添加以下代碼:

QT       += printsupport opengl
DEFINES += QCUSTOMPLOT_USE_OPENGL

這個 printsupport 是使用 QCustomPlot 時需要添加的,不論是否啟用 GPU 加速都需要添加,后面的 opengl 則是為了啟用 GPU 加速而新添的,此外,還需要使用 DEFINES 添加 QCUSTOMPLOT_USE_OPENGL 宏,

1.4 啟用 GPU 加速

對 QCustomPlot 物件使用 setOpenGl() 函式設定是否啟用 OpenGL,如下所示:

ui->Plot->setOpenGl(true);

可以通過 openGl() 函式的回傳值判斷是否成功啟用了 GPU 加速:

qDebug() << "啟用狀態" << ui->Plot->openGl();

需要注意的是,當繪制的圖形有大塊填充區域,尤其是半透明的填充時,GPU 加速的效果才明顯,這個時候才能減輕 CPU 壓力,如果僅僅繪制一些簡單的曲線圖還開啟 OpenGL,結果往往會適得其反,CPU 壓力不減反增,有興趣的可以進行測驗,打開任務管理器觀察啟用前后 CPU 的占用百分比即可,

1.5 加速效果

繪制實時更新的、含有填充區域的影像,未開啟 GPU 加速前的效果:

Oh Shit!-圖片走丟了-打個廣告-歡迎來博客園關注“木三百川”

開啟 GPU 加速后的效果:

Oh Shit!-圖片走丟了-打個廣告-歡迎來博客園關注“木三百川”

以上演示例中并沒有更改資料重繪頻率(都為 10 ms 間隔)及資料量大小(都為 100 個點),兩者僅有的差別為是否呼叫了 setOpenGl(true) 開啟了 GPU 加速,從結果中可以看到,開啟 OpenGL 后,CPU 占用率從 16%~17% 下降到 7%~8%,GPU 占用率從 0% 上升到 41%~43%,并且從視覺效果上看,重繪變得更快了,這可能是因為 CPU 被減輕了壓力,單次計算后顯示所需時間更短了,


2. 技巧二:添加曲線平滑功能

思路是先計算貝塞爾控制點,然后使用 QPainterPath 繪制平滑曲線,參考資料:

  • CodeProject-Draw-a-Smooth-Curve-through-a-Set-of-2D-Points-wit,
  • 公孫二狗 - 個人博客,
  • CSDN - 七六伍,

2.1 在 qcustomplot.h 檔案中添加代碼

在原生的 class QCP_LIB_DECL QCPGraph 類定義中(使用搜索功能找到對應位置)添加以下兩個內容,注意 publicprotected 限定符:

class QCP_LIB_DECL QCPGraph : public QCPAbstractPlottable1D<QCPGraphData>
{
public: 
    ...
    void setSmooth(bool smooth);             // 新增內容
    
protected:
    ...
    bool mSmooth;                            // 新增內容
}

qcustomplot.h 檔案的末尾(#endif 的上一行)添加 SmoothCurveGenerator 類定義的代碼:

class SmoothCurveGenerator
{
protected:
    static QPainterPath generateSmoothCurveImp(const QVector<QPointF> &points) {
        QPainterPath path;
        int len = points.size();
        
        if (len < 2) {
            return path;
        }
        
        QVector<QPointF> firstControlPoints;
        QVector<QPointF> secondControlPoints;
        calculateControlPoints(points, &firstControlPoints, &secondControlPoints);
        
        path.moveTo(points[0].x(), points[0].y());
        
        // Using bezier curve to generate a smooth curve.
        for (int i = 0; i < len - 1; ++i) {
            path.cubicTo(firstControlPoints[i], secondControlPoints[i], points[i+1]);
        }
        
        return path;
    }
public:
    static QPainterPath generateSmoothCurve(const QVector<QPointF> &points) {
        QPainterPath result;
        
        int segmentStart = 0;
        int i = 0;
        int pointSize = points.size();
        while (i < pointSize) {
            if (qIsNaN(points.at(i).y()) || qIsNaN(points.at(i).x()) || qIsInf(points.at(i).y())) {
                QVector<QPointF> lineData(i - segmentStart); std::copy(points.constBegin() + segmentStart, points.constBegin() + i - segmentStart, lineData.begin());
                result.addPath(generateSmoothCurveImp(lineData));
                segmentStart = i + 1;
            }
            ++i;
        }
        QVector<QPointF> lineData(i - segmentStart); std::copy(points.constBegin() + segmentStart, points.constBegin() + i - segmentStart, lineData.begin());
        result.addPath(generateSmoothCurveImp(lineData));
        return result;
    }
    
    static QPainterPath generateSmoothCurve(const QPainterPath &basePath, const QVector<QPointF> &points) {
        if (points.isEmpty()) return basePath;
        
        QPainterPath path = basePath;
        int len = points.size();
        if (len == 1) {
            path.lineTo(points.at(0));
            return path;
        }
        
        QVector<QPointF> firstControlPoints;
        QVector<QPointF> secondControlPoints;
        calculateControlPoints(points, &firstControlPoints, &secondControlPoints);
        
        path.lineTo(points.at(0));
        for (int i = 0; i < len - 1; ++i)
            path.cubicTo(firstControlPoints[i], secondControlPoints[i], points[i+1]);
        
        return path;
    }
    
    static void calculateFirstControlPoints(double *&result, const double *rhs, int n) {
        result = new double[n];
        double *tmp = new double[n];
        double b = 2.0;
        result[0] = rhs[0] / b;
        
        // Decomposition and forward substitution.
        for (int i = 1; i < n; i++) {
            tmp[i] = 1 / b;
            b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
            result[i] = (rhs[i] - result[i - 1]) / b;
        }
        
        for (int i = 1; i < n; i++) {
            result[n - i - 1] -= tmp[n - i] * result[n - i]; // Backsubstitution.
        }
        
        delete[] tmp;
    }
    
    static void calculateControlPoints(const QVector<QPointF> &knots,
                                       QVector<QPointF> *firstControlPoints,
                                       QVector<QPointF> *secondControlPoints) {
        int n = knots.size() - 1;
        
        firstControlPoints->reserve(n);
        secondControlPoints->reserve(n);
        
        for (int i = 0; i < n; ++i) {
            firstControlPoints->append(QPointF());
            secondControlPoints->append(QPointF());
        }
        
        if (n == 1) {
            // Special case: Bezier curve should be a straight line.
            // P1 = (2P0 + P3) / 3
            (*firstControlPoints)[0].rx() = (2 * knots[0].x() + knots[1].x()) / 3;
            (*firstControlPoints)[0].ry() = (2 * knots[0].y() + knots[1].y()) / 3;
            
            // P2 = 2P1 – P0
            (*secondControlPoints)[0].rx() = 2 * (*firstControlPoints)[0].x() - knots[0].x();
            (*secondControlPoints)[0].ry() = 2 * (*firstControlPoints)[0].y() - knots[0].y();
            
            return;
        }
        
        // Calculate first Bezier control points
        double *xs = nullptr;
        double *ys = nullptr;
        double *rhsx = new double[n]; // Right hand side vector
        double *rhsy = new double[n]; // Right hand side vector
        
        // Set right hand side values
        for (int i = 1; i < n - 1; ++i) {
            rhsx[i] = 4 * knots[i].x() + 2 * knots[i + 1].x();
            rhsy[i] = 4 * knots[i].y() + 2 * knots[i + 1].y();
        }
        rhsx[0] = knots[0].x() + 2 * knots[1].x();
        rhsx[n - 1] = (8 * knots[n - 1].x() + knots[n].x()) / 2.0;
        rhsy[0] = knots[0].y() + 2 * knots[1].y();
        rhsy[n - 1] = (8 * knots[n - 1].y() + knots[n].y()) / 2.0;
        
        // Calculate first control points coordinates
        calculateFirstControlPoints(xs, rhsx, n);
        calculateFirstControlPoints(ys, rhsy, n);
        
        // Fill output control points.
        for (int i = 0; i < n; ++i) {
            (*firstControlPoints)[i].rx() = xs[i];
            (*firstControlPoints)[i].ry() = ys[i];
            
            if (i < n - 1) {
                (*secondControlPoints)[i].rx() = 2 * knots[i + 1].x() - xs[i + 1];
                (*secondControlPoints)[i].ry() = 2 * knots[i + 1].y() - ys[i + 1];
            } else {
                (*secondControlPoints)[i].rx() = (knots[n].x() + xs[n - 1]) / 2;
                (*secondControlPoints)[i].ry() = (knots[n].y() + ys[n - 1]) / 2;
            }
        }
        
        delete xs;
        delete ys;
        delete[] rhsx;
        delete[] rhsy;
    }
};

2.2 在 qcustomplot.cpp 檔案中添加代碼

在原生的 QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) 建構式(使用搜索功能找到對應位置)實作中,添加 mSmooth 成員變數的初始化代碼:

QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) :
  QCPAbstractPlottable1D<QCPGraphData>(keyAxis, valueAxis)
{
    ...
    mSmooth = false;  // 新增內容
}

在對應位置添加 QCPGraph::setSmooth() 成員函式的實作(比如寫在 void QCPGraph::setAdaptiveSampling(bool enabled) 的后面):

void QCPGraph::setSmooth(bool smooth)
{
    mSmooth = smooth;
}

將原生的 QCPGraph::drawLinePlot 成員函式(使用搜索功能找到對應位置)修改為如下形式,實質上只添加了個 if 陳述句:

void QCPGraph::drawLinePlot(QCPPainter *painter, const QVector<QPointF> &lines) const
{
    if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
    {
        applyDefaultAntialiasingHint(painter);
        if (mSmooth && mLineStyle == lsLine) painter->drawPath(SmoothCurveGenerator::generateSmoothCurve(lines));
        else drawPolyline(painter, lines);
    }
}

2.3 啟用曲線平滑

對 QCPGraph 物件使用 setSmooth() 函式設定是否啟用曲線平滑,如下所示:

ui->Plot->graph(0)->setSmooth(true);

2.4 平滑效果

繪制 50 個點,未啟用曲線平滑時的效果:

Oh Shit!-圖片走丟了-打個廣告-歡迎來博客園關注“木三百川”

啟用曲線平滑時的效果:

Oh Shit!-圖片走丟了-打個廣告-歡迎來博客園關注“木三百川”


3. 技巧三:匯出一維繪圖資料地址

3.1 一維繪圖資料的記憶體結構

一維繪圖資料都存盤在 QCPDataContainer 這個類里面,繪圖資料存盤的容器為 QVector<DataType>,詳見 qcustomplot.h 檔案中 QCPDataContainer 的類定義,不同的一維繪圖型別有著不同的底層資料型別:

  • 對于 QCPGraph 繪圖型別,這個 DataTypeQCPGraphData,查看 QCPGraphData 類定義,它有且僅有兩個 double 型別的成員變數 keyvalue,因此 QCPGraph 的繪圖資料被存盤在一塊連續的記憶體塊中(類似于 double 陣列),繪圖資料在記憶體中按順序 x0-y0-x1-y1-x2-y2... 這樣依次排列,xiyi 分別表示第 i 個橫軸資料和第 i 個縱軸資料,
  • 對于 QCPCurve 繪圖型別,這個 DataTypeQCPCurveData,查看 QCPCurveData 類定義,它有且僅有三個 double 型別的成員變數 tkeyvalue,因此 QCPCurve 的繪圖資料在記憶體中按順序 t0-x0-y0-t1-x1-y1-t2-x2-y2... 這樣依次排列,這個 t 表示引數曲線對應的參變數,
  • 對于 QCPBars 繪圖型別,這個 DataTypeQCPBarsData,查看 QCPBarsData 類定義,它有且僅有兩個 double 型別的成員變數 keyvalue,因此 QCPBars 繪圖資料與 QCPGraph 繪圖資料的記憶體排列方式一樣,
  • QCPStatisticalBoxQCPFinancial 這兩個繪圖型別就相對復雜些,但不變的是,繪圖資料仍被依次存盤在一塊連續的記憶體塊中,感興趣的可以看下 QCPStatisticalBoxDataQCPFinancialData 的類定義,

更新一維繪圖資料時,QCustomPlot 提供了一些介面,分別為:

// QCPGraph 4個介面
void setData(QSharedPointer<QCPGraphDataContainer> data)
void setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted=false)
void addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted=false)
void addData(double key, double value)
    
// QCPCurve 7個介面
void setData(QSharedPointer<QCPCurveDataContainer> data)
void setData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted=false)
void setData(const QVector<double> &keys, const QVector<double> &values)
void addData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted=false)
void addData(const QVector<double> &keys, const QVector<double> &values)
void addData(double t, double key, double value)
void addData(double key, double value)
    
// QCPBars 4個介面
void setData(QSharedPointer<QCPBarsDataContainer > data)
void setData(const QVector< double > &keys, const QVector<double> &values, bool alreadySorted=false)
void addData(const QVector< double > &keys, const QVector<double> &values, bool alreadySorted=false)
void addData(double key, double value)
    
// QCPStatisticalBox 4個介面
void setData(QSharedPointer<QCPStatisticalBoxDataContainer> data)
void setData(const QVector<double> &keys, const QVector<double> &minimum, const QVector<double> &lowerQuartile, const QVector<double> &median, const QVector<double> &upperQuartile, const QVector<double> &maximum, bool alreadySorted=false)
void addData(const QVector<double> &keys, const QVector<double> &minimum, const QVector<double> &lowerQuartile, const QVector<double> &median, const QVector<double> &upperQuartile, const QVector<double> &maximum, bool alreadySorted=false)
void addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers=QVector<double>())
    
// QCPFinancial 4個介面
void setData(QSharedPointer<QCPFinancialDataContainer> data)
void setData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted=false)
void addData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted=false)
void addData(double key, double open, double high, double low, double close)

其中第一個介面暴露出來的指標并沒有直接指向繪圖資料所在記憶體的首地址,也無法通過這個指標來獲得 QVector<DataType> 這個容器的地址,除第一個介面外,原生的 setData()addData() 介面內部都會呼叫 QVector 相關的 resize()size()std::sort()std::inplace_merge() 等函式,還存在很多 if 陳述句,在一些時候,特別是資料點數固定但數值更新速率很高時,頻繁的呼叫 size() 等函式會大大延長重繪時間,此時原生介面中的很多操作都是不必要的,因此不妨直接將存盤繪圖資料的 QVector<DataType> 容器地址交給使用者,以獲得更佳的性能,縮短更新時間,

3.2 在 qcustomplot.h 檔案中添加代碼

QCPDataContainer 類定義的 public 區域,添加以下一行代碼即可:

template <class DataType>
class QCPDataContainer // no QCP_LIB_DECL, template class ends up in header (cpp included below)
{
public:
    ...
        
    // 新添內容
    QVector<DataType>* coreData() { return &mData; }
}

3.3 使用繪圖資料地址來更新資料

對相應的繪圖物件使用 coreData() 函式獲得繪圖資料的地址,如下所示:

QVector<QCPGraphData> *mData = https://www.cnblogs.com/young520/archive/2023/05/29/ui->Plot->graph(0)->data()->coreData();

得到這個地址后,就可以用陣列訪問的方式逐點更新資料,或者使用 memcpy() 做一次更新,后面繪圖時會默認資料已經排好了序,不會再進行排序操作,因此若需要重排資料順序,需人工提前排好,

// 可能需要預分配容器記憶體,預分配記憶體僅需一次
mData->reserve(totalSize);
mData->resize(totalSize);

// 逐點更新 xi = 5.0;
(*mData)[i].key = 5.0;

// 逐點更新 yi = sin(5.0);
(*mData)[i].value = https://www.cnblogs.com/young520/archive/2023/05/29/sin(5.0);

// 一次更新
memcpy((char*)mData, (char*)pData, sizeof(double)*totalSize*2);

注意:使用 memcpy() 一次更新時,這個 pData 為存盤新資料的記憶體首地址,pData 所指空間中資料的排列方式必須和對應繪圖資料的記憶體排列方式保持一致,


4. 技巧四:匯出 QCPColorMap 繪圖資料地址

4.1 QCPColorMap 繪圖資料的記憶體結構

QCPColorMap 繪圖資料存盤在 QCPColorMapData 這個類里面,詳見 qcustomplot.h 檔案中 QCPColorMapData 的類定義,繪圖資料存盤的容器為一維 double 陣列,按行進行存盤,縱坐標小的排在陣列前面,縱坐標最小的一行排在陣列最前面,縱坐標最大的一行排在陣列最后面;存盤每行時,橫坐標最小的排在陣列前面,橫坐標最大的排在陣列后面,QCustomPlot 提供的資料更新介面有:

// QCPColorMapData
void setData(double key, double value, double z)
void setCell(int keyIndex, int valueIndex, double z)
void fill(double z)
    
// QCPColorMap
void setData(QCPColorMapData *data, bool copy=false)

同樣在資料點數固定但數值更新速率很高時,原生介面中的很多操作都是不必要的,

4.2 在 qcustomplot.h 檔案中添加代碼

QCPColorMapData 類定義的 public 區域,添加以下一行代碼即可:

class QCP_LIB_DECL QCPColorMapData
{
public:
    ...

    // 新添內容
    double *coreData() { mDataModified = true; return mData; }
}

4.3 使用繪圖資料地址來更新資料

對 QCPColorMap 物件使用 coreData() 函式獲得繪圖資料的地址,如下所示:

double *mData = https://www.cnblogs.com/young520/archive/2023/05/29/m_pColorMap->data()->coreData();

得到這個地址后,就可以用陣列訪問的方式逐點更新資料,或者使用 memcpy() 做一次更新,

// 不要在外部使用 new 來分配記憶體,而應使用原生介面來做記憶體預分配
m_pColorMap->data()->setSize(xsize, ysize);

// 逐點更新 m[xi][yj] = 5.0; 其中 xi,yj 為非負整型索引值
mData[(yj-1)*xsize+xi] = 5.0;

// 一次更新
memcpy((char*)mData, (char*)pData, sizeof(double)*xsize*ysize);

注意:使用 memcpy() 一次更新時,這個 pData 為存盤新資料的記憶體首地址,pData 所指空間中資料的排列方式必須和 QCPColorMap 繪圖資料的記憶體排列方式保持一致,

本文作者:木三百川

本文鏈接:https://www.cnblogs.com/young520/p/17441950.html

著作權宣告:本文系博主原創文章,著作權歸作者所有,商業轉載請聯系作者獲得授權,非商業轉載請附上出處鏈接,遵循 署名-非商業性使用-相同方式共享 4.0 國際版 (CC BY-NC-SA 4.0) 著作權協議,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553747.html

標籤:其他

上一篇:HashMap底層原理

下一篇:返回列表

標籤雲
其他(159939) Python(38185) JavaScript(25462) Java(18151) C(15233) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7215) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5344) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4578) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1977) 功能(1967) Web開發(1951) HtmlCss(1949) C++(1926) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1862) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 【QCustomPlot】性能提升之修改原始碼(版本 V2.x.x)

    QCustomPlot 是開源專案,原始碼撰寫十分規范,想要理解它的可視化思路不算特別困難。我在這篇隨筆中總結一下常用的原始碼修改技巧,下面的每一個技巧都是獨立的,不同技巧中添加的代碼無任何依賴關系,相互之間也不會引發任何沖突,不會影響 QCustomPlot 原生的介面。示例中使用的 QCustomP... ......

    uj5u.com 2023-05-30 07:44:27 more
  • HashMap底層原理

    HashMap是Java中常用的資料結構之一,它提供了高效的鍵值對存盤和檢索功能。下面是HashMap底層的詳細原理介紹: 1. 資料結構:HashMap底層使用陣列和鏈表(或紅黑樹)的組合實作。它通過哈希演算法將鍵轉換為陣列索引,并將值存盤在對應索引位置上。 2. 哈希演算法:當我們向HashMap中 ......

    uj5u.com 2023-05-30 07:44:19 more
  • 常用的排序演算法總結

    # 常用的排序演算法 ## 一、冒泡排序 冒泡排序(Bubble Sort),是一種較簡單的排序演算法。 它重復地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果順序(如從大到小、首字母從Z到A)錯誤就把他們交換過來。走訪元素的作業是重復地進行直到沒有相鄰元素需要交換,也就是說該元素列已經排序完成。 ......

    uj5u.com 2023-05-30 07:44:15 more
  • Python連接es筆記二之查詢方式匯總

    > 本文首發于公眾號:Hunter后端 > 原文鏈接:[Python連接es筆記二之查詢方式匯總](https://mp.weixin.qq.com/s/0Yn5c-U9pBWrSC5HrCgWog) 上一節除了介紹使用 Python 連接 es,還有最簡單的 query() 方法,這一節介紹一下幾 ......

    uj5u.com 2023-05-30 07:44:09 more
  • 【python基礎】基本資料型別-數字型別

    Python3 支持int(整型資料)、float(浮點型資料)、bool(布爾型別) # 1.int(整型資料) 在Python 3里,**只有一種整數型別 int,表示為長整型**。像大多數語言一樣,數值型別的賦值和計算都是很直觀的。 ## 1.1數值運算 撰寫程式如下所示 ![image](h ......

    uj5u.com 2023-05-30 07:43:56 more
  • Redis+分布式+秒殺

    ## 聊一下MySQL 關于mysql關系型資料庫的一些分析: 1、從性能上:如果我們碰到需要執行耗時特別久,并且執行結果不是很頻繁變動的SQL陳述句,我們就沒有必要每次都去查詢資料庫,因為每次操作資料庫都很耗時。 2、從并發上:在大并發的情況下(比如618秒殺活動,你敢讓千萬級的請求直接打到資料庫上 ......

    uj5u.com 2023-05-30 07:43:44 more
  • < Python全景系列-8 > Python超薄感知,超強保護:例外處理的絕佳實

    歡迎來到系列第八篇,例外處理的深入探討。本文將分五部分展開。首先,我們將學習Python例外處理的基礎知識,理解`try/except`陳述句的用法。然后,我們將了解Python的常見例外型別并通過實體理解它們的作用。第三部分,我們將更深入地決議`try-except`塊,理解其作業原理及更加復雜的用... ......

    uj5u.com 2023-05-30 07:43:38 more
  • Pandas 加載資料的方法和技巧

    哈嘍大家好,我是咸魚 相信小伙伴們在學習 python 資料分析的程序中或多或少都會聽說或者使用過 pandas pandas 是 python 的一個拓展庫,常用于資料分析 今天咸魚將介紹幾個關于 pandas 匯入資料的方法和技巧 ## 從 URL 獲取 csv 資料 關于 pandas 匯入 ......

    uj5u.com 2023-05-30 07:43:31 more
  • Angular Highcharts教程_編程入門自學教程_菜鳥教程-免費教程分

    ## 教程簡介 Angular Highcharts是一個基于Angular的開源組件,可在Angular應用程式中提供優雅且功能豐富的高圖表可視化,并可與Angular組件無縫配合使用。 [Angular Highcharts入門教程](https://www.itbaoku.cn/tutoria ......

    uj5u.com 2023-05-30 07:43:26 more
  • 面試官:MySQL 自增主鍵一定是連續的嗎?大部分人都會答錯!

    ## 測驗環境: > MySQL版本:8.0 資料庫表:T (主鍵id,唯一索引c,普通欄位d) ![](http://img.javastack.cn/1685072039483867.png) 如果你的業務設計依賴于自增主鍵的連續性,這個設計假設自增主鍵是連續的。但實際上,這樣的假設是錯的,因為 ......

    uj5u.com 2023-05-30 07:43:14 more