文章目錄
- 驗證自動釋放(對象樹上的對象)
- 亂碼問題的緣由
- 解決亂碼問題
- 1. 使用QString
- 2. qDebug()
- 小結
簡介:上一篇文章寫到,當new出一個控件對象并且將它掛到對象樹上,無需我們手動釋放該對象,是因為在一個合適的時機,會統一釋放該對象樹上的控件對象。
這篇文章主要內容是去驗證確實會自動釋放該對象樹上的控件對象,并且討論Qt終端輸出的亂碼問題的緣由,并且如何去解決亂碼問題
,并且對這兩篇文章的內容做個小結,也能由此看出,雖然只是一個簡單的hello world程序,卻能連續許多的相關知識點(全是干貨)
驗證自動釋放(對象樹上的對象)
先創建一個項目,再左上角找到文件,新建一個項目,此時
選擇新添加類
這里輸入你添加類的名稱后,選擇它的基類。
如果點開三角沒看到,就直接手動輸入QLabel
,其它的就不用管了,直接下一步
完成后,左邊文件就會自動添加
mylabel.h與mylabel.cpp
// mylabel.h
#ifndef MYLABEL_H
#define MYLABEL_Hclass myLabel : public QLabel
{
public:myLabel();
};#endif // MYLABEL_H// mylabel.cpp
#include "mylabel.h"myLabel::myLabel()
{}
我們會發現添加的
mylabel.h
,它并沒有包含頭文件 QLabel.h
,這操作其實挺挫的,幫我們生成了一些代碼,但好像又沒完全生成,連頭文件都沒有自動包含,那也沒啥辦法,就只能手動的去包含。不過,它生成的默認構造很明顯也不是我們需要的,得手動寫 mylabel(QWidget* parent),這樣才能確保咱們自己的對象能夠加到對象樹上
#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>class myLabel : public QLabel
{
public:myLabel(QWidget* parent);
};#endif // MYLABEL_H
處理完
mylabel.h
文件,再來看看mylabel.cpp
文件。這里分享一個小技巧,在Qt Creator中,可以通過F4快捷鍵快速切換.h
和對應的.cpp
文件,這也是C++IDE的常規功能。下面構造函數中,QLabel(parent) 它會調用父類的構造函數去初始化,這樣才能讓咱們自己類的對象加入到Qt 對象樹上
,具體看Widget.cpp
文件的代碼
#include "mylabel.h"
myLabel::myLabel(QWidget* parent) : QLabel(parent)
{
}
完成了
mylabel.h
與mylabel.cpp
,接下來Widget.cpp
文件的操作就是復刻Qt 創建hello world程序的操作了,看看myLabel(this);
這就是為啥要手動寫構造函數的緣故
#include "widget.h"
#include "ui_widget.h"
#include "mylabel.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);myLabel* label = new myLabel(this);label->setText("hello world");
}Widget::~Widget()
{delete ui;
}
寫到這里界面上已經能顯示出hello world了,但如何去觀察該對象會自動釋放呢?
那對象要自動釋放,是不是只能從析構函數入手,所有只需要在 myLabel類中寫個析構函數即可判斷
,這里再分享一個小技巧,在寫完一個函數的聲明后,按下alt + enter
就可以自動在對應的cpp文件中添加函數的定義了
class myLabel : public QLabel
{
public:myLabel(QWidget* parent);~myLabel();// 先寫析構函數的聲明
};
它如果會自動釋放對象的話,是一定會走析構函數的,可能會遇到點疑問(其實是我自己搞蒙了,嘿嘿)?為啥這析構函數不去delete 對象呢?首先要分清楚
咱現在探究的是new出的這個對象我們沒有delete,它卻會自動釋放,那它釋放是肯定要去走析構函數的啊,其次它的成員變量也沒有指向一塊空間的啊,所有不需要在析構函數中delete啥
myLabel::~myLabel()
{std::cout << "Mylabel已經被銷毀" << std::endl;
}
當你運行程序時,你會發現下面的應用程序輸出啥也沒有,這是因為你還沒有關閉界面,沒有自動釋放,關閉界面后會看到應用輸出程序輸出中顯示:
Mylabel宸茬粡琚攢姣
,這有是有了但咋是亂碼呢?那這證明了該對象會自動析構,現在迎來了一個新問題:亂碼的緣由是什么,如何去解決亂碼
亂碼問題的緣由
通過上述的驗證,有日志顯示就說明了析構函數是執行了,雖然沒有手動delete,但是由于把
MyLabel
掛到了對象樹上,此時窗口銷毀的時候,就會自動銷毀對象樹中的所有對象,MyLabel的析構時執行到了。但實際的顯示結果卻出現亂碼,亂碼這個事情咱們以后是會經常遇到的
,而亂碼問題出現的原因有且只有一個(不局限于C++)就是編碼方式不匹配
那編碼方式不匹配是啥意思呢?就是如果你字符串本身是utf8編碼的,但是終端(控制臺)卻是按照 gbk(后面解釋)的方式來解析顯示的,此時就會出現亂碼。(就好像是拿著 utf8 的數值就查詢 gbk 的碼表,那能對才見了鬼了,此時就會出現亂碼
- 現在咱提出一個問題,一個漢字占多少個字節。這還用想嗎?在C語言中就知道一個漢字占兩個字節,但真的是這樣的嗎?
其實針對這個問題來說,只要你回答出一個具體的數字,那就一定是錯的!回答這個問題之前,要考慮到它的前提條件:也就是當前中文編碼使用的是哪種方式(字符集)
。 - 大家都知道計算機中只存儲二進制數字,那英文字母怎么表示的呢?就是通過ASCII碼表去規定每個字符都有一個對應的數字去表示,而表示英文只需要用到一個字節(也就八個比特位),畢竟英文字母數量就那么幾個。而表示中文一個字節根本不夠,因為中文常用字就大概四千個,算上生僻字就有差不多六萬個左右。
- 通過ASCII碼表去規定英文字母和一些字符,那自然會有一個大表格會給每個漢字去分配一個整數(計算機只要把這六萬個左右的整數以及對應的漢字直接存起來,遇到相應的表示漢字的二進制數字,再根據這張表找到對應的漢字輸出即可,而對于計算機來說,六萬多個符號的表格就是小case)
- 那具體這個表格是什么樣子的呢?具體每個漢字都使用哪個數字表示?這就不一定了,這個表示漢字的字符集有很多種,而不同的字符集表示同一個漢字,所使用的數字各不相同。
- 目前表示漢字字符集主要有兩種方式,
第一種:GBK 使用2個字節表示一個漢字,其中Windows簡體中文版,默認的字符集就是GBK。第二種:UTF-8 / utf8 變長編碼,它表示一個符號,使用的字節數有變化(2-4個)但是在utf8中,一個漢字一般是3個字節,其中Linux默認就是 utf8
。 - 一個漢字它具體的utf8 / GBK 編碼的數值是多少,可以通過一些在線工具查看
std::cout << "Mylabel已經被銷毀" << std::endl;
這個字符串使用的編碼方式和當前mylabel.pp
文件的編碼方式是一致的,如何去查看該文件的編碼方式呢?以記事本打開.cpp文件后,在將該文件另存為,就可以看到該文件的編碼方式了
,如果下面顯示的是 ANSI,則說明這個文件是GBK編碼
那現在終端出現了亂碼,那就證明Qt Creator內置的終端不是以 utf8 的方式來顯示字符串的,另外該終端好像
無法去設置字符編碼
,但可以從其它的方式入手
解決亂碼問題
1. 使用QString
QString可以去幫助我們自動的處理編碼方式,這了解一下就行了,誰會去用這玩意啊。所以著重介紹第二種方法
myLabel::~myLabel()
{QString str("Mylabel已經被銷毀");fprintf(stdout, "%s", str.toLocal8Bit().constData());
}
2. qDebug()
Qt其實提供了專門用來打印日志的工具,也就是
qDebug()工具
,借助這個就可以完成打印日志的過程,還能很好處理字符編碼(就無需我們去關注內部啦),這用起來嘎嘎的香。這里咱闡述一下qDebug(),它是一個宏,里面封裝了一個QDebug對象,而只需要直接使用qDebug()就行了,它這里其實就相當于重載了 “<<” 移位運算符去輸出(沒說它就是重載哈),可以把它當成cout來使用
#include<QDebug>
myLabel::~myLabel()
{qDebug() << "Mylabel已經被銷毀了";
}
后續使用Qt ,如果想通過打印日志的方式輸出一些調試信息,都優先使用 qDebug。雖然使用 cout 也行,但是cout 對于編碼的處理不太好,在windows 上容易出現亂碼(如果是 Linux使用 Qt Creator,一般就沒事了,Linux 默認的編碼一般都是 utf8)。而且使用qDebug,還有一個好處就是:打印的調試日志,是可以統一進行關閉的!
(因為輸出的日志,其實是開發階段,調試程序的時候使用的。但你將程序發布給用戶,是不希望用戶看到這些日志的!而qDebug 可以通過編譯開關,來實現一鍵式關閉)具體如何去關閉,加個宏就OK了(網上搜就直接出來了,這里就不過多介紹了)
小結
- 認識QLabel類,能夠在界面上顯示字符串,通過 setText 來設置參數QString (Qt中把C++里的很多容器類,進行了重新封裝以及歷史原因)
- 內存泄露/文件資源泄露
- 對象樹,Qt中通過對象樹來統一的釋放界面的控件對象,Qt 還是推薦使用 new的方式在堆上創建對象,通過對象樹去統一釋放對象,其次創建對象的時候,在構造函數中要指定父對象(此時才會掛到對象樹上),如果你的對象沒有掛到對象樹上,就必須要記得手動釋放!
- 通過繼承自Qt內置的類,就可以達到對現有控件進行功能擴展效果(因為Qt 內置的 QLabel是沒法看到銷毀過程的,那為了看清楚,就自己去創建類MyLabel,繼承自 QLabel,再重寫析構函數,在析構函數中,加上日志就能直觀的觀察到對象釋放的過程了)
其實面向對象"繼承",本質上是對現有代碼進行的"擴展"
當然也可以重寫控件中的任何功能,不僅僅是析構函數,后期學習總有一些控件是達不到要求的,那此時就得去功能擴展 - 亂碼問題和字符集~MySQL(很多地方都涉及到)
- 如何在Qt 中打印日志,作為調試信息。使用cout固然可以,但是并不是上策(字符編碼處理的不好,也不方便統一進行關閉)Qt 中推薦使用qDebug()完成日志的打印。那之前調試程序都是用調試器(VS/gdb),為啥要打印日志調試呢?(
因為有時調試器很多時候是有局限性的,是無法使用的,比如說,假設當前存在的 bug是一個概率性的 bug,出現的概率是1%甚至更小,如果要想去調試,那得調多少次才會出現這一次bug,但如果我們去使用日志,就可以很好的解決這種問題,我直接讓這個代碼按照邏輯循環走幾萬次,總會出現幾次bug吧,不過無論是哪種方式,本質上都是觀察程序執行的中間過程和中間結果
)