复杂数据类型(signal)的解读-C语言基础
这一篇文章要探讨的是C语言中复杂数据类型的解读。涉及到signal()函数数据类型的解读(并不解释signal()的作用)以及对于数据类型的理解,属于C语言基础篇。
在开始解读signal()这种复杂类型之前,先给大家分享一个技巧。我老师曾经教过我。如果你想知道一个变量的数据类型,最简单的方法就是找到这个变量的定义处,然后把变量名去掉,剩下的就是这个变量的数据类型了。例如数组a的定义是int a[5]那么把变量名“a”去掉,剩下的int[5]便就是变量a的数据类型了。
说到这,可能有人立刻就定义了一个int[5] a;来验证一下int[5]这个数据类型是否真的存在,然后发现居然在编译的时候就给报错拦下来了,现在可能正拿着39米长的大刀在找博主来的路上了。但你能不能先在40米那里等会儿,容我狡辩一下。
确实,我得承认你直接定义一个int[5] a;是会连编译都过不了。但这并不是我的错啊!这不过是C语言语法规定导致的问题而已。C语言在定义变量的时候,规定把对于这个变量的描述关键字放在变量名的左边,把和数组相关的描述放在变量名的右边。至于为什么要这么规定,愚不才,答不上来。
不过
int[5] a;在Java里面其实是可以编译通过的。事实上甚至有很大一部分人推崇用int[5] a;来定义数组比用int a[5];来定义数组更加合适。
因为前者能更加明确的表明变量a的数据类型。为什么在Java里面可以这么定义数组而在C语言里面不行呢?我估摸着应该是由于数组这个类型本身的特殊性导致的。首先C语言是一门很老的语言,C语言之父丹尼斯·里奇在创造C语言的时候可能并没有把数组当做是一个类型来看待。什么是数组?数组其实就是同一种数据类型数据的组合。所以在C语言中,数组的定义是int a[5];这样的,数组标示符[5]并没有参与到数组名a的描述中去。
但是这里有一个问题,数组本身也应该是一个数据类型啊,所以你定义一个数组的时候,数组标识符[]即应该是脱离数组名的数据类型而对原数据类型组合的描述,又应该是参与到数组名本身数据类型的描述中去作为"数组名数据类型"的一部分。所以在后面Java之父詹姆斯·高斯林创造Java的时候,就考虑到了这个问题,便从
int a[5]的定义语法中引申出int[5] a的语法。因为这样或许能更合理的描述数组a是个什么东西。好了,说了这么多,这也只是我极端个人主义的猜想而已,并没有去深究。
但是,虽然在C语言中你并不能直接用int[5]这个数据类型来定义一个数组,但是这也并不能代表int[5]在C语言中就不是一个数据类型了。
我们来看一下下面的代码:
#include <stdio.h>
int main(void)
{
int a[5] = {0};
printf("(a):sizeof[%lu]\n", sizeof(a));
printf("(int[5]):sizeof[%lu]\n", sizeof(int[5]));
return 0;
}
这篇代码的运行结果是这样子的:

在C语言中,虽然我们不能直接用int[5]来定义一个数组变量,但是sizeof运算符却能够识别int[5]这个数据类型,而且用sizeof计算int[5]这个数据类型出来的值和直接求这个数组变量名得出来的值是一样的。这其实已经间接说明了int[5]其实就是数组变量a的数据类型了。这就和你定义了一个int a变量,然后用sizeof来求int和用sizeof来求a得到的结果是一样的一个道理。
事实上,如果你不知道一长串关键字拼接在一起的东西是不是一个合法数据类型,那么你完全可以把它们扔到sizeof运算符里面,如果编译不报错的话,那么多半就是一个数据类型了。
例如我们待会要分析的信号处理函数的原型很复杂像这样:
void(*signal(int sig,void(*func)(int)))(int)
按照规律,我们找到定义处把左边起第一个非关键字的单词去掉,那么剩下的便是它的数据类型了。
void(*(int sig,void(*func)(int)))(int)
很复杂的一个数据类型,不过不管它再复杂,它最根本的属性不过是一个函数类型而已。
验证代码:
#include <stdio.h>
void f(void)
{
}
int main(void)
{
printf("signal类型大小为[%lu]\n",
sizeof(void(*(int sig,void(*func)(int)))(int)));
printf("f类型大小为[%lu]\n", sizeof(f));
return 0;
}
运行结果:

嗯,类型再复杂本质都是一样的。
好了,说了那么多,我们是时候该来解读一下signal()这个类型到底是个什么样子了,我们又是如何看出来它是一个函数的。
void(*signal(int sig,void(*func)(int)))(int)
按照惯例,我们得先找到这个变量名字,这么长一串东西里面,那个单词才是这个变量的名字呢?按照规定,从左边看起,第一个非关键字signal,即是这个变量的名字。那么周围这一整串东西其实都是在描述它的,都是它的数据类型。我们就从这个变量名开始,向左右两边扩展的看。变量名的左边是一个*号,右边是一个()号。
最靠近变量名的运算符:*signal()
这里有些人可能就不理解了,右边明明只有一个“(”号而已,那里冒出来了个“)”号了?但是啊,()号这个东西就是整体出现的啊,有时候当你看到表达式里面出现()号的时候,你甚至可以不用管()号里面是什么,而直接看)号后面的东西,丝毫不影响你理解整个表达式。
说了那么多,其实就是让你直接把:
void(*signal(int sig,void(*func)(int)))(int)
看成:
void(*signal())(int)
好了,找到了最靠近变量名的两个运算符了,那么接下来就要看优先级了,这直接决定了signal是个什么。查阅手册可知,()号的优先级是大于*号的,所以signal会先和()号匹配,于是signal首先是一个函数。知道了signal是个函数之后那就好办了,定义一个函数都需要提供什么?嗯,参数和返回值。所以现在我们只要找到signal的参数和返回值就可以了。
函数名后面的()里面的内容便是它的参数。所以我们刚刚忽略掉的()里面的内容就是这个函数的参数了。
参数是:(int sig, void(*func)(int))
一个整形变量:int sig
一个函数指针:void(*func)(int)
在这里别给我整个举一反三说func左右两边是*号和()号所以func它也是一个函数啊!
在这里func只有左边没有右边,因为func和*号是在()里面的,而()号里面就只有*func,func只能是和*号结合,所以func是一个指针。这个指针指向什么数据类型呢?在定义处把指针变量名func和指针符号*去掉,剩下的就是这个指针指向的数据类型了。
指针指向的数据类型:void ()(int)
()号里面什么都没有,相当于这个()不存在。
最终指针指向的数据类型:void (int) 一个函数
相当于函数void f(int a)去掉变量名f后的数据类型。所以func是一个函数指针,它指向函数f这种类型的函数。
既然函数signal的参数找到了,那么把函数名signal和它的参数去掉,剩下的就是它的返回值了。
原型:
void(*signal(int sig,void(*func)(int)))(int)
去掉函数名和函数的参数之后:
返回值是:void(*)(int)
诶,有点眼熟。。。
刚刚我们分析的signal的第二个参数是什么来着?
第二个参数: void (*func)(int)
要分析的返回值:void (*)(int)
嗯,没错,signal的返回值就是func的类型。也是一个函数指针。而且,这两个东西对于signal来说其实是一样的。
如果在仅仅只讨论signal数据类型的时候
void(*signal(int sig,void(*func)(int)))(int)
其实就是
void(signal(int,void()(int)))(int)
这两句在描述signal的数据类型的时候是等价的。因为数据类型本身就不包含对于非关键字的描述。
所以,最后,我们总算是分析完signal的数据类型了。
它首先是一个函数:
signal()
然后它的参数有两个,一个是整形变量,一个是指针,该指针指向的是void (int)这种类型的函数。
signal(int sig,void(*func)(int))
最后它有一个返回值,返回一个函数指针,这个指针也指向void (int)这种类型的函数。
void(*signal(int sig,void(*func)(int)))(int)
好了,至此,整篇文章算是完了。最后说点题外话。
写在后面:
已经很久没有发博客了,最近一直很忙。之前就有朋友和我反馈说我写的文章过长,嗯,认真思考过,这是个问题。我写的文章往往都涉及过于广。常常在写一个知识点的时候,又引申出一些别的知识点。然后全部都给写出来。很容易让人抓不住重点。其实我在写的时候,是有很认真的规划文章的脉络的,我也有信心在把读者的思维发散到其他知识点之后能够再把读者的思绪给拉回来。很多时候一段句子我会改很久很久,就是为了能让文章的脉络清晰易懂。而且我写博客的时候,也习惯于用口头语来书写,虽然口头语没有书面语那么整洁,会造成文章过长。但却会有很好的通俗易懂的效果。我更愿意把博客写成一篇像是在聊天在说故事的文章而不想把博客写成教科书式的死板。我更愿意告诉别人为什么而不是告诉别人是什么。但是过长的文章确实不适合当代的快餐式阅读。后面我会尽量把一篇原本很长的文章拆分开来,作为几篇文章来发。
原博客始发于CSDN,在如今博客界的转载抄袭泛滥的环境下,原创不易,点个赞再走呗。以下是博客首页的链接。
复杂数据类型(signal)的解读-C语言基础的更多相关文章
- 语言基础:C#输入输出与数据类型及其转换
今天学习了C#的定义及特点,Visual Studio.Net的集成开发环境和C#语言基础. C#语言基础资料——输入输出与数据类型及其转换 函数的四要素:名称,输入,输出,加工 输出 Console ...
- Go语言基础之数据类型
Go语言基础之数据类型 Go语言中有丰富的数据类型,除了基本的整型.浮点型.布尔型.字符串外,还有数组.切片.结构体.函数.map.通道(channel)等.Go 语言的基本类型和其他语言大同小异. ...
- JavaScript 引入方式 语言规范 语言基础 数据类型 常用方法 数组 if_else 比较运算符 for while 函数 函数的全局变量和局部变量 {Javascript学习}
Javascript学习 JavaScript概述 ECMAScript和JavaScript的关系 1996年11月,JavaScript的创造者--Netscape公司,决定将JavaScript ...
- C#-语言基础+数据类型+运算符
一.C#语言基础 新建项目:文件→新建→项目→Visual C#(默认.NET Framework 4.5)→控制台应用程序 1.项目结构 (1)项目后缀 .config ——配置文件(存放配置参数文 ...
- C语言基础知识-数据类型
C语言基础知识-数据类型 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.常量与变量 1>.关键字 C的关键字共有32个. >.数据类型关键字(12个) char,s ...
- Python语言基础-语法特点、保留字与标识符、变量、基本数据类型、运算符、基本输入输出、Python2.X与Python3.X区别
Python语言基础 1.Python语法特点 注释: 单行注释:# #注释单行注释分为两种情况,例:第一种#用于计算bim数值bim=weight/(height*height)第二种:bim=we ...
- Java 语言基础 (初识Java语言, 变量和数据类型, 运算符, 流程控制语句, 数组)
初始 Java 语言 Java SE -- Java Platform, Standard Edition 是 Java 平台的基础 Java SE 以前称为 J2SE, 可以编写桌面应用和基于 we ...
- Python语言基础与应用 (P16)上机练习:基本数据类型
本文是笔者在学习MOOC课程<Python语言基础与应用> (北京大学-陈斌)中根据上机课时的要求写下在代码 课程总链接: 中国大学MOOC B站 本节课链接 数值基本运算: 33和7+, ...
- Swift语言指南(一)--语言基础之常量和变量
原文:Swift语言指南(一)--语言基础之常量和变量 Swift 是开发 iOS 及 OS X 应用的一门新编程语言,然而,它的开发体验与 C 或 Objective-C 有很多相似之处. Swif ...
- GO学习-(19) Go语言基础之网络编程
Go语言基础之网络编程 现在我们几乎每天都在使用互联网,我们前面已经学习了如何编写Go语言程序,但是如何才能让我们的程序通过网络互相通信呢?本章我们就一起来学习下Go语言中的网络编程. 关于网络编程其 ...
随机推荐
- RestTemplate 连接池最大链接数
原文链接:https://www.cnblogs.com/x-x-736880382/p/11591906.html 以前我们项目都是基于Apache HttpClient 连接池进行web 接口调用 ...
- EF Core如何到回滚上一次迁移
update-database 上上次迁移记录 让数据库回滚 remove-migration 删除本次有误的迁移文件 修改完毕后 add-migration updata-database 完成
- Day 11 11.2 文件操作
文件操作 引言 到目前为止,我们做的一切操作,都是在内存里进行的,这样会有什么问题吗?如果一旦断电或发生意外关机了,那么你辛勤的工作成果将瞬间消失.是不是感觉事还挺大的呢?现在你是否感觉你的编程技巧还 ...
- C - Watchmen CodeForces - 651C (使用map例题)
#include<iostream>#include<map> using namespace std;map<int,int> x;map<int,int& ...
- drf从入门到飞升仙界 02
restful规范 # restful是一种定义web API接口的设计风格,适用于前后端分离的应用模式中 # 关于restful的10个规范 -1.数据的安全保障,通常使用https协议(http+ ...
- Go语言互斥锁(sync.Mutex)和读写互斥锁(sync.RWMutex)
暴力锁 package main import ( "fmt" "sync" "time" ) /* Go语言包中的 sync 包提供了两种 ...
- Python3之并发(五)---线程条件(Condition)和事件(Event)
一.线程条件Condition(条件变量) 依赖锁对象(Lock,RLock),锁对象可以通过参数传入获得,或者使用自动创建的默认锁对象当多个条件变量需要共享同一个锁时,建议传入锁对象 除了带有获取到 ...
- Linux基础第六章:逻辑卷的使用、扩容和磁盘配额
一.逻辑卷的使用及扩容 1.概念优点及注意事项 2.使用命令及基本格式 3.创建逻辑卷 ①创建物理卷 ②创建卷组 ③创建逻辑卷 ④格式化.挂载yk26逻辑卷在/mnt下并在逻辑卷yk26下创建文件a. ...
- NavicatPremium16破解!!!!!亲测可用!!!!!!!!!!!!!!!!!
前言 Navicat premium是一款数据库管理工具,是一个可多重连线资料库的管理工具,它可以让你以单一程式同时连线到 MySQL.SQLite.Oracle 及 PostgreSQL 资料库,让 ...
- Python 用exec来获取字符串所对应的字典
Python 用exec来获取字符串所对应的字典 Python exec 问题的提出 想要遍历两个结构相似的字典,但是不想采用字典内嵌套字典的方式,所以想要通过一个列表,该列表包含字典名称.也就是通过 ...