C语言(12)----结构体

简介: C语言(12)----结构体

一.结构体是什么?

(1)是一种数据类型

首先我们需要知道的是结构体是一种数据类型,它本质上是用于将不同类型的数据组合在一起形成的一个新的数据类型。

(2)是变化的

不同的类型组合在一起的时候,会产生不同的结构体,例如用char和float组成的与用int和char组成的实际上不是同一种结构体

(3)是自定义的

结构体是用户自定义的一种类型,它使用关键字struct定义结构体

二.结构体的格式(声明)

struct tag
{
 member-list;
 member-2ist;
 member-3ist;
 ...
}variable-list;variable-2ist;variable-3ist;...;
 

特殊声明

结构体变量的声明可以实名也可以是匿名声明,也就是把结构体的名称(如上方tag)给删除掉,进行匿名声明。但是这个时候这个结构体就只能使用一次。匿名结构体无法被其他代码块引用,也无法定义指向匿名结构体的指针,限制了其在复杂程序中的使用。

三.结构体变量的创建和初始化

首先我们需要说明一下结构体变量的访问操作符

a.

.”:用于直接访问结构体中的变量

例如:

struct Person person1;
strcpy(person1.name, "Alice");
person1.age = 25;
person1.height = 1.65;

b.

->”:用于访问结构体变量的地址

接下来我们就可以说明结构体变量的创建和初始化

#include <stdio.h>
struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[6];//性别
};
int main()
{
 
//按照结构体成员的顺序初始化
 
struct Stu s1 = { "小明", "20" ,"男"};
 printf("name: %s\n", s.name);
 printf("age : %d\n", s.age);
 printf("sex : %s\n", s.sex);
 
//按照指定的顺序初始化
 
struct Stu s2 = { .age = 16, .name = "xiaohua",  .sex = "⼥"};
 printf("name: %s\n", s2.name);
 printf("age : %d\n", s2.age);
 printf("sex : %s\n", s2.sex);
 return 0;
}

四.结构体的自引用

错误用法:

struct Node
{
 int data;
 struct Node next;
};//这样自引用是错误的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤
⼩就会⽆穷⼤

正确用法:

struct code
{
 int data;
 struct Node* next;
};//这样自引用是正确的,直接对其解引用就可以避免重复包含

五.结构体的内存对齐

结构体既然作为一种类型,那么它肯定也是占有内存的。而且由于结构体不同,其内存也会跟着不同。一般正常的内存存放方式就是按照类型本身所占字节来直接存放,由于它们类型统一,所以在存放的时候也是规律的,一般不会导致内存的溢出等问题。但是结构体包含多种不同的类型,所占的内存大小也就各不相同。要想保证内存的正常占用以及不出现溢出等问题,就需要进行内存对齐。

(1)内存对齐的规则

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处

意思就是第一个成员是从起始位置开始对齐,它与正常类型的存放方式无差别。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

这里介绍对齐数的概念:

对齐数:编译器默认的⼀个对齐数与该成员变量大小的较小值。

请注意,对齐数有时候并不是默认的对齐数,当该变量的大小小于默认对齐数时对齐数就是该成员变量的大小。

vs的默认对齐数是8bit。Linux中gcc没有默认对齐数,对齐数就是成员自身的大小。

3. 结构体大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的 整数倍。

举例:

struct MyStruct 
{
    char a;
    int b;
    char c;
};
 
//根据内存对齐规则,结构体MyStruct的内存布局如下:
 
//a的大小为1字节,偏移量为0。
//b的大小为4字节,由于前一个成员a的大小为1字节,所以b的偏移量为4的整数倍,即4。
//c的大小为1字节,由于前一个成员b的大小为4字节,所以c的偏移量为4的整数倍,即8。
//因此,结构体MyStruct的总大小为8字节。

4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对⻬数)的整数倍。

举例:

struct InnerStruct 
{
    char x;
    int y;
};
 
struct OuterStruct 
{
    char a;
    struct InnerStruct inner;
    double b;
};
 
/*根据内存对齐规则,结构体InnerStruct的内存布局如下:
x的大小为1字节,偏移量为0。
y的大小为4字节,由于前一个成员x的大小为1字节,所以y的偏移量为4的整数倍,即4。
因此,结构体InnerStruct的总大小为8字节。
接下来,根据内存对齐规则,结构体OuterStruct的内存布局如下:
a的大小为1字节,偏移量为0。
inner的大小为8字节(由上面计算得出),由于前一个成员a的大小为1字节,所以inner的偏移量为8的整数倍,即8。
b的大小为8字节,由于前一个成员inner的大小为8字节,所以b的偏移量为8的整数倍,即16。
因此,结构体OuterStruct的总大小为24字节。(既是4的整数倍又是8的整数倍)*/

(2)关于内存对齐原因的补充

1.硬件需求: 许多处理器在读取内存时要求数据按照特定的字节对齐方式存储,否则可能导致性能下降甚至错误。例如,某些处理器要求整型数据按4字节对齐,双精度浮点数按8字节对齐。不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.效率需求:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。如果出现某个变量内存恰好是卡在两个内存块中,同时占用了两个内存块,那么这样实际上就需要访问两次,大大降低了效率和性能;如果我们实现内存对齐就不会出现这种情况,减少了所需要消耗的时间,达到用空间换时间的目的。

(3)内存对齐的优化

既然内存对齐会浪费一定的空间,我们在排序变量的时候就应该尽可能的去减少浪费。

举例:

struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};
//这样排序s1只占了8个,而s2占了12个,所以s1既节省了时间又没有浪费过多空间

(4)默认对齐数的修改

我们可以通过一个指令来修改默认对齐数满足自己的需要

#pragma 预处理指令

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
 printf("%d\n", sizeof(struct S));//输出结果还原为默认的对齐数
 return 0;
}

六.结构体的传参

来看两种传参的方式

struct S
{
 int data[1000];
 int num;
};
 
 struct S s = {{1,2,3,4}, 1000};
 
//结构体传参
 void print1(struct S s)
{
 printf("%d\n", s.num);
}
 
//结构体地址传参
 void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
 
int main()
{
 print1(s); //传结构体
 print2(&s); //传地址
 return 0;
}

这里我们选择传地址的方式会比直接传结构体的方式更佳。

因为函数传参的时候是会涉及到压栈概念的,形参和实参都有各自的内存空间,如果我们直接传结构体的话,会新开辟一个空间;而乳沟我们传地址的话就不会,所耗的内存空间仅占一个地址的字节。所以我们通常选择传地址

七.结构体与位段

位段是一种特殊的结构体成员,即在一个字节或多个字节中指定位数的字段。位段的作用是可以有效地利用内存空间,将多个标志位或状态信息存储在一个整数类型(4字节)或一个字符类型(1字节)的变量中,从而节省内存空间。所以它与内存对齐的效果是相反的,它的第一标准是空间的节省。

格式:

struct 
{
    unsigned int flag1 : 1; // 1比特位的位段
    unsigned int flag2 : 2; // 2位的位段
    unsigned int flag3 : 5; // 5位的位段
} flags;

位段相对于结构体格式上的差别就是多了数字,用来表示所占有的比特位

但是需要注意的是,位段的使用可能会导致代码的可移植性问题,因为位段的存储顺序和字节对齐方式可能在不同的编译器和平台上有所不同。例如,vs的规则是:

1.内存从右向左使用 2.若剩余空间不够下一成员使用就浪费

但是在其他编译器中规则可能就不是这样。

原因在于:

1. int 位段被当成有符号数还是无符号数是不确定的。 2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。 3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。 4. 当⼀个结构包含两个位段,第二个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

需要注意的第二个点就是位段的成员是无法被取地址的,因为取地址是以字节为单位,从起始位置取,但是位段成员不一定在起始位置,那么根据内存中每个字节分配⼀个地址,⼀个字节内部的bit位就是没有地址的。

要想取到地址只能先将成员放入某个变量中,再赋值给位段成员。

目录
相关文章
|
30天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
133 14
|
1月前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
164 10
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
161 13
|
2月前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
2月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
73 11
|
2月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
61 4
|
3月前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
92 10
|
3月前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
3月前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
3月前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。