⭐️ C语言进阶 ⭐️ 自定义类型:结构体(位段),枚举,联合(一)

简介: 深入掌握结构体,枚举,联合的使用和特点,以及学会明白位段


结构体struct


  • 定义:

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量


结构体的声明

  • 普通声明:


struct tag
{
 member-list;
}variable-list;


  • 特殊的声明:不完全的声明(匿名结构体类型)


  • 示例:


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


  • 注意:

对于匿名结构在声明的时候省略掉了结构体标签(tag),也就是只能在声明的时候进行操作(声明外再次使用无法进行调用(没有名称))


  • 示例:


//在上面代码的基础上,下面的代码为err
p = &x;
//编译器会把上面的两个声明当成完全不同的两个类型


结构的自引用

在链表中我们需要用到的就是结构的自引用


  • 示例:


//创建链表节点
struct Node
{
 int data;
 struct Node* next;
};


  • 易错点:
typedef struct
{
 int data;
 Node* next;
}Node;
//只有在重命名后才能使用重命名名
//正确写法:
typedef struct Node
{
 int data;
 struct Node* next;
}Node;


结构体变量的定义和初始化

  • 示例1:单类型结构体


struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};


  • 示例2:多类型结构体



struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化


  • 示例3:嵌套型结构体


struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化


空结构体大小

  • 示例:


struct student
{
}stu;
int main (void)
{
  printf ("sizeof (stu) = %d\n", sizeof (stu));
  return 0;
}
输出结果:
在C中, sizeof (stu) = 0
在C++中, sizeof (stu) = 1


结论:

对于空结构体不同编译器理解不同,所以大小不一(可能0或者1(作为占位符))


结构体内存对齐

定义:

struct中的各成员变量的存储地址有一套对齐的机制(让CPU能够更舒服地访问变量)


总体来说:

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


原因:

平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常


性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问


对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数为编译器默认的一个对齐数与该成员大小的较小值。(VS中默认的值为8)
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍,如果不满足,在最后一个成员后面填充
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍


示例1:


struct S1
{
 char c1;
 int i;
 char c2;
};
printf("%d\n", sizeof(struct S1));
//输出结果:12


  • 解释:


image.png


  1. 第一个成员c1在与结构体变量偏移量为0的地址处
  2. 对于c2它的对齐数为4(int大小为4,小于平台默认值8),该变量要对齐到偏移量为4的倍数处,即从偏移量为4的位置开始存放
  3. 对于c3(char类型的对齐数为1,正数都为1的倍数),从偏移量为9的位置开始放
  4. 该结构体的总大小须为最大对齐数(每个成员变量都有一个对齐数)(这里也就是4)的整数倍,故为12(已经占用了9个字节)


示例2:


struct S2
{
 char c1;
 char c2;
 int i;
};
printf("%d\n", sizeof(struct S2));
//输出结果:8


  • 解释:
  1. 第一个成员c1在与结构体变量偏移量为0的地址处
  2. c2放在偏移量为1的地址处
  3. i放在偏移量为4的地址处(对齐到偏移量为对齐数4的倍数处)


  • 示例3:


struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3));
//输出结果:16


  • 解释:
  1. 第一个成员d在与结构体变量偏移量为0的地址处
  2. c放在偏移量为8的地址处
  3. i放在偏移量为12的地址处(对齐到偏移量为对齐数4的倍数处)


  • 示例4:


struct S4
{
 char c1;
 struct S3 s3;
 double d;
};
printf("%d\n", sizeof(struct S4));
//输出结果:48


解释:

  1. 第一个成员c1在与结构体变量偏移量为0的地址处
  2. s3放在偏移量为8的地址处(s3最大对齐数为8)
  3. d放在偏移量为24的地址处(对齐到偏移量为对齐数8的倍数处)
  4. 总大小为成员变量最大对齐数的倍数(也就是16的倍数)即大小为32


结论:

尽量让占用空间小的成员尽量集中在一起(既满足对齐,又节省空间)(如示例1与示例2)


修改默认对齐数

使用#pragma 这个预处理指令来改变我们的默认对齐数


示例:


#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
 char c1;
 int i;
 char c2;  
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\t", sizeof(struct S1));
    printf("%d\t", sizeof(struct S2));
    return 0;
}
//输出结果:12    6


  • 结论:

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


宏offsetof

  • 作用:

计算结构体中某变量相对于首地址的偏移,并给出说明


#include<stdio.h>
#include<stddef.h>
struct s
{
  char c;
  int i;
  double d;
};
int main()
{
  // offsetof其实是一个宏,用来表示成员相对于结构体的偏移量
    //而且offsetof的参数传的是一个类型,更加说了offsetof是一个宏
  printf("%d\n", offsetof(struct s, c));// 0
  printf("%d\n", offsetof(struct s, i));// 4
  printf("%d\n", offsetof(struct s, d));// 8
  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;
}


  • 结论:

其实效果都一样,两者都可以选择,但是推荐结构体传址


  • 原因:
  1. 函数传参的时候,参数是需要压栈的,压栈会占用空间
  2. 如果传递一个结构体对象的时候,结构体过大的话,那么参数压栈的的系统开销比较大,会导致性能下降


柔性数组


定义:

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员


使用:

  1. sizeof 返回的这种结构大小不包括柔性数组的内存
  2. 用malloc()函数进行内存动态分配,分配的内存应该大于结构的大小,以适应柔性数组的预期大小
  3. 用malloc函数分配了内存,肯定就需要用free函数来释放内存


示例:


#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct data
{
    int len;    //一般用来表示字符数组的字符个数 
    char name[];//空间大小为0
}S; 
int main(void)
{
    S s;
    printf("sizeof(s)=%d\n",sizeof(s));//输出为4,即是int类型大小
    int len = 10; //申请空间 
    struct data *p =(struct data*)malloc(sizeof(s)+sizeof(char)*len);
    //判断是否申请成功&请空处理 
    p->len = len;
    strcpy(p->name,"xxxxxx"); //字符串赋值需要用strcpy
    printf("%s\n",p->name); 
    //释放指针p
    free(p);
    return 0;
}
//输出结果:xxxxxx


struct与class的区别


在C++里struct关键字与class关键字一般可以通用


  • 只有一个很小的区别:

struct的成员默认情况下属性是public的,而class成员却是private的



相关文章
|
16天前
|
C语言
【C语言程序设计——循环程序设计】枚举法换硬币(头歌实践教学平台习题)【合集】
本文档介绍了编程任务的详细内容,旨在运用枚举法求解硬币等额 - 循环控制语句(`for`、`while`)及跳转语句(`break`、`continue`)的使用。 - 循环嵌套语句的基本概念和应用,如双重`for`循环、`while`嵌套等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台将对编写的代码进行测试,并给出预期输出结果。 5. **通关代码**:提供完整的代码示例,帮助理解并完成任务。 6. **测试结果**:展示代码运行后的实际输出,验证正确性。 文档结构清晰,逐步引导读者掌握循环结构与嵌套的应用,最终实现硬币兑换的程序设计。
46 19
|
16天前
|
C语言
【C语言程序设计——枚举】得到 3 种不同颜色的球的可能取法(头歌实践教学平台习题)【合集】
本关任务要求从红、黄、蓝、白、黑五种颜色的球中,每次取出3个不同颜色的球,列举所有可能的排列情况。通过定义枚举类型和使用嵌套循环语句实现。枚举类型用于表示球的颜色,循环语句用于生成并输出所有符合条件的排列 编程要求:在指定区域内补充代码,确保输出格式正确且完整。测试说明:平台将验证代码输出是否与预期一致,包括每种排列的具体顺序和总数。 示例输出: ``` Output: 1 red yellow blue 2 red yellow white ... 60 black white blue total: 60 ```
36 4
|
1月前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
174 14
|
1月前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
204 10
|
2月前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
228 13
|
16天前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
52 23
|
16天前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
46 15
|
16天前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
54 24

热门文章

最新文章