從C++到Qt(舍棄IDE或qmake、cmake等工具的束縛,嘗試通過幾個例子)


Qt 是 C++ 的庫,Qt 在 ansi C++ 的基礎上進行了一點擴展。

但國內似乎比較浮躁,學Qt的很多連基本的C++如何編譯似乎都不太清楚。本文舍棄IDE或qmake、cmake等工具的束縛,嘗試通過幾個例子,一步一步從標准 C++ 的編譯過渡到 Qt 的編譯。

本文涉及的都是最基本的東西,或許可以說,只要你用C++ Qt,不管是通過哪種工具(qmake、cmake、boost.build、qtcreator、vs2008、Eclipse、...),本文的內容都是需要理解的(盡管真正寫程序時,我們都不會直接用C++編譯器來編譯Qt程序)。

如果你對命令行比較恐懼,或許願意先看看我原來整理的這個 GCC新手入門

例子一:簡單的控制台程序


一個很簡單的例子,沒用到Qt擴展:(也就是說,這是一個普通的C++程序)

#include <QtCore/QCoreApplication>
#include <QtCore/QDebug> int main(int argc, char** argv) { QCoreApplication app(argc, argv); qDebug()<<"hello qt!"; app.exec(); } 

我們都知道,編譯一個C++的程序,無非是 編譯預處理,編譯、鏈接

  • 編譯預處理器:頭文件路徑 和 必要的宏
  • 編譯器:一些編譯參數
  • 鏈接器:一些鏈接參數 和 要鏈接的庫

g++

簡單一行命令,即可生成 main.exe (linux下,則生成可執行程序 main)

g++ main.cpp -DQT_CORE_LIB -Ie:\Qt\4.7.0\include -o main -Le:\Qt\4.7.0\lib -lQtCore4

單行命令,很簡單:

  • -I 指定頭文件路徑
  • -L 指定庫文件路徑
  • -l 指定需要鏈接的庫
  • -D 定義必要的宏(其實對這個小程序,這個宏也沒必要用)
  • -o 指定生成的可執行文件名

cl

簡單一行命令,即可生成 main.exe

cl main.cpp -ID:/Qt/4.7.0/include -DQT_CORE_LIB -Femain -link -LIBPATH:D:/Qt/4.7.0/lib QtCore4.lib

依然很簡單

  • -I 頭文件路徑
  • -D 定義必要的宏
  • -Fe 指定可執行程序文件名
  • -link 后面是鏈接器參數
    • -LIBPATH 庫文件路徑

例子二:簡單的GUI程序


這次稍微復雜一點,不是單一的控制台程序,而是一個簡單的GUI程序

  • main.cpp

    #include <QtGui/QApplication>
    #include "widget.h" int main(int argc, char** argv) { QApplication app(argc, argv); Widget w; w.show(); return app.exec(); } 
  • widget.h

    #include <QtGui/QWidget>
    class Widget : public QWidget { public: Widget(QWidget * parent=NULL); }; 
  • widget.cpp

    #include "widget.h"
    
    Widget::Widget(QWidget * parent) :QWidget(parent) { } 

同樣,這個程序未使用Qt的擴展,直接用C++的編譯器編譯:

g++

g++ main.cpp widget.cpp -DQT_CORE_LIB -DQT_GUI_LIB -Ie:\Qt\4.7.0-beta2\include -o main -Le:\Qt\4.7.0-beta2\lib -lQtCore4 -lQtGui4

因為我們使用了QtGui模塊,所以和前面相比:

  • 增加了 -DQT_GUI_LIB 和 -lQtGui4
  • 多了一個文件 widget.cpp

注意: Windows下

如果在非windows平台下,這條命令就可以了。但windows下,你知道的:分console和windows兩個鏈接子系統,而且入口函數分 main 和 WinMain 。

這條命令,編譯出的 main.exe 會彈出控制台。要想不要控制台,則使用下面的命令:

g++ main.cpp widget.cpp -DQT_CORE_LIB -DQT_GUI_LIB -DQT_NEEDS_QMAIN -Ie:\Qt\4.7.0-beta2\include -o main -Le:\Qt\4.7.0-beta2\lib -lQtCore4 -lQtGui4 -lqtmain -Wl,-subsystem,windows

多了兩個選項:

  • qtmain 該庫中一個WinMain 函數,它會調用我們的代碼的main函數。即對編譯器來說:入口函數的名字變了
  • -Wl,-subsystem,windows 你知道的,鏈接windows子系統
  • 對與MinGW來說,此處多了一個宏 QT_NEEDS_QMAIN,這個東西很有意思。在Qt Windows下鏈接子系統與入口函數(終結版) 中我們詳細提到了這個。(在此處,不過你可以忽略它,不會出錯,而且也不會有可感覺到的差異)

cl

同windows下的g++基本一樣,帶控制台:

cl main.cpp widget.cpp -ID:/Qt/4.7.0/include -DQT_CORE_LIB -DQT_GUI_LIB -Femain -link -LIBPATH:D:/Qt/4.7.0/lib QtCore4.lib QtGui4.lib

不帶控制台:

cl main.cpp widget.cpp -ID:/Qt/4.7.0/include -DQT_CORE_LIB -DQT_GUI_LIB -Femain /MD -link -LIBPATH:D:/Qt/4.7.0/lib -subsystem:windows qtmain.lib QtCore4.lib QtGui4.lib

分析同上:指定鏈接子系統,啟用WinMain入口函數

多文件的程序如何管理

直接調用編譯器有什么壞處呢?

  • 參數多啊,每次手動輸入,難免出錯。(例子中我們用的參數已經盡可能少了,可能都還是讓你眼暈了)。
  • 其次呢,很重要的一點,每次只要一個文件修改,所有東西都要重新編譯。

改變這種狀況的辦法,傳統的就是寫 Makefile,然后編譯時只需要輸入 make 就行了,他會判斷哪些文件被改動需要重新編譯。

另外就是VS等一些IDE自己提供的功能。下面簡單看一下本例子對應makefile文件:

mingw32-make的Makefile文件

CPPFLAGS = -DQT_CORE_LIB -DQT_GUI_LIB -Ie:\Qt\4.7.0\include LDFLAGS = -Le:\Qt\4.7.0\lib -lQtCore4 -lQtGui4 -lqtmain -Wl,-subsystem,windows objects = main.o widget.o dest = main $(dest) : $(objects) g++ -o $@ $(objects) $(LDFLAGS) 

nmake的Makefile文件

CPPFLAGS = -ID:/Qt/4.7.0/include -DQT_CORE_LIB -DQT_GUI_LIB -MD LDFLAGS = -LIBPATH:D:/Qt/4.7.0/lib -subsystem:windows qtmain.lib QtCore4.lib QtGui4.lib objects = main.obj widget.obj dest = main.exe $(dest) : $(objects) link $(objects) $(LDFLAGS) 

對此不做介紹,因為Makefile編寫也是一門學問。相當難寫,所有才有qmake、cmake這些工具來幫我們生成Makefile文件

例子三:引入moc


Qt 對 C++ 的擴展主要是3個方面:

  • 元對象系統,包含Q_OBJECT宏的文件(.h, .cpp等)需要 moc 預處理
  • 資源系統,.qrc 文件 需要 rcc 進行預處理
  • 界面系統,.ui 文件 需要 uic 進行預處理

這3者之中,元對象系統最復雜,也是 Qt 程序中重要的。其他兩個你都可以不要,唯獨這個不要就有點不像話了(沒它還叫Qt程序么?像我們前面寫的,只不過是普通的C++程序)

廢話少說,看例子:(修改前面的widget.h,加入Q_OBJECT)

#include <QtGui/QWidget>
class Widget : public QWidget { Q_OBJECT public: Widget(QWidget * parent=NULL); }; 

如何編譯這個程序呢?例子二中的命令還能用嗎?不妨試試:

哇,輸出好豐富啊!

來自 g++ 的問候:

main.o:main.cpp:(.text$_ZN6WidgetD1Ev[Widget::~Widget()]+0xb): undefined reference to `vtable for Widget' main.o:main.cpp:(.text$_ZN6WidgetD1Ev[Widget::~Widget()]+0x15): undefined reference to `vtable for Widget' widget.o:widget.cpp:(.text+0x39): undefined reference to `vtable for Widget' widget.o:widget.cpp:(.text+0x43): undefined reference to `vtable for Widget' 

來自 cl 的問候:

widget.obj : error LNK2001: 無法解析的外部符號 "public: virtual struct QMetaObject const * __thiscall Widget::metaObject(void)const " (?metaObject@Widget@@UBEPBUQMetaObject@@XZ)
widget.obj : error LNK2001: 無法解析的外部符號 "public: virtual void * __thiscall Widget::qt_metacast(char const *)" (?qt_metacast@Widget@@UAEPAXPBD@Z)
widget.obj : error LNK2001: 無法解析的外部符號 "public: virtual int __thiscall Widget::qt_metacall(enum QMetaObject::Call,int,void * *)" (?qt_metacall@Widget@@UAEHW4Call@QMetaObject@@HPAPAX@Z)發生了什么?

添加一個宏后,發生了什么?我們看看編譯器將宏展開后是什么樣子的:

#include <QtGui/QWidget>
class Widget : public QWidget { static const QMetaObject staticMetaObject; virtual const QMetaObject *metaObject() const; virtual void *qt_metacast(const char *); virtual int qt_metacall(QMetaObject::Call, int, void **); ... public: Widget(QWidget * parent=NULL); }; 

一下子多出來這么多函數,而且還沒有函數體,不出錯才怪。如何生成函數體呢?這正是moc所做的:

moc widget.h -o moc_widget.cpp

這樣一來,這些函數都在 moc_widget.cpp 被實現了,只要我們將該文件一塊編譯鏈接就行了

對g++來說,在例子二的基礎上,直接添加一個 moc_widget.cpp 文件,然后一切正常了:

g++ main.cpp widget.cpp moc_widget.cpp -DQT_CORE_LIB -DQT_GUI_LIB -Ie:\Qt\4.7.0-beta2\include -o main -Le:\Qt\4.7.0-beta2\lib -lQtCore4 -lQtGui4

對 cl 編譯器,同樣只要添加一個 moc_widget.cpp 即可。

例子四,rcc和uic


有點糟蹋這個名字了,本節中不講例子(因為 rcc 和 uic 概念比較簡單)

  • 如果我們用了資源,那么需要一個 xxx.qrc 文件,這個文件呢,C++ 編譯器不認識,於是

    rcc xxx.qrc -o qrc_xxx.cpp
    
  • 如果我們用了designer設計的界面 .ui。C++ 編譯器不認識這個文件,於是

    uic xxx.ui -o ui_xxx.h
    

這樣一來,我們得到是就全是 .h 和 .cpp 的文件了,剩下的工作,你知道的,交給 C++ 編譯器就行了。

其他


現在來看這個圖:是不是很簡單了?

From C++ to Qt

2015更新


對Qt5用戶,本文基本適用,只需注意

  • Qt4中gui在Qt5中主要分成了Qt5Gui、Qt5Widget等幾個庫。
Qt4 Qt5
QtCore4 Qt5Core
QtGui4 Qt5Gui Qt5Widget
  • 頭文件 QtGui/QWidget 等需要變成 QtWidgets/QWidget

http://blog.debao.me/zh/2010/11/from-cpp-to-qt/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM