优势:
不足:
同回调函数相比,信号和槽机制运行速度有些慢。通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:
然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。
本质 :回调函数
回调函数的本质是“你想让别人的代码执行你的代码,而别人的代码你又不能动”这种需求下产生的。
发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
使用 emit 在恰当的位置发送信号;
使用QObject::connect()函数连接信号和槽;
任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。
服务器端:
客户端:
QT下udp通信服务器端和客户端的关系是对等的, 做的处理也是一样的.
方法一:
方法二:
方法三:
QFuture< void> fut1 = QtConcurrent::run(processFun, command);
processFun为线程回调函数
多线程使用注意事项:
可以通过connect的第五个参数connectType进行控制信号槽执行时所在的线程
connect有几种连接方式,直接连接和队列连接、自动连接
直接连接:Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用
Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
队列连接:Qt::QueuedConnection:信号在信号发出者所在的线程中执行,槽函数在信号接收者所在的线程中执行
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
自动连接:多线程时为队列连接函数,单线程时为直接连接函数。
(1)互斥量:QMutex
QMutex类提供的是线程之间的访问顺序化。QMutex的目的是保护一个对象/数据结构或者代码段在同一时间只有一个线程可以访问。基本使用方法如下:
QMutex mutex;
int var;
void function()
{
mutex.lock();
// 访问var
var * var;
mutex.unlock();
}
如果使用mutex加锁,却没有使用unlock解锁,那么就会造成死锁,其他线程永远也得不到访问变量的机会,所以为了解决这个问题,Qt引入了QMutexLocker类,二者直接可以配合使用更加方便简洁,示例如下:
QMutex mutex;
int var;
void function()
{
QMutextLocker locker(&mutex);
// 访问var
var * var;
}
(2)QReadWriteLock
QMutex只允许某个时刻有一个线程对共享资源进行访问,如果需要多个线程对共享资源进行读访问,同时只有一个线程进行写访问,这种情况下就可以使用QReadWriteLock。QReadWriteLock主要实现多个线程读资源,一个线程写。写线程执行的时候会阻塞所有的读线程,而读线程之间的运行不需要进行同步。使用示例如下:
int var;
QReadWriteLock lock;
void function()
{
lock.lockForRead();
int x = var;
lock.unlock();
}
void function2()
{
lock.lockForWrite();
var = 100;
lock.unlock();
}
和QMutexLocker一样,Qt同样提供了QReadLocker和QWriteLocker。
int var;
QReadWriteLock lock;
void fun()
{
QReadLocker(&lock);
int x = var;
}
void fun2()
{
QWriteLocker(&lock);
var = 1000;
}
(3)QSemaphore
QSemaphore是QMutex的一般化,它可以保护一定数量的相同资源,而QMutex只能保护一个资源。信号量比互斥量具有更好的并发性,我们可以利用信号量实现生产者-消费者模式,如下所示:
const int dataSize = 100000;
const int bufferSize = 1024;
char buffer[bufferSize];
QSemaphore freeBytes(bufferSize);
QSemaphore usedButes;
void Producer::run()
{
for (int i = 0; i < dataSize; ++i)
{
freeBytes.acquire();
buffer[i % bufferSize] = i;
usedBytes.release();
}
}
void Consumer::run()
{
for (int i = 0; i < dataSize; ++i)
{
usedBytes.acquire();
qDebug() << buffer[i % bufferSize];
freeBytes.release();
}
}
1.QPointer
2.QScopedPointer QScopedArraytPointer与 std::unique_ptr/scoped_ptr
3.QSharedPointer QSharedArrayPointer 与 std::shared_ptr
4.QWeakPointer 与 std::weak_ptr
5.QSharedDataPointer
6.QExplicitlySharedDataPointe
保持一个库中的所有公有类的大小恒定的问题可以通过单独的私有指针给予解决。这个指针指向一个包含所有数据的私有数据结构体。这个结构体的大小可以随意改变而不会产生副作用,应用程序只使用相关的公有类,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针。
D指针的其他好处
其实以上的点都很细微,自己跟过源代码的人都会了解,qt是隐藏了d指针的管理和核心源的实现。像是在_p.h中部分函数的声明,qt也宣布在以后版本中将会删除。
d_ptr指针指向父类,使用如下宏定义辅助函数和声明友元类
#ifndef D_PTR_H
#define D_PTR_H
#include
template <typename T> static inline T *GetPtrHelper(T *ptr) { return ptr; }
#define DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private*>(GetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private*>(GetPtrHelper(d_ptr)); }\
friend class Class##Private;
#define DPTR(Class) Class##Private * const d = d_func()
class MyClassPrivate;
class MyClass : public QObject {
Q_OBJECT
public:
explicit MyClass(QObject *parent = 0);
virtual ~MyClass();
void testFunc();
protected:
MyClass(MyClassPrivate &d);
private:
MyClassPrivate * const d_ptr;
DECLARE_PRIVATE(MyClass);
MyClass(const MyClass&);
MyClass& operator= (const MyClass&);
};
#endif
#ifndef Q_PTR_H
#define Q_PTR_H
#include
#include "d_ptr.h"
#define DECLARE_PUBLIC(Class) \
inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
friend class Class;
#define QPTR(Class) Class * const q = q_func()
class MyClassPrivate : public QObject
{
Q_OBJECT
public:
MyClassPrivate(MyClass *q, QObject *parent = 0);
virtual ~MyClassPrivate() {}
signals:
void testSgnl();
private slots:
void testSlt();
public:
void fool();
private:
MyClass * const q_ptr;
DECLARE_PUBLIC(MyClass);
};
#endif
而如果QVariant a1=b1(b1是QVariant),改变b1的值会改变a1的。因为这样用的是shared指针
初看以为是对的,验证发现不准确,改变b1并没有改变a1的值,细看发现这里面有QT使用了个小技巧,要取b1的值然后改变时,会调用data函数
CVariantHelp* pBTemp = reinterpret_cast<CVariantHelp*>(b1.data());
pBTemp->j_ = 99;
而data的实现会调用detach将shared分离
void* QVariant::data()
{
detach();
return const_cast<void *>(constData());
}
void QVariant::detach()
{
if (!d.is_shared || d.data.shared->ref == 1)
return;
Private dd;
dd.type = d.type;
handler->construct(&dd, constData());
if (!d.data.shared->ref.deref())
handler->clear(&d);
d.data.shared = dd.data.shared;
}
1. 描述过程, 如何实现一个自定义按钮, 使其在光标进入,按下,离开三种状态下显示不同的图片.
创建一个类, 让其从QPushButton类派生, 重写该类中的事件处理器函数
方法1:
1>. enterEvent() – 光标进入
2>. leaveEvent() – 光标离开
3>. mousePressEvent() – 鼠标按下
4>. paintEvent() – 刷新背景图
方法二:
通过setstylesheet设置
2. QMainForm是从哪里派生的?
QMainWindow::QWidget::QObject
1. 角度不同
继承是从子类的角度讲的,派生是从基类的角度讲的。
2. 定义不同
继承 是面向对象软件技术当中的一个概念,与多态、抽象共为面向对象的三个基本特征。 继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。派生指江河的源头产生出支流。引申为从一个主要事物的发展中分化出来。
1. 单继承(派生类只从一个直接基类继承)时派生类的定义:
class 派生类名:继承方式 基类名
{
新增成员声明;
}
2. 多继承时派生类的定义:
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,…
{
成员声明;
}
注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。
1. 继承的访问控制
基类的public和protected成员:访问属性在派生类中保持不变;
基类的private成员:不可直接访问。
2. 访问权限
派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
通过派生类的对象:只能访问public成员。
3. 公有派生类对象可以被当作基类的对象使用,反之则不可。
派生类的对象可以隐含转换为基类对象;
派生类的对象可以初始化基类的引用;
派生类的指针可以隐含转换为基类的指针。
通过基类对象名、指针只能使用从基类继承的成员,派生类新增的成员就不能使用了
1. 新增widgets模块
在Qt4中,Qt提供的全部图形界面相关类都包含在Qt Gui模块中,但QT5将一些图形界面类移到了QT widgets模块中。所以在Pro文件中,需要增加一句话:
greaterThan(QT_MAJOR_VERSION, 4):QT += widgets
意思是如果Qt版本大于Qt4,则需要增加widgets模块。
2. 信号与槽的语法
Qt4中关联信号与槽一般这样写:
connect(sender, SINGAL(valueChanged(QString, QString)), receiver, SLOT(showValue(QString)));
但这样写,没有编译器检查,有时编译器通过但应该调用的槽函数没有执行。这是编译器不能给出错误信息,只能在运行时看是否有警告。
Qt5中关联信号与槽是这样写:
connect(sender, &Sender::valueChanged, receiver, &Receiver::showValue);
这种写法支持编译器检查,能够在编译时就发现错误;并支持类型的隐式转换。
3. 对c++11的支持
Qt5支持C++11,但有些编译器默认不开启。所以需要在Pro文件中增加一行:
CONFIG += c++11
信号和槽的非常强大的机制,使用connect()把信号和槽连接起来并且可以用disconnect()来破坏这种连接。为了避免从不结束的通知循环,你可以调用blockSignals()临时地阻塞信号。保护函数connectNotify()和disconnectNotify()使跟踪连接成为可能。
QObject可以通过event()接收事件并且过滤其它对象的事件。详细情况请参考installEventFilter()和eventFilter()。一个方便的处理者,childEvent(),能够被重新实现来捕获子对象事件。
最后但不是最不重要的一点,QObject提供了Qt中最基本的定时器,关于定时器的高级支持请参考QTimer。
注意Q_OBJECT宏对于任何实现信号、槽和属性的对象都是强制的。
所有的Qt窗口部件继承了QObject。方便的函数isWidgetType()返回这个对象实际上是不是一个窗口部件。它比inherits(“QWidget” )快得多。
这种传递方式中,实参和形参是两个不同的地址空间,参数传递的实质是将原函数中变量的值,复制到被调用函数形参所在的存储空间中,这个形参的地址空间在函数执行完毕后,会被回收掉。整个被调用函数对形参的操作,只影响形参对应的地址空间,不影响原来函数中的变量的值,因为这两个不是同一个存储空间。
即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。
这种参数传递方式中,实参是变量的地址,形参是指针类型的变量,在函数中对指针变量的操作,就是对实参(变量地址)所对应的变量的操作,函数调用结束后,原函数中的变量的值将会发生改变。
被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。
这种参数传递方式中,形参是引用类型变量,其实就是实参的一个别名,在被调用函数中,对引用变量的所有操作等价于对实参的操作,这样,整个函数执行完毕后,原先的实参的值将会发生改变。
被调函数对形参做的任何操作都影响了主调函数中的实参变量。
在内置类型当中三种传递方式的效率上都差不多;
在自定义类型当中,传引用的更高效一些,因为它没有对形参进行一次拷贝