Qt4中的訊號槽
Qt4中的訊號槽是通過SIGNAL,SLOT
兩個巨集,將引數轉換成字串.Qt編譯前,會從原始碼的標頭檔案中提取由signal
和slot
宣告的訊號和槽的函式,
將其組成一張訊號和槽對應的字串表.connect
函式的作用是,將訊號關聯的槽字串,同這張表的資訊進行對比.這樣訊號發出的時候,就可以知道呼叫哪一個槽函式了.
Qt4訊號槽的不足
- 沒有編譯期的檢查:Qt4中的訊號槽會被巨集轉化成字串處理,而字串的比較機制是在程式執行的時候檢測的.而且,轉換成字串後,訊號槽的引數資料型別就會丟失.這就導致,有的時候,訊號槽在編譯的時候沒有問題,在執行的時候,反而出錯.
- 無法使用相容型別的引數:因為訊號槽的機制使用的是字串的匹配的方法,所以,槽函式的引數型別的名字,必須和訊號引數型別的名字一致,同時,還必須和標頭檔案中宣告的型別名字一致,也就是字串意義上的嚴格相同.如果使用了
typdef
或者namespace
這樣的型別,雖然實際的型別是一樣的,但是由於字串的名字不一樣,所以Qt4中是會有錯誤的.如下虛擬碼示例(實際型別都是int,但因為按照字串處理,所以Qt4中,編譯前不能通
1 2 3 4 5 6 |
//head.h file typedef int MyInt; typedef int BigInt; //head.cpp file connect(Sender,SIGNAL(sigFun(MyInt)),Receiver,SLOT(sltFun(BigInt))); |
Qt5中的訊號槽
Qt5中不僅解決了上述Qt4中的問題,而且還有一些擴充.
- 支援編譯期的檢查:拼寫錯誤,槽函式引數個數大於訊號引數的個數等;
- 支援相容型別的自動轉換;
- 槽允許連線到任意的函式:Qt5中,因為槽使用的是函式指標,所以槽的呼叫,可以是任意的成員函式,靜態函式,還可以是C++11 的lambda表示式;Qt4中槽的宣告一般是
private slots
,private
是私有限制,只有把槽函式當作普通函式使用的時候,才會體現私有的性質.而SLOT
,把槽函式轉化成了字串,此時private
是不起作用的.Qt5中,因為使用的是函式指標,所以在類的外部,connect
是無法關聯一個類的私有槽的,否則,編譯的時候就會報錯.
總之,Qt5中,增加了訊號槽的靈活性,加強了訊號槽的檢測性.
Qt5訊號槽的語法例子
常用用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//ClassA.h signal: void sigClassA(int num); void sigStringChanged(QString str); //ClassB.h void sltClassB(int num);//任意的成員函式,靜態函式都闊以 void sltStringChanged(QVariant str); //ClassB.cpp connect(Sender, &ClassA::sigClassA, this, &ClassB::sltClassB);//函式指標關聯的時候,不需要指明引數,而且this可以省略[**Update:2016_11_20,注意在這種情況下,可以省略, 在其他的情況就一定了.省略會有錯誤危險的.**] connect(Sender, &ClassA::sigClassA, &ClassB::quit);//省略了this,同時靜態函式quit作為槽 //QString 可以轉化成 QVariant connect(Sender, &ClassA::sigStringChanged, &ClassB::sltStringChanged);//訊號槽的引數型別可以發生隱士型別轉化即可 |
訊號槽的過載
解決方法:
- 使用Qt4的方法(不再介紹)
- Qt5顯示轉換函式指標
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//訊號的過載和槽的過載都是一樣的解決機制 //ClassA.h signal: void sigClassA(); void sigClassA(int num); //ClassB.h void sltClassB(); void sltClassB(int num); //ClassB.cpp connect(Sender, static_cast<void(ClassA::\*)()>(&ClassA::sigClassA),//注意\*為markdown轉義 this,static_cast<void(ClassA::\*)()>(&ClassB::sltClassB) ); connect(Sender, static_cast<void(ClassA::\*)(int)>(&ClassA::sigClassA), this,static_cast<void(ClassA::\*)()>(&ClassB::sltClassB) ); connect(Sender, static_cast<void(ClassA::\*)(int)>(&ClassA::sigClassA), this,static_cast<void(ClassA::\*)(int)>(&ClassB::sltClassB) ); |
帶預設數值的槽函式
解決方法:
- 進一步的封裝函式(不做介紹)
- 採用Qt5的C++11 lambda表示式(表示式規則暫且不做詳細介紹)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//ClassA.h signal: void sigClassA(); void sigClassA(int num); //ClassB.h void sltClassB(int num = 10); //ClassB.cpp connect(Sender, static_cast<void(ClassA::\*)(int)>(&ClassA::sigClassA),//注意\*為markdown轉義 this,static_cast<void(ClassA::\*)(int)>(&ClassB::sltClassB) );//訊號和槽的引數個數對應,是可以的 connect(Sender, static_cast<void(ClassA::\*)()>(&ClassA::sigClassA), this,static_cast<void(ClassA::\*)()>(&ClassB::sltClassB) );//槽的引數,比訊號多,這個會報錯誤的 //函式引數的預設數值,只有在函式呼叫的時候,才會有效,取函式地址的時候,是看不到引數的預設數值的.函式指標並不包含預設數值.又因為槽包含預設數值,所以訊號可以不提供引數.那麼,這就和訊號的引數個必須大於槽的引數的個數產生了矛盾. connect(Sender, static_cast<void(ClassA::\*)()>(&ClassA::sigClassA), this,[=](int num = 10){//使用了lambda表示式 //...函式體 }] ); |
[update:2016_11_20]
思考this的省略?
前面提到過connect函式的第三個引數this指標是可以省略的.但是在某些情況下this是絕不可以省略的.甚至我建議大家為了避免不必要的錯誤, this指標最好不要省略, 還是帶上比較好.connect函式基本是如下的原型:
1 2 3 4 |
connect( 傳送者, 傳送者訊號, 接收者, 接收者處理方法 ); ///< 一般的四個引數 connect( 傳送者, 傳送者訊號, **傳送者**處理方法 ); ///< 如果省略this, 是三個引數, 那麼最後一個引數的意義就發生了變化. 此時呼叫的方法則是傳送者自己的方法. ///< 試想一下, 如果此時傳送者和接收者又剛好擁有同樣的函式名字, 但是內部的方法不同, 那麼最後的結果就會讓人莫名奇妙的詭異起來. |
所以,一定要明確的區分每個引數的具體意義, 馬馬虎虎最終還是自己填坑.
你也看到connect是可以使用C++的匿名函式的, 也是可以省略this的,但是, 這一步一定要小心了. 尤其是當你在使用執行緒的時候, 在接收執行緒訊號的時候, 一萬個小心.比如:
1 2 3 4 5 6 7 8 9 10 11 12 |
connect(pThread, &QThread::finished, [=]() { myFun(); } ); ///< 當執行緒執行完後, 你會驚奇的接收到應用程式的崩潰. 基本的提示內容, 就是, 你的某個執行緒出了問題. ///< 就上面的問題,你可以在多個地方, 把執行緒的ID列印出來就知道了. qDebug() << "ThreadId1:"; connect(pThread, &QThread::finished, [=]() { qDebug() << "ThreadId2:"; myFun(); } ); ///< 列印出來以後, 你會發現lambda表示式函式裡面ID和執行緒run函式裡面的id是一樣的. 雖然說, 程式碼在不同的類裡面, 不同的檔案裡面. 可是執行環境, 執行的執行緒卻是可以在一起的. 解決方法, 加個this, 就可以了. 你懂的. |