1.3 访问结构体成员
结构体成员通过点操作符(.
)来访问。
struct Person person1 = { "John" , 25 } ;
printf ( "Name: %s, Age: %d\n" , person1. name, person1. age) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
1.4 结构体的数组
结构体的数组允许我们创建多个结构体对象。声明结构体数组的方式与普通数组类似。
struct Person people[ 3 ] = { { "John" , 25 } , { "Alice" , 30 } , { "Bob" , 22 } } ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
访问结构体数组元素时,需要使用数组索引和点操作符:
printf ( "Name: %s, Age: %d\n" , people[ 0 ] . name, people[ 0 ] . age) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
1.5 结构体作为函数参数
结构体可以作为函数的参数传递。可以通过值传递或者引用传递(指针传递)传递结构体。
1.5.1 结构体值传递
在函数中对结构体进行值传递时,函数接收到结构体的副本,对副本的修改不会影响原结构体。
void printPerson ( struct Person p) {
printf ( "Name: %s, Age: %d\n" , p. name, p. age) ;
}
struct Person person1 = { "John" , 25 } ;
printPerson ( person1) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
1.5.2 结构体指针传递
如果希望在函数内修改结构体的内容,可以通过传递结构体指针来引用原结构体。
void updatePerson ( struct Person * p) {
p-> age = 28 ;
}
struct Person person1 = { "John" , 25 } ;
updatePerson ( & person1) ;
printf ( "Updated Age: %d\n" , person1. age) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
1.6 结构体内存对齐与填充
结构体在内存中的存储方式受到内存对齐的影响。为了提高处理器的效率,结构体的成员通常会根据其类型进行内存对齐。这意味着有时结构体成员之间可能会有空洞,称为“填充”。
可以使用#pragma pack
指令或__attribute__((packed))
来调整结构体的内存对齐方式。
# pragma pack ( push, 1 )
struct Example {
char a;
int b;
} ;
# pragma pack ( pop)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对齐的原则 结构体内存对齐与填充(Padding)是C语言中涉及数据结构存储方式的重要概念。它直接影响到程序的内存使用效率和性能,尤其在处理多平台或者低层次的系统编程时,需要对这一点有深入的理解。
1. 内存对齐的概念
内存对齐指的是将数据类型按一定的规则存储到内存中的方式。由于现代计算机处理器的存取效率,通常要求数据类型按一定的边界对齐存储。也就是说,不同类型的数据应该存放在特定的内存地址上,这样能够提高处理器访问内存的速度。
2. 内存对齐的原理
在C语言中,每个数据类型都有自己的“对齐要求”。对齐要求是指某个数据类型的变量在内存中应存储在某个特定的地址上,这个地址通常是该数据类型大小的倍数。
例如:
char
类型的数据通常可以存储在任意地址(1 字节对齐)。int
类型的变量通常要求存储在4字节对齐的地址上(即地址必须是4的倍数)。double
类型通常要求存储在8字节对齐的地址上(即地址必须是8的倍数)。
不同编译器可能会有不同的默认对齐方式,但是常见的规则是:
char
类型:1字节对齐。short
类型:2字节对齐。int
类型:4字节对齐。double
类型:8字节对齐。
3. 填充(Padding)
填充是指为了满足对齐要求,在结构体成员之间或结构体末尾插入空闲字节,以确保每个成员的数据按照其对齐要求存储。
举个例子,考虑一个结构体包含 char
和 int
类型的成员:
struct Example {
char a;
int b;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
假设系统对 int
类型要求4字节对齐,那么在 char a
后面会有 3 个字节的填充,以确保 b
在 4 字节对齐的位置开始存储。这是因为 b
需要在内存中存储在地址是4的倍数的位置。
因此,结构体的内存布局可能如下:
| char a | padding | padding | padding | int b |
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这个结构体的总大小将会是 8 字节,而不是 5 字节。这样,b
的起始地址就符合 4 字节对齐的要求。
4. 内存对齐与填充的规则
结构体成员对齐: 每个成员都必须存储在一个地址上,这个地址必须是该成员类型对齐要求的倍数。
结构体总对齐: 结构体的对齐要求是结构体中最大对齐要求成员的对齐要求。例如,如果结构体中有 char
和 double
成员,那么结构体的对齐要求就是 double
的对齐要求,通常是 8 字节。
结构体大小: 结构体的大小是根据最大对齐要求来计算的。结构体的大小通常是结构体总内存的最小倍数,这个倍数是结构体内最大成员对齐的倍数。
5. 示例:结构体内存对齐与填充
让我们来看一个例子,假设在一个系统中,int
类型要求4字节对齐,char
类型要求1字节对齐:
# include
struct Example {
char a;
int b;
char c;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这个结构体中的 a
需要 1 字节,而 b
需要 4 字节的对齐。由于 b
必须从 4 字节对齐的位置开始,因此 a
后面会有 3 个字节的填充,接着 b
存储。然后 c
占用 1 字节,由于 b
的对齐要求,结构体的总大小将根据最大对齐需求(通常为 4 字节)填充。
因此,结构体在内存中的布局如下:
| char a | padding | padding | padding | int b | char c | padding |
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
结构体总大小为 8 字节。可以通过 sizeof
操作符来查看结构体的实际大小:
printf ( "Size of struct Example: %zu\n" , sizeof ( struct Example ) ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
6. 编译器对齐设置
在一些情况下,编译器允许通过指令来设置对齐方式。例如,GCC 和 Clang 提供了 #pragma pack
指令,可以控制结构体的对齐方式。可以通过设置对齐大小来减小结构体的内存占用。
例如,在GCC中,使用 #pragma pack(1)
可以强制按 1 字节对齐,这样就不会有任何填充字节:
# pragma pack ( 1 )
struct Example {
char a;
int b;
} ;
# pragma pack ( )
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这样,结构体将没有填充字节,内存布局将是:
| char a | int b |
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
举例说明
2. 共用体的定义与使用
2.1 共用体的基本概念
共用体(union
)是一种特殊的数据结构,它与结构体类似,但与结构体不同的是,共用体的所有成员共享相同的内存空间。即同一时刻,共用体只能存储一个成员的值。这使得共用体能够节省内存空间。
共用体的定义格式如下:
union UnionName {
type member1;
type member2;
type member3;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.2 共用体的定义与初始化
定义一个共用体并初始化时,通常初始化其中的第一个成员。
union Data {
int i;
float f;
char c;
} ;
union Data data1;
data1. i = 10 ;
data1. f = 3.14 ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.3 访问共用体成员
由于共用体的成员共享相同的内存位置,因此只能访问最后存储的成员。当一个成员被赋值时,其他成员的值将被覆盖。
union Data data1;
data1. i = 10 ;
printf ( "Integer: %d\n" , data1. i) ;
data1. f = 3.14 ;
printf ( "Float: %.2f\n" , data1. f) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.4 共用体的应用场景
共用体主要用于节省内存,特别是在需要存储不同类型数据,但在任何时刻只需要其中之一的场合。常见的应用场景包括:
多种类型的数据共享同一内存空间。 处理不同类型数据的协议解析。
2.5 共用体与结构体的区别
内存分配 :结构体中的每个成员都有自己独立的内存空间,而共用体的所有成员共享同一块内存空间。用途 :结构体适用于需要存储多个不同类型数据的场合,而共用体适用于需要存储不同类型数据,但在同一时刻只需要其中一个的场合。
3. 结构体与共用体与指针的结合
3.1 结构体指针
结构体指针是指向结构体类型变量的指针。通过结构体指针,可以访问结构体的成员。结构体指针通常与malloc
动态内存分配结合使用。
3.1.1 声明与初始化
struct Person {
char name[ 50 ] ;
int age;
} ;
struct Person * ptr;
ptr = ( struct Person * ) malloc ( sizeof ( struct Person ) ) ;
strcpy ( ptr-> name, "John" ) ;
ptr-> age = 25 ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3.1.2 访问结构体成员
通过结构体指针访问成员时,使用箭头操作符(->
)。
printf ( "Name: %s, Age: %d\n" , ptr-> name, ptr-> age) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3.2 共用体指针
与结构体指针类似,我们也可以创建共用体指针,通过指针来访问共用体的成员。
3.2.1 声明与初始化
union Data {
int i;
float f;
char c;
} ;
union Data * ptr;
ptr = ( union Data * ) malloc ( sizeof ( union Data) ) ;
ptr-> i = 10 ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3.2.2 访问共用体成员
与结构体指针类似,共用体指针也使用箭头操作符(->
)来访问成员。
printf ( "Integer: %d\n" , ptr-> i) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3.3 结构体与共用体混合使用
结构体和共用体也可以混合使用,以满足更复杂的需求。例如,我们可以在结构体中包含一个共用体,或者在共用体中使用结构体。
struct Person {
char name[ 50 ] ;
int age;
} ;
union Data {
struct Person p;
int i;
} ;
union Data data;
data. p. age = 30 ;
strcpy ( data. p. name, "Alice" ) ;
printf ( "Name: %s, Age: %d\n" , data. p. name, data. p. age) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
4.结论
结构体和共用体是C语言中非常强大的数据结构。结构体允许你将不同类型的数据组织在一起,而共用体通过共享内存来节省空间。在实际开发中,理解这两者的使用场景和优缺点,并掌握它们与指针的结合,是编写高效和内存优化代码的关键。
通过本篇文章的学习,希望你能够全面理解结构体与共用体的定义、使用方式及其在指针方面的应用,从而更好地应对C语言编程中的复杂问题。
id="blogVoteBox" style="width:400px;margin:auto;margin-top:12px" class="blog-vote-box"> class="vote-box csdn-vote" style="opacity: 1;">
class="pos-box pt0" style="height: 222px; visibility: visible;">
评论记录:
回复评论: