这一篇文章要探讨的是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,在如今博客界的转载抄袭泛滥的环境下,原创不易,点个赞再走呗。以下是博客首页的链接。


零BUG是原则性问题。

复杂数据类型(signal)的解读-C语言基础的更多相关文章

  1. 语言基础:C#输入输出与数据类型及其转换

    今天学习了C#的定义及特点,Visual Studio.Net的集成开发环境和C#语言基础. C#语言基础资料——输入输出与数据类型及其转换 函数的四要素:名称,输入,输出,加工 输出 Console ...

  2. Go语言基础之数据类型

    Go语言基础之数据类型 Go语言中有丰富的数据类型,除了基本的整型.浮点型.布尔型.字符串外,还有数组.切片.结构体.函数.map.通道(channel)等.Go 语言的基本类型和其他语言大同小异. ...

  3. JavaScript 引入方式 语言规范 语言基础 数据类型 常用方法 数组 if_else 比较运算符 for while 函数 函数的全局变量和局部变量 {Javascript学习}

    Javascript学习 JavaScript概述 ECMAScript和JavaScript的关系 1996年11月,JavaScript的创造者--Netscape公司,决定将JavaScript ...

  4. C#-语言基础+数据类型+运算符

    一.C#语言基础 新建项目:文件→新建→项目→Visual C#(默认.NET Framework 4.5)→控制台应用程序 1.项目结构 (1)项目后缀 .config ——配置文件(存放配置参数文 ...

  5. C语言基础知识-数据类型

    C语言基础知识-数据类型 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.常量与变量 1>.关键字 C的关键字共有32个. >.数据类型关键字(12个) char,s ...

  6. Python语言基础-语法特点、保留字与标识符、变量、基本数据类型、运算符、基本输入输出、Python2.X与Python3.X区别

    Python语言基础 1.Python语法特点 注释: 单行注释:# #注释单行注释分为两种情况,例:第一种#用于计算bim数值bim=weight/(height*height)第二种:bim=we ...

  7. Java 语言基础 (初识Java语言, 变量和数据类型, 运算符, 流程控制语句, 数组)

    初始 Java 语言 Java SE -- Java Platform, Standard Edition 是 Java 平台的基础 Java SE 以前称为 J2SE, 可以编写桌面应用和基于 we ...

  8. Python语言基础与应用 (P16)上机练习:基本数据类型

    本文是笔者在学习MOOC课程<Python语言基础与应用> (北京大学-陈斌)中根据上机课时的要求写下在代码 课程总链接: 中国大学MOOC B站 本节课链接 数值基本运算: 33和7+, ...

  9. Swift语言指南(一)--语言基础之常量和变量

    原文:Swift语言指南(一)--语言基础之常量和变量 Swift 是开发 iOS 及 OS X 应用的一门新编程语言,然而,它的开发体验与 C 或 Objective-C 有很多相似之处. Swif ...

  10. GO学习-(19) Go语言基础之网络编程

    Go语言基础之网络编程 现在我们几乎每天都在使用互联网,我们前面已经学习了如何编写Go语言程序,但是如何才能让我们的程序通过网络互相通信呢?本章我们就一起来学习下Go语言中的网络编程. 关于网络编程其 ...

随机推荐

  1. Jmeter - Config Tips

    Guideline: Jmeter.properties file should avoid to be modified , modified user.properties instead > ...

  2. nop调试-区域路由问题

    1.在修改nop代码时,web项目里有一个namecontroller,然后区域Areas里也有一个namecontroller, 然后跳转时报错,提示有多个匹配项. 2.查看区域添加路由步骤:两步即 ...

  3. linux 下安装部署redis

    安装: 1.获取redis资源   wget http://download.redis.io/releases/redis-4.0.8.tar.gz 2.解压   tar xzvf redis-4. ...

  4. 手把手XTTS_V4迁移

    最近公司Oracle升级,考虑到停机时间等综合因数,最终选择了xtts数据迁移方案. 为此我整理了一份操作手册,方便以后查阅. 关于xtts的介绍可以参见这篇文章: <XTTS,又一个值得你重视 ...

  5. springBoot中对mongodb添加2dsphere位置索引

    项目需求:最近有个需求,就是要根据坐标位置找出附近的车辆(车辆有对应的坐标).然后翻了翻百度,cv流一顿操作之后,大概整理出来了一段代码如下 //根据当前位置坐标,找出附近*米内的所有车辆BasicD ...

  6. mfcc vs fbank

    There is some debate in the community regarding the use of the DCT, instead of directly using the lo ...

  7. 解决GitHub网页githubusercontent地址无法访问问题

    问题 解决GitHub网页githubusercontent地址无法访问问题 解决方法: 参考链接:https://zhuanlan.zhihu.com/p/107691233 注意 安装有火绒的,可 ...

  8. C#使用SharpZipLib解压多文件的zip压缩文件数据流,保存到本地

    代码: public async Task<ReturnModel<List<string>>> UploadModel() { var task = new Ta ...

  9. bootstrap-select使用、relation-graph使用

    bootstrap-select 这里要实现的是带有搜索功能的select框, bootstrap 官网没有可以直接拿来用的.如下是官网给出的解释,带搜索功能的select需要自定义. 在网上找到了有 ...

  10. Android 系统完整的权限列表

    访问登记属性  android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限  获取错略位置  android.perm ...