【嵌入式】C语言高级编程-数组和结构体初始化(02)
00. 目录
文章目录
01. 初始化概述
在标准 C 中,当我们定义并初始化一个数组时,常用方法如下:
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
按照这种固定的顺序,我们可以依次给 a[0] 和 a[8] 赋值。因为没有对 a[9] 赋值,所以编译器会将 a[9] 默认设置为0。当数组长度比较小时,使用这种方式初始化比较方便。当数组比较大,而且数组里的非零元素并不连续时,这时候再按照固定顺序初始化就比较麻烦了。
比如,我们定义一个数组 b[100],其中 b[10]、b[30] 需要初始化,如果还按照前面的固定顺序初始化,{}中的初始化数据中间可能要填充大量的0,比较麻烦。
那怎么办呢?C99 标准改进了数组的初始化方式,支持指定任意元素初始化,不再按照固定的顺序初始化。
int a[100] ={ [10] = 1, [30] = 2};
通过数组索引,我们可以直接给指定的数组元素赋值。除此之外,一个结构体变量的初始化,也可以通过指定某个结构体域直接赋值。
因为 GNU C 支持 C99 标准,所以 GCC 编译器也支持这一特性。甚至早期不支持 C99,只支持 C89 的 GCC 编译器版本,这一特性也被当作一个 GCC 编译器的扩展特性来提供给程序员使用。
02. 指定初始化数组元素
在 GNU C 中,通过数组元素索引,我们就可以给某个指定的元素直接赋值。
程序示例
#include <stdio.h> int main(void) { int i = 0; int a[10] = {[3] = 3, [5] = 5, [8] = 8}; for (i = 0; i < 10; i++) { printf("a[%d] = %d\n", i, a[i]); } return 0; }
执行结果
deng@itcast:~/tmp$ gcc 2array.c deng@itcast:~/tmp$ ./a.out a[0] = 0 a[1] = 0 a[2] = 0 a[3] = 3 a[4] = 0 a[5] = 5 a[6] = 0 a[7] = 0 a[8] = 8 a[9] = 0
在{ }中,我们通过下表3对数组元素索引,就可以直接给 a[3] 赋值了。这里有个细节注意一下,就是各个赋值之间用逗号 “,” 隔开,而不是使用分号“;”。
如果我们想给数组中某一个索引范围的数组元素初始化,可以采用下面的方式。
程序示例
#include <stdio.h> int main(void) { int i = 0; //注意 ...之间不能有空格 int a[10] = {[0 ... 4] = 1, [5 ... 9] = 2}; for (i = 0; i < 10; i++) { printf("a[%d] = %d\n", i, a[i]); } return 0; }
执行结果
deng@itcast:~/tmp$ gcc 2array.c deng@itcast:~/tmp$ ./a.out a[0] = 1 a[1] = 1 a[2] = 1 a[3] = 1 a[4] = 1 a[5] = 2 a[6] = 2 a[7] = 2 a[8] = 2 a[9] = 2
在这个程序中,我们使用 [0 … 4] 表示一个索引范围,相当于给 a[0] 到 a[4] 之间的5个数组元素赋值为1。
GNU C 支持使用 … 表示范围扩展,这个特性不仅可以使用在数组初始化中,也可以使用在 switch-case 语句中。比如下面的程序:
程序示例
#include <stdio.h> int main(void) { int i = 3; switch(i) { case 1: printf("1\n"); break; case 2 ... 8: printf("2...8\n"); break; case 9: printf("9\n"); default: printf("default\n"); break; } return 0; }
执行结果
deng@itcast:~/tmp$ gcc 3case.c deng@itcast:~/tmp$ ./a.out 2...8
在这个程序中,当 case 值为2到8时,都执行相同的 case 分支,可以通过 case 2 … 8: 的形式来简化代码。这
温馨提示
… 和其两端的数据范围2和8之间也要空格,不能写成2…8的形式,否则编译就会通不过。
03. 指定初始化结构体成员变量
跟数组类似,在标准 C 中,结构体变量的初始化也要按照固定的顺序。在 GNU C 中我们也可以通过结构域来初始化指定某个成员。
程序示例
#include <stdio.h> typedef struct _stu_t { int id; int age; char sex; }stu_t; int main(void) { stu_t s1 = {1, 18, 'M'}; stu_t s2 = { .id = 2, .age = 24, .sex = 'F' }; printf("s1 id:%d age: %d sex: %c\n", s1.id, s1.age, s1.sex); printf("s2 id:%d age: %d sex: %c\n", s2.id, s2.age, s2.sex); return 0; }
执行结果
deng@itcast:~/tmp$ gcc 4struct.c deng@itcast:~/tmp$ ./a.out s1 id:1 age: 18 sex: M s2 id:2 age: 24 sex: F
在程序中,我们定义一个结构体类型 stu_t,然后分别定义两个结构体变量 s1和 s2。初始化 s1时,我们采用标准 C 的初始化方式,即按照固定顺序直接初始化。初始化 s2时,我们采用 GNU C 的初始化方式,通过结构域名 .name 和 .age,我们就可以给结构体变量的某一个指定成员直接赋值。非常方便。
04. Linux内核中应用
在 Linux 内核驱动中,大量使用 GNU C 的这种指定初始化方式,通过结构体成员来初始化结构体变量。比如在字符驱动程序中,我们经常见到这样的初始化:
static const struct file_operations ab3100_otp_operations = { .open = ab3100_otp_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, };
在驱动程序中,我们经常使用
file_operations这个结构体变量来注册我们开发的驱动,然后以回调的方式来执行我们驱动实现的相关功能。结构体
file_operations在 Linux 内核中的定义如下:
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 *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iopoll)(struct kiocb *kiocb, bool spin); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); unsigned long mmap_supported_flags; int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*setfl)(struct file *, unsigned long); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int); loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int remap_flags); int (*fadvise)(struct file *, loff_t, loff_t, int); } __randomize_layout;
结构体
file_operations里面定义了很多结构体成员,而在这个驱动中,我们只初始化了部分成员变量,通过访问结构体的成员来指定初始化,非常方便。
05. 初始化总结
指定初始化方式,不仅使用灵活,而且还有一个好处就是:代码易于维护。尤其是在 Linux 内核这种大型项目中,几万个文件,几千万的代码量,当成百上千个文件都使用 file_operations 这个结构体类型来定义变量并初始化时,那么一个很大的问题就来了:如果采用标准 C 那种按照固定顺序赋值,当我们的 file_operations 结构体类型发生改变时,如添加成员、减少成员、调整成员顺序,那么使用该结构体类型定义变量的大量 C 文件都需要重新调整初始化顺序,牵一发而动全身,想想这是多么可怕!
我们通过指定初始化方式,就可以避免这个问题。无论file_operations 结构体类型如何变化,添加成员也好、减少成员也好、调整成员顺序也好,都不会影响其它文件的使用。
06. 附录
- 【嵌入式】C语言高级编程-长度为0的数组(05)
- 02-Linux C语言高级-shell脚本编程
- 【嵌入式】C语言高级编程-强符号和弱符号(09)
- 【嵌入式】C语言高级编程-内联函数(10)
- C语言,有5名学生保存在结构体数组中,编程按学生的成绩升序排序,按学生的姓名降序排序,按年龄从低到高排序
- 嵌入式C语言之数组$字符串$结构体$枚举进阶知识
- 【嵌入式】C语言高级编程-内建函数(11)
- 【嵌入式】C语言高级编程-可变参数宏(12)
- 嵌入式linux之高级c语言专题—数组&字符串&结构体&共用体&枚举
- C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com
- 嵌入式linux之高级c语言专题—数组&字符串&结构体&共用体&枚举
- C语言中的结构体,结构体中数组初始化与赋值
- 【嵌入式】C语言高级编程-container_of宏(04)
- C语言--- 高级指针2(结构体指针,数组作为函数参数)
- 嵌入式C语言自我修养 02:Linux 内核驱动中的指定初始化
- C语言结构体数组内带字符数组初始化和赋值
- C语言--- 字符串数组 、 预处理器和预处理指令 、 多文件编程 、 结构体
- 《IOS_作业C语言》高级指针(结构体指针、结构体数组指针、结构体数组指针,作为函数的参数使用、预编译指令)
- C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com
- C语言一维数组、二维数组、结构体的初始化