概述
结构体、联合体、枚举被称为C语言的构造数据类型,使用它们之前需要先把相应的类型构造出来,然后才可以用它定义变量。
结构体定义
结构体可以把其他的数据类型打包成一个新的数据类型。这样,如果程序中需要处理一些关联性比较强的数据,那么则可以使用结构体来定义。比如统计班上学生的信息,每个人都有姓名,学号,分数等信息,如果将这些信息分别用不同的变量存储,那么程序将非常杂乱,不易读懂。而如果将学生的信息定义成一个新的类型,则可以用一个该类型的变量来存储学生的全部信息,非常方便,以下是结构体的一般定义方法:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct 结构体名称
{
成员1;成员1;
成员2; 成员1;
… ...
};
; |
比如,可以用以下结构体来表示学生信息:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
struct student
{
char name[10];
int id;
int score;
};
|
那么,则定义了一个新的数据类型,它的类型是struct student
,接下来可以用它来定义变量并存储信息:那么,则定义了一个新的数据类型,它的类型是struct student,接下来可以用它来定义变量并存储信息:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct student stu1stu = {“Louis”"Louis", 22, 85};
|
结构体内存图

访问结构体成员
定义好结构体变量之后,就可以用”定义好结构体变量之后,就可以用.
”运算符访问结构体对象的成员了,如下:运算符访问结构体对象的成员了,如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
printf(“%s\n”, stu1.name);
stu1stu.id = 10;
stu1stu.socre = 90;
strcpy(stu.name, "Louis"); |
同样,如果要知道结构体的地址和成员的地址,可以用取地址符&
表示,如下:
代码块 |
language |
---|
c++ |
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
printf(“%p"%p\n”n", &stu1);
printf(“%p"%p\n”n", stu1.name);
printf(“%p"%p\n”n", &stu1.id);
printf(“%p"%p\n”n", &stu1.score);
|
结构体指针
用结构体类型同样可以定义指针变量,如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
struct student stu1stu = {“Louis”"Louis", 10, 85};
struct student * p = &stu1stu; //定义结构体指针变量并赋值
|
通过该指针也可以访问到结构体的成员,只要解一下地址就可以了:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
(*p).name ==> “Louis” //通过解地址方式访问成员
(*p).id ==> 10
(*p).score ==> 85
|
但是,如果某个指针是结构体类型的指针,通过该指针来访问结构体的成员时,可以不用写那么麻烦,直像像下面那样写就可以了:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
p->name ==> “Louis” //通过结构体指针访问成员
p->id ==> 10
p->score ==> 85
|
结构体内存对齐
要理解内存对齐,首先明白一个概念,在现代计算机体系中,CPU都是要从内存中拿数据的,CPU与内存通过总线连接,进行数据的存和取。在x86体系下,总线位宽是32位,这也是所有指针类型都占用4个字节的原因。既然总线位宽是32位,那就意味着CPU一次可以从内存拿到连续4个字节的数据。这样,将数据按照相隔4字节进行存放,存取的效率是最高的,相邻距离在4字节以内,存取效率反而会降低了。要理解内存对齐,首先明白一个概念,在现代计算机体系中,CPU从内存中拿数据,CPU与内存通过总线连接,进行数据读写。在x86体系下,总线位宽是32位,这也是所有指针类型都占用4个字节的原因。既然总线位宽是32位,那就意味着CPU一次可以从内存拿到连续4个字节的数据。这样,将数据按照相隔4字节进行存放,存取的效率是最高的,相邻距离在4字节以内,存取效率反而会降低了。
为了提高存取的效率,编译器在存放结构体的时候,会默认对结构体的成员进行内存对齐,对齐的规则如下:
- 只按char, short, int, float, double,指针等“原子类型对齐”,结构体嵌套时,需要将子结构体展开成原子类型。
- char对齐值为1,short对齐值为2,int,float,double,指针的对齐值为4指针的对齐值为4。
- 每个成员变量存放的起始位置相对于结构体起始位置的偏移量,必须是该成员对齐值与系统对齐值这两者较小值的整数倍。
- 整个结构体的大小,必须是结构体对齐值的整数倍。结构体对齐值为系统对齐值与结构体成员中的最大对齐值这两者的较小值。
修改默认的对齐方式
可以通过预编译指针 #pragma pack(value)
来告诉编译器使用指定的对齐值进行对齐。
如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
collapse |
---|
false | #pragma pack(1) // /*指定按1字节对齐*/
struct stu1stu
{
char str[10];
int a;
};
#pragma pack() //*取消指定对齐,恢复默认对齐*/
|
则sizeof则sizeof(struct
stu1stu)
为14字节。
关于结构体对齐注意点
关于字节对齐,工作上只是一个原则问题,并不需要一定按照最高存储效率的方式来定义结构体,大家只要知道,尽量将小的变量往前写,避免过度填充就可以了。
但是!!!结构体对齐经常出现在各种面试题中作为考点,大家在学习阶段一定要多加练习,熟练掌握!!!。除非是在设计二进制协议或映射寄存器等极特殊的场景下需要关注字节对齐问题,否则在工作上字节对齐只是一个原则问题,并不需要一定按照最高存储效率的方式来定义结构体,只需要知道尽量将小的变量往前写,避免过度填充就可以了。
结构体-其他
结构体定义与初始化
只可以在定义时对结构体进行一次性赋值,定义完成后,再次引用结构体时,只能引用结构体的成员,而不能引用整个结构体,如下:
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
struct student stu;
stu = {“Louis”, 10, 85}; //不可以这样赋值 |
定义结构体变量时,可以只对结构体的某些变量赋值,采用如下方式:
\
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
collapse
false | struct student
{
char name[10];
int id;
int score;
};
struct student stu = {
.id = 10,
|
//只对id和score两个成员进行赋值
可以在定义结构体类型的同时定义变量,如下:
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct student
{
char name[10];
int id;
int score;
} stu1, stu2; |
可以定义匿名的结构体变量,如下:
language | c++ |
---|
theme | Confluence
linenumbers | false |
---|
collapse | false |
structstruct
{
char name[10];
int id;
int score;
} stu1, stu2; |
结构体的也存在部分赋值,规则与数组的部分赋值一致,如下:
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct A
{
int a;
int b;
int c;
};
struct A stru1 = {0}; //定义结构体变量并清零
struct A stru2 = {1, 2}; //定义变量并设置前两个成员的值,后面的值则为0; |
可以通过结构体给另一个结构体赋值,如下:
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
struct A a = {1, 2, 3};
struct A b |
;
b
结构体数组
通过结构体定义的数组与普通数据无本质区别。
结构体包含结构体
结构体的成员变量可以是另一个结构体,但不可以是自身的类型,类似下面的写法是错误的:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct stu1stu
{
int a;
struct stu1stu b; //错误,不可以是自身的类型错误,成员不可以是自身类型
};
|
结构体作为函数参数
结构体作为参数参数时,参数由实参传给形参时,也是按照传值的方式进行传递的,实参的值会完整地复制一份给形参。
结构体类型转换
结构体本身不可以进行类型转换,类似下面的写法是错误的:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
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语言中,定义位段的方式如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct 位域结构名
{
类型说明符 位域名:位域长度;
类型说明符 位域名:位域长度;
…
};
|
细分的定义方式和结构体一样,不再赘述。
比如可以按如下方式定一个位段结构:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct bitField
{
unsigned char l:4;
unsigned char h:4;
}
|
使用位域要注意以下几点:
- 位域的类型只可以是整数类型(char, int及其变体)
- 位域的定义必须从右往左的顺序,从数据的最低位开始定义
- 一个位域必须存储在同一个字节中,不能跨越两个字节,如果一个字节所剩空间不够放另一个域时,应该从下一单元起存放位域,如下所示:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct bs
{
int a:4;
int :0; //空域
int b:5; //从第二个字节开始存放
int c:3;
}
|
- 一个位域不能大于一个字节,也就是说一个位域不能超过8位。
- 一个位域可以无位域名,这时它只作填充或调整位置。无名的位域不能使用。
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
struct bs
{
int a:4;
int :2; //这两位不能使用
int b:2;
int c:5;
int d:3;
}
|
- 不可以对位域进行取地址操作。
联合体
联合体的关键字是union,它与struct有一样的语法规则,不同的是,联合体的各个成员在内存中是共享一块内存的,它占用的内存大小是整个联合体中最大的那个成员的大小。以下是union的使用示例与内存图:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
union un
{
int a;
int b;
double c;
};
|
内存图展示:

通过联合体判断系统的大小端
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | false |
---|
collapse | false |
---|
|
/*
*Des:判断系统大小端
*Return Value: 0:小端 1:大端
*/
int testEndian(void)
{
union{
int i;
char c;
}test;
test.i = 1;
return (test.c == 1);
}
|