自定义类型,结构体、枚举、联合(C语言)

news2024/9/27 15:34:31

目录

结构体

结构体的基础知识:

结构体的声明:

特殊声明:

结构体的自引用

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

结构体内存对齐:

修改默认对齐数:

结构体传参

结构体的柔型数组

柔型数组的书写

柔性数组的特点

柔性数组的使用

柔性数组的优势

位段

什么是位段

位段的内存分配

位段的跨平台问题

位段的使用

枚举

枚举类型的定义

枚举的优点

枚举的使用

联合(共用体)

联合类型的定义

联合的特点

联合大小的计算

联合面试题


结构体

结构体的基础知识:

结构是一些值的集合,这些值被叫做成员变量,这些成员变量可以是不同的类型

结构体的声明:

结构体关键字:struct ,用struct关键字来声明一个结构体,声明规则如下:

// tag是结构体标签
//member-list 是成员变量列表
//variable-list 是结构体变量列表,这里定义的变量是结构体全局变量

struct tag//结构体标签
{

    //是成员变量列表
    member-list

}variable-list;
//是结构体变量列表,这里定义的变量是结构体全局变量

创建一个学生类型:

//用结构体描述一个学生类型
//创建学生类型

struct Student
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号

}; //分号不能丢

特殊声明:

在声明结构体的时候,可不完全声明,也就是,结构体类型在声明的时候,省略掉了结构体标签(tag),此时的结构体类型我们可以理解为没有名字(标签)的结构体,此结构体我们称为匿名结构体,匿名结构体,只有在定义的同时创建变量,此时创建的变量是结构体全局变量,不能单独使用此类型创建变量,不能用一个结构体指针去指向另一个结构体,这样是不合法的,哪怕这两个结构体的成员变量完全相同,也是不合法的,编译器会把两个结构体的声明,看做是完全不同的两种类型,因为类型不同,所以不同类型的指针指向一种类型的变量是不合法的(等同于用 int 类型的指针指向 double类型的变量,是非法的),匿名结构体具体该如何声明定义呢?具体看以下代码:

//匿名结构体类型
struct
{
    int a;
    char b;
    float c;

}x;// x是结构体变量

struct
{
    int a;
    char b;
    float c;

}a[20], *p;//a是结构体数组,p是结构体指针变量


//上面两个结构体就是匿名结构体,
//两个结构体都省略了标签名
//匿名结构体只有在定义的时候创建结构体变量
//不能单独使用匿名结构体创建结构体变量!

//有一个重点:
int main()
{
    p=&x;
//这条语句是不合法的
//虽然这两个结构体成员变量相同
//但在编译器的角度是把它们看做不同类型的两种数据
//编译器会把它们的两个声明看成是完全不同的两种类型
//所以是非法的

    return 0;
}

结构体的自引用

我们知道结构体的成员可以是不同类型的,所以结构体成员也可以是结构体类型,那么我们就可以通过一个结构体访问另一个结构体,既然结构体的成员变量可以是结构体,那么我们思考一个问题,一个结构体成员列表中是否可以存放这个结构体自身呢?也就是通过它的成员变量来访问自身(结构体的自引用),该如何通过它的成员变量访问到自身呢?我们在自引用的时候只能用它的结构体指针来访问它自身,不能直接在成员变量中放入它自身创建的结构体变量去访问,(因为结构体变量是要开辟空间的,我们在自引用的时候写到成员变量的位置,还不确定此结构体究竟有多大,无法知道它的大小,所以用结构体变量不合适,是非法的,因为该结构体类型还没创建出来,而用它自身的结构体指针,只要是指针它的大小就是确定32位下是4个字节,64位下是8个字节,所以我们确切的知道结构体该成员的大小,是合法的),具体如下代码:

1、通过一个结构体访问另一个结构体:

//通过一个结构体访问另一个结构体
// 
//通过结构体变量访问
struct A
{
	int a;
	char b;
	float c;
};
struct B
{
	int a;
	char b;
	struct A ss;//创建一个A结构体类型变量ss,可通过ss变量访问结构体A
	float c;
};

//通过结构体指针访问
struct C 
{
	int a;
	float b;
	char c;
};
struct D
{
	int a;
	float b;
	struct C* p;//创建一个C结构体类型指针p,可通过指针p方位内结构体C
	char c;
};

int main()
{
	//1、成员变量是结构体变量的方式访问另一个结构体

	//定义B结构体变量bb 并初始化
	struct B bb = { 10,'a',{3,'r',3.25},3.14 };
	//通过B结构体成员变量访问结构体A
	printf("%d %c %.2f\n", bb.ss.a, bb.ss.b, bb.ss.c);

	//2、成员变量是结构体指针的方式访问另一个结构体
	//创建C结构体变量cc并初始化
	struct C cc = { 4,4.4,'a' };
	//创建D结构体变量dd并初始化
	struct D dd = { 5,5.5,&cc,'b' };//让D结构体中的成员变量p指向 C结构体的变量cc
	//通过D结构体的成员变量p访问C结构体的变量
	printf("%d %.2f %c\n", dd.p->a, dd.p->b, dd.p->c);

	return 0;
}

2、结构体自身引用自己

#include <stdio.h>
#include <stdlib.h>

//结构体自己引用自己

错误的引用方式
//struct Stun
//{
//	char name[10];//姓名
//	int age;//年龄
//	char eax[5];//性别
//	struct Stun student;//创建Stun结构体变量
//};

//正确的引用方式
struct Stu
{
	char name[10];
	int age;
	char eax[5];
	struct Stu* p;//创建Stu结构体指针
};

int main()
{
	//Stu结构体成员p指针自己引用自己
	struct Stu ss = { "张三",18,"男",&ss };
	//通过自己的成员指针访问自己
	printf("%s  %d   %s\n", ss.p->name, ss.p->age, ss.p->eax);

	return 0;
}

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

结构体是我们自定义的类型,它也是一种类型,我们知道通过类型是可以创建变量的,如:通过整型int创建整型变量,通过浮点型创建浮点型变量........,所以通过我们自定义的结构体类型也是可以创建变量的,而变量是可以初始化的,结构体变量也可以初始化,那么结构体变量究竟如何定义又如何初始化呢?  具体如何做,以代码的形式进行注释说明:

#include <stdio.h>
#include <stdlib.h>

//创建结构体类型
struct pp
{
	int a;
	float b;
	char c;
}s1;// 创建结构体全局变量s1
struct pp s2;//创建结构体全局变量s2
//s1和s2 都是全局变量

struct stu
{
	int a;
	int b;
	int c;
}t1={12,13,14};//定义全局变量且同时初始化
struct stu t2 = { 1,2,3 };//定义全局变量并初始化

//用typedef重命名的结构体类型不能直接在括号后面创建全局变量
//因为括号后面要写结构体的新名字
typedef struct sinput
{
	float a;
	float b;
	float c;
}ss;
ss n1;//通过新名字创建变量

//用一个结构体访问其他结构体
struct A
{
	int a;
	struct pp s4;
	struct stu* t4;
}m1 = { 12,{6,3.55,'c'},NULL};//结构体嵌套初始化
struct A m2 = { 45,{58,2.66,'p'},&t2 };//结构体嵌套初始化

int main()
{
	struct pp s3;//定义结构体变量 是局部变量

	struct stu t3 = { 4,5,6 };//定义局部变量并初始化

	ss n2 = { 3.14,2.56,4.23 };	//通过新名字创建变量并初始化

	struct A m3 = { 34,{56,3.99,'q'},&t1 };//结构体嵌套初始化

	return 0;
}

结构体内存对齐:

从以上内容,我们大致了解了结构体的使用,那么一个结构体类型的大小是多少呢?也就是一个结构体类型在内存中占用字节的个数,因为结构体的成员变量的类型是不同的,所以结构体没有特定的大小,此时我们要知道结构体的大小,就得自己通过计算得到一个结构体类型的大小,那么结构体类型的大小该怎么计算呢?是直接将其成员类型的大小加起来嘛(这种计算方法是错误的),而正确的计算方法是:结构体的内存对齐,要按照内存对齐规则,来计算结构体的大小

结构体内存对齐规则:

1、第一个成员在与结构体变量偏移量为0的地址处一字节一字节的开始存放

2、其它成员要对齐到对齐数的整数倍处

     (就是从这个对齐数的整数倍处的偏移量位置开始存放)

     对齐数 = 编译器默认的对齐数 与 该成员大小的 较小值 (vs的默认对齐数是8)

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

4、嵌套结构体的情况,嵌套的那个结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍!

用vs编译器为例,讲解几个例子:

 代码1: 

#include <stido.h>

struct poin
{
	int a;
	char b;
	float c;
};

int main()
{
	//打印结构体大小
	printf("%zd\n", sizeof(struct poin));//大小为12

	return 0;
}

解析:

答案:12

看图简单明了:

代码2:

#include <stdio.h>

struct poin
{
	int a;
	char b;
	float c;
};

struct ps
{
	int a;
	struct poin s1;
	char b;
};

int main()
{
	//打印结构体的大小
	printf("%zd\n", sizeof(struct ps));//大小是20

	return 0;
}

解析:

答案:20

看图简单明了:

为什么要有内存对齐?

有两种说法:

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

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常,所以将数据对齐到它能访问到的地址处,以便于硬件平台的访问读取!
2、性能原因

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法


修改默认对齐数:

结构体在对齐方式不合适的时候,我们可以自己修改结构体的默认对齐数,我们用 #pragma 这个预处理指令来修改默认对齐数!具体实现看代码:

#include <stdio.h>

//将结构体 S1 的默认对齐数设置为5 
#pragma pack(5)//设置默认对齐数为5
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

//将结构体 S2 的默认对齐数设置为1 
#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{
	//输出的结果是什么?
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));

		return 0;
}

结构体传参

对于结构体传参,我们传结构体本身,跟指针都可以,但建议传结构体指针,因为结构体在传参的时候会有压栈操作,当把结构体本身传过来,如果结构体过大,参数压栈的系统开销就比较大,性能就相对比较弱,可若是传指针,只传过去一个指针,压栈开销较小,性价比较高,所以结构体传参的时候尽量传指针是一个好的选择!

看以下代码:

#include <stdio.h>

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); //传地址
//print2首选
	return 0;
}

总结:结构体在传参的时候,要传结构体的地址!

结构体的柔型数组

自C99标准开始规定:结构体中的最后一个成员可以是未知大小的数组,而这个数组就叫柔性数组

柔型数组的书写

两种书写方式:

1、在给定数组大小的时候什么都不写

2、数组的大小给定为0

由于编译器的不同,柔性数组的书写方式也就出现了分歧,有些编译器支持只第一种书写方式,而有些只支持第二种书写方式

具体如下代码:

//第一种
typedef struct st_type
{
	int i;
	int a[];//柔性数组成员
}type_a;

//第二种
typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_b;

柔性数组的特点

柔性数组有三大特点:

1、结构中的柔性数组成员前面必须至少有一个其他成员。
2、sizeof 返回的这种结构大小不包括柔性数组的内存。

3、包含柔性数组成员的结构用malloc ()函数(在主页动态内存管理这篇文章中有详细介绍)进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

具体看代码:

#include <stdio.h>

typedef struct st_type
{
	int i; //柔性数组前面必须至少要有一个其它成员
	int a[0];//柔性数组成员
}type_a;

int main()
{
	//结构的大小不包含柔性数组的内存
	printf("%d\n", sizeof(type_a));//4

	return 0;
}

柔性数组的使用

我们知道了柔性数组的概念,以及特点,那柔性数组该怎么使用呢?其实很简单就是用malloc在堆区给柔性数组开辟一块空间供柔性数组使用,但在开辟空间的时候要将柔性数组之前的结构成员所占用的大小字节要加上,用结构体指针来接收开辟好的空间,此时可以通过结构体指针来访问给柔性数组开辟的空间了

具体看代码;

#include <stdio.h>

struct PP
{
	int a;
	int arr[0];//柔性数组成员
};

int main()
{
	//创建结构体指针
	struct PP* ptr=NULL;

	//用结构体指针来接收开辟好的空间,且给柔性数组开辟了10个整型空间
	ptr = (struct PP*)malloc(sizeof(struct PP) + 10 * sizeof(int));

	//进行访问
	ptr->a = 49;//给结构成员a赋值为49

	//访问柔性数组空间,且给其赋值
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ptr->arr[i] = i + 1;
	}
	
	//打印柔性数组的内容
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ptr->arr[i]);
	}

	free(ptr);//将malloc开辟的堆区空间进行释放
	ptr = NULL;//将指针置为空,避免野指针

	return 0;
}

柔性数组的优势

对于上述柔性数组使用的代码,我们可以直接在结构里面定义一个整型指针来实现,直接用malloc开辟10个整型的空间让结构体的指针来接收,也是可以实现上述的使用的,

具体看代码:

#include <stdio.h>

struct PP
{
	int a;
	int* arr;//整型指针
};

int main()
{
	//创建结构体指针
	struct PP* ptr = NULL;

	//开辟好整型指针之前的成员的空间用ptr接收
	ptr = (struct PP*)malloc(sizeof(struct PP));

	//开辟10个整型空间,让arr指针接收
	ptr->arr = (int*)malloc(sizeof(int) * 10);

	//进行访问
	ptr->a = 49;//给结构成员a赋值为49

	//访问柔性数组空间,且给其赋值
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ptr->arr[i] = i + 1;
	}

	//打印柔性数组的内容
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ptr->arr[i]);
	}

	free(ptr->arr);//将malloc开辟的10个整型空间释放
	ptr->arr = NULL;//将结构成员的整型指针arr置为NULL,避免野指针

	free(ptr);//将malloc为整型指针前面的成员开辟的堆区空间进行释放
	ptr = NULL;//将指针置为空,避免野指针

	return 0;
}

此代码在申请空间的时候申请了两次,两次空间是分开申请的,而在free的时候释放了两次指针,由此我们对比可看出,柔性数组比指针更加有优势!

柔性数组的优势:

1、方便内存释放

当我们在函数里写了上述整型指针接收空间的代码,这时候若是只返回结构体指针,用户最后在使用的时候最后只释放了结构体指针指向的空间,而整型指针成员申请的空间不会被释放,函数返回之后也没人知道它的存在,也不能再找到它的位置进行释放,这是一个弊端,然而你用柔性数组的时候,把结构体的内存以及其成员要的内存一次性分配好当把结构体的指针传过来,在释放的时候会将所有申请的空间,一次性释放干净!

2、有利于访问速度

在内存中我们申请空间的时候,每一块我们所用到的空间与空间之间是有间距的,而这个间距我们称之为:内存碎片,上述例子我们用指针成员申请空间的时候,会申请两块空间,此时产生内存碎片较多,而当我们用柔性数组之间申请一块内存空间的时候,此时产生的内存碎片就少!

位段

什么是位段

位段相对于结构体而言,就是为了节省空间,位段里面的成员占用的空间的大小是我们自己定义的,位段的声明跟结构体是类似的,只有两点不同:

1、位段的成员必须是整型家族(int,unsigned int,signed int ,char....)

2、位段的成员后面有一个冒号和数字(数字就是该成员占用的位数,是我们自己定义的)

定义一个位段:

//A就是一个位段
//冒号后面的数字就是这个成员所占用的字节数

struct A
{
	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. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

分配空间:

位段在分配空间的时候,是先按照成员类型,开辟1个或者4个字节的空间,然后将成员变量从上往下,按照他们后面的数字,也就是它们所占用的位数(比特位),来进行截断存放,当开辟的1个或者4个字节的空间放不下其中的一个成员变量的时候,再继续开辟1个或者4个字节的空间,直到将所有成员所占用的位数(比特位)放完为止,最后开辟的1个或者4个字节的空间的总和就是位段的总大小,也就是位段占用的字节数!

求位段的大小

如下代码:

#include <stdio.h>

struct S
{
    char a:3;
    char b:4;
    char c:5;
    char d:4;
};

struct S s = {0};

//空间是如何开辟的?
int main()
{
    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;

    printf("%zd\n",sizeof(struct S));

    return 0;
}

解析:

答案:3

画图解释:

位段的跨平台问题

位段有跨平台的问题,位段的跨平台性很差,所谓跨平台,通俗点说就是,系统跟机器的位数不一样,就是不同的平台,计算机硬件+操作系统就是一个平台,有的机器是大端存储,有的机器是小端存储,有32位的机器,也有64位的机器,有不同的操作系统,也有不同的计算机硬件,所有就存在很多平台,而跨平台性就是能否在各种情况下去使用这段代码,在任何平台上功能都是相同的,这样的意思。

位段的跨平台性就很差,原因有四个:

1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。

跟结构体相比,位段更加有利于节省空间,但位段具有跨平台的问题,使用需要看实际情况,看自己的代码是否要考虑跨平台性能!

位段的使用

对一些需要分开讨论的数据分类,假设对其进行进行模块化的划分,几个字节表示啥,又几个字节表示啥,这个时候就要用到位段了,可以给其知道要占用的位数,后续在工作中我们将会彻底了解位段的用处!

枚举

枚举就是把可能的取值一一列举出来,比如现实生活中,一周的星期一到星期天有限的7天,可以一一列举出来,还有性别有男有女也可以一一列举,月份有12个月,也可以一一列举等等......这些东西都可以以常量的形式一一列举出来,枚举类型又被叫做枚举常量,它里面存放的成员都是常量,可以说它是一些常量的集合!那么枚举类型如何定义,又如何使用呢?我们耐心往下看:

枚举类型的定义

枚举关键字enum ,用枚举关键字来定义枚举类型,enum+标签名+大括号+枚举常量列表,就可以定义一个枚举类型,具体实现看代码:

enum poin     //poin是枚举标签
{
	//常量列表
	constant_list
};

我们实际定义几个枚举类型如下代码:

//将一星期一一列举出来
enum Day
{
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday,
	Sunday
};

//将性别一一列举出来
enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};

//将三原色列举出来
enum Color//颜色
{
	RED,
	GREEN,
	BLUE
};

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。{}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值,赋值后的那个枚举常量后面的取值是从那个位置开始自上而下一次递增1,而被赋值的那个枚举常量前面的取值还是从0开始一次递增1,具体看代码解释:


 

//枚举类型可以定义在主函数外面,也可以定义在主函数里面
//但定义在主函数里面的话这个枚举类型只能在主函数内部使用

enum P
{
	A,
	B,
	C,
	D
};

int main()
{
	//枚举类型P中,当不给任何常量赋初始值
	//值自上而下从0开始一次递增1
	printf("%d\n", A);//0
	printf("%d\n", B);//1
	printf("%d\n", C);//2
	printf("%d\n", D);//3

	enum Q
	{
		AA,
		BB = 20,
		CC,
		DD
	};
	//枚举类型Q中,给Q赋值20时,往后的常量依次增加1,
	//而前面的常量还是从0开始递增1
	printf("%d\n", AA);//0
	printf("%d\n", BB);//20
	printf("%d\n", CC);//21
	printf("%d\n", DD);//22

	return 0;
}

枚举的优点

我们知道枚举的成员都是常量,那既然是常量,我们用#define来定义不就行了,为什么要有枚举呢?枚举相对于#define是有很多优点的:

枚举的优点:

1、增加代码的可读性和可维护性

2、枚举是一种类型,有类型检查,更加严谨,#define无类型检查

3、防止常量命名污染(封装起来的)

4、便于调试

5、使用方便,一次可以定义多个常量,而#define一次只能定义一个

枚举的使用

枚举是是一种类型,我们知道是类型就可以创建变量,那么枚举类型也可以创建变量,而枚举类型变变量的赋值,只能拿枚举常量,也就是枚举类型的可能取值来赋值,若是赋其他值,会存在类型差异,枚举类型具体该如何使用呢?如下代码:

#include <stdio.h>

enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};

enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
// clr = 5; //ok??  枚举变量在初始化之后不能再改变其值,因为它具有常量属性不可被修改

int main()
{
	printf("%d\n", clr);
	return 0;
}

联合(共用体)

联合类型的定义

联合关键字 union 联合也是一种特殊的自定义类型,联合类型的定义也包含一系列的成员,联合的特征是这些成员共用同一片空间(所以联合体也叫共用体)。联合体也是一种类型,是类型就可以定义变量,那么联合体类型该怎么声明和定义变量呢?具体看以下代码:

#include <stdio.h>

//联合体类型的声明
union Un
{
	char a;
	int b;
	float c;
};
int main()
{
	//联合变量的定义
	union Un ss;

	//计算联合体的大小,
	//因为联合体的成员共用同一片空间,
	//所以联合体至少要能容纳下成员中占用空间最大的成员的大小
	printf("%zd\n", sizeof(ss));//4

	return 0;
}

联合的特点

联合体的成员是共用同一块内存空间的,由此可知,一个联合体的大小至少是最大成员的大小(因为联合体至少得有能力保存最大的那个成员)。联合体的特点就是所用成员共用一片空间,但是几个成员不能同时使用一片空间,但一个成员在使用的时候其它成员不能使用,直到使用结束其它成员才可以使用!联合体的使用其实就是从最低位开始,由低到高每个成员类型不同占用(访问)的空间不同,画图大致理解:

 看完图之后我们再看一份代码,加深理解:

#include <stdio.h>

union Un
{
	int i;
	char c;
};
union Un un;//创建联合全局变量

int main()
{
	//因为共用同一片空间,
	// &地址的时候,取出来的是最低位的地址,所以取出来的地址相同!
	//联合的成员是从低到高访问的
	printf("%d\n", &(un.i));
	printf("%d\n", &(un.c));

	return 0;
}

联合大小的计算

我们知道联合的特点就是共用同一片内存空间,联合的大小至少是最大成员的大小,看以下代码:

#include <stdio.h>

union Un1
{
	char c[5];
	int i;
};

union Un2
{
	short c[7];
	int i;
};
//下面输出的结果是什么?

int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

代码解析:

答案:8 16

为什么会是 8 和16呢?我们知道联合的大小至少是最大成员的大小,这里面注意两个字,至少,说明联合的大小可能还要比最大成员的大小 大一些,那么联合的大小究竟是怎样计算得来的呢?

计算规则:

1、联合体的大小至少是最大成员的大小

2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍(关于对齐数,我们上面说结构体的时候已经说过了!)

那我们回头再看这个代码,画图说明:

 仔细看图理解,我们就可理解联合体的大小是如何计算的了

联合面试题

题目:用联合体判断当前机器是小端字节序存储还是大端字节序存储!

分析:这道题,我们最重要的就是要知道联合体的特点:联合体的成员共用块内存空间,在共用的时候是从低地址往高地址访问,类型多大访问的空间就多大,通过这个思路,我们可以创建一个联合体类型,往联合体里面放两个成员变量,放个int型变量,和char型变量,然后在int变量的空间里面存入数字1,再通过char类型去访问它的第一个字节的空间,通过大小端字节序的概念(前面数据的存储的文章中有大小端字节序的介绍)可知:若该空间的值为1说明是小端存储,若为0说明是大端存储,

代码答案:

#include <stdio.h>

union P
{
	int a;
	char b;
};

int main()
{
	union P ss;//创建联合体变量
	ss.a = 1;//给联合体的第一个成员赋值为1

	if (ss.b == 1)//ss.b访问联合体的第二个成员
	{
		printf("小端字节序存储\n");
	}
	else
	{
		printf("大端字节序存储\n");
	}
	return 0;
}

解析:

说到这里基础薄弱的老铁或许会有很多疑惑,那么不着急,接下来我来带大家进行画图细致说明:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/170285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java寒假打卡】JavaWeb-Tomcat

【Java寒假打卡】JavaWeb-Tomcat服务器Tomcat下载和安装Tomcat的目录结构基本使用控制台乱码的问题IDEA集成TomcatJavaWeb项目的目录结构Tomcat-idea发布项目Tomcat-WAR包发布项目Tomcat配置文件的介绍Tomcat配置虚拟目录Tomcat配置虚拟主机服务器 Tomcat下载和安装 将下载好的…

干货 | 数据安全和个人信息保护审计的方法研究

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;概述我们的研究核心是个人信息保护合规审计&#xff0c;具体指个人信息处理活动是否遵守我国相关法律法规的监督性审计。在个保法出台后&#xff0c;我国形成了以内部审计为…

我用ChatGPT写神经网络:一字不改,结果竟很好用

自从去年底推出以来&#xff0c;对话式 AI 模型 ChatGPT 火遍了整个社区。 ChatGPT 的确是一个了不起的工具&#xff0c;就像一个「潘多拉魔盒」。一旦找到正确的打开方式&#xff0c;你或许会发现&#xff0c;自己再也离不开它了。 作为一个全能选手&#xff0c;人们给 Chat…

Fedora 38发布Budgie与Sway定制版

导读两款新的 Fedora 定制版将在 Fedora 38 发布时首次亮相。我们期待着它们在 Fedora 37 时出现&#xff0c;但在 Fedora 38 中终于来了&#xff01; 早在 2022 年 5 月&#xff0c;Budgie 项目的主要开发者 Joshua Strobl ​​宣布​​&#xff0c;Budgie 已被提交到 Fedora…

第五届字节跳动青训营 前端进阶学习笔记(四)TypeScript入门

文章目录前言TypeScript概要1.什么是TypeScript2.TypeScript基本语法基础数据类型对象类型函数类型函数重载数组类型补充类型泛型约束和泛型默认参数类型别名和类型断言高级类型1.联合类型2.交叉类型3.类型守卫类型谓词总结前言 课程重点&#xff1a; TypeScript概要TypeScri…

Kubernets核心介绍及实战

1、资源创建方式 命令行YAML 2、Namespace 名称空间用来隔离资源 “namespace"通常被翻译为「命名空间」&#xff0c;听起来好像比较抽象&#xff0c;其实重点是在这个"space”。它和描述进程的虚拟地址空间的address space一样&#xff0c;都是提供一种独占的视角…

linux引导和启动程序

1.BIOS/Bootloader: 一上电&#xff0c;硬件强制让cpu的cs:ip寄存器指向bios程序的位置&#xff0c;从bios程序开始执行&#xff0c;由pc机的BIOS &#xff08;0xFFFFO是BIOs存储的总线地址&#xff09;把bootsect从某个固定的地址拿到了内存中的某个固定地址&#xff08;0x90…

SpringAMQP快速入门

介绍Spring AMQP 项目将核心 Spring 概念应用于基于 AMQP 的消息传递解决方案的开发它提供了一个“模板”作为发送和接收消息的高级抽象它还通过“侦听器容器”为消息驱动的 POJO 提供支持这些库促进了 AMQP 资源的管理&#xff0c;同时促进了依赖注入和声明性配置的使用包含两…

纷享销客华为云CXO思享会华东系列活动成功举办!

3天&#xff0c;3座城市&#xff0c;5家标杆企业&#xff0c;11位不同领域的专家&#xff0c;超百位企业家云集。1月10日-13日&#xff0c;纷享销客与华为云联合举办的“数字创新 高效增长”CXO思享会华东系列活动成功举办。全国各地超百名企业家齐聚华东参与本次思享会&#x…

kubeasz安装kubernetes1.25.5

kubeasz安装k8s 1 配置 kubeasz安装kubernetes&#xff0c;只需要做好网通配置&#xff0c;做好ssh免密通信配置即可 1.1 环境介绍 OS&#xff1a;CentOS Linux release 8.5.2111 机器: IPhostname10.104.10.201k8s-master10.104.10.202k8s-node 所有机器&#xff0c;都将…

3-4存储系统-虚拟存储器(CO)

文章目录一.页式存储1.页式存储系统2.逻辑地址到物理地址的转换3.页表4.快表TLB二.虚拟存储器&#xff08;一&#xff09;页式虚拟存储器&#xff08;二&#xff09;段式虚拟存储器&#xff08;三&#xff09;段页式虚拟存储器一.页式存储 1.页式存储系统 为提高主存的空间利…

JS中的splice方法添加或删除数组中的元素

splice方法嘚吧嘚语法下标问题实战splice(index)splice(index,howmany)示例一(howmany&#xff1e;0)示例二(howmany ≤ 0)splice(index,howmany,item1,.....,itemX)howmany ≤ 0示例一(index ≥ 0)示例二(index&#xff1c;0)howmany&#xff1e;0示例一(index ≥ 0)示例二(in…

Spring Cloud Kubernetes 本地开发

简介 Spring Cloud Kubernetes 是spring官方集成Kubernetes的一个框架.可以直接将springboot项目使用Kubernetes做为注册中心.很方便,但是我们本地开发的时候很难进行调试,因为我们本地没有Kubernetes环境. 下面介绍一种能够本地开发的方式 KT-Connect KtConnect&#xff08;Kt…

【倍增+最短路】P1613 跑路

不知道是因为这样的套路太典了还是因为什么&#xff0c;这难度只有绿题&#xff0c;可是我感觉好难想到QwQ不过今天写了好几道倍增&#xff0c;好像有点感觉了捏P1613 跑路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)题意&#xff1a;思路&#xff1a;一开始的思路就是建图…

【内存函数】-关于内存的操作函数

作者&#xff1a;小树苗渴望变成参天大树 作者宣言&#xff1a;认真写好每一篇博客 作者gitee&#xff1a;gitee 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 内存函数前言一、[memcpy](https://cplusplus.com/reference/cstring/memc…

初识软件测试

作者&#xff1a;~小明学编程 文章专栏&#xff1a;测试开发 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 什么是软件测试&#xff1f; 软件测试和软件开发的区别&#xff1f; 调试和测试的区别&#xff1f; 优秀的测试人员应该具备哪些素质&#xff…

cherry-pick的定义和使用方法

1、定义 使用遴选&#xff08;cherry-pick&#xff09;命令&#xff0c;Git 可以让你将任何分支中的个别提交合并到你当前的 Git HEAD 分支中。当执行 git merge 或者 git rebase 时&#xff0c;一个分支的所有提交都会被合并。cherry-pick 命令允许你选择单个提交进行整合。 …

正点原子STM32(基于HAL库)3

目录RTC 实时时钟实验RTC 时钟简介RTC 框图RTC 寄存器硬件设计低功耗实验电源控制&#xff08;PWR&#xff09;简介电源系统电源监控电源管理PVD 电压监控实验PWR 寄存器硬件设计睡眠模式实验硬件设计停止模式实验PWR 寄存器硬件设计待机模式实验PWR 寄存器硬件设计ADC 实验ADC…

全国产加固以太网交换机选择技巧

全国产加固交换机用于连接以太网设备&#xff1a;首先接收由某台设备发出的数据帧&#xff0c;然后再将这些帧传送到与其它以太网设备相连的适当交换机端口上。随着它传送这些帧&#xff0c;学习并掌握以太网设备的位置&#xff0c;并用这些信息来决定该用哪些端口来传送帧&…

83. 深度循环神经网络及代码实现

1. 回顾&#xff1a;循环神经网络 2. 更深 再看公式&#xff1a; 3. 总结 深度循环神经网络使用多个隐藏层来获得更多的非线性性 4. 代码简洁实现 实现多层循环神经网络所需的许多逻辑细节在高级API中都是现成的。 简单起见&#xff0c;我们仅示范使用此类内置函数的实现方式…