title: 面试-Qt应用开发 tags: [嵌入式Linux, 面试, Qt, C++, 面向对象, 类与对象, 继承, 重载, 多态, 虚函数, 命名空间, Qt Creator, qmake, ui文件, 信号与槽, connect, Q_OBJECT, 控件, QPushButton, QComboBox, QLineEdit, 布局, QBoxLayout, QGridLayout, 模型视图, QStandardItemModel, QListView, QTableView, QFile, QTextStream, QPainter, QChart, QThread, QMutexLocker, QTcpSocket, QTcpServer, QUdpSocket, QMediaPlayer, QSqlDatabase, QSqlTableModel, SQLite, 交叉编译, linuxfb, eglfs, I.MX6U] created: 2026-09-18 updated: 2026-09-18
💡 关联知识:[[05-Qt应用开发/01-C++基础与面向对象]]、[[05-Qt应用开发/02-Qt入门与项目结构]]、[[05-Qt应用开发/03-信号与槽]]、[[05-Qt应用开发/04-Qt控件详解]]、[[05-Qt应用开发/05-布局容器与模型视图]]、[[05-Qt应用开发/06-文本绘图与图表]]、[[05-Qt应用开发/07-多线程网络多媒体与数据库]]、[[05-Qt应用开发/08-嵌入式Qt交叉编译与项目实战]];延伸阅读:[[编程学习项目/使用Qt和C++构造UI界面/1. Qt概述]]、[[实际项目开发文档/加解密客户端(Qt+LibreSSL)/1.项目源代码]]
覆盖《I.MX6U嵌入式Qt开发指南》全书 8 大主题、40 道题。每题含「答案要点 / 详细解答(代码、对比表)/ 2 条追问」,可直接用于复习、查询与面试自测。
cin/cout 与 scanf/printf 有何区别?命名空间有什么用?答案要点:新增 bool 类型、变量可随用随定义并支持直接初始化;cin/cout 替代 scanf/printf,无需指定格式符;命名空间用于解决名字冲突。
详细解答:
int x(100); 初始化,变量不必集中在函数开头定义。cin >> x >> y;、cout << x << y << endl;;endl 等价于换行 \n,但 endl 会刷新缓冲。效率上 scanf/printf 更高,便利性上 cin/cout 更好。注意变量间要用 << 而非逗号。命名空间:标准库名字都加 std:: 前缀,using namespace std; 后可直接写 vector、cout。也能自定义:
namespace A {
int x = 1;
void fun() { cout << "A namespace" << endl; }
}
using namespace A;
int main() {
fun(); /* 已声明 using,可直接调用 */
A::x = 3; /* :: 表示作用域 */
A::fun();
return 0;
}
注意 #include <iostream> 是标准流(无 .h,需命名空间);#include <iostream.h> 是非标准 C 库。
追问
std::cin 中的 :: 是什么运算符?(作用域运算符,表示“std 命名空间下的”)using namespace?(库或公司自定义命名空间存在同名冲突时)答案要点:类是对事物的抽象,对象是类的实例;栈上对象自动释放,堆上对象用 new/delete 手动管理;构造在实例化时自动调用一次,析构在生命周期结束时自动调用。
详细解答:
class Dog {
public:
string name;
int age;
void run() { cout << name << " " << age << endl; }
};
Dog dog1; /* 栈中实例化,函数结束自动析构 */
dog1.name = "旺财"; dog1.age = 2; dog1.run();
Dog *dog2 = new Dog(); /* 堆中实例化,需手动 delete */
dog2->name = "富贵"; dog2->age = 1; dog2->run();
delete dog2; dog2 = NULL;
访问限定符:public(公有)、private(私有)、protected(保护),默认 private。
构造函数特点:与类名同名、可重载、没有返回类型(void 也不行)、实例化时自动调用且仅一次。析构函数特点:~类名()、无参数、无返回值、生命周期结束时释放资源。类内实现或类外 :: 实现均可。
追问
this 指针是什么?(指向当前实例对象的指针,只能在成员函数使用,成员函数默认首参为 T * const this)protected 继承后基类成员在派生类中的权限如何变化?答案要点:public / protected / private 三种;权限按“取更严格者”规则变化;默认为 private。
详细解答:
| 继承方式 | 基类 public | 基类 protected | 基类 private |
|---|---|---|---|
| public | public | protected | 不可直接访问 |
| protected | protected | protected | 不可直接访问 |
| private | private | private | 不可直接访问 |
class Animal { public: string color; int weight; };
class Dog : public Animal { public: string name; int age; void run(); };
/* Dog 的实例可直接使用继承来的 color / weight */
继承达到代码重用、提高效率的目的。Qt 中大量通过继承来重写/扩展已有类。
追问
答案要点:同一作用域内同名但参数列表不同的函数;运算符重载本质是函数重载,形式为 operator 运算符(参数表);.、::、sizeof、?: 等不能重载。
详细解答:
函数重载:参数个数、类型或顺序必须不同,仅返回类型不同不能构成重载。
class Dog {
public:
void getWeight(int w) { cout << w << "kG" << endl; }
void getWeight(double w) { cout << w << "kG" << endl; }
};
运算符重载:
class Dog {
public:
int weight;
Dog operator+(const Dog &d) { /* 重载 + */
Dog dog;
dog.weight = this->weight + d.weight;
return dog;
}
};
不可重载:成员访问 .、成员指针访问 .* 与 ->*、域运算符 ::、sizeof、条件运算符 ?:、预处理符号 #。
追问
+、== 等,形式统一)this,因为对象是作为参数传入的)答案要点:同一调用根据对象实际类型执行不同函数;条件为继承、同名虚函数、基类指针/引用调用;虚函数必须有实现,纯虚函数 =0 无实现、使类成为抽象类。
详细解答:
形成多态必须具备三个条件:① 存在继承关系;② 继承关系中有同名虚函数(基类用 virtual 声明);③ 存在基类类型的指针或引用,通过它调用虚函数。
class Animal { public: virtual void run() { cout << "Animal" << endl; } };
class Dog : public Animal { public: void run() { cout << "Dog" << endl; } };
class Cat : public Animal { public: void run() { cout << "Cat" << endl; } };
Animal *animal; /* 基类指针,未实例化 */
Dog dog; Cat cat;
animal = &dog; animal->run(); /* 输出 Dog */
animal = &cat; animal->run(); /* 输出 Cat */
virtual ReturnType Fun(Param),必须实现;使编译器动态绑定(后期绑定)。virtual void funtion1() = 0;,没有实现,用来规范派生类接口;含纯虚函数的类是抽象类,不能实例化,只能作接口。追问
virtual 会怎样?(静态链接到基类函数,调用基类自己的 run(),多态失效)QWidget 的 paintEvent() 为什么能被子类重写?(它在基类中定义为虚函数,这正是多态的典型应用)答案要点:Qt 是跨平台 C++ 开发库(不止 GUI);Qt Creator 是 IDE;Qt C++ 功能强、成熟,QML 适合做炫酷界面,二者常结合使用。
详细解答:Qt 除了控件、布局、交互,还内置多线程、数据库、图像/音视频、网络、文件等模块;支持 Windows/Linux/Unix/Android/iOS/QNX/VxWorks 等平台,一份代码稍作修改即可跨平台运行。Qt 还有 Python、Ruby 等绑定。Qt Creator 是官方 IDE,负责编辑、编译、调试、UI 设计。
Qt C++ 效率更高、底层功能突出;QML 是描述性语言,配合 Qt Quick 做移动端风格的界面,但底层(文件读写、图像处理、多线程通信)仍靠 C++。低版本(Qt5.6 以下)QML 不够成熟,Qt5.7 以上更完善。本教程主打 Qt C++。
追问
QMainWindow/QWidget/QDialog 如何选?答案要点:.pro(工程)、.h(声明)、.cpp(实现)、.ui(界面);QMainWindow 带菜单/工具栏/状态栏,QWidget 是界面基类且最“轻”,QDialog 是对话框。
详细解答:
| 文件 | 作用 |
|---|---|
*.pro |
项目管理文件,增删文件时 Qt Creator 自动维护,也可手动加配置 |
*.h |
类的声明;用到信号的类必须有 Q_OBJECT |
*.cpp |
类的实现 |
*.ui |
XML 描述的界面文件,编译时生成 ui_*.h |
基类选择:
QMainWindow:主窗口,自带菜单栏、工具栏、状态栏,适合嵌套窗口程序;QWidget:可视界面类的基类,QMainWindow 由它继承而来,功能更少、更轻,嵌入式常选它(不要标题栏/状态栏);QDialog:对话框,较少作为主基类。
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv); /* 每个 Qt 程序必须有一个 */
MainWindow w;
w.show();
return a.exec(); /* 进入事件循环 */
}
追问
main() 最后是 a.exec()?(把控制权交给 Qt,进入事件循环处理用户/系统事件)QMainWindow 与 QWidget 的继承关系?(QMainWindow 继承自 QWidget).pro 文件中常见条目的含义。答案要点:QT += 引入模块,greaterThan(...) 按 Qt 大版本追加模块,CONFIG += c++11 用 C++11,TARGET 指定可执行文件名,SOURCES/HEADERS/FORMS 管理文件。
详细解答:
QT += core gui # 默认模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets # Qt5 需加 widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
TARGET = 01_led # 不写则默认工程名
SOURCES += main.cpp mainwindow.cpp
HEADERS += mainwindow.h
FORMS += mainwindow.ui
QT += serialport # 串口模块
QT += serialbus # CAN 模块
QT += charts # 图表模块
QT += multimedia # 多媒体模块
QT += sql # 数据库模块
QT += network # 网络模块
第 1 行 core 与 gui 是默认设置;部署规则 qnx: ... / else: unix:!android: ... 用于指定安装路径。
追问
.pro 加什么?(QT += core gui serialport).pri 文件是什么?(语法同 .pro,可被 include() 引入,便于复用与拆分大型工程).ui 文件是如何被程序使用的?ui_mainwindow.h、setupUi()、Ui::MainWindow 各是什么?答案要点:.ui 由设计器生成 XML,编译时生成 ui_mainwindow.h;Ui::MainWindow 是界面对象类,setupUi(this) 负责把界面控件实例化并挂到主窗口。
详细解答:
/* mainwindow.h */
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; } /* 前置声明,注意与下面的 MainWindow 不同 */
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui; /* 指向界面对象的指针 */
};
/* mainwindow.cpp */
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this); /* 界面初始化,控件以 this 为父对象 */
}
MainWindow::~MainWindow() {
delete ui; /* new 且无父对象的对象需手动 delete */
}
ui_mainwindow.h 是构建后才生成的(位于 build 目录),所以使用 UI 设计的信号槽连接,需要编译后才能在 setupUi() 里看到。
追问
ui-> 里控件必须写在 setupUi(this) 之后?(该调用才完成控件初始化)Q_OBJECT 宏的作用?(Qt 元对象系统的基础,凡用到信号槽的类都要加)答案要点:Designer 直观快捷但控件多时难管理、信号槽连接不直观;纯代码利于管理与审查、能锻炼布局能力;Qt Creator 帮助文档和快捷键很实用。
详细解答:
setupUi() 看、要么进设计器看,不直观。Qt Creator 技巧:左侧“帮助”可查类/方法/信号槽用法;Ctrl + N 新建项目;Ctrl + R 编译并运行;Ctrl + Shift + T 查看类的继承关系;F1 查看光标处符号的帮助;按住 Ctrl 点击符号跳转到定义。
追问
this->resize(800, 480); 或 setGeometry(0,0,800,480))connect() 的基本语法是什么?SIGNAL/SLOT 宏的作用?答案要点:信号是特定情况下发射的事件,槽是响应函数;QObject::connect(sender, SIGNAL(sig()), receiver, SLOT(slot()));两个宏把函数签名转成字符串。
详细解答:信号(Signal)是特定情况下被发射的事件,如按钮的 clicked()、下拉框的 currentIndexChanged()。槽(Slot)是对信号响应的函数,本质是普通 C++ 函数,可定义在 public/protected/private 下,可带参数,也能被直接调用;特殊之处在于能与信号关联,信号发射时自动执行。
connect(sender, SIGNAL(clicked()), receptor, SLOT(close()));
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
connect() 是 QObject 的静态函数,QObject 是所有 Qt 类的基类,故常省略限定符。SIGNAL/SLOT 把信号和槽的参数转换为相应字符串。带参时只需写参数类型,可省略参数名。
追问
connect 中发送者与接收者的顺序能颠倒吗?(不能,sender 与信号在一起,receiver 与槽在一起)答案要点:一个信号可连多个槽、多个信号可连同一个槽、一个信号可连另一个信号;槽按连接顺序执行;使用的类必须加 Q_OBJECT;信号发射后槽立即执行,全部槽执行完才继续。
详细解答:
/* 一对多:按连接顺序依次执行 */
connect(pushButton, SIGNAL(clicked()), this, SLOT(hide()));
connect(pushButton, SIGNAL(clicked()), this, SLOT(close()));
/* 多对一:任一按钮点击都关闭 */
connect(pushButton_2, SIGNAL(clicked()), this, SLOT(close()));
connect(pushButton_3, SIGNAL(clicked()), this, SLOT(close()));
/* 信号连信号:一个信号发射会触发另一个信号 */
connect(pushButton, SIGNAL(objectNameChanged(QString)),
this, SIGNAL(windowTitleChanged(QString)));
要点:严格情况下参数个数与类型需一致;类中必须加 Q_OBJECT 宏;信号发射时槽立即执行,像普通函数调用一样,全部槽完成才执行发射点之后的代码。
追问
Q_OBJECT 会怎样?(信号槽机制无法工作,编译或链接报错)答案要点:在类中 signals: 下声明,只需声明无需实现;使用时用 emit 发射;信号无返回类型。
详细解答:
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
/* 声明一个信号,只需声明,无需定义 */
void pushButtonTextChanged();
};
信号由 moc 自动生成实现,因此不能也不必自己写函数体;信号没有 public/protected/private 概念,名称建议贴合含义(如“按钮文本改变后”)。
追问
emit,如 emit pushButtonTextChanged();)答案要点:在类中 slots: 下声明,并在 cpp 中实现(必须实现);槽可以是任意成员函数、普通全局函数或静态函数;参数和返回值要与信号匹配。
详细解答:
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
public slots:
void changeButtonText(); /* 声明槽 */
void pushButtonClicked();
private:
QPushButton *pushButton;
};
/* 实现槽:槽里改变按钮文本 */
void MainWindow::changeButtonText() {
pushButton->setText("被点击了!");
}
/* 实现按钮点击槽:用 emit 发射自定义信号 */
void MainWindow::pushButtonClicked() {
emit pushButtonTextChanged();
}
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
this->resize(800, 480);
pushButton = new QPushButton(this);
pushButton->setText("我是一个按钮");
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
connect(this, SIGNAL(pushButtonTextChanged()), this, SLOT(changeButtonText()));
}
槽特点:① 可以是任何成员函数、普通全局函数、静态函数;② 与信号的参数和返回值要一致。本例流程:点击按钮 → pushButtonClicked() → 发射 pushButtonTextChanged() → changeButtonText() 响应,按钮文本变为“被点击了!”。
追问
QPushButton 还有哪些构造重载?(QPushButton(const QString &text, QWidget *parent)、QPushButton(const QIcon &icon, const QString &text, QWidget *parent))disconnect() 有哪几种常用形式?答案要点:用 disconnect(),可断开某对象的全部连接、某信号的全部连接、或与指定接收者的连接。
详细解答:
/* 断开 myObject 的一切连接 */
disconnect(myObject, 0, 0, 0); /* 等价 myObject->disconnect(); */
/* 断开连接到 mySignal() 的全部连接 */
disconnect(myObject, SIGNAL(mySignal()), 0, 0); /* 等价 myObject->disconnect(SIGNAL(mySignal())); */
/* 与指定接收者断开 */
disconnect(myObject, 0, myReceiver, 0); /* 等价 myObject->disconnect(myReceiver); */
初学者一般很少主动断开连接;但动态创建/销毁对象时要注意避免悬空连接。
追问
SIGNAL/SLOT 字符串,运行期解析)答案要点:QPushButton(clicked)、QToolButton(带图标/菜单)、QRadioButton(toggled,互斥)、QCheckBox(stateChanged,三态)、QCommandLinkButton、QDialogButtonBox。
详细解答:
| 控件 | 特点 | 常用信号 |
|---|---|---|
| QPushButton | 普通按钮,可显示文本/图标 | clicked() |
| QToolButton | 工具按钮,常用于工具栏,可带下拉菜单 | clicked() |
| QRadioButton | 单选按钮,同组互斥 | toggled(bool) |
| QCheckBox | 复选按钮,支持三态 | stateChanged(int) |
| QCommandLinkButton | 命令链接按钮,Vista 风格 | clicked() |
| QDialogButtonBox | 对话框标准按钮集合 | clicked(QAbstractButton*) |
常用方法:setText()、setIcon()、setCheckable()、setChecked()、setEnabled()、setMinimumSize()。
追问
setCheckable(true) 有什么效果?(按钮变为可选中/取消的切换状态,如“开始/关闭”)QButtonGroup)QComboBox、QLineEdit、QSpinBox 常用方法是什么?答案要点:QComboBox、QFontComboBox、QLineEdit、QTextEdit、QPlainTextEdit、QSpinBox、QDoubleSpinBox、QDateTimeEdit、QDial、QScrollBar、QSlider、QKeySequenceEdit。
详细解答:
| 控件 | 用途 | 常用方法 |
|---|---|---|
| QComboBox | 下拉选择 | addItem()、addItems()、setCurrentIndex()、currentText() |
| QLineEdit | 单行输入 | setText()、text()、setEchoMode()、setPlaceholderText() |
| QTextEdit | 多行富文本 | setPlainText()、toPlainText()、insertPlainText() |
| QPlainTextEdit | 多行纯文本 | setPlainText()、appendPlainText() |
| QSpinBox | 整数微调 | setRange()、setValue()、value()、valueChanged(int) |
| QDoubleSpinBox | 浮点微调 | 同上 |
| QDateTimeEdit | 日期时间 | setDateTime()、dateTime() |
| QDial / QSlider | 旋钮 / 滑块 | setValue()、valueChanged(int) |
追问
QLineEdit 密码框怎么实现?(setEchoMode(QLineEdit::Password))QComboBox 如何给选项附带数据?(addItem(text, userData) + currentData())QLabel 如何显示图片?QProgressBar 如何更新进度?答案要点:QLabel、QCalendarWidget、QLCDNumber、QProgressBar、QFrame、QTextBrowser、QGraphicsView;QLabel::setPixmap() 显示图片,setScaledContents(true) 自适应;QProgressBar::setValue() 更新进度。
详细解答:
QLabel *label = new QLabel(this);
label->setPixmap(QPixmap(":/images/openedv.png"));
label->setScaledContents(true); /* 图片随控件缩放 */
QProgressBar *bar = new QProgressBar(this);
bar->setRange(0, 100);
bar->setValue(50); /* 更新到 50% */
QLCDNumber 显示数字风格的数值,QCalendarWidget 提供日历,QTextBrowser 支持富文本浏览(带链接),QGraphicsView 用于图形场景显示。
追问
QLabel 的自动换行怎么设置?(setWordWrap(true)).qrc?(编译进可执行文件,运行时用 :/ 前缀访问,避免路径问题)QTabWidget 与 QStackedWidget、QListWidget 如何配合?答案要点:QGroupBox、QScrollArea、QToolBox、QTabWidget、QStackedWidget、QMdiArea、QDockWidget;QStackedWidget 常与 QListWidget 通过 setCurrentIndex(int) 槽联动。
详细解答:
/* 左边列表,右边堆叠页面,点击列表切换页面 */
stackedWidget = new QStackedWidget();
listWidget = new QListWidget();
connect(listWidget, SIGNAL(currentRowChanged(int)),
stackedWidget, SLOT(setCurrentIndex(int)));
/* QTabWidget:直接 addTab 添加带标签页 */
tabWidget->addTab(pageWidget, "蓝牙聊天");
tabWidget->addTab(pageWidget2, "蓝牙列表");
tabWidget->setCurrentIndex(0);
QGroupBox 用于给一组控件加标题边框;QScrollArea 提供滚动区域(内容大于可视区时使用);QToolBox 是抽屉式分组;QTabWidget 是标签页;QStackedWidget 是“同一时间只显示一页”的堆叠容器。
追问
QStackedWidget 与 QTabWidget 的本质关系?(QTabWidget 内部基于堆叠布局,多了标签栏)QScrollArea)答案要点:基于模型的控件(QListView/QTreeView/QTableView/QColumnView)由外部 Model 提供数据,适合大数据与多视图共享;基于项的控件(QListWidget/QTreeWidget/QTableWidget)自带内部模型,直接用 addItem 等操作,简单方便。
详细解答:
| 维度 | 基于模型(View) | 基于项(Widget) |
|---|---|---|
| 代表 | QListView、QTreeView、QTableView | QListWidget、QTreeWidget、QTableWidget |
| 数据来源 | 外部 Model(如 QStandardItemModel) | 内部自带模型 |
| 操作方式 | setModel() |
addItem()、setItem() |
| 适用 | 大数据、复杂结构、多视图共享 | 中小数据、快速开发 |
QListWidget 继承 QListView,QTreeWidget 继承 QTreeView,QTableWidget 继承 QTableView——即“控件版”是“视图版”的便捷封装。
追问
QListWidget::setItemWidget(item, widget))答案要点:布局能随窗口大小自动调整、适配不同分辨率、便于维护;主要有 QBoxLayout(含 QHBoxLayout/QVBoxLayout)、QGridLayout、QFormLayout。
详细解答:绝对定位(setGeometry/move)写死后,窗口缩放或换屏幕分辨率时界面就错乱;嵌入式设备分辨率各异,布局管理是必须。常见布局:
| 布局 | 排列方式 | 典型场景 |
|---|---|---|
| QHBoxLayout | 水平 | 一排按钮、标签+输入框 |
| QVBoxLayout | 垂直 | 上下分区 |
| QGridLayout | 网格 | 串口/CAN 参数表 |
| QFormLayout | 两列表单 | 登录、设置表单 |
追问
addLayout,实现复杂界面)QSpacerItem 有什么用?答案要点:addWidget()、addLayout()、addStretch()、setContentsMargins()、setSpacing()、setAlignment();QSpacerItem 在布局中提供空白,用于撑开或对齐。
详细解答:
QVBoxLayout *vboxLayout = new QVBoxLayout();
vboxLayout->addWidget(textBrowser);
vboxLayout->addWidget(textEdit);
vboxLayout->setContentsMargins(0, 0, 0, 0); /* 左上右下外边距 */
vboxLayout->setSpacing(10); /* 控件间距 */
mainWidget->setLayout(vboxLayout);
/* 网格布局:把标签放 (0,i),下拉框放 (1,i) */
gridLayout->addWidget(label[i], 0, i);
gridLayout->addWidget(comboBox[i], 1, i);
/* 空间间隔 */
QSpacerItem *hSpacer = new QSpacerItem(10, 10,
QSizePolicy::Expanding, QSizePolicy::Minimum);
hBoxLayout->addSpacerItem(hSpacer);
QSpacerItem 继承 QLayoutItem,含水平/垂直间隔,用来把控件挤向一侧或在控件之间留白。
追问
addStretch() 与 QSpacerItem 的区别?(前者是快捷方式,本质也是插入可伸缩间隔)setContentsMargins 与 setSpacing 分别控制什么?(布局四周外边距 / 相邻控件间距)QStandardItemModel 如何设置表头与数据?答案要点:Model(数据)、View(显示)、Delegate(渲染与编辑)三部分;QStandardItemModel 是通用模型,setHorizontalHeaderLabels() 设表头,appendRow() 逐行添加。
详细解答:
QStandardItemModel *model = new QStandardItemModel();
QStringList labels;
labels << "姓名" << "年龄" << "性别";
model->setHorizontalHeaderLabels(labels); /* 设置列数与表头文字 */
/* 逐行添加数据 */
QList<QStandardItem *> row;
row << new QStandardItem("啊万") << new QStandardItem("27") << new QStandardItem("男");
model->appendRow(row);
tableView->setModel(model); /* 视图绑定模型 */
好处是数据与显示分离:同一份 Model 可被 List/Tree/Table 多种 View 共享,也可加 QSortFilterProxyModel 做排序过滤。
追问
setEditTriggers(QAbstractItemView::NoEditTriggers))QListView、QTreeView、QTableView 如何填充数据?答案要点:三者都继承 QAbstractItemView,通过 setModel() 绑定模型;列表用 QStringListModel 或 QStandardItemModel,树/表用 QStandardItemModel 的 appendRow/setItem。
详细解答:
/* 列表视图:用字符串列表模型 */
QStringList list; list << "A" << "B" << "C";
QStringListModel *stringListModel = new QStringListModel(list);
QListView *listView = new QListView;
listView->setModel(stringListModel);
listView->setViewMode(QListView::IconMode); /* 图标模式 */
/* 表视图:先设表头,再用 setItem(row, col, item) */
QStandardItemModel *tableModel = new QStandardItemModel();
tableModel->setHorizontalHeaderLabels(QStringList() << "编号" << "姓名");
for (int r = 0; r < 3; r++)
for (int c = 0; c < 2; c++)
tableModel->setItem(r, c, new QStandardItem(QString::number(r * 2 + c)));
tableView->setModel(tableModel);
/* 树视图:一级标题 appendRow,子项用父项 appendRow */
QStandardItemModel *treeModel = new QStandardItemModel(treeView);
treeModel->setHorizontalHeaderLabels(QStringList() << "分类" << "说明");
QStandardItem *root = new QStandardItem("传感器");
root->appendRow(new QStandardItem("AP3216C"));
treeModel->appendRow(root);
treeView->setModel(treeModel);
追问
QTreeView 如何展开全部节点?(expandAll())QTableView 如何隐藏网格线?(setShowGrid(false))QListWidget/QTreeWidget/QTableWidget 如何用?怎样在列表项里放自定义控件?答案要点:直接 addItem()/setItem();QTableWidget 先 setRowCount/setColumnCount 再 setItem(row,col,new QTableWidgetItem(...));列表项内嵌控件用 setItemWidget(item, widget)。
详细解答:
/* QListWidget:添加文本项 */
QListWidget *listWidget = new QListWidget(this);
listWidget->addItem("0.mp3");
listWidget->addItem("1.mp3");
/* QTableWidget:设置行列与表头,再填单元格 */
QTableWidget *tableWidget = new QTableWidget(3, 2, this);
tableWidget->setHorizontalHeaderLabels(QStringList() << "姓名" << "年龄");
tableWidget->setItem(0, 0, new QTableWidgetItem("啊万"));
tableWidget->setItem(0, 1, new QTableWidgetItem("27"));
/* 在列表项中嵌入自定义 Widget(智能家居的控制卡片就是这么做的) */
QListWidgetItem *item = new QListWidgetItem();
listWidget->addItem(item);
listWidget->setItemWidget(item, customWidget);
基于项控件本质是“封装好模型的视图”,操作直观;数据量大时应改用 View + Model。
追问
QTableWidget 如何让表格自动适应内容?(resizeColumnsToContents())QListWidget 单击切换页面用什么信号?(currentRowChanged(int) 连 QStackedWidget::setCurrentIndex)QFile 打开文件有哪些模式?常用方法有哪些?路径分隔符要注意什么?答案要点:ReadOnly/WriteOnly/ReadWrite/Append/Truncate/Text 可组合;open/close/flush/exists/remove/size/pos/seek/atEnd;文件路径统一用 /。
详细解答:QFile 是读写文本、二进制和资源的 I/O 设备,可单独用,也可配合 QTextStream/QDataStream。
QFile file("/sys/devices/platform/leds/leds/sys-led/brightness");
if (!file.exists()) return;
if (!file.open(QIODevice::ReadWrite))
qDebug() << file.errorString();
file.write("1");
file.close();
| 模式 | 含义 |
|---|---|
| ReadOnly | 只读 |
| WriteOnly | 只写 |
| ReadWrite | 读写 |
| Append | 追加到尾部 |
| Truncate | 清空原内容 |
| Text | 文本模式,\n 自动翻译 |
关键点:无论 Windows、Linux 还是 Mac,QFile 的分隔符都是 /,不要用 \。
追问
/sys 节点?(Linux 一切皆文件,无需驱动即可控制 GPIO/LED)file.readAll(),文本模式配合 QTextStream::readAll())QTextStream 与 QFile 有何区别?什么时候用 QTextStream?答案要点:QTextStream 是文本流的方便接口,常与 QFile 结合;支持流操作符、字段填充、对齐与数字格式化;读写纯文本时更简洁。
详细解答:
/* 读取:用流读取全部文本 */
file.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream stream(&file);
textEdit->setPlainText(stream.readAll());
file.close();
/* 写入:用流插入运算符 */
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file);
out << textEdit->toPlainText();
file.close();
QFile 更底层(read/write/readLine 字节级),QTextStream 面向文本,提供 <</>>、readLine()、编码与格式化选项。需要逐行处理或格式化输出时优先用它。
追问
QTextStream 能操作 QString/QByteArray 吗?(能,它可在 QIODevice、QByteArray、QString 上操作)stream.readLine())paintEvent() 的作用是什么?何时会触发?答案要点:QPainter(绘制)、QPaintDevice(绘图设备基类)、QPaintEngine(平台抽象);paintEvent() 是 QWidget 的受保护虚函数,界面初始化或需要刷新时执行,用 update() 主动触发。
详细解答:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this); /* this 是绘图设备 */
painter.setRenderHints(QPainter::Antialiasing
| QPainter::SmoothPixmapTransform);
painter.drawRect(10, 10, 100, 50);
}
QWidget、QPixmap、QImage、QLabel 等可视类都可作为“画布”。paintEvent() 在父类中定义为空,专供子类画图;构造对象时会执行一次,之后要靠 update() 触发重绘。追问
update()?(paintEvent 不会自动反复执行)setRenderHints(QPainter::Antialiasing, true))QPainter 绕图片中心旋转?变换顺序为什么重要?答案要点:先 translate 把原点移到图形中心,再 rotate 旋转,然后把原点恢复;因为绘图变换会累积,顺序错了旋转中心就不对。
详细解答:
QPainter painter(this);
QPixmap image; image.load(":/image/cd.png");
QRectF rect((width()-image.width())/2, (height()-image.height())/2,
image.width(), image.height());
/* 1. 参考点从左上角移到 CD 中心 */
painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
/* 2. 按角度旋转 */
painter.rotate(angle);
/* 3. 参考点移回原点位置 */
painter.translate(-(rect.x() + rect.width()/2), -(rect.y() + rect.height()/2));
painter.drawImage(rect, image.toImage(), image.rect());
painter.drawRect(rect.toRect());
配合 QTimer 每 100ms update() 一次,angle++ 到 360 归零,CD 就转起来了。
追问
QRectF 与 QRect 的区别?(前者浮点坐标,绘图更精确)painter.save() / painter.restore())答案要点:QChart(图表)、QChartView(视图)、QSplineSeries/QLineSeries(曲线)、QValueAxis(坐标轴);动态曲线的原理是“存有限个数据点,超出则删队首,反复重绘”。
详细解答:.pro 加 QT += charts,头文件加 QT_CHARTS_USE_NAMESPACE(或 using namespace QtCharts)。基本流程:创建 QSplineSeries → 创建 QChart 并 addSeries → 创建两条 QValueAxis 并 addAxis → series->attachAxis() → chartView->setChart(chart)。
QList<int> data;
void MainWindow::receivedData(int value)
{
data.append(value);
while (data.size() > maxSize) /* 超过上限删掉最早的 */
data.removeFirst();
splineSeries->clear(); /* 清空后重新追加,就形成“移动”效果 */
int xSpace = maxX / (maxSize - 1);
for (int i = 0; i < data.size(); ++i)
splineSeries->append(xSpace * i, data.at(i));
}
追问
QLineSeries)splineSeries->attachAxis(axisX/axisY))答案要点:耗时操作会阻塞主线程导致界面冻结,需放到子线程;方式是继承 QThread 重写 run(),或继承 QObject 再用 moveToThread();Qt4.8 后官方推荐后者,更灵活。
详细解答:嵌入式 CPU 主频低,摄像头拍照、图像处理、文件读写都可能卡界面。两种方式区别:
| 方式 | 新线程中执行的代码 | 灵活度 |
|---|---|---|
| 继承 QThread | 只有 run() 在新线程,其它方法仍在主线程 |
一般 |
| 继承 QObject + moveToThread | 整个对象的方法(槽函数)都在新线程 | 更灵活 |
追问
run())QMutexLocker,作用域结束自动解锁)QThread 的线程如何写?如何正确地启动和退出?答案要点:继承 QThread 重写 run();用 start() 启动(它调用 run()),用 quit() + wait() 退出,不要用 terminate()。
详细解答:
class WorkerThread : public QThread {
Q_OBJECT
public:
void run() override {
QString result = "线程开启成功";
sleep(2); /* 模拟耗时操作 */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
/* 主线程 */
workerThread = new WorkerThread(this);
connect(workerThread, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
void MainWindow::pushButtonClicked() {
if (!workerThread->isRunning()) /* 防止重复启动 */
workerThread->start();
}
MainWindow::~MainWindow() {
workerThread->quit();
if (workerThread->wait(2000))
qDebug() << "线程已经结束!" << endl;
}
追问
start() 与直接调用 run() 有何区别?(start() 创建新线程并调用 run();直接调用等于在主线程执行)wait() 的作用?(阻塞等待线程结束,可带超时)QObject + moveToThread() 如何安全终止一个循环线程?答案要点:用 moveToThread() 迁移 Worker,用 QMutexLocker 保护一个 bool 标志位,主线程调用 stopWork() 置位,工作循环检测到后跳出。
详细解答:
class Worker : public QObject {
Q_OBJECT
private:
QMutex lock;
bool isCanRun;
public slots:
void doWork1(const QString ¶meter) {
isCanRun = true;
while (1) {
{ /* 限定锁的作用范围 */
QMutexLocker locker(&lock);
if (!isCanRun) break;
}
QThread::sleep(2);
emit resultReady(parameter + "doWork1 函数");
}
emit resultReady("打断doWork1 函数");
}
public:
void stopWork() { /* 注意:不能放在槽函数下 */
QMutexLocker locker(&lock);
isCanRun = false;
}
signals:
void resultReady(const QString &result);
};
/* 使用 */
worker = new Worker; /* 继承 QObject 的多线程类不能指定父对象 */
worker->moveToThread(&workerThread);
connect(&workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(this, SIGNAL(startWork(QString)), worker, SLOT(doWork1(QString)));
quit()/exit() 不会中途终止线程,terminate() 不安全。加锁虽然损耗性能,但能避免访问冲突。
追问
stopWork() 不能做成槽?(否则它会被排到新线程的事件队列里,无法及时打断)deleteLater() 在事件循环空闲时安全销毁)答案要点:低级类 QTcpSocket/QTcpServer/QUdpSocket,高级类 QNetworkRequest/QNetworkReply/QNetworkAccessManager;QHostInfo 查主机名,QNetworkInterface 列网卡 IP。
详细解答:
#include <QNetworkInterface>
#include <QHostInfo>
QString str = "主机名称:" + QHostInfo::localHostName() + "\n";
QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();
foreach (QNetworkInterface interface, list) {
/* 每个接口可能有 IPv4 / IPv6 两个地址 */
QList<QNetworkAddressEntry> entries = interface.addressEntries();
foreach (QNetworkAddressEntry entry, entries) {
str += entry.ip().toString() + "\n";
}
}
使用网络需在 .pro 加 QT += network(嵌入式视频监控、云端 API 都用到)。
追问
QHostInfo::fromName() 与 localHostName() 区别?(前者按名字解析地址,后者取本机名)QNetworkAccessManager::get/post)答案要点:TCP 服务端 QTcpServer::listen() + nextPendingConnection();客户端 QTcpSocket::connectToHost();UDP QUdpSocket::bind() + writeDatagram()/readDatagram();组播再 joinMulticastGroup()。
详细解答:
/* TCP 服务端 */
tcpServer = new QTcpServer(this);
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
tcpServer->listen(QHostAddress::Any, 8888);
void MainWindow::onNewConnection() {
tcpSocket = tcpServer->nextPendingConnection();
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
}
/* TCP 客户端 */
tcpSocket = new QTcpSocket(this);
tcpSocket->connectToHost("192.168.1.100", 8888);
/* UDP:发送数据报,无需先 bind */
QUdpSocket udpSocket;
QByteArray data = "hello";
udpSocket.writeDatagram(data.data(), data.size(),
QHostAddress::Broadcast, 8888); /* 广播 */
/* UDP 接收端 */
udpSocket.bind(QHostAddress::Any, 8888);
connect(&udpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
/* 收到后 readDatagram() 读出,注意必须先读走,否则收不到下一个数据报 */
追问
joinMulticastGroup() 加入组播组)QSound 与 QSoundEffect 有什么区别?答案要点:QMediaPlayer、QSound、QSoundEffect、QAudioOutput、QAudioInput、QAudioRecorder、QVideoWidget;QSound 只能异步播放本地 WAV,QSoundEffect 还能播放网络文件。
详细解答:Qt5 起弃用 phonon,改用 Qt Multimedia 模块,pro 加 QT += multimedia。Ubuntu 需安装 GStreamer 解码插件,Windows 需安装 LAVFilters。
| 类 | 播放源 | 特点 |
| -- | ------ | ---- |
| QSound | 本地 WAV | 异步、低延时、体积大,适合短提示音 |
| QSoundEffect | 本地或网络 | 支持 QUrl 网络文件 |
播放音效示例:QSound::play(":/audio/bell.wav");
追问
QMediaPlayer 与 QAudioRecorder 分别怎么用?答案要点:QMediaPlayer 设置媒体源并 play(),视频配合 QVideoWidget::setMediaPlayer() 显示;QAudioRecorder 设置输入设备、编码参数、容器格式与输出路径后 record()/stop()。
详细解答:
/* 播放器(音乐 / 视频) */
QMediaPlayer *player = new QMediaPlayer;
player->setMedia(QUrl::fromLocalFile("test.mp4"));
videoWidget->setMediaPlayer(player); /* 视频画面输出到 QVideoWidget */
player->play();
/* 录音(语音识别项目用它录 16k 单声道 wav) */
QAudioRecorder *recorder = new QAudioRecorder(this);
QAudioEncoderSettings settings;
settings.setSampleRate(16000); /* 百度语音要求 */
settings.setChannelCount(1); /* 单声道 */
recorder->setEncodingSettings(settings, QVideoEncoderSettings(), "audio/x-wav");
recorder->setOutputLocation(QUrl::fromLocalFile("./16k.wav"));
recorder->record(); /* 开始录音 */
recorder->stop(); /* 停止录音 */
播放器还可通过 QMediaPlaylist 管理多首歌曲,或用 setPlaybackMode 设置循环。
追问
QLabel 上?(需要 QVideoWidget 作为渲染目标)audio/x-wav)QSqlDatabase、QSqlQuery、QSqlTableModel 各自的作用是什么?答案要点:QSqlDatabase 连接数据库,QSqlQuery 执行 SQL,QSqlTableModel 把表读成模型供 QTableView 显示;嵌入式常用 SQLite。
详细解答:pro 加 QT += core gui sql。SQLite 是进程内、无服务器、零配置的轻量数据库,适合嵌入式。
/* 打开 / 创建数据库 */
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("alarm.db");
if (!db.open()) qDebug() << db.lastError();
/* 用 QSqlQuery 建表、增删改查 */
QSqlQuery query(db);
query.exec("create table alarm (id int primary key, time vchar(15), state vchar(10))");
query.prepare("insert into alarm values(?, ?, ?)"); /* 预处理防注入 */
query.addBindValue(0); query.addBindValue("06:00"); query.addBindValue("false");
query.exec();
/* 用 QSqlTableModel + QTableView 显示,可直接编辑单元格改库 */
QSqlTableModel *model = new QSqlTableModel(this, db);
model->setTable("alarm");
model->setEditStrategy(QSqlTableModel::OnFieldChange);
model->select();
QTableView *view = new QTableView;
view->setModel(model);
追问
QSqlQuery 与 QSqlTableModel 各自适合什么场景?(前者适合精确 SQL 操作,后者适合表格化显示与编辑)QSqlQueryModel)答案要点:用交叉工具链的 qmake + make,或在 Qt Creator 配置 ARM Kit;运行时用 -platform linuxfb 或 -platform eglfs 指定平台插件。
详细解答:
# 命令行交叉编译
/opt/Qt5.12.9/Tools/QtCreator/bin/qtcreator.sh & # 带环境变量启动 Qt Creator
/path/to/arm-qmake 01_led.pro && make -j8
scp 01_led root@192.168.1.100:/home/root/
# 运行
./01_led # 使用默认平台插件
./01_led -platform eglfs # EGL/OpenGL ES 全屏(有 GPU)
./01_led -platform linuxfb # Linux Framebuffer(无 GPU)
| 插件 | 依赖 | 场景 |
|---|---|---|
| linuxfb | /dev/fb0 |
无 GPU、直接帧缓冲 |
| eglfs | EGL/OpenGL ES | 有 GPU,I.MX6ULL 出厂系统常用 |
| xcb | X Server | Ubuntu 桌面上仿真 |
⚠️ 来源说明:平台插件部分不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
追问
答案要点:确认设备节点 → 用定时器/事件触发 → 用 QFile 或 C 的 open/read 读写 → 通过信号把数据交给界面;综合项目(如智能家居)再叠加串口、网络与云端 API。
详细解答:Qt 在嵌入式里操作硬件的通用套路:
.../sys-led/brightness、AP3216C /sys/class/misc/ap3216c/*、ICM20608 /dev/icm20608、摄像头 /dev/video1、串口 /dev/ttymxc2、CAN can0。QTimer(如 500ms 采一次),交互用事件(keyPressEvent)或信号(readyRead、framesReceived)。open()/read()(二进制结构体,如 icm20608 的 7 个 int)。ap3216cDataChanged())通知主线程刷新。综合实例(智能家居物联网):ESP8266 通过串口用 AT 指令接入 WIFI 与原子云 → 定时发心跳;控制端用 QNetworkAccessManager 走 HTTPS 拿设备列表与 token,用 QWebSocket 鉴权并订阅消息;点开关时下发 0x03 命令,云端转发到设备,设备端收到“开/关”后写 LED 节点。整个链路把串口 + 网络 + 硬件控制 + 信号槽 + 多模块协作都串了起来。
追问
内容来源:正点原子《I.MX6U嵌入式Qt开发指南V1.2》第一章 在Ubuntu下编写C++、第二章 C++基础、第三章 初识Qt、第四章 使用Qt Designer开发、第五章 Qt信号与槽、第六章 Qt Creator的使用技巧、第七章 Qt控件、第八章 文本读写、第九章 绘图与图表、第十章 多线程、第十一章 网络编程、第十二章 多媒体、第十三章 数据库、第十四章 I.MX6U Qt开发 ~ 第二十八章 视频监控项目;配套例程 Embedded-Qt-Tutorial/Qt/01、Qt/02、Qt/03、Qt/04;平台插件 -platform linuxfb/-platform eglfs 部分为扩展知识。