我的 QT 学习记录 02
一、对象树
对象树,也叫对象模型,是把子类依次绑定到父类,形成的树状结构
对象树是
Qt
简化内存回收的模型,析构一个对象时会先依次递归析构其子对象,然后把自己从parent
的children()
里面删除在栈上可能遇到析构的问题示例,解决方案是建议在堆上进行构造
1
2
3
4
5
6
7
8
9
10
11// 该段代码无问题,因为栈一般先构造的后析构
// 这里先析构 button,并从 window 的子对象列表中删除
// 然后析构 window(此时已经无子对象)
Qwidget window;
OPushButton button = QPushButton ("退出",&window);
// 该段代码有问题:因为栈一般先构造的后析构,导致重复析构,程序崩溃。
// 下面程序是先析构 window,析构 window 及其子对象(quit),然后析构quit
QPushButton quit("Quit");
Qwidget window;
quit.setParent(&window);
二、信号与槽机制
信号槽是
Qt
特色,某个事件发生后,会广播一个信号(signal),如果有对象需要对这个信号反馈,就会使用连接函数(connect),即为将要处理的信号与自己的反馈函数(称为槽slot)绑定来处理这个信号。也就是说,信号被发出后,被连接的槽函数会自动被回调,类似观察者模式连接函数
connect
的使用- 基本用法:
connect(sender,signal,receiver,slot)
- 发送者,信号,接收者,槽函数
- 可以参考帮助手册上需要的类及其父类的
Public Slots
和Signals
- 基本用法:
使用预设的信号槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// 创建按钮
QPushButton *btn=new QPushButton("点击关闭窗口",this);
this->resize(600,400);
// 绑定信号
connect(btn,&QPushButton::clicked,this,&Widget::close);
}
Widget::~Widget()
{
}自定义信号槽
大致思路
- 添加信号发送类,接收类
- 在发送类中声明信号,在接收类中声明并实现槽函数
注意
- 信号 在头文件的
signals
中声明,返回值void
,可以有参数,仅声明不实现,可重载 - 槽函数 在头文件的
public slots
里面声明,需要实现,返回值void
,可以有参数,可重载。对于一些高版本的来说,也可以写到public
或者全局。
- 信号 在头文件的
简易示例,借助
QDebug
项目结构
widget.h
,widget.cpp
,main.cpp
, 按照预设mysender.h
,mysender.cpp
,继承自QObject
myreceiver.h
,myreceiver.cpp
, 继承自QObject
信号与槽的定义实现
mysender.h
添加内容1
2signals: // 信号声明
void here_a_signal();myreceiver.h
添加内容1
2public slots: // 槽函数声明
void slots_ok();myreceiver.cpp
添加内容1
2
3
4
5
void MyReceiver::slots_ok()
{
qDebug()<<"这是槽函数,已经收到信号";
}
配置与连接
widget.h
1
21
2
3public:
MySender *sdr;
MyReceiver *rcvr;widget.cpp
1
2
3
4
5// 自定义信号与槽
this->sdr = new MySender();
this->rcvr =new MyReceiver();
// 连接信号槽
connect(sdr,&MySender::here_a_signal,rcvr,&MyReceiver::slots_ok);
信号产生
widget.h
1
2pubic:
void signal_generate();widget.cpp
1
2
3
4void Widget::signal_generate() // 信号产生函数
{
emit sdr->here_a_signal();// 触发信号
}1
2// 发出信号
signal_generate();
程序现象
1
2
310:01:14: Starting Qtlearning01.exe...
>>这是槽函数,已经收到信号
10:01:20: Qtlearning01.exe 退出,退出代码: 0示例更进一步-绑定按钮
方式一,信号触发函数
1
connect(button,&QPushButton::clicked,this,&Widget::signal_generate);
方式二,信号连接信号
1
connect(button,&QPushButton::clicked,sdr,&MySender::here_a_signal);
槽函数的参数与重载 参考CSDN
在Qt中信号和槽都可以是函数指针,即 &函数名,即可得到函数所在的地址,但是如果遇到重载的函数,如果不区分就会报错。
重载的两种方式
使用
QOverload
1
2
3
4connect(comboBox, QOverload<int>::of(&QComboBox::activated),
[=](int index){ /* ... */ });
connect(comboBox, QOverload<const QString &>::of(&QComboBox::activated),
[=](const QString &text){ /* ... */ });借助函数指针的思想
1
2
3
4
5
6void (QComboBox:: * activatedInt)(int) = &QComboBox::activated;
void (QComboBox:: * activatedString)(QString) = &QComboBox::activated;
connect(comboBox, activatedInt,
[=](int index){ /* ... */ });
connect(comboBox, activatedString,
[=](const QString &text){ /* ... */ });
C++
补充示例-函数指针遇上函数重载 参考CSDN确定类型指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void print(int a){
cout << "a is " << a << endl;
}
void print(){
cout << "hello world" << endl;
}
typedef void (* Fun)(int);
typedef void (* Fun2)();
int main()
{
Fun pPrint = print;
Fun2 pPrint2 = print;
pPrint(12);
pPrint2();
return 0;
}程序运行结果
1
2>>a is 12
>>hello world非确定类型指针
重载函数作为参数传递时,特别形参的类型不是确定的函数指针类型时,如
void *
,例如Qt
中的QObject::connect()
函数,重载的信号或槽传入到connect
时,可以使用static_cast<>
来区别重载函数:1
connect(subWidget, static_cast<void(SubWidget::*)()>(&SubWidget::switchWin), this, &MainWidget::switchWinSlot);
一般方法 示例
重写信号声声明(带参数)
1
2
3signals:
void here_a_signal();
void here_a_signal(QString content);重写槽函数声明与定义(带参数)
1
2
3public slots:
void slots_ok();
void slots_ok(QString content);1
2
3
4void MyReceiver::slots_ok(QString content)
{
qDebug()<<"这是重载的槽函数,信号内容:"<<content;
}1
2
3public:
void signal_generate();
void signal_generate(QString qstr);1
2
3
4void Widget::signal_generate(QString qstr)
{
emit sdr->here_a_signal(qstr);// 触发信号
}由于函数重载了,这里利用函数指针指向函数地址,然后再作连接
1
2
3
4
5
6
7
8// 重载的信号与槽
// ***函数指针指向重载的函数
void (MySender::*myowsgnal)(QString)=&MySender::here_a_signal;
void (MyReceiver::*myowslot)(QString)=&MyReceiver::slots_ok;
// ***信号连接
connect(sdr,myowsgnal,rcvr,myowslot);
// 信号触发
signal_generate("这是一个信号");程序现象
1
2
311:18:41: Starting Qtlearning01.exe...
>>这是重载的槽函数,信号内容: "这是一个信号"
11:18:50: Qtlearning01.exe 退出,退出代码: 0补充-关于
QString
QString
直接输出是有双引号的,如果要去掉双引号,可以按照以下途径进行转换1
// QString->QByteArray->char*
1
qDebug()<<"信号内容:"<<content.toUtf8().data();
此时两个输出之间会多一个空格,若要取消空格,可以使用
nospace()
方法1
qDebug().nospace()<<"信号内容:"<<content.toUtf8().data();
补充-关于
Qt4
的信号与槽使用示例
1
connect(sender,SIGNAL(here_a_signal(QString)),receiver,SLOT(slot_ok(QString)));
其中用到了
SIGNAL
和SLOT
两个宏,把信号和槽函数转为字符串,但是这个过程编译时不会检查拼写错误,故容易出错
三、信号与槽的总结
注意事项
- 信号发送者和接收者都是
QObject
的子类,除非使用全局槽函数或lambda
函数 - 信号和槽函数返回值都是
void
- 信号只声明,槽函数声明并实现
- 槽函数是普通的成员函数,会受到
public
,privace
,protect
的影响 - 可以使用
emit
在恰当位置发送信号 - 使用
connect()
连接信号与槽 - 任何成员函数、
static
函数、全局函数和lambda
表达式都可以作为槽函数 - 信号槽要求信号和槽函数的参数一致。如果不一致,允许的情况是槽函数参数比信号少,但类型和顺序应当一致,这样可以在在槽函数中选择忽略信号传来的数据
- 信号发送者和接收者都是
拓展
一个信号可以连接多个槽函数
此时这些槽都会被调用, 但调用顺序不确定
多个信号可以连接一个槽函数
任意信号发出, 槽函数都会被调用
信号连接信号
一个信号发出会触发第二个信号
槽可以取消连接
不常见, 因为一个对象被 Delete 后, 这个对象上的槽也会被自动取消
可以使用
C++11
的lambda
表达式