Qt 概述

1. Qlabel HelloWorld 程序

使用纯代码实现

// widget.cpp
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 给当前这个lable对象,指定一个父对象
    QLabel* label = new QLabel(this);
    // C语言风格的字符串可以直接进行隐式类型转换成QString
    label->setText("Hello World");
    // same as label->setText(QString("Hello World"));
}
Widget::~Widget()
{
    delete ui;
}

2. 对象树

2.1 概念和注意事项

看到上面的第8行,在堆上new了一个对象,但是在析构函数中并没有delete,这样不会出现问题,因为Qt中有对象树的概念

  • QObject是以对象树的形式组织起来的。
    • 当创建⼀个QObject对象时,会看到QObject的构造函数接收⼀个QObject指针作为参数,这个参数就是parent,也就是⽗对象指针。
    • 这相当于,在创建QObject对象时,可以提供⼀个其⽗对象,我们创建的这个QObject对象会自动添加到其⽗对象的children()列表。
    • 当⽗对象析构的时候,这个列表中的所有对象也会被析构。(注意,这⾥的⽗对象并不是继承意义上的⽗类!而是一个指针)
  • QWidget是能够在屏幕上显⽰的⼀切组件的⽗类。
    • QWidget继承⾃QObject,因此也继承了这种对象树关系。⼀个孩⼦⾃动地成为⽗组件的⼀个⼦组件。因此,它会显⽰在⽗组件的坐标系统中,被⽗组件的边界剪裁。例如,当⽤⼾关闭⼀个对话框的时候,应⽤程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该⼀起被删除。事实就是如此,因为这些都是对话框的⼦组件
    • 当然,我们也可以⾃⼰删除⼦对象,它们会⾃动从其⽗对象列表中删除。⽐如,当我们删除了⼀个⼯具栏时,其所在的主窗⼝会⾃动将该⼯具栏从其⼦对象列表中删除,并且⾃动调整屏幕显⽰
  • Qt引⼊对象树的概念,在⼀定程度上解决了内存问题
    • 当⼀个QObject对象在堆上创建的时候,Qt会同时为其创建⼀个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的
    • 任何对象树中的QObject对象delete的时候,如果这个对象有parent,则⾃动将其从parent的children()列表中删除;如果有孩⼦,则⾃动delete每⼀个孩⼦。Qt保证没有QObject会被delete两次,这是由析构顺序决定的

如果QObject在栈上创建,Qt保持同样的⾏为。正常情况下,这也不会发⽣什么问题。但是要注意顺序

QWidget window;
QPushButton quit("quit", &window);

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

QPushButton quit("quit");
QWidget window;
quit.setParent(&window);

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


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

image-20240929114250963

2.2 看到析构函数执行

自己定义一个类,继承QLabel

// myqlabel.h
class MyQLabel : public QLabel
{
public:
    // 构造函数需要使用QWidget *版本
    // 这样才能确保咱自己写的构造函数能够加到对象树上
    MyQLabel(QWidget* parent);
    ~MyQLabel();
};

// myqlabel.cpp
// 调用QLabel类的构造函数,将MyQLabel挂到对象树上
MyQLabel::MyQLabel(QWidget *parent) : QLabel(parent)
{
    std::cout << "MyQLabel(QWidget *parent)" << std::endl;
}

MyQLabel::~MyQLabel()
{
    std::cout<<"~MyQLabel()"<<std::endl;
}

// widget.cpp
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 这里使用自己定义的Mylabel代替原来的 Qlabel, 虽然写的是“继承”,本质上是拓展
    // 保持原有功能不变的基础下,打印日志,方便观察结果
    MyQLabel* label = new MyQLabel(this);
    label->setText("Hello World");
}
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 这里使用自己定义的Mylabel代替原来的 Qlabel, 虽然写的是“继承”,本质上是拓展
    // 保持原有功能不变的基础下,打印日志,方便观察结果
    MyQLabel* label = new MyQLabel(this);
    label->setText("Hello World");
}
Widget::~Widget()
{
    delete ui;
}

image-20240929120442912

可以看到,自动调用了析构函数,所以挂到了对象树上,就无需担心内存泄露问题

3. QLineEdit Hello World

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QLineEdit* edit = new QLineEdit(this);
    edit->setText("Hello World");
}

image-20240930211641376

可以看到,与Qlabel 差别不大

4. Qt中的坐标系

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

image-20240930231946033

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


 QPushButton* b1 = new QPushButton(this);
QPushButton* b2 = new QPushButton(this);
b1->setText("按钮1");
b2->setText("按钮2");
b2->move(200,300);          // b2按钮move到(200,300)位置,相对于Widget来说

image-20240930232411045

上一篇:1516-函数指针


下一篇:基于SpringBoot+Vue+MySQL的旅游网站-代码展示