GSignal信号

1、描述

信号的基本概念是信号发射。信号引入了信号类型并通过字符串进行识别。为父类型引入的信号也可以在派生类型中使用,因此基本上它们是继承的每个类型的工具。

信号发射主要涉及以精确定义的方式调用特定的一组回调。此类回调主要分为两类:对象的回调和用户的回调。(尽管信号可以处理任何类型的可实例化类型,但在下文中,我将这些类型称为“对象类型”,仅仅是因为这是大多数用户会遇到信号的上下文。)每个对象的回调最常见称为“对象方法处理程序”或“默认(信号)处理程序”,而用户提供的回调通常仅称为“信号处理程序”。

对象方法处理程序是在信号创建时提供的(这最经常发生在对象类创建的末尾),而用户提供的处理程序经常在某些对象实例上与特定信号连接或断开连接。

信号发射包括五个阶段,除非过早停止:

1. G_SIGNAL_RUN_FIRST信号的对象方法处理程序的调用

2. 调用普通的用户提供的信号处理程序(after 未设置标志的地方)

3. G_SIGNAL_RUN_LAST信号的对象方法处理程序的调用

4. 调用用户提供的信号处理程序(after 已设置标志)

5. G_SIGNAL_RUN_CLEANUP信号的对象方法处理程序的调用

用户提供的信号处理程序按其连接顺序调用。

所有处理程序都可能过早停止信号发射,并且在信号发射期间可以连接,断开连接,阻塞或解除阻塞任何数量的处理器。

在信号发射的第2阶段和第4阶段,有某些条件可以跳过用户处理程序。

首先,用户处理程序可能被阻止。在回调调用期间,被忽略的处理程序被省略,为了从被阻塞状态返回,一个处理程序必须被取消阻塞的次数与之前被阻塞的次数完全相同。

其次,在发出G_SIGNAL_DETAILED信号时,detail 传入的附加 参数g_signal_emit()必须与当前受调用的信号处理程序的detail参数匹配。信号处理程序的无详细信息参数的指定(连接时省略信号规范的详细信息部分)用作通配符,并匹配传递给发射的任何详细参数。

尽管自detail 变量通常用于传递对象属性名称(如“ notify”一样),但对于详细信息字符串,没有强制要求使用任何特定格式,只是该格式必须为非空。

信号处理程序的内存管理

如果要将处理程序连接到信号并使用GObject实例作为信号处理程序用户数据,则应记住将的调用g_signal_connect()g_signal_handler_disconnect()或的 调用配对 g_signal_handlers_disconnect_by_func()。当确定发出信号的对象时,信号处理程序会自动断开连接,而销毁信号处理程序的用户数据时,信号处理程序不会自动断开连接。如果此用户数据是GObject实例,则在完成后从信号处理程序使用它是一个错误。

有两种用于管理此类用户数据的策略。第一个是在确定用户数据(对象)后断开信号处理程序(使用g_signal_handler_disconnect()或 g_signal_handlers_disconnect_by_func());这必须手动实现。对于非线程程序, g_signal_connect_object()可用于自动实现。但是,当前在线程程序中使用是不安全的。

第二个是对用户数据保持强大的参考,直到由于其他原因导致信号断开之后。可以使用自动实现g_signal_connect_data()

建议采用第一种方法,因为如果信号处理程序由于某种原因从未断开连接,则第二种方法可能导致有效的用户数据内存泄漏。

2、自定义信号实例 

signal-demo.h 内容如下

#ifndef SIGNAL_DEMO_H

#define SIGNAL_DEMO_H


#include <glib-object.h>
#define SIGNAL_TYPE_DEMO (signal_demo_get_type ())
#define SIGNAL_DEMO(object) \
        G_TYPE_CHECK_INSTANCE_CAST ((object), SIGNAL_TYPE_DEMO, SignalDemo)
#define SIGNAL_IS_DEMO(object) \
        G_TYPE_CHECK_INSTANCE_TYPE ((object), SIGNAL_TYPE_DEMO))
#define SIGNAL_DEMO_CLASS(klass) \
        (G_TYPE_CHECK_CLASS_CAST ((klass), SIGNAL_TYPE_DEMO, SignalDemoClass))
#define SIGNAL_IS_DEMO_CLASS(klass) \
        (G_TYPE_CHECK_CLASS_TYPE ((klass), SIGNAL_TYPE_DEMO))
#define SIGNAL_DEMO_GET_CLASS(object) (\
                G_TYPE_INSTANCE_GET_CLASS ((object), SIGNAL_TYPE_DEMO, SignalDemoClass))

typedef struct _SignalDemo SignalDemo;
struct _SignalDemo {

        GObject parent;

};

typedef struct _SignalDemoClass SignalDemoClass;
struct _SignalDemoClass {
        GObjectClass parent_class;
        void (*default_handler) (gpointer instance, const gchar *buffer, gpointer userdata);

};
GType signal_demo_get_type (void);

#endif

signal-demo.c 内容如下:

#include "signal-demo.h"

G_DEFINE_TYPE (SignalDemo, signal_demo, G_TYPE_OBJECT);

static void signal_demo_default_handler (gpointer instance, const gchar *buffer, gpointer userdata)
{
        g_printf ("Default handler said: %s\n", buffer);
}


void signal_demo_init (SignalDemo *self)
{

}

void signal_demo_class_init (SignalDemoClass *klass)
{

        klass->default_handler = signal_demo_default_handler;
        g_signal_new ("hello",
                      G_TYPE_FROM_CLASS (klass),
                      G_SIGNAL_RUN_FIRST,
                      G_STRUCT_OFFSET (SignalDemoClass, default_handler),
                      NULL,
                      NULL,
                      g_cclosure_marshal_VOID__STRING,
                      G_TYPE_NONE,
                      1,
                      G_TYPE_STRING);
}
  • 第 1 个参数是字符串“hello”,它表示信号。

  • 第 2 个参数是 SignalDemo 类的类型 ID,可以使用 G_TYPE_FROM_CLASS 宏从 SignalDemoClass 结构体中获取,也可直接使用 signal-demo.h 中定义的宏 SIGNAL_TYPE_DEMO。

  • 第 3 个参数可暂时略过。

  • 第 4 个参数比较关键,它是一个内存偏移量,主要用于从 SignalDemoClass 结构体中找到 default_handler 指针的位置,可以使用 G_STRUCT_OFFSET 宏来获取,也可以直接根据 signal-demo.h 中的 SignalDemoClass 结构体的定义,使用 sizeof (GObjectClass) 来得到内存偏移量,因为 default_handler 指针之前只有一个 GObjectClass 结构体成员。

  • 第 5 个和第 6 个参数暂时略过。

  • 第 7 个参数设定闭包的  marshal。在文档“函数指针、回调函数与 GObject 闭包” 中,描述了 GObject 的闭包的概念与结构,我们可以将它视为回调函数 + 上下文环境而构成的一种数据结构,或者再简单一点,将其视为回调函数。另外,在那篇文档中,我们也对 marshal 的概念进行了一些粗浅的解释。事实上 marshal 主要是用来“翻译”闭包的参数和返回值类型的,它将翻译的结果传递给闭包。之所以不直接调用闭包,而是在其外加了一层 marshal 的包装,主要是方便 GObject 库与其他语言的绑定。例如,我们可以写一个 pyg_closure_marshal_VOID__STRING 函数,其中可以调用 python 语言编写的“闭包”并将其计算结果传递给 GValue 容器,然后再从 GValue 容器中提取计算结果。

  • 第 8 个参数指定 marshal 函数的返回值类型。由于本例的第 7 个参数所指定的 marshal 是 g_cclosure_marshal_VOID__STRING 函数的返回值是 void,而 void 类型在 GObject 库的类型管理系统是 G_TYPE_NONE 类型。

  • 第 9 个参数指定 g_signal_new 函数向 marshal 函数传递的参数个数,由于本例使用的 marshal 函数是 g_cclosure_marshal_VOID__STRING 函数,g_signal_new 函数只向其传递 1 个参数。

  • 第 10 个参数是可变参数,其数量由第 8 个参数决定,用于指定 g_signal_new 函数向 marshal 函数传递的参数类型。由于本例使用的 marshal 函数是 g_cclosure_marshal_VOID__STRING 函数,并且 g_signal_new 函数只向其传递一个参数,所以传入的参数类型为 G_TYPE_STRING(GObject 库类型管理系统中的字符串类型)。

必须去构建 SignalDemo 类的使用者,即 main.c 源文件

#include "signal-demo.h"


static void my_signal_handler (gpointer *instance, gchar *buffer, gpointer userdata)
{
        g_print ("my_signal_handler said: %s\n", buffer);
        g_print ("my_signal_handler said: %s\n", (gchar *)userdata);
}


int main (void)
{

        g_type_init ();
        gchar *userdata = "This is userdata";
        SignalDemo *sd_obj = g_object_new (SIGNAL_TYPE_DEMO, NULL);

        /* 信号连接 */
        g_signal_connect (sd_obj, "hello",
                          G_CALLBACK (my_signal_handler),
                          userdata);



        /* 发射信号 */
        g_signal_emit_by_name (sd_obj,
                               "hello",
                               "This is the second param",
                               G_TYPE_NONE);
        return 0;
}

编译 signal-demo.c 与 main.c:

$ gcc signal-demo.c main.c -o test $(pkg-config --cflags --libs gobject-2.0)

程序运行结果如下:

我们再来看一下在第 1 个实例中被我们忽略的 g_signal_new 函数的第  3 个参数,我们将其设为 G_SIGNAL_RUN_FIRST。实际上,这个参数是枚举类型,是信号默认闭包的调用阶段的标识,可以是下面 7 种形式中 1 种,也可以是多种组合。这个参数被设为 G_SIGNAL_RUN_FIRST,表示信号的默认闭包要先于信号使用者的闭包被调用,这个观察一下上面的 test 程序的输出结果便可知悉。如果我们将这个参数设为 G_SIGNAL_RUN_LAST,则表示信号的默认闭包要迟于信号使用者的闭包而被调用。对于这个参数的理解暂且到此为止,后面在讲述信号连接的时候 还会再次谈到它。

在图信号处理(Graph Signal Processing, GSP)中,频率并不是传统的时间频率(如 Hz),而是**图拉普拉斯矩阵的特征值** $\lambda_i$,它们自然分布在 $[0, \lambda_{\max}]$ 区间内,通常可视为“图频率”。其中: - 小特征值对应**低频分量**(平滑信号); - 大特征值对应**高频分量**(剧烈变化信号)。 我们要设计一个**图低通滤波器**: 使得对前 $K$ 个低频成分(即小 $\lambda_i$)保留(响应 ≈1),其余高频部分抑制(响应 ≈0)。 使用切比雪夫多项式逼近实现该滤波器响应,并求出系数 $\alpha_l$。 由于切比雪夫多项式定义在 $[-1,1]$ 上,我们必须将图频率 $\lambda_i \in [0, \lambda_{\max}]$ **线性映射到 $[-1,1]$** 才能使用。这是关键一步! --- ### ✅ MATLAB 测试代码(适用于图信号重构) ```matlab % test_graph_chebyshev_filter.m % 图信号低通滤波器设计:基于切比雪夫多项式逼近 % 给定真实图频率(无需用户归一化,程序自动处理) % 设计目标:前 K 个低频点响应为 1,其余为 0 clear; clc; close all; %% ================= 用户可配置参数 =================== % 模拟图拉普拉斯矩阵的特征值(即“图频率”) num_nodes = 100; % 节点数(也即频率点数) eig_vals = sort(4 * rand(num_nodes, 1)); % 随机生成 [0,4] 内的特征值,模拟典型图谱分布 % 注意:这里假设最大特征值约为 4(例如网格图或小世界网络) K = 30; % 带宽:前 K 个低频成分保留(截止频率为 eig_vals(K)) poly_order = 15; % 切比雪夫多项式阶数(建议 10~20) %% ==================================================== N = length(eig_vals); fprintf('图信号重构测试开始...\n'); fprintf(' - 总节点数 / 频率点数: %d\n', N); fprintf(' - 截止频率索引 K: %d → 对应图频率 λ_K = %.4f\n', K, eig_vals(K)); %% 步骤1:将图频率从 [0, λ_max] 映射到 [-1, 1] lambda_min = min(eig_vals); lambda_max = max(eig_vals); % 线性映射:x ∈ [a,b] -> y ∈ [-1,1]: y = 2*(x-a)/(b-a) - 1 freq_normalized = 2 * (eig_vals - lambda_min) / (lambda_max - lambda_min) - 1; % 确保没有超出边界(防止数值误差) freq_normalized = max(-1, min(1, freq_normalized)); %% 步骤2:构造设计矩阵 A = [T_0(λ_i), T_1(λ_i), ..., T_{L-1}(λ_i)] L = poly_order; A = zeros(N, L); for l = 0:L-1 A(:, l+1) = chebypoly(l, freq_normalized); % 计算 T_l 在所有点上的值 end %% 步骤3:构建理想低通响应 b b_ideal = zeros(N, 1); b_ideal(1:K) = 1; % 前 K 个最低频率设为通带(低通) %% 步骤4:最小二乘求解 α alpha = A \ b_ideal; % 可选:加权最小二乘(强调通带精度) % W = diag([10*ones(K,1); ones(N-K,1)]); % alpha = (A' * W * A) \ (A' * W * b_ideal); %% 步骤5:计算实际逼近响应 response_approx = A * alpha; %% 步骤6:绘制结果 figure('Position', [100, 100, 900, 700]); % 子图1:主响应对比 subplot(3,1,1); plot(eig_vals, b_ideal, 's', 'MarkerSize', 4, 'DisplayName', '理想低通响应'); hold on; plot(eig_vals, response_approx, '-', 'LineWidth', 1.5, 'DisplayName', '切比雪夫逼近响应'); plot([eig_vals(K), eig_vals(K)], [-0.1, 1.1], '--r', 'DisplayName', ['截止频率 \lambda_K = ', num2str(eig_vals(K), '%.3f')]); xlabel('图频率 \lambda_i (拉普拉斯特征值)'); ylabel('增益'); title('【图信号低通滤波器】理想 vs 切比雪夫多项式逼近'); legend('Location', 'best'); grid on; hold off; % 子图2:绝对误差 error = abs(b_ideal - response_approx); subplot(3,1,2); plot(eig_vals, error, '-o', 'Color', [0.9, 0.4, 0.1], 'MarkerSize', 3); xlabel('图频率 \lambda_i'); ylabel('逼近误差 |e(\lambda)|'); title('逼近绝对误差'); grid on; % 子图3:通带细节放大(前K个点) subplot(3,1,3); idx_pass = 1:K; plot(eig_vals(idx_pass), response_approx(idx_pass), '-d', ... 'DisplayName', '通带逼近值', 'LineWidth', 1.2); yyaxis right; plot(eig_vals(idx_pass), error(idx_pass), '--x', 'Color', 'r', ... 'DisplayName', '通带误差', 'LineWidth', 1); xlabel('图频率 \lambda_i'); ylabel('增益 / 误差'); title('通带(低频)区域细节'); legend('show'); grid on; sgtitle('图信号重构用切比雪夫低通滤波器设计测试'); %% 输出统计信息 fprintf(' - 图频率范围: [%.4f, %.4f]\n', lambda_min, lambda_max); fprintf(' - 多项式阶数: %d\n', poly_order); fprintf(' - 平均误差: %.6f\n', mean(error)); fprintf(' - 最大误差: %.6f\n', max(error)); fprintf(' - 通带平均误差: %.6f\n', mean(error(1:K))); fprintf(' - 阻带平均误差: %.6f\n', mean(error(K+1:end))); % 显示最终系数 disp('切比雪夫基下的滤波器系数 α:'); disp(alpha'); %% ================= 辅助函数:计算第 n 阶切比雪夫多项式 T_n(x) ================= function T = chebypoly(n, x) if n == 0 T = ones(size(x)); elseif n == 1 T = x; else T0 = ones(size(x)); T1 = x; for k = 2:n T = 2*x.*T1 - T0; T0 = T1; T1 = T; end end end ``` --- ### 📌 关键说明 | 功能 | 说明 | |------|------| | ✅ 自动映射 | 将任意区间 $[\lambda_{\min}, \lambda_{\max}]$ 映射到 $[-1,1]$ | | ✅ 支持任意图频率 | 输入可以是真实图拉普拉斯特征值(无需人工归一化) | | ✅ 完整可视化 | 包括误差分析、通带细节 | | ✅ 实际可用 | 可直接嵌入图信号重构流程中作为滤波器设计模块 | --- ### 🔧 应用于图信号重构的后续步骤(提示) 有了系数 $\alpha_l$ 后,在图信号重构中你可以这样做: ```matlab % 假设你有一个图信号 gsignal,想用切比雪夫近似滤波器进行低通滤波 % 使用快速切比雪夫滤波算法(避免特征分解) filtered_signal = chebyshev_graph_filter(A_adjacency, alpha, gsignal, lambda_min, lambda_max); ``` 其中 `chebyshev_graph_filter` 是基于递推的快速图滤波函数(下次可为你提供)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值