【C++ Qt】Hello World、初始信号槽、理解对象树 ~~~(通俗易懂 图文并茂)


每日激励:"不设限和自我肯定的心态:I can do all things。 --- Stephen Curry"
**绪论​:
本章是Qt的第二篇,带你认识Qt中几个简单的控件如何实现,以及通过信号槽的方式实现一定的用户和程序的联动,还有许多细节如:对象树、信号槽逻辑图、Qt的命名规范、帮助文档的使用、Qt窗口中的坐标系。

早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。**

初始 Qt 程序

1. Qt中使用控件的两种方式:

  1. 图形化界面的方式,通过在界面上创建出一个控件显示
    1. 打开widget.ui文件
    2. 选择Display Widget 中的 Label控件
    3. 将它拉到界面上,后在内部输入英文即可(如上图)

附:

  • 其中右上角会显示出界面上有那些控件
  • 上述拖拽的QLabel控件的方式,本质其实是在 ui 文件中的xml中就会出现一段代码
  • 当 qmake 在编译项目时,基于这个xml内容生成一段C++代码,从而构建出界面的内容(qt自动完成)
  • 细节:Qt就会根据xml代码生成新的widget.h代码(就和label有关,具体如下图)
  1. 通过纯代码(如下)的方式,通过编写的代码,在界面创建控件显示
    1. 一般通过代码来构件界面的时候,退出会把构造界面的代码放到 Widget(这样一开始就会创建) 的构造函数中
    2. 创建QLabel对象并new构造(堆上创建),并传递this参数(给当前这个label对象指定一个父对象,可以不指定但一般都会加上,后面马上细说)
    3. 其中注意需要包含头文件(错误如下),Qt每个类都有一个对应的同名头文件
      1. #include <QLabel> 其中发现会有一个qlabel.h头文件这是古老风格的头文件
      2. 在1998年后 C++标准成立了 C++98 规定了 统一使用 #include <cstdio> 替换以前的 stdio.h
      3. 其中当我们使用某个类时要记住包含头文件,若果没有报错可能是其他类中包含过了

1.1 label 标签:界面上一个用来显示内容的字符串控件

  1. 给label设置文本 setText 设置控件中显示的文本
    void setText(const QString &)
  • 其中的 QString 其实就是 qt 的 std::string
  • QString 会对编码进行一定的处理,所以比std::string 舒服一点点,因为在Qt中经常可能出现编码问题
  • 它是 Qt 自己开发的基础类(因为当年c/c++中的字符串都不好用)
  • 其中还有一些其他的 动态数组 QVector QList QMap,
  • 在现在编写Qt代码过程中 可以使用Qt的这一套、也能使用 C++标准库的!

但这个代码 new 了对象后我们不进行delete,那岂不是内存泄漏了?


2. 对象树

其实上述代码在Qt中并不会出现内存泄漏,之所以能够释放,因为把这个对象挂到了对象树上,前端开发(网页开发)也涉及到类似的对象树(DOM)。本质上也是一个树形结构(N叉树),通过把树形结构把界面上的各种元素组织起来(具体如下图)

  • 其中当有了这个对象树后,其内部的对象就会统一放到树形结构上
  • 那么它们的释放也会进行和树一样的统一,也就是都会在这个界面关闭/销毁的时候释放(具体情况如下图)
  • 其中若某个对象提前delete释放了,那么是不是代表他就不会在显示在界面中了
  • 此处类new的时候添加了this参数,就会将这个类挂在对象树上
  • 但假如放到栈上 (直接创建变量,那么就可能出现提前释放的问题/不会显示),此时可以看到无法显示(label对象随着构造结束而销毁)

2.1 验证对象树:

创建新文件:C++文件

  1. 选择 C++ class

  2. 填写类名和继承的父类

  3. next即可

注意事项:

  • 其中生成的文件可能会有一点点问题(此时我们就自己包含头文件就OK了:#include <QLabel>):

  • 将 MyLabel构造初始化上 QWidget * parent

小技巧: f4 快速切换头文件和对应的 .cpp文件(Vim中 :A 的方式完成切换、:AT 的方式打开新标签页)

  • 切换到对应的 .cpp 文件中,设置构造函数:

    1. 接收外部传递进来的父类,然后进行初始化列表将parent参数给到父类
    2. QLabel的构造函数初始化(如下图),这里麻烦的就是需要调用父类的构造函数,才能让自己类的对象加入到Qt对象树中
  • 此时自定义析构函数,在析构函数中添加一个打印,查看自动释放时情况(注意需要先在类中添加公共的析构声明)

  • 打印日志:

小技巧:在创建的类的头文件中,快速的创建.cpp中的析构,将光标放到析构上 点击 alt 键选择 + enter即可

创建自己的对象并调用

运行结果:

发现打印了 乱码(出现的乱码原因有且只有一个 就是编码方式不匹配,查看那些环节涉及了编码)

一个汉字,占几个字节?

不同的中文编码(字符集 )的不同大小就不同

主要的表示汉字字符集主要是两种方式:

  1. GBK(使用2byte表示一个汉字)Windows默认是这个
  2. utf-8 变长编码,表示一个符号,使用的字节数有变化有变化 2~4,但一般是3byte(Linux默认)
    一个汉字具体的utf8/gbk编码的数值是多少,可以通过一些在线工具查看

    而 Qt Creator 内置的终端不是utf8所以就出现了乱码
    当前表示中文,主流的方式,还是utf8(支持各种语言文字)

解决办法:

  1. Qt中 有 QString可以帮我们自动处理编码问题!
  2. Qt 还提供了专门用来打印日志的工具 qDebug(),也能自动处理编码问题

具体如下(使用QDebug来代替cout):

最终结果:

  • 使用qDebug,还有一个好处:打印的调试日志,是可以统一关闭(不希望用户看到的日志),可以通过编译开关,一键式关闭

小结:

  1. 认识 QLabel 类,能够在界面上显示字符串
    • 通过setText设置,参数 QString(Qt 中把C++中的很多容器进行了重新封装)
  2. 内存泄漏 / 文件资源泄漏(申请了内存、文件就一定需要关闭!)
  3. 对象树:Qt中通过对象树统一的释放界面的控件对象
    • Qt中还是推荐使用new的方式在堆上创建对象,通过对象树,统一释放对象
    • 创建对象的时候,在构造函数中,指定父对象(此时才会挂到对象树上)
    • 如果你的对象没有挂到对象树上,就必须手动释放!
  4. 通过继承子 Qt 内置的类,就可以达到对现有的控件进行功能的扩展效果
    • 在析构函数中,加上日志,直观的观察到对象释放的过程
    • 面向对象的"继承",本质上就是对现有代码进行 "扩展"
  5. 乱码问题 和 字符集问题
  6. 如何在Qt中打印日志,作为调试信息
    • Qt 中更推荐使用 qDebug() 完成日志的订阅
    • 虽然cout也可以,但并不是好的方法

调试的本质是观察程序执行过程和中间的结果


下面还有几种实现hello world的方式:


3. 使用编辑框控件实现 helloworld

  1. 使用方法同上的QLabel,可以直接在ui文件中拖拽的方式放上界面
  2. 同理也能使用纯代码的方式实现,在widget.cpp中的构造函数中实现:
  3. 还能使用多行编辑框QTextEdit(这里就不过述了因为单行即可完成)

4. 使用按钮的方式来创建 Push Button(普通按钮)

  1. 拖拽(双击按钮修改内部的字符)

初始信号槽

如何让按钮点击后出现效果,就要使用Qt的信号槽(后面篇章会细说,这里了解即可)

  1. Qt 中的 connect 的作用是连接信号和槽(和tcp中的建立连接没有任何关系)
  2. 也就是使用connect实现该按钮控件的点击后的触发效果
  3. 本质也就是给这个按钮连接一个函数
    第一个参数:指定界面文件中访问的按钮(写成ui->pushButton(pushButton就是该控件的ObjectName属性,在ui文件中可以选中控件在右边属性段查看))

    第二个参数:函数指针 点击按钮触发的一个信号(QPushButton::clicked点击信号,当点击该按钮时就会自动触发)
    第三个参数:指定谁处理这个信号(一般来说就是this,当前类对象)
    第四个参数:这个信号怎么处理(一般来说写成类成员函数)
  4. 实现 handleClick
    1. 首先在widget.h头文件中声明新函数 handle
    2. 再在widget.cpp中添加新函数的具体实现
    3. 判断当前按钮中的值是否为:hello world
    4. 若为则 切换为 hello qt、反之若不是则为hello world(代表为hello qt)

其中注意的是:能使用 ui -> pushButton 它是因为当你添加了控件,就会生成一个标识objectName的标识,他就会在Ui::Widget中生成该控件成员,这样就能通过ui访问到

其中的为什么能访问到是因为ui_widget.h的UI::Widget类就会包含该成员对象(它是Qt自动生成的文件中根据ui界面的xml生成的代码)

  1. 其中对于PushButton来说他会默认生成的标识为:pushButton
  2. 我们也能对他进行修改,从而达到使用自己的名称来进行指定
  3. 它的本质其实就是通过这个名称在ui_widget.h(ui类文件)文件中生成一个相同名称的成员变量,这样就能在 ui -> ...

信号槽的总体逻辑图:

Ui::Widget的逻辑过程:


纯代码的方式创建:

  1. 创建QPushButton类成员变量 myButton(设置为Widget的成员变量这样就能跨函数(构造函数和触发函数)使用)
  2. 具体实现:
    1. 和拖拽的一样,只不过此时再使用PushButton对象时直接获取成员变量即可,不用通过ui对象获取了!

源码:

widget.h:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H
#include <QPushButton>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

    void handle();

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

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include "mylabel.h"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

//    QLabel lable;//不能定义成局部的,因为这个是在栈上创建,退出函数就会删除
//    QLabel* lable2 = new QLabel(this);
//    lable.setText("777");
//    lable2->setText("666");

    MyLabel* mylabel = new MyLabel(this);
    mylabel->setText("这是自己的Label");
    //四个参数
    // 参数1:收到信号的地方、参数2:信号类型、参数3:处理的地方、参数4:处理方法
//    connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handle);

    mypushButton = new QPushButton(this);
    connect(mypushButton,&QPushButton::clicked,this,&Widget::handle);
    mypushButton->setText("hello world");
}

Widget::~Widget()
{
    delete ui;
}
void Widget::handle()
{
    if(mypushButton->text() == QString("hello world")){
        mypushButton->setText("hello qt");
    }else{
        mypushButton->setText("hello world");

    }
}


//void Widget::handle()
//{
//    if(ui->pushButton->text() == QString("hello world")){
//        ui->pushButton->setText("hello qt");
//    }else{
//        ui->pushButton->setText("hello world");

//    }
//}

拖拽和自己实现的区别:

  • 而在实际开发中,这两种都很主要,难分主次
  • 当前这个界面内容比较固定,就会以图形化的方式构建
  • 但如果你的程序界面,经常要动态变化,就会以代码的方式构造界面
  • 那种方便就用那种,这两种方式还能配合使用

Qt中的命名规范(和C++中差不多)

给变量/函数/文件/类 起名字,需要注意

  1. 起的名字有描述性,不要使用如 a 、 b ... 的无规律的名字
  2. 如果名字较长,有多个单词构成,需要使用适当的方式来进行区分不同单词
    1. 蛇形命名法:C++/Linux中的 _(unordered_map...)C/C++、Python偏好使用
    2. 驼峰命名法:Qt 中偏好使用大写字母来进行单词分割,如小驼峰:studentCount(一般是变量或函数)、大驼峰QApplication(一般是类)...(Java/JS/Go偏好使用,也更广泛)
    3. 对于这几种命名法,不存在好坏,对于它的使用就根据自己公司的风格即可

使用帮助文档 F1

  1. 光标放到要查询的类名/⽅法名上, 直接按 F1
  2. Qt Creator 左侧边栏中直接⽤⿏标单击 "帮助" 按钮:
  3. 也能找到 Qt Creator 的安装路径,在 "bin" ⽂件夹下找到 assistant.exe,双击打开

Qt 窗口坐标体系

坐标体系:以左上⻆为原点(0,0),X向右增加,Y向下增加。 (平面直角坐标系(笛卡尔坐标系))

Qt 的某个控件的位置,对于某个控件来说,坐标系原点就是相对于父窗口/控件的

代码控制:

  1. 创建QButton对象,调用内部设置值setText

  2. 其中能使用move的方式进行移动位置

  3. 对于move移动时,内部参数就是坐标的值(单位是像素)

    1. 显示器本质上就是有一堆 发光的小亮点/小灯泡构成(也就是像素)
    2. 其中在我们电脑中的分辨率本质就是 横向和水平方向的像素个数
    3. 如 1920 * 1080 (水平 1920 垂直 1080 个像素(亮点))
    4. 其中越好的显示器亮点越多,画面越好
    • 其中水平就是 200 像素、垂直就是 300像素
  • 其中还能设置窗口的位置(它没有父元素,也就是整个桌面为坐标系原点)通过this指针的方式

本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量Qt细致内容,早关注不迷路。

相关推荐
lllsure8 分钟前
Python基础语法
开发语言·python
zxctsclrjjjcph1 小时前
【高并发内存池】从零到一的项目之centralcache整体结构设计及核心实现
开发语言·数据结构·c++·链表
zm2 小时前
服务器多客户端连接核心要点(1)
java·开发语言
炯哈哈2 小时前
【上位机——MFC】单文档和多文档视图架构
开发语言·c++·mfc·上位机
FuckPatience2 小时前
关于C#项目中 服务层使用接口的问题
java·开发语言·c#
利刃大大2 小时前
【网络编程】四、守护进程实现 && 前后台作业 && 会话与进程组
linux·网络·c++·网络编程·守护进程
编程轨迹_2 小时前
使用 Spring 和 Redis 创建处理敏感数据的服务
java·开发语言·restful
oioihoii2 小时前
C++23 std::tuple与其他元组式对象的兼容 (P2165R4)
c++·链表·c++23
赵和范3 小时前
C++:书架
开发语言·c++·算法
boooo_hhh3 小时前
第J7周:对于ResNeXt-50算法的思考
开发语言·python·深度学习