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

开发笔记:自定义类型详解(结构体,枚举,联合体)

篇首语:本文由编程笔记#小编为大家整理,主要介绍了自定义类型详解(结构体,枚举,联合体)相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了自定义类型详解(结构体,枚举,联合体)相关的知识,希望对你有一定的参考价值。








自定义类型


  • 1. 结构体struct
    • 1.1 结构体类型的声明
    • 1.2 结构体的自引用
    • 1.3 结构体变量的定义和初始化
    • 1.4 结构体内存对齐
    • 1.5 结构体传参
    • 1.6 位段

  • 2. 枚举enum
    • 2.1枚举类型的定义
    • 2.2枚举的优点
    • 2.3枚举的应用

  • 3. 联合(共用体)union
    • 3.1联合类型的定义
    • 3.2联合的特点
    • 3.3联合所占内存大小的计算




1. 结构体struct

结构体是由一批数据组合而成的一种新的数据类型。组成结构型数据的每个数据称为结构型数据的“成员”。


1.1 结构体类型的声明

struct 结构体名
{
成员列表;
}

例:声明一个学生结构体类型,其中包含学生的姓名,学号,年龄,性别,地址。

struct Student
{
char NAME[20];
char Number[11]
char sex[3];
int age;
char address[20];
}

特殊的结构体声明:匿名结构体类型
在声明结构体时,不指出结构体名。当然由于没有具体的名字,所以该类结构体只能在声明的时候定义。
例如:

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

1.2 结构体的自引用

错误的自引用方式:

struct Node
{
int vale;
struct Node node;
}

  • 这种声明是错误的,因为这种声明实际上是一个无限循环,成员node是一个结构体,node的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。

正确的自引用方式:

struct Node
{
int vale;
struct Node *Next;
}

  • 由于指针的长度是确定的(在32位机器上指针长度为4),所以编译器能够确定该结构体的长度。

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

定义结构体变量
示例1:定义结构体变量不初始化。

#include
struct Node
{
int value1;
int Value2;
}
int main
{
int Vale;
return 0;
}

示例2.定义结构体变量并初始化。

#include
struct Node
{
int value1;
int Value2;
}
struct Student
{
char NAME[20];
int age;
}
struct Point1
{
struct Node node;
int a;
struct Student student;
}
struct Point2
{
int b;
struct Point2 *next;
}
int main
{
struct Node node={1 , 2};
struct Student student={"wangzhen",21};
struct Point1 point1={{1,2},3,{"wangzhen",21}};
struct Point2 point2={1,NULL};
return 0;
}

1.4 结构体内存对齐

近年来的热门考点,让你计算一个结构体所占内存大小。
也就是考察我们对内存对齐的概念的理解。
结构体内存对齐规则:


  • 结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
  • 在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。

//练习1
struct S1
{
//所有元素中最宽的元素为(int)类型,占四个字节
char c1;//1+3
int i; //4
char c2;//1+3
};
printf("%d\\n", sizeof(struct S1));//输出12,1+3+4+1+3=12.

在这里插入图片描述

//练习2
struct S2
{
//所有元素中最宽的元素为(int)类型,占四个字节
char c1;//1
char c2;//1+2
int i;//4
};
printf("%d\\n", sizeof(struct S2));//输出8,1+1+2+4=8.

在这里插入图片描述

//练习3
struct S3
{
double d;//8
char c;//1
int i;//1+6,//由于结构体存储单元需要为其最宽成员的整数倍,所以应该补6个字节
};
printf("%d\\n", sizeof(struct S3));//输出16

在这里插入图片描述

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

为什么要内存对齐?


  • 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能
    在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的
    内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
    总的来说,结构体对齐就是拿着空间换时间,在没有特殊要求得情况下尽量把空间小得成员集中在一起,有助于节省空间。

1.5 结构体传参

#include
#include
typedef struct Student
{
char Name[10];
int num;
}Student;
void test01(Student s)//结构体传参
{
printf("the student name is %s",s.Name);
}
void test02(Student* s)//结构体指针传参
{
printf("the student name is %s",s->Name);
}
int main()
{
Student student={"wangwu",10086};
test01(student);//传结构体
test02(&student);//传结构体的地址
}

test01与test02相比,明显test02更快,因为函数在传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。而传结构体地址的时候,就只有四个字节(64位8个字节)的内容需要压栈。所以test02更快。

综上:结构体传参的时候要传地址。因为传地址更快,效率更高。


1.6 位段

什么是位段
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
位段的声明:

struct s
{
int a:2;
int b:5;
int c:10;
int d:30;
};

位段的内存分配:


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

printf("%d",sizeof(s));//占8个字节
//因为位段的成员是int类型,是按照四个字节的方式来开辟的,a,b,c所占空间总共是17个bit,
//剩余的空间无法存储d,只能另外开辟四个字节的空间来存放d,
//所以改结构体s所占空间为8个字节。

例:

#include
#include
typedef struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
}S;
int main()
{
S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%d", sizeof(S));
system("pause");
return 0;
}

在这里插入图片描述
位段不能跨平台的原因是


  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
    与结构体相比,位段可以达到同样的效果,但位段跨平台会引起各种问题的出现

2. 枚举enum

枚举:在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。


2.1枚举类型的定义

enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};

以上定义都是枚举类型,{}中的内容是枚举类型的可能取值,也叫枚举常量。
这些值默认从一开始,依次递增1,同样也可以在定义的时候赋初值。


2.2枚举的优点


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

2.3枚举的应用

enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};

3. 联合(共用体)union

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。


3.1联合类型的定义

union Un//联合体声明
{
char c;
int i;
short s;
};
int main()
{
union Un un;//联合体定义
}

3.2联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

#include
union Un//联合体声明
{
char c;
int i;
short s;
};
int main()
{
union Un un;//联合体定义
printf("%p\\n",&un.c);
printf("%p\\n",&un.i);
printf("%p\\n",&un.s);
un.i=0x11223344;
un.c=0x00;
un.s=0x1111;
printf("%d\\n",un.i);
return 0;
}

在这里插入图片描述


3.3联合所占内存大小的计算


  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍





推荐阅读
  • OO第一单元自白:简单多项式导函数的设计与bug分析
    本文介绍了作者在学习OO的第一次作业中所遇到的问题及其解决方案。作者通过建立Multinomial和Monomial两个类来实现多项式和单项式,并通过append方法将单项式组合为多项式,并在此过程中合并同类项。作者还介绍了单项式和多项式的求导方法,并解释了如何利用正则表达式提取各个单项式并进行求导。同时,作者还对自己在输入合法性判断上的不足进行了bug分析,指出了自己在处理指数情况时出现的问题,并总结了被hack的原因。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 加密世界下一个主流叙事领域:L2、跨链桥、GameFi等
    本文介绍了加密世界下一个主流叙事的七个潜力领域,包括L2、跨链桥、GameFi等。L2作为以太坊的二层解决方案,在过去一年取得了巨大成功,跨链桥和互操作性是多链Web3中最重要的因素。去中心化的数据存储领域也具有巨大潜力,未来云存储市场有望达到1500亿美元。DAO和社交代币将成为购买和控制现实世界资产的重要方式,而GameFi作为数字资产在高收入游戏中的应用有望推动数字资产走向主流。衍生品市场也在不断发展壮大。 ... [详细]
author-avatar
曹lister_638
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有