自定义类型详解(结构体,位段,枚举, 联合)

自定义类型详解

1、结构体

1.1、结构体声明

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

struct tag
{
	member-list;
}variable-list;
#include <stdio.h>
#include <stdlib.h>

//结构体是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
//一般声明
struct Stu
{
	char name[20];
	int age;
	char sex[5];
	char id[20];
};

//特殊声明
//匿名结构体类型,下面两个结构体的声明的时候省略掉了结构体标签(tag)
struct
{
	int a;
	char b;
	float c;
}x;

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

int main()
{
	p = &x;
	system("pause");
	return 0;
}

当结构体声明的时候省略了结构体标签,编译器会把两个相同类型值组成的结构体声明为两个不同的类型,是非法操作。
1.2、结构体的自引用

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

//结构体中包含一个类型为该结构体本身的成员
struct Node
{
	int data;
	struct Node *next;
};

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

int main()
{

	system("pause");
	return 0;
}

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

#include <stdint.h>
#include <stdlib.h>

struct Point
{
	int x;
	int y;
}p1;		//声明类型的同时定义变量p1

//赋初值
struct Point p3 = { 2, 3 };

struct Student		//类型声明
{
	char name[15];
	int age;
};

struct Student s = { "zhangsan", 20 };//初始化


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

struct Node3 n2 = { 20, { 5, 6 }, NULL };//结构体嵌套初始化

int main()
{

	system("pause");
	return 0;
}

1.4、结构体内存对齐

结构体对齐规则:
1、第一个成员与结构体变量偏移量为0的地址处。
2、其他成员变量对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
注:vs默认对齐数为8,Linux的默认对齐数为4

3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4、如果嵌套了结构体,嵌套结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

为什么存在内存对齐:
1、移植原因:不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构应该尽可能在自然边界对齐,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问。
结构体内存对齐选择空间换时间,提高性能

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

/*
结构体的对齐规则:
1、第一个成员与结构体变量偏移量为0的地址处。
2、其他成员变量对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值  
注:vs默认对齐数为8,Linux的默认对齐数为4
3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4、如果嵌套了结构体,嵌套结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
*/

struct s1
{
	char c1;
	int i;
	char c2;
};

struct s2
{
	char c1;
	char c2;
	int i;
};

struct s3
{
	double d;
	char c;
	int i;
};

struct s4
{
	char c1;
	struct s3 s;
	double d;
};


int main()
{
	printf("%d\n", sizeof(struct s1));//12
	printf("%d\n", sizeof(struct s2));//8
	printf("%d\n", sizeof(struct s3));//16
	printf("%d\n", sizeof(struct s4));//32
	system("pause");
	return 0;
}

我们在设计结构体的时候,我们既要满足对齐,又要节省空间
我们采取占用空间小的成员尽量集中在一起。
1.5、修改默认对齐数

使用 #pragma pack(字节数)修改默认对齐数

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

//修改默认对齐数为8
#pragma pack(8)

struct s1
{
	char c1;
	int i;
	char c2;
};

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


//修改默认对齐数为1
#pragma pack(1)

struct s2
{
	char c;
	int i;
	char c2;
};

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

int main()
{
	printf("%d\n", sizeof(struct s1));
	printf("%d\n", sizeof(struct s2));
	system("pause");
	return 0;
}

1.6、结构体传参
结构体传参的时候传结构体地址,防止在参数压栈的时候系统开销过大,导致性能下降

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

//结构体传参首选传地址,这样结构体过大的情况下可以防止参数压栈过程中开销过大,导致性能下降的问题

struct qwe
{
	int data[2000];
	int num;
};

struct qwe q = { { 1, 2, 3, 4 }, 1000 };

//结构体传参
void Print1(struct qwe q)
{
	printf("%d\n", q.num);
}

void Print2(struct qwe *q)
{
	printf("%d\n", q->num);
}

int main()
{
	Print1(q); //传结构体
	Print2(&q);//传地址
	system("pause");
	return 0;
}

2、位段

2.1、什么是位段

位段和结构体类似
1、未断电额成员必须是int、unsigned int或signed int
2、位段的成员名后面跟所占的位数

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

//1、位段的成员必须是int、unsigned int或signed int
//2、位移的成员名后边有一个冒号和一个数字。
//3、与结构体类似。

struct A
{
	int a : 2;//位段a占2位
	int b : 5;//位段b占5位
	int c : 10;//位段c占10位
	int d : 30;//位段d占30位
};

int main()
{
	printf("%d\n", sizeof(struct A));
	system("pause");
	return 0;
}

2.2、位段的内存分配

1、位段的成员可以是int、unsigned int、signed int或者char(属于整形家族)类型
2、位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
3、位段的使用要避免跨平台

2.3、位段的跨平台问题

1、int位段在不同的编译器不能确定是不是有无符号类型
2、位段中最大位的数目不能确定。
3、位段的成员在内存中是从左向右还是从右向左定义尚未定义
4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段的剩余位段是利用还是舍弃不确定

使用位段可以达到和结构体相同的效果,比结构体节省空间,但是存在跨平台问题。

3、枚举

3.1、枚举类型的定义

枚举类型的定义:{}中内容是枚举类型的可能取值,叫枚举常量

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

//enum默认从0开始,依次递增1


//枚举的优点:
/*
1、增加代码的可读性和可维护性
2、和#define定义的标识符比较,枚举有类型检查,更加严谨
3、防止了命名污染(封装)
4、便与调试,使用过方便,一次可以定义多个常量
*/
enum Day//星期
{
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday,
	Sunday
};

enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};

enum Color//颜色
{
	Red,
	Orange,
	Yellow,
	Green,
	Blue,
	Purple
};

int main()
{

	system("pause");
	return 0;
}

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

3.2、枚举的使用

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

enum Color1
{
	Red = 1,
	Orange = 2,
	Yellow = 3,
	Green = 4,
	Blue = 5,
	Purple = 7
};

int main()
{
	enum Color1 clr = Green;
	printf("%d\n", clr);
	system("pause");
	return 0;
}

4、联合

4.1、联合的特点

联合的成员共用一块内存空间,这样一个联合变量的大小,至少是最大成员的大小。

联合的使用:

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

//联合体变量声明
union Un
{
	char c;
	int i;
};

int main()
{
	//联合体的定义
	union Un un;
	printf("%d\n", sizeof(un));
	system("pause");
	return 0;
}

联合的特点:

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

union UN
{
	int i;
	char c;
};

int main()
{
	union UN un;
	printf("%d\n", sizeof(un.i));
	printf("%d\n", sizeof(un.c));

	un.i = 0x11223344;
	un.c = 0x55;
	printf("%d\n", sizeof(un.i));
	system("pause");
	return 0;
}

4.2、联合体大小的计算

联合体大小的计算规则:
1、联合的大小至少是最大成员的大小
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

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

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

union UN2
{
	short s[7];
	int i;
};

int main()
{
	union UN1 un1;
	union UN2 un2;
	printf("%d\n", sizeof(un1));//8
	printf("%d\n", sizeof(un2));//16
	system("pause");
	return 0;
}
发布了117 篇原创文章 · 获赞 48 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/gp1330782530/article/details/104513149