IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 用C模拟QT的信号槽机制(下) -> 正文阅读

[数据结构与算法]用C模拟QT的信号槽机制(下)


前言

我在上一篇文章讲了关于用C模拟信号槽的思路,并完成了一个基本雏形,可以实现一个信号对应多个槽的功能,我原本以为这已经够用了,但是实际用起来发现根本不够用,于是我又重新构思了一下,将多个信号对应一个槽的功能也加了进来,并且还实现一个信号去连接另一个信号的功能。

基本思路

由于一个槽要连接多个信号,因此定义槽的时候就不能像之前一样定义一个节点而是也要定义一个链头,然后在连接的时候去动态申请一个槽节点,这个槽节点内部包含两个节点,一个是添加到自身槽的链头,另一个添加到信号的链头,断开连接的时候,就是槽遍历自身的链表,判断哪个节点连接了该信号,然后在将节点移除,信号的思路与槽一样,因为信号也有可能去连接其他的信号,以下是代码。

signal_slot_.h

/**
 * @file signal_slot.h
 * @author salalei
 * @brief 用C模拟的信号槽机制
 * @version V0.0.1
 * @date 2021-08-28
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#ifndef __SIGNAL_SLOT_H__
#define __SIGNAL_SLOT_H__

#ifdef __cplusplus
extern "C" {
#endif

//用户接口配置部分

//用户需要包含的头文件
#include "list.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct list_node user_list_node_t;                                             //用户提供的链表类型
#define USER_LIST_HEAD_INIT(x)                    LIST_HEAD_INIT(x)                    //用户提供链头初始化宏
#define USER_LIST_FOR_EACH_ENTRY(head, node, mem) list_for_each_entry(head, node, mem) //用户提供的遍历链表宏
#define USER_LIST_ADD(head, node)                 list_add_tail(head, node)            //用户提供的添加节点的函数
#define USER_LIST_DEL(node)                       list_del(node)                       //用户提供的删除节点的函数
#define USER_MALLOC                               malloc                               //用户提供的申请内存的函数
#define USER_FREE                                 free                                 //用户提供的释放内存的函数
#define USER_WARN                                 printf                               //用户提供的打印信息的函数

/**
 * @brief 信号或槽的节点(内部使用)
 */
struct _signal_slot_node
{
    user_list_node_t self_node;   //连接自身的节点
    user_list_node_t signal_node; //连接信号的节点
    void *func;                   //执行的函数
    void *signal;                 //归属于哪个信号
};

int _connect(struct _signal_slot_node *, struct _signal_slot_node *, struct _signal_slot_node *);
struct _signal_slot_node *_disconnect(struct _signal_slot_node *, struct _signal_slot_node *);

#define _SIGNAL_SLOT_DEF(name, ...) \
    void name(struct _signal_slot_node *sender, ##__VA_ARGS__); \
    struct _signal_slot_node name##_head = { \
        .self_node = USER_LIST_HEAD_INIT(name##_head.self_node), \
        .signal_node = USER_LIST_HEAD_INIT(name##_head.signal_node), \
        .func = name, \
        .signal = NULL, \
    }; \
    void name(struct _signal_slot_node *sender, ##__VA_ARGS__)

#define _SIGNAL_SLOT_DECL(name, ...) \
    void name(struct _signal_slot_node *sender, ##__VA_ARGS__); \
    extern struct _signal_slot_node name##_head

/**
 * @brief 定义一个信号
 * 
 * @param name 信号的名称
 * @param ... 这个信号的参数
 */
#define SIGNAL_DEF(name, ...) \
    typedef void (*name##_t)(struct _signal_slot_node * sender, ##__VA_ARGS__); \
    _SIGNAL_SLOT_DEF(name, ##__VA_ARGS__)

/**
 * @brief 发送信号(用在定义信号的函数里面)
 * 
 * @param name 信号的名称
 * @param ... 要发送的参数
 */
#define SIGNAL_SEND(name, ...) \
    struct _signal_slot_node *node; \
    name##_t func; \
    USER_LIST_FOR_EACH_ENTRY(&name##_head.signal_node, node, signal_node) \
    { \
        func = (name##_t)node->func; \
        func(&name##_head, ##__VA_ARGS__); \
    }

/**
 * @brief 将已定义的信号对外申明
 * 
 * @param name 信号的名称
 * @param ... 这个信号的参数
 */
#define SIGNAL_DECL(name, ...) \
    _SIGNAL_SLOT_DECL(name, ##__VA_ARGS__)

/**
 * @brief 定义一个槽
 * 
 * @param name 槽的名称
 * ... 这个槽的参数
 */
#define SLOT_DEF(name, ...) \
    _SIGNAL_SLOT_DEF(name, ##__VA_ARGS__)

/**
 * @brief 将已定义的槽对外申明
 * 
 * @param name 信号的名称
 * @param ... 这个槽的参数
 */
#define SLOT_DECL(name, ...) \
    _SIGNAL_SLOT_DECL(name, ##__VA_ARGS__)

/**
 * @brief 发射一个信号
 * 
 * @param name 需要发射的信号名称
 * @param ... 需要发送的数据
 */
#define EMIT(name, ...) \
    name(&name##_head, ##__VA_ARGS__)

/**
 * @brief 在槽函数中判断是谁发送的信号
 * 
 * @param name 需要判断的信号名称
 * @return 如果是name发射的信号返回非0,否则返回0
 */
#define IS_SENDER(name) \
    (sender == &name##_head)

/**
 * @brief 将指定信号与指定槽连接(使用动态内存方式)
 * 
 * @param signal_name 需要连接的信号名称
 * @param slot_name 需要连接的槽名称
 */
#define CONNECT(signal_name, slot_name) \
    do \
    { \
        struct _signal_slot_node *node; \
        node = USER_MALLOC(sizeof(struct _signal_slot_node)); \
        if (node == NULL) \
            USER_WARN("Failed to malloc %s node\n", #slot_name); \
        else \
        { \
            if (_connect(&signal_name##_head, &slot_name##_head, node)) \
            { \
                USER_WARN("%s has been connected to the %s\n", #slot_name, #signal_name); \
                USER_FREE(node); \
            } \
        } \
    } \
    while (0)

/**
 * @brief 将指定信号与指定槽断开连接(使用动态内存方式)
 * 
 * @param signal_name 需要连接的信号名称
 * @param slot_name 需要连接的槽名称
 */
#define DISCONNECT(signal_name, slot_name) \
    do \
    { \
        struct _signal_slot_node *node; \
        node = _disconnect(&signal_name##_head, &slot_name##_head); \
        if (node) \
            USER_FREE(node); \
        else \
            USER_WARN("The %s and %s have been disconnected\n", #signal_name, #slot_name); \
    } \
    while (0)

/**
 * @brief 将指定信号与指定槽连接(使用静态内存方式)
 * 
 * @param signal_name 需要连接的信号名称
 * @param slot_name 需要连接的槽名称
 */
#define CONNECT_STATIC(signal_name, slot_name) \
    do \
    { \
        static struct _signal_slot_node node; \
        if (_connect(&signal_name##_head, &slot_name##_head, &node)) \
            USER_WARN("%s has been connected to the %s\n", #slot_name, #signal_name); \
    } \
    while (0)

/**
 * @brief 将指定信号与指定槽断开连接(使用静态内存方式)
 * 
 * @param signal_name 需要连接的信号名称
 * @param slot_name 需要连接的槽名称
 */
#define DISCONNECT_STATIC(signal_name, slot_name) \
    do \
    { \
        if (_disconnect(&signal_name##_head, &slot_name##_head) == NULL) \
            USER_WARN("The %s and %s have been disconnected\n", #signal_name, #slot_name); \
    } \
    while (0)

#ifdef __cplusplus
}
#endif

#endif

signal_slot.c

/**
 * @file signal_slot.c
 * @author salalei
 * @brief 用C模拟的信号槽机制
 * @version V0.0.1
 * @date 2021-08-28
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#include "signal_slot.h"

/**
 * @brief 连接信号槽
 * 
 * @param signal_head 信号的链头
 * @param head 被连接的信号或槽的链头
 * @param node 新的信号或槽的节点
 * @return int 成功返回0,若已有节点存在则返回-1
 */
int _connect(struct _signal_slot_node *signal_head, struct _signal_slot_node *head, struct _signal_slot_node *new_node)
{
    struct _signal_slot_node *node;

    USER_LIST_FOR_EACH_ENTRY(&head->self_node, node, self_node)
    {
        if (node->signal == signal_head)
            return -1;
    }
    new_node->signal = signal_head;
    new_node->func = head->func;
    USER_LIST_ADD(&head->self_node, &new_node->self_node);
    USER_LIST_ADD(&signal_head->signal_node, &new_node->signal_node);
    return 0;
}

/**
 * @brief 将指定信号和槽的连接断开
 * 
 * @param signal_head 信号的链头
 * @param slot_head 槽的链头
 * @return struct _signal_slot_node* 成功返回被删除的节点,失败返回NULL
 */
struct _signal_slot_node *_disconnect(struct _signal_slot_node *signal_head, struct _signal_slot_node *slot_head)
{
    struct _signal_slot_node *node;

    USER_LIST_FOR_EACH_ENTRY(&slot_head->self_node, node, self_node)
    {
        if (node->signal == signal_head)
        {
            USER_LIST_DEL(&node->signal_node);
            USER_LIST_DEL(&node->self_node);
            return node;
        }
    }
    return NULL;
}

list.h

/**
 * @file list.h
 * @author hz010153 (yunlei.zhou@3d-scantech.com)
 * @brief 简单的双向链表
 * @version V1.0.0
 * @date 2021-08-29
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#ifndef __LIST_H__
#define __LIST_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

#define MEMBER_OFFSET(type, mem)   ((size_t) & (((type *)0)->mem))
#define CONTAINER_OF(p, type, mem) ((type *)((size_t)(p)-MEMBER_OFFSET(type, mem)))

struct list_node
{
    struct list_node *prev;
    struct list_node *next;
};

#define LIST_HEAD_INIT(head) \
    { \
        .prev = &(head), \
        .next = &(head) \
    }

/**
 * @brief 在链表尾部添加一个节点
 * 
 * @param head 指向链头的指针
 * @param node 指向新节点的指针
 */
static inline void list_add_tail(struct list_node *head, struct list_node *node)
{
    node->next = head;
    node->prev = head->prev;
    head->prev->next = node;
    head->prev = node;
}

/**
 * @brief 删除指定的节点
 * 
 * @param node 指向要移除的节点的指针
 */
static inline void list_del(struct list_node *node)
{
    node->prev->next = node->next;
    node->next->prev = node->prev;
    node->prev = node;
    node->next = node;
}

/**
 * @brief 正向遍历链表
 * 
 * @param head 指向链头的指针
 * @param node 返回的当前节点
 * @param mem 链表在该结构体中的成员名
 */
#define list_for_each_entry(head, node, mem) \
    for ((node) = CONTAINER_OF((head)->next, typeof(*node), mem); \
         &(node)->mem != (head); \
         (node) = CONTAINER_OF((node)->mem.next, typeof(*node), mem))

#ifdef __cplusplus
}
#endif

#endif

为了减小耦合,提高模块复用,我将链表部分的代码单独拎出来,这样的话用户可以使用我的链表,也可以使用他们自己写的,在signal_slot.h改一下配置就行,链表我参考了linux内核的链表,并只写了我要用到的几个函数。

用法就不多说了,直接上示例代码更快。


示例

signal_test.c

#include "signal_test.h"

SIGNAL_DEF(signal1, char *str)
{
    SIGNAL_SEND(signal1, str);
}

SIGNAL_DEF(signal2, char *str)
{
    SIGNAL_SEND(signal2, str);
}

signal_test.h

#ifndef __SIGNAL_TEST_H__
#define __SIGNAL_TEST_H__

#include "../signal_slot.h"

SIGNAL_DECL(signal1, char *);
SIGNAL_DECL(signal2, char *);

#endif

slot_test.c

#include "slot_test.h"
#include "signal_test.h"

#include <stdio.h>

static void _slot(int index, void *sender, char *content)
{
    printf("[slot%d] ", index);
    printf("sender:");
    if (IS_SENDER(signal1))
        printf("signal1");
    else if (IS_SENDER(signal2))
        printf("signal2");
    else
        printf("unknown");
    printf(" content:%s\n", content);
}

SLOT_DEF(slot1, char *str)
{
    _slot(1, sender, str);
}

SLOT_DEF(slot2, char *str)
{
    _slot(2, sender, str);
}

slot_test.h

#ifndef __SLOT_TEST_H__
#define __SLOT_TEST_H__

#include "../signal_slot.h"

SLOT_DECL(slot1, char *);
SLOT_DECL(slot2, char *);

#endif

main.c

#include "../signal_slot.h"
#include <stdio.h>

#include "signal_test.h"
#include "slot_test.h"

int main(int argc, char **argv)
{
    CONNECT(signal1, slot1);
    CONNECT(signal1, slot2);
    CONNECT(signal2, slot1);
    CONNECT(signal2, slot2);
    CONNECT(signal1, signal2);

    EMIT(signal1, "hello");
    EMIT(signal2, "world");

    return 0;
}

我定义了两个信号,两个槽,然后每个信号连接两个槽,每个槽连接两个信号,并且又将信号1和信号2相连,以下为运行结果。

在这里插入图片描述


总结

感觉功能应该实现的差不多了,暂时就这样了。今天顺便上传了github,https://github.com/salalei/c_signal_slot,各位要者自取。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-30 12:27:46  更:2021-08-30 12:28:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:30:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码