一、Qt信号槽机制与优势与不足
优点:
- 类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致,编译器会报错。
- 松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号,它只需在适当的时间发送适当的信号即可,而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用,即使关联的对象在运行时被删除。程序也不会奔溃。
- 灵活性。一个信号可以关联多个槽,或多个信号关联同一个槽。
不足:
- 速度较慢。与回调函数相比,信号和槽机制运行速度比直接调用非虚函数慢10倍。
原因:
- 需要定位接收信号的对象。
- 安全地遍历所有关联槽。
- 编组、解组传递参数。
- 多线程的时候,信号需要排队等待。(然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。)
二、Qt信号和槽的本质
- 回调函数。信号或是传递值,或是传递动作变化;槽函数响应信号或是接收值,或者根据动作变化来做出对应操作。
三、QT中的文件流和数据流的区别
- 文件流(QTextStream):操作轻量级数据(int、double、QString),数据写入文本件中以后以文本的方式呈现。
- 数据流(QDataStream):通过数据流可以操作各种数据类型,包括对象,存储到文件中数据为二进制。
文件流,数据流都可以操作磁盘文件,也可以操作内存数据。通过流对象可以将对象打包到内存,进行数据的传输。
四、QT之TCP通讯流程
服务端:(QTcpServer)
①创建QTcpServer对象
②监听listen需要的参数是地址和端口号
③当有新的客户端连接成功会发送newConnect信号
④在newConnection信号槽函数中,调用nextPendingConnection函数获取新连接QTcpSocket对象
⑤连接QTcpSocket对象的readyRead信号
⑥在readRead信号的槽函数使用read接收数据
⑦调用write成员函数发送数据
//代码
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//第一步:创建QTcpServer对象
tcpServer = new QTcpServer;
//第二步:监听list需要的参数是地址和端口号
tcpServer->listen(QHostAddress("192.168.0.111"),1234);
//第三步:当有新的客户端连接成功回发送newConnect信号
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(new_connect()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::new_connect()
{
qDebug("--new connect--");
//第四步:在newConnection信号槽函数中,调用nextPendingConnection函数获取新连接QTcpSocket对象
QTcpSocket* tcpSocket = tcpServer->nextPendingConnection();
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(read_data()));
socketArr.push_back(tcpSocket);
}
void Widget::read_data()
{
for(int i=0; i<socketArr.size(); i++)
{
if(socketArr[i]->bytesAvailable())
{
char buf[256] = {};
//第五步:连接QTcpSocket对象的readRead信号
socketArr[i]->read(buf,sizeof(buf));
qDebug("---read:%s---",buf);
}
}
}
客户端:(QTcpSocket)
①创建QTcpSocket对象
②当对象与Server连接成功时会发送connected 信号
③调用成员函数connectToHost连接服务器,需要的参数是地址和端口号
④connected信号的槽函数开启发送数据
⑤使用write发送数据,read接收数据
//代码
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//第一步:创建QTcpSocket对象
tcpSocket = new QTcpSocket;
//第二步:当对象与Server连接成功时会发送connected信号
connect(tcpSocket,SIGNAL(connected()),this,SLOT(connect_success()));
//第三步:调用成员函数connectToHost连接服务器,需要的参数是地址和端口号
tcpSocket->connectToHost("172.20.10.3",1234);
}
Widget::~Widget()
{
delete ui;
}
void Widget::connect_success()
{
//第四步:connected信号的槽函数开启发送数据
ui->send->setEnabled(true);
}
void Widget::on_send_clicked()
{
std::string msg = ui->msg->text().toStdString();
//第五步:使用write发送数据,read接收数据
int ret = tcpSocket->write(msg.c_str(),msg.size()+1);
qDebug("--send:%d--",ret);
}
五、 Qt之UDP通讯流程
- UDP(User Datagram Protocol即用户数据报协议)是一个轻量级的,不可靠的,面向数据报的无连接协议。在网络质量令人十分不满意的环境下,UDP协议数据包丢失严重。由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。所以QQ这种对保密要求并不太高的聊天程序就是使用的UDP协议。
- 在Qt中提供了QUdpSocket 类来进行UDP数据报(datagrams)的发送和接收。Socket简单地说,就是一个IP地址加一个port端口 。
流程:
①创建QUdpSocket套接字对象;
②如果需要接收数据,必须绑定端口;
③发送数据用writeDatagram,接收数据用 readDatagram 。
六、多线程使用使用方法
- 方法一:
①创建一个类从QThread类派生
②在子线程类中重写 run 函数, 将处理操作写入该函数中
③在主线程中创建子线程对象, 启动子线程, 调用start()函数
- 方法二:
①将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数
②在主线程中创建一QThread类对象
③在主线程中创建一个业务类对象
④将业务类对象移动到子线程中
⑤在主线程中启动子线程
⑥通过信号槽的方式, 执行业务类中的业务处理函数
- 多线程使用注意事项:
- 业务对象, 构造的时候不能指定父对象
- 子线程中不能处理ui窗口(ui相关的类)
- 子线程中只能处理一些数据相关的操作, 不能涉及窗口
七、信号槽在多线程下执行的方式
可以通过connect的第五个参数进行控制信号槽执行时所在的线程,connect有几种连接方式,直接连接和队列连接、自动连接:
- 直接连接(Qt::DirectConnection):信号槽在信号发出者所在的线程中执行
- 队列连接 (Qt::QueuedConnection):信号在信号发出者所在的线程中执行,槽函数在信号接收者所在的线程中执行
- 自动连接 (Qt::AutoConnection):多线程时为队列连接函数,单线程时为直接连接函数。
八、自定义控件流程
- 继承需要自定义的控件类,如QPushButton;
- 从外观设计上:QSS、继承绘制函数重绘、继承QStyle相关类重绘、组合拼装等等;
- 从功能行为上:重写事件函数、添加或者修改信号和槽等等
九、对QObject的理解
- QObject 类是Qt 所有类的基类。
- QObject是Qt对象模型的核心。这个模型的中心要素就是一种强大的叫做信号与槽无缝对象沟通机制。你可以用 connect() 函数来把一个信号连接到槽,也可以用disconnect() 函数来破坏这个连接。为了避免永无止境的通知循环,你可以用blockSignal() 函数来暂时阻塞信号。保护函数 connectNotify() 和 disconnectNotify() 可以用来跟踪连接。
- 对象树都是通过QObject 组织起来的,当以一个对象作为父类创建一个新的对象时,这个新对象会被自动加入到父类的 children() 队列中。这个父类有子类的所有权。能够在父类的析构函数中自动删除子类。可以通过findChild()和findChildren() 函数来寻找子类。
- 每个对象都一个对象名称objectName() ,而且它的类名也可以通过metaObject()函数。你可以通过inherits() 函数来决定一个类是否继承其他的类。当一个对象被删除时,它会发射destory() 信号,你可以抓住这个信号避免某些事情。
- 对象可以通过event() 函数来接收事情以及过滤来自其他对象的事件。就好比installEventFiter() 函数和eventFilter() 函数。childEvent() 函数能够重载实现子对象的事件。
- QObject还提供了基本的时间支持,QTimer类 提高了更高层次的时间支持。
- 任何对象要实现信号与槽机制,Q_OBJECT 宏都是强制的。你也需要在源原件上运行元对象编译器。不管是否真正用到信号与槽机制,最好在所有QObject子类使用Q_OBJECT宏,以避免出现一些不必要的错误。
- 所有的Qt widgets 都是基础QObject。如果一个对象是widget,那么isWidgetType()函数就能判断出。