1、Qt信号槽机制的优势

(1)类型安全。需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数同接收该信号的槽的参数类型和参数个数相同。不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数必须是信号参数的最后一个或几个参数。如果信号和槽的签名不符,编译器就会报错。
(2)松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是哪个对象的哪个槽需要接收它发出的信号,它只需在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道是哪个对象的哪个槽收到了信号。同样的,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除,应用程序也不会崩溃。
(3)信号和槽机制增强了对象间通信的灵活性。一个信号可以关联多个槽,也可以多个信号关联一个槽。

2、Qt信号槽机制的不足

同回调函数相比,信号和槽机制运行速度有些慢。通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:
(1)需要定位接收信号的对象;
(2)安全地遍历所有的关联(如一个信号关联多个槽的情况);
(3)编组/解组传递的参数;
(4)多线程的时候,信号可能需要排队等待。
然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。

3、请描述过程, 如何实现一个自定义按钮, 使其在光标进入,按下,离开三种状态下显示不同的图片.

创建一个类, 让其从QPushButton类派生, 重写该类中的事件处理器函数

方法一:
1>. enterEvent() – 光标进入
2>. leaveEvent() – 光标离开
3>. mousePressEvent() – 鼠标按下
4>. paintEvent() – 刷新背景图

方法二:

通过setstylesheet设置

4. Qt信号和槽的本质是什么

回调函数

5、描述QT中的文件流(QTextStream)和数据流(QDataStream)的区别, 他们都能帮助我们完成一些什么事情.

QTextStream – 文本流, 操作轻量级数据(int, double, QString), 数据写入文件中之后以文本的方式呈现。
QDataStream – 数据流, 通过数据流可以操作各种数据类型, 包括类对象, 存储到文件中数据可以还原到内存(二进制)。
QTextStream, QDataStream可以操作磁盘文件, 也可以操作内存数据, 通过流对象可以将数据打包到内存, 进行数据的传输.

6、描述Qt下Tcp通信的整个流程

服务器端:

  1. 创建用于监听的套接字
  2. 给套接字设置监听
  3. 如果有连接到来, 监听的套接字会发出信号newConnected
  4. 接收连接, 通过nextPendingConnection()函数, 返回一个QTcpSocket类型的套接字对象(用于通信)
  5. 使用用于通信的套接字对象通信
    1>. 发送数据: write
    2>. 接收数据: readAll/read
    客户端:
  6. 创建用于通信的套接字
  7. 连接服务器: connectToHost
  8. 连接成功与服务器通信
    1>. 发送数据: write
    2>. 接收数据: readAll/read

7、 描述QT下udp通信的整个流程

QT下udp通信服务器端和客户端的关系是对等的, 做的处理也是一样的.

  1. 创建套接字对象
  2. 如果需要接收数据, 必须绑定端口
  3. 发送数据: writeDatagram
  4. 接收数据: readDatagram

8. 描述QT下多线程的两种使用方法, 以及注意事项

方法一:

    1. 创建一个类从QThread类派生
    1. 在子线程类中重写 run 函数, 将处理操作写入该函数中
    1. 在主线程中创建子线程对象, 启动子线程, 调用start()函数

方法二:

    1. 将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数
    1. 在主线程中创建一QThread类对象
    1. 在主线程中创建一个业务类对象
    1. 将业务类对象移动到子线程中
    1. 在主线程中启动子线程
    1. 通过信号槽的方式, 执行业务类中的业务处理函数

方法三:

QFuture fut1 = QtConcurrent::run(processFun, command);

processFun为线程回调函数

多线程使用注意事项:

    1. 业务对象, 构造的时候不能指定父对象
    1. 子线程中不能处理ui窗口(ui相关的类)
    1. 子线程中只能处理一些数据相关的操作, 不能涉及窗口

9、多线程下,信号槽分别在什么线程中执行,如何控制

可以通过connect的第五个参数进行控制信号槽执行时所在的线程

connect有几种连接方式,直接连接和队列连接、自动连接

直接连接:信号槽在信号发出者所在的线程中执行

队列连接:信号在信号发出者所在的线程中执行,槽函数在信号接收者所在的线程中执行

自动连接:多线程时为队列连接函数,单线程时为直接连接函数。

10. 如何使用C++模拟Qt信号和槽

Qt的信号和槽原理就是回调函数。所以,我们需要保存对象绑定的回调函数

1. 创建槽类Slot,该类的功能是保存对象和对象绑定的回调函数

template<class T>
class SlotBase
{
public:
    virtual void Exec(T param1) = 0;  //纯虚函数
    virtual ~SlotBase(){}
};


/*
* func: 槽函数
* parm:
* return:
*/
template<class T, class T1>
class Slot : public SlotBase<T1>
{
public:

    /* 定义Slot的时候,获取槽函数信息 */
    Slot(T* pObj, void (T::*func)(T1))
    {
        m_pSlotBase = pObj;
        m_Func = func;
    }

    /* signal触发时,调用 */
    void Exec(T1 param1)
    {
        (m_pSlotBase->*m_Func)(param1);
    }

private:
    /* 槽函数信息 暂存 */
    T* m_pSlotBase;
    void (T::*m_Func)(T1);
};
 

2. 创建signal类

重要阐述:

  • 1.创建一个Signal 类,该类保主要是保存多个Slot对象,当一个信号发送时,会遍历这个表,对每一个slot绑定的回调函数进行调用。

  • 2.重载运算符(), 遍历这个表,调用回调函数,即signal触发机制

  • 3.写一个绑定函数Bind,用于将Slot对象添加到槽表中

template<class T1>
class Signal
{
public:

    /* 模板函数 -> Bind时获取槽函数指针 */
    template<class T>
    void Bind(T* pObj, void (T::*func)(T1))
    {
        m_pSlotSet.push_back(new Slot<T,T1>(pObj,func));
    }

    /* 重载操作符 -> signal触发机制 */
    void operator()(T1 param1)
    {
        for(int i=0;i<(int)m_pSlotSet.size();i++)
        {
            m_pSlotSet[i]->Exec(param1);
        }
    }

    ~Signal()
    {
        for(int i=0;i<(int)m_pSlotSet.size();i++)
        {
            delete m_pSlotSet[i];
        }
    }

private:
    vector<SlotBase<T1>*> m_pSlotSet; //这一句很重要,靠基类的指针来存储 信号槽指针
};

3.测试类

测试类包含多个signal 当调用接口就将调用signal的()函数,从而调用slot

class TestSignal
{
public:
    TestSignal()
    {
    }
    void setValue(int value)
    {
        emit ValueChanged(value);
    }

    void setfValue(int value)
    {
        emit ValueChanged_f(value);
    }

public slots:
    void FuncOfA(int parm)
    {
        printf("enter FuncOfA parm = %d\n", parm);
    }

    void FuncOfB(int parm)
    {
        printf("enter FuncOfB parm = %d\n", parm);
    }

signals:
    Signal<int> ValueChanged;
    Signal<float> ValueChanged_f;
};

4.定义一个链接函数

#define Connect(sender, signal, receiver, method) ((sender)->signal.Bind(receiver, method))

11.QVariant使用

  • 1、用户自定义需要先注册一个类型,即使用qRegisterMetaType,注册到QT的一个Vector中

  • 2、QVariant里面会new一个用户自定义类型的内存,并调用拷贝构造函数,QVariant自身的赋值会使用共享内存管理

所以用户可以传入一个临时变量地址,如果用户传入的是一个指针,这个指针需要用户自己析构,改变这个指针的值,并不会改变QVariant,因为是两个不同的空间了

而如果QVariant a1=b1(b1是QVariant),改变b1的值会改变a1的。因为这样用的是shared指针

初看2以为是对的,验证发现不准确,改变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;

}

12.Qt中的2指针

  1. QPointer
    特点:当其指向的对象(T必须是QObject及其派生类)被销毁时,它会被自动置NULL.
    注意:它本身析构时不会自动销毁所guarded的对象
    用途:当你需要保存其他人所拥有的QObject对象的指针时,这点非常有
    2.QScopedPointer QScopedArraytPointer与 std::unique_ptr/scoped_ptr
    这是一个很类似auto_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它的所有权更加严格,不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。

无论是QScopedPointer 还是 std::unique_ptr 都拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,这点我们可以对比QObject及其派生类的对象哈。

  1. QSharedPointer QSharedArrayPointer 与 std::shared_ptr
    QSharedPointer 与 std::shared_ptr 行为最接近原始指针,是最像指针的"智能指针",应用范围比前面的提到的更广。

QSharedPointer 与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全地放到标准容器中,并弥补了std::auto_ptr 和 QScopedPointer 因为转移语义而不能把指针作为容器元素的缺陷。

  1. QWeakPointer 与 std::weak_ptr
    强引用类型的QSharedPointer已经非常好用,为什么还要有弱引用的 QWeakPointer?

QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手(因为它不具有普通指针的行为,没有重载operator*和->)。它的最大作用在于协助 QSharedPointer 工作,像一个旁观者一样来观测资源的使用情况。

weak_ptr 主要是为了避免强引用形成环状。摘自msdn中一段话:
A cycle occurs when two or more resources controlled by shared_ptr objects hold mutually referencing shared_ptr objects. For example, a circular linked list with three elements has a head node N0; that node holds a shared_ptr object that owns the next node, N1; that node holds a shared_ptr object that owns the next node, N2; that node, in turn, holds a shared_ptr object that owns the head node, N0, closing the cycle. In this situation, none of the reference counts will ever become zero, and the nodes in the cycle will not be freed. To eliminate the cycle, the last node N2 should hold a weak_ptr object pointing to N0 instead of a shared_ptr object. Since the weak_ptr object does not own N0 it doesn’t affect N0’s reference count, and when the program’s last reference to the head node is destroyed the nodes in the list will also be destroyed.
在Qt中,对于QObject及其派生类对象,QWeakPointer有特殊处理。它可以作为QPointer的替代品
这种情况下,不需要QSharedPointer的存在
5. QSharedDataPointer
这是为配合 QSharedData 实现隐式共享(写时复制 copy-on-write))而提供的便利工具。

Qt中众多的类都使用了隐式共享技术,比如QPixmap、QByteArray、QString、…。而我们为自己的类实现隐式共享也很简单,比如要实现一个 Employee类:

定义一个只含有一个数据成员(QSharedDataPointer) 的 Employee 类

我们需要的所有数据成员放置于 派生自QSharedData的 EmployeeData类中。

  1. QExplicitlySharedDataPointe
    这是为配合 QSharedData 实现显式共享而提供的便利工具。

QExplicitlySharedDataPointer 和 QSharedDataPointer 非常类似,但是它禁用了写时复制功能。这使得我们创建的对象更像一个指针。

13. QT的d和p指针

保持一个库中的所有公有类的大小恒定的问题可以通过单独的私有指针给予解决。这个指针指向一个包含所有数据的私有数据结构体。这个结构体的大小可以随意改变而不会产生副作用,应用程序只使用相关的公有类,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针。

D指针的其他好处
1.隐藏实现细节——我们可以不提供widget.cpp文件而只提供WidgetLib和相应的头文件和二进制文件。
2.头文件中没有任何实现细节,可以作为API使用。
3.由于原本在头文件的实现部分转移到了源文件,所以编译速度有所提高。

4.二进制兼容
其实以上的点都很细微,自己跟过源代码的人都会了解,qt是隐藏了d指针的管理和核心源的实现。像是在_p.h中部分函数的声明,qt也宣布在以后版本中将会删除。( This file is not part of the Qt API. It exists purely as an implementation detail. This header file may change from version to version without notice, or even be removed.)

q_ptr指针指向父类,使用如下宏定义辅助函数和声明友元类

#ifndef D_PTR_H
#define D_PTR_H
 
#include <QObject>
 
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 <QObject>
#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 
03-22 16:13