『 QT 』Qt初识
文章目录
- Qt介绍
- IDE选择
- 环境配置简述
- QtSDK 工具简述
- 项目的创建
- 空白项目代码解释
- from file 文件
- .pro 工程文件
- 创建简单的 HelloWorld
- 对象树
- 为什么不推荐将QT控件对象创建在栈上
- 自定义控件类的创建
- 析构函数的顺序
- QT中的乱码问题
Qt介绍
Qt
是一个 跨平台的 C++
图形用户界面应用程序框架, 通常用于开发桌面, 移动和嵌入式系统的应用程序;
Qt
支持多种操作系统, 包括 Windows
, Linux
, macOS
, Android
和 iOS
等;
同时 Qt
提供了丰富的工具和库, 帮助开发者快速构建高质量的图形用户界面(GUI)
和后台逻辑;
Qt
因版本不同, 可能会在语法上出现些许差异, 该系列博客采用Qt6
;
IDE选择
Qt常用的工具一般为QtCreator
, vscode
, visual studio
, Eclipse
等工具, 但通常情况下可以使用QtCreator
作为初始的开发工具, 主要因为该工具集成与配置简单, 界面友好, 即配即用, 同时Qt Creator
是 Qt
的官方集成开发环境 (IDE), 其他的工具(IDE)可能涉及到需要额外配置, 在学习的初期可能增加整体成本;
环境配置简述
Qt
的环境配置通常需要配置三个部分, 分别为C++
编译器, Qt SDK
, Qt
的集成开发环境;
-
编译器
使用C++编写Qt程序时需要配置编译器, 通常Qt所使用的编译器为
gcc/g++
,cl.exe
等; -
Qt SDK
所谓的
SDK
就是软件开发工具包, 通常情况下,Windows
版本的Qt SDK
已经内置了C++
的编译器(mingw
, 可以理解为Windows
版本下的gcc/g++
);除此之外可以使用其他的编译器, 但是需要根据需求来进行额外的配置;
-
集成开发环境
所谓的集成开发环境就是在上文中所提到的
QtCreator
,vscode
,visual studio
,Eclipse
等环境, 初学时推荐使用QtCreator
;
通常情况下, 若是使用QtCreator
作为开发的IDE
时只需要安装QtSDK
, 安装QtSDK
后会默认安装编译器和QtCreator IDE
;
Qt
的安装步骤此处省略;
QtSDK 工具简述
通常安装了QtSDK后在Qt目录中找到这些工具;
主要为Qt Creator
, Assistant
, Designer
, Linguist
;
-
Assistant
- Qt助手这是Qt的官方离线文档;
-
Designer
- Qt设计师这是一个Qt的图形化界面的开发工具, 可以通过点击或者拖拽来添加控件, 而通常情况下QtCreator将会内置使用这个工具达到"所见即所得";
-
Linguist
- Qt语言家这是QtSDK所提供的一个用于适配国际化的翻译工具, 支持 Qt C++ 和 Qt Quick 应用程序的国际化;
开发者, 翻译者和发布者可以使用 Qt Linguist 来创建, 管理和使用翻译文件, 以支持多语言应用程序;
-
Qt
该程序将会自动打开一个命令行, 在该命令行中可以直接使用QtSDK内置的命令对项目进行构建;
-
Qt Creator
QtSDK
自带的原生的集成开发环境IDE
, 上述工具通常内置在Qt Creator
中;通常使用
QtCreator
来完成具体的项目开发;
项目的创建
通常情况下构建项目的顺序为上图所示;
在项目一栏中存在多个选项;
通常根据不同的需求点击不同的选项, 右侧的子页面也会根据项目一栏的选项进行更变, 如Application(Qt)
选项为创建的项目为一个默认的项目, 使用C++
进行编码;
而若是选择Application(Qt for Python)
则表示所创建的项目是一个通过Python
编码的Qt
项目(此处不进行介绍);
当选择Application(Qt)
时其右侧子页面将会存在下列几个模板选项;
-
Qt Widgets Application
Qt Widgets Application
是基于传统的控件(Widgets
)来构建用户界面GUI
的应用程序;其中
Widgets
是像按钮(QButton
), 文本框(QLineEdit
), 列表(QListView
)等标准元素;主要使用
C++
代码来创建和操作界面, 也可通过配合QtDesigner
工具通过拖拽方式设计XML
格式的.ui
文件;其主要特点为如下:
-
原生外观
在不同的操作系统上会使用该系统的原生风格;
-
功能强大复杂
适合开发较为复杂的, 传统的桌面应用程序, 如工业控制软件, 数据库管理系统,
IDE
(如QtCreator
本身就为Widgets
所写)等; -
重量级
该模板下的控件通常较"重", 更适合鼠标键盘交互;
-
-
Qt Console Application
Qt Console Application
是一个没有GUI
的命令行程序, 通常运行在终端或者控制台窗口中;使用
C++
进行编写, 可以使用Qt
核心模块中的非GUI
的功能, 如网络(Qt Network
), 数据库(Qt SQL
), XML处理, 文件操作等;该模板的主要特点为如下:
-
无界面
通过输入输出文本与用户交互;
-
轻量
编译后的程序体积小, 不需要加载GUI库;
-
适配自动化
该模板非常适合编写后台服务, 自动化脚本, 数据处理工具等;
-
-
Qt Quick Application
Qt Quick Application
是基于下一代 Qt (Qt Quick
)来构建现代, 流畅, 动效丰富的用户界面的应用程序, 其核心是QML
(一种声明式JavaScript-like
语言)来描述界面, 通常使用C++
或JavaScript
来处理逻辑;通常使用
QML
(用于UI和交互)与JavaScript
/C++
配合编写;在此不进行过多解释;
-
Qt Quick Application (compat)
该模板为从
Qt5
向Qt6
迁移项目而提供的兼容性模板;该模板与标准的
Qt Quick Application
类似, 但它会使用一个为了兼容Qt5
代码而存在的Qt5Compat
模块;在此不进行过多解释;
通常情况下编写传统桌面软件时使用Widgets
即可;
当模板选择好后进行下一步, 为项目创建路径与名称(项目路径中不能使用带有中文的路径, 否则可能会出现项目构建失败等问题);
当项目名称和路径创建好后需要选择Qt
项目的构建工具;
构建工具通常有qmake, CMake以及Qbs;
-
qmake
qmake
是QT原生支持的项目构建工具, 针对于QT项目, 能够提供较为简单且快速的项目构建; -
cmake
cmake
是大多数项目的构建工具, 同时也能够用来构建QT项目工具; -
Qbs
一款新型的QT项目构建工具, 但使用人数较少, 目前已经暂停维护;
通常QT项目的构建工具可以选择除Qbs
以外的工具, 当项目较为简单时使用qmake
;
当项目较为复杂且超出qmake
限制时采用cmake
;
在使用Qt Creator
创建项目时会自动生成一些代码, 通常情况下所生成的代码会包含一个类, 而在Base class
中可以选择自动生成类的父类;
三个选项分别为QMainWindow
, QWidget
与QDialog
三个类;
-
QMainWindow
该父类通常表示一个完整的应用程序窗口;
包含菜单栏, 工具栏, 状态栏等其他组件;
-
QWidget
该父类表示一个基础的窗口类, 是
QMainWindow
与QDialog
的父类, 其为轻量级的通用容器, 无预置菜单栏, 工具栏等组件, 可作为独立的窗口或嵌套组件;通常用于自定义对话框, 设计复杂界面中的子面板, 绘制自定义控件的基础;
-
QDialog
是一个专用对话框基类, 其支持模态/非模态弹窗支持, 同时自带对话框按钮布局, 如(
OK
/Cancel
);
上文的这些类都是QT
中的内置类, 而QT
的内置类都是以Q
作为开头的;
当选择好Base class
后可对Class name
进行命名, 通常情况下, 此处的 class name
和文件名, 路径名是相关联的, 但通常情况下, 这种关联并不是强制的, 当然推荐所创建的类名与文件名相关联是一个比较好的做法, 可以对项目进行一个更好的管理;
在Details
部分中涉及到几个文件, 分别为.h
头文件, cpp
源文件和一个.ui
的xml
结构文件, 其中.ui
文件是一个可以展示当前样式的文件, 以达到所见即所得, 可通过QTCreater
中的代码或是Designer
的鼠标直接拖动来完成应用程序的设计;
Detail
部分结束后是Translation
页面, 在该页面下可以选择设置语言翻译来完成应用程序的一个国际化的翻译, 若是没有需求可直接点击下一步;
在构建套组页面中选择基于哪个编译器的QtSDK
来构建后续代码, 通常使用该页面下默认所选择的QtSDK
即可;
在汇总页面中可根据需求判断项目是否添加到版本控制系统;
最后点击完成即可构建一个新的QT
项目;
创建完项目后可直接对项目进行运行;
该窗口即为一个Widget
项目的初始状态;
Ps: 在Qt项目中尽可能不要出现中文, 否则可能会出现项目构建失败等问题!! |
空白项目代码解释
当项目生成后将会有四个文件, 分别为widget.h
文件, widget.cpp
文件与一个main.cpp
文件;
-
main.cpp
主函数所在源文件;
#include "widget.h"#include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); }
-
QApplication
一个
QT
项目中必须存在一个QApplication
对象, 通常情况下, 这个对象用来接收命令行参数;管理程序的事件循环(如鼠标事件, 键盘事等事件)并提供基本的
GUI
设置和管理功能; -
Widget w
Widget
是在项目构建时所选择的一个框架类型, 在这里实例化了一个自定义的窗口类, 一般情况下默认直接使用栈进行内存分配, 当程序结束时将会自动销毁;这个类继承于
QWidget
类; -
w.show()
这是一个展示函数, 主要的功能是让窗口进行显示, 本质上新创建的窗口默认是隐藏的,
show()
成员函数能够让改框架的窗口进行展示;相关的方法还有:
-
w.show()
显示窗口(非模态);
-
w.showMaximized()
最大化显示;
-
w.showFullScreen()
全屏显示;
-
w.hide()
隐藏窗口;
-
-
return a.exec()
这里是启动
Qt
的事件循环, 程序运行并进行exec()
成员函数的执行后, 程序会再次进入无限循环, 等待并处理事件(如鼠标点击, 键盘输出, 窗口重绘等);当接收到退出信号时(如关闭窗口或是程序异常)时将会终止循环, 并返回应用程序的退出码;
该处的
exec
与Linux中的exec
系统调用接口无关;
其大致的工作流为如下:
-
程序启动
执行
main()
函数; -
初始化
创建
QApplication
对象并设置应用程序环境; -
创建界面
实例化主窗口类;
-
显示界面
调用
show()
使窗口可见; -
事件处理
进入事件循环, 响应用户交互;
-
程序退出
用户关闭窗口, 事件循环结束, 程序退出;
-
其次widget.h
与widget.cpp
两个文件共同组成了一个Widget
类;
-
widget.h
该文件描述了
widget
类的声明;#ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private:Ui::Widget *ui; }; #endif // WIDGET_H
上述代码为
widget
类的声明, 其中使用了声明卫士以确保该头文件只被包含一次以防止头文件重复包含问题;同时引用了头文件
QWidget
;在代码中可以看到,
Widget
类继承自基类QWidget
, 该基类则来自QWidget
头文件, 所继承的类则为在创建项目时所选择的基类;其中
Q_OBJECT
是QTSdk
中内置的宏, 通常是为 信号 和 槽 所准备的;在该段代码中存在这样一行代码:
Widget(QWidget *parent = nullptr);
在QT中引入了一个名为"对象树"的机制;
当创建一个QT的对象时需要将对象挂在对象树上, 本质上就是指定其父节点;
在
private
成员中存在一个UI::Widget *ui
, 该对象与.ui
文件密切相关; -
widget.cpp
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }
其中
widget.h
为widget
对象的声明头文件,ui_widget.h
头文件则是form file
被qmake
生成的头文件;在这段代码中存在:
ui->setupUi(this);
即为将
form file
生成的界面和当前的widget
进行关联;最后的析构函数则为对已用资源进行释放;
from file 文件
在QTCreator中的文件管理中存在一个目录为from file
(界面文件), 在当前form file
中存在一个widget.ui
文件;
直接通过鼠标双击的方式打开文件将会间接使用Designer
工具以设计模式打开该文件;
这是一个图形化的界面编辑器;
在左侧中存在QT内置的空间, 可直接采用拖动的方式将控件直接拖到应用程序内部进行可视化设计;
在左侧中的编辑按钮可以将当前的设计模式改为编辑模式;
相应的如果在该处直接用拖动等方式进行设计时, 对应的在其编辑模式中的代码也会出现相应的变化;
同时也可以在程序窗口的右侧为控件设置对应的属性;
Qt中使用的XML
文件本质就是描述程序的界面, 进一步的项目构建工具(cmake
, qmake
)将会调用相关的工具, 依据XML
文件生成一些Cpp
代码, 从而构建完整的界面;
.pro 工程文件
在项目目录中将会存在一个.pro
文件, 该文件为项目构建工具选择qmake
时的工程文件, 也是qmake
构建项目的重要依据;
在项目结构中存在一个build
目录, 该目录下通常存放一些项目构成的中间件;
在该目录下存在一个.h
文件, 其文件名通常为ui_xxx.h
文件, 该文件即为.ui
的XML
文件所生成的.h
文件;
该.h
文件中存在一个成员方法, 为void setupUi(QWidget *Widget)
, 该成员方法即为widget.cpp
中所调用的方法;
在该目录下的Debug/Release
目录中的.exe
文件即为该项目构建后的最终的可执行文件;
创建简单的 HelloWorld
-
可视化设计
在项目中点击
.ui
文件打开设计模式, 拖动控件(此处举例lable
标签)进窗口并编辑显示文本;从上文中提到的项目结构中的
ui_xxx.h
文件中能观察到其新增的控件; -
代码编辑方式编写
代码编写的方式通常是去编辑
Widget.cpp
这样的文件对程序页面进行一个设计;通常将构造界面的代码放到
Widget/MainWindow
的构造函数中;QT中每个类都有一个对应同名的头文件, 若是需要使用某些控件需要显式的去包含这些头文件;
此处同样以
Label
标签为例;在引入头文件中可能会出现两个与控件名相同的头文件;
一般使用
<QLabel>
的这种风格(c++98
标准成立后规定包含头文件使用<cstdio>
的这种风格来代替<stdio.h>
的风格);QLabel* label = new QLabel(this); // 传入this指针表示为当前label对象指定一个父对象// QLabel label; // 若是设置对象树可能导致非法访问引出的段错误问题
在创建对象可以使用在栈上创建, 也可以使用在堆上创建, 但一般情况下更推荐在堆上创建对象(与对象树有关);
设置完
QLabel
对象后可为该对象设置文本属性, 通常使用QLabel::setText()
函数设置对象的文本;QString text() const void setText(const QString &);
其中该函数所传入的参数为
const QString&
类型的参数;在
c++98
标准前的STL
容器并不标准好用, 因此QT为了适配自身的项目体系重新设计了一套容器,QString
类型即为QT内部的字符串类型;除了
QString
以外还有QList
,QVector
,QMap
等容器;在新版的QT中, 可以使用
C++
原生的容器(C++ STL
越发完善, 并存在标准), 也可以使用QT
内置容器;一般情况下
QT
中的容器的头文件已经被其他类内置包含了, 因此一般不需要再显式包含;Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);QLabel* label = new QLabel(this); // 表示为当前label对象指定一个父对象label->setText("Hello world!!"); }
构建运行程序;
从这个例子中可以看到, 在设计QT控件时并没有为控件显式调用
delete
来释放内存, 本质上是因为该控件被连接到了对象树上;当程序运行时(如关闭窗口), 其根节点会通过
QObject
类的析构函数进行清理, 这个清理工作是一个递归的操作, 即QObject
去调用它的析构函数, 这个操作会一直向下遍历他的子节点, 让他们分别去调用子节点的析构函数, 最终从子节点向上去释放内存(深度优先);通常对象树是由
QObject
中的Children
列表所管理的;
对象树
在QT中存在一种主要的结构被称为对象树, 这棵对象树主要来管理其子节点的生命周期;
对象树的结构如上图所示, 本质上是一个节点的实例, 每个节点都存放一个指针, 指针指向一个ChildrenList
列表, 列表是每个节点指针;
/* 示例 */
class QObject{private:QObject *_parent; = nullptr; // 指向父节点的指针QList<QObject *> _childrens; // 存储子对象的链表指针QString objectName;// ...public:~QObject(){qDeleteAll(_childrens); // 删除所有子对象, 遍历子对象对子对象进行内存释放, 在释放过程中子对象将会调用他们自己的析构函数完成深度优先遍历的内存释放}void setParent(QObject *parent){if(this->_parent){this -> _parent -> _childrens.removeAll(this); // 从原父节点中移除this -> _parent = parent;if(_parent){_parent->_childrens.append(this); // 设置当前节点的父节点指针关联后 将当前父节点中的子节点列表中添加自己(this指针);}}const QList<QObject*>& children() const {return _children;}QObject* parent() const {return _parent; // 返回父节点}}
};
对象树的核心在于QObject
类中的析构函数和setParent()
方法;
-
析构函数
~QObject
这是自动销毁子对象的起点, 当父对象析构时, 他会自动析构所有其所有子对象, 从而形成级联删除, 以确保所有子对象被正确释放;
/* 示例 */ QObject::~QObject() {// 1. 核心:删除所有子对象(对象树的基石)QObjectPrivate *d = d_func();// 如果存在子对象列表且不为空if (!d->_children.isEmpty()) {// 遍历并删除所有子对象(注意:从后往前删除更高效)for (int i = d->_children.size() - 1; i >= 0; --i) {QObject *child = d->_children.at(i);// 确保子对象存在且当前对象是其父对象if (child && child->_parent() == this) {// 关键:设置子对象的父对象为nullptr,然后删除它child->setParent(nullptr); // 避免重复删除delete child;}}}// 2. 从父对象的子对象列表中移除自己if (d->_parent) {d->_parent->d_func()->removeChild(this);}// 3. 清理其他资源(信号槽连接、事件过滤器、定时器等)// ... 其他清理代码 ... }
当然成功析构需要确保有将新控件交由父节点管理, 即调用setParent()
函数来设置新控件的父节点指针;
-
setParent()
void setParent(QObject *parent){if(this->_parent){this -> _parent -> _childrens.removeAll(this); // 从原父节点中移除this -> _parent = parent;if(_parent){_parent->_childrens.append(this); // 设置当前节点的父节点指针关联后 将当前父节点中的子节点列表中添加自己(this指针);}}
setParent()
的大致结构如该段代码为例, 如果当前节点存在父节点, 那么将该节点的父节点与当前节点去除关联, 随后再与新的父节点新增关联;
为什么不推荐将QT控件对象创建在栈上
从上文得知, Qt控件对象在创建过程中, 如果涉及到父子关系的话, 需要将控件对象的指针挂在对象树上, 也方便内存管理;
同时在Qt上通过代码创建控件对象时通常是在当前QT框架的构造函数上编写的, 这会造成两个问题:
-
控件对象生命周期端
框架的构造函数结束时, 对应控件的生命周期也将结束;
-
访问野指针
由于控件的生命周期结束, 但只是表示该指针位置对应的空间不可用(地址失效), 不代表与对象树关联的地址也被清除, 对应的已经传入的指针会成为野指针, 对野指针进行访问时可能出现段错误;
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QLabel label(this);label.setText("hello world");
}Widget::~Widget()
{delete ui;
}
如这段代码为例, 当项目构建并运行后标签并不会展出, 本质上是因为标签的生命周期在框架的构造函数结束后也跟着结束了, 并且在这段代码中, 如果label
并不是标签而是其他子控件, 那么label
将会成为其他子控件的父节点, 那么将会出现对非法地址进行访问;
自定义控件类的创建
在一个QTCreator项目中可以在左上角添加新的控件类c++ class
;
添加后可选择是否将该类添加至项目中;
将新class
添加至项目后, 项目将会自动生成两个文件;
示例:
在项目中创建了一个名为MyLabel
的类, 该类继承于QLabel
对应的项目结构中增加了两个文件, 分别为mylabel
的头文件与源文件;
在mylabel.h
文件中, 为class MyLabel
的声明, MyLabel
继承自QLabel
, 这意味这MyLabel
包含其父类QLabel
;
而通常情况下子控件为了更好的内存管理需要将其链接至父对象的children
列表中, 但当前MyLabel
只是继承于QLabel
, 自身并没有能力链接到对象树上, 因此需要接收并将parent
指针传进MyLabel
中并在MyLabel
中的初始化列表中调用其父类QLabel
的构造函数;
MyLabel::MyLabel(QWidget* parent/* 接收父节点指针 */):QLabel(parent)/* 调用父类的构造函数完成与对象树的连接 */ {}
因此在某个框架下需要构造一个MyLabel
控件时只需要传入this
指针即可, this
指针作为MyLabel
的父节点进行传入, 随后MyLabel
将会接收parent
指针并在初始化列表中调用其父类QLabel
的构造函数完成对象树的链接;
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);MyLabel *_mylabel = new MyLabel(this); // 构造一个QLabel对象_mylabel -> setText("This is MyLabel");
}
析构函数的顺序
通常情况下一个子控件对象若是传入parent
指针将会把当前控件挂在对象树上;
-
widget.cpp
#include "widget.h" #include "ui_widget.h" #include <QLabel> #include "mylabel.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);MyLabel *_mylabel = new MyLabel(this);_mylabel -> setText("This is MyLabel"); }Widget::~Widget() {delete ui; }
-
mylabel.cpp
#include "mylabel.h" #include <iostream>MyLabel::MyLabel(QWidget* parent):QLabel(parent) {}MyLabel::~MyLabel() {std::cout<<"MyLabel destroied"<<std::endl; }
-
mylabel.h
#ifndef MYLABEL_H #define MYLABEL_H#include <QLabel>class MyLabel : public QLabel // 继承自QLabel { public:MyLabel(QWidget* parent); // 在自定义的控件对象的构造函数中需要传入parent// 这样才能在构造的时候将父子节点进行关联 将新控件关联到对象树中~MyLabel(); };#endif // MYLABEL_H
从上文代码中可以看到, MyLabel
继承自QLabel
, 并且创建了一个QLabel
对象, QLabel
的析构函数将会打印出 "MyLabel destroied"
, 但在这里的widget
中并没有在其析构函数中去释放MyLabel
的内存, 在运行之前我们无法知道MyLabel
是否被释放;
运行程序, 程序正常运行;
当点击右上角关闭按钮关闭窗口后, "MyLabel destroied"
在终端中被打印;
说明MyLabel
的析构函数被调用(生命周期结束所以调用析构函数), MyLabel
被释放, 这就是对象树对子控件的内存管理;
当父控件被关闭后, 将会调用~QObject()
析构函数, 析构函数将会遍历自身节点中所指向的ChildrenList
进行delete
释放内存;
被遍历删除的节点同样会调用自己的析构函数, 对自己的ChildrenList
遍历释放, 形成递归, 呈深度优先的内存释放方式逐个释放内存;
这种内存释放的方式避免了空间需要手动释放内存;
QT中的乱码问题
通常情况下如果在QT中使用std::cout
打印中文字符时, 可能出现乱码问题;
在QT的main函数中引入<iostream>
头文件并使用std::cout
打印中文字符, 重新构建并运行程序;
#include "widget.h"#include <QApplication>#include <iostream>
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();std::cout<<"你好世界"<<std::endl;return a.exec();
}
从控制台上看到虽然打印的是中文, 但出现的是乱码, 这本质上是因为编码不同所导致的, 编码问题类似于类型问题, 就如你使用double
的方式对数据进行存储, 但最终使用int
的方式将数据取出, 那么就会出现问题;
#include <iostream>
int main() {double n = 5.556;printf("%d\n", n);return 0;
}
这段代码运行的结果为:
文字的乱码也是相同的道理, 写入与写出采用不同的编码格式则会出现乱码, 通常情况下, Windows
若是为中文, 那么其控制台采用的是GBK
编码格式, 而代码输出的采用的是UTF8
的格式, 而std::cout
并不会自动处理编码问题, 那么在输出展示则会出现问题;
而QT中的QString
与QDebug()
可以自动处理编码方式;
通常推荐上述两种方式而不是使用std::cout
;
#include "widget.h"
#include <QApplication>
#include <QString>
#include <QDebug>int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();qDebug() << "这是qDebug";return a.exec();
}
运行结果为:
通常情况下qDebug()
用来在开发过程中输出日志, 当一个项目产品已经过了Dev
阶段后, 开发者可以考虑通过一些编译开关关闭qDebug()
以避免日志信息展示给用户;