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

news/2025/10/13 20:35:43/文章来源:https://www.cnblogs.com/slgkaifa/p/19139302

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 
#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 
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.mzph.cn/news/936403.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025公众号排版效率榜:5款AI工具实测对比,从排版到分发一站搞定

作为新媒体运营者,你是否也曾经历过这些场景:花2小时排版一篇公众号文章,换平台发布时格式错乱需重新调整;追热点时选题慢半拍,配图担心版权风险;团队协作时版本混乱,分发10个平台重复操作到深夜?2025年,随着…

完整教程:R语言——离群点检测应用

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

OpenLayers地图交互 -- 章节十六:双击缩放交互详解 - 教程

OpenLayers地图交互 -- 章节十六:双击缩放交互详解 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consol…

中国联通重要突破!正式获得开展eSIM手机运营服务商用试验批复

中国联通重要突破!正式获得开展eSIM手机运营服务商用试验批复Posted on 2025-10-13 20:25 lzhdim 阅读(0) 评论(0) 收藏 举报今日,中国联通公众号发文称,近期,中国联通正式获得开展eSIM手机运营服务商用试验的…

071_尚硅谷_十进制转其它进制

071_尚硅谷_十进制转其它进制1.十进制转二进制 2.十进制转八进制 3.十进制转十六进制

CF1935E Distance Learning Courses in MAC

刚开始以为是 case 题,结果是性质题。 首先肯定从高到低考虑,现在比较困难的事情就是如何决策到底哪些数占据高位哪些数占据低位。这样分类讨论贼多而且还不好做,出题人肯定不会自己给自己设限,想一写基于性质的做…

联考の记录

记录&总结。虽然我写东西的时候总会感觉有其他人看我写的东西导致写出来的东西非常不自然,但我决定还是写一下。这个东西每打一次联考就来更一下,不然偶然写一次感觉也没什么用处。 从一次大失败开始: 10.13 得…

苹果iMessage群发协议,苹果iMessage短信,苹果iMessage推信,iMessage协议版自动群发完美实现。

苹果iMessage群发协议,苹果iMessage短信,苹果iMessage推信,iMessage协议版自动群发完美实现。一、PC电脑版苹果系统(Mac OS)上实现imessage群发总结为以下几种方式: /*MacOS苹果系统,正常情况下,只能安装到苹果公司自…

06-mysql备份实战 #

06-mysql备份实战 1.备份恢复演练(mysqldump+binlog) 知识储备 如下内容。。 全量备份 全量数据,指的是某一整个数据库(如kings)中所有的表、以及表数据,进行备份。 例如备份所有数据库、以及所有数据,上面也讲了…

静态内部类

静态内部类不依赖外部对象,可通过创建或通过类名访问,可声明静态成public class Outer {private String name="xxx";private String age="18";//静态内部类:和外部类相同static class Inne…

SAP ABAP ALV 布局

SAP ABAP ALV 布局百度:ABAP ALV布局 前言 在SAP ABAP开发中ALV是用于展示结构化数据的一个强大工具。I_DEFAULT, I_SAVE, 和 IS_VARIANT 是在调用ALV相关函数(如 REUSE_ALV_GRID_DISPLAY 或通过 SALV 类)时…

05_mysql备份方案

05_mysql备份方案1.为什么要数据备份保护服务器数据安全 维护公司运维资产7*24小时运转企业丢失了数据,就等于失去了商机,客户,产品,甚至倒闭 在各种各样的数据中,数据库的数据是核心中的核心,当然其他各式各样的…

实验1_CPP

实验1 源代码 #include <iostream> #include <string> #include <vector> #include <algorithm>template <typename T> void output(const T &c);void test1(); void test2(); void…

08 数组

08 数组数组 初始化 dataType[] name = new dataType[size];#静态初始化:创建+赋值 int[] a ={1,2,3};#动态初始化 int[] b=new int[10];内存声明数组: "name"被压入栈中.创建数组: 在堆中开辟空间,每个元素…

CF2153 Codeforces Round 1057 (Div. 2) 游记

仅开出两题,靠手速掉分不太多,回到蓝名边缘。省流 仅开出两题,靠手速掉分不太多,回到蓝名边缘。10.10 内含剧透,请vp后再来。 不是题解!!!!!!! 赛前 忘了。因为补以前的旧账会写的很简单。 赛时 A 题不谈。…

Java 架构师系列:JVM 与 AI 负载的优化策略 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

面向新质生产力,职业院校“人工智能”课程教学解决方案 - 教程

面向新质生产力,职业院校“人工智能”课程教学解决方案 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Co…

从《花果山》到《悬鉴》:一首诗的蜕变与AI元人文理论的建构历程

从《花果山》到《悬鉴》:一首诗的蜕变与AI元人文理论的建构历程 摘要 本文系统梳理了2025年8月21日至10月13日期间,作者通过持续修订旧作《花果山》,在诗学层面实现四重突破,并基于现实事件的激发,最终完成《七绝…

java循环

do while循环会至少循环一次 // for each public class Main { public static void main(String[] args) { int[] ns = { 1, 4, 9, 16, 25 }; for (int n : ns) { System.out.println(n); } } } System.out.println(Ar…

10.13做题笔记

[CSP-S 2022] 策略游戏 看了一眼发现就是分讨一下,然后维护四个东西:最大,最小,大于零的最小,小于零的最大。 然后经过杰哥提醒,发现分讨太愚蠢了,可以把四个东西先拿下来,枚举小 L 取的,然后小 Q 选择一个使…