进阶C语言 第四章-------《自定义类型》 (结构体、枚举、联合)知识点+完整思维导图+深入细节+通俗易懂+基本练习题+建议收藏(一)

简介: 进阶C语言 第四章-------《自定义类型》 (结构体、枚举、联合)知识点+完整思维导图+深入细节+通俗易懂+基本练习题+建议收藏(一)

绪论

       书接上回,通过上章的一些函数,我们可以让我们对于一些数值的调整有很大的帮助,本章自定义类型在C语言中同样也有着非常重要的地位,相信只要认真的阅读了本章,一定会对你有很大的帮助。image.png

所以安全带系好,发车啦(建议电脑观看)。


思维导图:

image.png

要XMind思维导图的话可以私信哈


目录


1.结构体

1.1结构体的声明

1.2结构体变量的初始化

1.3结构体内存对齐

1.4修改默认对齐数

1.5结构体传参

2.位段

3.枚举(enum)

3.1枚举的定义

4.联合体(共用体union)

1.结构体

结构是指一些值的集合,这些值被称为成员变量,而结构体成员可以是不同的类型(类似数组,数组也是一些值的集合,不过这些数组的值的类型是统一的)。

而结构体就像一张张体检表,上面有着一个人的许多信息,或者某样物的许多信息的集合

1.1结构体的声明

知识点:

语法:

struct name

{

       member;

       .....;

};

对于里面的成员至少是1个;

下面直接通过代码的形式来展示如何声明(让这个结构体合法)一个结构体

struct Stu
{
  char name[20];//名字
  int age;//年龄
  char sex[5];//性别
};

       这样就声明了一个结构体,其实结构体的声明也可以看成创建了一个新的类型,里面的成员有着不同的类型来修饰这个新的类型,这样就能对于一些复杂对象进行描述如上的学生

细节(注意点):

一种特别的声明匿名结构体类型(不完全声明):

struct
{
  int a;
};

此时的声明没有名字,只能跟在该声明后创建变量名/用typedef的实现

而对于结构体变量的定义有两种方法:

直接跟在结构体后面,这种是全局的

在函数内以结构体类型 + 变量名 的形式来创建一个变量(结构体类型是一种自定义类型),并且是局部的

具体如下:

//方法1
struct
{
  int a;
}s1,s2;//可以同时创建一个或多个结构体变量(s1,s2)
struct Stu student = { "张三",20,"男"};//(全局的)
//方法2
struct Stu
{
  char name[20];
  int age;
  char sex[5];
};
int main()
{
  struct Stu student = { "张三",20,"男"};//(结构体变量student)
  return 0;
}

所以对于匿名结构体来说他是没有结构体的名字的所以就会导致无法进行第二种,只能第一种直接在结构体后面创建变量,来使用和初始化。

用typedef来对结构体来进行简化重命名

typedef struct Stu
{
    int age;
    char name[20];
    char sex[5];
}Stu;

当typedef重命名匿名结构体时让匿名结构体就可以让其在其余地方用

typedef struct
{
  int a;
}a;
int main()
{
  a b = { 20 };
  printf("%d", b.a);    
  return 0;
}

但是要注意当嵌套时不能省略里面

typedef struct Stu
{
    int name;
    struct Stu * Node;//不能写成Stu* Node因为此时还不行因为重命名还在后面才执行
}Stu;

1.2结构体变量的初始化

知识点:

对于结构体变量的初始化的方法同样分为了两种方法,对应着创建结构体变量的两种方法:

在全局的结构体变量后直接初始化

struct Stu
{
  char name[20];
  int age;
  char sex[5];
}s1 = { "张三",20,"男"};

在局部的结构体变量后再初始化

struct Stu
{
  char name[20];
  int age;
  char sex[5];
  struct Stu* a;
};
int main()
{
  struct Stu student1;
  struct Stu student = { "张三",18,"男",&student1 };
  printf("%s %d %s %p", student.name, student.age, student.sex,student.a);
  return 0;
}

单独逐一初始化

struct Stu
{
  char name[20];
  int age;
  char sex[5];
}student;
int main()
{
  student.age = 25;
//字符串数组的话,你要想赋值,你就必须要通过拷贝
    strcpy (student.name ,"LiSi");
    strcpy (student.sex ,"男"); 
  printf("%s %d %s",student.name,student.age,student.sex);  
  return 0;
}

细节:

不能在自己的结构体成员中有和自己一样的结构体类型这样的话这个类型的大小将无限大,(要创建只能是别的结构体类型)

struct Stu
{
  int age;
};
struct Stu
{
  char name[20];
  int age;  
  //struct Stu b;error
  struct Stu p;  
    char sex[5];
}s1 = { "张三",20,{0},"男"};
int main()
{
  printf("%s %d %s", s1.name, s1.age, s1.p.age , s1.sex);
  return 0;
}

当创建了结构体变量后,对于结构体变量初始化,只能单独改变了,不能同时改变了(只能在struct Stu student = {....}这种形式时改)

struct Stu
{
  char name[20];
  int age;
  char sex[5];
};
int main()
{
  struct Stu student1 = { "张三",20,"男" };
  student = { "lisi",18,"nan" };error
  student.age = 25;
  printf("%s %d %s", student.name, student.age, student.sex);
  return 0;
}

乱序初始化:

struct Stu
{
  char name[20];
  int age;
  char sex[5];
  struct Stu* a;
};
int main()
{
  struct Stu student1;
  struct Stu student = { .a = &student1 , .age = 20 , .name= "lisi",.sex= "nan"};
  printf("%s %d %s %p", student.name, student.age, student.sex,student.a);
  return 0;
}

1.3结构体内存对齐

知识点:

是当我们要求一个结构体大小时就需要用到结构体内存对齐

对齐规则:

第一个成员变量直接对齐到偏移量为0的地址处

从第二个成员变量开始要对齐到偏移量为自身对齐数的整数倍处                                                  对齐数 = 判断自身大小和默认对齐数并取较小值(在vs环境下默认的一个对齐数值是8)

结构体的总大小最终要为所有成员变量中的取过的最大对齐数的整数倍

如果有镶嵌结构体,那这个最大对齐数的判断也要包括所镶嵌的结构体 内的 成员的对齐数 ,并且这个镶嵌结构体也要对齐到自身的最大的对齐数上(在外部的结构体内)

附:在Linux gcc 环境下没有默认对齐数 对齐数就是其本身大小

下面通过例子就可以更好的去理解和记忆

细节:

计算偏移量的宏offsetof

两个参数:offsetof(结构体类型,成员名)

头文件:#include<stddef.h>

为什么要有内存对齐

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(如int 4 double 8)

性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。(例:在32位机器上一次读取数据是4byte 假如结构体存中存着char 和 int  其中char 要占1byte int 占 4byte 这样总共就是5byte  那当我们要读取int 时机器就会先读取前面的4byte 那int还剩1byte要读所以就会再读一次,可若当我们进行了内存对齐就只需要一次就能将4byte的int 读完)

总体来说:结构体的内存对齐是拿空间来换取时间的做法。

所以因为存在内存对齐:所以一个成员相同的结构体,其大小都有可能不同:

struct s
{
    char c1;
    int i;
    char c2;  
};// 1 -> 4 + 4 -> 8 + 1 -> 12(要是最大对齐数的整数倍)
struct c
{
    char c1;
    char c2;
    int i;
};// 1 -> 1 + 1 -> 4 + 4 -> 8 

所以我们要有内存对齐的概念并且通过改变写法来节约空间

所以我们在写结构体时应该经量将小的成员放在一起

练习:

例1:

#include<stdio.h>
#include<stddef.h>
struct S1
{
    char c1;
    int i;
    char c2;
};
int main()
{
    printf("%d\n", sizeof(struct S1));   
    printf("%d\n", offsetof(struct S1,c1));
    printf("%d\n", offsetof(struct S1,i));
    printf("%d\n", offsetof(struct S1, c2)); 
}

分析:

1.首先将c1对齐到偏移量为0处                                                                                                      

2.其次看其余的成员变量:

i --> 自身大小为4byte 、 默认对齐数大小为8byte 、取较小值4为最终的对齐数(取较小值)

所以i要对齐到偏移量为4的倍数的地址去,0处往后就有偏移量为4处的地址可以 存i的4byte

c2 --> 自身大小为1byte、默认为8、取1为对齐数(取较小值)

c2要对齐到偏移量为1的倍数中去,4偏移处存了i的4byte后 就到了8偏移处 往后的任意位置都是1倍数,所以在8偏移处存c2                                                                                                    

3.最后:从0 ~ 8 结构体的总大小变成了9并不是移处不是最大对齐数(1 4 1)4的倍数,所以最终要放到偏移量为11处,所以最终大小为12byte(0 ~ 11)

image.png

偏移量:可以看成距离起始位置(箭头指向的)的距离(看线)

image.png


附加一种我自己的判断方法(不用画图直接口算):

第一个成员直接放(毋庸置疑),从第二个成员开始就要满足从对齐数的整数倍地址放,这样举个例子来解释:如当一个int 类型大小为4 所以就要从偏移量(而这个偏移量就直接找倍数即可不用想的太多)为 4处开始 ,那么就有:

从某一开始的偏移量(满足对齐数的倍数) +  自身元素大小 = 总大小  /  所到的偏移量处(继续往后开始的偏移量)

这个公式是我自己总结的可以细品,多来两道体就可以很快的算出结构体大小

具体如下:

struct name
{
    int a;
    char b;
    int c;
    int d;
};

直接口算:

先放了int 这样就是 0 + 4 = 4 此时还没放完  所以是位移到了4偏移量处 (继续往后开始的偏移量)

再放char 此处因为char的对齐数肯定是1 所以可以放到任何位置  4 + 1 = 5

再放int 应该找到8偏移处开始, 所以就是 8 + 4 = 12 移动到12偏移处(继续往加开始的偏移量,直接找倍数"8")

再放int 直接从12开始 ,12 + 4 = 16 就为最终的总大小

再快一点就可以直接 :4 -> 4 + 1 -> 8(直接找倍数"8")+ 4 - > 12(已经是4的倍数) + 4 = 16

image.png

例2:

struct S3
{
  double d;// 对齐数8
  char c;// 1
  int i;//4
};//总大小为 8 -> 8 + 1 -> 12 + 4 -> 16
struct S4
{
  char c1;// 1
  struct S3 s3;// 16
  double d;// 8
};
//此处double类型的大小是8 默认也是8 所以最终对齐数就是8 ,其次注意嵌套结构体 放在外部的结构体时要对
    //齐到嵌套结构体内部最大对齐数的整数倍即8的倍数
int main()
{
  printf("%d\n", sizeof(struct S4));//总大小为:1 - > 8(直接找倍数) + 16 - > 24 + 8 -> 32
  return 0;
}

附:对于结构体成员是数组是如 char arr[5];这是其实可以直接把他们看成5和char类型的成员,char c1 ;char c2 ;char c3 ;char c4 ;char c5;在用对齐知识进行正常对齐  

1.4修改默认对齐数

知识点:

如标题所示:默认对齐数是可以修改的方法如下:

#pragma pack( )在括号内填所要修改成的值:如#pragma pack( 4 )

如果#pragma pack( )单独出现时表示回复默认对齐数

细节:

不能随意的修改默认对齐数应该根据所需,如当修改成了#pragma pack( 1 )这样其实也就不存在内存对齐了

就会导致这时的对齐数一定都是1

所以当对齐方式不合适时,我们就可以自行修改默认对齐数

1.5结构体传参

知识点:

当我们把结构体变量放在函数里时,我们同样可以分成值传递、和址传递。

具体如下:

struct s
{
    int a;
    char b;
}s1;
void print1(struct s s1)
{
    printf("%d\n",s1.a);
    printf("%c\n",s1.b);
}
void print2(struct s * s1)
{
    printf("%d\n",(*s1).a);
    printf("%c\n",(*s1).b);
    printf("%d\n",s1->a);
    printf("%c\n",s1->b);
}
int main()
{
    print(s1);
    print(&s1);
    return 0;
}

当然,print2传地址肯定比print1传值是要好的(函数传参要压栈,如果结构体过大对于时间和空间上都会有损失),所以最好直接统一直接传结构体的地址

练习:通讯录(学生信息管理系统)

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