Qt/QML 編碼規(guī)范
一. 概述
良好的編碼規(guī)范可以大幅提高程序的可讀和可理解性,最終目標(biāo)是實現(xiàn)更友好的可維護性沛婴。
保持良好的開發(fā)和編碼規(guī)范也是一種利己利人的高效習(xí)慣引镊,值得每一位開發(fā)者去鍛煉、養(yǎng)成和堅持疏尿。
本規(guī)范僅是建議淋淀,不能強求一律遥昧,允許權(quán)衡例外。
All for one, one for all —— 人人為我朵纷,我為人人炭臭。合作之道,就在其中袍辞。
二. C++ 相關(guān)的約定
- 不要使用異常
- 不要使用 rtti (運行時類型信息;即typeinfo結(jié)構(gòu)鞋仍、dynamic_cast或typeid操作符,包括拋出異常)
- 明智地使用模板搅吁,而不是因為你可以這樣做威创。(Use templates wisely, not just because you can.)
三. Qt/QML 統(tǒng)一規(guī)范
- 行尾不能有空格
- 每個 QObject 子類都必須加上 Q_OBJECT 宏,即使它沒有定義任何信號或插槽谎懦,否則使用 qobject_cast 將失敗肚豺,此時倒不如不繼承 QObject
- 你可以將本文檔真的 <SomeOne> 替換為你的公司名稱
四. Qt 編碼規(guī)范
1. 頭文件
(1) 頭文件保護
所有頭文件都應(yīng)該使用 #define 防止頭文件被多重包含,命名格式為:
// classname.h
#pragma once
#ifndef CLASSNAME_H_A37F2BB8_82AE_4426_87BD_8969C0000936
#define CLASSNAME_H_A37F2BB8_82AE_4426_87BD_8969C0000936
// ……
#endif //CLASSNAME_H_A37F2BB8_82AE_4426_87BD_8969C0000936
其中的 H_ 后的部分為 UUID 界拦,這部分可以使用工具生成:Linux 和 Mac 系統(tǒng)可以使用命令行工具吸申,Windows 系統(tǒng)可以使用uuid在線生成工具。
關(guān)于頭文件保護的涉及到的宏的簡介享甸,可以參考《C++ 頭文件保護》
(2) 頭文件依賴
減少頭文件中 #include 文件的數(shù)量呛谜,盡量使用前向聲明:
# include<MustIncludeClassA>
//...
class SomeClassIncludeInCppA;
class SomeClassIncludeInCppB;
// ...
class MyClass {
// ...
};
(3) 頭文件包含次序
將包含次序標(biāo)準(zhǔn)化可增強可讀性,次序建議如下:
Qt 庫的頭文件枪萄、C/C++ 標(biāo)準(zhǔn)庫頭文件、其他三方庫的頭文件猫妙、項目內(nèi)定義的頭文件瓷翻。
如果不得不包含私有頭文件。使用以下語法割坠,不管 whatever_p.h 在哪個模塊或目錄中齐帚。
#include <private/whatever_p.h>
2. 命名約定
(1) 通用命名約定
避免使用縮寫
(2) 文件命名
文件名建議全部小寫,可以包含下劃線彼哼,例如:
some_class_test_a.h
some_class_test_a.cpp
someclasstestb.h
someclasstestb.cpp
(3) 類命名
類名是名詞对妄,每個單詞以大寫字母開頭,不包含下劃線敢朱,且以大寫字母C開頭剪菱,例如:
class CSomeClassA {
};
class CMyClassB {
};
(4) 變量命名
- 變量名是名詞
- 首個單詞以小寫字母開頭摩瞎,后續(xù)單詞以大寫字母開頭
- 每個變量占一行
- 單一字符的變量只在臨時變量或循環(huán)計數(shù)中使用
- 類成員變量需在變量名前加 m_ 前綴,例如:
int m_myValue;
- 使用內(nèi)部封裝類作為 d 指針成員變量時孝常,封裝類內(nèi)可不使用 m_ 前綴
- 局部變量要等到需要使用時再進(jìn)行定義變量旗们,且定義時必須進(jìn)行初始化:整數(shù)用 0、實數(shù)用 0.0构灸、指針用 nullptr上渴、字符用 '\0'
- 盡量不要使用全局變量,以降低耦合
- bool 類型的變量盡量使用 enabled喜颁、visible稠氮、movable 這種形式
(5) 常量命名
常量不含前綴且應(yīng)該大寫,單詞間有下劃線半开,包括全局常量和宏定義隔披,例如:
const int MY_DEFINE_NUMBER = 0;
//...
#define MY_DEFINE_VALUE 0
(6) 函數(shù)命名
函數(shù)命名力求通過名稱就能達(dá)到使調(diào)用者知道其返回的是什么屬性值,或者其調(diào)用之后的作用的目標(biāo)稿茉。
因此锹锰,取值函數(shù)盡量使用名詞或者帶修飾詞的名詞,而賦值或者帶來改變的函數(shù)采用動賓語法漓库,例如 setWidgetMovable()恃慧,返回 bool 的函數(shù)可以使用 is 加屬性名的方式,首個單詞以小寫字母開頭渺蒿,后續(xù)單詞以大寫字母開頭痢士,例如:
QString name() const;
QSize screenSize() const;
void setPoint(const QPoint& point);
void drawLine(const QPoint& startPoint, const QPoint& endPoint);
bool isMovable() const;
void setMovable(bool movable);
函數(shù)參數(shù)使用名詞或者帶限定修飾詞的名詞,盡量避免使用縮寫茂装,或者使用與 Qt 一致的縮寫形式怠蹂,首個單詞以小寫字母開頭,后續(xù)單詞以大寫字母開頭少态,參數(shù)類型如果不是基本數(shù)據(jù)類型城侧,使用 const 引用作為參數(shù),例如:
void drawRectangle(const QPoint& topLeft, const QPoint& bottomRight);
void drawRectangle(const QRect& rect);
函數(shù)及其參數(shù)的命名一定不要出現(xiàn)無意義或者根據(jù)其名稱難以知道其作用的名稱彼妻,例如:fun0嫌佑、f1 、param0侨歉、p1 等名稱屋摇。這種名稱往往作者本人回頭看代碼時都要看下函數(shù)實現(xiàn),才能知道當(dāng)初自己想要做什么幽邓。
(7) 枚舉命名
枚舉名和枚舉值都是名詞炮温,每個單詞以大寫字母開頭,且枚舉名的第一個單詞是 <SomeOne>牵舵,例如:
enum SomeOneTitleBarBackgroundColor {
Transparent,
White,
Black,
Red
};
(8) 命名空間命名
命名空間的名稱是名詞柒啤,每個單詞以大寫字母開頭倦挂,且前兩個單詞是 <SomeOne>,命名空間的結(jié)束大括號之后跟命名空間名稱的注釋白修,例如:
namespace SomeOneUtils {
//...
} // SomeOneUtils
(9) 結(jié)構(gòu)體命名
- 結(jié)構(gòu)體中只定義變量妒峦,不定義函數(shù),需要定義函數(shù)請使用類
- 結(jié)構(gòu)體名使用名詞兵睛,每個單詞以大寫字母開頭
- 結(jié)構(gòu)體成員使用名詞定義肯骇,首單詞以小寫字母開頭,后續(xù)單詞以大寫字母開頭
示例如下:
struct SuperDevice {
bool isSuperDevice;
QString name;
};
3. 注釋約定
(1) 簡單注釋
- 單行注釋:/// 或者 //!
- 多行注釋:/** 或者 /*!
- 注釋掉的代碼使用://
- 行內(nèi)注釋掉代碼:/* 和 */
(2) 類注釋
類的頭文件頂部需添加說明性注釋祖很,包括版權(quán)說明笛丙、版本號、作者假颇、生成日期胚鸯、類的功能描述等。
/**
* Copyright (C) 2020 Beijing <SomeOne> Technology Co.,Ltd. All rights reserved.
*
* @brief: doxygen 測試類
* 這個類是用于測試使用 doxygen 的實例類
* @author: ZhaoDongshuang
* @email: imtoby@126.com
* @version: 1.0.0
* @date: 2020-04-27 14:52
*
* This software, including documentation, is protected by copyright controlled
* by Beijing <SomeOne> Technology Co.,Ltd. All rights are reserved.
*/
(3) 常量笨鸡、變量的注釋
通常其命名時應(yīng)做到足以說明用途姜钳,特定情況下,需要額外注釋說明的話形耗,一般可分兩種形式哥桥,可以根據(jù)喜好選擇,只要保證在整個工程中統(tǒng)一即可
- 代碼上一行注釋
//! 緩存大小
#define BUF_SIZE 1024*4
- 代碼后注釋
#define SIMPLE_PI 3.14 ///< 不精確的圓周率
(4) 函數(shù)注釋
重要函數(shù)頭部應(yīng)該進(jìn)行注釋激涤,包括函數(shù)名拟糕、函數(shù)功能描述、輸入?yún)?shù)倦踢、輸出參數(shù)送滞、返回值及其他。以 main 函數(shù)為例:
/**
*
* @brief: 主函數(shù)
* @detail: 應(yīng)用程序入口
* @param argc: 命令參數(shù)個數(shù)
* @param argv: 命令參數(shù)指針數(shù)組
* @return: 程序執(zhí)行成功與否
* @retval 0: 程序執(zhí)行成功
* @retval 1: 程序執(zhí)行失敗
* @note: 測試
* @attention: 注意
* @warning: 警告
* @exception: 異常
*/
int main(int argc, char* argv[])
{
// ...
return 0;
// ...
return 1;
}
(5) 代碼塊或代碼行的注釋
對于實現(xiàn)代碼中重要或不容易理解的地方加以注釋辱挥。建議采用代碼塊上方注釋的形式:
//! 不使用中間變量實現(xiàn)交換變量的值犁嗅,使用引用或指針都可以
void swap(int& a, int& b) {
a=a^b;
b=a^b;
a=a^b;
}
(6) TODO 注釋
計劃中但未完成的代碼使用TODO注釋,但是一定要對 TODO 的內(nèi)容進(jìn)行簡單的說明晤碘,例如:
//! 不使用中間變量實現(xiàn)交換變量的值愧哟,使用引用或指針都可以
void swap(int& a, int& b) {
a=a^b;
b=a^b;
a=a^b;
}
// TODO: 添加使用中間變量實現(xiàn)交換變量的值的實現(xiàn)版本
(7) doxygen 其他的一些標(biāo)注方式及說明
命令 | 生成字段名 | 備注 |
---|---|---|
@see | 參考 | |
@class | 引用類 | 用于文檔生成連接 |
@var | 引用變量 | 用于文檔生成連接 |
@enum | 引用枚舉 | 用于文檔生成連接 |
@code | 代碼塊開始 | 與@endcode成對使用 |
@endcode | 代碼塊結(jié)束 | 與@code成對使用 |
@bug | 缺陷 | 鏈接到所有缺陷匯總的缺陷列表 |
@todo | TODO | 鏈接到所有TODO 匯總的TODO 列表 |
@example | 使用例子說明 | |
@remarks | 備注說明 | |
@pre | 函數(shù)前置條件 | |
@deprecated | 函數(shù)過時說明 |
4. 代碼排版約定
(1) 每一行的長度
行長度限定不超過 80 個字符,超出部分分成多行書寫哼蛆,長表達(dá)式要在較低優(yōu)先級操作符處劃分新行,操作符放在新行之首霞赫,逗號放在一行的結(jié)束腮介,劃分出的新行要進(jìn)行適當(dāng)?shù)目s進(jìn),使排版整齊端衰,語句可讀叠洗,例如:
// Wrong
if (longExpression +
otherLongExpression +
otherOtherLongExpression) {
}
// Correct
if (longExpression
+ otherLongExpression
+ otherOtherLongExpression) {
}
(2) 縮進(jìn)
使用 4 個空格進(jìn)行代碼縮進(jìn)甘改,不要用 Tab 鍵。但是對于由開發(fā)工具自動生成的代碼可以有不一致灭抑。
預(yù)處理指令不要縮進(jìn)十艾,從頂格開始,例如:
if (isActive()) {
#if OS_WIN32
dropEverything();
#endif
backToNormal();
}
(3) 空白
空行可將語句進(jìn)行適當(dāng)?shù)姆纸M腾节,便于閱讀忘嫉,在相對獨立的代碼塊之間必須加一行空行,且始終只使用一行空行案腺。
一定要在關(guān)鍵字之后和花括號之前使用單個空格:
// Wrong
if(foo){
}
// Correct
if (foo) {
}
- 對于指針或引用庆冕,總是在類型和'*'或'&'之間使用一個空格,但是在'*'或'&'和變量名之間不使用空格:
char *x;
const QString &myString;
const char * const y = "hello";
- 用空格包圍二進(jìn)制運算符
- 在每個逗號后面留一個空格
- 用于轉(zhuǎn)換的右尖括號后不要有空格(避免C風(fēng)格的轉(zhuǎn)換)
// Wrong
char* blockOfMemory = (char* ) malloc(data.size());
// Correct
char *blockOfMemory = reinterpret_cast<char *>(malloc(data.size()));
- 在控制流語句中另起一行
// Wrong
if (foo) bar();
// Correct
if (foo) {
bar();
}
(4) 大括號
- 類劈榨、類方法的實現(xiàn)访递、全局方法的左大括號永遠(yuǎn)單獨占一行,其他情況不單獨占一行同辣,跟在語句后面拷姿,例如:
static void foo(int g)
{
qDebug("foo: %i", g);
}
namespace SomeOneUtils {
bool isCEqualToAPlusB(int numA, int numB, int numC) {
if ((numA + numB) == numC) {
qDebug() << numA << "+" << numB "=" << numC;
return true;
}
return false;
}
class TestA
{
public:
void todoTest();
};
void TestA::todoTest()
{
if (isCEqualToAPlusB(1, 1, 2)) {
// ...
}
}
} // SomeOneUtils
- 條件語句即使只有一條主體內(nèi)容,也建議使用大括號旱函,以便可能的添加和閱讀
- 即使條件語句的主體為空時响巢,也使用大括號
// Wrong
while (a);
// Correct
while (a) {}
(5) 圓括號
使用圓括號將表達(dá)式分組,即使運算符的優(yōu)先級相同陡舅,或者明確知道表達(dá)式的執(zhí)行優(yōu)先級抵乓,也要用圓括號進(jìn)行分組,以便于閱讀靶衍,例如:
// Wrong
if (a && b || c)
// Correct
if ((a && b) || c)
// Wrong
a + b & c
// Correct
(a + b) & c
(6) switch 語句
- case 和 switch 位于同一列
- 每個 case 必須在結(jié)尾有一個 break(或 return)語句灾炭,或者使用 Q_FALLTHROUGH() 來表明沒有故意中斷,除非另一個 case 緊隨其后
switch (myEnum) {
case Value1:
doSomething();
break;
case Value2:
case Value3:
doSomethingElse();
Q_FALLTHROUGH();
default:
defaultHandling();
break;
}
5. 代碼編寫上的約定
- 代碼采用 C++11 標(biāo)準(zhǔn)颅眶,盡量使用智能指針蜈出,盡量不使用裸指針(Qt 中使用 QScopedPointer)
- 使用 Qt5 新的信號和槽連接方式,信號和槽的命名不加 signal 和 slot 前綴涛酗,用動作和on動作方式铡原,如信號 clicked(),槽為 onClicked()
- 關(guān)鍵代碼需要有單元測試商叹,非界面代碼一般認(rèn)為是關(guān)鍵代碼
- 默認(rèn)debug build前推薦使用靜態(tài)代碼檢查工具(Cppcheck/Clang/VS)檢查燕刻,編譯時 使用 gcc sanitizer 增加地址和未定義行為動態(tài)檢查
- 開啟 qtcreator 插件 beautifier,并配置 clang-format 格式化工具剖笙,進(jìn)行項目代碼整體風(fēng)格的統(tǒng)一管理卵洗,可以參考使用 Qt Creator 插件 beautifier 配置 clang-format 工具管理項目代碼整體風(fēng)格
- 在填入界面顯示文字時,使用英文弥咪,通過 Qt Linguist 翻譯成中文过蹂,以便多語言的實現(xiàn)十绑,文件編碼一律使用 UTF-8,添加 BOM
- 在繪制界面的時候酷勺,盡量使用 layout 和 stretch 來布局本橙,盡量不要使用固定的大小,例如固定 10 像素
- 避免使用 C 類型的強制轉(zhuǎn)換脆诉,選擇 C++ 類型的強制轉(zhuǎn)換(static_cast甚亭、const_cast、reinterpret_cast)库说,雖說 reinterpret_cast 和 C 類型的強制轉(zhuǎn)換都是危險的狂鞋,但至少 reinterpret_cast 不會刪除 const 修飾符
- 使用 qobject_cast 來代替 dynamic_cast
- 使用構(gòu)造函數(shù)來轉(zhuǎn)換簡單類型: int(myFloat) 而不是 (int)myFloat
五. QML 編碼規(guī)范
1. 文件命名
(1) QML 文件命名
- 僅由英文單詞組成
- 以大寫字母 C (Custom type 之意)開始,遵循大駝峰法的命名規(guī)則
示例:
CProgressDialog.qml
CListView.qml
CMainWindow.qml
(2) js 文件命名
- js 文件名由小寫的英文單詞和 _ 連接組成
- js 文件名一律以“ <someone> ”結(jié)尾(<someone>表示公司或組織名稱)
2. 編碼規(guī)范
(1) import 規(guī)則
qml 文件中潜的,import 模塊的建議順序為:
- Qt 內(nèi)置模塊
- 框架自定義 Qt Quick 模塊
- 項目自定義 Qt Quick 模塊
- 本地 *.qml 組件包
- javascript 導(dǎo)入腳本文件
- javascript 引用腳本模塊
示例:
// CPopupWinodw.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
import com.facetoby.UITools.Image 1.0 as FaceImage
import com.facetoby.UITools.Video 1.0 as FaceVideo
import com.facetoby.GoodUtils 1.0 as FaceUtils
import common_tool_kits 1.0
import "self_tool_kits"
import "rename_tool_someone.js" as RenameTool
import "utils.js" as Utils
為了避免不同模塊之間的命名沖突骚揍,我們在導(dǎo)入模塊時可以使用“as”關(guān)鍵字設(shè)置該模塊在本 qml 文件中的“別名”,對于"as"的相關(guān)規(guī)則:
- 導(dǎo)入 Qt 內(nèi)置模塊的版本號選擇對應(yīng)的 Qt 版本的最高版本
- 導(dǎo)入 js 文件到 qml 中必須要命名別名
- 導(dǎo)入模塊啰挪,存在命名沖突時信不,則至少給其中一個模塊設(shè)置別名
- 不對 Qt 內(nèi)置模塊設(shè)置別名
- 對于自定義模塊需設(shè)置別名
- 別名需保持在當(dāng)前 qml 文件內(nèi)的唯一性
- 別名的命名規(guī)則采用大駝峰法
(2) qml 組件組成結(jié)構(gòu)規(guī)則
qml 文件描述的實際上一個由 “UI組件節(jié)點” 組成的“樹”。而每一個 qml 有有且只有一個“根節(jié)點”亡呵。我們可以稱其為“根組件”抽活。
跟組件的定義采用使 qml 文件復(fù)雜度最低原則,例如我們有如下 qml 文件:
// CPageBackground.qml
Item {
// ...
Rectangle {
// ...
}
}
則應(yīng)改寫為:
// CPageBackground.qml
Rectangle {
// ...
}
除非有不得不封裝的理由锰什,否則應(yīng)以堅持降低 qml 的復(fù)雜度為首要原則下硕。
(3) qml 組件封裝的實現(xiàn)規(guī)則
qml 文件的根組件及新定義的屬性、方法汁胆、信號梭姓,都對其使用者可見,因此按照約定俗成的添加雙下劃線("__")前綴的方式并不能避免用戶有意的對相應(yīng)的屬性嫩码、方法誉尖、信號的調(diào)用。
因此铸题,本規(guī)則建議將需要作為私有(private)的屬性铡恕、方法、信號丢间,包含在 QtObject 節(jié)點組件中探熔。
示例:
// CPageBackground.qml
import QtQuick 2.2
Rectangle {
id: pageBackground
color: privateObject.defaultColor
// ... other codes
// private:
QtObject {
id: privateObject
objectName: "someonePageBackground"
property color defaultColor: "black"
// ...
function resetColor() {
pageBackground.color = defaultColor;
}
// ...
}
}
(4) qml 組件內(nèi)容的定義規(guī)則
- 一行只寫一條語句
- 使用 QML 進(jìn)行顯示,使用 C++/Qt 處理復(fù)雜的計算和數(shù)據(jù)管理邏輯
- Loader 出來的 qml 界面烘挫,在界面關(guān)閉時祭刚,一定要清空 Loader 資源
- js 代碼中,能夠用條件運算符(? :)代替 if...else... 地方,要用條件運算符涡驮,這樣使代碼更簡潔明了
- 一個 qml 文件中,設(shè)置 id 的個數(shù)不要超過 7 個喜滨,多余 7 個捉捅,則最好進(jìn)行 qml 代碼的拆分,建議設(shè)置不超過 4 個 id 為宜虽风,更小粒度的 qml 更加便于閱讀和維護
- 當(dāng)一個 qml 文件中棒口,代碼行達(dá)到 200 至 300 行時,要從其 qml 文件中分解出子 qml 文件辜膝,這樣可以避免出現(xiàn)控件關(guān)聯(lián)復(fù)雜无牵,篇幅較長的代碼。原因也是厂抖,更小粒度的 qml 更加便于閱讀和維護
- 如果有歸屬同一類組的多個屬性需要設(shè)置茎毁,為了提高代碼可讀性,則使用組的提示符忱辅,而不是點的運算符號
例如:
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.leftMargin: 10
應(yīng)該改寫成:
anchors {
top: parent.to
left: parent.left
topMargin: 10
leftMargin: 10
}
(5) qml 組件內(nèi)容定義順序規(guī)則
qml 組件內(nèi)容的定義順序建議如下:
- id 一般可以使用文件名去掉開頭的字符 C 作為 id 的值七蜘,以小寫字母開頭,遵循駝峰命名法
- 自定義屬性 property
- 信號 signal
- JavaScript functions
- QtObject 封裝的私有內(nèi)容
- 自身的其他屬性設(shè)置
- 其他子組件
- 狀態(tài)機(states)
- 動畫(animation)
- 轉(zhuǎn)換屬性(transitions)
為了更好的可讀性墙懂,我們要使用空行分隔這些不同的部分
(6) JavaScript 代碼規(guī)則
- 如果腳本是一個單一的表達(dá)式橡卤,我們建議寫它內(nèi)聯(lián):
Rectangle { color: "blue"; width: parent.width / 3 }
- 如果腳本只有幾行,我們通常使用一個塊:
Rectangle {
color: "blue"
width: {
var w = parent.width / 3
console.debug(w)
return w
}
}
- 如果腳本超過幾行或可以被不同的對象使用损搬,我們建議創(chuàng)建一個函數(shù)并像這樣調(diào)用它:
function calculateWidth(object)
{
var w = object.width / 3
// ...
// more javascript code
// ...
console.debug(w)
return w
}
Rectangle { color: "blue"; width: calculateWidth(parent) }
- 對于長腳本碧库,我們可以把函數(shù)放在自己的 JavaScript 文件中,然后像這樣導(dǎo)入:
import "myscript.js" as Script
Rectangle { color: "blue"; width: Script.calculateWidth(parent) }
- 如果代碼超過一行巧勤,因此在一個塊中嵌灰,我們使用分號來表示每個語句的結(jié)尾:
MouseArea {
anchors.fill: parent
onClicked: {
var scenePos = mapToItem(null, mouseX, mouseY);
console.log("MouseArea was clicked at scene pos " + scenePos);
}
}
(7) qml 組件內(nèi)容的命名規(guī)則
- qml 組件內(nèi)容的命名規(guī)則應(yīng)與 C++/Qt 的名稱規(guī)則一致
3. QML 注釋規(guī)則
(1) 組件功能注釋
/*!
\qmltype Button
\inqmlmodule QtQuick.Controls
\since 5.1
\ingroup controls
\brief A push button with a text label.
\image button.png
The push button is perhaps the most commonly used widget in any graphical
user interface. Pushing (or clicking) a button commands the computer to
perform some action or answer a question. Common examples of buttons are
OK, Apply, Cancel, Close, Yes, No, and Help buttons.
\qml
Button {
text: "Button"
}
\endqml
Button is similar to the QPushButton widget.
You can create a custom appearance for a Button by
assigning a \l {ButtonStyle}.
*/
組件功能注釋以“/*!”開始,以“ */” 結(jié)束踢关,包含的對文件功能的說明伞鲫,和控件的使用方法,使用用例以“\qml”開始签舞,以“\endqml”結(jié)束
(2) 自定義屬性秕脓、信號、方法注釋
自定義的屬性儒搭、信號吠架、方法如果僅通過名稱不便于理解其意義,就要添加注釋搂鲫,注釋示例:
/*! This property holds whether the push button is the default button.
Default buttons decide what happens when the user presses enter in a
dialog without giving a button explicit focus. \note This property only
changes the appearance of the button. The expected behavior needs to be
implemented by the user.
The default value is \c false.
*/
property bool isDefault: false
/*! Assign a \l Menu to this property to get a pull-down menu button.
The default value is \c null.
*/
property Menu menu: null
/*!
\qmlmethod TableViewColumn BasicTableView::addColumn(object column)
Adds a \a column and returns the added column.
The \a column argument can be an instance of TableViewColumn,
or a Component. The component has to contain a TableViewColumn.
Otherwise \c null is returned.
*/
function addColumn(column) {
return insertColumn(columnCount, column)
}
/*!
\qmlsignal TextArea::linkHovered(string link)
\since QtQuick.Controls 1.1
This signal is emitted when the user hovers a link embedded in the text.
The link must be in rich text or HTML format and the
\a link string provides access to the particular link.
\sa hoveredLink
The corresponding handler is \c onLinkHovered.
*/
signal linkHovered(string link)
(3) 其他注釋
代碼注釋格式為:“ // xxxxxxxxx ”傍药,寫在被注釋代碼上方,例如:
function syncHandlesWithSelection()
{
if (!blockRecursion && selectionHandle.delegate) {
blockRecursion = true
// We cannot use property selectionPosition since it gets updated after onSelectionStartChanged
cursorHandle.position = cursorPosition
selectionHandle.position = (selectionStart !== cursorPosition) ? selectionStart : selectionEnd
blockRecursion = false
}
ensureVisible(cursorRectangle)
}
4. QML 工程目錄結(jié)構(gòu)規(guī)劃規(guī)則
qml.qrc
|--- main.qml
|--- components ///< 用來放置項目或插件的專有控件
|--- images ///< 用來放置項目或插件的圖片資源
|--- views ///< 用來放置用來放置除專有控件、圖片資源和 main.qml 外的其他文件
六. Qt 官方編碼規(guī)范
1. 對齊處理時需要注意
當(dāng)一個指針被強制轉(zhuǎn)換為目標(biāo)指針類型所需的對齊方式并需要增加位數(shù)時拐辽,轉(zhuǎn)換后的指針可能在某些目標(biāo)機上產(chǎn)生運行時崩潰拣挪。例如,如果一個 const char * 被轉(zhuǎn)換成一個 const int *俱诸,它將在整數(shù)必須在兩個或四個字節(jié)的邊界上對齊的機器上崩潰菠劝。
使用 union 可以強制編譯器正確地對齊變量。在下面的示例中睁搭,可以確保 AlignHelper 的所有實例都在整數(shù)邊界處對齊赶诊。
union AlignHelper {
char c;
int i;
};
2. 全局對象定義的注意事項
任何有構(gòu)造函數(shù)或需要運行特定代碼來初始化的東西都不能在庫代碼中用作全局對象,因為構(gòu)造函數(shù)/代碼將在什么時候運行(第一次使用時园骆、在庫加載時舔痪、在main()之前或根本不運行時)是未定義的。即使初始化器的執(zhí)行時間是為共享庫定義的锌唾,當(dāng)你在插件中移動代碼或者庫是靜態(tài)編譯的時候豪治,你也會遇到麻煩:
// global scope
static const QString x; // Wrong - default constructor needs to be run to initialize x
static const QString y = "Hello"; // Wrong - constructor that takes a const char * has to be run
QString z; // super wrong
static const int i = foo(); // wrong - call time of foo() undefined, might not be called at all
我們可以通過如下方式完成上述定義:
// global scope
static const char x[] = "someText"; // Works - no constructor must be run, x set at compile time
static int y = 7; // Works - y will be set at compile time
static MyStruct s = {1, 2, 3}; // Works - will be initialized statically, no code being run
static QString *ptr = nullptr; // Pointers to objects are ok - no code needed to be run to initialize ptr
3. 使用 Q_GLOBAL_STATIC 和 Q_GLOBAL_STATIC_WITH_ARGS 宏來創(chuàng)建靜態(tài)全局對象
Q_GLOBAL_STATIC(QString, s)
void foo()
{
s()->append("moo");
}
上述代碼在 C++11 之后是不會有作用域問題的仰猖,在首次進(jìn)入作用域欣范,構(gòu)造函數(shù)將運行锌介,上述代碼是可重入的。
4. char 的默認(rèn)情況下是有無符號是平臺相關(guān)的
如果我們確實想要一個 有/無符號的 char渐排,使用 signed char 或 unsigned char 是明智的炬太。在默認(rèn) char 是無符號的平臺上,以下代碼中的條件始終為true:
char c; // c can't be negative if it is unsigned
/********/
/*******/
if (c > 0) { … } // WRONG - condition is always true on platforms where the default is unsigned
5. 避免 64 位 enum 值
- aapcs 將所有 enum 值硬編碼為一個 32 位整數(shù)
- Microsoft 編譯器不支持 64 位 enum 值
6. 使用 enum 來定義常量驯耻,不要使用靜態(tài) const int 或 defines
- enum 值將在編譯時被編譯器替換亲族,從而加快代碼的速度
- defines 不是名稱空間安全的(而且看起來弱爆了)
7. 參數(shù)名不要使用簡寫
- 大多數(shù) ide 會在它們的補全框中顯示參數(shù)名
- 在文檔中看起來更方便
- 錯誤示范:doSomething(QRegion rgn, QPoint p); 趕緊用doSomething(QRegion clientRegion, QPoint gravitySource) 替換掉前面那段垃圾代碼
8. 重新實現(xiàn)一個虛方法時,不要加 'virtual' 關(guān)鍵字了
- 在 Qt5 中可缚,一般使用 override 關(guān)鍵字在函數(shù)聲明之后“;”(或“{”)之前注釋霎迫,來標(biāo)記它們
9. 不從模板/工具類繼承
- 析構(gòu)函數(shù)不是虛擬的,這會導(dǎo)致潛在的內(nèi)存泄漏
- 這些符號沒有被導(dǎo)出(大部分是內(nèi)聯(lián)的)帘靡,可能導(dǎo)致莫名其妙的符號沖突
- 例如: 庫A有:
庫B有:class Q_EXPORT X: public QList<QVariant> {};
突然知给,QList 的符號從兩個庫中導(dǎo)出,(crash...)class Q_EXPORT Y: public QList<QVariant> {};
10. 不要混合使用 const 和非 const 迭代器
這可能導(dǎo)致靜悄悄的崩潰描姚。
for (Container::const_iterator it = c.begin(); it != c.end(); ++it) //W R O N G
for (Container::const_iterator it = c.cbegin(); it != c.cend(); ++it) // Right
11. Q[Core]Application 是一個單例類
一次只能有一個實例涩赢。但是,可以銷毀該實例并創(chuàng)建一個新實例轩勘,這很可能在ActiveQt或瀏覽器插件中實現(xiàn)筒扒。下面這樣的代碼很容易崩潰:
static QObject *obj = nullptr;
if (!obj)
obj = new QObject(QCoreApplication::instance());
如果 QCoreApplication 應(yīng)用程序被銷毀,obj 將是一個懸浮指針绊寻。應(yīng)該使用 將是一個懸浮指針花墩。對靜態(tài)全局對象使用 Q_GLOBAL_STATIC 定義靜態(tài)全局對象悬秉,并使用 qAddPostRoutine 方法來對靜態(tài)全局對象進(jìn)行清理。
12. 二進(jìn)制和源碼兼容性
(1) 定義
- Qt 5.0.0 是一個主要版本冰蘑,Qt 5.1.0 是一個次要版本和泌,Qt 5.1.1 是一個補丁版本
- 向后二進(jìn)制兼容性:鏈接到庫的早期版本的代碼可以繼續(xù)工作
- 正向二進(jìn)制兼容性:鏈接到新版本庫的代碼與舊版本庫兼容
- 源碼兼容性:無需修改即可編譯代碼
在次要版本中保持向后二進(jìn)制兼容性 + 向后源代碼兼容性
在補丁版本中保持向后和向前的二進(jìn)制兼容性 + 向前和向后的源代碼兼容性
- 不要添加/刪除任何公共API (例如全局函數(shù)、公共/受保護/私有方法)
- 不要重新實現(xiàn)方法(即使是內(nèi)聯(lián)方法祠肥,也不要實現(xiàn)受保護/私有方法)
- 參考二進(jìn)制兼容性的變通方法允跑,以保持二進(jìn)制兼容性
關(guān)于二進(jìn)制兼容性的詳細(xì)信息:https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C++
在編寫 QWidget 子類時,重新實現(xiàn) event()搪柑,即使它是空的。這確保 Widget 子類可以在不破壞二進(jìn)制兼容性的情況下得到修復(fù)索烹。
13. 運算符
一個對兩個參數(shù)都一視同仁的二元操作符不應(yīng)該是成員工碾。因為,除了stack overflow 答案中提到的原因外百姓,當(dāng)操作符是成員時渊额,參數(shù)是不相等的。
QLineF 的操作符 == 是一個成員垒拢,就是一個很不幸的例子:
QLineF lineF;
QLine lineN;
if (lineF == lineN) // Ok, lineN 可以隱式轉(zhuǎn)換為 QLineF 類型
if (lineN == lineF) // Error: QLineF 無法轉(zhuǎn)為 QLine旬迹,且 LHS 是成員無法轉(zhuǎn)換
如果操作符 == 是非成員的方式實現(xiàn)的,則轉(zhuǎn)換規(guī)則對兩邊都適用求类。
14. 公共頭文件的約定
我們的公共頭文件必須經(jīng)受住一些用戶的嚴(yán)格設(shè)置奔垦。所有安裝的頭文件必須遵循以下規(guī)則:
- 沒有C風(fēng)格的類型轉(zhuǎn)換
- 不進(jìn)行浮點類型的比較
- 使用 qFuzzyCompare 比較兩個浮點數(shù)是否相等
- 使用 qIsNull 判斷一個浮點數(shù)是否完全等于 0,而不是將浮點數(shù)與 0.0 比較
- 不要在子類中隱藏虛方法
- 如果基類 A 有一個虛的 int val() 方法尸疆,而子類 B 有一個同名的重載 int val(int x) 方法椿猎,那么 A 的 val 函數(shù)就被隱藏了。此時應(yīng)使用 using 關(guān)鍵字使它再次可見:
class B: public A
{
using A::val;
int val(int x);
};
- 避免同名變量
- 舉個例子寿弱,就是要避免這種形式: this->x = x;
- 不要給變量與類中聲明的函數(shù)的參數(shù)相同的名稱
- 在使用預(yù)處理器變量的值之前犯眠,一定要檢查它是否已定義
#if Foo == 0 // W R O N G
#if defined(Foo) && (Foo == 0) // Right
#if Foo - 0 == 0 // 很棒,但不是誰都明白這是要干啥症革,為更好的可讀性筐咧,請使用上面的方法
15. C++ 11 的使用慣例
(1) Lambda 表達(dá)式
我們可以在以下限制下使用 lambda 表達(dá)式:
- 如果使用 lambda 所在類的靜態(tài)函數(shù),這樣就不要使用 lambda 了噪矛,最好改寫下代碼量蕊。例如:
void Foo::something()
{
//...
std::generate(begin, end, []() { return Foo::someStaticFunction(); });
//...
}
我們可以簡單地傳遞函數(shù)指針:
void Foo::something()
{
//...
std::generate(begin, end, &Foo::someStaticFunction);
//...
}
原因是 GCC 4.7 和更早的版本有一個 bug,解決它需要捕獲 this摩疑,但是 Clang 5.0 和更晚的版本會因此產(chǎn)生一個警告:
void Foo::something()
{
// ...
std::generate(begin, end, [this]() { return Foo::someStaticFunction(); });
// warning: lambda capture 'this' is not used [-Wunused-lambda-capture]
...
}
根據(jù)以下規(guī)則格式化 lambda:
- 始終為參數(shù)列表編寫括號危融,即使函數(shù)不接受參數(shù)。
[]() { doSomething(); }
而不是
[]() { doSomething(); }
- 將捕獲列表雷袋、參數(shù)列表吉殃、返回類型和左大括號放在第一行辞居,正文縮進(jìn)到下面幾行,右大括號放在新行蛋勺。
[]() -> bool {
something();
return isSomethingElse();
}
而不是
[]() -> bool { something();
somethingElse(); }
- 將封閉函數(shù)調(diào)用的右括號和分號放在與 lambda 的右括號相同的行上瓦灶。
foo([]() {
something();
});
- 如果在 if 語句中使用 lambda,請在新行中開始 lambda抱完,以避免 lambda 的左大括號和 if 語句的左大括號之間的混淆贼陶。
if (anyOf(fooList,
[](Foo foo) {
return foo.isGreat();
})) {
return;
}
而不是
if (anyOf(fooList, [](Foo foo) {
return foo.isGreat();
})) {
return;
}
- 可選的規(guī)則,將 lambda 完全放在一行中(如果合適的話)巧娱。
foo([]() { return true; });
if (foo([]() { return true; })) {
// ...
}
(2) auto 關(guān)鍵字
可以在以下情況下使用 auto 關(guān)鍵字碉怔。如果猶豫要不要使用,例如如果使用 auto 會降低代碼的可讀性禁添,那么就不要使用 auto撮胧。請記住,代碼被閱讀的次數(shù)要比被書寫的次數(shù)多得多老翘。
- 避免在同一語句中重復(fù)書寫某個類型時
auto something = new MyCustomType;
auto keyEvent = static_cast<QKeyEvent *>(event);
auto myList = QStringList() << QLatin1String("FooThing") << QLatin1String("BarThing");
- 分配迭代器類型時
auto it = myList.const_iterator();