1. Qlabel HelloWorld 程序
使用纯代码实现
cpp
// 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保持同样的⾏为。正常情况下,这也不会发⽣什么问题。但是要注意顺序
cpp
QWidget window;
QPushButton quit("quit", &window);
作为⽗组件的window和作为⼦组件的quit都是QObject的⼦类。这段代码是正确的,quit的析构函数不会被调⽤两次,因为标准C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作⽤域时,会先调⽤quit的析构函数,将其从⽗对象window的⼦对象列表中删除,然后才会再调⽤ window的析构函数。 如果改成下面这样
cpp
QPushButton quit("quit");
QWidget window;
quit.setParent(&window);
在上⾯的代码中,作为⽗对象的window会⾸先被析构,因为它是最后⼀个创建的对象。在析构过程中,它会调⽤⼦对象列表中每⼀个对象的析构函数,也就是说,quit此时就被析构了。然后,代码继续执⾏,在window析构之后,quit也会被析构,因为quit也是⼀个局部变量,在超出作⽤域的时候当然也需要析构。但是,这时候已经是第⼆次调⽤quit的析构函数了,C++不允许调⽤两次析构函数,因此,程序崩溃了
**所以,在Qt中,尽量在构造的时候就指定parent对象,并且大胆在堆上创建。 **
2.2 看到析构函数执行
自己定义一个类,继承QLabel
cpp
// 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;
}
可以看到,自动调用了析构函数,所以挂到了对象树上,就无需担心内存泄露问题
3. QLineEdit Hello World
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLineEdit* edit = new QLineEdit(this);
edit->setText("Hello World");
}
可以看到,与Qlabel 差别不大
4. Qt中的坐标系
坐标体系:以左上⻆为原点(0,0),X向右增加,Y向下增加。
对于嵌套窗⼝,其坐标是**相对于⽗窗⼝**原点来说的
cpp
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来说