Qt的編程風格與規範

Qt的編程風格與規範

來源: http://blog.csdn.net/qq_35488967/article/details/70055490javascript

參考資料:

  1. https://wiki.qt.io/Qt_Contribution_Guidelines
  2. https://wiki.qt.io/Qt_Coding_Style
  3. https://wiki.qt.io/Coding_Conventions
  4. https://community.kde.org/Policies/Library_Code_Policy
  5. https://wiki.qt.io/UI_Text_Conventions
  6. https://wiki.qt.io/API_Design_Principles
  7. http://doc.qt.io/qt-5/qml-codingconventions.html
  8. https://google.github.io/styleguide/cppguide.html

變量聲明

  • 聲明每個變量都要用獨立的一行
  • 避免短的或無心義的命名
  • 單個字符的變量名只適用於用來計數的臨時變量,由於此時該變量的用途十分明顯
  • 當一個變量被用到時再聲明它
// Wrong int a, b; char *c, *d; // Correct int height; int width; char *nameOfThis; char *nameOfThat;

變量通常命名法

  • 變量名和函數名以小寫字母開頭,開頭以後的部分每一個單詞以大寫字母開頭
  • 避免使用縮寫
// Wrong short Cntr; char ITEM_DELIM = ' '; // Correct short counter; char itemDelimiter = ' ';

變量在Qt中的命名

  • 類名以大寫字母開頭,公開類以Q開頭,緊跟大寫字母;公用函數以q開頭。(此爲Qt內部規範,咱們可不遵照)
  • 首字母縮寫詞出如今命名中,採用駝峯命名法,如QXmlStreamReader,而不是QXMLStreamReader(即只有第一個字母大寫)

空白行與空格的使用

  • 用空行在適當的地方劃分代碼塊
  • 老是隻用一個空行
  • 在關鍵詞和花括號之間老是隻用一個空格符
// Wrong if(foo){ } // Correct if (foo) { }

指針的書寫規範

  • 對於指針或引用,在類型名和或&之間用一個空格,可是在或&和變量名之間沒有空格
char *x; const QString &myString; const char * const y = "hello";

二元操做符

  • 二元操做符的左右都要有空格
  • 二元操做符對待它的兩個參數是一樣對待的,只是在該操做符是類外的操做符
  • 例如QLineF有它本身的==操做符
QLineF lineF;
QLine lineN;

if (lineF == lineN) // Ok, lineN is implicitly converted to QLineF if (lineN == lineF) // Error: QLineF cannot be converted implicitly to QLine, and the LHS is a member so no conversion applies

逗號

  • 逗號左邊沒有空格,逗號右邊有一個空格
#include <QApplication> #include <QMessageBox> int main(int argc, char *argv[]) { QT_REQUIRE_VERSION(argc, argv, "4.0.2") QApplication app(argc, argv); ... return app.exec(); }

分號

  • 分號左邊沒有空格,分號做爲語句的結束符,其右邊通常再也不有內容
struct Point2D { int x; int y; };

井號

  • #號右邊沒有空格
#include <QtGlobal> #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #include <QtWidgets> #else #include <QtGui> #endif

引號

  • 左引號的左邊和右引號的右邊都有一個空格,左引號的右邊和右引號的左邊都沒有空格
  • 若是右引號右邊是又括號的話,它們之間沒有空格
qDebug() << Q_FUNC_INFO << "was called with value1:" << value1 << "value2:" << value2; QT_REQUIRE_VERSION(argc, argv, "4.0.2")

cast

  • cast後無須空格
// Wrong char* blockOfMemory = (char* ) malloc(data.size()); // Correct char *blockOfMemory = reinterpret_cast<char *>(malloc(data.size()));
  • 避免C語言的casts,儘可能用C++的casts (static_cast, const_cast, reinterpret_cast)。 reinterpret_cast 和 C風格的cast用起來都是危險的,但至少 reinterpret_cast 不會把const修飾符去掉。
  • 涉及到QObjects或重構本身的代碼時,不要使用dynamic_cast,而是用qobject_cast,例如在引進一個類型的方法時
  • 用構造函數去cast簡單類型,例如:用int(myFloat)代替(int)myFloat

語句

  • 不要在一行寫多條語句
  • 另起一行寫控制流語句的定義
// Wrong if (foo) bar(); // Correct if (foo) bar();

花括號寫法

  • 使用緊貼括號:左括號和語句的開頭在同一行,若是右括號是緊跟在一個關鍵詞以後的,則右括號和該關鍵詞在同一行
// Wrong if (codec) { } else { } // Correct if (codec) { } else { }
  • 例外:函數的實現和類的聲明中,左括號老是在一行的開頭
static void foo(int g) { qDebug("foo: %i", g); } class Moo { };
  • 當條件語句的執行部分多於一句的時候才使用花括號
// Wrong if (address.isEmpty()) { return false; } for (int i = 0; i < 10; +''i) { qDebug("%i", i); } // Correct if (address.isEmpty()) return false; for (int i = 0; i < 10;i) qDebug("%i", i);

花括號用途

  • 例外1:若是父語句佔有多行,或通過多層封裝,子語句要用到花括號
// Correct if (address.isEmpty() || !isValid() || !codec) { return false; }
  • 例外2:對稱原則:在if-else語句塊中,若是if或else中的一個包含了多行,另外一個爲了對稱性原則,也要用花括號
// Wrong if (address.isEmpty()) qDebug("empty!"); else { qDebug("%s", qPrintable(address)); it; } // Correct if (address.isEmpty()) { qDebug("empty!"); } else { qDebug("%s", qPrintable(address)); it; } // Wrong if (a) … else if (b) … // Correct if (a) { … } else { if (b) … }
  • 當條件語句的執行體是空語句的時候,用一個花括號
// Wrong while (a); // Correct while (a) {}

圓括號

  • 圓括號用來給語句分組
 // Wrong if (a && b || c)  // Correct if ((a && b) || c)  // Wrong a + b & c  // Correct (a + b) & c

Switch 語句

  • case標籤和switch在同一列
  • 每個case語句的末尾都要有一個break語句或return語句,除非因功能須要故意不加或另一個case是緊跟上一個case的。
switch (myEnum) { case Value1: doSomething(); break; case Value2: case Value3: doSomethingElse(); // fall through default: defaultHandling(); break; }

跳轉語句

  • 包括:break, continue, return, and goto
  • 不要在跳轉關鍵詞後邊加else
// Wrong if (thisOrThat) return; else somethingElse(); // Correct if (thisOrThat) return; somethingElse();
  • 例外:若是這段代碼是固有的對稱結構,用else實現視覺上的對稱也是能夠的

換行

  • 每行代碼很少於100個字符;如有必要,用括號括起來
  • 逗號在行尾。操做符在新行的開頭位置,這是由於編輯器過窄的話,操做符在行尾容易看不見
  • 換行時儘可能避免行於行之間看起來良莠不齊
// Wrong if (longExpression + otherLongExpression + otherOtherLongExpression) { } // Correct if (longExpression + otherLongExpression + otherOtherLongExpression) { }

通常例外與Artistic style選項

  • 通常例外:當嚴格遵照一條規範會讓你的代碼看起來很糟糕時,廢棄這條規範
  • 用astyle格式化代碼時的選項
--style=kr --indent=spaces=4 --align-pointer=name --align-reference=name --convert-tabs --attach-namespaces --max-code-length=100 --max-instatement-indent=120 --pad-header --pad-oper
  • 例如,你能夠這樣用以上的代碼
 int foo = some_really_long_function_name(and_another_one_to_drive_the_point_home( first_argument, second_argument, third_arugment));

C++特性

  • 不要使用異常處理
  • 不要使用運行時類型識別
  • 理智地使用模板,不要僅僅由於你會用就去用

Qt源碼中的規範

  • 全部代碼都是ascii,使用者若是不肯定的話,只多是7字節
  • 每個QObject的子類都必須有Q_OBJECT宏,即便這個類沒用到信號或槽。不然qobject_cast將不能使用
  • 在connect語句中,使信號和槽的參數規範化(參看 QMetaObject::normalizedSignature),能夠加快信號/槽的查找速度。可使用qtrepotools/util/normalize規範已有代碼

包含頭文件

  • 用#include
#include <qstring.h> // Qt class #include <new> // STL stuff #include <limits.h> // system stuff
  • 若是你想包含qplatforms.h,把它做爲第一個被包含的頭文件
  • 若是你想包含私有頭文件,要十分當心。使用如下的語法而不用管whatever_p.h屬於哪一個模塊在哪一個文件目錄下
#include <private/whatever_p.h>

編譯器/平臺特定問題

  • 使用三目運算符 ?時要特別當心,若是每次的返回值的類型可能不同的話,一些編譯器會在運行時生成衝突的代碼(此時編譯器甚至不會報錯)
QString s;
 return condition ? s : "nothing"; // crash at runtime - QString vs. const char *
  • 要特別當心對齊問題。不管什麼時候,當一個指針被cast後的對齊數是增長的時候,它均可能會崩潰。例如一個const char 被cast成了const int,當cast以後的數字不得不在2或4個字節之間對齊時,指針就會在機器上崩潰。
  • 使用一個union強迫編譯器正確地對齊變量,示例以下,你能夠肯定AlignHelper中全部的實例都和int邊界對齊了
union AlignHelper { char c; int i; };
  • 任何須要須要執行構造函數或相關代碼進行初始化的實例,都不能用做庫代碼中的全局實例。由於當構造函數或代碼將要運行的時候,該實例尚未被定義(在第一次調用該實例時,在加載庫時,在執行main()以前)
// 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 = 0; // Pointers to objects are ok - no code needed to be run to initialize ptr
  • 用Q_GLOBAL_STATIC定義全局實例
Q_GLOBAL_STATIC(QString, s)

 void foo() { s()->append("moo"); }
  • char型變量是有符號的仍是無符號的取決於它運行環境的架構。若是你明確地想使用一個signed或unsinged char,就使用signed char或unsigned char。如下代碼運行在把char默認爲無符號的平臺上時,其條件判斷恆爲真。
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
  • 避免64位的枚舉值 
    • 嵌入式應用系統二進制接口將全部的枚舉類型的值硬編碼成32位int值
    • 微軟的編譯器不支持64位的枚舉值

編程美學

  • 偏心用枚舉值定義常量而非用const int或defines 
    • 枚舉值會在編譯時被編譯器用實際值替換掉,於是運行時得出結果的速度更快
    • defines不是命名空間安全的(而且看起來很醜)
  • 偏心使用冗長而詳細的參數名
  • 當從新實現一個虛方法時,不要在頭文件中用virtual關鍵字,在Qt5中,用 Q_DECL_OVERRIDE宏在函數聲明以後,分號以前註解它。

避免出現的事

  • 不要繼承模版/工具類 
    • 其析構函數不是虛函數,會致使潛在的內存泄漏
    • 其符號不是導出的(not exported),會致使符號衝突
// 例如:A庫有如下代碼 class Q_EXPORT X: public QList<QVariant> {};
//B庫有如下代碼 class Q_EXPORT Y: public QList<QVariant> {};

這樣,QList的符號就被導出了兩次php

  • 不要把const-iterator和none-const iterator搞混。
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
  • Q[Core]Application 是一個單例類。同一時間只能有一個實例在運行,可是這個實例能夠被銷燬,新的實例將能夠被建立,以下的代碼容易產生崩潰
static QObject *obj = 0; if (!obj) obj = new QObject(QCoreApplication::instance());
  • 當QCoreApplication application被銷燬時,obj成爲了迷途指針(野指針),能夠用 Q_GLOBAL_STATIC 和qAddPostRoutine清理
  • 爲了儘量地支持靜態關鍵詞,避免使用匿名命名空間。編譯單位內的一個靜態名稱能夠保證它是一個內部鏈接。而一個位於匿名命名空間的名稱,C++規定它是一個外部連接。

二進制和源兼容性

  • 定義 
    • Qt 4.0.0是一個主版本,Qt 4.1.0是一個微調版本,Qt 4.1.1是一個補丁版本。
    • 在此以後的版本:代碼連接到以前版本的庫能夠運行
    • 在此以前的版本:代碼連接到一個新版本的庫只對舊版本的庫能工做。
    • 源碼兼容性:源碼在不修改的狀況下進行編譯
  • 微調版本保持向後的二進制兼容性
  • 補丁版本保持向後和向前的二進制兼容性 
    • 不要增長或去掉任何公共API(例如公共的函數,公有/保護/私有的方法)
    • 不要從新實現方法(甚至不要修改內連方法,也不要修改保護/私有方法)
  • 當繼承一個QWidget的子類時,老是要去重寫event(),即便它是空的。這將使你的widget類能夠被操做而不破壞其二進制兼容性
  • 全部從Qt中導出的方法,必須以q或Q開頭。用autotest符號檢測是否存在違反此規則的狀況。(此爲Qt自己要求的規範,咱們不須要嚴格執行。)

命名空間

  • 請記住,Qt中,除了Tests和WibKit,所有都是處在命名空間中的代碼

float值

  • 沒有float值之間的比較 
    • 用qFuzzyCompare去和delta比較其值
    • 用qIsNull去判斷float值是否是二進制0,而不是和0.0比較。
[static] bool qFuzzyCompare(double p1, double p2) // Instead of comparing with 0.0 qFuzzyCompare(0.0,1.0e-200); // This will return false // Compare adding 1 to both values will fix the problem qFuzzyCompare(1 + 0.0, 1 + 1.0e-200); // This will return true

虛方法

  • 不要在子類中隱藏父類的虛方法:假設A類中有個virtual int val()方法,如下代碼是不規範的。
class B: public A { using A::val; int val(int x); };

宏定義

  • 在操做一個預處理器以前,先判斷它是否被定義
#if Foo == 0 // W R O N G #if defined(Foo) && (Foo == 0) // Right #if Foo - 0 == 0 // Clever, are we? Use the one above instead, for better readability

類的成員命名

  • 成員變量通常爲名詞
  • 函數成員通常爲動詞/動詞+名詞,可是當動詞爲get時,get經常省略。當返回值爲Bool型變量時,函數名通常之前綴’is’開頭
public: void setColor(const QColor& c); QColor color() const; void setDirty(bool b); bool isDirty() const; private Q_SLOTS: void slotParentChanged();

定義私有類

//.h文件 class KFooPrivate; class KFoo { public: /* public members */ private: const QScopedPointer<KFooPrivate> d; };
//.cpp文件
class KFooPrivate
{
    public:
        int someInteger;
};

KFoo::KFoo() : d(new KFooPrivate)
{
    /* ... */ } KFoo::~KFoo() { // You must define a non-inline destructor in the .cpp file, even if it is empty // else, a default one will be built in placed where KFooPrivate is only forward // declare, leading to error in the destructor of QScopedPointer }

標記(flags)

  • 避免使用無心義的bool型參數,如下是糟糕的例子
static QString KApplication::makeStdCaption( const QString &caption, bool withAppName, bool modified);
  • 解決方案是用QFlags。即便其中只有一個值,也建議這麼作,這將容許你之後很方便地添加更多的值而且保持二進制兼容性。
  • 示例以下:
class KApplication
{
public: /* [...] */ enum StandardCaptionOption { /** * Indicates to include the application name */ WithApplicationName = 0x01, /** * Note in the caption that there is unsaved data */ Modified = 0x02 }; Q_DECLARE_FLAGS(StandardCaptionOptions, StandardCaptionOption) /** * Builds a caption using a standard layout. * * @param userCaption The caption string you want to display * @param options a set of flags from MakeStandartCaptionOption */ static QString makeStandardCaption(const QString& userCaption, const StandardCaptionOptions& options = WithApplicationName); /* [...] */ }; Q_DECLARE_OPERATORS_FOR_FLAGS(KApplication::StandardCaptionOptions)

常引用

  • 每個對象,只要它不是基礎類型(int, float, bool, enum, or pointers),都應該以常量引用的形式傳遞。這條使得代碼運行得更快。即便一個對象是隱式共享的,也應該這麼作
QString myMethod( const QString& foo, const QPixmap& bar, int number );
  • 避免常引用的返回類型
const QList<int> &someProperty() const;
  • 有種狀況仍是可使用常引用的返回類型的,這種狀況下,此處代碼的運行性能相當重要,此處的代碼實現也是固定的,思考再三以後,你能夠寫成這樣:
QList<int> someProperty() const;

庫代碼中的信號&槽

  • 在庫代碼中,用Q_SIGNALS 和 Q_SLOTS 代替 signals 和 slots。它們在語法上是相等的,用以免和boost信號的衝突。和python協同工做時使用」slots」

屬性

  • 設置屬性時用Q_PROPERTY。理由是屬性能夠被javascript 接口訪問到
  • moc中設置特定的flag用 QMetaProperty.

構造函數

  • 爲了使構造函數被錯誤使用的可能性降到最小,每個構造函數(除了拷貝構函數)都應該檢查本身是否須要加上explicit 符號。

#include

  • 儘可能減小在頭文件中包含其餘頭文件的數量
  • 以下所示,能夠用前置聲明法
#include <kfoobase.h> class KBar; class KFoo : public KFooBase { public: /* [...] */ void myMethod(const KBar &bar); };
  • 包含Qt自帶頭文件或外部庫的頭文件用尖括號
#include <iostream> #include <QDate> #include <zlib.h>
  • 包含本身的項目頭文件用雙引號
#include "myclass.h"
  • 包含Qt類的頭文件不用包含它所在的模塊名
//正確示例 #include <QDate> //correct
//錯誤示例 #include <QtCore/QDate> //incorrect, no need to specify the module QtCore #include <QtCore> //incorrect, do not include the top-level Qt module
  • 假如你有一個Foo類,有Foo.h文件和Foo.cpp文件,在你的Foo.cpp文件中,要先包含Foo.h文件再包含其餘頭文件
  • 若是你的代碼寫成下面這樣:
//.h文件 class Foo { public: Bar getBar(); };
.cpp文件
#include "bar.h" #include "foo.h"
  • 你的cpp文件可以正常編譯,但當其餘人用到Foo.h文件時,若是沒有包含bar.h文件,編譯器將不能進行編譯。
  • 所以在cpp文件中首先包含其相應的.h文件,是爲了.h文件可以被其餘人使用
  • 頭文件必須進行包含保護:避免屢次包含而引發屢次的編譯
#ifndef MYFOO_H #define MYFOO_H ... <stuff>... #endif /* MYFOO_H */

信號&槽的標準化寫法

  • 標準化的寫法增長代碼可讀性
  • 不標準的寫法多是以下寫法
QObject::connect(this, SIGNAL( newValue(const QString&, const MyNamespace::Type&) ), other, SLOT( value(const QString &) ));
  • 建議採用如下寫法
QObject::connect(this, SIGNAL(newValue(QString,MyNamespace::Type)), other, SLOT(value(QString)));

API-最小化原則

  • 最小化的API意味着,每一個API中使用盡量少的類,每一個類中使用盡量少的公用成員(public members)。這樣作的好處是使得API易於理解、記憶、調試和修改

API-完整性原則

  • 一個完整的API意味着實現它預期的功能。這和最小化的特性可能會產生衝突。另外若是一個成員函數出如今一個錯誤的類裏,API的使用者們可能會找不到它

API-有明確和簡單的語意

  • 正如其餘的設計工做同樣,你應當遵照「最小驚奇」原則。通常狀況下,這是容易作到的。請不要用解決方案所解決的問題過於籠統。(例如Qt3中的QMimeSourceFactory,應該被叫作 QImageLoader從而做爲一個不一樣的API)

API-直觀性原則

  • 不一樣的經歷和背景讓人們對什麼具備「直觀性」什麼不具備,有着不一樣的感受。如下狀況咱們能夠認爲一個API是直觀的
  • 一個稍有一些經驗的用戶在不看幫助文檔的狀況下,可以正確地使用該API
  • 一個從不知道該API的用戶可以看懂使用該API寫成的代碼

API-便於記住

  • 選擇一個一致的和準確的命名約定
  • 使用可識別的模式和概念
  • 避免使用縮寫

API-易讀性原則

  • 代碼是一次寫成,但須要屢次閱讀,易讀的代碼可能寫的時候會花費稍長的時間,可是在整個產品的生命週期之中,但節省你不少閱讀和理解的時間
  • 最後,記住不一樣的用戶會用到一個API的不一樣部分。雖然直接使用Qt的類生成一個實例是直觀的,但咱們仍是有理由期待用戶在派生一個Qt的類以前先閱讀它的官方文檔

API-靜態多態性

  • 類似的類應該有類似的API,這能夠用繼承的方式實現,這用到了運行時多態。
  • 可是多態也能夠發生在設計類的時候,例如,你把一個對象的類型從QProgressBar 換成Qslider,或者從QString 換成QByteArray,你會發現它們之間的API是何其的類似,以致於你能夠很簡單地用一個去替換另外一個。這就是咱們所說的「靜態多態性」
  • 「靜態多態性」使得記住這些API和使用編程模式都更爲簡單了。所以,爲一系列相關類設計類似的API好過爲每一個類設計獨立的、更切合自身的API
  • 通常來講,在Qt中咱們偏心使用「靜態多態性」而非實際的繼承,除非一些不可控制的緣由要求咱們不得不如此。

常引用

  • 若是一個類型超過16個字節,用常引用傳遞它。
  • 若是一個類型有一個非平凡拷貝構造函數(non-trivial copy-constructor),或一個非平凡析構函數(non-trivial destructor),用常引用傳遞它的值而避免使用以上方法
  • 全部的其餘類型都應該直接傳遞其值
void setAge(int age); void setCategory(QChar cat); void setName(QLatin1String name); void setAlarm(const QSharedPointer<Alarm> &alarm); // const-ref is much faster than running copy-constructor and destructor // QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect are good examples of other classes you should pass by value.

枚舉類型和枚舉值的命名

  • 如下的示例說明了枚舉值命名時給出通常的名稱的危險
namespace Qt
{
enum Corner { TopLeft, BottomRight, … }; enum CaseSensitivity { Insensitive, Sensitive }; … };
tabWidget->setCornerWidget(widget, Qt::TopLeft); str.indexOf("$(QTDIR)", Qt::Insensitive);
  • 在最後一行,Insensitive 是什麼含義呢?這是不易於理解的。所以,枚舉值命名時,至少重複枚舉類型名中的一個字母
namespace Qt
{
enum Corner { TopLeftCorner, BottomRightCorner, … }; enum CaseSensitivity { CaseInsensitive, CaseSensitive }; … };
tabWidget->setCornerWidget(widget, Qt::TopLeftCorner); str.indexOf("$(QTDIR)", Qt::CaseInsensitive);

企圖少寫代碼的陷阱

  • 不要爲了圖方便少些一些代碼。由於代碼是一次書寫,後期不止一次地要去理解。例如
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
  • 改爲下面的方式會更容易理解
QSlider *slider = new QSlider(Qt::Vertical); slider->setRange(12, 18); slider->setPageStep(3); slider->setValue(13); slider->setObjectName("volume");

Bool型參數陷阱

  • 這方面經典的例子是Qt中的repaint()函數,它的bool型參數用來標記窗口的背景是否被擦除。用法以下:
widget->repaint(false);
  • 上面的代碼很容易被理解成「不從新繪製」
  • 上面代碼的中用到的repaint()函數,其設計時的考慮無非是爲了能夠少定義一個函數,結果反而帶來了誤解,有多少人能夠對如下三行代碼所表明隊含義進行準確地區分呢?
widget->repaint(); widget->repaint(true); widget->repaint(false);
  • 稍微好一點的作法是這樣
widget->repaint(); widget->repaintWithoutErasing();
  • 還有一個很明顯的作法是儘量地用枚舉類型代替bool型參數,請對比如下的兩行代碼
str.replace("USER", user, false); // Qt 3 str.replace("USER", user, Qt::CaseInsensitive); // Qt 4
相關文章
相關標籤/搜索