Qt 类完整例程【逐词拆解 + 逐步骤执行保姆教程】

前面学了纯 C++ 普通类,现在学习 Qt 的类。 记住一句话:Qt 类本质就是 C++ 的类,只是 Qt 框架扩展了一套【QObject 对象模型】

本例选用 Qt 最基础的自定义 QObject 子类,只用到信号槽,不涉及 UI 界面(Qt 控制台项目,更容易理解)

cpp 复制代码
// myphone.h 头文件
#ifndef MYPHONE_H
#define MYPHONE_H

// 引入QObject头文件,QObject是Qt所有对象的根类
#include <QObject>

// ========== 类定义开始 ==========
class MyPhone : public QObject
{
    // Q_OBJECT宏,Qt独有,开启元对象系统,支持信号槽
    Q_OBJECT
public:
    // 构造函数
    explicit MyPhone(QObject *parent = nullptr);

    // 普通成员函数:发射打电话信号
    void startCall(QString number);

// 信号:Qt独有,只声明,不用写函数实现
signals:
    void callSignal(QString num);

// 槽函数,接收信号,执行业务逻辑
public:
    void onCallReceive(QString num);
};
// ========== 类定义结束 ==========
#endif // MYPHONE_H
cpp 复制代码
// myphone.cpp
#include "myphone.h"
#include <QDebug>

// 构造函数实现
MyPhone::MyPhone(QObject *parent)
    : QObject(parent)
{
}

void MyPhone::startCall(QString number)
{
    // emit:发射信号,Qt关键字
    emit callSignal(number);
}

void MyPhone::onCallReceive(QString num)
{
    qDebug() << "收到来电,号码:" << num;
}
cpp 复制代码
// main.cpp
#include <QCoreApplication>
#include "myphone.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyPhone phone1;
    MyPhone phone2;

    // connect 信号槽连接函数
    connect(&phone1, &MyPhone::callSignal, &phone2, &MyPhone::onCallReceive);

    phone1.startCall("10086");

    return a.exec();
}

运行输出:

cpp 复制代码
收到来电,号码:"10086"

第一部分:头文件 class MyPhone : public QObject 逐词拆解

cpp 复制代码
class MyPhone : public QObject
  1. class

    • C++ 关键字,含义:定义一个类
    • 作用:告诉编译器,下面要定义一张 "图纸"。和上一节普通 C++ 类的 class 完全一样。
    • 必须写,没有 class,编译器不会识别这是类。
  2. MyPhone

    • 类名,我们自定义的名字,代表这个 Qt 类叫 MyPhone(我的手机)
    • 驼峰命名,首字母大写,只是图纸名称,此时没有创建对象,不存储数据,不占对象内存
  3. : public QObject

    • : 冒号:代表继承,C++ 原生语法
    • public:公有继承
    • QObject:Qt 提供的基类(父类图纸)
    • 整句含义:MyPhone 类继承自 QObject,自动拥有 QObject 自带全部能力(信号槽、对象树、元对象系统)

    普通 C++ 类没有这一段;继承 QObject 是 Qt 类的标志性写法

  4. { }大括号 大括号内部存放这个类的全部内容:宏、成员函数、信号。}; 结尾分号不能丢。


Q_OBJECT

cpp 复制代码
Q_OBJECT
  1. Q_OBJECT 是 Qt 提供的宏,不是标准 C++ 关键字
  2. 作用:开启 Qt 元对象系统,只有写了这个,当前类才能使用信号、槽。
  3. 强制规则:
    • 必须写在继承 QObject 的类内部;
    • 写在头文件里面;
    • 添加或者删除 Q_OBJECT 之后,必须执行 qmake,重新构建项目,否则编译报错虚表错误。

普通 C++ 类没有 Q_OBJECT。

explicit MyPhone(QObject *parent = nullptr); 构造函数

cpp 复制代码
explicit MyPhone(QObject *parent = nullptr);
  1. explicit:C++ 关键字,禁止隐式类型转换。Qt 继承 QObject 的类,构造函数基本都会加 explicit。
  2. MyPhone :构造函数,名字和类名完全相同,没有返回值,创建对象自动调用。
  3. QObject *parent :形参,父对象指针 。
    • parent 用来设置 Qt 对象树,父对象销毁时自动删除子对象,自动管理内存。
  4. = nullptr :默认参数。创建对象时,如果不传 parent,parent 默认等于空指针。
    • nullptr:空指针,代表没有父对象。

void startCall(QString number);

  1. void:返回值类型,函数执行完不返回任何数据
  2. startCall:成员函数名字
  3. (QString number):参数列表,调用函数时,需要传入一个 QString 字符串,存到局部变量 number 中。
  4. ;:声明结束(头文件只做声明,函数体写在 cpp 文件)

signals:

  • Qt 的标记,不是 C++ 原生关键字
  • 含义:下面定义的全部是信号
  • 信号特点:只写函数声明,不需要写 {} 函数体,moc 工具自动帮我们生成实现代码。
cpp 复制代码
void callSignal(QString num);

callSignal 信号函数:可以携带参数,用来向外传递数据。

public:

和普通 C++ 一样,public 下面的函数,外部代码(main)可以调用。

cpp 复制代码
void onCallReceive(QString num);

槽函数,用来接收信号,收到信号后执行里面代码,函数体在 cpp 实现。

#ifndef / #define / #endif

头文件保护宏,防止头文件被多次重复包含导致编译报错,Qt 头文件默认都带。

cpp 复制代码
#ifndef MYPHONE_H
#define MYPHONE_H
// 代码
#endif // MYPHONE_H

第二部分 cpp 文件逐词讲解

cpp 复制代码
MyPhone::MyPhone(QObject *parent)
    : QObject(parent)
{
}
  1. MyPhone::MyPhone
    • :: 作用域解析符。读作 "的"
    • 含义:MyPhone 类里面的 MyPhone 构造函数
  2. : QObject(parent) 初始化列表
    • 子类构造函数执行前,先调用父类 QObject 的构造函数,把 parent 传给父类
    • Qt 继承 QObject 的类,构造函数必须写这个。
cpp 复制代码
emit callSignal(number);
  1. emit:Qt 宏,标记 "发射信号"
  2. callSignal(number):触发 callSignal 信号,把 number 的值传递出去

emit 本质是空宏,编译后消失,只是给程序员看的标记。

cpp 复制代码
void MyPhone::onCallReceive(QString num)
{
    qDebug() << "收到来电,号码:" << num;
}
  1. MyPhone::onCallReceive:MyPhone 类里面的 onCallReceive 函数
  2. qDebug():Qt 打印输出工具,替代 cout,可以直接打印 QString 类型
  3. << 输出运算符,可以拼接多个内容,自动换行。

第三部分 main.cpp 逐词拆解,重点看 phone1.startCall("10086"); 和 connect

cpp 复制代码
MyPhone phone1;
MyPhone phone2;
  1. MyPhone:Qt 类名(图纸)
  2. phone1:对象名,按照 MyPhone 图纸创建出来的实例。 phone1 和 phone2 是两个独立对象,各自拥有自己的成员。 创建的时候,自动调用 MyPhone 构造函数,parent 默认是 nullptr。
cpp 复制代码
connect(&phone1, &MyPhone::callSignal, &phone2, &MyPhone::onCallReceive);

connect 是 Qt 全局函数,用来把信号和槽绑定在一起。 参数逐个拆解:

  1. &phone1:& 取地址,代表信号发送对象是 phone1(指针)
  2. &MyPhone::callSignal:取信号函数地址,phone1 发出的信号 callSignal
  3. &phone2:接收信号的对象 phone2
  4. &MyPhone::onCallReceive:接收信号后执行的槽函数

一句话:把 phone1 的 callSignal 信号,连接到 phone2 的 onCallReceive 槽函数。只要 phone1 发射 callSignal,phone2 的 onCallReceive 就自动执行。

cpp 复制代码
phone1.startCall("10086");
  1. phone1:对象名字,我们操作 phone1 这个实例
  2. . 点号(成员访问运算符),读作 "的"。访问 phone1 对象里面的成员函数
  3. startCall:成员函数名称
  4. **()圆括号**:代表调用、执行这个函数。括号里面"10086"` 作为参数传给 number
  5. ; 语句结束标记。

整句含义:调用 phone1 对象的 startCall 成员函数,传入号码 10086。 startCall 内部执行 emit callSignal(number) → 发射信号。 因为 connect 提前绑定好了,phone2 的 onCallReceive 自动被调用,打印信息。

cpp 复制代码
return a.exec();

a 是 QCoreApplication 对象,a.exec() 开启 Qt事件循环,监听信号事件。

普通 C++ 控制台程序不需要事件循环;Qt 信号槽程序必须开启事件循环。

第四部分 完整执行流程,一步一步跟着走

  1. QCoreApplication a(argc, argv); 创建 Qt 应用程序对象,Qt 程序必须要有这个,管理事件循环。
  2. MyPhone phone1; 创建 phone1 对象,自动调用 MyPhone 构造函数,parent=nullptr。
  3. MyPhone phone2; 创建 phone2 对象,自动调用构造函数,parent=nullptr。
  4. connect(...) 建立连接:phone1 的 callSignal 信号绑定 phone2 的 onCallReceive 槽。此时函数不会执行,只是登记绑定关系。
  5. phone1.startCall("10086"); 调用 phone1 的 startCall 函数,传入参数 "10086" 执行 emit callSignal(number) → 发射 callSignal 信号,附带参数 "10086" Qt 事件系统查找所有绑定这个信号的槽函数 找到 phone2.onCallReceive,自动执行,把 "10086" 传给形参 num qDebug 打印:收到来电,号码:"10086"
  6. return a.exec(); 开启事件循环,等待后续信号事件。本例没有更多事件,程序等待,最后退出。

第五部分 Qt 类 vs 普通 C++ 类对比(复习巩固)

项目 普通 C++ 类(MobilePhone 例子) Qt 继承 QObject 的类(MyPhone)
class 关键字 有,定义类 有,定义类,底层仍然 C++
继承 可选 一般继承 QObject
Q_OBJECT 无 需要写,开启元对象系统
signals、emit 不存在 Qt 扩展,实现信号槽通信
内存管理 new 对象必须手动 delete 设置 parent,父对象自动释放子对象
对象拷贝 支持 ❌禁止拷贝,QObject 不能直接赋值复制
事件循环 不需要 信号槽程序需要 a.exec () 事件循环

新手高频踩坑

  1. 加了 Q_OBJECT,忘记执行 qmake,报 undefined reference to vtable 虚表错误。
  2. 信号槽参数:槽的参数不能比信号参数多,否则连接失败。
  3. QObject 子类不能写 MyPhone p = phone1; 拷贝对象,编译报错。
  4. connect 里要传对象地址,所以要写&phone1,不要漏掉 &。
  5. 头文件末尾 }; 分号不能丢。
相关推荐
code2cat2 小时前
Java进阶篇之AtomicReferenceFieldUpdater:用不可变快照替换整份状态
java·开发语言·cas
Nathan__272 小时前
PHP面试题——情景应用
开发语言·php
geats人山人海2 小时前
现代c++2.4 虚继承
java·开发语言·c++
我命由我123452 小时前
Android 广播 - 组件通信
android·java·开发语言·android studio·android jetpack·android-studio·android runtime
前端 贾公子2 小时前
LangGraph == 图的状态(State)管理 (中)
开发语言·数据库·python
李福春3 小时前
斗破Google ADK Java苍穹-第2境斗者-凝旋筑基-Java环境
java·开发语言·gemini·llmagent·深圳同盟·腾讯云架构师技术同盟·adk java
老王爱玩车3 小时前
预处理详解
c语言·开发语言·学习
灯澜忆梦3 小时前
【Go Zap 日志库】#1 |快速上手 + 配置详解
开发语言·后端·golang
a努力。4 小时前
四维解析Agent记忆架构:从零实战LangGraph长期记忆
java·开发语言·数据库