C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(上)

简介: C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)

本篇将对C语言自定义类型进行讲解

1.结构体(struct)

前面简单讲过结构体,这里将会把前面结构体还没讲完的知识继续补充。复习链接:

1.1 结构的基础知识

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

1.2 结构的声明

 
struct tag  //结构体关键字+标签  合起来是结构体类型
{
    member - list;   //成员变量列表
}variable - list;   //变量列表

例如描述一个学生:

 
struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}; //分号不能丢

1.3 匿名结构体

//匿名结构体类型(一次性使用)

 
struct
{
    int a;
    char b;
    float c;
    double d;
} s;
 
struct
{
    int a;
    char b;
    float c;
    double d;
} *ps;

对于上面的代码如果进行如下操作,是非法的

 
int main()
{
    ps = &s; // error
    return 0;
}

1.4 结构的自引用

介绍:结构体中包含一个类型为该结构体本身的成员,包含同类型的结构体指针(不是包含同类型的结构体变量)

 
struct A
{
    int i;
    char c;
};
 
struct B
{
    char c;
    struct A sa;
    double d;
};

注意事项1:结构体不能自己包含自己,不能包含同类型的结构体变量

 
struct N
{
    int d;
    struct N n; //结构体里不能存在结构体自己类型的成员
};

为了加深理解,先引入一下数据结构的一些知识:

注意事项2:结构体自引用时,不要用匿名结构体:

 
struct  // 如果省略结构体名字
{
    int data;
    struct Node* next; // 这里的 struct Node* 是哪里来的?
};

即使使用 typedef 重新取名为 Node,也是不行的。因为要产生 Node 必须先有结构体类型之后才能重命名 Node,即先 Node* next 定义完成员之后才 typedef 才能对这个类型重命名为 Node。

所以这种方式仍然是不行的:

 
typedef struct
{
    int data;
    Node* next; // 先有鸡还是先有蛋???
} Node;

正确方法:

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

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

有了结构体类型,那如何定义变量,其实很简单。

 
struct S
{
    char c;
    int i;
} s1, s2; // 声明类型的同时创建变量
 
int main()
{
    struct S s3, s4;
 
    return 0;
}

创建变量的同时赋值(初始化)

 
struct S
{
    char c;
    int i;
} s1, s2;
 
int main()
{
    struct S s3 = {'x', 20};
//                  c    i
 
    return 0;
}

结构体包含结构体的初始化方法

 
struct S
{
    char c;
    int i;
} s1, s2;
 
struct B
{
    double d;
    struct S s;
    char c;
};
 
int main()
{
    struct B sb = {3.14, {'w', 100}, 'q'};
    printf("%lf %c %d %c\n", sb.d, sb.s.c, sb.s.i, sb.c);
    
    return 0;
}

1.6 结构体内存对齐

现在已经掌握了结构体的基本使用了。

深入讨论一个问题:计算结构体的大小。

这也是一个特别热门的考点: 结构体内存对齐

首先得掌握结构体的对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的值为8)

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

 
//练习1
#include <stdio.h>
struct S
{
     char c1;
     int i;
     char c2;
};
int main()
{
    struct S s = {0};
    printf("%d\n", sizeof(s));//12
    return 0;
}

d71165441c954d48a56e90802fb82d14.png

 
//练习2
struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct S2));//8
 
//练习3
struct S3
{
    double d;//8
    char c;//1
    int i;//4
};
printf("%d\n", sizeof(struct S3));//16
 
//练习4-结构体嵌套问题
#include <stdio.h>
struct S4
{
    double d;
    char c;
    int i;
};
struct S5
{
    char c1;
    struct S4 s4;
    double d;
};
int main()
{
    struct S5 s5 = {0};
    printf("%d\n", sizeof(s5));
    return 0;
}
//上面第4点: 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
//结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//此题嵌套的结构体对齐到自己的最大对齐数就是8(所以浪费了下面的7个字节)且32是8的倍数

cb442a1d516c479ab65048040b9bddbe.png 为什么存在内存对齐? 大部分的书籍都是这样说的:

1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;

某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2. 性能原因

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;

而对齐的内存访问仅需要一次访问。

总体来说:

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

那在设计结构体的时候,既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起。

 
//例如:
struct S1
{
    char c1;
    int i;
    char c2;
};
 
struct S2
{
    char c1;
    char c2;
    int i;
};
//S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。
//S1大小是12 而S2大小是8

1.7 修改默认对齐数

之前我们见过了 #pragma 这个预处理指令,这里再次使用,可以改变默认对齐数。

 
#include <stdio.h>
// 默认对齐数是8
#pragma pack(2) // 把默认对齐数改为2(一般改为2的几次方)
struct S
{
    char c1; //1
    int i; // 4
    char c2; // 1
};
#pragma pack() // 取消
int main()
{
    printf("%d\n", sizeof(struct S)); //修改默认对齐数后12变为8
    return 0;
}

c8a1f24c6c344813ad7aaf98ab972c78.png

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


百度笔试题:

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

注:这里还没学习宏,可以放在宏讲解完后再自己实现。

考察: offsetof 宏的实现

该宏用于求结构体中一个成员在该结构体中的偏移量。

头文件: stddef.h

使用方法演示:

 
#include <stdio.h>
#include <stddef.h>
struct S
{
    char c1; //1
    int i; // 4
    char c2; // 1
};
int main()
{
    printf("%d\n", offsetof(struct S, c1));//0
    printf("%d\n", offsetof(struct S, i));//4
    printf("%d\n", offsetof(struct S, c2));//8
    return 0;
}

C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(中):https://developer.aliyun.com/article/1513097

目录
打赏
0
0
0
0
47
分享
相关文章
|
1月前
|
【C语言程序设计——循环程序设计】枚举法换硬币(头歌实践教学平台习题)【合集】
本文档介绍了编程任务的详细内容,旨在运用枚举法求解硬币等额 - 循环控制语句(`for`、`while`)及跳转语句(`break`、`continue`)的使用。 - 循环嵌套语句的基本概念和应用,如双重`for`循环、`while`嵌套等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台将对编写的代码进行测试,并给出预期输出结果。 5. **通关代码**:提供完整的代码示例,帮助理解并完成任务。 6. **测试结果**:展示代码运行后的实际输出,验证正确性。 文档结构清晰,逐步引导读者掌握循环结构与嵌套的应用,最终实现硬币兑换的程序设计。
52 19
|
1月前
|
【C语言程序设计——枚举】得到 3 种不同颜色的球的可能取法(头歌实践教学平台习题)【合集】
本关任务要求从红、黄、蓝、白、黑五种颜色的球中,每次取出3个不同颜色的球,列举所有可能的排列情况。通过定义枚举类型和使用嵌套循环语句实现。枚举类型用于表示球的颜色,循环语句用于生成并输出所有符合条件的排列 编程要求:在指定区域内补充代码,确保输出格式正确且完整。测试说明:平台将验证代码输出是否与预期一致,包括每种排列的具体顺序和总数。 示例输出: ``` Output: 1 red yellow blue 2 red yellow white ... 60 black white blue total: 60 ```
51 4
c语言回顾-联合和枚举
c语言回顾-联合和枚举
53 4
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体
深入理解 C 语言中的联合体
深入理解 C 语言中的联合体
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
63 23
|
1月前
|
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
70 15
|
1月前
|
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
61 24
|
1月前
|
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
68 16

热门文章

最新文章