QT信號槽機制的優缺點
(1)問題:
為什么Qt使用信號與槽機制而不是傳統的回調函數機制進行對象間的通信呢?
回調函數的本質是“你想讓別人的代碼執行你的代碼,而別人的代碼你又不能動”這種需求下產生的。
回調函數是函數指針的一種用法,如果多個類都關注某個類的狀態變化,此時需要維護一個列表,以存放多個回調函數的地址。對於每一個被關注的類,都需要做類似的工作,因此這種做法效率低,不靈活。
(2)解決辦法:
Qt使用信號與槽機制來解決這個問題,程序員只需要指定一個類含有哪些信號函數、哪些槽函數,Qt會處理信號函數和槽函數之間的綁定。當信號函數被調用時,Qt會找到並執行與其綁定的槽函數。允許一個信號函數和多個槽函數綁定,Qt會依次找到並執行與一個信號函數綁定的所有槽函數,這種處理方式更靈活。
(3)優點:
Qt信號與槽機制降低了Qt對象的耦合度.
相關資料:https://blog.csdn.net/QIJINGBO123/article/details/86155060
多線程情況下, Qt中的信號槽分別在什么線程中執行, 如何控制?
通過connect函數的第五個參數connectType來控制。
connect用於連接qt的信號和槽,在qt編程過程中不可或缺。它其實有第五個參數,只是一般使用默認值,在滿足某些特殊需求的時候可能需要手動設置。
Qt::AutoConnection: 默認值,使用這個值則連接類型會在信號發送時決定。如果接收者和發送者在同一個線程,則自動使用Qt::DirectConnection類型。如果接收者和發送者不在一個線程,則自動使用Qt::QueuedConnection類型。
Qt::DirectConnection:槽函數會在信號發送的時候直接被調用,槽函數運行於信號發送者所在線程。效果看上去就像是直接在信號發送位置調用了槽函數。這個在多線程環境下比較危險,可能會造成奔潰。
Qt::QueuedConnection:槽函數在控制回到接收者所在線程的事件循環時被調用,槽函數運行於信號接收者所在線程。發送信號之后,槽函數不會立刻被調用,等到接收者的當前函數執行完,進入事件循環之后,槽函數才會被調用。多線程環境下一般用這個。
Qt::BlockingQueuedConnection:槽函數的調用時機與Qt::QueuedConnection一致,不過發送完信號后發送者所在線程會阻塞,直到槽函數運行完。接收者和發送者絕對不能在一個線程,否則程序會死鎖。在多線程間需要同步的場合可能需要這個。
Qt::UniqueConnection:這個flag可以通過按位或(|)與以上四個結合在一起使用。當這個flag設置時,當某個信號和槽已經連接時,再進行重復的連接就會失敗。也就是避免了重復連接。
相關資料:https://blog.csdn.net/QIJINGBO123/article/details/86155060
Qt 信號槽機制
自定義信號槽注意事項:
(1)發送者和接收者都需要是QObject的子類(當然,槽函數是全局函數、Lambda 表達式等無需接收者的時候除外);
(2)使用 signals 標記信號函數,信號是一個函數聲明,返回 void,不需要實現函數代碼;
(3)槽函數是普通的成員函數,作為成員函數,會受到 public、private、protected 的影響;
(4)使用 emit 在恰當的位置發送信號;
(5)使用QObject::connect()函數連接信號和槽;
(6)任何成員函數、static 函數、全局函數和 Lambda 表達式都可以作為槽函數。
信號槽的多種用法:
(1)一個信號可以和多個槽相連
如果是這種情況,這些槽會一個接一個的被調用,但是它們的調用順序是不確定的。
(2)多個信號可以連接到一個槽
只要任意一個信號發出,這個槽就會被調用。
(3)一個信號可以連接到另外的一個信號
當第一個信號發出時,第二個信號被發出。除此之外,這種信號-信號的形式和信號-槽的形式沒有什么區別。
(4)槽可以被取消鏈接
這種情況並不經常出現,因為當一個對象delete之后,Qt自動取消所有連接到這個對象上面的槽。
(5)使用Lambda 表達式
在使用 Qt 5 的時候,能夠支持 Qt 5 的編譯器都是支持 Lambda 表達式的。
相關資料:https://blog.csdn.net/QIJINGBO123/article/details/86155060
繼承與派生的區別
1、角度不同
繼承是從子類的角度講的,派生是從基類的角度講的。
2、定義不同
派生指江河的源頭產生出支流。引申為從一個主要事物的發展中分化出來。繼承 是面向對象軟件技術當中的一個概念,與多態、抽象共為面向對象的三個基本特征。 繼承可以使得子類具有父類的屬性和方法或者重新定義、追加屬性和方法等。
https://blog.csdn.net/weixin_42325069/article/details/84105347
單繼承和多繼承
單繼承(派生類只從一個直接基類繼承)時派生類的定義:
class 派生類名:繼承方式 基類名
{
新增成員聲明;
}
多繼承時派生類的定義:
class 派生類名:繼承方式1 基類名1,繼承方式2 基類名2,…
{
成員聲明;
}
注意:每一個“繼承方式”,只用於限制對緊隨其后之基類的繼承。
https://blog.csdn.net/weixin_42325069/article/details/84105347
三種繼承方式:公有繼承,私有繼承和保護繼承
公有繼承(public)
1)繼承的訪問控制
基類的public和protected成員:訪問屬性在派生類中保持不變;
基類的private成員:不可直接訪問。
2)訪問權限
派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
通過派生類的對象:只能訪問public成員。
3)公有派生類對象可以被當作基類的對象使用,反之則不可。
派生類的對象可以隱含轉換為基類對象;
派生類的對象可以初始化基類的引用;
派生類的指針可以隱含轉換為基類的指針。
通過基類對象名、指針只能使用從基類繼承的成員,派生類新增的成員就不能使用了。
https://blog.csdn.net/weixin_42325069/article/details/84105347
Qt4與Qt5的三個區別
1.新增widgets模塊
在Qt4中,Qt提供的全部圖形界面相關類都包含在Qt Gui模塊中,但QT5將一些圖形界面類移到了QT widgets模塊中。所以在Pro文件中,需要增加一句話:
greaterThan(QT_MAJOR_VERSION, 4):QT += widgets
意思是如果Qt版本大於Qt4,則需要增加widgets模塊。
2.信號與槽的語法
Qt4中關聯信號與槽一般這樣寫:
connect(sender, SINGAL(valueChanged(QString, QString)), receiver, SLOT(showValue(QString)));
但這樣寫,沒有編譯器檢查,有時編譯器通過但應該調用的槽函數沒有執行。這是編譯器不能給出錯誤信息,只能在運行時看是否有警告。
Qt5中關聯信號與槽是這樣寫:
connect(sender, &Sender::valueChanged, receiver, &Receiver::showValue);
這種寫法支持編譯器檢查,能夠在編譯時就發現錯誤;並支持類型的隱式轉換。
3.對C++11的支持
Qt5支持C++11,但有些編譯器默認不開啟。所以需要在Pro文件中增加一行:
CONFIG += c++11
https://blog.csdn.net/c1n2k3000/article/details/97620480
多態
多態:同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。
C++中,實現多態有以下方法:虛函數,抽象類,覆蓋,模板(重載和多態無關)。
C++ 類(純虛函數和抽象類)
a. 純虛函數是一個在基類中只有聲明的虛函數,在基類中無定義。要求在任何派生類中都定義自己的版本;
b. 純虛函數為各派生類提供一個公共界面(接口的封裝和設計,軟件的模塊功能划分);
c. 純虛函數聲明形式:
virtual void func()=0; //純虛函數
d. 一個具有純虛函數的類稱為抽象類。
結論:
(1). 抽象類對象不能做函數參數,不能創建對象,不能作為函數返回類型;
(2).可以聲明抽象類指針,可以聲明抽象類的引用;
(3). 子類必須繼承父類的純虛函數才能創建對象。
https://blog.csdn.net/wue1206/article/details/81283280
QMainForm是從哪里派生的?
QMainWindow::QWidget::QObject
Qwidget、Qobejct實現了哪些功能
QObject
- 信號和槽的非常強大的機制,使用connect()把信號和槽連接起來並且可以用disconnect()來破壞這種連接。為了避免從不結束的通知循環,你可以調用blockSignals()臨時地阻塞信號。保護函數connectNotify()和disconnectNotify()使跟蹤連接成為可能。
- QObject可以通過event()接收事件並且過濾其它對象的事件。詳細情況請參考installEventFilter()和eventFilter()。一個方便的處理者,childEvent(),能夠被重新實現來捕獲子對象事件。
- 最后但不是最不重要的一點,QObject提供了Qt中最基本的定時器,關於定時器的高級支持請參考QTimer。
- 注意Q_OBJECT宏對於任何實現信號、槽和屬性的對象都是強制的。
- 所有的Qt窗口部件繼承了QObject。方便的函數isWidgetType()返回這個對象實際上是不是一個窗口部件。它比inherits( "QWidget" )快得多。
QWidget
- QWidget類是所有用戶界面對象的基類。
- Widget是用戶界面的基本單元:它從窗口系統接收鼠標,鍵盤和其他事件,並在屏幕上繪制自己。每個Widget都是矩形的,它們按照Z-order進行排序。
http://focus.blog.chinaunix.net/uid-22666248-id-1746350.html
C++指針和引用及區別
1.變量
變量在內存中的操作其實是需要經過2個步驟的:
找出與變量名相對應的內存地址。
根據找到的地址,取出該地址對應的內存空間里面的值進行操作。
2.指針
指針的特殊之處在於:指針變量相對應的內存空間存儲的值恰好是某個內存地址。這也是指針變量區別去其他變量的特征之一。
3.引用
引用是一種特殊的指針。引用是一個指向其它對象的常量指針,它保存着所指對象的存儲地址。並且使用的時候會自動解引用,而不需要像使用指針一樣顯式提領。
4.指針和引用的區別總結
①指針有自己的一塊空間,而引用只是一個別名;
②使用sizeof看一個指針的大小是4,而引用則是被引用對象的大小;
③指針可以被初始化為NULL,而引用必須被初始化且必須是一個已有對象的引用;
④作為參數傳遞時,指針需要被解引用才可以對對象進行操作,而直接對引用的修改都會改變引用所指向的對象;
⑤可以有const指針,但是沒有const引用;
⑥指針在使用中可以指向其它對象,但是引用只能是一個對象的引用,不能被改變;
⑦指針可以有多級指針(**p),而引用至於一級;
⑧指針和引用使用++運算符的意義不一樣;
⑨如果返回動態內存分配的對象或者內存,必須使用指針,引用可能引起內存泄露。
https://www.cnblogs.com/WindSun/p/11434417.html
https://blog.csdn.net/cherrydreamsover/article/details/81839010
參數傳值、指針、引用有什么區別,在什么場景常用哪種傳遞方式?
傳值、傳址、傳引用的區別,哪個更高效?
1.傳值
這種傳遞方式中,實參和形參是兩個不同的地址空間,參數傳遞的實質是將原函數中變量的值,復制到被調用函數形參所在的存儲空間中,這個形參的地址空間在函數執行完畢后,會被回收掉。整個被調用函數對形參的操作,只影響形參對應的地址空間,不影響原來函數中的變量的值,因為這兩個不是同一個存儲空間。
即使形參的值在函數中發生了變化,實參的值也完全不會受到影響,仍為調用前的值。
2.傳址
這種參數傳遞方式中,實參是變量的地址,形參是指針類型的變量,在函數中對指針變量的操作,就是對實參(變量地址)所對應的變量的操作,函數調用結束后,原函數中的變量的值將會發生改變。
被調用函數中對形參指針所指向的地址中內容的任何改變都會影響到實參。
3.傳引用
這種參數傳遞方式中,形參是引用類型變量,其實就是實參的一個別名,在被調用函數中,對引用變量的所有操作等價於對實參的操作,這樣,整個函數執行完畢后,原先的實參的值將會發生改變。
被調函數對形參做的任何操作都影響了主調函數中的實參變量。
4.哪一種更高效?
在內置類型當中三種傳遞方式的效率上都差不多;
在自定義類型當中,傳引用的更高效一些,因為它沒有對形參進行一次拷貝
https://blog.csdn.net/cherrydreamsover/article/details/81839010
const與#define有什么區別
(1)const和#define都可以定義常量,但是const用途更廣。
(2)const 常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。而對后者只進行字符替換,沒有類型安全檢查,並且在字符替換可能會產生意料不到的錯誤。
(3) 有些集成化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。
struct和class有什么區別?
C++中,class與struct都可以定義一個類。他們有以下兩點區別:
1.默認繼承權限,如果不指定,來自class的繼承按照private繼承處理,來自struct的繼承按照public繼承處理;
2.成員的默認訪問權限。class的成員默認是private權限,struct默認是public權限。
以上兩點也是struct和class最基本的差別,也是最本質的差別;
但是在C++中,struct進行了擴展,現在它已經不僅僅是一個包含不同數據類型的數據結構了,它包括了更多的功能。
Struct能包含成員函數、有自己的構造函數、可以有析構函數、支持繼承、支持多態、支持Private、Protected、Public關鍵字。
如果是class的父類是struct關鍵字描述的,那么默認訪問屬性是什么?
當出現這種情況時,到底默認是public繼承還是private繼承,取決於子類而不是基類。
class可以繼承自struct修飾的類;同時,struct也可以繼承自class修飾的類,繼承屬性如下列描述:
class A{};
class B:A{}; // private 繼承
struct B:A{}; // public 繼承
最后,那么到底是使用struct,還是使用class呢?
一般來說,兩個關鍵字都是可以的,但是由於編程規范的問題,如果要定義的是一種數據結構,那么用struct,如果是一種對象的話,那么用class。
https://www.cnblogs.com/ZhenXin0101/p/11451694.html
tdcall、stdcall、pascall是什么?C++默認是哪種?
__cdecl、__stdcall是聲明的函數調用協議。主要是傳參和彈棧方面的不同。
__cdecl:
一般c++用的是__cdecl
函數參數按照從右到左的順序入棧
由調用函數者把參數彈出棧以清理堆棧
PS:那么為什么還需要_cdecl呢?當我們遇到這樣的函數如fprintf()它的參數是可變的,不定長的。
__stdcall:
windows里大都用的是__stdcall(API)
函數參數按照從右到左的順序入棧
被調用的函數在返回前清理傳送參數的棧
__fastcall:
約定用於對性能要求非常高的場合
約定將函數的從左邊開始的兩個大小不大於4個字節(DWORD)的參數分別放在ECX和EDX寄存器,其余的參數仍舊自右向左壓棧傳送,
被調用的函數在返回前清理傳送參數的堆棧
https://blog.csdn.net/p312011150/article/details/82229672
static_cast, dynamic_cast, reinterpret_cast, const_cast區別比較
static_cast <new_type> (expression) 靜態轉換
static_cast最接近於C風格轉換了,但在無關類的類指針之間轉換上,有安全性的提升
該運算符把exdivssion轉換為type-id類型,但沒有運行時類型檢查來保證轉換的安全性。它主要有如下幾種用法:
①用於類層次結構中基類和子類之間指針或引用的轉換。
進行上行轉換(把子類的指針或引用轉換成基類表示)是安全的;
進行下行轉換(把基類指針或引用轉換成子類表示)時,由於沒有動態類型檢查,所以是不安全的。
②用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
③把空指針轉換成目標類型的空指針。
④把任何類型的表達式轉換成void類型。
注意:static_cast不能轉換掉exdivssion的const、volitale、或者__unaligned屬性。
dynamic_cast <new_type> (expression) 動態轉換
動態轉換確保類指針的轉換是合適完整的,它有兩個重要的約束條件,其一是要求new_type為指針或引用,其二是下行轉換時要求基類是多態的(基類中包含至少一個虛函數)。
該運算符把exdivssion轉換成type-id類型的對象。Type-id必須是類的指針、類的引用或者void *;
如果type-id是類指針類型,那么exdivssion也必須是一個指針,如果type-id是一個引用,那么exdivssion也必須是一個引用。
dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。
在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;
在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。
reinterpret_cast <new_type> (expression) 重解釋轉換
這個轉換是最“不安全”的,兩個沒有任何關系的類指針之間轉換都可以用這個轉換實現
const_cast <new_type> (expression) 常量向非常量轉換
這個轉換好理解,可以將常量轉成非常量。
https://blog.csdn.net/chenlycly/article/details/38713981
https://blog.csdn.net/u012411498/article/details/80804755
C++內存分配有幾種方式?
內存的三種分配方式:
1. 從靜態存儲區分配:此時的內存在程序編譯的時候已經分配好,並且在程序的整個運行期間都存在。全局變量,static變量等在此存儲。
2. 在棧區分配:相關代碼執行時創建,執行結束時被自動釋放。局部變量在此存儲。棧內存分配運算內置於處理器的指令集中,效率高,但容量有限。
3. 在堆區分配:動態分配內存。用new/malloc時開辟,delete/free時釋放。生存期由用戶指定,靈活。但有內存泄露等問題。
常見內存錯誤及對策
1. 內存分配未成功,卻被使用。
對策:使用內存之前檢查是否分配成功。用p!=NULL判斷。
2. 內存分配成功,未初始化就被使用。
內存的缺省值沒有統一的標准。大部分編譯器以0作為初始值,但不完全是。
對策:內存初始化時賦初值。
3. 內存操作越界。
對策:只能是小心了。
4. 釋放了內存,仍然使用。
(1) 使用顯示delete和free的野指針。
對策:釋放完內存,將指針置為NULL。
(2) 使用隱式delete和free的野指針。主要是指函數返回指向棧內存的指針或引用。
對策:當然是不要返回就可以了。
5. 未釋放內存,導致內存泄露。
用new/malloc開辟了內存,沒用delete/free釋放.
對策:new和delete的個數一定相同;malloc和free的個數一定相同;new[]和[]delete一定對應。
https://zhidao.baidu.com/question/2058022112316039427.html
模板的實現可以放在cpp里嗎?為什么?
答:模板聲明和實現要放在一個文件。因為放在CPP里面實現會編譯不過。
https://blog.csdn.net/woyaoxuechengxu/article/details/103095670
C++中#ifndef, #define, #endif的作用和使用的注意事項
答:其實這幾句代碼的主要作用的官方解釋是:為了防止頭文件的重復包含和編譯。
具體來說就是,當你在設計一個很大很大的工程時,可能很多文件里面都會包含同一個頭文件,可能你需要使用該頭文件的目的完全是相同的,可是該頭文件在聲明時,如果沒有加上上面三句代碼在代碼段的前后,當你將整個工程統一編譯,希望鏈接成一個完整的可執行文件時,就會出現大量錯誤,因為每一個相同的頭文件都會進行所謂的“重定義”;而加上上面那三句,則不會出現“重定義”的情況。
通俗來說,我們可以認為以上語句是一個判決條件,即類似於if語句,當執行#ifndef XXX_H_語句時,去判斷xxx.h頭文件是否已經被定義過,如果是,就把#ifndef XXX_H_一直到#endif之間的代碼段跳過,如果不是,則執行#ifndef XXX_H_一直到#endif之間的代碼段。
對於編寫這些語句時,一般習慣將頭文件名全部使用相應的大寫字母,然后把.改成_,在頭文件名的前后都加下划線,即編寫xxx.h的語句時,就如下定義:
#ifndef _XXX_H_
#define _XXX_H_
……
#endif
注意事項:
#ifndef AAA
#define AAA
...
int i;
...
#endif
里面有一個變量定義
在vc中鏈接時就出現了i重復定義的錯誤,而在c中成功編譯。
解決方法:
(1).把源程序文件擴展名改成.c。
(2).推薦解決方案:
.h中只聲明 extern int i;在.cpp中定義
<x.h>
#ifndef __X_H__
#define __X_H__
extern int i;
#endif //__X_H__
<x.c>
int i;
注意問題:
變量一般不要定義在.h文件中。
https://blog.csdn.net/leowinbow/article/details/82884518
https://blog.csdn.net/myyllove/article/details/83067808
extern關鍵字在哪里使用?
A.置於變量或者函數前,以標示變量或者函數的定義在別處,提示編譯器遇到此變量和函數時在其他地方尋找其定義。
B.可用來進行鏈接指定。
https://www.cnblogs.com/Camilo/p/3765494.html