Qt初体验-第一个窗口程序踩的坑

Qt 初体验:第一个窗口程序踩的那些坑

大三了,想着暑期得找份实习,翻 BOSS 直聘看了一圈 C++ 岗,发现"熟悉 Qt"几乎是个标配技能。学校虽然开过 Qt 课,但当时就跟着老师拖了几个控件做了一个小作业,原理基本一窍不通,纯属应付完事。

正好这阵子课不算忙,决定自己重新学一遍 Qt,目标是能独立写点小工具放到简历里凑项目经历。这篇就记一下学 Qt 头一天踩的几个坑,主要是搞清楚一个最简单的窗口程序到底是怎么跑起来的,给自己留个备忘,也希望同样在自学 Qt 的同学能少走点弯路。

Hello World:用代码版还是 Designer 版

打开 Qt Creator,新建项目走 Qt Widgets Application,一路下一步选 MinGW Kit。生成出来五个文件:

复制代码
helloworld/
├── helloworld.pro      # 工程描述
├── main.cpp            # 入口
├── widget.h
├── widget.cpp
└── widget.ui           # 这玩意是 Designer 用的

我第一反应是双击 widget.ui 进 Designer,拖一个 QLabel,改文字"Hello World",按 Ctrl+R 跑------成了,但我心里其实有点虚。因为整个过程里我啥也没写,IDE 帮我藏了太多东西。万一 Designer 出 bug 或者我想动态改界面,根本不知道从哪儿下手。

所以我又重新搞了一遍纯代码版的。把 .ui 那行从 .pro 里删掉,然后在 widget.cpp 的构造函数里手动 new 控件:

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    setWindowTitle("Hello Qt");
    resize(480, 320);

    // 注意这个 this,后面专门吐槽
    QLabel *label = new QLabel("Hello, Qt!", this);
    label->setAlignment(Qt::AlignCenter);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(label);
}

跑起来效果是这样:

第一个真正让我懵的概念:对象树

上面那段代码里 new QLabel("Hello, Qt!", this) 那个 this 是干啥的?我一开始以为是"指定 label 显示在哪个窗口里"。

直到我手贱试了一下不传 this:

cpp 复制代码
QLabel *label = new QLabel("Hello, Qt!");  // 没传 parent
label->setAlignment(Qt::AlignCenter);
// 跑起来发现 label 根本不在窗口里!
// 它变成一个独立的小窗口飘在桌面上某个角落,而且我没 show() 它,所以你压根看不见

查了 Qt Assistant 才搞明白:new 一个 QWidget 时传的那个指针不是"放到哪里",是"谁是它爹" 。Qt 有个东西叫对象树------你给一个控件指定 parent 之后:

  1. 它会自动作为 parent 的子控件渲染
  2. 当 parent 析构时,所有 child 自动 delete,不用你管

这个机制让 C++ 写 GUI 不用动不动手动 delete,挺香的。但坑也来了:

cpp 复制代码
// 我一开始这么写的,觉得既然 new 了就该自己 delete
QLabel *label = new QLabel(this);
// ... 用完了想清理
delete label;
// 然后程序退出时 this 析构,Qt 又 delete 了一次这个 label
// 程序崩溃,直接 abort

正确做法是要么交给 Qt 管,要么自己 setParent(nullptr) 把它从对象树脱离再 delete。这事儿文档其实写了,但我没仔细看,被坑了大概 20 分钟。

为了把这个事儿讲清楚,我在窗口里加了俩按钮做实验:一个"加一个子控件",一个"看看我有几个孩子"。点几下看效果:


注意看:我点了两次"加一个子控件",眼睛看到的可见控件是2 个标题 label + 2 个按钮 + 2 个新加的 label = 6 个 ,但 children() 数出来是 7

多出来那个是 QVBoxLayout ------ 布局管理器也是 QObject,也算 child 。这个细节文档上有写,但藏得挺深,我是 debug print 了一遍 children() 列表里每个对象的 className 才反应过来。

.pro 文件其实就是一行行的赋值

之前看 .pro 觉得是某种神秘配置,后来发现就是 qmake 自己定义的 DSL,本质就是变量赋值:

pro 复制代码
QT       += core gui widgets       # 用哪些 Qt 模块,widgets 必须有
CONFIG   += c++17                  # 编译选项
TARGET   = helloworld              # 输出 exe 名字
TEMPLATE = app                     # app=可执行,lib=动态库
SOURCES += main.cpp widget.cpp
HEADERS += widget.h

+= 是追加,= 是覆盖。语法有点像 Makefile 又不太像,反正记住几个关键变量就够用。

一个新手必踩的坑:改了 .pro 之后必须 "Run qmake" 一次 ,不能直接 Ctrl+R。否则 qmake 不会重新生成 Makefile,你新加的源文件根本不会被编译。Qt Creator 里右键项目能看到这个菜单项。

必须有的 Q_OBJECT 宏

widget.h 里有这么一行:

cpp 复制代码
class Widget : public QWidget
{
    Q_OBJECT      // ← 这个
public:
    // ...
};

我第一次自己写 Qt 类的时候没加这个,自定义信号槽,编译器给我报:

复制代码
undefined reference to `vtable for MyClass'

链接错误,看上去跟 Q_OBJECT 八竿子打不着。查了半天才发现是这个宏漏了。

原理:Qt 的信号槽、属性、动态调用这一套不是纯 C++ 能实现的,需要一个叫 moc (meta object compiler)的预处理器,扫描带 Q_OBJECT 的类,生成额外的 moc_xxx.cpp 文件来支撑这些功能。没这个宏 moc 就跳过你这个类,链接的时候找不到那些"虚的"函数。

记住一条规则:只要类里用了信号槽,就必须加 Q_OBJECT

一些零碎踩坑

  • 中文显示成 ??? :源文件保存编码改成 UTF-8 (with BOM),或者用 QStringLiteral 包字符串。我后来全用 UTF-8 BOM 省事。
  • 改了头文件后编译异常 :删掉 build-xxx/ 目录重新构建。Qt Creator 的 shadow build 有时候缓存抽风。
  • Ctrl+R 提示找不到 Kit :去 Edit → Preferences → Kits 看一下,红色感叹号的话点进去看缺啥(一般是 Qt Versions 或 Compilers 没指定)。
  • 运行起来报缺 Qt6Core.dll :这是你想把 exe 拷到别的地方跑出来的报错。要把 Qt 的 bin/ 目录加到 PATH,或者用 windeployqt.exe your.exe 一键复制所有依赖。

一点感想

Qt 这套机制有点反 C++ 直觉------对象树自动管内存、moc 生成元信息、信号槽这种"魔法"。一开始挺别扭,会觉得"这不像我熟悉的 C++"。

但跑通几个 demo 之后慢慢发现,Qt 是在用一套自己的运行时把 C++ 改造成"更适合写 GUI 的 C++"。你不需要每个控件手撸智能指针,不需要自己注册回调,不需要纠结跨线程通信。它把这些复杂度都吞下去了。

代价就是有自己的一套思维方式要学,而不是一上来就能写。我大概折腾了一晚上才有点感觉,先记到这。下次想搞搞信号槽,把按钮点击之后干点别的事儿这套流程跑顺。


代码我放在自己机器的 D:/QT_Learn/projects/ch01_helloworld/ 下面,编译命令是:

bash 复制代码
qmake.exe ch01_helloworld.pro
mingw32-make.exe release
./release/ch01_helloworld.exe

如果有人也在折腾 Qt 入门,欢迎评论区交流踩过的坑。

相关推荐
灰色人生qwer1 小时前
python 中 BaseModel 在这里有什么用?
开发语言·python·状态模式
思麟呀1 小时前
在C++基础上理解CSharp-3
开发语言·c++·c#
techdashen1 小时前
Rust 能帮你捕获什么,又不能捕获什么
开发语言·后端·rust
Geometry Fu1 小时前
《设计模式》2026编程作业汇总
java·c++·设计模式
吃好睡好便好1 小时前
在Matlab中绘制柱面图
开发语言·学习·算法·matlab
Anastasiozzzz1 小时前
深度解析 AI 时代的“TCP/IP协议”:Agent-to-Agent (A2A) 通信架构与多智能体协同底层逻辑
大数据·开发语言·网络·数据库·网络协议·tcp/ip·架构
ChoSeitaku1 小时前
02.变量_数据类型转换_运算符
java·大数据·开发语言
AI科技星1 小时前
基于全域数学0-1-∞体系的1.237宇宙临界常数及时空超导统一理论
c语言·开发语言·线性代数·量子计算·agi
Arman_1 小时前
Rust 接入阿里云 OSS 断点上传下载:rusty-cat 直连模式实战
开发语言·阿里云·rust·oss断点续传