如何才能學到Qt的精髓?


被邀請了很久了,一直在思考,今天終於下決心開始寫回答。

這個問題的確是夠大的,Qt的代碼規模在整個開源世界裡也是名列前茅的,這麼大的項目其中的精華是非常多的,很難說得全面,實際上我對Qt也不是完全了解,裡面還有很多我不熟悉的東西。

首先,我想談的是 signal/slot,Qt算是發明了signal/slot,這個思想也被其他一些框架語言借鑒了。

談signal/slot之前先來談談C++的缺欠,這個問題也被討論很多了,這裡只談一點,C++的設計目標是面向對象語言,它不僅提供了對象的定義和構建的方式,也定義了對象間的關係,比如 繼承 派生 聚合,但是它沒有提供對象間通信和共享數據的方式,這個缺點在一般程序的開發上不算個大問題,我們可以自己簡單實現,但是對於GUI開發,這個缺點就被放大了很多倍。GUI上的對象實在太多,窗口是對象,布局是對象,定時器是對象,而且對象間有錯綜複雜的關係,通信和數據交換非常頻繁,比如按鈕按下要通知父窗口或容器對象,滾動條變化了要通知列表對象。這種數量龐大的對象以及複雜的通信關係,可不是自己搞個簡單的實現就能解決的。

說到通信和共享,其實他們是一回事,共享很多時候就是為了通信,而C++里要通信就必然要共享。

比如,一個類實例擁有另一個的指針,就可以訪問對方的數據,調用對方的方法了,這實際就是共享了一個指針,這個類指針也是另一個對象的this。訪問數據和調用方法其實都是通信,把對方的數據拿過來,把自己的數據送過去,交換數據就是通信。

在C++里,由於沒有GC,管理大量原生指針是極其危險的,對象的生命周期不可控,野指針的出現概率會很高,大型C++ 的GUI項目參與開發的人數眾多,很難保證都不犯錯。

那麼用觀察者模式呢?其實也一樣,還是共享了IObserverXXX指針。

那麼發消息行不行呢?比如 MFC那樣,可以,但是本質上還是共享了窗口句柄,否則消息發給誰呢?而且還帶來另外的問題,就是類型安全,消息的參數是無法類型安全的。

Qt作為大型GUI項目的Framework,它必須解決這個問題,否則這個程序是寫不大的,寫大了就會問題層出不窮。

來看一段代碼,看看Qt 的解決方案:

Window::Window()
{
QPushButton *b = new QPushButton(this);
connect(b, SIGNAL(clicked()), SLOT(on_button_clicked()));
}

Window::on_button_clicked()
{
QPushButton *b = qobject_cast&(sender());
b-&>setText("clicked!");
}

這段代碼,通過Qt的signal slot機制,把QPushButton的點擊事件連接到了Window的on_button_clicked響應函數上。

Window 和 QPushButton並沒有互相保存對方指針,QPushButton的指針b 只是個局部變數,用過之後很快銷毀,Window和QPushButton實現了通信,數據共享,事件響應,但是卻沒有共享指針,而且他們不受對方的生命周期影響,無論誰先銷毀,這段代碼都不會出錯。

這種方式還是類型安全的,當signal和slot的類型不匹配的時候 connect是會報錯的。

有人會說,我們用智能指針不就好了。好啊,智能指針你不會自己寫吧,那麼用boost?boost里能創建窗口嗎?不能吧,還是要其他GUI庫的,把兩個異構的Framework撮合到一起也不是輕而易舉的。再說了Qt出來的時候,別說Boost,STL都還沒有呢。

signal/slot為對象間通信提供了非常靈活方便的實現,如果你只關心一個signal那就可以只connect一個,可以多個slot連接同一個signal,也可以一個slot連接到多個signal,Qt會負責管理連接關係和對象生命周期,對象銷毀時會自動斷開連接。

Qt為了實現signal/slot也是付出代價的,在無法改變C++語法的情況下,只能通過moc預編譯器來擴展關鍵字。這大概是獨一無二的實現方式了,後來的signal/slot實現要不用C++ template,或者發明種語言直接做到語法里,比如C# delegate。

最後總結下,Qt的signal/slot是為了解決對象間通信問題,同時避免共享指針造成的內存野指針和對象生命周期問題。

下一個議題,等我想好了再說。。。。

&<待續&>


這麼大的命題,邀請我?太看的起我了、、、

我先佔個坑,等大牛們解答、、

順便先拋個磚:

Qt把C++的特性利用的很給力,你讀Qt源碼就能學習很多C++的技巧。

Qt的moc系統,讓靜態的C++模擬了動態語言的很多特性,例如反射、、

Qt的很多設計思想很給力,那幾種設計模式在Qt中也有很多應用的,而且很優雅、、

Qt的跨平台封裝、、

······

至於你說怎麼學到:

一、先學習理解設計的思路。

二、讀源碼、、

附上我簡單看源碼寫的兩篇博文:

Qt父子對象內存管理實現簡析

Qt信號槽的一些事

Qt線程使用雜談


在回答問題之前,你需要先知道Qt的精髓有哪些,而想要知道Qt的精髓,就應該先知道什麼叫精髓。

首先,把精髓一詞分開來看

  • 精:字面意思是人的精血,我們從小就聽到人有精、氣、神,而且一級比一級高,而精則是氣和神的基礎,所以是組成物質的基本條件。
  • 髓:字面意思就是骨髓,骨髓是人體核心的核心,即精中之精。

現在我們合起來再看,所謂精髓,是指構成物質的最基本條件+最核心部分。而物質之所以區別於其他物質,就是因為他們的精髓不同。

那麼我們回到正題,Qt的精髓,也就是說區別於其它語言的部分是哪裡。

精——構成Qt的最基本條件,你想想Qt中的哪個模塊是其它模塊中的基石,其實在Qt的模塊開頭中已經給出了答案,QtCore。

髓——那麼根據這個髓的定義:精中之精。

想想QtCore中的精是什麼,其實Qt的文檔也已經給出了答案。

看到了嗎,Qt真正的核心,元對象系統、屬性系統、對象模型、對象樹、信號槽。

至於其他的比如事件系統等,在其他框架中都有類似的應用。

那麼如何學到這些精髓,我提供以下幾個建議:

1. 無論你是學Qt,Java,Python或其它,都需要明白一個道理:摒棄掉你的好奇心,千萬不要去追求第三方類或工具是怎麼實現的,這往往會讓你收效甚微,其實,你只需要熟練掌握它的介面,知道類的目的即可,不可犯面向過程的毛病,刨根問底。記住,你的目標是讓其它工具為你服務,你要踩在巨人的肩膀上創造世界。

2. 往死里啃這五大特性,在你的項目中,逐漸的設法加入這些特性,多多練習使用它們,長此以往你會收穫意想不到的效果。

3. 一邊請教別人,一邊多多重構,其實編碼這條路雖然有人給你指路,但真正走下去的是你自己,當你真正走完時,你的編碼水平一定會有非常大的提升。也許別人1000行的代碼,在你這裡幾十行就搞定了,這也正事Qt的魅力。

4. 在閱讀Qt的幫助文檔時,要靜下心來,不要放過每一句,記住在文檔中沒有廢話,尤其是每段的開頭。我來舉個例子吧。下面是文檔中介紹Qt信號的第一句話。

Signals are emitted by an object when its internal state has changed in some way that might be interesting to the object"s client or owner.

我們翻譯過來就是:

當對象的內部狀態以某種方式發生變化時,該對象的客戶或其所有者可能會對此變化感興趣,從而發出信號。

簡單的一句話,它告訴我們非常重要的編程方法,我們來抓幾個關鍵詞來分析

  • 內部變化:一個設計良好的類,如果你認為它的內部某個屬性的變化會對外界有很大影響,那麼你就可以在它改變的時候,發出信號。
  • 對象客戶,所有者:這個告訴了你一般情況下,誰會去關心這樣的變化,你可能會考慮為這些外部者定製槽函數。
  • 感興趣:這個詞翻譯成代碼就是connect,如果外部對變化感興趣,就connect。

通過上面三個關鍵詞,你會發現Qt通過信號槽實現的這個低耦合,高內聚是多麼的令人叫絕。

大概的建議就先寫這麼多吧,希望有用。

順帶,如果有人想要學習Qt的可以關注我的專欄:跟小豆君學Qt。

更多分享請關注我的公眾號:小豆君,只要關注,便可加入小豆君為大家創建的C++Qt交流群,方便討論學習。


這個命題,本身就不是一個可以回答的命題。精髓不如改為優點罷了。

另外,嘴癢,補充一些。

編程的精髓除了,0101,演算法,設計模式外,還有什麼?與其學精髓不如學這些。哦,還有一些Qt中與其它高級語言的不同思想。具體什麼思想,無法言說,原因沒有深究。最後,說了等於白說。哈哈。


曾經看到一個做電路的傢伙(會C語言),在不懂C++的情況下,用Qt寫出了windows應用程序。

這就是精髓吧,想想怎麼做到的?


正好在搞Qt, 也說說看法吧

首先Qt作為一個c++框架, 可以說是包羅萬象, 先貼三張圖看一下最新的5.9.0版有哪些module吧

怎麼樣, 是不是太多了?

接觸的越深, 就越感覺到它的強大, 也能越來越感覺到它的無奈.

回到問題, 我個人覺得精髓的地方:

精髓一: 想來想去, 還是給信號槽吧, 畢竟做GUI的時候這個特性應該說是極大的方便程序員吧

精髓二: 易用性, 得益於它的各種設計以及命名規範,每個類和函數都是顧名思義非常容易理解, 很多做嵌入式並不太懂c++的人看個小demo就能上手做點小界面, 這點不誇張吧

這裡貼個相關博客

設計Qt風格的C++API - zhuyf87 - 博客園

精髓三: 跨平台封裝, 目前已經支持Windows, Linux/arm-linux, Mac等等, 還在繼續完善對Android和ios的支持, Qt是開源的, 意味著你可以去看源碼, 學習他們是如何對特定平台進行封裝的

精髓四: 事件系統, 這個就不多說了吧, 日常開發應該用的很多

精髓五: 規範而詳細的幫助文檔, 這個也不用多說了吧, Qt良心

順便說說Qt無奈的地方吧, 我認為它太過於追求大而全了, 一方面它得益於此, 一方面又極大的受限於此, 這種大而全的東西, 導致了太多的妥協, 它只是大而全而不能精, 很多情況下大家用Qt應該也只是一個妥協吧, 比如開發一個桌面軟體, MFC不易用, win32也不熟, C#又不能跨平台, 那我就勉強用一下Qt吧.....


Qt粉絲…

Qt5已經發布了好幾年了,不過工作中一直在用Qt4,最近有空可能升級到Qt5。

Qt模塊眾多,那麼怎麼才能去學習它。

我覺得可以從基礎模塊開始看,首先核心就是QtCore。

1. 命名

如果說Qt最讓人影響深刻的,就是命名了,清晰簡潔,統一規範,老實說做到這個真的太不容易了。

2. 生命期管理

Qt是C++寫的,C++程序對象和資源的生命期管理可是個麻煩事

Qt主要是用推薦對象樹,我還是很喜歡這個方法的,尤其Gui程序中十分好用。

同時用了COW,弱對象指針等。

3. 元對象系統

元對象系統的核心是MOC,元對象編譯器,它對C++本身實現了擴展。

  • MOC

Qt的元對象系統實現了對類型和方法的反射,對註冊過的對象實現了信號槽,INVOKABLE 方法,動態屬性系統等。這也是能夠用XML或QML來寫Gui界面的基礎。

基本方法是對Q_OBJECT 宏標記的類,編譯時用MOC掃描生成代碼文件 ,裡面實現反射的方法的表。 編譯Qt程序看看moc_xxx.cpp文件就可以看到它生成的代碼。

這一塊其實我們自己也可以實現。比如自己插入一個Map&保存對象的成員函數表,Map&保存屬性, 然後再通過一些宏,來實現將屬性和方法保存到Map里。

  • signal slot

Qt發明用來做對象間通信的方法,在C#里叫Event Delegate。

Qt實現signal slot也是基於其元對象系統,MOC生成代碼文件,signal實現成函數。

但signal本身是一種抽象的概念,可以表面對象的狀態發聲改變,slot是用來接收這個信息的介面。

signal slot相比於使用回調指針,更加低耦合,不用保存對方指針,Qt還實現了自動斷開連接。

signal slot的實現本身也不複雜,藉助C++11, 自己實現一個簡單的例子看看基本原理。

namespace signal_slot_
{

template&
struct arg_traits
{
typedef const T param_type;
};

template&
struct arg_traits&
{
typedef T param_type;
};

template&
struct arg_traits&
{
typedef const T param_type;
};

template&
struct arg_traits&
{
typedef T param_type;
};

template&
using arg_traits_t = typename arg_traits& ::param_type;

class connection_context_base
{
std::atomic& connected_ = true;
public:
virtual ~connection_context_base(){}
virtual void disconnectSelf() = 0;

bool isConnected()
{
return connected_;
}

void disconnect()
{
disconnectSelf();
connected_ = false;
}

void disconnected()
{
connected_ = false;
}

};

class signal_base
{
public:
virtual ~signal_base(){}
virtual void removeConnection(connection_context_base* conn) = 0;
};

template&
class signal;

template&
class connection_context;

template&
class connection_context& : public connection_context_base
{
std::function& slot_fun_;
signal_base* signal_;
public:
connection_context(signal_base* signal, std::function& slot_fun)
: signal_(signal), slot_fun_(std::move(slot_fun))
{

}

virtual void disconnectSelf()
{
signal_-&>removeConnection(this);
}

void invoke(A... args)
{
if (isConnected())
{
slot_fun_(std::forward&(args)...);
}
}
};

class connection
{
protected:
std::weak_ptr& context_;
public:
connection(const std::weak_ptr& d)
:context_(std::weak_ptr&(d))
{

}

void disconnect()
{
if (auto context = context_.lock())
{
context-&>disconnect();
}
}
};

template&
class signal& : public signal_base
{
std::list&&>&> connections_;

connection add(const std::shared_ptr&&> conn)
{
connections_.emplace_back(conn);
return connection(conn);
}

void removeConnection(connection_context_base* conn) override
{
auto it = connections_.begin();
for (; it != connections_.end();)
{
if ((*it).get() == conn)
{
auto conn = *it;
conn-&>disconnected();
connections_.erase(it);
return;
}
else
{
++it;
}
}
}
public:
signal() = default;

connection connect(std::function& slot_func)
{
return add(std::make_shared&&>(this, std::move(slot_func)));
}

void operator()(A... args)
{
(*this).emit(std::forward&(args)...);
}

void emit(arg_traits_t&... args)
{
auto it = connections_.begin();
for (; it != connections_.end();)
{
auto next = it;
++next;
(*it)-&>invoke(arg_traits_t&(args)...);
it = next;
}
}
};
}

Qt自己的signal slot相比於boost或其它的實現,跟自己的框架結合更緊密,可以有Queue Connection來實現跨線程(同線程也可以非同步,默認Auto Connection會自動判斷線程)的非同步連接。

注意的是signal slot實現的是對象間通信,如果兩個對象在邏輯上關係非常遠,可以使用其他的消息訂閱機制。

4. 驅動程序的核心,事件循環QEventloop

事件循環/消息循環,是程序運行核心,有一個循環,不斷獲取系統事件或自定義事件,然後進行分發處理。

QEventLoop loop;
loop.exec();

Qt封裝不同平台自己的native事件處理,同時自己擁有一套QEvent事件心態,在loop.exec()處理線程中的各種事件。這一點各類Gui庫/框架大同小異。

5. QThread

Qt的QThread應該和QEventloop結合起來看。


我真不覺得Qt有什麼好的(儘管我現在還得用這玩意),比如說:

  • Qt這個東西簡直就是大逆不道,擴展了C++的語法,非常的噁心。如果一個類繼承QObject,那麼這個類的實現就只能寫在一個文件裡面(想想一個稍微大一點的類的實現寫在一個文件裡面是個什麼樣子)。
  • Qt這個東西有signal和slot,很多人都覺得這個是如何如何的創新,但是使用回調函數不一樣嗎?
  • Qt這個東西根本就他媽的不是一個庫,Y根本就是一個框架。知道什麼叫框架嗎?框架就是它玩你!!!它玩你!!!它玩你!!!
  • Qt這個東西的Socket還有專門的伺服器端的class對吧,不過誰會用Qt搞伺服器端編程呢???


Qt的庫很龐大,並且使用本來特性就包羅萬象的C++,不能貪多只能慢慢來。

Qt的signal/slot機制算是很重要的一個環節。

除此之外我講一個signal/slot與QEventLoop結合的小技巧,直接貼代碼:

QNetworkAccessManager m;
QNetworkRequest req(QUrl("http://www.google.com"));
QNetworkReply* rep = m.get(req);
QEventLoop l;
QObject::connect(rep, SIGNAL(finished()), l, SLOT(quit()));
l.exec();

上面的代碼可以實現使用get方法獲取頁面內容的同時又不阻塞界面UI。

是不是挺好玩兒的?


作為一個從Qt2.2商業版,一直用到現在Qt5.5.1商業版的Qter。很想來回答這個問題。

很多人可能先聊到signalslot,如果是4.8之前可以這樣說,但是現在很多軟體庫都有類似的機制,如boost,而且Qt也在變化,5.0之後的信號機制已經和4.8有點不一樣,只是Qt在力推QML,所以大家已經不大關注Qt c++部分的實現機制。

我覺得Qt的精華主要有3點

A1:跨平台

使用Qt,最大的優勢就是跨平台,Qt的跨平台經過無數商業工程項目的驗證,我們無需擔心技術可行性和一堆移植的坑,只要懂c++就行。

跨平台可以降低軟體團隊的開發成本,也就是可以說服領導,為什麼需要使用一個在國內小眾的開發庫。但是跨平台也是有成本的,前提是需要一個,懂得這幾個平台開發的人員,如果工程師連linux命令都不是很了解,你要求寫跨win和linux的代碼,這個是有點壓力的。

當然公司實力強,可以同時維護不同的原生開發環境和平台。

A2:嵌入式圖形開發:

Qt嵌入式圖形在32位的嵌入式控制器項目上,大家其實已經沒有任何多的選擇,如果成本不是很敏感,擁抱Qt吧。只有Qt可以支持你未來的成長和應用,你的技術投資將一直被有價值的重用,這個不管是對企業或是個人都是有巨大好處。Qt通過wayland和lite 大大簡化了移值的成本。同時,不單linux,幾個大的嵌入式操作系統都有porting好的Qt,不用當心porting的坑。

A3:

Qt的example,用Qt的一般都是C++工程師,所以大家學習精華的地方,就看它的example,當在想開始一段Qt代碼和功能的時候,可以看看是不是已經有demo了,尤其是新手,照著它的例子改寫,你會明白面向對象,抽象,設計模式,這些我們之前經常聽到的文字,其實Qt有很好的例子給你參考。

以上就是我學習到的Qt精華。現在c++不是很流行,因為開發或是說產出比太低,所以大家不願意搞了,還好有Qt,讓大家有個c++工具,寫圖形界面不至於太累。


我想說 一個 Windows 下的 bug 三年了沒改還 close 了。。。

https://bugreports.qt.io/browse/QTBUG-23822

注意確保在 windows 下不要用 QMediaplayer

等我學到精髓的時候我會嘗試著修正的。。。


其實QT很好用啊,感覺比vc要好用的多呢


qt 最重要的一點, 就是它的MVC 開發模式, 這讓很多人的入行變得更為簡單,,

因為你只要寫好你所要的類就行了, 其它的你管不了, 也不用管,


弄過一陣子qt,收穫最大的應該是知道了圖形程序的事件驅動架構(eventloop)吧,當然信號插槽機制也挺好用,相當於自動管理了註冊機制,如果沒有這個玩意,則需要自己管理一堆註冊回調比較麻煩,其他倒沒覺得有啥精髓,看過一小部分源碼,裡面有些宏用的挺靈活可以借鑒一下,還有就是qt的容器不錯,感覺比stl易用。


多做項目,練好基礎功。


以上回答的人個人感覺沒有幾個是精通的。。。反映了知乎的日漸沒落。。

----------

好吧我確實比較懶,有人回復我,我也不想說的太具體。。我所說的沒幾個精通的,是我的片面之詞,現在想想很不負責任,其實大家也跟我一樣懶而已,不願意多說。。。

簡單的說一下,就是,其實qt沒什麼好精髓不精髓的,這個世界上所有的框架個人覺得也沒什麼精髓不精髓的。

要做到自己還比較懂qt,你可以嘗試從根部寫一個類似於qt的界面庫試試看,從封裝底層的繪圖介面,到實現跨平台特性,當然qt不只是界面庫這麼簡單,還有網路庫,文件讀寫,各種編碼支持,webkit,等等,你要是全弄懂了其實就是個大牛了,你已經超越了qt。可能這才算是某種意義上的精通。

如果只是想要使用方面的精通,你只需要學一學那些基本的教程,學會怎麼用qss,怎麼用信號槽,怎麼使用拖拽,等等,還是比較容易精通的。。

好吧,說的很亂。。。歡迎拍磚。。


qt 的精髓在於開源,Windows的標準控制項不是開源的,無法知道gui控制項具體是如何實現的,擴展不開源的gui就像帶著僚拷跳舞,修改,解決bug也麻煩,所以大部分gui庫只是封裝而已,qt基本上是全是自己的一套,並且完全開源,提供了一個絕好機會窺視gui具體實現。


推薦閱讀:

C++20有望實現自動PIMPL嗎?
如何評價 Herb Sutter 的 C++ 提案:metaclasses?
如何評價c++的協程庫libgo?
為什麼一般要用#include「xxx.h」?
模塊相比於 #include 指令的優點是什麼?

TAG:圖形用戶界面 | QtC開發框架 | C |