Qt开发学习——QtCreator深度介绍/程序运行/开发规范/对象树

前面我们已经简单介绍了Qt的开发历史和应用,以及如何安装配置Qt的开发环境。本期就让我们对Qt的主要IDE------QtCreator进行深度的了解学习

相关代码已经上传至作者的个人gitee:楼田莉子 (riko-lou-tian) - Gitee.com喜欢请关注一下谢谢

认识 Qt Creator 界⾯

对于以下的Qt默认代码

cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])//main函数形参就是命令行参数
{
    //编写图形化界面程序一定要有这个对象
    QApplication a(argc, argv);
    //创建了一个对象,让它显示出来
    Widget w;
    w.show();
    //.show()让控件显示出来
    //.hide()让控件隐藏
    return a.exec();
}

左边框

在编辑模式下,左边竖排的两个窗⼝叫做 "边栏" 。① 是项⽬⽂件管理窗⼝,② 是打开⽂件列表窗⼝。在 QtCreator 菜单 "控件" -------> "Show Left Sidebar",或者使⽤快捷键:"Alt + 0" 可以控制边栏的显⽰和隐藏

边栏⾥的窗⼝数⽬可以增加,边栏⼦窗⼝标题栏有⼀排⼩按钮,最右边的是关闭按钮,倒数第⼆个是增加分栏按钮,可以添加多个边栏⼦窗⼝。

边栏⼦窗⼝标题栏第⼀个控件是组合框,可以选择该⼦窗⼝的功能视图类型,⽬前可以选择 8 个视图类型:

它们功能如下:

视图类型 说明
项目 即项目文件管理视图,可以选择项目里的文件进行编辑,包括 pro 文件也可以手动编辑。
打开文档 当前已经打开的文件列表,文件名右边如果有 * 号,是该文件被修改了但尚未保存。
书签 右击代码编辑器行号位置,看到 "切换书签",可以给代码行添加书签,方便跳转到该位置。
文件系统 相当于系统里的文件资源管理器,可以查看项目文件夹在磁盘里的实际文件列表。
类视图 可以查看项目里包含的类及相应源代码文件里的成员函数、成员变量。
大纲 编辑器所显示的当前文件的大纲列表,如名字空间、类名、成员函数、成员变量等。
类型层次 当前项目包含的类及其基类、派生类列表。
Include Hierarchy 包含视图,显示当前项目里 .h、.cpp 以及 Qt 类库头文件之间的包含关系。

代码编辑区

①和②:导航按钮 "返回" 和 "前进",这与⽹⻚浏览器的前进和后退按钮类似,可以在之前浏览的多个代码⽂件或⼀个代码⽂件⾥多个位置之间快速切换。

③:标识当前显⽰的⽂件是只读还是可写,⼀般都是可写的。

④:⽂件类型图标,当前显⽰⽂件的类型,这个控件其实是⼀个菜单按钮,点击可以弹出丰富的⽂件处理功能菜单。

⑤:打开的⽂件名,可以在多个打开的⽂件之间选择切换,与边栏的 "打开⽂档" 视图是对应的。

⑥:关闭当前显⽰的⽂档。

⑦:为当前显⽰的⽂件添加额外的C++预处理指令,⼀般⽤不着。

⑧:选择符号,可以在当前显⽰的⽂件⾥多个函数、类、成员变量等之前快速切换,与边栏"⼤纲"视图是对应的。

⑨:编辑区光标的⾏号和列号。

⑩:代码编辑区分栏,可以增加多个编辑器窗⼝,显⽰多个打开的⽂档或显⽰较⼤源码⽂件的多个位置。

行首区:主要⽤来显⽰代码⾏号,以及调试断点标志和代码书签标志。右击⾏⾸区可以弹出右键菜单,菜单⾥可以切换书签、编辑书签以及设置或取消断点。

同⼀⾏是既可以打断点也可以设置书签的,⼆者不冲突,其实它们根本就没关系。单击⾏号前⾯的浅灰⾊空⽩区可以直接打断点,再次单击可以取消断点,另外也可以⽤快捷键 F9 设置或取消断点。代码书签⼀般⽤右键菜单来设置,也可以⽤快捷键 Ctrl+M 设置或取消书签。

编辑区 :写代码的区域。

UI设计界⾯

双击 widget.ui ⽂件,Qt Creator 会⾃动进⼊设计模式,可以对图形界⾯进⾏可视化编辑:

①:组件选择窗⼝。组件选择窗⼝分为多个组,如 Layouts、Buttons、Display Widgets 等,界⾯设计的常⻅组件都可以在组件选择窗⼝中找到。

②:UI 设计窗⼝。如果要将某个组件放置到该窗⼝上时,从组件选择窗⼝上拖放⼀个组件到窗体上即可。

③:动作编辑窗⼝。动作编辑窗⼝包括 Action Editor 以及 Signals 和 Slots 编辑器。 Action Editor 主要是⽤来新建 Action,并且通过拖拽的动作,将新建好的 Action 添加到菜单栏和⼯具栏上;Signals和 Slots 编辑器⽤于可视化地进⾏信号与槽的关联。

④:对象浏览窗⼝。⽤树状视图显⽰窗体上各组件之间的布局包含关系,视图有两列,显⽰每个组件的对象名称(ObjectName)和类名称。

⑤:属性设置窗⼝。显⽰某个选中的组件或窗体的各种属性及其取值,可以在属性设置窗⼝⾥修改这些属性的值。

构建区

上图所示的左下⻆⼀共有四个按钮,下⾯分别介绍⼀下:

第⼀个按钮是选择构建项⽬使⽤的 Qt 套件和构建⽬标程序的类型(Debug 或 Release)。

对于第⼀个按钮,默认的是Debug,构建的是 Debug 类型的⽬标程序。如果需要构建 Release 版⽬标程序,点开左下⻆第⼀个按钮

QtCreator 的构建模式对比

模式名称 说明
Debug 用于调试 的构建模式。编译器会使用 -g 等标志,生成带有完整调试符号信息的二进制文件。此模式下代码未进行优化,运行速度较慢,但可以设置断点、单步执行,便于查找和修复错误。
Release 用于发布 的构建模式。编译器会进行高强度优化(如使用 -O2 / -O3 标志),生成的文件更小、运行效率更高。但不会包含调试符号,因此不适用于调试。
Profile 用于性能分析 的构建模式。它在 Release 模式的基础上,额外添加了调试符号(类似 -g -O2 的组合)。这样既能保持较高的运行性能,又能与性能分析工具(如 gprof, perf 等)配合使用,定位性能瓶颈。

上图是针对项⽬只⽤到单⼀ Qt 套件的,如果之前配置了多个 Qt 套件,看到的类似下图:

如果项⽬配置了多个可⽤的 Qt 套件,点开左下⻆第⼀个按钮后,会看到各个套件以及构建类型,如果要切换 Qt 套件或构建类型,直接选中相应条⽬,然后点击运⾏按钮就⾏了。如果构建和运⾏时没出错,就会显⽰出构建好的⽬标程序界⾯。

第⼆个是运⾏按钮,快捷键是 Ctrl+R,如果还没构建项⽬或刚修改了代码,直接点击运⾏的话,QtCreator 会⾃动构建⽣成新的⽬标程序并运⾏。

第三个是调试按钮,快捷键是 F5。调试程序之前,QtCreator 会⾃动构建⽣成最新的⽬标程序,并进⼊调试模式。

第四个是构建按钮,快捷键是 Ctrl+B,只构建最新的⽬标程序,但不运⾏。

Qt代码解释

前面我们已经创建好了新的项目,简单认识了一下QtCreator的界面。接下来我们以widget为例子,对默认生成的代码进行说明

main.cpp

cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])//形参为命令行参数
{
    QApplication a(argc, argv);//编写Qt界面一定要有QApplication对象
    Widget w;//Widget项目创建的时候自动填写类名
    w.show();
    //以上两行代码作用:创建一个控件对象并显示出来
    //.show()方法让控件显现出来
    //.hide()方法让控件隐藏
    return a.exec();//表示让程序运行起来
}

其中对于以下两行代码

cpp 复制代码
Widget w;//Widget项目创建的时候自动填写类名
w.show();

是Widget项目创建的时候自动填写类名

对于Widget对象,show()方法让控件显现出来,hide()方法让控件隐藏

Widge是QWidge的父类,方法由QWidge提供

exec函数表示让程序运行起来,本质上是进程程序替换(Linux中有exec函数,但是没有任何关联)

widget.h

cpp 复制代码
//条件编译
//保证头文件只被包含一次
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget//创建项目的时候选择的父类。使用这个类要包含头文件
{
    Q_OBJECT//Qt内置的宏

public:    //Qt的对象树
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;//与form file密切相关
};
#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"//创建项目生成的头文件
#include "./ui_widget.h"//formfile被编译生成的头文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)//给UI属性创建一个实例
{
    ui->setupUi(this);//显示创建出来的实例
}
//析构函数
Widget::~Widget()
{
    delete ui;
}

其中以下代码的作用是将formfile生成的文件/界面与 widget关联起来

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)//给UI属性创建一个实例
{
    ui->setupUi(this);//显示创建出来的实例
}

widget.ui

formfile是什么呢?就是UI文件,通常在项目中的form文件夹中

双击后调用Qt Designer打开UI文件,进入图形化设计界面

可以拖拽左侧界面进行设计,右侧则是对应控件的属性

进入编辑模式可以看到.ui文件的源代码:

这个格式为.xml格式,与之前学习过的HTML类似。通过使用成对的标签表示数据。标签有哪些,以及标签的含义是程序员自定义的,这些标签的含义是Qt大佬们约定俗成的。

与之相比HTML的标签是由标准委员会严格规定其内容和含义的,各个浏览器也按照这些规定来设计浏览器内核

Qt通过使用xml文件描述界面的样子,通过构建方式(qmake、CMake等等)调用工具生成对应的C++代码,把完整的界面描述出来

工程文件

工程文件一般就是架构文件,对于Qt而言架构有两种:qmake和CMake

其中qmake构建的工程文件为:

cpp 复制代码
QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    widget.h

FORMS += \
    widget.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

Qt引入的模块:

cpp 复制代码
QT       += core gui

C++编译标准:

cpp 复制代码
CONFIG += c++17

当前项目中参与构建的文件(编译器编译哪些文件)。一般不需要主动去修改

bash 复制代码
SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    widget.h

FORMS += \
    widget.ui

以上的.cpp、.h、.ui、.pro均为源代码,除此之外还会生产中间代码

运行一次后,会生成build目录,存放的就是运行中的临时文件

在其中最重要的是这个文件:ui_widget.h

其源代码是这样的。是通过widget.ui的xml代码生成的.h文件

cpp 复制代码
/********************************************************************************
** Form generated from reading UI file 'widget.ui'
**
** Created by: Qt User Interface Compiler version 6.8.3
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

#ifndef UI_WIDGET_H
#define UI_WIDGET_H

#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_Widget
{
public:
    QPushButton *pushButton;

    void setupUi(QWidget *Widget)
    {
        if (Widget->objectName().isEmpty())
            Widget->setObjectName("Widget");
        Widget->resize(800, 600);
        pushButton = new QPushButton(Widget);
        pushButton->setObjectName("pushButton");
        pushButton->setGeometry(QRect(180, 150, 191, 151));

        retranslateUi(Widget);

        QMetaObject::connectSlotsByName(Widget);
    } // setupUi

    void retranslateUi(QWidget *Widget)
    {
        Widget->setWindowTitle(QCoreApplication::translate("Widget", "Widget", nullptr));
        pushButton->setText(QCoreApplication::translate("Widget", "PushButton", nullptr));
    } // retranslateUi

};

namespace Ui {
    class Widget: public Ui_Widget {};
} // namespace Ui

QT_END_NAMESPACE

#endif // UI_WIDGET_H

CMake构建的工程文件为:

cpp 复制代码
cmake_minimum_required(VERSION 3.16)

project(project1 VERSION 0.1 LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(PROJECT_SOURCES
        main.cpp
        widget.cpp
        widget.h
        widget.ui
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(project1
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET project1 APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(project1 SHARED
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(project1
            ${PROJECT_SOURCES}
        )
    endif()
endif()

target_link_libraries(project1 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.project1)
endif()
set_target_properties(project1 PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS project1
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(project1)
endif()

基础配置代码块,设置CMake最低版本要求,定义项目名称、版本和编程语言。

cpp 复制代码
cmake_minimum_required(VERSION 3.16)
project(project1 VERSION 0.1 LANGUAGES CXX)

自动化处理,启用Qt的自动处理功能:

  • AUTOUIC:自动处理.ui文件

  • AUTOMOC:自动处理元对象编译

  • AUTORCC:自动处理资源文件

cpp 复制代码
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

C++标准配置,确定C++编译标准

cpp 复制代码
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

Qt包查找,查找Qt库,优先选择Qt6,支持回退到Qt5,必须包含Widgets组件。

bash 复制代码
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

源文件查找:定义项目的所有源文件、头文件和界面文件。

cpp 复制代码
set(PROJECT_SOURCES
        main.cpp
        widget.cpp
        widget.h
        widget.ui
)

根据Qt版本创建目标:

  • Qt6:使用qt_add_executable并手动完成最终化

  • Qt5:Android平台创建共享库,其他平台创建可执行文件

cpp 复制代码
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(project1 MANUAL_FINALIZATION ${PROJECT_SOURCES})
else()
    if(ANDROID)
        add_library(project1 SHARED ${PROJECT_SOURCES})
    else()
        add_executable(project1 ${PROJECT_SOURCES})
    endif()
endif()

将Qt Widgets库链接到目标。

bash 复制代码
target_link_libraries(project1 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

设置跨平台属性

  • macOS:Bundle标识符、版本信息

  • Windows:设置为GUI应用程序(不显示控制台)

bash 复制代码
set_target_properties(project1 PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

安装配置

bash 复制代码
include(GNUInstallDirs)
install(TARGETS project1
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

完成Qt6目标的最终配置。

cpp 复制代码
if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(project1)
endif()

Qt Hello World 程序

使用按钮实现

在widget.cpp文件中写如下代码

cpp 复制代码
#include "widget.h"//创建项目生成的头文件
#include "./ui_widget.h"//formfile被编译生成的头文件
#include<QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)//给UI属性创建一个实例
{
    ui->setupUi(this);//显示创建出来的实例
    QPushButton *btn=new QPushButton;//创建按钮
    btn->setText("hello world");//按钮上显示文本
    btn->setParent(this);//按钮至于窗口上
}
//析构函数
Widget::~Widget()
{
    delete ui;
}

效果为:

可视化操作实现

(1)双击:" widget.ui " ⽂件

(2)拖拽按钮组件,右键修改属性中的文本

效果为:

标签方式实现

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>//"标签"
#include<QFont>//"字体"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建标签
    QLabel *Ql=new QLabel(this);
    //设置标签内容
    Ql->setText("欢迎您的到来");
    //设置窗口大小
    setFixedSize(1000,900);
    //设置字体
    QFont qf("微软雅黑",64);;
    Ql->setFont(qf);
    //设置显示的位置
    Ql->move(0,300);
    //设置字体颜色
    Ql->setStyleSheet("color:red");
}

Widget::~Widget()
{
    delete ui;
}

效果为:

可视化操作实现

进入方式同上,拖拽标签组件放入界面修改标签内容

效果为:

理论

Qt中每个类都有对应同名的头文件

Qt有两种方式创建对象

cpp 复制代码
QLabel *Ql=new QLabel(this);//堆上创建
QLabel Ql//栈上创建

不过我们更推荐堆上创建对象,并添加参数this。而this就是我们在main.cpp中创建的对象

cpp 复制代码
Widget w;

QString相较于std::string的好处是对字符编码进行了调整

对于QString来说两种构造方式均可以,同时QString对C风格字符串进行了构造。QString被很多Qt内置类包含了,很多时候我们不需要另外包含头文件

cpp 复制代码
Ql->setText("欢迎您的到来");
Ql->setText(QString("欢迎您的到来"));

QLabel代码创建默认显示在左上角,我们可以后续进行调整

Qt编程规范

Qt命名规范

Qt偏好驼峰命名法

驼峰命名法:将多个单词连接在一起,每个单词(除了可能的第一个单词)的首字母大写,使得整个名称看起来像骆驼的峰背一样起伏。

标识符类型 小驼峰命名法示例 大驼峰命名法示例 错误的写法(对比)
变量 studentName totalScore isPassed (不用于变量) studentname (难读) total_score (蛇形命名法) is-passed (烤肉串命名法)
函数/方法 getStudentInfo() calculateAverageScore() saveToDatabase() (不用于普通方法) getstudentinfo() (难读) calculate_average_score() (蛇形命名法)
(不用于类) Student ScoreCalculator DatabaseConnection student (不符合惯例) score_calculator (蛇形命名法)

使用驼峰命名法的原因:

  1. 增强可读性

    这是最主要的原因。相比所有字母都小写(studentaccountbalance),驼峰命名法(studentAccountBalance)通过大写字母清晰地分割了单词,让开发者能一眼看懂变量名的含义,大大提高了代码的可读性。

  2. 遵循编程语言的规范和惯例

    许多主流编程语言社区已经将驼峰命名法作为事实上的标准。

    • Java/C#: 强制或强烈推荐使用小驼峰命名法(变量、方法)和大驼峰命名法(类、接口)。

    • JavaScript: 普遍使用小驼峰命名法(变量、函数)和大驼峰命名法(类、React 组件)。

    • C++ : 风格多样,但驼峰命名法在现代 C++ 和许多大型项目(如 Qt)中非常流行。

      遵循这些惯例能使你的代码更容易被其他开发者理解和接受。

  3. 替代空格

    在编程中,标识符内不允许有空格(student name 是非法语法)。驼峰命名法提供了一种优雅的方式将多个单词连接成一个连贯的、无空格的字符串。

  4. 与蛇形命名法形成对比

    蛇形命名法(如 student_account_balance)使用下划线 _ 分隔单词,在 Python、Ruby 等语言中很常见。驼峰命名法相比之下更简洁,没有额外的下划线字符,使得标识符更紧凑。

Qt快捷键

类别 快捷键 功能描述
通用文件操作 Ctrl + N 创建新文件或项目
Ctrl + O 打开文件
Ctrl + S 保存当前文件
Ctrl + Shift + S 另存为
Ctrl + W 关闭当前文件
Ctrl + Shift + W 关闭所有文件
Ctrl + F4 关闭当前文档(同 Ctrl + W
编辑与导航 Ctrl + F 查找/替换面板
F3 / Shift + F3 查找下一个/上一个
Ctrl + G 跳转到指定行
Ctrl + L 跳转到指定行(另一种方式)
Ctrl + K 定位器 - 快速搜索并跳转到文件、类、方法等
F2 跟随符号 - 跳转到变量/函数/类的定义处
Shift + F2 在声明和定义之间切换(用于C++头文件和源文件)
F4 在头文件(.h)和源文件(.cpp)之间切换
Alt + Left / Alt + Right 向前/向后导航浏览历史
Ctrl + Shift + R 重命名符号 - 安全地重命名变量、函数等(重构)
Ctrl + Space 代码自动完成(可能被系统输入法占用)
Ctrl + / 注释/取消注释当前行或选中行(单行注释)
Ctrl + Shift + / 注释/取消注释选中区块(块注释)
Tab / Shift + Tab 增加/减少选中代码的缩进
Ctrl + Shift + Up / Down 将当前行或选中行向上/向下移动
Ctrl + D 复制当前行或选中行
Ctrl + Shift + L 格式化当前文件代码(需要配置代码风格)
构建与运行 Ctrl + B 构建当前项目
Ctrl + R 编译并运行当前项目
Ctrl + Shift + B 选择构建配置并构建
F5 开始/继续调试
F10 单步跳过(Step Over)
F11 单步进入(Step Into)
Shift + F11 单步跳出(Step Out)
F9 切换断点
Shift + F5 停止调试
界面管理 Ctrl + Shift + F11 切换全屏模式
Esc 将焦点从任何窗格移回编辑器
Alt + 0 显示/隐藏编译输出窗格
Alt + 1 显示/隐藏项目/文件窗格
Alt + 2 显示/隐藏大纲窗格(当前文件的类/函数结构)
Alt + 3 显示/隐藏搜索结果窗格
Alt + 4 显示/隐藏编译问题窗格
Alt + 5 显示/隐藏应用程序输出窗格
Alt + 9 显示/隐藏版本控制窗格(如 Git)
帮助 F1 在光标位于某个类或函数上时,按F1可查看上下文帮助
Shift + F1 打开Qt Assistant(独立的帮助文档浏览器)

帮助文档

打开帮助⽂档有三种⽅式. 实际编程中使⽤哪种都可以.

1、光标放到要查询的类名/⽅法名上, 直接按 F1

2、Qt Creator 左侧边栏中直接⽤⿏标单击 "帮助" 按钮:

3、在Qt Creator安装路径中找到assistant.exe。

点击后进入索引,就可以进行搜索

举例说明:索引处搜索QLabel

对象树

类似于前端开发的对象树,Qt开发也有属于自己的对象树

Qt 的对象树是一种基于父子关系的对象管理机制。它通过建立对象之间的父子关系,形成一个层次化的树状结构,并利用这个结构来自动化管理对象的生命周期,特别是内存的释放。

当一个对象被创建时,它可以指定另一个对象作为其父对象。这个简单的动作,就在两者之间建立了牢固的父子链接。

如下面的代码中,如果用堆创建对象那么在合适的时机(如窗口销毁/关闭)由Qt对象树统一销毁。

cpp 复制代码
QLabel *Ql=new QLabel(this);//堆上创建
QLabel Ql//栈上创建

在 Qt 中创建很多对象的时候会提供⼀个 Parent 对象指针,下⾯来解释这个 parent 到底是⼲什么的。

QObject 是以对象树的形式组织起来的

当创建⼀个 QObject 对象时,会看到 QObject 的构造函数接收⼀个 QObject 指针作为参数,这个参数就是 parent,也就是⽗对象指针。

这相当于,在创建 QObject 对象时,可以提供⼀个其⽗对象,我们创建的这个 QObject 对象

会⾃动添加到其⽗对象的 children() 列表。

当⽗对象析构的时候,这个列表中的所有对象也会被析构。(注意,这⾥的⽗对象并不是继承意义上的⽗类!)

这种机制在 GUI 程序设计中相当有⽤。例如,⼀个按钮有⼀个 QShortcut(快捷键)对象作为其⼦对象。当删除按钮的时候,这个快捷键理应被删除。这是合理的。

QWidget 是能够在屏幕上显示的一切组件的父类

QWidget 继承⾃ QObject ,因此也继承了这种对象树关系。⼀个孩⼦⾃动地成为⽗组件的⼀

个⼦组件。因此,它会显⽰在⽗组件的坐标系统中,被⽗组件的边界剪裁。例如,当⽤⼾关闭

⼀个对话框的时候,应⽤程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该

⼀起被删除。事实就是如此,因为这些都是对话框的⼦组件。

当然,我们也可以⾃⼰删除⼦对象,它们会⾃动从其⽗对象列表中删除。⽐如,当我们删除了⼀个⼯具栏时,其所在的主窗⼝会⾃动将该⼯具栏从其⼦对象列表中删除,并且⾃动调整屏幕

显⽰

Qt 引入对象树的概念,在一定程度上解决了内存问题

当⼀个 QObject 对象在堆上创建的时候,Qt 会同时为其创建⼀个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。

任何对象树中的 QObject 对象 delete 的时候,如果这个对象有 parent,则⾃动将其从 parent 的children() 列表中删除;如果有孩⼦,则⾃动 delete 每⼀个孩⼦。Qt 保证没有 QObject 会被delete 两次,这是由析构顺序决定的。

如果 QObject 在栈上创建,Qt 保持同样的⾏为。正常情况下,这也不会发⽣什么问题。来看下⾯的代码⽚段:

cpp 复制代码
QWidget window;
QPushButton quit("Quit",&window);

作为⽗组件的 window 和作为⼦组件的 quit 都是 QObject 的⼦类(事实上,它们都是QWidget的⼦类,⽽QWidget 是 QObject 的⼦类)。

这段代码是正确的,quit 的析构函数不会被调⽤两次,因为标准 C++ 要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作⽤域时,会先调⽤ quit 的析构函数,将其从⽗对象 window 的⼦对象列表中删除,然后才会再调⽤ window 的析构函数。

但是如果这样写的话

cpp 复制代码
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);

情况⼜有所不同,析构顺序就有了问题。

我们看到,在上⾯的代码中,作为⽗对象的 window 会⾸先被析构,因为它是最后⼀个创建的对象。在析构过程中,它会调⽤⼦对象列表中每⼀个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执⾏,在 window 析构之后,quit 也会被析构,因为 quit 也是⼀个局部变量,在超出作⽤域的时候当然也需要析构。但是,这时候已经是第⼆次调⽤ quit 的析构函数了,C++ 不允许调⽤两次析构函数,因此,程序崩溃了。

因此我们发现Qt 的对象树机制虽然在⼀定程度上解决了内存问题,但是也引⼊了⼀些值得注意的事情。这就印证了前面说的创建对象最好还是在堆区创建

在 Qt 中,尽量在构造的时候就指定 parent 对象,并且⼤胆在堆上创建

Qt的对象树

对象树确保的是先释放子节点的内存, 后释放父节点的内存.

而析构函数的调⽤顺序则不⼀定遵守上述要求. 因此看到⼦节点的析构执⾏顺序反⽽在⽗节点析构顺序之后.

Qt窗口坐标系

坐标体系:以左上⻆为原点(0,0),X向右增加,Y向下增加。

对于嵌套窗⼝,其坐标是相对于⽗窗⼝ 来说的。

举例说明:

widget.h

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<QPushButton>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"
#include "./ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton *btn1=new QPushButton(this);
    btn1->setText("这是一个按钮1");
    btn1->move(100,100);
    QPushButton *btn2=new QPushButton(this);
    btn2->setText("这是一个按钮2");
}

Widget::~Widget()
{
    delete ui;
}

效果为:

假设有父部件 Parent 和子部件 Child

函数 (在 Child 上调用) 作用 示例
mapToParent(const QPoint &point) 子部件本地坐标系 中的一个点,映射到父部件坐标系中。 Child->mapToParent(QPoint(0, 0)) 返回 Child->pos()
mapFromParent(const QPoint &point) 父部件坐标系 中的一个点,映射到子部件本地坐标系中。 上述过程的逆操作。
mapToGlobal(const QPoint &point) 子部件本地坐标系 中的一个点,映射到全局屏幕坐标系中。 用于获取一个控件在屏幕上的绝对位置,例如显示工具提示。
mapFromGlobal(const QPoint &point) 全局屏幕坐标系 中的一个点,映射到子部件本地坐标系中。 常用于鼠标事件中,将全局鼠标坐标转换为部件内的本地坐标

窗口大小

窗口大小主要通过以下几个函数获取,它们都描述了客户区(内容区域)

函数 描述 返回值
width() 返回窗口内容区域的宽度。 int
height() 返回窗口内容区域的高度。 int
size() 返回一个 QSize 对象,包含 width()height() QSize
rect() 返回一个 QRect,位于本地坐标系中,为 QRect(0, 0, width(), height()) QRect
cpp 复制代码
QWidget window;
window.resize(400, 300); // 设置内容区域的大小为 400x300

qDebug() <<"屏幕宽度" <<window.width();  // 输出:400
qDebug() <<"屏幕高度"<< window.height(); // 输出:300
qDebug() <<"屏幕尺寸"<< window.size();   // 输出:QSize(400, 300)
qDebug() <<"屏幕本地坐标系"<< window.rect();   // 输出:QRect(0,0 400x300)

结果为:

相对位置

相对位置描述了一个部件相对于其父部件的位置。

函数 描述 返回值
pos() 返回部件内容区域左上角在其父部件坐标系中的位置。 QPoint
x() 相当于 pos().x() int
y() 相当于 pos().y() int
geometry() 返回一个 QRect,描述部件在其父部件坐标系中的位置和大小,等于 QRect(pos(), size()) QRect

编程中的汉字存储字节讨论

汉字中有几个字节?这是一个很经典又很容易错的题目。具体的字节数是错误的,因为一个汉字有几个字节取决于具体的字符编码标准,不同的字符编码标准下是不一样的

编码标准 英文/数字(1个字符) 常见汉字(1个字符) 特点与说明
ASCII 1 字节 无法表示 最早的编码,只包含英文字母、数字和控制符。
GB2312 / GBK 1 字节 2 字节 中国国家标准,向下兼容ASCII。是早期Windows中文系统的默认编码。
UTF-8 1 字节 通常 3 字节 当今互联网和跨平台领域的绝对主流。是一种变长编码。
UTF-16 2 字节 (基本多文种平面) 2 字节 (基本多文种平面) Java、JavaScript内部、Windows API常用。同样是变长编码,但对于绝大多数汉字是2字节。
UTF-32 4 字节 4 字节 定长编码,处理简单但空间浪费严重,不常用。
Unicode 码点 - 1 个逻辑单元 这不是编码,是字符集。汉字码点范围广泛,如U+4E25(严)。

详细角度分析

角度一:从历史与地域编码标准看(GB2312/GBK)

这是中国本土发展出来的编码方案,为了解决ASCII无法表示中文的问题。

  • 核心原理 :采用双字节编码,并向下兼容ASCII。

    • 当一个字节的值小于128(0x80)时,它就表示一个ASCII字符。

    • 当一个字节的值大于127(0x80)时,它就和下一个字节组合在一起,共同表示一个汉字(或其它全角符号)。

  • 字节数 :所以,在GB2312或GBK编码下,一个汉字固定占用 2 个字节

  • 现状:虽然现在全球趋势是UTF-8,但在一些遗留系统、中文文件或特定场景(如某些数据库环境)中仍可能遇到。

角度二:从现代国际统一标准看(Unicode 与 UTF-8)

为了统一全球所有字符的编码,Unicode字符集诞生了。但Unicode本身只定义了字符到数字(码点)的映射,如何存储这个数字则需要具体的编码方式,最著名的就是UTF-8

  1. Unicode 码点

    • 它给世界上几乎所有字符都分配了一个唯一的编号(例如,汉字"严"的码点是 U+4E25)。

    • 这个码点本身是一个逻辑概念,不涉及存储。它告诉你"这个字是哪个",但不规定"如何在计算机里存它"。

  2. UTF-8 编码

    • 核心原理变长编码,非常聪明地平衡了兼容性和效率。

    • 规则

      • ASCII 字符(U+0000 到 U+007F)用 1 个字节存储,这与ASCII完全一致。

      • 带有变音符号的拉丁文、希腊文、西里尔字母等用 2 个字节

      • 绝大多数常用汉字(属于"基本多文种平面"U+0000 ~ U+FFFF)使用 3 个字节 。例如,"严"的UTF-8编码是 E4 B8 A5,正好3个字节。

      • 非常生僻的汉字、表情符号等(在辅助平面)会占用 4 个字节

    • 字节数 :因此,在当今最主流的UTF-8编码下,一个汉字通常占用 3 个字节

角度三:从其他Unicode编码方式看(UTF-16 / UTF-32)
  • UTF-16:

    • 也是变长编码。对于绝大多数位于"基本多文种平面"的字符(包括几乎所有的现代常用汉字),它使用 2 个字节表示。

    • 对于非常生僻的字符,它会使用4个字节(一对"代理对")。

    • 所以,在UTF-16中,你遇到的汉字大概率是 2 个字节

  • UTF-32:

    • 最简单粗暴的定长编码。每个Unicode码点都用固定的 4 个字节来存储。

    • 优点:处理简单,因为每个字符长度相同。

    • 缺点:极其浪费存储空间和网络带宽,因此在实际应用中很少见。

角度四:从编程与实践角度看

在编程中,混淆编码是常见错误的根源。

  • 字符串长度函数strlen("汉") 在C语言中返回的是字节数,而不是字符数。如果文件是UTF-8编码,这个结果是3;如果是GBK,结果是2。

  • 现代编程语言 :如Python 3,字符串内部使用Unicode表示。len("汉") 返回的是字符数 1,因为它已经将字节序列解码为抽象的字符。你需要关心编码主要是在进行文件I/O(读/写)网络传输时。

一个生动的例子:

汉字"严"

  • 它的 Unicode 码点U+4E25(一个逻辑编号)。

  • UTF-8 编码 下,它被存储为三个字节:E4 B8 A5

  • GBK 编码 下,它被存储为两个字节:D1 CF

  • UTF-16LE 编码 下,它被存储为两个字节:25 4E

如果你用错误的编码方式去打开一个文本文件(例如用GBK编码去打开一个UTF-8保存的文件),就会产生我们常说的乱码

总结

视角 核心答案 关键决定因素
历史/地域 2 字节 (GBK/GB2312) 操作系统的区域设置,文件的保存格式。
现代/国际 通常 3 字节 (UTF-8) 当今Web、Linux、macOS及跨平台应用的事实标准。
特定系统/语言 通常 2 字节 (UTF-16) Java、Windows NT内核内部、.NET字符串。
理论存储 4 字节 (UTF-32) 仅用于特殊需求,不通用。

强烈推荐在所有场景下(网页、数据库、文件存储、数据传输)都使用 UTF-8 编码

因此未来工作的时候,最普遍和未来的答案是:通常是 3 个字节

本期内容就到这里了,喜欢请点个赞谢谢

封面图自取:

相关推荐
暮之沧蓝2 小时前
Vue总结
前端·javascript·vue.js
oioihoii2 小时前
超越 std::unique_ptr:探讨自定义删除器的真正力量
c++
Gohldg3 小时前
C++算法·贪心例题讲解
c++·数学·算法·贪心算法
木易 士心3 小时前
Promise深度解析:前端异步编程的核心
前端·javascript
Le1Yu3 小时前
2025-10-7学习笔记
java·笔记·学习
im_AMBER3 小时前
Web 开发 21
前端·学习
又是忙碌的一天3 小时前
前端学习day01
前端·学习·html
韩立学长3 小时前
【开题答辩实录分享】以《基于python的奶茶店分布数据分析与可视化》为例进行答辩实录分享
开发语言·python·数据分析
Joker Zxc3 小时前
【前端基础】20、CSS属性——transform、translate、transition
前端·css