QT使用教程(五)之程序編寫

第一個Qt小程序

1 最簡單的Qt小程序

<code> 
 

int

main

(

int

argc,

char

*argv[])

{

QApplication

a

(argc, argv)

; Widget w; w.show();

return

a.exec(); } /<code>

解釋:

1、Qt系統提供的標準類名聲明頭文件沒有.h後綴
2、Qt一個類對應一個頭文件,類名就是頭文件名
3、QApplication應用程序類
3.1管理圖形用戶界面應用程序的控制流和主要設置。
3.2是Qt的整個後臺管理的命脈它包含主事件循環,在其中來自窗口系統和其它資源的所有事件處理和調度。它也處理應用程序的初始化和結束,並且提供對話管理。
3.2對於任何一個使用Qt的圖形用戶界面應用程序,都正好存在一個QApplication 對象,而不論這個應用程序在同一時間內是不是有0、1、2或更多個窗口。


4a.exec()
程序進入消息循環,等待對用戶輸入進行響應。這裡main()把控制權轉交給Qt,Qt完成事件處理工作,當應用程序退出的時候exec()的值就會返回。在exec()中,Qt接受並處理用戶和系統的事件並且把它們傳遞給適當的窗口部件。

2按鈕的創建

在Qt程序中,最常用的控件之一就是按鈕了,首先我們來看下如何創建一個按鈕,兩種創建方式如下:

<code>頭文件:    
     
第一種:
    QPushButton * btn = 

new

QPushButton; btn->setParent(

this

); btn->setText(

"德瑪西亞"

); btn->move(

100

,

100

); 第二種: QPushButton * btn2 =

new

QPushButton(

"孫悟空"

,

this

);

this

->resize(

600

,

400

); 設置窗口標題:

this

->setWindowTitle(

"第一個項目"

);

this

->setFixedSize(

600

,

400

); /<code>

上面代碼中,一個按鈕其實就是一個QPushButton類下的對象,如果只是創建出對象,是無法顯示到窗口中的,所以我們需要依賴一個父窗口,也就是指定一個父親利用setParent函數即可,如果想設置按鈕上顯示的文字利用setText,移動按鈕位置用move

對於窗口而言,我們可以修改左上角窗口的標題setWindowTitle,重新指定窗口大小:resize,或者設置固定的窗口大小setFixedSize;

QT使用教程(五)之程序編寫

3 對象模型(對象樹)

在Qt中創建對象的時候會提供一個Parent對象指針,下面來解釋這個parent到底是幹什麼的。

1、QObject是以對象樹的形式組織起來的。
2、當你創建一個QObject對象時,會看到QObject的構造函數接收一個QObject指針作為參數,這個參數就是 parent,也就是父對象指針。
這相當於,在創建QObject對象時,可以提供一個其父對象,我們創建的這個QObject對象會自動添加到其父對象的children()列表。
3、當父對象析構的時候,這個列表中的所有對象也會被析構。(注意,這裡的父對象並不是繼承意義上的父類!)
這種機制在 GUI 程序設計中相當有用。例如,一個按鈕有一個QShortcut(快捷鍵)對象作為其子對象。當我們刪除按鈕的時候,這個快捷鍵理應被刪除。這是合理的。
4、QWidget是能夠在屏幕上顯示的一切組件的父類。
5、QWidget繼承自QObject,因此也繼承了這種對象樹關係。一個孩子自動地成為父組件的一個子組件。因此,它會顯示在父組件的座標系統中,被父組件的邊界剪裁。例如,當用戶關閉一個對話框的時候,應用程序將其刪除,那麼,我們希望屬於這個對話框的按鈕、圖標等應該一起被刪除。事實就是如此,因為這些都是對話框的子組件。


6、當然,我們也可以自己刪除子對象,它們會自動從其父對象列表中刪除。比如,當我們刪除了一個工具欄時,其所在的主窗口會自動將該工具欄從其子對象列表中刪除,並且自動調整屏幕顯示。

Qt 引入對象樹的概念,在一定程度上解決了內存問題。

1、當一個QObject對象在堆上創建的時候,Qt 會同時為其創建一個對象樹。不過,對象樹中對象的順序是沒有定義的。這意味著,銷燬這些對象的順序也是未定義的。
2、任何對象樹中的 QObject對象 delete 的時候,如果這個對象有 parent,則自動將其從 parent 的children()列表中刪除;如果有孩子,則自動 delete 每一個孩子。Qt 保證沒有QObject會被 delete 兩次,這是由析構順序決定的。

如果QObject在棧上創建,Qt 保持同樣的行為。正常情況下,這也不會發生什麼問題。來看下下面的代碼片段:

<code>{
    QWidget 

window

; QPushButton quit(

"Quit"

, &

window

); } /<code>

作為父組件的 window 和作為子組件的 quit 都是QObject的子類(事實上,它們都是QWidget的子類,而QWidget是QObject的子類)。這段代碼是正確的,quit 的析構函數不會被調用兩次,因為標準 C++要求,局部對象的析構順序應該按照其創建順序的相反過程。因此,這段代碼在超出作用域時,會先調用 quit 的析構函數,將其從父對象 window 的子對象列表中刪除,然後才會再調用 window 的析構函數。

但是,如果我們使用下面的代碼:

<code>{
    QPushButton quit(

"Quit"

); QWidget

window

; quit.setParent(&

window

); } /<code>

情況又有所不同,析構順序就有了問題。我們看到,在上面的代碼中,作為父對象的 window 會首先被析構,因為它是最後一個創建的對象。在析構過程中,它會調用子對象列表中每一個對象的析構函數,也就是說, quit 此時就被析構了。然後,代碼繼續執行,在 window 析構之後,quit 也會被析構,因為 quit 也是一個局部變量,在超出作用域的時候當然也需要析構。但是,這時候已經是第二次調用 quit 的析構函數了,C++ 不允許調用兩次析構函數,因此,程序崩潰了。

由此我們看到,Qt 的對象樹機制雖然幫助我們在一定程度上解決了內存問題,但是也引入了一些值得注意的事情。這些細節在今後的開發過程中很可能時不時跳出來煩擾一下,所以,我們最好從開始就養成良好習慣,在 Qt 中,儘量在構造的時候就指定 parent 對象,並且大膽在堆上創建。

4 信號和槽機制

信號槽是 Qt 框架引以為豪的機制之一。所謂信號槽,實際就是觀察者模式。當某個事件發生之後,比如,按鈕檢測到自己被點擊了一下,它就會發出一個信號(signal)。這種發出是沒有目的的,類似廣播。如果有對象對這個信號感興趣,它就會使用連接(connect)函數,意思是,將想要處理的信號和自己的一個函數(稱為槽(slot))綁定來處理這個信號。也就是說,當信號發出時,被連接的槽函數會自動被回調。這就類似觀察者模式:當發生了感興趣的事件,某一個操作就會被自動觸發。

4.1 系統自帶的信號和槽

下面我們完成一個小功能,上面我們已經學習了按鈕的創建,但是還沒有體現出按鈕的功能,按鈕最大的功能也就是點擊後觸發一些事情,比如我們點擊按鈕,就把當前的窗口給關閉掉,那麼在Qt中,這樣的功能如何實現呢?

其實無法兩行代碼就可以搞定了,我們看下面的代碼

<code>  QPushButton * quitBtn = new QPushButton(

"關閉窗口"

,

this

); connect(quitBtn,&QPushButton::clicked,

this

,&MyWidget::close); /<code>

第一行是創建一個關閉按鈕,這個之前已經學過,第二行就是核心了,也就是信號槽的使用方式

connect()函數最常用的一般形式:

<code>

connect

(sender, signal, receiver, slot); /<code>

參數解釋:

sender:發出信號的對象
signal:發送對象發出的信號
receiver:接收信號的對象
slot:接收對象在接收到信號之後所需要調用的函數(槽函數)

那麼系統自帶的信號和槽通常如何查找呢,這個就需要利用幫助文檔了,在幫助文檔中比如我們上面的按鈕的點擊信號,在幫助文檔中輸入QPushButton,首先我們可以在Contents中尋找關鍵字 signals,信號的意思,但是我們發現並沒有找到,這時候我們應該想到也許這個信號的被父類繼承下來的,因此我們去他的父類QAbstractButton中就可以找到該關鍵字,點擊signals索引到系統自帶的信號有如下幾個

QT使用教程(五)之程序編寫

這裡的clicked就是我們要找到,槽函數的尋找方式和信號一樣,只不過他的關鍵字是slot。

程序如下:

QT使用教程(五)之程序編寫


4.2 自定義信號和槽

使用connect()可以讓我們連接系統提供的信號和槽。但是,Qt 的信號槽機制並不僅僅是使用系統提供的那部分,還會允許我們自己設計自己的信號和槽。

下面我們看看使用 Qt 的信號槽:

<code>首先定義一個學生類和老師類:
	老師類中聲明信號 餓了 hungry
signals:
       

void

hungury

()

; 學生類中聲明槽 請客 treat

public

slots:

void

treat

()

; 在窗口中聲明一個公共方法下課,這個方法的調用會觸發老師餓了這個信號,而響應槽函數學生請客

void

MyWidget::ClassIsOver() { emit teacher->hungury(); } 學生響應了槽函數,並且打印信息

void

Student::eat() { qDebug() <

"該吃飯了!"

; } 在窗口中連接信號槽 teacher =

new

Teacher(

this

); student =

new

Student(

this

); connect(teacher,&Teacher::hungury,student,&Student::treat); 並且調用下課函數,測試打印出 “該吃飯了” 自定義的信號 hungry帶參數,需要提供重載的自定義信號和 自定義槽

void

hungury

(QString name)

; 自定義信號

void

treat

(QString name )

; 自定義槽 但是由於有兩個重名的自定義信號和自定義的槽,直接連接會報錯,所以需要利用函數指針來指向函數地址, 然後在做連接

void

(Teacher:: * teacherSingal)(QString) = &Teacher::hungury;

void

(Student:: * studentSlot)(QString) = &Student::treat; connect(teacher,teacherSingal,student,studentSlot); /<code>

自定義信號槽需要注意的事項:


1、發送者和接收者都需要是QObject的子類(當然,槽函數是全局函數、Lambda 表達式等無需接收者的時候除外);
2、信號和槽函數返回值是 void
3、信號只需要聲明,不需要實現
4、槽函數需要聲明也需要實現
5、槽函數是普通的成員函數,作為成員函數,會受到 public、private、protected 的影響;
6、使用 emit 在恰當的位置發送信號;


7、使用connect()函數連接信號和槽。
8、任何成員函數、static 函數、全局函數和 Lambda 表達式都可以作為槽函數
9、信號槽要求信號和槽的參數一致,所謂一致,是參數類型一致。
10、如果信號和槽的參數不一致,允許的情況是,槽函數的參數可以比信號的少,即便如此,槽函數存在的那些參數的順序也必須和信號的前面幾個一致起來。這是因為,你可以在槽函數中選擇忽略信號傳來的數據(也就是槽函數的參數比信號的少)。

4.3信號槽的拓展

1、一個信號可以和多個槽相連
如果是這種情況,這些槽會一個接一個的被調用,但是它們的調用順序是不確定的。
2、多個信號可以連接到一個槽
只要任意一個信號發出,這個槽就會被調用。
3、一個信號可以連接到另外的一個信號
當第一個信號發出時,第二個信號被髮出。除此之外,這種信號-信號的形式和信號-槽的形式沒有什麼區別。
4、槽可以被取消鏈接


這種情況並不經常出現,因為當一個對象delete之後,Qt自動取消所有連接到這個對象上面的槽。
5、信號槽可以斷開
利用disconnect關鍵字是可以斷開信號槽的
6、使用Lambda 表達式
在使用 Qt 5 的時候,能夠支持 Qt 5 的編譯器都是支持 Lambda 表達式的。

在連接信號和槽的時候,槽函數可以使用Lambda表達式的方式進行處理。後面我們會詳細介紹什麼是Lambda表達式

4.4 Qt4版本的信號槽寫法

<code>

connect

(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString))); /<code>

這裡使用了SIGNAL和SLOT這兩個宏,將兩個函數名轉換成了字符串。注意到connect()函數的 signal 和 slot 都是接受字符串,一旦出現連接不成功的情況,Qt4是沒有編譯錯誤的(因為一切都是字符串,編譯期是不檢查字符串是否匹配),而是在運行時給出錯誤。這無疑會增加程序的不穩定性。

Qt5在語法上完全兼容Qt4,而反之是不可以的。

4.5 Lambda表達式

C++11中的Lambda表達式用於定義並創建匿名的函數對象,以簡化編程工作。首先看一下Lambda表達式的基本構成:

<code>[

capture

]() mutable ->return-type { statement } [

函數對象參數

]()mutable ->返回值{函數體} /<code>

① 函數對象參數;

[],標識一個Lambda的開始,這部分必須存在,不能省略。函數對象參數是傳遞給編譯器自動生成的函數對象類的構造函數的。函數對象參數只能使用那些到定義Lambda為止時Lambda所在作用範圍內可見的局部變量(包括Lambda所在類的this)。函數對象參數有以下形式:

1、空。沒有使用任何函數對象參數。
2、=。函數體內可以使用Lambda所在作用範圍內所有可見的局部變量(包括Lambda所在類的this),並且是值傳遞方式(相當於編譯器自動為我們按值傳遞了所有局部變量)。
3、&。函數體內可以使用Lambda所在作用範圍內所有可見的局部變量(包括Lambda所在類的this),並且是引用傳遞方式(相當於編譯器自動為我們按引用傳遞了所有局部變量)。


4、this。函數體內可以使用Lambda所在類中的成員變量。
5、a。將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,因為默認情況下函數是const的。要修改傳遞進來的a的拷貝,可以添加mutable修飾符。
6、&a。將a按引用進行傳遞。
7、a, &b。將a按值進行傳遞,b按引用進行傳遞。
8、=,&a, &b。除a和b按引用進行傳遞外,其他參數都按值進行傳遞。
9、&, a, b。除a和b按值進行傳遞外,其他參數都按引用進行傳遞。

② 操作符重載函數參數;

標識重載的()操作符的參數,沒有參數時,這部分可以省略。參數可以通過按值(如:(a,b))和按引用(如:(&a,&b))兩種方式進行傳遞。

③ 可修改標示符;

mutable聲明,這部分可以省略。按值傳遞函數對象參數時,加上mutable修飾符後,可以修改按值傳遞進來的拷貝(注意是能修改拷貝,而不是值本身)。

<code>    QPushButton * myBtn = 

new

QPushButton (

this

); QPushButton * myBtn2 =

new

QPushButton (

this

); myBtn2->move(

100

,

100

);

int

m =

10

; connect(myBtn,&QPushButton::clicked,

this

,[m] ()

mutable

{ m =

100

+

10

; qDebug() << m; }); connect(myBtn2,&QPushButton::clicked,

this

,[=] () { qDebug() << m; }); qDebug() << m; /<code>

④ 函數返回值;

->返回值類型,標識函數返回值的類型,當返回值為void,或者函數體中只有一處return的地方(此時編譯器可以自動推斷出返回值類型)時,這部分可以省略。

⑤ 是函數體;

{},標識函數的實現,這部分不能省略,但函數體可以為空。


分享到:


相關文章: