指定初始化器的概念

C90 标准要求初始化程序中的元素以固定的顺序出现,与要初始化的数组或结构体中的元素顺序相同。但是在新标准 C99 中,增加了一个新的特性:指定初始化器。利用该特性可以初始化指定的数组或者结构体元素。

数组的指定初始化器

一维数组的指定初始化器

利用指定初始化器的特性,我们可以这样定义并初始化一个数组:

  1. int a[6] = {[4] = 10,[2] = 25};

上述的初始化就等同于如下方式:

  1. int a[6] = {0,0,25,0,10,0};

可以看到通过这种方式能够不按照顺序,且指定具体的元素进行初始化。

除了上述这样的用法,我们也能够初始化数组内一段范围内的用元素,比如这样:

  1. int a[5] = {[4] = 10,[0 ... 3] = 23};

上面这段程序的初始化也就等同于如下初始化:

  1. int a[5] = {23,23,23,23,10};

那如果数组初始化里有指定的元素初始化又有未指定的元素又是如何分析呢?比如这样:

  1. int a[5] = {11,[3] = 44,55,[1] = 22,33};

那它等同于下面的代码:

  1. int a[5] = {11,22,33,44,55};

如果定义数组时没有指定数组的大小,那么数组实际的大小又是多少呢?比如这样:

  1. int main(void)
  2. {
  3. int number[] = {[20] = 1,[10] = 8,9};
  4. int n = sizeof(number)/sizeof(number[0]);
  5. printf("The Value of n is:%d\n",n);
  6. }

输出结果是这样的:

  1. The Value of n is:21

也就是说,如果未给出数组的大小,则最大的初始化位置确定数组的大小

二维数组的指定初始化器

二维数组同样可以采用指定初始化器的方法,下面是一个二维数组的初始化:

  1. int array[2][2] =
  2. {
  3. [0] = {[0] = 11},
  4. [1] = {[1] = 22},
  5. };

这样的初始化也就等同于下述代码:

  1. int array1[2][2] =
  2. {
  3. {11,00},
  4. {00,22}
  5. };

通过上述代码,我们也可以知道,二维数组的指定初始化器的方法中,第一个[]里的数字表示的是初始化的二维数组的行数,而在{}内的则是对当前行的元素进行初始化,实际也就是说{}内的初始化方法也就和一维数组的一样了,一维数组可行的方法,二维数组也是可行的。

应用

在讲述了数组指定初始化器的基本概念之后,我们来看一个具体的例子,下面这个例子是基于状态机的编程方法实现的 ATM 机器,首先 ATM 具有如下几种状态;



我们就可以使用状态机的思路来编写这个程序,首先使用枚举的方式来定义各个状态和相应的操作:

  1. typedef enum
  2. {
  3. Idle_State,
  4. Card_Inserted_State,
  5. Pin_Entered_State,
  6. Option_Selected_State,
  7. Amount_Entered_State,
  8. last_State
  9. }eSysyemState;
  10. typedef enum
  11. {
  12. Card_Insert_Event,
  13. Pin_Enter_Event,
  14. Option_Selection_Event,
  15. Amount_Enter_Event,
  16. Amount_Dispatch_Event,
  17. last_Event
  18. }eSystemEvent;

然后是对应操作的具体实现:

  1. eSysyemState AmountDispatchHandler(void)
  2. {
  3. return Idle_State;
  4. }
  5. eSysyemState EnterAmountHandler(void)
  6. {
  7. return Amount_Entered_State;
  8. }
  9. eSysyemState OptionSelectionHandler(void)
  10. {
  11. return Option_Selected_State;
  12. }
  13. eSysyemState InsertCardHandle(void)
  14. {
  15. return Card_Inserted_State;
  16. }
  17. eSysyemState EnterPinHandler(void)
  18. {
  19. return Pin_Entered_State;
  20. }

为了使得状态机的实现看起来不是那么的冗长,我们这里采用查表的方式,首先重定义一个函数指针二维数组类型:

  1. typedef eSysyemState (* const afEventHandler[last_State][last_Event])(void);

简单说一个这是一个二维数组,二维数组里面存放的是函数指针,这个函数指针指向的是返回值为 eSysyemState,形参为 void 的函数。

在重定义了这个类型之后,我们就可以用其定义新的变量了,在这之前,补充一点数组相关的内容,比如有如下代码:

  1. typedef int array[3];
  2. array data;

那么上述代码也就等同于如下代码:

  1. int data[3];

有了上述代码之后,我们就可以实现我们的查找表了,具体代码如下:

  1. static afEventHandler StateMachine =
  2. {
  3. [Idle_State] = {[Card_Insert_Event] = InsertCardHandle},
  4. [Card_Inserted_State] = {[Pin_Enter_Event] = EnterPinHandler },
  5. [Pin_Entered_State] = {[Option_Selection_Event] = OptionSelectionHandler},
  6. [Option_Selected_State] = {[Amount_Entered_Event] = EnterAmountHandler},
  7. [Amount_Entered_State] = {[Amount_Dispatch_Event] = AmountDispatchHandler},
  8. };

现在再来看到这个初始化的方法也就比较清楚了,这实际上也就是一个二维数组使用指定初始化器解析的方法,最后,也就是我们的状态机运行代码:

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. eSysyemState eNextState = Idle_State;
  5. eSystemEvent eNewEvent;
  6. while(1)
  7. {
  8. eNewEvent = ReadEvent();
  9. /*省略相关判断*/
  10. eNextState = (*StateMachine[eNextState][eNewEvent])();
  11. }
  12. return 0;
  13. }

结构体的指定初始化器

定义了如下结构体:

  1. struct point
  2. {
  3. int x,y;
  4. }

那么对于结构体变量的初始化可以采用以下的方式:

  1. struct point p =
  2. {
  3. .y = 2,
  4. .x = 3
  5. };

上述代码也就等价于如下代码:

  1. struct point p = {3,2};

那这样的初始化有什么作用呢?下面是 linux 内核的一段代码:

  1. const struct file_operations eeprom_fops =
  2. {
  3. .llseek = eeprom_lseek,
  4. .read = eeprom_read,
  5. .write = eeprom_write,
  6. .open = eeprom_open,
  7. .release = eeprom_close
  8. };

上述就是通过指定初始化器的方法来进行初始化的,其中 file_operations 这个结构体中的成员有很多,上述初始化的成员只是其中一部分,

  1. struct file_operations {
  2. struct module *owner;
  3. loff_t (*llseek) (struct file *, loff_t, int);
  4. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  5. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  6. int (*open) (struct inode *, struct file *);
  7. int (*flush) (struct file *, fl_owner_t id);
  8. int (*release) (struct inode *, struct file *);
  9. /*还有很多,省略*/
  10. }

采用这种指定初始化器的方法,使用灵活,而且代码易于维护。因为如果按照固定顺序赋值,当我们的 file_operations 结构体类型发生改变时,比如添加成员、减少成员、调整成员顺序,那么使用该结构体类型定义变量的大量 C 文件都需要重新调整初始化顺序,那将导致程序大幅度地更改。

结构体数组的指定初始化器

在叙述了上面关于结构体和数组的指定初始化器之后,我们也可以以这种方式来来初始化结构体数组,比如这样:

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. struct point {int x,y;};
  5. struct point pts[5] =
  6. {
  7. [2].y = 5,
  8. [2].x = 6,
  9. [0].x = 2
  10. };
  11. int i;
  12. for(i = 0;i < 5;i++)
  13. printf("%d %d\n",pts[i].x,pts[i].y);
  14. }

输出结果如下:

  1. 2 0
  2. 0 0
  3. 6 5
  4. 0 0
  5. 0 0

总结

以上便是指定初始化器所包含的大致内容,这也是自己之前的知识盲点,通过这次总结学习,也能够很好的掌握了,不积跬步,无以至千里~

参考资料:

[1] https://blog.51cto.com/zhaixue/2346825

[2] https://www.geeksforgeeks.org/designated-initializers-c/

[3] https://aticleworld.com/state-machine-using-c/

最后,如果您觉得我的文章对您所有帮助,欢迎关注我的个人公众号

C语言指定初始化器解析及其应用的更多相关文章

  1. Swift 中类的初始化器与继承

    首先,Swift 为类定义了两种初始化器来确保类中所有的储存属性都能得到一个初始化值.这两种初始化器就是「指定初始化器」(Designated Initializer)与「便利初始化器」(Conven ...

  2. Asp.net Mvc 数据库上下文初始化器

    在Asp.net Mvc 和Entity FrameWork程序中,如果数据库不存在,EF默认的行为是新建一个数据库.如果模型类与已有的数据库不匹配的时候,会抛出一个异常. 通过指定数据库上下文对象初 ...

  3. 【P4语言学习】Parser解析器

    参考文章:王垠:谈谈Parser 簡單介紹 P4 語言(一)- Parser 什么是Parser 传统的parser,一般出现在编译器和编译原理课程中,援引<谈谈Parser>的定义: 首 ...

  4. 嵌入式C语言自我修养 02:Linux 内核驱动中的指定初始化

    2.1 什么是指定初始化 在标准 C 中,当我们定义并初始化一个数组时,常用方法如下: ] = {,,,,,,,,}; 按照这种固定的顺序,我们可以依次给 a[0] 和 a[8] 赋值.因为没有对 a ...

  5. C#高级知识点概要(3) - 特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询

    1.特性(Attributes) 特性(Attributes),MSDN的定义是:公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型.字段.方法 ...

  6. 第七节:语法总结(1)(自动属性、out参数、对象初始化器、var和dynamic等)

    一. 语法糖简介   语法糖也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方 ...

  7. C语言里字符串的解析

      根据给定的字符串,按照一定规则解析字符串,卡住好几次,这次做个笔记,以供参考 函数名称:   strtok 函数原型:   char *strtok(char *s1, const char *s ...

  8. .NET中那些所谓的新语法之一:自动属性、隐式类型、命名参数与自动初始化器

    开篇:在日常的.NET开发学习中,我们往往会接触到一些较新的语法,它们相对以前的老语法相比,做了很多的改进,简化了很多繁杂的代码格式,也大大减少了我们这些菜鸟码农的代码量.但是,在开心欢乐之余,我们也 ...

  9. 6.Swift协议|扩展|访问权限|异常调试|类型转换|运算函数|ARC|类类型初试化器|值类型初始化器

    1. 协议(Protocol):与OC之间唯一不同的是Swift中的协议不管是属性还时方法全部是必须实现的 /** protocol*/ protocol FullNamed { /** 计算属性申明 ...

随机推荐

  1. c++ 常量/有符号数和无符号数

    一.宏定义 #define 和常量 const 1. const关键字 const是constant的简写,只要一个变量前面用const来修饰,就意味着该变量里的数据可以被访问,不能被修改.也就是说c ...

  2. AutoCompleteTextView的简单使用

    1.AutoCompleteTextView功能 自动完成文本框,由EditText派生而来,是一个文本编辑框,相较普通的文本编辑框多了提示功能,即用户输入一定数量的字符后,自动完成文本框会弹出一个下 ...

  3. 【python系统学习13】类(class)与对象(object)

    目录: 类(class)和实例 类 实例 小测试 对象(object) 属性和方法 类的创建 伪代码 示例代码 属性(attribute) 方法(method) 类的实例化 实例对象调用类属性和方法 ...

  4. UVA11300 Spreading the Wealth 数学

    前方数学警告 题目链接:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=25&am ...

  5. Appium自动化(2) - appium环境安装常见问题的解决方案

    如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 问题1:adb检测不到设备 解决 ...

  6. Wirte-up:攻防世界Web解题过程新手区01-06

    文章更新于:2020-02-18 说明:为了标识图片边界,有些图片加了红线以增强观感. 注1: web 环境搭建参见: Windows&linux使用集成环境搭建 web 服务器 注2:DVW ...

  7. 家庭版记账本app开发之关于(数据库查找出数据)圆饼图的生成

    这次完成的主要的怎样从数据库中调出数据.之后通过相关的数据,生成想要的圆饼图.以方便用户更加直观的看见关于账本的基本情况. 在圆饼图生成中用到了一些外部资源 具体的import如下: import c ...

  8. Alpha测试与Beta测试

    粗略说一下Alpha测试与beta测试 1.Alpha测试 α测试是由一个用户在开发环境下进行的测试,也可以是公司内部的用户在模拟实际操作环境下进行的测试.α测试的目的是评价软件产品的功能.局域化.可 ...

  9. hadoop(三)伪分布模式hdfs文件处理|5

    伪分布模式hdfs 1.启动hsfs 2. 编辑vi hadoop-env.sh image.png image.png 3.配置nameNode和生产文件第地址 [shaozhiqi@hadoop1 ...

  10. Powershell操作MySQL

    最近再用Python写一些监控脚本,并将监控数据输出到MySQL中,最后通过Python抓取MySQL中的数据进行监控汇总告警 考虑到一些微软产品使用Powershell更为方便,于是找了些资料,尝试 ...