C语言之旅:自定义类型 (结构体)

目录

一.结构体

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

1.2结构的自引用

1.3结构体对齐

1.4修改默认对齐数

1.5 结构体传参  

1.6结构体实现位段

1.7位段的应用

1.8 位端的内存分配

1.9位段跨平台问题

2.0 总结


一.结构体

结构体(struct)是一种复合数据类型,它允许你将多个不同类型的数据项组合成一个单一的类型。结构体常用于表示具有多个属性的复杂对象,如一个包含姓名、年龄和地址的人。

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

// 声明一个名为Person的结构体  
struct Person 
{  
    char name[50]; // 名字,字符数组  
    int age;       // 年龄,整数  
    char aess[100]; // 地址,字符数组  
};

一旦你声明了结构体,你就可以创建该类型的变量,并访问其成员。例如:

#include <stdio.h>  
  
struct Person 
{  
    char name[50];  
    int age;  
    char aess[100];  
};  
  
int main() 
{  
    struct Person john;   
    // 初始化john的成员  
    strcpy(john.name, "John Doe");   
    john.age = 30;  
    strcpy(john.aess, "123 Main St");  
  
    // 打印john的信息  
    printf("Name: %s\n", john.name);  
    printf("Age: %d\n", john.age);  
    printf("Address: %s\n", john.aess);  
  
    return 0;  
}

1.2结构的自引用

构的自引用指的是在结构体的定义中,结构体自身包含指向其自身类型的指针或数组。这种自引用结构通常用于创建链表、树、图等数据结构,其中每个节点或元素都可能需要引用其他相同类型的节点或元素。

代码示例:

typedef struct Node
{
 int data;
 struct Node* next;
}Node;

1.3结构体对齐

首先得掌握结构体的对齐规则:
(1). 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
(2). 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值。
VS 中默认的值为 8
代码练习:
struct S1
{
 char c1;
 int i;
 char c2;
};

3a26780a71834c43b31c921395f345cb.png

从图中可以看出1+3+4+1=9,然后由对齐规则我们要偏移到12。
b6691943ba1f4ba4890e20655e67e758.png

代码示例:

struct S2
{
    char c1;
    char c2;
    int i;
};

870352a69d9b4245a17fd6729a83053f.png

从图中知道1+1+2+4 =8 满足结构体总大小为最大对齐数的整数倍

运行结果:

e108c14784bb4da8be50e9d33da1df70.png

1.4修改默认对齐数

#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;
}

 输出结果:

6

因为此时对齐数被修改为1 ,对于int i来说,它的大小为4字节,而默认对齐数为1,两者比较,取较小值,此时偏移量从1的倍数开始,因此,struct S的总大小是 1 + 4 + 1 = 6 字节。

1.5 结构体传参  

#include<stdio.h>
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;
}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下 降。
结论:
结构体传参的时候,要传结构体的地址。

1.6结构体实现位段

代码示例:
#include <stdio.h>  

struct bit {
    unsigned int flag1 : 1;       // 1位  
    unsigned int flag2 : 2;       // 2位  
    unsigned int field : 4;       // 4位  
    unsigned int l_field : 8; // 8位(即1字节)  
    unsigned int even_larger : 16; // 16位(即2字节)  
};

int main() {
    struct bit bf;

    bf.flag1 = 1;
    bf.flag2 = 3; // 在2位范围内,3的二进制表示是11  
    bf.field = 15; // 在4位范围内,15的二进制表示是1111  
    bf.l_field = 255; // 在8位范围内,255的二进制表示是11111111  
    bf.even_larger = 65535; // 在16位范围内,65535的二进制表示是11111111 11111111  

    printf("flag1: %d\n", bf.flag1);
    printf("flag2: %d\n", bf.flag2);
    printf("field: %d\n", bf.field);
    printf("l_field: %d\n", bf.l_field);
    printf("even_larger: %d\n", bf.even_larger);

    return 0;
}

输出结果:

flag1: 1
flag2: 3
field: 15
l_field: 255
even_larger: 65535

注意:
位段的长度是成员名后面的冒号后面的数字指定的。

位段的具体内存布局(即如何在内存中排列这些位)是依赖于编译器和平台的。因此,不应假设位段在内存中的具体顺序或对齐方式。

位段不能包含浮点数、双精度数、指针或聚合类型(如数组或结构体)。

位段不能跨越多个存储单元(即它们不能跨越多个字节)。如果一个位段需要更多的空间,那么编译器可能会在下一个存储单元中为它分配空间,并可能在它们之间插入填充位。

当读取或写入位段时,它会被当作一个有符号整数或无符号整数来处理,具体取决于该位段的类型。

1.7位段的应用

节省存储空间:
在某些情况下,如过程控制、参数检测或数据通信领域,控制信息往往只占一个字节中的一个或几个二进制位。利用位段能够以较少的位数存储数据,从而节省存储空间。当程序需要成千上万个数据单元时,这种方法就显得尤为重要。

简化程序源代码:
位段可以很方便的访问一个整数值的部分内容,从而可以简化程序源代码。例如,可以通过直接操作位段的位来设置或读取某个特定位的值,而无需对整个字节或整数进行操作。

特定应用场景:
在一些需要精确控制数据存储和访问的场景中,如嵌入式系统开发、硬件接口编程等,位段可以发挥重要作用。

1.8 位端的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型。

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。

3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。

9c0138b4af7e407dbc67d97894112aa2.png

8f3a73f52714476082a8ecb5c8ea7667.png

1.9位段跨平台问题

.int位段被当成有符号数还是⽆符号数是不确定的。

.位段中最大位的数目不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会出问题。

.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

.当⼀个结构包含两个位段,第二个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

2.0 总结

通过结构体,我们能够将多个相关字段封装成一个单一的数据类型,这不仅使得代码更加清晰和易于理解,还提高了代码的可维护性和可重用性。

位段是C语言中一种用于在结构体中定义按位分配内存的字段的特性,它允许开发者精确控制内存使用,但具体行为取决于编译器和平台,使用时需谨慎并确保代码的可移植性和健壮性。

感谢大家阅读这篇关于结构体的章,希望它能够为你带来一些启发和帮助。祝你在编程的道路上越走越远,收获更多的知识和成就!

  • 31
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值