前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C语言指定初始化器解析及其应用

C语言指定初始化器解析及其应用

作者头像
wenzid
发布2021-03-04 15:38:16
4450
发布2021-03-04 15:38:16
举报
文章被收录于专栏:wenzi嵌入式软件wenzi嵌入式软件

由于笔者能力有限,文中如果出现错误的地方,欢迎大家给我指出来,我将不胜感激,谢谢~

指定初始化器的概念

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

数组的指定初始化器

一维数组的指定初始化器

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

代码语言:javascript
复制
int a[6] = {[4] = 10,[2] = 25};

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

代码语言:javascript
复制
int a[6] = {0,0,25,0,10,0};

可以看到通过这种方式能够不按照顺序,且指定具体的元素进行初始化。除了上述这样的用法,我们也能够初始化数组内一段范围内的用元素,比如这样:

代码语言:javascript
复制
int a[5] = {[4] = 10,[0 ... 3] = 23};

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

代码语言:javascript
复制
int a[5] = {23,23,23,23,10};

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

代码语言:javascript
复制
int a[5] = {11,[3] = 44,55,[1] = 22,33};

那它等同于下面的代码:

代码语言:javascript
复制
int a[5] = {11,22,33,44,55};

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

代码语言:javascript
复制
int main(void)
{
    int number[] = {[20] = 1,[10] = 8,9};
    int n = sizeof(number)/sizeof(number[0]);
    printf("The Value of n is:%d\n",n);
}

输出结果是这样的:

代码语言:javascript
复制
The Value of n is:21

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

二维数组的指定初始化器

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

代码语言:javascript
复制
int array[2][2] = 
{
    [0] = {[0] = 11},
    [1] = {[1] = 22},
};

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

代码语言:javascript
复制
int array1[2][2] = 
{
    {11,00},
    {00,22}
};

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

应用

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

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

代码语言:javascript
复制
typedef enum
{
    Idle_State,
    Card_Inserted_State,
    Pin_Entered_State,
    Option_Selected_State,
    Amount_Entered_State,
    last_State
}eSysyemState;

typedef enum
{
    Card_Insert_Event,
    Pin_Enter_Event,
    Option_Selection_Event,
    Amount_Enter_Event,
    Amount_Dispatch_Event,
    last_Event
}eSystemEvent;

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

代码语言:javascript
复制
eSysyemState AmountDispatchHandler(void)
{
    return Idle_State;
}

eSysyemState EnterAmountHandler(void)
{
    return Amount_Entered_State;
}

eSysyemState OptionSelectionHandler(void)
{
    return Option_Selected_State;
}

eSysyemState InsertCardHandle(void)
{
    return Card_Inserted_State;
}

eSysyemState EnterPinHandler(void)
{
    return Pin_Entered_State;   
}

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

代码语言:javascript
复制
typedef eSysyemState (* const afEventHandler[last_State][last_Event])(void);

简单说一个这是一个二维数组,二维数组里面存放的是函数指针,这个函数指针指向的是返回值为 eSysyemState,形参为 void 的函数。在重定义了这个类型之后,我们就可以用其定义新的变量了,在这之前,补充一点数组相关的内容,比如有如下代码:

代码语言:javascript
复制
typedef int array[3];
array data;

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

代码语言:javascript
复制
int data[3];

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

代码语言:javascript
复制
static afEventHandler StateMachine = 
    {
        [Idle_State] = {[Card_Insert_Event] = InsertCardHandle},
        [Card_Inserted_State] = {[Pin_Enter_Event] = EnterPinHandler },
        [Pin_Entered_State] = {[Option_Selection_Event] = OptionSelectionHandler},
        [Option_Selected_State] = {[Amount_Entered_Event] = EnterAmountHandler},
        [Amount_Entered_State] = {[Amount_Dispatch_Event] = AmountDispatchHandler},
    };

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

代码语言:javascript
复制
#include <stdio.h>

int main(void)
{
    eSysyemState eNextState = Idle_State;
    eSystemEvent eNewEvent;
    while(1)
    {
        eNewEvent = ReadEvent();
        /*省略相关判断*/
        eNextState = (*StateMachine[eNextState][eNewEvent])();
    }
    return 0;
}

结构体的指定初始化器

定义了如下结构体:

代码语言:javascript
复制
struct point
{
    int x,y;
}

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

代码语言:javascript
复制
struct point p = 
{
    .y = 2,
    .x = 3
};

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

代码语言:javascript
复制
struct point p = {3,2};

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

代码语言:javascript
复制
const struct file_operations eeprom_fops =
{
  .llseek  = eeprom_lseek,
  .read    = eeprom_read,
  .write   = eeprom_write,
  .open    = eeprom_open,
  .release = eeprom_close
};

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

代码语言:javascript
复制
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    /*还有很多,省略*/
    }

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

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

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

代码语言:javascript
复制
#include <stdio.h>
int main(void)
{
    struct point {int x,y;};
    struct point pts[5] =
    {
        [2].y = 5,
        [2].x = 6,
        [0].x = 2
    };
    int i;
    for(i = 0;i < 5;i++)
        printf("%d %d\n",pts[i].x,pts[i].y);
}

输出结果如下:

代码语言:javascript
复制
2 0
0 0
6 5
0 0
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/

您的阅读是对我最大的鼓励,您的建议是对我最大的提升

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 wenzi嵌入式软件 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 指定初始化器的概念
    • 数组的指定初始化器
      • 一维数组的指定初始化器
      • 二维数组的指定初始化器
      • 应用
    • 结构体的指定初始化器
      • 结构体数组的指定初始化器
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档