微信分销商城平台系统/宁波seo自然优化技术
文章目录
- 进程信号
- 信号的产生方式(信号产生前)
- 1. 硬件产生
- 2.调用系统函数向进程发信号
- 3.软件产生
- 4.定位进程崩溃的代码(进程异常退出产生信号)
- 信号保存的方式(信号产生中)
- 获取pending表&&修改block表
- 信号的处理(信号处理时)
- 修改handler函数补充—— sigaction 系统调用
进程信号
信号在生活中无处不在,例如闹钟、红绿灯,快递到达发的短信等等
-
信号的例子
例如在网上你买了一个东西就是信号的注册;
快递员该你打电话要你拿一下快递,就是给你发送了一个信号;
你收到信号之后,你知道怎么去处理这个信号,在这里就是去拿快递;
但是你也不一定立马去拿,你可能会等你忙完现在的事在去处理;
从技术的角度说,平时电脑上按
alt+f4
就是一种信号,它会关闭当前的窗口,Linux中ctrl+c
可以终止进程
信号是进程之间事件异步通知的一种方式,属于软中断
- 信号的种类
信号的种类可以通过
kill -l
命令来查看 Linux 系统的信号列表:
如图,其中1-31是普通信号,也是我们在这里要重点学习的信号;34-64是实时信号
信号的产生方式(信号产生前)
1. 硬件产生
例如当我们的程序发生死循环时,按下CTRL+C
,就可以终止进程。CTRL+C
的本质其实是向进程发送2号信号SIGINT,而SIGINT的默认处理动作是从键盘中断(man 7 signal查看手册)
那我们可以把信号2的默认处理动作更换成我们自己的验证一下,这里需要用到
sighandler_t signal(int signum, sighandler_t handler);
这里我让收到2号信号后先打印一段话再退出
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>void mysignal_2(int signo)
{printf("你好signo:%d\n",signo);exit(1);
}int main()
{signal(2,mysignal_2); //相当于一个函数指针,这里填的函数名相当于函数地址while(1){printf("hello\n");sleep(1);}return 0;
}
可以看到运行结果:按下ctrl+c后,先打印再终止进程
例如野指针问题引起的段错误,只要运行程序就会崩溃,那么下面验证一下
先把1-31的信号处理动作全部换成自定义,之后捕捉信号查看野指针对应的信号
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>void handler(int signo)
{printf("signo:%d\n",signo);exit(1);
}int main()
{int sig = 1;for(;sig <= 31; sig++){signal(sig,handler);}while(1){int* p = NULL;*p = 100;printf("hello\n");sleep(1);}return 0;
}
运行结果可知:野指针引起的进程崩溃是收到了11信号
再例如除0错误,把上面的代码修改一下进行验证:
while(1){int i= 0;i /= 0;printf("hello\n");sleep(1);}
可以看到,进程也是直接就崩溃了,是因为收到了8号信号
上述提到的ctrl+c、除0错误、野指针的段错误为什么会产生退出信号呢?
首先要知道,os是硬件的管理者,负责管理好硬件资源监视硬件状态等等,
- ctrl+c是由键盘发出的,os检测到键盘发出的信号就会向进程发出2号信号;
- 除0时是在CPU中运算的,CPU中有一些寄存器,会记录运行的状态,除0时的运算会产生异常寄存器就会记录这个状态,OS检测到这个异常状态,就会向进程发送8号信号;
- 当我们对空指针进行直接赋值时,OS会发现我们的进程地址空间与物理内存的映射对不上,就会向进程发送11号信号
综上这些错误分别体现在键盘、CPU、内存中,所以软件层面上的错误。
也就是体现在硬件或其他软件上,而OS是硬件的管理者,发现硬件出现一些异常,就会反馈回出现异常的代码所在的进程上,进程根据对应的信号进行处理(上面提到的错误都是默认终止进程)
2.调用系统函数向进程发信号
系统提供了一些函数接口,可以手动发送信号
int kill(pid_t pid, int signo);
- pid 表示目标进程的 pid 。
- sig 表示要发给目标进程的信号。
- 成功返回0,失败返回-1
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>static void Usage(const char* proc)
{printf("Usage:\n\t %s signo who \n",proc);
}int main(int argc ,char* argv[])
{if(argc != 3){Usage(argv[0]);return 1;}int signo = atoi(argv[1]);int who = atoi(argv[2]);kill(who,signo);printf("signo:%d who:%d\n",signo,who);return 0;
}
-
int raise(int signo);
给自己发送信号,signo:要发送的信号
int main() {sleep(1);raise(3);return 0; }
-
void abort(void);
给自己发送固定的信号6
3.软件产生
unsigned int alarm(unsigned int seconds);
- seconds是代表几秒后执行
- 执行成功返回0,取消定时则返回剩余秒数
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
int main()
{alarm(30);sleep(4);int ret = alarm(0); //取消定时printf("%d\n",ret);return 0;
}
还有管道通信时,写端退出,读端收到13号信号等等
4.定位进程崩溃的代码(进程异常退出产生信号)
进程退出一般分为三种:
- 代码运行完毕,结果正常
- 代码运行完毕,结果不正常
- 代码异常直接终止
如图:退出码+退出信号+core dump标志位共16位,waitpid函数接口的第二个参数可以获取到并查看退出码和退出信号
(具体用法自行搜索,这里不做介绍)
在Linux中是可以定位到具体崩溃到哪一行的代码的,当一个进程退出的时候它会根据退出的情况置退出码或者退出信号,表明退出的原因,如果必要,OS会设置退出信息中的core dump标志位,并将进程在内存中的数据转存到磁盘上,以便后期调试
云服务器上的core dump是默认关闭的,可以输入 ulimit -c 102400
打开core dump,ulimit -a
可以查看
这里用除0错误演示一下:
int main()
{while(1){int i = 0;i /= 0;printf("hello\n");sleep(1);}return 0;
}
在我们运行程序后,除了显示退出原因,还会显示(core dumped),并且多了一个 core.5195文件,之后利用gdp调试打开我的可执行程序(Linux默认是release版本的,需要在编译时加上 -g)
之后输入core-file core.5195
加载 core.5195文件
也不是所有信号都会core dump,例如死循环使用ctrl+c的2号信号,或者 kill -9杀掉进程就没有core dump
总结:信号产生的方式有很多,但本质都是由OS向目标进程发送的
信号保存的方式(信号产生中)
在Linux中,一个进程收到信号也不一定马上处理,待当前工作处理完成后寻找合适时机处理,那就需要PCB有保存信号的能力
先来解释三个名词:
实际执行信号的处理动作称为信号递达(delivery)
信号递达可以是自定义捕捉、默认、忽略
信号从产生到递达之间的状态称为信号未决(Pending)
本质是这个信号因为一些原因(如优先级低等)被暂时存在了task_struct中
进程可以选择阻塞某个信号(block)
本质是OS允许进程暂时屏蔽指定的信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
在进程的task_struct内有三张表,是用来保存信号状态的
pending 表和 block 表都是位图,而 handler 表是一个函数指针数组,指向的是信号递达时对应的处理动作(默认/忽略/自定义)
pending和block位图的比特位的位置代表信号的编号
pending位图的内容代表是否收到信号
block位图内容代表是否被阻塞(阻塞位图也叫信号屏蔽字)
信号处理过程示例:
只要block为1,那么pending不管是1还是0,该信号都是阻塞状态
有了这三张表,进程就可以识别信号了
因为task_struct是内核的,没有人能写入到内核,只有OS可以,所以信号的产生方式都是由OS统一向PCB发送的,
(不是所有信号都会被屏蔽,例如9号信号)
获取pending表&&修改block表
pending表和block表的位图结构,就是由这个sigset_t的类型来存储的。sigset_t是一个位图结构类型,称为信号集;这个类型可以表示每个信号的有效或无效状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决定状态。
由于每个平台的sigset_t信号集实现位图的方法不一定一样,所以不推荐用户对这个信号集直接进行修改信号集,需要通过OS提供的信号集操作函数进行修改。
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
int sigemptyset(sigset_t *set):
初始化set所指向的信号集,使其中
所有信号的对应bit清零
,表示该信号集不包含任何有效信号成功返回0,失败返回-1
int sigfillset(sigset_t *set):
初始化 set ,将其中所有信号对应的比特位置 1 。
成功返回0,失败返回-1
int sigaddset (sigset_t *set, int signo)
把signo信号添加到 set 集合里,其实就是把signo信号对应的比特位由 0 置 1
成功返回0,失败返回-1
int sigdelset(sigset_t *set, int signo)
把 signo 信号从 set 集合里删去,其实就是把 signo 信号对应的比特位由 1 置 0
成功返回0,失败返回-1
int sigismember(const sigset_t *set, int signo)
判定 signo 信号是否在 set 集合里,其实就是判定 signo 信号对应的比特位是否为 1
包含就返回1,不包含返回0,失败返回-1
有了上面的信号集操作函数后,想要修改block表就需要用到sigprocmask 系统调用
sigprocmask
:获取或更改当前进程的信号屏蔽字
how:表示要对信号屏蔽字进行的操作类型
SIG_BLOCK:set 包含了我们希望添加到当前信号屏蔽字的信号。 (mask = mask | set )
SIG_UNBLOCK:set 包含了我们希望从当前信号屏蔽字中解除阻塞的信号。 (mask = mask & ~set)
SIG_SETMASK:设置当前信号屏蔽字为 set 。 (mask = set) 常用set:输入型参数,就是我们要设置的信号集的指针
oldset:输出型参数,用来获取原信号集(可设置NULL)
返回值:成功返回0,失败返回-1
而想要获取pending表就要用到sigpending 系统调用
- 参数:传出型参数,用于存储进程pending表
- 返回值:成功返回0,失败返回-1
示例:阻塞2号信号
void test1()
{sigset_t iset,oset;//1.先清空sigemptyset(&iset);sigemptyset(&oset);//2.给iset指定信号编号sigaddset(&iset,2);//3.设置屏蔽字sigprocmask(SIG_SETMASK,&iset,&oset);while(1){printf("hello\n");sleep(1);}}
这时按ctrl+c是无效的,只能通过kill -9来结束进程
示例:先阻塞2号信号,后获取pending表并打印,20秒后放开2号信号
void test2()
{//设置信号集sigset_t iset,oset;sigemptyset(&iset);sigemptyset(&oset);//初始化信号集sigaddset(&iset,2);sigprocmask(SIG_SETMASK,&iset,&oset); //阻塞sigset_t pending;//用于获取打印用int count = 0;while(1){if(count == 20)sigprocmask(SIG_SETMASK,&oset,NULL); //解除2号的阻塞(更改阻塞的信号集为oset,oset为全0)sigemptyset(&pending); //先清空sigpending(&pending); //在获取sleep(1); //延时1scount++; //计数show_pending(&pending); //打印}
}
20s后,2号被取消阻塞,信号递达,进程退出
综上步骤:
想要获取pending表或者更改block表的必备步骤是:
- 设置信号集
- 初始化信号集
信号的处理(信号处理时)
每个进程都有自己的地址空间和对应的页表,地址空间一般一共4G,3G的用户空间,而剩下的1G空间是内核空间,存储的是OS的数据和代码,且内核空间也有对应的内核页表映射到物理内存上,但是内核页表不管有多少个进程,都只共享一份;保证无论进程怎么切换,都能够找到同一个OS
进程是可以看到内核和用户的内容的,但是不一定能够访问,需要有权限来证明进程处于哪种工作模式,在进程里面是有对应的相关数据来标识进程的工作模式的(用户模式 / 内核模式)
- 想要访问内核数据,就需要切换至内核态
- 想要访问用户数据,就需要切换至用户态
这个数据会被加载到 CPU 的其中一个寄存器当中(CR3),用于保存当前进程是处于用户态还是处于内核态。
- 内核态:执行 OS 的代码和数据时所处的状态。OS 的代码的执行全部都是在内核态。
- 用户态:用户的代码和数据被访问或执行时所处的状态。也就是说我们写的代码全部都是在用户态执行的。
而系统调用就是:将进程切换至内核态去调用系统函数
信号是如何处理的?
信号是被保存在PCB中的pending位图里面,处理的工作分为检测、递达(默认、忽略、自定义),当进程从内核态返回到用户态的时候,进行信号的处理工作
处理的流程大致如下图所示:
抽象图:
为什么不能由OS直接执行捕捉函数方法?
要知道OS是不相信任何人的,handler方法是用户定义的,内核态只能执行OS的代码数据,所以首先再身份上就不合适。其次因为OS只相信自己,也就是只相信自己的代码和数据是安全的,假如handler的函数方法定义一些恶意代码,那么OS等于以它的权限去执行,造成安全隐患。所以OS要保护好自己
什么时候处理信号?从内核态切换回用户态的时候进行信号检测并处理
**当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字。**这样就保证了在处理某个信号时,如果这种信号再次产生,那么它(第二次)会被阻塞到当前处理结束为止
在 Linux 中,如果把进程的某一个普通信号屏蔽了,然后 OS 给这个进程多次发送该信号,该进程只能记住一次,因为记录信号的标记位只有一个比特位。也就是说,在 Linux 中,普通信号是可能会被丢失的。而实时信号不会被丢失,因为内核是以链表队列的形式把所有的实时信号组织起来的,来一个就链一个。(数据结构的差别)
修改handler函数补充—— sigaction 系统调用
和signal函数的功能是一样的,都是修改handler表中的方法
- signum 表示要设置的信号
- act 输入型参数,需填充该结构体里面 signum 的对应处理动作。
- oldact 输出型参数,若为非空,则带回内含老的处理动作的结构体;若不关心,则可设为 NULL 。
- 成功返回0,失败返回-1
与信号集sigset_t相关的操作类似
- sa_handler 代表 signum 的对应处理动作
- sa_mask 代表在调用信号处理函数时需要额外屏蔽的信号
- sa_flags 代表选项,我们在这里设为 0
- sa_sigaction 和 sa_restorer 通常与实时信号相关联,我们在这里不关心
如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用 struct sigaction
结构体中的 sa_mask 字段说明这些需要额外屏蔽的信号(mask是信号集,可以用信号集操作函数)
相关文章:

Linux操作系统学习(信号处理)
文章目录进程信号信号的产生方式(信号产生前)1. 硬件产生2.调用系统函数向进程发信号3.软件产生4.定位进程崩溃的代码(进程异常退出产生信号)信号保存的方式(信号产生中)获取pending表&&修改block表…...

CopyOnWriteArrayList 源码解读
一、CopyOnWriteArrayList 源码解读 在 JUC 中,对于 ArrayList 的线程安全用法,比较推崇于使用 CopyOnWriteArrayList ,那 CopyOnWriteArrayList是怎么解决线程安全问题的呢,本文带领大家一起解读下 CopyOnWriteArrayList 的源码…...

方法
方法方法(函数)一、课前问答二、方法和函数三、方法的参数3.1 单个参数3.2 多个参数四、方法的返回值五、方法的多级调用六、递归方法(函数) 一、课前问答 1、break和continue的区别 2、嵌套循环的执行流程 3、二进制有哪些运算&…...

C/C++实现发送邮件功能(附源码)
C++常用功能源码系列 本文是C/C++常用功能代码封装专栏的导航贴。部分来源于实战项目中的部分功能提炼,希望能够达到你在自己的项目中拿来就用的效果,这样更好的服务于工作实践。 专栏介绍:专栏讲本人近10年后端开发常用的案例,以高质量的代码提取出来,并对其进行了介绍。…...

Java虚拟机JVM-运行时数据区域说明
及时编译器 HotSpot虚拟机中含有两个即时编译器,分别是编译耗时短但输出代码优化程度较低的客户端编译器(简称为C1)以及编译耗时长但输出代码优化质量也更高的服务端编译器(简称为C2),通常它们会在分层编译…...

修复电子管
年前在咸鱼捡漏买到了10根1G4G电子管,这是一种直热三极管,非常的少见。买回来的时候所有的灯丝都是通的,卖家说都是新的,库存货,但是外观实在是太糟糕了,看着就像被埋在垃圾场埋了几十年的那种,…...

【Java】反射机制和代理机制
目录一、反射1. 反射概念2. 反射的应用场景3. 反射机制的优缺点4. 反射实战获取 Class 对象的四种方式二、代理机制1. 代理模式2. 静态代理3. 动态代理3.1 JDK动态代理机制1. 介绍2.JDK 动态代理类使用步骤3. 代码示例3.2 CGLIB 动态代理机制1.介绍2.CGLIB 动态代理类使用步骤3…...

synchronized底层
Monitor概念一、Java对象头二、Monitor2.1、Monitor—工作原理2.2、Monitor工作原理—字节码角度2.2、synchronized进阶原理(优化)2.3、synchronized优化原理——轻量级锁2.4、synchronized优化原理——锁膨胀2.5、synchronized优化原理——自旋优化2.6、…...

数据结构:复杂度的练习(笔记)
数据结构:复杂度的练习(笔记) 例题一: 可以先给数组排序,然后再创建一个i值,让他循环一次一次,遍历这个排序后的数组,但如果用qsort函数进行排序,时间复杂度就和题目要求…...

JAVA练习69- 从前序与中序遍历序列构造二叉树
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 前言 提示:这里可以添加本文要记录的大概内容: 3月5日练习内容 提示:以下是本篇文章正文内容,下面案例可供参考 一、题目-从…...

brew安装问题
最近使用mac安装了Python和PyCharm,使用python中的绘制图像的turtle库后,执行报错: import _tkinter # If this fails your Python may not be configured for Tk ModuleNotFoundError: No module named _tkinter 查询后需在mac 命令行执行&…...

【数据挖掘与商务智能决策】第一章 数据分析与三重工具
numpy基础 numpy与数组 import numpy as np # 用np代替numpy,让代码更简洁 a [1, 2, 3, 4] # 创建列表a b np.array([1, 2, 3, 4]) #从列表ach print(a) print(b) print(type(a)) #打印a类型 print(type(b)) #打印b类型[1, 2, 3, 4] [1 2 3 4] <class ‘list’>…...

计算机底层:BDC码
计算机底层:BDC码 BDC码的作用: 人类喜欢十进制,而机器适合二进制,因此当机器要翻译二进制给人看时,就会进行二进制和十进制的转换,而常规的转换法(k*位权)太麻烦。因此就出现了不同…...

【C++】平衡二叉搜索(AVL)树的模拟实现
一、 AVL树的概念 map、multimap、set、multiset 在其文档介绍中可以发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树…...

[2019红帽杯]childRE
题目下载:下载 参考:re学习笔记(24)BUUCTF-re-[2019红帽杯]childRE_Forgo7ten的博客-CSDN博客 这道题涉及到c函数的修饰规则,按照规则来看应该是比较容易理解的。上面博客中有总结规则,可以学习一下。 载…...

2D图像处理:九点标定_下(机械手轴线与法兰轴线不重合)(附源码)
文章目录 2. 机械手轴线与法兰轴线不重合2.1 两次拍照避免标定旋转中心2.2 旋转中心标定2.3 非标定中心的方法2.3.1 预备内容-点坐标旋转计算2.3.2 工件存在平移和旋转3. 代码(待更新)上一篇:2D图像处理:九点标定_上(机械手轴线与法兰轴线重合)(附源码) 2. 机械手轴线…...

【二分查找】分巧克力、机器人跳跃、数的范围
Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…...

Hyperf使用RabbitMQ消息队列
Hyperf连接使用RabbitMQ消息中间件 传送门 使用Docker部署RabbitMQ,->传送门<使用Docker部署Hyperf,->传送门-< 部署环境 安装amqp扩展 composer require hyperf/amqp安装command命令行扩展 composer require hyperf/command配置参数 假…...

【Linux】P3 用户与用户组
用户与用户组root 超级管理员设置超级管理员密码切换到超级管理员sudo 临时使用超级权限用户与用户组用户组管理用户管理getentroot 超级管理员 设置超级管理员密码 登陆后不会自动开启 root 访问权限,需要首先执行如下步骤设定 root 超级管理员密码 1、解除 roo…...

Spring核心模块——Aware接口
Aware接口前言基本内容例子结尾前言 Spring的依赖注入最大亮点是所有的Bean对Spring容器对存在都是没有意识到,Spring容器中的Bean的耦合度是很低的,我们可以将Spring容器很容易换成其他的容器。 但是实际开发的时候,我们经常要用到Spring容…...

Linux网络编程 第六天
目录 学习目标 libevent介绍 libevent的安装 libevent库的使用 libevent的使用 libevent的地基-event_base 等待事件产生-循环等待event_loop 使用libevent库的步骤: 事件驱动-event 编写一个基于event实现的tcp服务器: 自带buffer的事件-buff…...

STM32开发(六)STM32F103 通信 —— RS485 Modbus通信编程详解
文章目录一、基础知识点二、开发环境三、STM32CubeMX相关配置1、STM32CubeMX基本配置2、STM32CubeMX RS485 相关配置四、Vscode代码讲解五、结果演示以及报文解析一、基础知识点 了解 RS485 Modbus协议技术 。本实验是基于STM32F103开发 实现 通过RS-485实现modbus协议。 准备…...

AcWing1049.大盗阿福题解
前言如果想看状态机的详解,点机这里:dp模型——状态机模型C详解1049. 大盗阿福阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。这条街上一共有 N家店铺,每家店中都有一些现金。阿福事先调查得知,只有当…...

python日志模块,loggin模块
python日志模块,loggin模块loggin模块日志的格式处理器种类日志格式的参数使用loggin模块 logging库采用模块化方法,并提供了几类组件:记录器,处理程序,过滤器和格式化程序。 记录器(Logger)&a…...

接口自动化入门-TestNg
目录1.TestNg介绍2、TestNG安装3、TestNG使用3.1 编写测试用例脚本3.2 创建TestNG.xml文件(1)创建testng.xml文件(2)修改testng.xml4、测试报告生成1.TestNg介绍 TestNg是Java中开源的自动化测试框架,灵感来源于Junit…...

Spring AOP —— 详解、实现原理、简单demo
目录 一、Spring AOP 是什么? 二、学习AOP 有什么作用? 三、AOP 的组成 3.1、切面(Aspect) 3.2、切点(Pointcut) 3.3、通知(Advice) 3.4、连接点 四、实现 Spring AOP 一个简…...

(蓝桥真题)异或数列(博弈)
题目链接:P8743 [蓝桥杯 2021 省 A] 异或数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 样例输入: 4 1 1 1 0 2 2 1 7 992438 1006399 781139 985280 4729 872779 563580 样例输出: 1 0 1 1 分析:容易想到对于异或最大值…...

4万字数字政府建设总体规划方案WORD
本资料来源公开网络,仅供个人学习,请勿商用。部分资料内容: 我省“数字政府”架构 (一) 总体架构。 “数字政府”总体架构包括管理架构、业务架构、技术架构。其中,管理架构体现“管运分离”的建设运营模式…...

CCF/CSP 201709-2公共钥匙盒100分
试题编号:201709-2试题名称:公共钥匙盒时间限制:1.0s内存限制:256.0MB问题描述:问题描述 有一个学校的老师共用N个教室,按照规定,所有的钥匙都必须放在公共钥匙盒里,老师不能带钥…...

【OC】Blocks模式
1. Block语法 Block语法完整形式如下: ^void (int event) {printf("buttonId:%d event%d\n", i, event); }完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同。 没有函数名。带有“^”(插入记号)。 因为O…...