Qt实战教程一:创建第一个Qt程序

Qt实战教程

  • 本文档创作于2021/1/17。

  • 作者:南京航空航天大学 Eric

1 创建第一个Qt程序

1.1 创建过程

  • 点击 文件-新建文件或项目可开始创建。

  • Location为路径选择;Build System为创建的系统,建议使用默认Qmake。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-puvTvydX-1614409666269)(C:\Users\Divin\AppData\Roaming\Typora\typora-user-images\image-20210117231352469.png)]

  • Details即创建一个类,父类可以选择QMainWindow、QDialog、QWidget之中的一个,其中QMainWindow、QDialog都是QWidget的子类。下方的Generate Form勾选上会额外生成.ui文件,且可使用设计窗口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2rzyPNBS-1614409666270)(C:\Users\Divin\AppData\Roaming\Typora\typora-user-images\image-20210117235541982.png)]

  • 构建套件Kits建议全部勾选上。

1.2 各文件的代码含义

在这里插入图片描述

  • 创建完工程后会自动生成如上文件,其中.pro为工程文件,main是主程序入口,另外两个.h和.cpp是创建时的类。
  1. 工程文件FirstProject.pro
# 增加core核心和gui图像模块
QT       += core gui

# 如果版本号>4 增加widget模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
main.cpp \
mywidget.cpp

HEADERS += \
mywidget.h

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

在这里插入图片描述

  1. 主程序入口main.cpp
#include "mywidget.h"

#include <QApplication> //包含一个应用程序类的头文件

//main程序入口
int main(int argc, char *argv[]) //argc为命令行变量的数量,argv为命令行变量的数组
{
    //创建应用程序对象a,有且只有一个
    QApplication a(argc, argv);
    //创建窗口对象w,父类为QWidget
    myWidget w;
    //窗口对象默认不会显示,必须调用show方法显示窗口
    w.show();

    //让应用程序对象进入消息循环机制(死循环),防止窗口一闪而过
    //让代码阻塞到这行,后面代码均不执行
    return a.exec();
}
  1. 类的头文件mywidget.h
//避免mywidget.h头文件被重复包含,相当于设立了标识
#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>

class myWidget : public QWidget
{
    Q_OBJECT //Q_OBJECT宏 允许类中使用信号和槽的机制

public:
    //有参构造(用父类初始化列表  )
    myWidget(QWidget *parent = nullptr);
    //析构
    ~myWidget();
};
#endif // MYWIDGET_H
  1. 类的源文件mywidget.cpp
#include "mywidget.h"

//有参构造(调用父类构造函数)
myWidget::myWidget(QWidget *parent)
    : QWidget(parent)
{
}

myWidget::~myWidget()
{
}

1.3 命名规范及快捷键

  • 命名规范

    类名:首字母大写,单词和单词之间首字母大写。

    函数名、变量名:首字母小写,单词和单词之间首字母大写。

  • 快捷键

    注释:Ctrl + /

    运行:Ctrl + r

    编译:Ctrl + b

    字体缩放:Ctrl + 鼠标滚轮

    查找:Ctrl + f

    自动对齐:Ctrl + i

    帮助文档:F1(光标要查询的代码上) ,再按一次全屏帮助,退出按Esc / 打开Assistant快捷方式

    同名间的 .h / .cpp切换:F4

1.4 新建一个按钮

#include "mywidget.h"

//包含按钮头文件
#include<QPushButton>

myWidget::myWidget(QWidget *parent)
    : QWidget(parent)
{
    //创建第一个按钮,默认构造
    QPushButton * btn1 = new QPushButton;
    //创建第二个按钮,有参构造,参数为(按钮文本,父亲)
    QPushButton * btn2 = new QPushButton("按钮2",this);

    //设置按钮文本,此处由char*强转为QString
    btn1->setText("按钮");
    //设置按钮的"父亲",即为myWidget窗口
    btn1->setParent(this);

    //重置窗口大小
    this->resize(300,300);
    //设置固定窗口大小(用户不能拖拽)
    this->setFixedSize(300,500);

    //移动btn2按钮
    btn2->move(100,200);
    //重置btn2按钮大小
    btn2->resize(80,40);

    //设置窗口标题
    this->setWindowTitle("第一个程序");
}

myWidget::~myWidget()
{
}

1.5 对象树

  • 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 两次,这是由析构顺序决定的。

在这里插入图片描述

对象树析构顺序判断方法:析构大的,快析构完后,找"儿子",等所有儿子都析构完后,析构自己。

创建顺序:从上到下;析构顺序:从下到上。

1.6 信号和槽

  • 连接函数connect()
语法:
connect(信号的发送者,发送的信号(函数地址),信号的接受者,处理的槽函数(函数地址));

//如按下按钮使myWidget窗口关闭
connect( myBtn, &QPushButton::clicked, this, &QWidget::close);
  • 信号和槽的特点:松散耦合。

  • 常用QPushButton类的四个信号:

void clicked(bool checked = false) 点击
void pressed() 按下
void released() 松开
void toggled(bool checked) 切换

1.7 自定义信号和槽

  • 实现效果:定义老师和学生类,在myWidget主窗口类默认构造下定义这两个类对象,并创建下课函数,当调用下课函数时,触发“老师饿了”信号,并通过connect调用槽函数,并打印相应文字

  • 1.在老师类中建立信号函数:

    • 写在signals:下。
    • 返回值是void,只需要声明,不需要实现。
    • 可以有参数,可以重载。
  • 老师类头文件

#ifndef TEACHER_H
#define TEACHER_H

#include <QObject>

class Teacher : public QObject
{
    Q_OBJECT
    public:
    explicit Teacher(QObject *parent = nullptr);

    signals:
    //自定义信号写在signals下
    //返回值是void,只需要声明,不需要实现
    //可以有参数,可以重载
    void hungry();
};

#endif // TEACHER_H
  • 老师类的实现(空)
#include "teacher.h"
Teacher::Teacher(QObject *parent) : QObject(parent)
{
}
  • 2.在学生类中建立槽函数
    • 5.6版本以上槽函数写在public下就可以,5.6版本之前需要写到public slots下。
    • 返回值是void,需要声明也需要实现。
    • 可以有参数,可以发生重载。
  • 学生类头函数
#ifndef STUDENT_H
#define STUDENT_H

#include <QObject>

class Student : public QObject
{
    Q_OBJECT
public:
    explicit Student(QObject *parent = nullptr);
    //5.6版本以上槽函数写在public下就可以,5.6版本之前需要写到public slots下
    //返回值是void,需要声明也需要实现
    //可以有参数,可以发生重载
    void treat();
signals:
};

#endif // STUDENT_H
  • 学生类的实现
#include "student.h"
#include <QDebug>

Student::Student(QObject *parent) : QObject(parent)
{
}

void Student::treat()
{
    qDebug()<<"请吃饭";
}
  • 3.主窗口新建老师和学生对象,并连接信号和槽函数

  • 主窗口头函数

#ifndef MYWINDOW_H
#define MYWINDOW_H

#include <QMainWindow>
#include"teacher.h"
#include"student.h"

class myWindow : public QMainWindow
{
    Q_OBJECT

public:
    myWindow(QWidget *parent = nullptr);
    ~myWindow();
private:
    Teacher *lh;
    Student *st;
    void classIsOver();
};
#endif // MYWINDOW_H
  • 主窗口的实现
#include "mywindow.h"
#include <QPushButton>

myWindow::myWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //新建老师和学生对象
    this->lh=new Teacher(this);
    this->st=new Student(this);
    //新建连接
    connect(lh,&Teacher::hungry,st,&Student::treat);
    classIsOver();
}

myWindow::~myWindow()
{
}

void myWindow::classIsOver()
{
    //emit表示触发信号,不写也没事
    emit lh->hungry();
}

注意点:必须要先创建连接再触发下课函数

1.8 信号和槽发生重载

  • 案例:当Teacher类对象的“饿了”信号有重载版本hungry(QString)抑或是Student对象“请客”有重载版本treat(QString)时,再用connect函数连接时编译器不知道要连接哪一个信号和槽,则需要定义函数指针指向函数地址
//语法: 返回值类型(类::函数指针名)(参数列表)=&类::函数名;
	void (Teacher::*teacherSignal)(QString)=&Teacher::hungry;
	void (Student::*studentSignal)(QString)=&Student::treat;
	connect(lh,teacherSignal,st,studentSignal);

注意点:信号函数和槽函数的参数必须一致,且编译器自动将信号函数中的参数传到槽函数中

  • 如果直接用QString输出会输出一个双引号,用char*输出就不会出现问题。QString转成char*需要先转为QByteArray再转成char*
    qDebug()<<"请吃"<<foodName.toUtf8().data();

1.9 信号连接信号

  • 在上面的案例中,都是通过主窗口的默认构造中调用了classIsOver()函数,从而自动发送”下课“信号的,本例旨在通过按钮触发。
  • 思路1:按钮信号触发下课槽函数,在下课槽函数中发送"饿了"信号,触发“请客”。
    //连接带参数的函数和槽
    //需要定义函数指针指向函数地址
    void (Teacher::*teacherSignal1)(QString)=&Teacher::hungry;
    void (Student::*studentSignal1)(QString)=&Student::treat;
    connect(lh,teacherSignal1,st,studentSignal1);
    QPushButton *classButton1=new QPushButton;
    classButton1->setText("下课1");
    classButton1->setParent(this);
    classButton1->move(100,100);
    classButton1->resize(80,40);
    //按下课1按钮触发下课槽函数,下课槽函数会发送hungry有参信号
    connect(classButton1,&QPushButton::clicked,this,&myWindow::classIsOver);
  • 思路2:按钮信号直接触发“饿了”信号,再触发“请客”。
    //直接通过按钮触发下课信号,即信号连接信号
    void (Teacher::*teacherSignal2)()=&Teacher::hungry;
    void (Student::*studentSignal2)()=&Student::treat;
    connect(lh,teacherSignal2,st,studentSignal2);
    QPushButton *classButton2=new QPushButton;
    classButton2->setText("下课2");
    classButton2->setParent(this);
    classButton2->move(200,100);
    classButton2->resize(80,40);
    //按下课2按钮直接触发饿了信号
    connect(classButton2,&QPushButton::clicked,lh,teacherSignal2);
  • 断开信号disconnect
	disconnect(classButton2,&QPushButton::clicked,lh,teacherSignal2);

1.10 信号和槽拓展知识

  • 信号可以连接信号。
  • 一个信号可以连接多个槽函数。
  • 多个信号可以连接一个槽函数。
  • 信号和槽的参数类型必须一一对应,且参数自动传递。
  • 信号参数个数可以多于槽的参数个数,即信号可以浪费参数,槽函数的所有参数必须要有信号来传,且类型必须一一对应。

书上常见的QT4版本以前的信号和槽连接方式

	connect(lh,SIGNAL(hungry()),st,SLOT(treat()));
  • 优点:参数直观,且对于有参的信号和槽无需写函数指针。
  • 缺点:如果信号和槽参数类型没有对应,编译器不作检测,在运行阶段才能看到这种错误,故不推荐这种写法,且此时的槽函数必须写到public slots下。

1.11 Lambda表达式

C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:

//[函数对象参数](操作符重载函数参数)mutable ->返回值{函数体}
[capture](parameters) mutable ->return-type
{
statement
}
  • 1.函数对象参数

    [],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:

    • n 空。没有使用任何函数对象参数。

    • n =。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。

    • n &。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。

    • n this。函数体内可以使用Lambda所在类中的成员变量。

    • n a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。

    • n &a。将a按引用进行传递。

    • n a, &b。将a按值进行传递,b按引用进行传递。

    • n =,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。

    • n &, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。

  • 2.操作符重载函数参数

    标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。

  • 3.可修改标示符

    mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。

    QPushButton * myBtn = new QPushButton (this);
    QPushButton * myBtn2 = new QPushButton (this);
    myBtn2->move(100,100);
    int m = 10;
    connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 100 + 10; qDebug() << m; });
    connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });
  • 4.函数返回值

    ​ ->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

    	int ret = []()->int{return 1000;}();  //时刻记住lambda表达式是一个匿名函数,不加()只是函数声明,即函数地址,加了()才是函数实现
    
  • 5.是函数体

    {},标识函数的实现,这部分不能省略,但函数体可以为空。

  • lambda表达式的优势

    作为一个匿名函数,常用于简写槽函数,且可以写“不对应”的信号和槽。

    在上面的例子中,"饿了"无参信号只能连接“请客”无参槽函数,否则会报错,使用lambda表达式可以解决这一问题。

    	connect(lh,&Teacher::hungry,st,[=](){emit st->treat("宫保鸡丁");});
    
        //connect(btn,&QPushButton::clicked,this,&QWidget::close);重写
        connect(btn,&QPushButton::clicked,this,[=](){close();});
    
  • 最常用的lambda表达式格式是[=](){函数体},函数体内写槽函数。

第一章作业

  • 案例:创建一个窗口,并设置一个按钮“打开”。按下按钮时,打开第二个窗口,并将按钮字符改为“关闭”;再次按下按钮时。关闭第二个窗口,并将按钮字符改为“打开”。
  • 案例使用了lambda表达式作为槽函数,在槽函数中设置选择语句if可以很方便地切换字符并实现开闭窗口。
    setWindowTitle("第一个窗口");
    setFixedSize(400,200);
    QPushButton *btn1 = new QPushButton("打开",this);
    btn1->move(100,100);
    btn1->setVisible(true);
    secondWindow *w2=new secondWindow;
    connect(btn1,&QPushButton::clicked,w2,[=](){
        if(btn1->text()=="打开")
        {
            btn1->setText("关闭");
            w2->show();
        }
        else
        {
            btn1->setText("打开");
            w2->close();
        }
    });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值