目錄
- 一、概述
- 1.1 背景介紹:從UI到邏輯
- 1.2 學習模式:Qt控制臺應用
- 二、C++語法快速入門
- 2.1 變量、數據類型與注釋
- 2.2 函數與代碼封裝
- 2.3 循環與容器:批量處理
- 三、面向對象編程:封裝數據與行為
- 四、Qt的核心擴展:信號與槽通信
- 五、總結與展望
一、概述
1.1 背景介紹:從UI到邏輯
在上一篇文章中,我們成功搭建了開發環境,并為“AI螺絲瑕疵檢測系統”創建了一個專業、美觀的項目骨架。一個軟件的UI(用戶界面)是它的“面孔”,負責與用戶交互;而其C++后端則是它的“大腦”和“骨骼”,負責處理數據、執行復雜邏輯并與硬件通信。
本篇文章的核心任務,就是構建這個強大的“大腦”和“骨骼”。我們將深入C++的世界,但會聚焦于在Qt開發中最常用、最核心的部分。通過本篇的學習,讀者將掌握創建后端邏輯、組織代碼以及實現對象間通信的基本功,為后續集成真正的視覺算法和硬件交互打下堅實的語言基礎。
1.2 學習模式:Qt控制臺應用
為了讓讀者能專注于C++語法本身,暫時不受QML圖形界面的干擾,本章的所有示例都將以**Qt控制臺應用程序(Qt Console Application)**的形式呈現。這是一種沒有圖形界面的、純粹在命令行輸出信息的程序,是學習和驗證后端邏輯的絕佳工具。
二、C++語法快速入門
本節面向C++初學者,旨在通過可直接運行的、獨立的控制臺程序,快速介紹后續章節將會頻繁使用的基本語法元素。
2.1 變量、數據類型與注釋
【例2-1】 產品信息存儲與顯示。
變量是程序中用于存儲數據的基本單元,可以將其看作是內存中一個帶標簽的盒子。數據類型則定義了這個“盒子”能裝什么類型的東西(如整數、小數、字符串等)以及能對它進行哪些操作。在C++中,定義任何變量都必須首先聲明其數據類型。注釋用于解釋代碼,不會被編譯器執行。
(1) 基本數據類型: int
(整數), double
(雙精度浮點數), bool
(布爾值, true或false)。
(2) Qt常用數據類型: QString
(字符串)。在Qt開發中, 推薦使用QString
而非C++標準庫的std::string
,因為它提供了更豐富的功能和更好的Unicode支持。
(3) 注釋: 單行注釋以//
開始,多行注釋以/*
開始,以*/
結束。
使用 Qt Creator 創建一個Qt Console Application程序Demo_2_1_Variables
。項目創建完成后,打開main.cpp
,修改代碼如下:
#include <QCoreApplication>
#include <QDebug> // 包含Qt的調試輸出頭文件
#include <QString> // 包含Qt的字符串類頭文件int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// --- 變量定義與初始化 ---int screwCount = 100;double diameter = 5.02;bool isQualified = true;QString batchNumber = "SN20250715-B";// --- 使用qDebug()進行控制臺輸出 ---qDebug() << "開始進行產品信息檢查...";qDebug() << "螺絲數量:" << screwCount << "個";qDebug() << "產品直徑:" << diameter << "mm";qDebug() << "質量狀態:" << isQualified;qDebug() << "生產批號:" << batchNumber;qDebug() << "檢查完畢。";return a.exec();
}
單擊 Qt Creator 左下角的綠色“運行”按鈕。在下方的“應用程序輸出”窗口,可以看到如下結果:
開始進行產品信息檢查...
螺絲數量: 100 個
產品直徑: 5.02 mm
質量狀態: true
生產批號: "SN20250715-B"
檢查完畢。
關鍵代碼分析:
(1) #include <QDebug>
: 要使用qDebug()
,必須包含此頭文件。
(2) #include <QString>
: QString
是Qt中處理文本的專用類,功能強大,完美支持Unicode(包括中文),是Qt開發的首選字符串類型。
(3) 變量定義: 數據類型 variableName = initialValue;
是C++定義變量的基本格式。變量名通常使用小駝峰命名法(camelCase)。
(4) qDebug() << ...
: 這是qDebug
的鏈式調用語法。它會自動在每個<<
分隔的變量之間加上空格,并在最后輸出一個換行符,非常便捷。
2.2 函數與代碼封裝
【例2-2】 封裝尺寸檢查邏輯。
函數是一段被命名、可重復使用的代碼塊,用于執行一個特定的任務。隨著程序邏輯變得復雜,將不同功能的代碼組織成獨立的函數,可以極大地提高程序的可讀性、可維護性和代碼的復用性。一個函數通常有輸入(參數)和輸出(返回值)。
使用 Qt Creator 創建一個Qt Console Application程序Demo_2_2_Functions
。項目創建完成后,打開main.cpp
,修改代碼如下:
#include <QCoreApplication>
#include <QDebug>// --- 函數定義 ---
// 這個函數接收一個double類型的參數(parameter),沒有返回值(void)
// 它的作用是封裝了完整的尺寸檢查邏輯
void checkScrewDiameter(double diameterToTest)
{qDebug() << "--- 開始檢查直徑:" << diameterToTest << "mm ---";const double standardDiameter = 5.0;const double tolerance = 0.05;if (diameterToTest > standardDiameter + tolerance) {qDebug() << "判定結果: 不合格,直徑過大。";} else if (diameterToTest < standardDiameter - tolerance) {qDebug() << "判定結果: 不合格,直徑過小。";} else {qDebug() << "判定結果: 合格,尺寸在公差范圍內。";}qDebug() << "--- 檢查結束 ---" << Qt::endl; // Qt::endl顯式地換行
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// --- 函數調用 ---// 通過函數名和傳遞相應的參數來執行函數中的代碼checkScrewDiameter(5.08); // 調用函數,檢查一個過大的螺絲checkScrewDiameter(4.93); // 調用函數,檢查一個過小的螺絲checkScrewDiameter(5.01); // 調用函數,檢查一個合格的螺絲return a.exec();
}
單擊 Qt Creator 左下角的綠色“運行”按鈕,可以看到如下結果:
--- 開始檢查直徑: 5.08 mm ---
判定結果: 不合格,直徑過大。
--- 檢查結束 ------ 開始檢查直徑: 4.93 mm ---
判定結果: 不合格,直徑過小。
--- 檢查結束 ------ 開始檢查直徑: 5.01 mm ---
判定結果: 合格,尺寸在公差范圍內。
--- 檢查結束 ---
關鍵代碼分析:
(1) 函數定義: “返回值類型 函數名(參數類型1 參數名1, ...)
”是定義函數的基本形式。void
是一種特殊的返回類型,表示該函數執行完畢后不返回任何值。
(2) 函數參數: double diameterToTest
是函數的形式參數(形參),它是在函數定義時聲明的,作為函數內部的一個局部變量來接收外部傳入的數據。
(3) 函數調用: checkScrewDiameter(5.08)
是在main
函數中調用已定義的函數。5.08
是實際參數(實參),在調用發生時,它的值會被復制一份并傳遞給函數中的形參diameterToTest
。
(4) const
關鍵字: const double standardDiameter = 5.0;
const
表示standardDiameter
是一個常量,其值在程序運行期間不能被修改。對于程序中固定不變的數值(如標準、公差),使用常量是一種推薦的編程實踐,可以防止意外修改并提高代碼的可讀性。
2.3 循環與容器:批量處理
【例2-3】 批量質檢一批產品。
循環是一種控制結構,它允許一段代碼根據設定的條件重復執行多次。容器則是一種特殊的對象,專門用于存儲和管理一組數據集合。當需要對一個集合中的所有元素執行相同操作時(例如,檢查流水線上的一整批產品),將循環與容器結合使用,可以實現高效的自動化處理。
使用 Qt Creator 創建一個Qt Console Application程序Demo_2_3_LoopsAndContainers
。項目創建完成后,打開main.cpp
,修改代碼如下:
#include <QCoreApplication>
#include <QDebug>
#include <QVector> // 包含QVector容器的頭文件int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 創建一個QVector容器,用于存放一批待檢測的螺絲直徑QVector<double> diameterBatch;diameterBatch.append(5.01); // 使用append()方法向容器末尾添加元素diameterBatch.append(4.98);diameterBatch.append(5.09); // 這個不合格diameterBatch.append(4.92); // 這個也不合格diameterBatch.append(5.04);qDebug() << "流水線來了一批產品,共" << diameterBatch.size() << "個,開始質檢...";int qualifiedCount = 0;// 使用for循環遍歷QVector中的每一個元素for (int i = 0; i < diameterBatch.size(); ++i) {double currentDiameter = diameterBatch[i]; // 使用索引[i]訪問容器中的元素bool isCurrentQualified = (currentDiameter >= 4.95 && currentDiameter <= 5.05);if (isCurrentQualified) {qDebug() << "第" << i + 1 << "個螺絲,直徑" << currentDiameter << "mm, 合格。";qualifiedCount++;} else {qDebug() << "第" << i + 1 << "個螺絲,直徑" << currentDiameter << "mm, 不合格!";}}qDebug() << "質檢完成。合格數量:" << qualifiedCount;return a.exec();
}
單擊 Qt Creator 左下角的綠色“運行”按鈕,可以看到如下結果:
流水線來了一批產品,共 5 個,開始質檢...
第 1 個螺絲,直徑 5.01 mm, 合格。
第 2 個螺絲,直徑 4.98 mm, 合格。
第 3 個螺絲,直徑 5.09 mm, 不合格!
第 4 個螺絲,直徑 4.92 mm, 不合格!
第 5 個螺絲,直徑 5.04 mm, 合格。
質檢完成。合格數量: 3
關鍵代碼分析:
(1) #include <QVector>
: QVector
是Qt提供的一個模板容器類,功能類似于一個可以動態調整大小的數組。要使用它,必須包含此頭文件。
(2) QVector<double>
: 定義了一個專門用于存放double
類型數據的QVector
容器。尖括號< >
中的類型指定了容器內所有元素的類型。
(3) append()
: 這是QVector
的成員函數,用于在容器的末尾追加一個新元素。
(4) size()
: 這是QVector
的成員函數,用于獲取容器中當前存儲的元素總數。
(5) for循環結構: for(int i = 0; i < diameterBatch.size(); ++i)
是最經典的for循環結構。它包含三個部分,由分號隔開:
- 初始化:
int i = 0
,創建一個循環計數器i
并賦初值為0。 - 循環條件:
i < diameterBatch.size()
,每次循環開始前檢查此條件,若為真則執行循環體,否則退出循環。 - 迭代表達式:
++i
,每次循環體執行完畢后執行此操作,使計數器加1。
(6) 元素訪問:diameterBatch[i]
,通過方括號和索引i
(從0開始)來訪問QVector
容器中的具體元素。
(7)&&
運算符:(currentDiameter >= 4.95 && currentDiameter <= 5.05)
中的&&
是邏輯“與”運算符,它表示其兩側的條件必須同時為真,整個表達式的結果才為真。
三、面向對象編程:封裝數據與行為
當程序邏輯變得復雜時,使用零散的變量和函數來管理會變得混亂。面向對象編程(Object-Oriented Programming, OOP)提供了一種更高級的組織方式,其核心就是類(Class)。
【核心概念:數據與行為的統一體】
類是創建對象的“藍圖”。它將描述某個事物的數據(稱為屬性或成員變量)和能對這些數據進行的操作(稱為方法或成員函數)封裝(Encapsulation)在一起。通過類,可以創建出具體的對象(Object),每個對象都是一個獨立、完整的實體,極大地提高了代碼的結構化程度。
【例2-4】 創建一個螺絲計數器類。
1. 創建項目與類文件
- 新建一個名為
Demo_2_4_Classes
的Qt控制臺項目。 - 在Qt Creator中,右鍵點擊項目名稱,選擇
添加新文件...
->C++
->C++ Class
,類名輸入SimpleCounter
,基類保持默認的無
,完成向導。Qt Creator會自動創建simplecounter.h
和simplecounter.cpp
。
2. 編寫代碼 (simplecounter.h)
#ifndef SIMPLECOUNTER_H
#define SIMPLECOUNTER_H// 類的聲明
class SimpleCounter
{
public:// 構造函數:在創建對象時自動調用,用于初始化SimpleCounter();// 公有(public)成員函數: 外部代碼可以直接調用void increment(); // 計數加一void clear(); // 計數清零int getCount() const; // 獲取當前計數值private:// 私有(private)成員變量: 外部代碼無法直接訪問,實現了封裝和保護int m_count;
};#endif // SIMPLECOUNTER_H
3. 編寫代碼 (simplecounter.cpp)
#include "simplecounter.h"// 構造函數的實現
SimpleCounter::SimpleCounter()
{// 在這里進行初始化工作m_count = 0;
}// 成員函數的實現
void SimpleCounter::increment()
{m_count++;
}void SimpleCounter::clear()
{m_count = 0;
}// const關鍵字表示這個函數不會修改類的任何成員變量
int SimpleCounter::getCount() const
{return m_count;
}
4. 編寫代碼 (main.cpp)
#include <QCoreApplication>
#include <QDebug>
#include "simplecounter.h" // 包含我們自己定義的類的頭文件int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "創建兩個計數器: 合格品計數器和次品計數器";// 從SimpleCounter類創建兩個具體的對象SimpleCounter qualifiedCounter;SimpleCounter defectiveCounter;// 模擬生產過程qDebug() << "生產了3個合格品...";qualifiedCounter.increment();qualifiedCounter.increment();qualifiedCounter.increment();qDebug() << "生產了1個次品...";defectiveCounter.increment();// 通過公有方法獲取內部的計數值并打印qDebug() << "當前合格品數量:" << qualifiedCounter.getCount();qDebug() << "當前次品數量:" << defectiveCounter.getCount();return a.exec();
}
5. 運行結果
創建兩個計數器: 合格品計數器和次品計數器
生產了3個合格品...
生產了1個次品...
當前合格品數量: 3
當前次品數量: 1
關鍵代碼分析:
(1) 頭文件(.h)與源文件(.cpp): 在C++中,類的聲明(它有什么)通常放在頭文件中,而成員函數的具體實現(它怎么做)放在源文件中。這是一種代碼組織規范。
(2) public:
與 private:
: 這是訪問說明符。public
部分是類的對外接口,任何代碼都可以訪問。private
部分是類的內部實現,只有該類的成員函數才能訪問,外部代碼無法直接觸及。這體現了封裝的思想,保護了內部數據的安全性。
(3) 構造函數: SimpleCounter()
是一個特殊的成員函數,它沒有返回類型,且函數名與類名完全相同。在創建類的對象時(如SimpleCounter qualifiedCounter;
),構造函數會被自動調用,是執行初始化操作的最佳位置。
(4) SimpleCounter::
: ::
是作用域解析運算符。在.cpp
文件中,它用于指明一個函數實現是屬于哪個類的。
四、Qt的核心擴展:信號與槽通信
【核心概念:Qt的靈魂機制】
在圖形界面程序中,一個對象的狀態改變(如按鈕被點擊)常常需要通知其他多個對象。如果使用直接函數調用的方式,對象之間會產生緊密的依賴關系(耦合),不利于程序的維護和擴展。
為此,Qt引入了其最核心、最強大的創新之一:信號與槽(Signals and Slots)。
- 信號(Signal): 當對象內部狀態發生改變時,由該對象發射(emit)出去的一種“廣播”或“通知”。它只負責發出通知,不關心誰會收到。
- 槽(Slot): 一個普通的成員函數,用于接收并處理某個信號。
通過connect
函數,可以將一個對象的信號與另一個(或同一個)對象的槽連接起來。當信號被發射時,所有與之連接的槽函數就會被自動調用。這種機制使得信號的發送方和接收方可以互不知道對方的存在,實現了完美的解耦。
要讓一個類支持信號與槽,必須:
- 公有繼承自
QObject
。 - 在類聲明的私有區頂部添加
Q_OBJECT
宏。
【例2-5】 工作者完成任務后通知管理者。
1. 創建項目與類文件
- 新建一個名為
Demo_2_5_SignalsAndSlots
的Qt控制臺項目。 - 分別添加
Worker
和Manager
兩個C++類,注意它們的基類都要選擇QObject
。
2. 編寫代碼 (worker.h & worker.cpp)
// worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QString>class Worker : public QObject
{Q_OBJECT // 必須添加此宏以支持信號與槽
public:explicit Worker(QObject *parent = nullptr);void doWork(); // 模擬執行一個任務
signals:// 信號只需要聲明,無需實現。在signals關鍵字下聲明void workFinished(const QString &result);
};
#endif // WORKER_H// worker.cpp
#include "worker.h"
#include <QDebug>
#include <QThread>Worker::Worker(QObject *parent) : QObject(parent) {}
void Worker::doWork()
{qDebug() << "[工作者] 開始執行耗時任務...";QThread::sleep(2); // 模擬耗時2秒QString result = "所有螺絲檢測完畢";qDebug() << "[工作者] 任務完成,準備發射信號...";emit workFinished(result); // 使用emit關鍵字發射信號
}
3. 編寫代碼 (manager.h & manager.cpp)
// manager.h
#ifndef MANAGER_H
#define MANAGER_H
#include <QObject>
#include <QString>class Manager : public QObject
{Q_OBJECT
public:explicit Manager(QObject *parent = nullptr);
public slots:// 槽函數聲明在 public slots: 區域void onWorkFinished(const QString &result);
};
#endif // MANAGER_H// manager.cpp
#include "manager.h"
#include <QDebug>Manager::Manager(QObject *parent) : QObject(parent) {}void Manager::onWorkFinished(const QString &result)
{qDebug() << "[管理者] 收到信號,槽函數被調用!";qDebug() << " > 結果:" << result;
}
4. 編寫代碼 (main.cpp)
#include <QCoreApplication>
#include "worker.h"
#include "manager.h"
#include <QDebug>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Worker worker;Manager manager;// --- 信號與槽的連接 ---QObject::connect(&worker, &Worker::workFinished,&manager, &Manager::onWorkFinished);qDebug() << "[主程序] 命令工作者開始工作。";worker.doWork(); // 調用此函數將最終導致信號被發射return a.exec();
}
5. 運行結果
[主程序] 命令工作者開始工作。
[工作者] 開始執行耗時任務...
[工作者] 任務完成,準備發射信號...
[管理者] 收到信號,槽函數被調用!> 結果: "所有螺絲檢測完畢"
關鍵代碼分析:
(1) QObject
與Q_OBJECT
: Worker
和Manager
都繼承自QObject
,并在類聲明中包含了Q_OBJECT
宏,這是使用信號槽的前提。Qt的MOC(元對象編譯器)會預處理這個宏,生成支持信號槽所需的額外代碼。
(2) signals:
: 用于聲明信號函數。信號函數沒有函數體,其返回值類型通常是void
。
(3) emit
: 這是一個空宏,在代碼中用于標記一個信號的發射。當執行到emit workFinished(...)
時,Qt的元對象系統會接管,并調用所有與workFinished
信號連接的槽。
(4) public slots:
: 用于聲明槽函數。槽函數的本質是一個普通的成員函數,可以有自己的參數和實現。
(5) QObject::connect(...)
: 這是建立連接的核心函數。其最常用的形式是:
connect(信號發送者地址, &發送者類::信號名, 信號接收者地址, &接收者類::槽函數名);
這種寫法在編譯時就會進行類型檢查,如果信號和槽的參數不匹配,或者函數名寫錯,編譯器會直接報錯,非常安全。
五、總結與展望
在本篇文章中,我們不僅復習了C++的基礎語法,更深入探討了面向對象編程的核心——類與對象,以及Qt框架的靈魂——信號與槽機制。這些是構建任何復雜、可維護的Qt應用程序都不可或缺的知識。
現在,我們已經具備了用C++構建結構化、可交互的后端邏輯的能力。在下一篇文章**【《使用Qt Quick從零構建AI螺絲瑕疵檢測系統》——3. QML入門:像搭積木一樣構建UI】**中,我們將把目光轉向用戶界面,開始探索QML的精彩世界,學習如何為我們的C++后端打造一個現代化的“面孔”。