热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:C语言结构体,枚举,共用体

篇首语:本文由编程笔记#小编为大家整理,主要介绍了C语言----结构体,枚举,共用体相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了C语言----结构体,枚举,共用体相关的知识,希望对你有一定的参考价值。






1)结构体

//例:一个描述学生的结构体
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};

匿名结构体

//例1:
struct
{
int a;
char b;
float c;
}x;
//例2:
struct
{
int a;
char b;
float c;
}a[20], *p;


  1. 在这里我们会认为p = &x;没有问题,但是编译器会将两个声明当成完全不同的两个类型,是非法的
  2. 匿名结构体创建后只能用一次

结构的自引用

错误的自引用(会造成死递归):

struct N
{
int a;
struct N n;
};


在线性数据结构中有顺序表和链表,顺序表及连续存放数据的空间,
但令一种方式在内存中的存放没有规律,但是通过将结构体分为数据域和指针域,指针域及为下一个结构体的地址,只要知道第一个数据就可以得到之后的数据,这就是链表,如图


在这里插入图片描述
正确的自引用(包含同类型的结构体指针,而不是变量):

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

注意,以下代码:

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


上面代码错误 执行会先创建Node* next变量才会去typedef


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

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

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

结构体内存对齐
例:

struct S3
{
double d;
char c;
int i;
};
printf("%d\\n", sizeof(struct S3));
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
}
printf("%d\\n", sizeof(struct S4));
打印 16 32

在这里插入图片描述



计算结构体的大小


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





为什么存在内存对齐?


  1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

为了要满足对齐,又要节省空间,在设计结构体的时候,让占用空间小的成员尽量集中在一起
S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有区别

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

或者我们可以修改默认对齐数

#pragma pack(8)//设置默认对齐数为8
#pragma pack()//取消设置的默认对齐数,还原为默认

结构体传参

//结构体传参
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.位段的成员必须是 int、unsigned int 或signed int ,char(整形家族)
2.位段的成员名后边有一个冒号和一个数字。






位段的内存分配:


  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段





位段的跨平台问题:


  1. int 位段被当成有符号数还是无符号数是不确定的
  2. 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

:

struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;


a:10:二进制:01010
b:12:二进制:01100
c:3: 二进制:00011
d:4: 二进制:00100



在这里插入图片描述
2)枚举

枚举类型大小和int一样占4个字节
例:性别枚举

enum sex
{
male,
female,
secret
}

使用(只能拿枚举常量给枚举变量赋值,才不会出现类型的差异)

enum sex linda = female;

限定female来自于sex,加上限定符'::'

enum sex linda = sex::female;

这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值

枚举相对于#define的优点



如:#define N 5
#define是在预编译阶段直接将N替换为5(不便于调试)


  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量


3)联合(共用体)






推荐阅读
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了基于c语言的mcs51单片机定时器计数器的应用教程,包括定时器的设置和计数方法,以及中断函数的使用。同时介绍了定时器应用的举例,包括定时器中断函数的编写和频率值的计算方法。主函数中设置了T0模式和T1计数的初值,并开启了T0和T1的中断,最后启动了CPU中断。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
author-avatar
Gala彬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有