【非常详细!】QT基础【二万字长文】

简介: 【非常详细!】QT基础【二万字长文】



1 QMake

是QT中独有的代码构建工具,一般来说,没有特殊要求,创建QT项目都是使用QMake

2 Qt中三个窗口部件的区别

2.1 QMainWindow

包含菜单栏、工具栏、状态栏

2.2 QWidget

一个普通的窗口,不包含菜单栏、状态栏,除了登录界面,新建项目时建议使用awidget,因为大部分的窗口可能都要做成无边框窗口,需要自定义标题栏,实现拉伸等。因此QMainwindow使用的场景不多

2.3 QDialog

对话框,常用来做登录窗口、弹出窗口(例如设置界面

3 Visual Studio的QT项目与QtCreater项目相互转换

3.1 QtCreater项目转VS项目

  1. 环境变量配置
    将qmake.exe所在的目录到系统path。该Exe在前面也配置过。我的电脑中,这两个EXE的路径分别为:

D:\VisualStudio\QT\5.15.2\msvc2019\bin\qmake.exe

D:\VisualStudio\QT\5.15.2\msvc2019_64\bin\qmake.exe

想转32位的项目就用第一个,想转64位项目的就用第二个。一般来说是用32位。将其添加到系统环境变量中,如图。

再将cl.exe添加到环境变量。我的系统中,32位的cl.exe路径为

D:\VisualStudio\2022\VC\Tools\MSVC\14.34.31933\bin\Hostx86\x86\cl.exe

而64位的cl.exe路径为:

D:\VisualStudio\2022\VC\Tools\MSVC\14.34.31933\bin\Hostx64\x64\cl.exe

假如你选择转换成32位项目,就用32位的cl.exe,64位项目用64位的cl.exe。将其添加到环境变量

  1. 转化命令
qmake -tp vc 项目名称.pro

我之前已经创建了项目:

在控制台输入qmake -tp vc the_first_project.pro,即可完成转换,如图:

  1. vs打开项目

转换成功后,原文件夹中生成一个.vcxproj文件,使用VisualStudio打开,

32位的项目必须用x86的平台去打开,但是我没有这个选项,应该需要额外配置(尚未解决)

3.2 VS项目转QtCreater项目

未出现相关的菜单。尚未解决。

相关资料:

将VS工程转为Qt的pro工程及VS安装Qt插件后没有create basic .pro file菜单问题解决_使用vs2019生成pro文件-CSDN博客

4 Qt学习方法

  1. 多看Qt文档,自己多写代码测试,英文看不的,用有道翻译

Qt文档中文版链接:提取码:ikun

  1. 学习Qt提供的demo,自己调试,运行,分析

Qt Creator中有很多示例。点开可以运行,还可以查看代码。

  1. Qt主要是用来做界面开发,要熟悉Qt设计界面的技巧,掌握各种控件的使用,非常熟悉各种布局的实现方案,既要会用Qt设计器进行布局,又要非常熟悉手写布局。实际项目中,一般都是手写布局。
  2. 熟悉无边框窗口的设计;
  3. 需要有一点界面交互的逻辑,例如自定义非标控件,以及各种样式表的使用;
    学习相关领域的知识,例如:图形图像、音视频、动画、Web交互、http编程、加解密;
  4. 多总结,要有自己的代码库,以后遇到相同的模块需求,可以复用自己的代码;

5 QT入门:实现一个简易的图片查看程序

5.1 布局思路

QLabel用来显示图片,QLabel显示"文件路径"

选中三个控件,右键->布局->水平布局

选中整体的空白处,右键->布局->垂直布局

选中容器,可以找到layoutStrech,设置其为1,8。也就是上面的3个空间所占空间与下面显示图片的label所占空间之比为1:8。

5.2 选择文件功能实现

界面功能都是.h头文件中声明,.cpp文件中实现。

private slots: //表示这是一个槽函数,private表示不可以被别的类使用
    void on_btnSelect_clicked(); //按钮被按下的操作。btnSelect为按钮的名字。clicked表示激活该功能的按钮的动作为点击

绑定槽函数也可以使用connect函数实现,具体在后面的博客中可以看到

源文件中可以先进行调试,例如:

// 需要包含#include<qDebug>头文件
void Widget::on_btn_file_select_clicked(){
    qDebug()<<"clike the button"<<endl; //创建一个匿名对象,并输出一句话
}

可以在控制台看到正常输出的语句。

而为了实现选择文件的功能,需要使用QFIleDialog类。

且我们要让显示的图片能显示,需要使用

5.3 QT中的配置文件

5.3.1 配置文件格式

以[]表示一个组(group),组中有多个键值对,等号左边是key,等号右边是value。如:

[main]
CSDN_NAME=godspeed_lucip
CSDN_TAG=C++
[other]
CSDN_DISCRIPTION=Sharing
[QT]
QT_CLASS=QSettings
QT_INI=config.ini

5.3.2 配置文件读写

配置文件可以用QSettings读写。

  • 主要使用两个函数
  • QVariant value(const QString &key, const QVariant &defaultValue = QVariant())
  • 第一个参数是键
  • 第二个参数是默认值,也就是说如果没有读到值,就返回该默认值,如填-1 ,就返回-1
  • 返回值是QVariant。在Qt中,QVariant是一个通用的值容器,它可以存储任意类型的数据,例如整数、字符串、列表等等。它的主要作用是提供一种通用的数据类型,方便在不同的函数、类、模块之间传递数据。
  • void setValue(const QString &key, const QVariant &value)
  • 第一个参数是键(group+key)
  • 第二个参数是值

5.4 记住上次打开的文件位置

每次打开文件都会从默认的文件路径中打开,因此需要实现这样的功能:按下选择文件按钮,可以直接跳转到上次打开的文件路径。实现此功能需要配置文件。

打开项目的配置目录,我的项目的路径为:

D:\VisualStudio\QtPrograms\ch1_7

则项目配置目录为:

D:\VisualStudio\QtPrograms\build-ch1_7-Desktop_Qt_5_15_2_MSVC2019_32bit-Debug\debug

新建一个config目录。再新建一个Settings.ini配置文件,创建group为last_path,添加键值对:

此处写或不写都可以,因为最后在写入时也会进行创建

[last_path]
path=

5.4.1 qApp

qApp是一个指向QApplicationQGuiApplication对象的全局指针,可以理解为就是指向窗口的指针。

QApp->appllicationDirPath()得到的路径始终都是可执行文件所在的绝对路径。而可执行文件实际上就在build-ch1_7-Desktop_Qt_5_15_2_MSVC2019_32bit-Debug\debug文件路径中。

5.4.2 QStandardPaths

系统标准路径类,也就是用户的特定目录或系统的配置目录。比如在Windows系统中的“我的文档”,“视频”,“图片”等目录位置。

不同的操作系统中,其标准路径都不一样,但是在Qt中,我们却可以通过这个类,直接获取到对应路径(就算没有也会被创建)。

共有两种方式获取到路径:

//都是静态方法    
static QString writableLocation(StandardLocation type);
static QStringList standardLocations(StandardLocation type);
//下面列举出标准路径的枚举
enum StandardLocation {
        DesktopLocation, //桌面路径
        DocumentsLocation, //文档路径
        FontsLocation, //字体路径
        ApplicationsLocation,
        MusicLocation,
        MoviesLocation,
        PicturesLocation, //图片路径
        TempLocation,
        HomeLocation,
        DataLocation,
        CacheLocation,
        GenericDataLocation,
        RuntimeLocation,
        ConfigLocation,
        DownloadLocation,
        GenericCacheLocation,
        GenericConfigLocation,
        AppDataLocation,
        AppConfigLocation,
        AppLocalDataLocation = DataLocation
    };
    Q_ENUM(StandardLocation)

参考:系统标准路径类详解——QStandardPaths - 知乎 (zhihu.com)

5.5 让图片自适应

假设要让一个QLabel来显示一张图片。大致思路是重新设置图片和label的大小,比设置图片居中即可。

为了保证图片保持原长宽比,可以计算图片宽与label宽、图片长和label长的比值,选择较小的比值作为最终的缩放因子(如果选择较大的比值,就会导致图片放缩之后过大)。

之后根据缩放因子调整图片的大小即可。具体做法见代码注释。

5.6 程序效果

5.7 完整代码

widget.cpp:

#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
#include<QFileDialog>
#include<QString>
#include<QSettings>
#include<QStandardPaths>
#include<QImage>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}
Widget::~Widget()
{
    delete ui;
}
void Widget::on_btnSelect_clicked(){
    QString init_path = qApp->applicationDirPath()+"/config/Settings.ini"; //获取配置文件的路径
    //`qApp`是一个指向`QApplication`或`QGuiApplication`对象的全局指针,可以理解为就是指向窗口的指针。
    // qDebug()<<init_path; //测试app_path是否正常
    // 根据ini件路径新建QSettings类
    QSettings *pSetIni = new QSettings(init_path,QSettings::IniFormat);
    //获取ini配置文件中last_path组中的键值对path
    QString last_path = pSetIni->value("/last_path/path").toString();
    if(last_path==NULL){ //如果获取到的值为空,也就是第一次使用该键,就给其设置一个默认的路径
        //对于默认的文件路径,想让其为windows的图片文件夹,可以使用系统标准路径类
        last_path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
        // qDebug()<<last_path; //输出结果为:"C:/Users/86186/Pictures"
    }
    //前面已经获取到了打开文件夹的路径,下面就要获取文件
    QString file_path = QFileDialog::getOpenFileName(this, "选择图片进行展示",last_path, "文件(*.png#pic_center *.jpg);;");
    //要从file_name中提取出文件所在文件夹路径。注意文件路径的格式:C:/Users/86186/Pictures/pic.png#pic_center
    int index = file_path.lastIndexOf("/"); //获取到最后一个/的位置
    QString dir_path = file_path.left(index);
    //使用setValue方法写入Ini文件。第一个参数为group+key。第二个参数为value
    pSetIni->setValue("/last_path/path",dir_path);
    //记得释放堆区的数据、
    delete pSetIni;
    pSetIni=NULL; //让其指向空,防止野指针
    if(file_path.isEmpty()){ //如果文件名为空
        return ;
    }
    this->ui->fileNameDisp->setText(file_path); //将文件名显示出来
    //使图片自适应窗口,同时保证图片自身的比例
    QImage *label_image = new QImage(file_path); //使用QImage打开图片
    QImage pil_image = this->resize_image(this->ui->imageDisp->width(), this->ui->imageDisp->height(), *label_image);
    QPixmap pixmap = QPixmap::fromImage(pil_image); //根据QImage对象创建QPixmap对象
    this->ui->imageDisp->resize(pixmap.width(),pixmap.height());
    this->ui->imageDisp->setPixmap(pixmap);
    this->ui->imageDisp->setAlignment(Qt::AlignCenter); //设置图片居中显示
    delete label_image; //释放堆区数据
    label_image = NULL;
}
QImage Widget::resize_image(int width,int height,QImage image){ //调整图片的大小
    //width:要适应的窗口的宽
    //height:要适应的窗口的长
    //image:图片对象
    int image_width = image.width();
    int image_height = image.height(); //获取原始图片的长和宽
    float f1 = 1.0*width/image_width;
    float f2 = 1.0 * height/image_height;
    float factor = f1<f2?f1:f2; //去最小的因子,也就是最多放缩多少倍
    width = int(image_width * factor);
    height = int(image_height * factor); //对原图的大小进行放缩
    return image.scaled(width, height);
}

百度网盘链接:提取码:ikun

6 lambda函数

6.1 概述

C++ lambda表达式的本质就是重载了 operator(),lambda是一个类,在调用时会进行编译展开,因此lambda表达式对象其实就是一个匿名的functor,所以lambda 表达式 也叫匿名函数对象。

Qt槽函数可以使用lambda函数来写

C++中lambda表达式的构成:

[捕获列表](形参列表)mutable 异常列表->返回类型
{
    函数体
}

6.2 参数详解

捕获列表:捕获外部变量,捕获的变量可以在函数体中使用,可以省略,即不捕获外部变量。一共有三种捕获方式:值捕获、引用捕获和隐式捕获,

形参列表:和普通函数的形参列表一样。可省略,即无参数列表

mutable:mutable 关键字,如果有,则表示在函数体中可以修改捕获变量。根据具体需求决定是否需要省略。

异常列表:noexcept/throw(…),和普通函数的异常列表一样。可省略,即代表可能抛出任何类型的异常。

返回类型:和函数的返回类型一样。可省略,如省略,编译器将自动推导返回类型。

函数体:代码实现。可省略,但是没意义。

6.3 捕获方式

值捕获:不能在lambda表达式中修改捕获变量的值

引用捕获:使用引用捕获一个外部变量,需在捕获列表变量前面加上一个引用说明符&

隐式捕获:其实就是值捕获和引用捕获的其中一种

int main()
{
    //值捕获
    int value = 100;
    auto f = [value](int a, int b)->int {
       //value++;  值捕获中,捕获列表中的参数是不可以改变的
       return a + b + value;
    };
   cout << f(1, 2) << endl;
   // 引用捕获
   auto f2 = [&value](int a, int b)->int { //引用捕获中捕获列表中的参数要使用引用形式
       value++; //引用捕获中可以改变值
       return a + b;
   };
   cout << f2(1, 3) << endl;
   cout << "value = " << value << endl; //value为101
   // 隐式捕获
   //参数列表中为 = 表示是值捕获
   //参数列表中为 & 表示是引用捕获
   int age = 123;
   auto f3 = [&](int a, int b)->int {
       value++;
       age++; //应用捕获中可以改变值
       return a + b;
   };
    return 0;
}

7 Qt槽函数的写法

7.1 Qt4的写法(不推荐)

connect(ui->element_name, SlGNAL(clicked), this, slot(function()));

7.2 Qt5的写法

connect(ui.element_name,&QPushButton::clicked, this,&Widget::func_name);

推荐使用这种写法,信号名字、槽函数名字写错了,编译器会直接报错

参数解释:

第一个参数:信号的发出者

第二个参数:发出的信号

第三个参数:信号的接受者

第四个参数:处理的槽函数

7.3 示例

#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(btn_click())); //QT4的写法;
    connect(this->ui->pushButton,&QPushButton::clicked,this,&Widget::btn_click); //Qt5的写法;
    //解释:将按钮点击事件绑定到名为pushButton的ui控件上,当该控件发生点击事件时,会运行btn_click函数。
}
Widget::~Widget()
{
    delete ui;
}
void Widget::btn_click(){
    QMessageBox::information(this,"title","push");
}

效果:

8 跨界面发送信号

8.1 例子

目标实现这样的功能:一个界面显示数字,另外一个界面有按钮,按一下之后数字加1。

8.2 界面效果

一个QLineEdit显示数字

打开弹窗之后,按下自增键,主窗口显示数字会加1

8.3 总体思路

按下主窗口按钮,会出现子窗口:只需要让按钮绑定槽函数,在槽函数中创建一个子窗口对象并让其显示即可

按下子窗口”自增“按钮,父窗口数字加1:自定义一个信号value_add,"自增"按钮按下,就发送信号。同时将此信号与父窗口的handle_value_add函数绑定,该函数负责更新QLineEdit中的值。

父窗口更新value后,同时发送自定义信号send_value,将value值发送,同时将子窗口与该信号绑定,以便于子窗口更新它的value值

8.4 添加一个新界面

点击项目->右键->添加新文件->Qt->Qt设计器界面类->确定

选择没有多余控件的界面。

随便起一个名字

点击确定完成创建

UI界面添加一个按钮即可。

8.5 自定义信号

  1. 使用signals声明
  2. 返回值是void,只需要声明,不需要实现
  3. 在需要发送的地方使用:emit 信号名字(参数);进行发送
  4. 在需要链接的地方使用connect进行链接
  5. 可以有参数,可以重载

8.6 槽函数

  1. 5.4版本以下,必须要写到Public slots下。高级版本下,既可以写到public slots,也可以写到全局下。
  2. 返回值是void,需要声明也需要实现
  3. 可以由参数,可以发生重载

8.7 详细代码

项目代码下载链接:提取码:ikun

注意:子窗口发送信号的函数中参数列表必须与父窗口接受信号的槽函数中参数列表保持一致。否则会接受不到,导致错误

(Qt Creator中自动生成的代码就没有全部写上来了)

manwindow.h

class MainWindow : public QMainWindow
{
    Q_OBJECT
        
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void handle_value_add(int value); //处理value加一的信号的槽函数。这类函数不需要定义在public slots中
    
signals: //信号都需要定义在signals中
    void send_value(int value); //将value值发送给子窗口的信号
public slots:
    void on_btnOpen_clicked(); //槽函数,也就是只依据函数名就自动实现绑定、无须手动实现的函数,必须要定义在public slots里面。UI中按钮控件为btnOpen
    
private:
    Ui::MainWindow *ui;
};

mainwindow.cpp

void MainWindow::handle_value_add(int value){
    //让value值显示在界面上
    this->ui->lineEdit->setText(QString::number(value));
    int val = this->ui->lineEdit->text().toInt();
    emit this->send_value(val);
}
void MainWindow::on_btnOpen_clicked(){ //注意这个事件不要写成on_btnOpen_click()
    setDialog *setWindow = new setDialog(this); //弹窗对象,填上this之后,该对象就和主窗口在同一个对象树上,可以由系统自动实现空间回收等
    int value = this->ui->lineEdit->text().toInt(); //获取初始值,并转换为int数值类型
    setWindow->set_value(value); //将子窗口的初始值设置为上面显示的
    //将子窗口的信号函数绑定
    //当MainWindow发送send_value信号时,setWindow对象的handle_send_value会运行
    connect(this,&MainWindow::send_value,setWindow,&setDialog::handle_send_value);
    
    //当sewtWindow发送value_add信号时,mainwddow对象的handle_value_add会运行
    connect(setWindow,&setDialog::value_add,this,&MainWindow::handle_value_add);
    setWindow->exec(); //窗口事件循环,相当于显示窗口。会阻塞代码的执行
}

setWindow.h

class setDialog : public QDialog
{
    Q_OBJECT
public:
    explicit setDialog(QWidget *parent = nullptr);
    ~setDialog();
    void set_value(int value); //设置value
    int get_value();
    void handle_send_value(int value); //子窗口处理父窗口发送value的信号的槽函数
private slots:
    void on_btnAdd_clicked(); //子窗口按钮被按下的槽函数
signals: //自定义信号,写到signals下。value+1的信号是由子窗口发出的,当然要写在子窗口的头文件中
    void value_add(int value);
private:
    int value;
    Ui::setDialog *ui;
};

setWindow.cpp

void setDialog::on_btnAdd_clicked(){ //ui中按钮控件名为btnAdd
    //按下按钮,就发送信号
    int val = this->get_value();
    val++;
    emit this->value_add(val);
}
void setDialog::handle_send_value(int value){ //父窗口发送,则把自己的value设为父窗口lineEdit中的值
    //如果不更新value,那么子窗口发送给父窗口的value会一直是一样的
    this->set_value(value);
}
void setDialog::set_value(int value){
    this->value = value; //设置value值
}
int setDialog::get_value(){
    return this->value;
}

9 跨进程发送信号

项目代码下载:提取码:ikun

其实与跨界面发送信号是一致的,只不过是发送信号的一方变成了进程

9.1 添加子进程类

  1. 选择C++ class

  1. 选择其父类为QThread(也可以自行在代码中继承QThread)

  1. 点击完成即可。

9.1.1 非基础类型数据的注册

此处我们要使用一个score结构体,要是没有注册,则会报错:

QObject::connect: Cannot queue arguments of type 'score'
(Make sure 'score' is registered using qRegisterMetaType().)

注册如下:

将score结构体注册,用score使用即可。注册的位置不固定,只需要在项目中即可。

qRegisterMetaType<score>("score");

TIP:string也属于非基础类型数据

9.1.2c string相关的问题

由于string是std中的,因此就算包含了#incude头文件,在使用时也要加上std,例如:

std::string
std::to_string(int index)

否则会报错:

9.1.3 代码

child_name.h

#include <QThread>]
#include<string>
struct score{
    int age;
    std::string name;
    int rate;
};
class child_name : public QThread
{
    Q_OBJECT //Q_OBJECT宏,使该类可以发送Qt信号
public:
    child_name();
signals:
    void send_index(score sc); //信号
protected:
    void run () override; //需要重写run方法
};

child_name.cpp

#include "child_name.h"
#include<stdlib.h>
#include<QDebug>
#include<string>
child_name::child_name() {}
void child_name::run(){
    while(1){
        //不间断的发送index,同时使index++
        score sc;
        sc.age = 13;
        sc.name="Tom";
        sc.rate = 100;
        emit this->send_index(sc);
        sleep(1); //每隔一秒发送
    }
}

Widget.h

#include <QWidget>
#include "child_name.h"
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void handle_send_index(score score); //处理信号的槽函数
    void connectFunc();//专门绑定各槽函数
    child_name *child; //让子进程成为自己的成员,便于操作
public slots:
    void on_start_btn_clicked(); //按钮按下的槽函数
private:
    Ui::Widget *ui;
};

Widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "child_name.h"
#include<QString>
#include<string>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    qRegisterMetaType<score>("score");
    ui->setupUi(this);
    connectFunc();
}
Widget::~Widget()
{
    delete ui;
}
void Widget::connectFunc(){
    //专门绑定各槽函数
    this->child = new child_name();
    connect(child,&child_name::send_index,this,&Widget::handle_send_index);
}
void Widget::handle_send_index(score sc){
    std::string str = "name: "+sc.name+"age: "+std::to_string(sc.age)+"rate: "+std::to_string(sc.rate);
    QString info = "index: "+QString::number(this->index++)+QString::fromStdString(str); //将int型转化为QString类型
    this->ui->dispInfo->clear(); //清除原有内容
    this->ui->dispInfo->setText(info);
}
void Widget::on_start_btn_clicked(){
    this->child->start(); //开始运行子进程
}

9.2 槽函数参数和信号参数的关系

Qt槽函数的参数需要和信号的参数保持一致,可以比信号的参数少,但是不能顺序不同,也不能比信号的参数多。

否则就会报错:

重名信号的处理

例如,QCOmboBox里面的currentIndexChanged有两个重载的版本,而我们在使用QT5的connect时是只写函数名的,因此此时编译器会报错。

若要处理,则使用Qt4的版本,因为它必须要指明参数的类型。例如

connect(this->ui->comboBox,SIGNAL(currentIndexChanged(int)),this,SLOT(handle_index_change(int));

此外,还可以采用泛型编程的思想:

connect(ui->comboBox, Q0verload<int>::of(&0comboBox::currentIndexchanged),this,&Widget::onIndex);

此时,QOverload的数据类型为int,则此时currentIndexChanged函数中参数列表就是int型的。反之,为QOverload<const QString&>时则函数参数列表为const QString&类型

connect函数

connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender //发送者的指针
Func1 signal, //信号
const QObject *contexct  //接收者的指针
Func2 slot, //槽函数
Qt::ConnectionType type = Qt::AutoConnection) //连接的类型默认为AutoConection

连接方式是枚举型,有以下几种连接方式:

enum ConnectionType {
        AutoConnection,
        DirectConnection,
        QueuedConnection,
        BlockingQueuedConnection,
        UniqueConnection =  0x80
    };

AutoConnection:默认的连接方式,如果接收方在发出信号的线程中,使用Qt::DirectConnection;否则使用Qt::Queuedconnection。使用这种方式,Qt在发出信号时确定连接类型。在Qt中默认是用的AutoConnection,所以平时写信号槽时都是4个参数。

DirectConnection:当发出信号时,插槽立即被调果难槽在发送信易的线程中执行。

QueuedConnection:当控制返回到接收方线程的事件循环时调用槽,槽在接收方的线程中执行。

BlockingQueuedConnection:与Qt::QueuedConnection相同,只是发送信号的线程会阻,直到返回如果接收方存在于发送信号的线程中,则不能使用此连接,否则应用程序将死锁。

UniqueConnection:这是一个可以使用按位OR与上述任何一种连接类型组合的标志,当Qt::UniqueConnection被设置时,如果连接已经存在,QObject::connect()将失败(例如,如果相同的信号已经连接到相同的对象对的插槽)。

10 Qt信号槽与moc

moc 全称是 Meta-Object Compiler,也就是“元对象编译器“。Qt 程序在交由标准编译器编译之前,先要使用 moc 分析 C++源文件。如果它发现在一个头文件中包含了宏 Q_OBJECT,则会生成另外一个 C++ 源文件,这个源文件中包含了 Q_OBJECT宏的实现代码,这个新的文件名字将会是原文件名前面加上moc构成,这个新的文件同样将进入编译系统,最终被链接到二进制代码中去。

因此我们可以知道,这个新的文件不是“替换”掉旧的文件,而是与原文件一起参与编译。另外,我们还可以看出一点,moc的执行是在预处理器之前。因为预处理器执行之后,Q_OBJECT宏就不存在了。

可以这么理解,moc把Qt中一些不是C++的关键字做了解析,让C++编译器认识,例如slots,signals,emit等。moc会把这些重新编译解析。

运行moc指令,可以将带有Q_OBJECT宏的头文件进行代码翻译:

moc widget.h -o moc widget.cpp

moc.exe的路径:D:\VisualStudio\QT\5.15.2\msvc2019\bin\moc.exe。我们之前已经将此路径添加到环境变量Path中去了。

11 C++模板技术实现Qt信号槽机制

11.1 概述

信号槽机制不只在Qt中才可以实现,一般的C++项目也可以使用信号槽

信号的好处:例如类B要使用类A,则B就必须包含A,假如项目较大,文件较多,就很容易出错。而使用信号之后,就可以完成解耦。

与Qt中的信号一样,sigslot的槽函数返回值为void(因为槽函数只是用来响应信号的,通常不关心它的返回值是什么)

另外,槽函数和信号的参数列表也必须是一致的

11.2 代码

首先新建一个C++空项目,并添加sigslot.h头文件和sigslot.cc源文件。文件我都会放在百度网盘中,以供下载。

window中定义了2个信号,handle_signal中定义了两个槽函数,main函数中绑定信号和槽函数,并发送信号。

#include "sigslot.h"
#include<iostream>
using namespace std;
class window { //发送信号的类
public:
  //定义一些信号
  sigslot::signal0<>sig_button_click;
  sigslot::signal1<int>sig_print;
  //在sibgslot中,signal0表示不带任何参数的信号,signal1表示带一个参数的信号
};
class handle_signal :public sigslot::has_slots<> { //处理信号的类
public:
  void handle_button_click_sig() {
    cout << "按钮点击信号被调用" << endl;
  }
  void handle_print_sig(int val1) {
    cout << "打印信号被发送,值为:" << val1 << endl;
  }
};
int main() {
  window w;
  handle_signal handle;
  w.sig_button_click.connect(&handle, &handle_signal::handle_button_click_sig);
  w.sig_print.connect(&handle, &handle_signal::handle_print_sig);
  w.sig_button_click.emit(); //发送信号
  w.sig_print.emit(100); //发送信号
  return 0;
}

11.3 下载链接

百度网盘链接:提取码:ikun

参考:c++之sigslot库 - mohist - 博客园 (cnblogs.com)

12 Qt半内存访问机制

参考:https://blog.csdn.net/lizun7852/article/details/88740970

子窗口可以通过指定父窗口的方式,来托管子窗口的内存,而父窗口又可以通过它的父窗口来托管自己的内存,一层一层,顶级窗口(QWidget类对象或者其派生类widgwt对象)一般在main函数中实例化为可以自动销毁空间的栈上对象,这样子窗口的空间就只需手动申请而不用管释放了。其中的底层实现大概是这样:当父窗口释放时,会在析构函数中遍历自己所有的子窗口,将它们释放。

所有的QObject类对象及其派生类对象都可以通过指定父对象来托管内存,QWidget继承自QObject,所以它及其派生类对象能够使用内管管理机制。

那么所有的子对象只能在父对象销毁的时候才能一起销毁,势必会造成内存驻留,能不能自己来销毁呢?当然开发者也可以使用delete来销毁子对象,但是不推荐这种用法,因为QObject对象远比普通的类对象复杂的多,QObject底层实现在我们不知道的情况下,正进行着一些工作。如果这个时候使用delete强制释放,会造成一些不可预知的错误。一般推荐使用deleteLater函数,进行子对象的安全释放,它会等待所有事情都处理完毕后才释放。

这也就是为什么之前,当我们在创建对象时,一般是这样的写法:TestWin tw = new TestWin(this),也就是指定父指针为this(一般为当前的窗口)。

13 解决Qt中文乱码

Qt对中文的支持不是很好,使用Qtcreator会出现各种乱七八糟的中文乱码问题,如何处理这种问题?

  1. 粘贴别人的代码时,现在记事本里"过一遍”,再贴到Qtcreator
  2. 使用u8,也就是在中文前都加上u8。例如:ui.pushButton->setText(u8"你好”)
  3. 不使用atcreator开发,直接使用vs2019
  4. 在涉及到中文的文件中,在其最开头写上一句:#pragma execution character set(“utf-8”)
  5. QtCreator — 选项 — 文本编辑器 — UTF8 BOM总是删除

点击确定即可。

14 总结

在代码的舞台上翩翩起舞, Qt,如诗如画,编织梦的彩虹。

跨越平台的轻盈舞姿, 文档的琴音,灵感的涟漪。

模块的花瓣,细腻而丰满, 开发者的心灵,在那里盛开。

清新而深邃,如林中明月, Qt,用优雅的笔触,谱写未来的篇章。

渴望挑战Qt的学习路径和掌握进阶技术?不妨点击下方链接,一同探讨更多Qt的奇迹吧。我们推出了引领趋势的💻QT专栏:《QT从基础到进阶》 ,旨在深度探索Qt的实际应用和创新。🌐🔍

相关文章
|
运维 前端开发 安全
万字长文搞懂产品模式和项目模式
万字长文搞懂产品模式和项目模式
177 0
|
6月前
|
数据采集 机器学习/深度学习 存储
万字长文,Python的应用领域有哪些?
万字长文,Python的应用领域有哪些?
|
6月前
|
存储 搜索推荐 编译器
万字长文:C++模板与STL【模板】
万字长文:C++模板与STL【模板】
|
存储 芯片
复习单片机:8*8点阵---->点亮数字0(内含:1.设计思路+2.数字0的编程数据+3.原始代码+4.实验现象)(注:获得编程数据工具的下载和使用放在下一篇文章)
复习单片机:8*8点阵---->点亮数字0(内含:1.设计思路+2.数字0的编程数据+3.原始代码+4.实验现象)(注:获得编程数据工具的下载和使用放在下一篇文章)
453 0
复习单片机:8*8点阵---->点亮数字0(内含:1.设计思路+2.数字0的编程数据+3.原始代码+4.实验现象)(注:获得编程数据工具的下载和使用放在下一篇文章)
|
Web App开发 存储 SQL
万字长文,带你学会JMeter的使用!!!赶紧收藏(下)
万字长文,带你学会JMeter的使用!!!赶紧收藏
218 0
万字长文,带你学会JMeter的使用!!!赶紧收藏(下)
|
XML 网络协议 Oracle
万字长文,带你学会JMeter的使用!!!赶紧收藏(上)
万字长文,带你学会JMeter的使用!!!赶紧收藏
245 0
万字长文,带你学会JMeter的使用!!!赶紧收藏(上)
|
前端开发 JavaScript Java
万字长文,带你学会JMeter的使用!!!赶紧收藏(中)
万字长文,带你学会JMeter的使用!!!赶紧收藏
177 0
万字长文,带你学会JMeter的使用!!!赶紧收藏(中)
|
存储 缓存 网络协议
前端面试基础网络问题(万字长文)
这篇文章你会了解到什么 • OSI七层协议和TCP/IP四层协议模型 • 什么是UDP协议 • 什么是TCP协议 • TCP连接过程 • TCP两次握手就可以完成,为撒需要三次了? • TCP断开连接过程 • 为什么 A 要进入 TIME-WAIT 状态,等待 2MSL 时间后才进入 CLOSED 状态? • ARQ协议(超时重传机制):停止等待ARQ协议、连续ARQ协议 • 滑动窗口协议 • 拥塞处理过程 • 拥塞算法
240 0