void (*signal(int signo, void (*fun(int))))(int)

本文深入探讨Linux信号处理机制,解释signal函数的使用方法及其与alarm函数结合实现的定时任务执行过程。通过实例演示如何在主程序中利用信号机制定时触发特定进程,提升程序的响应性和灵活性。

void (*signal(int signo, void (*fun(int))))(int)

int (*p)();
这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.
int (*fun())();
这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.所以说对于

void (*signal(int signo, void (*fun)(int)))(int);

就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数), 而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.
signal函数返回的其实是指向以前的信号处理程序的指针,

 

void (*signal(int signo, void (*fun(int))))(int)
signal信号处理函数原型,signal有两个参数,一个整形,一个是有一个整形参数的func函数返回的void类型,这个函数返回一个指针,指向可以咯有一个int参数的函数并且返回一个void类型的值,这个函数太复杂,本身没有意义,一般都是用typedef之后的简单的信号处理函数

 

利用signal()函数与alarm()函数,实现在主程序中安一定的时间间隔运行另一个进程
先说一下signal()这个函数.
unix信号signal定义
#include <signal.h>
void (*signal(int signo,void(*fun)(int)))(int)

说明:1 返回值是一个 void (*)(int)类型的函数指针
         2 signo是信号名
         3 第二个参数为该信号的处理函数指针,类型为void (*)(int)
          SIG_IGN 忽略参数signum指定的信号。
       SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。

         4 函数返回:成功则返回以前的信号处理配置函数指针,出错返回SIG_ERR
         5 SIG_ERR原形为 #define SIG_ERR (void (*)())-1

 
看一个例子!
#include <signal.h>
#include<stdio.h>
 
void handler() {
    printf(“hello\n”);
}
main()
{
    int i;
    signal(SIGALRM,handler);
    alarm(5);
    for(i=1;i<7;i++){
         printf(“sleep %d ...\n”,i);
         sleep(1);
    }
}
    alarm(5)会在它的参数指定的时间到达的时候发出一个SIGALRM信号,也就是5秒之后发出一个SIGALRM,然后signal(SIGALRM,handler)捕捉到了这个信号就去执行它的第二个参数指定的函数,这里是handler()函数。
    无论程序执行到哪一部分,只要时间一到,alarm()就会发出SIGALRM信号,只要一有SIGALRM信号,signal()就捕捉,然后去执行handler()函数
所以执行结果应该如下:
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
sleep 5 ...
hello
sleep 6 ...
 
下面还有个例子,大家自己去琢磨吧!
#include <unistd.h>
#include <signal.h>
#include<stdio.h>
#include<stdlib.h>

#define        PARSE_TIMEOUT                5

static void sig_usr(int);
void sig_parse();

int main()
{
        if (signal(SIGUSR1,sig_usr) == SIG_ERR)
                printf("can't catch SIGUSR1");
        if (signal(SIGUSR2,sig_usr) == SIG_ERR)
                printf("can't catch SIGUSR12");
        if (signal(SIGKILL,sig_usr) == SIG_ERR)
                printf("can't catch SIGKILL\n");
        if (signal(SIGSTOP,sig_usr) == SIG_ERR)
                printf("can't catch SIGSTOP\n");
               
        signal(SIGALRM,        sig_parse);
        alarm(PARSE_TIMEOUT);
       
        for ( ; ;)
                pause();
       
}

static void sig_usr(int signo)
{
        if(signo == SIGUSR1)
                printf("received SIGUSR1\n");
        else if(signo == SIGUSR2)
                printf("received SIGUSR2\n");
        else if(signo == SIGKILL)
         
进程间的通信 1、什么是进程间的通信? 不同进程间的消息的传播或者交换信息。 简单来说进程之间可以互相发送消息 2、进程间的通信方式 1、传统间的进程通信方式 无名管道 有名管道 信号通信 2、系统V版本引入的通信方式 消息队列 共享内存 信号灯集 一、无名管道 1、无名管道的特点 1.无名管道是内核空间实现的机制 2.只能用于亲缘间进程的通信(父子进程) 3.无名管道数据半双工的通信的方式 单工 : A -------------->B 半双工 : 同一时刻 A----->B B------>A 全双工 : 同一时刻 A<---->B 4.无名管道的大小是64K 5.无名管道不能够使用lseek函数(调用会出错 返回 -1) 6.管道中的数据被都走了就没有了 7.读写的特点 如果读端存在 写管道:有多少写多少,直到写满为止(64k)写阻塞,直到管道中腾出新的4K空间,写操作解除阻塞 如果读端不存在 写管道,管道破裂(SIGPIPE) 如果写端存在 读管道:有多少读多少,没有数据的时候阻塞等待 如果写端不存在 读管道:有多少读多少,没有数据的时候立即返回(非阻塞) #include <unistd.h> int pipe(int pipefd[2]); 功能: pipe会创建一个管道,是一个单向的数据通道,可以用于进程间通信 参数: pipefd:管道的文件描述符 pipefd[0] 是管道的读端 pipefd[1] 是管道的写端 返回值: 成功 0 失败 -1 重置错误码 #include <my_head.h> int main(int argc, char const *argv[]) { // 创建无名管道 int pipefd[2]={0}; int ret = 0; ret = pipe(pipefd); if(ret == -1){ printf("pipe error\n"); exit(-1); } //如果读端存在 写管道:有多少写多少, //直到写满为止(64k)写阻塞,直到管道中腾出新的4K空间,写操作解除阻塞 int buf[65536]={0}; write(pipefd[1],buf,65536); read(pipefd[0],buf,4096); write(pipefd[1],"a",1); printf("写入成功\n"); #if 0 //如果写端不存在 读管道:有多少读多少,没有数据的时候立即返回(非阻塞) char buf[32]= "hello"; write(pipefd[1], buf, sizeof(buf)); close(pipefd[1]); //关闭写端 memset(buf,0, sizeof(buf)); read(pipefd[0], buf, sizeof(buf)); printf(" 第一次读取 buf =%s \n",buf); memset(buf, 0, sizeof(buf)); read(pipefd[0], buf, sizeof(buf)); printf(" 第二次读取 buf =%s \n",buf); #endif # if 0 //管道写数据 char buf[32]= "hello"; write(pipefd[1], buf, sizeof(buf)); //读取管道中的内容 memset(buf, 0, sizeof(buf)); read(pipefd[0], buf, sizeof(buf)); printf(" 第一次读取 buf =%s \n",buf); memset(buf, 0, sizeof(buf)); read(pipefd[0], buf, sizeof(buf)); printf(" 第二次读取 buf =%s \n",buf); #endif close(pipefd[0]); close(pipefd[1]); return 0; } 二、有名管道 1、特点 - 普通文件 l 链接文件 s 套接字文件 p 管道文件 b 块设备文件 c 字符设备文件 d 目录文件 有名管道会在文件系统中创建一个管道文件,只需要打开这个文件,进行读写操作即可 管道文件本质是在内存上的,在硬盘上的只是一个标识 1.可以用于任意进程间的通信,不仅限亲缘进程 2.有名管道数据是半双工的通信方式 3.有名管道的大小是64K 4.有名管道不能够使用lseek函数(调用会失败 返回 -1) 5、读写的特点 如果读端存在、写管道:有多少写多少,直到写满为止(64k)写阻塞 如果读端不存在、写管道: 1.读端没有打开,写端在open的位置阻塞 2.读端打开后关闭,管道破裂(SIGPIPE) 如果写端存在 读管道:有多少读多少,没有数据的时候阻塞等待 如果写端不存在 读管道 1.写端没有打开,读端在open的位置阻塞 2.写端打开后关闭,有多少读多少,没有数据的时候立即返回 #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); 功能:创建一个管道文件 参数: pathname:管道文件的路径和名字 mode:权限 mode & ~umask --> 最终权限 返回值: 成功 0 失败 -1 重置错误码 #include <my_head.h> int main(int argc, char const *argv[]) { int fd = open("my_fifo", O_WRONLY); if(-1 == fd) exit(-1); printf("管道文件打开成功\n"); char buf[128]={0}; while(1){ memset(buf, 0, sizeof(buf)); fgets(buf, sizeof(buf), stdin); buf[strlen(buf)-1] = '\0'; write(fd, buf , sizeof(buf)); if(!strcmp(buf,"quit")){ break; } } close(fd); return 0; } #include <my_head.h> int main(int argc, char const *argv[]) { int fd = open("my_fifo", O_RDONLY); if(-1 == fd) exit(-1); printf("管道文件打开成功\n"); char buf[128]={0}; while(1){ memset(buf, 0, sizeof(buf)); read(fd, buf , sizeof(buf)); if(!strcmp(buf,"quit")){ break; } printf("读到的数据 : %s\n",buf); } close(fd); return 0; } #include <my_head.h> int main(int argc, const char *argv[]) { if(-1 == mkfifo("my_fifo", 0664)) printf("mkfifo error"); return 0; } 三、信号通信(重点) 1、信号的概念 信号是在软件层次上对中断机制的一种模拟,是一种 异步通信方式。 用户可以给进程发信号,进程可以给进程发信号,linux内核也可以给进程发信号。 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它; 2、信号的分类 在Linux中,信号被分为不可靠信号和可靠信号,一共64种,可以通过kill -l命令来查看 ● 不可靠信号:也称为非实时信号,不支持排队,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,信号值取值区间为1~31 ● 可靠信号:也称为实时信号,支持排队,信号不会丢失,发多少次,就可以收到多少次,信号值取值区间为32~64 3、信号的产生方式: ● 对于前台进程,用户可以输入特殊终端字符来发送,比如输入Ctrl+C 结束进程 ● 系统异常,比如浮点异常和非法内存段访问 ● 系统状态变化,比如alarm定时器到期时将引起SIGALRM信号 ● 在终端运行kill命令或在程序中调用kill函数 4、信号的处理方式 信号的处理方式有三种:默认、忽略、捕捉 ● 忽略信号:不做任何处理,但是对SIGKILL和SIGSTOP信号不能做忽略处理,一个杀死,一个停止 ● 捕捉信号:执行自定义的信号处理函数 ● 执行(缺省)默认操作:Linux系统中对每种信号规定了默认操作,即执行信号默认的功能 5、常见的信号 kill -l 查看信号 man 7 signal: SIGKILL:结束进程,不能被忽略不能被捕捉 SIGSTOP:结束进程,不能被忽略不能被捕捉 SIGCHLD:子进程状态改变时给父进程发的信号,不会结束进程 SIGINT:结束进程,对应快捷方式ctrl+c SIGTSTP:暂停信号,对应快捷方式ctrl+z SIGQUIT:退出信号,对应快捷方式ctrl+\ SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。 SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号 SIGCHLD 当子进程退出的时候,会给父进程发送该信号,默认的行为是忽略。 上述信号中只有 SIGKILL 和 SIGSTOP 不能被忽略和捕捉 6、信号处理的过程 程序运行在用户空间时->进程由于系统调用或中断进入内核->转向用户空间执行信号处理函数->信号处理函数完毕后进入内核->返回用户空间继续执行程序 7、信号处理函数API #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 功能:信号处理函数 参数: signum:信号的编号 除了 SIGKILL 和 SIGSTOP !!!!! handler:信号的处理方式 SIG_IGN 忽略 SIG_DFL 默认 也可以传一个函数的首地址 捕捉 void sig_func(int signum){ //信号处理函数 } 返回值: 成功 handler的首地址 失败 SIG_ERR 重置错误码 #include <my_head.h> void sig_func(int signum){ //信号处理函数 printf("我是杀不死的\n"); } int main(int argc, char const *argv[]) { #if 0 signal(SIGINT, SIG_IGN); //忽略 #endif #if 0 signal(SIGINT, SIG_DFL); //默认 #endif signal(SIGINT, sig_func); //捕捉 while(1){ sleep(1); printf("我还活着\n"); } return 0; } #include <signal.h> int raise(int sig); 功能:给自己发信号 参数:sig:信号的编号 返回值: 成功 0 失败 非0 #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); 功能:给指定pid的进程发信号 参数: pid: >0 给指定pid的进程发信号 --常用 =0 发给和调用进程同组的进程 -1 给所有有权限操作的进程发信号(init 除外) -1 > 发给所有组ID位 -pid 的进程 sig:信号的编号 返回值: 成功 0 失败 -1 重置错误码 #include <my_head.h> int main(int argc, char const *argv[]) { if(argc != 2) return -1; printf("准备 杀死自己\n"); int pid = getpid(); int signum = atoi(argv[1]+1); kill(pid, signum); while(1){ sleep(1); printf("马上了 别急\n"); } return 0; } #include <my_head.h> void func(int signum){ printf("子进程退出成功\n"); raise(SIGKILL); } int main(int argc, char const *argv[]) { signal(SIGCHLD, func); pid_t pid = 0; if(-1 == (pid = fork())){ printf("fork error\n"); return -1; }else if(pid == 0){ printf("我是子进程,3秒退出\n"); sleep(3); printf("我是子进程 我要退出了\n"); exit(0); }else if(pid > 0 ){ while(1){ sleep(1); printf("我是父进程\n"); } } return 0; } #include <unistd.h> unsigned int alarm(unsigned int seconds); 功能: 在设置的时间到达后,给进程发一个SIGALRM信号 如果seconds为0 则不会发出信号 且会取消之前的闹钟 重新调用alarm也会取消之前的闹钟 参数:seconds:秒数 返回值: 如果之前设置过闹钟,返回的是上一个闹钟剩余的秒数 如果之前没有设置过闹钟,返回0 #include <my_head.h> void fun(int signum){ printf("自动出牌\n"); alarm(3); } int main(int argc, char const *argv[]) { signal(SIGALRM, fun); alarm(3); while(1){ getchar(); printf("手动出牌\n"); alarm(3); } return 0; } 四、共享内存 1、概念 在内核中创建共享内存(基于内核实现的),让进程A和进程B都能够访问到,通过这段内存进行数据的传递。 共享内存是所有进程间通信方式中效率最高的(不需要来回进行数据的拷贝) 2、函数 shared memory 1. 创建唯一key键值 ftok #include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id); 功能: 获取IPC通信的键值 (//密钥) 参数: pathname:路径和文件名(必须是已存在的 可访问的文件) proj_id:填充的是任意一个字符,但只使用了低8bit位 返回值: 成功 key_t 键值 失败 -1 重置错误码 2. 创建或打开共享内存 shmget #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); 功能: 创建共享内存 参数: key:键值 size:共享内存的大小 PAGE_SIZE(4k)的整数倍 shmflg:共享的标志位 IPC_CREAT|0666 或 IPC_CREAT|IPC_EXCL|0666 返回值: 成功 共享内存编号 失败 -1 重置错误码 3. 映射共享内存 shmat #include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); 功能: 映射共享内存到当前的进程空间 参数: shmid:共享内存编号 shmaddr:属性 NULL,让系统自动分配 shmflg:共享内存操作方式 0 读写 SHM_RDONLY 只读 返回值: 成功 指向共享内存的地址//通过这个地址就可以操作共享内存了 失败 (void *)-1 重置错误码 4. 取消映射 shmdt int shmdt(const void *shmaddr); 功能: 取消地址映射 参数: shmaddr:指向共享内存的指针 返回值: 成功 0 失败 -1 重置错误码 5. 删除共享内存 shmctl int shmctl(int shmid, int cmd, struct shmid_ds *buf); 功能: 共享内存控制的函数 参数: shmid:共享内存编号 cmd:操作的命令码 IPC_STAT:获取shmid属性信息,用到第三个参数(了解) struct shmid_ds shd; shmctl(shmid, IPC_STAT, &shd); IPC_SET:设置shmid属性信息,用到第三个参数(了解) struct shmid_ds shd; shd.size = 100; shmctl(shmid, IPC_SET, &shd); IPC_RMID:删除共享内存,第三个参数填空 (重要) shmctl(shmid, IPC_RMID,NULL); buf:共享内存属性结构体指针 返回值: 成功 0 失败 -1 重置错误码 #include <my_head.h> int main(int argc, char const *argv[]) { //创建键值 key_t my_key = ftok("./09ftok.c",'a'); if(-1 == my_key) return -1; printf("my_key = %#x\n",my_key); //创建或者打开共享内存 int shmid = shmget(my_key, 4096, IPC_CREAT | 0666); if(-1 == shmid) return -1; printf("shmid = %d\n",shmid); //映射到当前空间 char *p = shmat(shmid, NULL, 0); if(p == (void *)-1) return -1; //读写 操作 //取消映射 if(-1 ==shmdt(p)) return 01; //删除共享内存 shmctl(shmid, IPC_RMID, NULL); return 0; } 生成思维导图
最新发布
08-10
<think>我们有一个复杂的函数声明需要解释:void (*signal(int signo, void (*func)(int)))(int); 根据引用[1]到[5]的内容,我们可以逐步分析这个声明。 首先,这个声明是C语言中信号处理函数`signal`的原型。它的目的是设置一个信号处理函数,当特定的信号(由`signo`指定)发生时,调用设置的处理函数。 分解声明: 1. 最内层:`void (*func)(int)` 这是一个函数指针,名为`func`,它指向一个函数,该函数接受一个`int`参数并返回`void`(即无返回值)。这个函数就是信号处理函数。 2. 中间层:`signal(int signo, void (*func)(int))` 这里`signal`是一个函数,它有两个参数: - 第一个参数`int signo`:表示信号编号(如SIGINT等)。 - 第二个参数`void (*func)(int)`:就是上面提到的函数指针,指向用户定义的信号处理函数。 3. 外层:`void (* ... )(int)` 整个声明表示:`signal`函数返回一个函数指针,该指针指向一个函数,这个函数接受一个`int`参数并返回`void`。这个返回的函数指针实际上指向的是之前的信号处理函数(即在设置新的信号处理函数之前,该信号所关联的处理函数)。 因此,整个声明可以理解为: - `signal`是一个函数,它接受两个参数(一个整型信号编号和一个函数指针),并返回一个函数指针(该函数指针指向一个接受整型参数且无返回值的函数)。 为了简化这个复杂的声明,通常使用typedef来定义函数指针类型。例如: typedef void (*sighandler_t)(int); 然后,`signal`的声明就可以写成: sighandler_t signal(int signo, sighandler_t func); 这样,`signal`函数的第二个参数和返回值都是`sighandler_t`类型,即指向信号处理函数的指针。 根据引用[4]中的例子,我们可以用typedef简化: typedef void (*ptr_to_func)(int); // 定义函数指针类型 ptr_to_func signal(int, ptr_to_func); // 用新类型声明signal函数 这样声明就清晰多了。 另外,引用[5]提到,如果我们将返回的函数指针类型定义为`type`,那么声明可以写成: type signal(int, void (*func)(int)); 其中`type`是`void (*)(int)`的别名。 现在,我们可以用简单的语言总结: 函数`signal`的作用是:为信号`signo`设置一个新的信号处理函数`func`,并返回该信号之前的处理函数(也是一个同类型的函数指针)。这样,如果需要,我们可以保存旧的处理函数,并在适当的时候恢复。 示例代码(来自引用[4]): #include <stdio.h> enum { RED, GREEN, BLUE }; void OutputSignal(int sig) { printf("The signal you've input is: "); switch(sig) { case RED: puts("RED!"); break; case GREEN: puts("GREEN!"); break; case BLUE: puts("BLUE!"); break; } } void ( *signal( int sig, void (*func)(int) ) ) (int) { puts("Hello, world!"); func(sig); return func; } int main(void) { (*signal(GREEN, &OutputSignal))(RED); return 0; } 在这个例子中,`signal`函数被定义(注意:实际中我们不会这样定义signal,这里只是示例如何理解声明)。在`main`函数中,我们调用`signal(GREEN, &OutputSignal)`,它返回一个函数指针(这里返回的是`OutputSignal`的指针),然后我们立即用`RED`作为参数调用这个返回的函数指针。 注意:实际使用中,`signal`函数是由C标准库提供的,我们只需要包含头文件<signal.h>,并使用正确的函数声明。 因此,我们可以这样使用: #include <signal.h> void my_handler(int sig) { // 处理信号 } int main() { // 设置信号处理函数,并保存旧的处理函数 void (*old_handler)(int) = signal(SIGINT, my_handler); // ... // 如果需要,可以恢复旧的处理函数 signal(SIGINT, old_handler); return 0; } 总结: - `void (*signal(int signo, void (*func)(int)))(int);` 是一个复杂的函数声明。 - `signal`函数接受两个参数:一个整型信号编号和一个指向信号处理函数的指针(该函数接受一个整型参数并返回void)。 - `signal`函数返回一个指向信号处理函数的指针(该函数类型与第二个参数相同),即返回的是设置新处理函数之前的旧处理函数。 通过使用typedef可以简化这个声明,提高可读性。</think>### `signal` 函数声明解析 `void (*signal(int signo, void (*func)(int)))(int);` 是 C 语言中信号处理函数 `signal` 的原型声明,其核心含义如下: #### 1. **逐步分解声明** - **最内层:** `void (*func)(int)` - `func` 是一个**函数指针**,指向一个接受 `int` 参数(信号编号)、返回 `void` 的函数(即信号处理函数)。 - **中间层:** `signal(int signo, void (*func)(int))` - `signal` 是一个函数,接受两个参数: - `int signo`:信号编号(如 `SIGINT`)。 - `void (*func)(int)`:新的信号处理函数指针。 - **最外层:** `void (* ... )(int)` - `signal` 函数的**返回值**是一个函数指针,指向一个接受 `int` 参数、返回 `void` 的函数(即旧的信号处理函数)。 #### 2. **完整语义** - **功能**:`signal` 为信号 `signo` 设置新的处理函数 `func`,并返回该信号**之前的处理函数指针**(用于恢复或链式调用)。 - **返回值类型**:与参数 `func` 类型相同(`void (*)(int)`),即旧处理函数的指针。 #### 3. **简化理解(使用 `typedef`)** 通过 `typedef` 可大幅简化声明: ```c typedef void (*sighandler_t)(int); // 定义函数指针类型 sighandler_t signal(int signo, sighandler_t func); // 简化后的声明 ``` - `sighandler_t` 表示信号处理函数的类型(接受 `int`,返回 `void`)。 - `signal` 直接返回 `sighandler_t` 类型(旧处理函数指针)。 #### 4. **示例用法** ```c #include <signal.h> #include <stdio.h> // 自定义信号处理函数 void handler(int sig) { printf("Received signal: %d\n", sig); } int main() { // 设置 SIGINT 的处理函数为 handler,并保存旧处理函数 void (*old_handler)(int) = signal(SIGINT, handler); // 触发 SIGINT(模拟 Ctrl+C) raise(SIGINT); // 恢复旧的处理函数 signal(SIGINT, old_handler); return 0; } ``` - **输出**:`Received signal: 2`(`SIGINT` 编号为 2)。 - **关键点**:`signal(SIGINT, handler)` 返回指向旧处理函数的指针,可后续用于恢复[^3][^4]。 #### 5. **核心要点总结** - `signal` 的**参数**:信号编号 + 新处理函数的指针。 - `signal` 的**返回值**:旧处理函数的指针(同类型)。 - **设计目的**:允许动态修改信号行为,并支持恢复原有处理逻辑[^1][^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值