这是 Qt 的对话框类的头文件(.h),典型写法:类内只写【函数声明】,函数实现写到对应的
.cpp源文件里面,正好对应刚才讲的「类内声明、类外实现」! 里面包含:构造函数声明、析构函数声明。
#ifndef TEST_WIDGET_PROJECTDIALOG_H #define TEST_WIDGET_PROJECTDIALOG_H #include <QWidget> namespace Ui { class test_widget_projectdialog; } class test_widget_projectdialog : public QWidget { Q_OBJECT public: explicit test_widget_projectdialog(QWidget *parent = nullptr); ~test_widget_projectdialog(); private: Ui::test_widget_projectdialog *ui; }; #endif // TEST_WIDGET_PROJECTDIALOG_H第一部分:头文件保护宏(防止头文件重复包含)
#ifndef TEST_WIDGET_PROJECTDIALOG_H #define TEST_WIDGET_PROJECTDIALOG_H#ifndef:if not defined,翻译:如果没有定义这个宏TEST_WIDGET_PROJECTDIALOG_H:自定义宏名,一般规则:文件名全大写 + 下划线分隔#define:定义这个宏
逻辑:第一次包含这个头文件时,宏不存在 → 执行下面所有代码;第二次再 include 这个文件,宏已经存在,直接跳过中间全部代码。 作用:避免同一个头文件被多次引入,造成类重复定义编译报错
#include <QWidget>#include:引入头文件。QWidget是 Qt 基础窗口控件,我们这个窗口类继承它。
namespace Ui { class test_widget_projectdialog; }namespace Ui:Ui 命名空间。Qt 的 UI 界面自动生成的代码全部放在Ui命名空间。class test_widget_projectdialog;这叫前置声明
只是告诉编译器:有这么一个类存在,不用现在看完整定义。 这个 Ui 里的类,是 Qt uic 工具从
.ui界面文件自动生成的类,负责管理拖拽出来的按钮、输入框等控件。
第二部分:定义窗口类 test_widget_projectdialog
class test_widget_projectdialog : public QWidgetclass:定义类test_widget_projectdialog:当前类名,后面判断构造函数就拿这个名字对比!::继承标记public QWidget:公有继承 QWidget,我们写的这个窗口,是 QWidget 的子类,拥有 QWidget 窗口所有能力。
{ Q_OBJECTQ_OBJECT:Qt 特有宏,只要这个类要使用信号槽机制,就必须写这个宏,放在类里面第一行;
注意:写了 Q_OBJECT,项目需要执行 moc 编译,否则编译报错。
public:public:公有访问区域,外部可以调用这里声明的函数。
explicit test_widget_projectdialog(QWidget *parent = nullptr);✅这就是【构造函数的声明】!!(类内声明,类外实现,和我们刚才讲的例子一模一样)
我们用之前构造函数 3 条铁律校验它: 规则 1:函数名test_widget_projectdialog,和类名完全一致 ✅规则 2:没有返回值,没有写 void ✅规则 3:创建对象时自动调用,不能手动调用 ✅
额外关键字拆解:
explicit:关键字。禁止隐式类型转换,Qt 窗口构造函数几乎都加这个。简单理解:不加 explicit 时,部分写法会偷偷自动转换类型,容易出 bug,Qt 窗口构造习惯性加上。
(QWidget *parent = nullptr)形参QWidget *parent:父窗口指针。Qt 窗口采用对象树机制,父窗口负责自动回收子窗口内存。= nullptr:默认参数。不传参数时,parent 默认等于空指针(没有父窗口)。
;分号:只有声明,没有 {} 函数体,函数的代码写在.cpp文件(类外实现)。
~test_widget_projectdialog();析构函数声明
~波浪号 + 类名,析构函数。对象销毁时自动执行,用来释放资源(这里释放 ui 指针)- 同样只有声明,实现放在 cpp 里面。
private: Ui::test_widget_projectdialog *ui;私有成员变量:Ui::test_widget_projectdialog *ui;一个指针,指向 Ui 命名空间里的界面类实例。
所有 ui 上拖拽的控件(按钮、标签)都通过
ui->访问,私有,只能在本类的成员函数访问。
};类定义结束,末尾分号不能丢!C++ class 结束必须加分号。
#endif // TEST_WIDGET_PROJECTDIALOG_H头文件保护的结束标记,和最开头#ifndef配对。
配套 .cpp 文件(类外实现,补上构造、析构函数)
新建test_widget_projectdialog.cpp,就是类外实现,对应这个头文件里的声明:
#include "test_widget_projectdialog.h" #include "ui_test_widget_projectdialog.h" // ==========类外实现【构造函数】========== test_widget_projectdialog::test_widget_projectdialog(QWidget *parent) : QWidget(parent) // 初始化列表,调用父类QWidget的构造函数 { // 实例化ui对象 ui = new Ui::test_widget_projectdialog; ui->setupUi(this); // 把ui界面搭建到当前窗口上 } // ==========类外实现【析构函数】========== test_widget_projectdialog::~test_widget_projectdialog() { delete ui; // 释放ui界面内存,防止内存泄漏 }重点看构造函数这一行(类外实现写法)
test_widget_projectdialog::test_widget_projectdialog(QWidget *parent)- 前面
test_widget_projectdialog:类名 ::作用域解析运算符,代表后面这个函数属于test_widget_projectdialog类- 后面
test_widget_projectdialog:构造函数名,和类名相同
完全匹配我们上一节讲的
类名::类名(参数){}类外实现格式!
执行逻辑
- 在别的代码创建这个窗口对象时:
test_widget_projectdialog *dlg = new test_widget_projectdialog(this);- 创建对象瞬间,自动调用上面 cpp 里类外实现的构造函数
- 构造函数里面:创建 ui 对象,加载 ui 界面,把按钮、控件绘制到窗口。
- 窗口销毁的时候,自动调用析构函数,delete ui 释放内存。
结合 3 条铁律再次验证这个 Qt 构造函数
✅规则 1:函数名test_widget_projectdialog和类名一模一样 ✅规则 2:没有返回值,没有 void ✅规则 3:创建对象自动调用,不能手动调用
虽然声明写在.h 头文件,实现写在.cpp 源文件(类外实现),它依然是构造函数,三条铁律全部生效。
新手重点坑点(Qt 项目高频踩坑)
- 头文件 class 后面的
};分号忘记写,直接编译报错。 - cpp 里面写构造函数,不要加
void! ❌void test_widget_projectdialog::test_widget_projectdialog(QWidget *parent) - 类外实现构造函数,不要漏掉
::,漏掉就变成普通全局函数。 - 类里写了
Q_OBJECT,qmake/cmake 必须执行 moc,否则报信号槽相关未定义引用错误。 explicit只对单参数构造函数起作用,多参数构造写 explicit 不报错,但没有效果。