结构体、联合体、枚举被称为C语言的构造数据类型,使用它们之前需要先把相应的类型构造出来,然后才可以用它定义变量。
结构体可以把其他的数据类型打包成一个新的数据类型。这样,如果程序中需要处理一些关联性比较强的数据,那么则可以使用结构体来定义。比如统计班上学生的信息,每个人都有姓名,学号,分数等信息,如果将这些信息分别用不同的变量存储,那么程序将非常杂乱,不易读懂。而如果将学生的信息定义成一个新的类型,则可以用一个该类型的变量来存储学生的全部信息,非常方便,以下是结构体的一般定义方法:
struct 结构体名称 { 成员1; 成员2; … }; |
比如,可以用以下结构体来表示学生信息:
struct student { char name[10]; int id; int score; }; |
那么,则定义了一个新的数据类型,它的类型是struct student,接下来可以用它来定义变量并存储信息:
struct student stu1 = {“Louis”, 22, 85}; |
定义好结构体变量之后,就可以用”.”运算符访问结构体对象的成员了,如下:
printf(“%s\n”, stu1.name); stu1.id = 10; stu1.socre = 90; |
同样,如果要知道结构体的地址和成员的地址,可以用取地址符&表示,如下:
printf(“%p\n”, &stu1); printf(“%p\n”, stu1.name); printf(“%p\n”, &stu1.id); printf(“%p\n”, &stu1.score); |
用结构体类型同样可以定义指针变量,如下:
struct student stu1 = {“Louis”, 10, 85}; struct student* p = &stu1; //定义结构体指针变量并赋值 |
通过该指针也可以访问到结构体的成员,只要解一下地址就可以了:
(*p).name ==> “Louis” //通过解地址方式访问成员 (*p).id ==> 10 (*p).score ==> 85 |
但是,如果某个指针是结构体类型的指针,通过该指针来访问结构体的成员时,可以不用写那么麻烦,直像像下面那样写就可以了:
p->name ==> “Louis” //通过结构体指针访问成员 p->id ==> 10 p->score ==> 85 |
要理解内存对齐,首先明白一个概念,在现代计算机体系中,CPU都是要从内存中拿数据的,CPU与内存通过总线连接,进行数据的存和取。在x86体系下,总线位宽是32位,这也是所有指针类型都占用4个字节的原因。既然总线位宽是32位,那就意味着CPU一次可以从内存拿到连续4个字节的数据。这样,将数据按照相隔4字节进行存放,存取的效率是最高的,相邻距离在4字节以内,存取效率反而会降低了。
为了提高存取的效率,编译器在存放结构体的时候,会默认对结构体的成员进行内存对齐,对齐的规则如下:
可以通过预编译指针 #pragma pack(value) 来告诉编译器使用指定的对齐值进行对齐。
如下:
#pragma pack(1) /*指定按1字节对齐*/ struct stu1 { char str[10]; int a; }; #pragma pack() /*取消指定对齐,恢复默认对齐*/ |
则sizeof(struct stu1)为14字节。
关于字节对齐,工作上只是一个原则问题,并不需要一定按照最高存储效率的方式来定义结构体,大家只要知道,尽量将小的变量往前写,避免过度填充就可以了。
但是!!!结构体对齐经常出现在各种面试题中作为考点,大家在学习阶段一定要多加练习,熟练掌握!!!。
struct student stu; stu = {“Louis”, 10, 85}; //不可以这样赋值 |
struct student { char name[10]; int id; int score; }; struct student stu = { .id = 10, //只对id和score两个成员进行赋值 .score = 85 }; |
struct student{ char name[10]; int id; int score; }stu1, stu2; |
struct{ char name[10]; int id; int score; }stu1, stu2; |
struct A { int a; int b; int c; }; struct A stru1 = {0}; //定义结构体变量并清零 struct A stru2 = {1, 2}; //定义变量并设置前两个成员的值,后面的值则为0; |
struct A a = {1, 2, 3}; struct A b; b = a; //结构体直接赋值 |
通过结构体定义的数组与普通数据无本质区别。
结构体的成员变量可以是另一个结构体,但不可以是自身的类型,类似下面的写法是错误的:
struct stu1 { int a; struct stu1 b; //错误,不可以是自身的类型 }; |
结构体作为参数参数时,参数由实参传给形参时,也是按照传值的方式进行传递的,实参的值会完整地复制一份给形参。
结构体本身不可以进行类型转换,类似下面的写法是错误的:
struct A a; struct B b; a = (struct A) b; //错误,结构体对象不可以进行类型转换但是,通过结构体指针,可以达到类型转换的目的,如下: struct B * pb = NULL; pb = (struct B *)&a; //pb作为结构体B的指针,实际指向的是结构体a的内存空间 |
1、结构体包含自身类型的指针 ==> 链表结构,数据结构部分讲解
2、结构体包含函数指针 ==> 表驱动法,,《代码大全(第2版)》第18章。
3、结构体包含指向堆内存的指针 ==> 深拷贝与浅
字节对齐有时会浪费一部内存,但是有的时候,节约一点点的内存也会有奇效,比如网络传输。而且,在存储某些信息的时候,我们可能并不需要占用一个完整的字节,而只需要占一个或几个二进制位就可以了,比如存储一个八进制数只需要3个二进制位。为了节省存储空间,C语言提供了位段这种数据结构。所谓位域,就是把存储空间中的二进制位划分成几个不同的区域,每个区域有一个名称,允许在程序中按这个名称对内存进行操作。C语言中,定义位段的方式如下:
struct 位域结构名 { 类型说明符 位域名:位域长度; 类型说明符 位域名:位域长度; … }; |
细分的定义方式和结构体一样,不再赘述。
比如可以按如下方式定一个位段结构:
struct bitField { unsigned char l:4; unsigned char h:4; } |
使用位域要注意以下几点:
struct bs { int a:4; int :0; //空域 int b:5; //从第二个字节开始存放 int c:3; } |
struct bs { int a:4; int :2; //这两位不能使用 int b:2; int c:5; int d:3; } |
联合体的关键字是union,它与struct有一样的语法规则,不同的是,联合体的各个成员在内存中是共享一块内存的,它占用的内存大小是整个联合体中最大的那个成员的大小。以下是union的使用示例与内存图:
union un { int a; int b; double c; }; |
内存图展示:
/* *Des:判断系统大小端 *Return Value: 0:小端 1:大端 */ int testEndian(void) { union{ int i; char c; }test; test.i = 1; return (test.c == 1); } |