QT入门学习(二)---继承关系、访问控制和变量定义
一:继承关系
1.1 基本继承语法
class 子类 : public 父类 {// 子类定义
};
1.public
表示继承方式(还有 protected
和 private
,但 Qt 中通常用 public
)。
2.子类会继承父类的所有 public
和 protected
成员。
3.子类可以重写(override)父类的虚函数。
1.2 QT的典型继承链
这里我只讲OBJECT继承链
QObject
├── QWidget
│ ├── QAbstractButton
│ │ ├── QPushButton
│ │ ├── QCheckBox
│ │ └── QRadioButton
│ ├── QLineEdit
│ ├── QLabel
│ ├── QComboBox
│ ├── QGroupBox
│ └── QFrame
│ ├── QSplitter
│ ├── QScrollArea
│ └── QToolBox
├── QThread
├── QTimer
├── QAbstractItemModel
└── QIODevice
1.2.1 QObject
QObject是所有对象的基类,作为根节点。
QWidget、QThread、QTimer 等均继承自 QObject
核心机制:
对象树:通过 parent
参数自动管理内存(如 QWidget(parent)
)。
信号与槽:所有子类均可使用 connect()
实现事件通信。
示例代码
QWidget *widget = new QWidget(parent); // parent为QObject指针
// widget的生命周期由parent管理
1.2.2 QWidget 与 UI 组件
GUI 组件的抽象:QAbstractButton → QPushButton/QCheckBox/QRadioButton
QLineEdit、QLabel、QComboBox 等直接继承 QWidget。
布局系统:通过 QLayout
管理子控件位置(如 QVBoxLayout::addWidget()
)。
示例代码
QPushButton *button = new QPushButton("Click me", this); // this为QWidget*
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(button); // 布局管理button的位置
1.2.3 线程模块QThread
作用
1. 实现多线程编程,将耗时操作(如文件处理、网络请求)放在后台线程,避免阻塞 UI。
2. 通过信号与槽实现线程间安全通信。
常用函数
函数名 | 功能描述 |
---|---|
start() | 启动线程,触发 run() 方法执行。 |
run() | 线程入口点,需在子类中重写(继承 QThread 方式)。 |
quit() | 请求线程退出(需线程内部配合检查)。 |
terminate() | 强制终止线程(不推荐,可能导致资源泄漏)。 |
wait() | 阻塞当前线程,直到目标线程结束。 |
isRunning() | 判断线程是否正在运行。 |
示例代码
// 方式1:继承QThread(简单但有限制)
class WorkerThread : public QThread {Q_OBJECT
protected:void run() override {for (int i = 0; i < 100; ++i) {emit progress(i);msleep(100); // 模拟耗时操作}}
signals:void progress(int value);
};// 使用
WorkerThread *thread = new WorkerThread(this);
connect(thread, &WorkerThread::progress, ui->progressBar, &QProgressBar::setValue);
thread->start(); // 启动线程
1.2.4 定时器模块QTimer
1.实现周期性任务
2.基于事件循环,不会阻塞线程。
常用函数
函数名 | 功能描述 |
---|---|
start(int msec) | 启动定时器,每隔 msec 毫秒触发一次 timeout 信号。 |
stop() | 停止定时器。 |
setSingleShot(bool) | 设置是否单次触发(true 表示仅触发一次)。 |
isActive() | 判断定时器是否正在运行。 |
interval() | 返回当前定时器的间隔时间(毫秒)。 |
示例代码
// 周期性定时器
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MyClass::updateStatus);
timer->start(1000); // 每秒触发一次timeout信号// 单次延迟执行
QTimer::singleShot(3000, this, [this]() {qDebug() << "3秒后执行此代码";
});
1.2.5 数据模型QAbstractItemModel
1.实现模型 - 视图 - 控制器(MVC)架构,将数据与显示分离。
2.支持多种视图(列表、表格、树)展示同一数据。
常用函数(需重写)
函数名 | 功能描述 |
---|---|
rowCount() | 返回模型的行数。 |
columnCount() | 返回模型的列数。 |
data(const QModelIndex &index, int role) | 返回指定索引和角色的数据(如 Qt::DisplayRole 表示显示文本)。 |
headerData() | 返回表头数据。 |
setData() | 修改模型数据(需配合 dataChanged 信号)。 |
示例代码
// 自定义表格模型
class PersonModel : public QAbstractTableModel {Q_OBJECT
public:enum Columns { Name, Age, ColumnCount };int rowCount(const QModelIndex &) const override { return m_data.size(); }int columnCount(const QModelIndex &) const override { return ColumnCount; }QVariant data(const QModelIndex &index, int role) const override {if (role == Qt::DisplayRole) {const auto &person = m_data[index.row()];return (index.column() == Name) ? person.name : QString::number(person.age);}return QVariant();}void addPerson(const QString &name, int age) {beginInsertRows(QModelIndex(), rowCount(), rowCount());m_data.append({name, age});endInsertRows();}private:struct Person { QString name; int age; };QList<Person> m_data;
};// 使用
PersonModel model;
model.addPerson("Alice", 25);
QTableView *view = new QTableView();
view->setModel(&model); // 视图自动更新
1.2.5 I/O 操作QIODevice
1.提供统一的输入输出接口,支持文件、网络、内存等不同设备。
2.支持同步和异步操作。
常用函数
函数名 | 功能描述 |
---|---|
open(OpenMode mode) | 打开设备(如 QIODevice::ReadOnly 、QIODevice::WriteOnly )。 |
close() | 关闭设备。 |
read(qint64 maxSize) | 读取最多 maxSize 字节的数据。 |
readAll() | 读取所有可用数据。 |
write(const QByteArray &data) | 写入数据。 |
atEnd() | 判断是否到达数据末尾。 |
bytesAvailable() | 返回可用字节数。 |
示例代码
// 文件读写
QFile file("data.txt");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {QTextStream out(&file);out << "Hello, World!\n";file.close();
}// 网络通信(TCP客户端)
QTcpSocket socket;
socket.connectToHost("127.0.0.1", 1234);
connect(&socket, &QTcpSocket::readyRead, [&]() {qDebug() << "Received:" << socket.readAll();
});
socket.write("Request data");
二:访问控制
2.1 C++ 基础访问控制符
Qt 基于 C++,因此首先需要理解 C++ 的三种基本访问控制符:
控制符 | 含义 |
---|---|
public | 所有代码均可访问(包括类外部、子类)。 |
protected | 仅类内部和子类可访问,类外部不可访问。 |
private | 仅类内部可访问,子类和类外部均不可访问。 |
示例代码:
class Base {
public:int publicVar; // 公共变量,可被任何代码访问
protected:int protectedVar; // 受保护变量,仅Base及其子类可访问
private:int privateVar; // 私有变量,仅Base内部可访问
};class Derived : public Base {
public:void accessMembers() {publicVar = 1; // 合法:public成员可被任何代码访问protectedVar = 2; // 合法:子类可访问protected成员// privateVar = 3; // 非法:子类无法访问父类的private成员}
};
2.2 Qt 特有的访问控制机制
1. Q_OBJECT 宏与元对象系统
作用:Q_OBJECT
宏使类支持信号与槽、属性系统和元对象反射。
访问限制:所有使用 Q_OBJECT
的类必须继承自 QObject
,且必须在头文件中声明。
示例代码
class MyClass : public QObject {Q_OBJECT // 必须包含此宏
public:explicit MyClass(QObject *parent = nullptr);
signals:void mySignal(); // 信号默认是protected的
public slots:void mySlot(); // 槽函数可设置为public/protected/private
};
2. 信号的访问控制
默认权限:信号默认是 protected
的,只能通过 emit
语句在类内部或子类中触发。
设计意图:防止外部代码直接触发信号,确保事件流的可控性。
示例代码
class Button : public QPushButton {Q_OBJECT
public:void fakeClick() {// emit clicked(); // 非法:信号是protected的,不能直接调用click(); // 合法:调用父类的public方法,该方法内部会emit clicked()}
};
3. 属性系统的访问控制(Q_PROPERTY)
语法
Q_PROPERTY(type name READ read WRITE write NOTIFY notify)
1.READ
方法必须是 public
的。
2.WRITE
方法(如果有)必须是 public
的。
3.NOTIFY
信号必须是 protected
的(由 Qt 自动处理)。
示例代码
class Person : public QObject {Q_OBJECTQ_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:explicit Person(QObject *parent = nullptr);QString name() const { return m_name; } // READ方法必须publicvoid setName(const QString &name) { // WRITE方法必须publicif (m_name != name) {m_name = name;emit nameChanged(name);}}signals:void nameChanged(const QString &name); // NOTIFY信号默认protectedprivate:QString m_name;
};
4. 事件处理函数的访问控制
1.常见事件函数:paintEvent()
、mousePressEvent()
等默认是 protected
的。
2.设计意图:鼓励子类重写这些函数,而不是从外部直接调用。
示例代码
class CustomWidget : public QWidget {Q_OBJECT
protected:void paintEvent(QPaintEvent *event) override {// 自定义绘制逻辑QPainter painter(this);painter.drawText(rect(), "Custom Text");QWidget::paintEvent(event); // 调用父类实现}
};
2.3 继承与访问控制
1. 继承方式对访问权限的影响
继承方式 | 基类的 public 成员 | 基类的 protected 成员 | 基类的 private 成员 |
---|---|---|---|
public | 仍为 public | 仍为 protected | 不可访问 |
protected | 变为 protected | 变为 protected | 不可访问 |
private | 变为 private | 变为 private | 不可访问 |
示例代码
class Base {
public:int pub;
protected:int prot;
private:int priv;
};class PublicDerived : public Base {// pub 保持 public// prot 保持 protected// priv 不可访问
};class PrivateDerived : private Base {// pub 变为 private// prot 变为 private// priv 不可访问
};
2. 子类对父类成员的访问规则
1.private
成员:子类永远无法直接访问。
2.protected
成员:子类可访问,但外部代码不可访问。
3.public
成员:子类和外部代码均可访问。
2.4 友元(Friend)机制
允许特定类或函数访问另一个类的 private
和 protected
成员。
语法
friend class FriendClass;
friend void friendFunction();
示例代码
class BankAccount {
private:double balance;
public:friend class BankManager; // BankManager可访问所有private/protected成员friend void resetBalance(BankAccount &account); // 友元函数
};class BankManager {
public:void adjustBalance(BankAccount &account) {account.balance = 0; // 合法:BankManager是友元类}
};void resetBalance(BankAccount &account) {account.balance = 0; // 合法:resetBalance是友元函数
}
2.5. 访问控制的最佳实践
1.最小权限原则
尽量将成员设为 private
,仅通过 public
接口暴露必要功能。
示例代码
class Counter {
private:int m_count = 0;
public:int count() const { return m_count; } // 只读访问void increment() { m_count++; } // 提供受控修改
};
2.使用 protected
进行接口扩展
当需要子类重写某些功能时,将函数设为 protected virtual
。
示例代码
class Shape {
public:double area() const { return doCalculateArea(); }
protected:virtual double doCalculateArea() const = 0; // 子类必须实现
};
3.避免过度使用友元
友元破坏了封装性,优先考虑通过 public
接口或继承解决问题。
三:变量定义
1. 局部变量
作用域:函数或代码块内部。
生命周期:栈上分配,离开作用域自动销毁。
示例:
void MyClass::doWork() {int count = 0; // 基本类型QString message = "Hello"; // Qt类QVector<int> numbers{1, 2, 3}; // 容器类
} // 变量在此处销毁
2. 成员变量
作用域:类的所有成员函数。
生命周期:与对象相同(堆或栈)。
访问控制:通过 public
/protected
/private
限制。
class MyClass : public QObject {Q_OBJECT
private:int m_counter = 0; // 前缀 `m_` 是常见约定QString m_name; // 默认初始化QScopedPointer<QTimer> m_timer; // 智能指针管理资源
};
3. 静态变量
作用域:类或文件内部(取决于 static
位置)。
生命周期:程序启动时创建,程序结束时销毁。
示例代码
class MyClass {
public:static int instanceCount; // 类静态变量,所有实例共享
};int MyClass::instanceCount = 0; // 必须在类外初始化// 文件静态变量(仅当前文件可见)
static QString s_appName = "MyApp";
4. Qt 特有的变量类型
1. Qt 基本数据类型
类型 | 描述 | 等价 C++ 类型 |
---|---|---|
qint8 | 8 位有符号整数 | signed char |
quint32 | 32 位无符号整数 | unsigned int |
qreal | 浮点数(通常为 double ) | double |
QString | Unicode 字符串 | - |
QByteArray | 二进制数据 | - |
QVariant | 可变类型(可存储多种数据类型) | - |
qint64 fileSize = 1024 * 1024; // 64位整数
QString text = "你好,Qt"; // Unicode字符串
QByteArray data = QByteArray::fromHex("48656C6C6F"); // 二进制数据
2. 容器类
优势:自动内存管理、支持隐式共享(写时复制)。
常用容器:
QVector<int> numbers; // 动态数组(连续内存)
QList<QString> names; // 链表(适合频繁插入删除)
QMap<QString, int> ages; // 键值对(红黑树实现,有序)
QHash<QString, int> scores; // 哈希表(无序,查找更快)
示例代码
QVector<QString> fruits{"Apple", "Banana"};
fruits.append("Cherry");QMap<QString, int> person;
person["age"] = 30;
person["height"] = 175;
5. 内存管理与智能指针
1. 对象树管理(QObject 派生类)
机制:通过 parent
参数自动管理内存。
示例代码
QWidget *window = new QWidget(nullptr); // 顶级窗口
QPushButton *button = new QPushButton("Click", window); // button的parent是window
// 当window被删除时,button会自动被删除
2. 智能指针
QScopedPointer
:独占所有权,离开作用域自动删除。
void func() {QScopedPointer<QTimer> timer(new QTimer());// timer在函数结束时自动释放
}
QSharedPointer
:共享所有权,引用计数为 0 时删除。
void shareData() {QSharedPointer<QImage> image(new QImage("image.png"));QSharedPointer<QImage> copy = image; // 共享同一对象// 当所有sharedPointer都被销毁时,QImage才会被删除
}
6. 线程安全与变量
1. 线程局部存储(QThreadStorage)
作用:为每个线程创建独立的变量副本。
示例代码
QThreadStorage<int> threadCounter;void incrementCounter() {if (!threadCounter.hasLocalData())threadCounter.setLocalData(0);threadCounter.localData()++;
} // 每个线程的counter独立计数
2. 原子变量(QAtomicInteger)
作用:提供无锁的线程安全操作。
QAtomicInteger<int> atomicCounter(0);void threadFunc() {atomicCounter.fetchAndAddOrdered(1); // 原子自增
}
7. 属性系统(Q_PROPERTY)
1. 声明属性
Q_PROPERTY(type name READ read WRITE write NOTIFY notify)
2. 通过元对象系统访问属性
Person person;
person.setProperty("name", "Alice"); // 动态设置属性
QString name = person.property("name").toString(); // 动态获取属性
8. 变量初始化与资源管理
1. 成员变量初始化
构造函数初始化列表:
class MyClass {
public:MyClass() : m_value(0), m_name("") {} // 初始化列表
private:int m_value;QString m_name;
};
就地初始化(C++11+):
class MyClass {
private:int m_value = 0; // 就地初始化QString m_name{"Name"}; // 统一初始化语法
};
2. 资源管理(RAII)
使用 QFile
自动管理文件句柄。
void writeToFile() {QFile file("data.txt");if (file.open(QIODevice::WriteOnly)) {QTextStream out(&file);out << "Hello, Qt!";} // 文件在离开作用域时自动关闭
}