当前位置: 首页 > news >正文

C语言自学--自定义类型:结构体

目录

1、结构体类型声明

1.1、结构体回顾

1.2 结构的特殊声明

        1.2.1、匿名结构体类型分析

        1.2.2、匿名结构体的特点

        1.2.3、匿名结构体的适用场景

1.3 结构的自引用

2、结构体内存对齐

2.1 内存对齐规则      

2.2、结构体大小计算分析

练习1:struct S1

练习2:struct S2

练习3:struct S3

练习4:struct S4(嵌套S3)

2.3、内存对齐的必要性

24 、修改默认对⻬数

3、 结构体传参

4、结构体实现位段

4.1、位段的概念

4.2、位段的语法

4.3、位段的特性

4.4、示例代码

4.5、注意事项


1、结构体类型声明

        我们在学习操作符时已经接触过结构体的相关知识,这里先简单回顾一下。

1.1、结构体回顾
1、结构体类型声明结构体(Structure)是C语言中一种重要的复合数据类型,它允许将不同类型的数据组合成一个整体。结构体类型的声明使用`struct`关键字,基本语法格式如下:```c
struct 结构体名 {数据类型 成员1;数据类型 成员2;//...更多成员数据类型 成员n;
}variable_list;;

其中:

  • struct是声明结构体的关键字
  • 结构体名是用户自定义的标识符
  • 大括号内是该结构体包含的成员变量声明
  • 每个成员声明后需要加分号
  • 整个结构体声明以分号结束
  • variable-list 是可选的,可以在定义结构体的同时声明该类型的变量。
struct Point {int x;int y;
} p1, p2;

        上面的代码定义了一个名为 Point 的结构体,包含两个整型成员 x 和 y,同时声明了两个 Point 类型的变量 p1 和 p2。结构体定义后,可以通过成员访问运算符 . 来访问其成员变量:

p1.x = 10;
p1.y = 20;

        在C语言中,结构体定义后使用时必须带上 struct 关键字,除非使用 typedef 进行重命名:

typedef struct Point {int x;int y;
} Point;Point p3;  // 不需要写 struct Point

        在C++中,结构体定义后可以直接使用结构体名称声明变量,无需 struct 关键字。


示例1:声明一个表示学生的结构体

struct Student {int id;          // 学号char name[20];   // 姓名float score;     // 成绩char gender;     // 性别
};

示例2:声明一个表示日期的结构体

struct Date {int year;int month;int day;
};

结构体声明时需要注意:

  1. 结构体声明本身不分配内存,只有定义了结构体变量才会分配内存
  2. 结构体成员可以是任何数据类型,包括基本类型、数组、指针,甚至是其他结构体
  3. 结构体可以嵌套声明,例如:
struct Person {char name[20];struct Date birthday;  // 嵌套Date结构体
};

结构体声明通常放在头文件(.h)中,以便多个源文件共享使用。


1.2 结构的特殊声明

        在声明结构的时候,可以不完全的声明。

        1.2.1、匿名结构体类型分析

        匿名结构体(unnamed struct)是指在声明时没有提供结构体标签(tag)的结构体类型。这种结构体可以直接定义变量,但不能在其他地方复用该类型。

        代码示例1:

  • struct {int a;char b;float c;
    } x;
    

        这里定义了一个匿名结构体并直接声明了一个变量x。由于没有标签,后续无法通过struct tag的方式声明其他变量。

        代码示例2:

  • struct {int a;char b;float c;
    } a[20], *p;
    

        同样定义了一个匿名结构体,但声明了一个数组a[20]和一个指针p。由于没有标签,无法在其他地方声明相同类型的变量。

        1.2.2、匿名结构体的特点

        匿名结构体的成员可以正常访问,例如:

  • x.a = 10;
    p->b = 'X';
    

        但编译器会将两个匿名结构体视为不同的类型,即使它们的成员完全一致。因此以下代码会报错:

  • p = &x;  // 错误:类型不兼容
    

        1.2.3、匿名结构体的适用场景

  • 临时使用的结构体,不需要复用类型。
  • 结构体仅用于单个变量或少量变量时。
  • 嵌套在联合体或其他结构体中作为匿名成员(C11标准支持)。

        如果需要复用结构体类型,应使用带标签的声明方式:

  • struct named_tag {int a;char b;float c;
    };
    struct named_tag y, z;  // 合法
    

1.3 结构的自引用

结构体中是否可以包含自身类型的成员?例如,定义链表节点时:

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

        这段代码是否正确?如果正确,sizeof(struct Node) 的值是多少?经过仔细分析,这段代码存在不合理之处。因为结构体中如果包含同类型的结构体变量,会导致结构体大小无限增长,这是不合理的实现方式。正确的自引用方式应该是使用指针。

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

        该代码定义了一个结构体 Node,其中包含一个 int 类型的 data 成员和一个 struct Node 类型的 next 成员。这种定义方式会导致结构体无限递归,因为 next 成员本身又是一个完整的 Node 结构体,而 Node 又包含 next,如此循环下去,无法计算其大小。

        结构体不能直接包含自身类型的成员变量,因为这样会导致:

  • 结构体大小无法计算(无限递归)。
  • 编译器无法分配内存,因为 sizeof(struct Node) 会无限增长。

        在使用结构体自引用时,若结合typedef对匿名结构体类型进行重命名,可能会引发问题。请观察以下代码示例,其可行性如何?

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

        答案是不行的。因为Node是对前面的匿名结构体类型的重命名,但在匿名结构体内部提前使用Node类型来创建成员变量,这是不允许的。

优化后的表达如下:

定义结构体不要使用匿名结构体了

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

2、结构体内存对齐

        我们已经掌握了结构体的基本用法。现在让我们深入探讨一个关键问题:如何计算结构体的大小。这同时也是面试中的高频考点。


2.1 内存对齐规则      

结构体的内存对齐需遵循以下规则:

  1. 结构体首成员从偏移量为0的地址开始存放

  2. 其余成员需对齐到对齐数的整数倍地址:

    • 对齐数 = min(编译器默认对齐数,该成员大小)
    • VS默认对齐数为8
    • Linux gcc无默认对齐数,对齐数等于成员自身大小
  3. 结构体总大小为最大对齐数的整数倍(取所有成员对齐数的最大值)

  4. 嵌套结构体的情况:

    • 嵌套的结构体成员对齐到其内部最大对齐数的整数倍处
    • 整体结构体大小须为所有最大对齐数(含嵌套结构体)的整数倍
2.2、结构体大小计算分析

结构体的大小计算涉及内存对齐原则,不同编译器和平台可能有不同对齐规则。以下基于常见对齐规则(如默认4字节对齐)进行分析:

练习1:struct S1
struct S1 {char c1;    // 1字节int i;      // 4字节(对齐到4的倍数)char c2;    // 1字节
};
  • c1占用1字节,起始偏移0
  • i需要4字节对齐,因此在c1后填充3字节(偏移1→4)
  • c2占用1字节,偏移8
  • 结构体总大小需为最大成员(int)对齐值的整数倍,最终填充到12字节

输出结果12

练习2:struct S2
struct S2 {char c1;    // 1字节char c2;    // 1字节int i;      // 4字节(对齐到4的倍数)
};

  • c1c2连续存放,占用2字节(偏移0-1)
  • i需要4字节对齐,在c2后填充2字节(偏移2→4)
  • 结构体总大小为8字节(无需额外填充)

输出结果8

练习3:struct S3
struct S3 {double d;   // 8字节char c;     // 1字节int i;      // 4字节(对齐到4的倍数)
};

  • d占用8字节,起始偏移0
  • c占用1字节,偏移8
  • i需要4字节对齐,在c后填充3字节(偏移9→12)
  • 结构体总大小为16字节(无需额外填充)

输出结果16

练习4:struct S4(嵌套S3)
struct S4 {char c1;        // 1字节struct S3 s3;   // 16字节(对齐到8的倍数)double d;       // 8字节
};

  • c1占用1字节,起始偏移0
  • s3需要8字节对齐(因其最大成员为double),在c1后填充7字节(偏移1→8)
  • s3自身大小为16字节(偏移8-23)
  • d占用8字节,偏移24(已对齐)
  • 结构体总大小为32字节(无需额外填充)

输出结果32

2.3、内存对齐的必要性

内存对齐主要基于两个关键原因:

  1. 硬件兼容性 并非所有硬件平台都支持任意地址的数据访问。某些平台只能在特定地址读取特定类型的数据,否则会触发硬件异常。这种限制使得内存对齐成为跨平台兼容的重要考量。

  2. 性能优化 对齐的数据结构(特别是栈结构)能显著提升访问效率。处理器访问未对齐内存需要两次操作,而对齐内存只需一次。例如:一个8字节读取的处理器,若double类型数据都按8字节对齐存储,就能单次完成读写;否则数据可能跨越两个内存块,导致需要两次访问。

简而言之,内存对齐是以空间换取时间的优化策略。

在设计结构体时,如何兼顾内存对齐和空间节省:让占用空间小的成员尽量集中在⼀起

 //例如:struct S1{char c1;int i;char c2;};struct S2{char c1;char c2;int i;};

S1 和 S2 类型的成员一模一样,但是 S1 和  S2 所占空间的大小有了一些区别。


24 、修改默认对⻬数

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

        结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。


3、 结构体传参

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

print1print2 函数哪个更好?答案是:优先选择 print2

原因:函数传参时需要进行参数压栈操作,会产生时间和空间上的系统开销。当传递大型结构体对象时,由于参数压栈的系统开销过大,会导致性能显著下降。


4、结构体实现位段

4.1、位段的概念

        位段(Bit-field)是C语言中结构体的一种特殊用法,允许按位来定义成员变量的大小。通过位段可以有效节省内存空间,特别适合存储开关标志或状态码等小范围数据。

4.2、位段的语法

        在结构体定义中,通过在成员变量后加上冒号和位数来声明位段:

struct 结构体名 {类型 成员名1 : 位数1;类型 成员名2 : 位数2;...
};
  • 类型:通常为intunsigned intsigned int(C99后支持_Bool)。
  • 位数:指定该成员占用的二进制位数(1到类型位宽之间)。
4.3、位段的特性
  1. 内存分配单位:位段成员按定义顺序从低位到高位布局,具体对齐方式由编译器决定。
  2. 跨字节处理:当位段总位数超过一个存储单元(如int的位数)时,可能跨越多个单元。
  3. 未命名位段:可定义无名位段实现填充,例如unsigned : 4;表示跳过4位。
  4. 零宽度位段:定义长度为0的位段(如unsigned : 0;)会强制下一个位段从新存储单元开始。
4.4、示例代码
#include <stdio.h>struct Status {unsigned flag1 : 1;  // 1位,表示布尔值unsigned flag2 : 3;  // 3位,范围0~7unsigned       : 4;  // 4位填充(未使用)unsigned mode  : 2;  // 2位,范围0~3
};int main() {struct Status s;s.flag1 = 1;s.flag2 = 5;s.mode = 2;printf("Sizeof struct: %zu bytes\n", sizeof(s));  // 通常输出1或4(依赖对齐)return 0;
}
4.5、注意事项
  • 可移植性:位段的具体内存布局因编译器和平台而异,跨平台时需谨慎。
  • 地址操作:无法对位段成员取地址(如&s.flag1是非法的)。
  • 符号处理:使用signed int时,最高位会被视为符号位。

http://www.dtcms.com/a/419954.html

相关文章:

  • Oracle快照备库FRA空间满导致无法连接
  • [xboard]17 uboot中的binman
  • 绵阳网站建设怎么选网站互点都是怎么做的
  • 源码下载网站百度下载2021新版安装
  • 给公司建立网站不可以做到的泰兴中信建设有限责任公司
  • MySQL程序简介
  • 创新的赣州网站建设网站建设与维护是什么
  • 湖北省市政工程建设网站网站开发框架的工具
  • sdio的切换I/O电压的详细流程
  • 浮梁网站推广房产网站建设价格
  • Java基础-面向对象复习知识5
  • GitHub 官宣 GitHub Copilot CLI 开发公测:AI CLI 大战
  • 哪些网站可以进行域名注册设计制作一个 个人主页网站
  • OSS-对象存储服务
  • cpa自己做网站高端品牌洗发水
  • 职业教育专业建设验收网站海南做网站的公司有哪些
  • 马云归来,“新零售”不死
  • 湖南省建设厅网站官网镇江网站制作案例
  • 哪些网站是做免费推广的wordpress函数手册 pdf
  • 网站网站做维护犯罪赣州酒店网站设计
  • 洛阳网站建设汉狮报价怎么查域名的注册人
  • 插座配线工程量-连续测量快速计量
  • 软考中级-软件设计师(七)
  • CDN 网站是否需要重新备案链爱交易平台
  • 高端网站建设大概多少费用商城网站建设哪个公司好
  • C++ 多返回值的几种实现方式
  • 徐州网站建设的特点做企业网站用什么
  • 东莞营销网站网站虚拟主机 会计处理
  • CSS定位布局
  • 织梦首饰网站模板茶楼 网站