一文带你玩转自定义类型

 作者主页:paper jie的博客_CSDN博客

本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。

本文录入于《系统解析C语言》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将算法基础知识一网打尽,希望可以帮到读者们哦。

其他专栏:《算法详解》《C语言》《C语言-语法篇》等

内容分享:本期将对c语言中的自定义类型进行详细的讲解,各位看官姥爷快搬好小板凳坐好叭。

    -------- 不要998,不要98,只要一键三连,三连买不了吃亏,买不了上当

目录

结构体

结构体的声明

特殊的声明

结构体的自引用

结构体的定义和初始化

结构体内存对齐 

练习

内存对齐的原因

修改默认对齐数

 结构体传参

位段

位段的含义

位段的内存分配

位段的跨平台问题

位段的应用

枚举

枚举类型的定义

枚举的优点

枚举的使用

联合体

联合体的定义

联合的特点

联合体大小的计算


结构体

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

结构体的声明

注意:它的分号不能丢了

//栗子:这里定义一个学生
struct Stu
{
	int age;
	char name[20];
	char sex[5];
};

特殊的声明

在声明结构体的时候,还有一种特殊的声明,就是匿名结构体类型:

//栗子:
struct
{
	int a;
	char b;
	float c;
}s1;

struct
{
	int a;
	char b;
	float c;
}*p;

上面两个结构体省略了结构体的名字,这时有一个问题就是:

p = &s1在编译器上是编译不过去的。原因就是编译器把他们两个当成了不同的结构体类型。

结构体的自引用

在结构体中包含一个类型为该结构本身的成员是可以的:

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

结构体的定义和初始化

声明类型的时候可以定义变量,也可以在声明完后定义。初始化可以在定义完后初始化,也可以定义的同时初始化。

struct point
{
	int a;
	int b;
}s1;   //声明的同时定义
struct point s2; //先声明再定义结构体变量
//初始化:定义的同时初始化
struct point s3 = { 1,2 };


struct Stu
{
	int age;
	char name[20];
};

struct Stu s = { 20,"zhangsan" };

struct Node
{
	int date;
	struct Stu s1;
	struct Node* next;
}n1 = {1, {19, "lishi"}, NULL}; //结构体嵌套初始化

struct Node n2 = { 2, {25,"wangwu"}, NULL };//结构体嵌套初始化

结构体内存对齐 

对于结构体内存对齐有以下几点规则:

第一个成员放在与结构体变量偏移量为0的位位置

其他变量要对齐到对齐数的整数倍处的地址处

对齐数是编译器默认的一个对齐数与该成员大小的较小值(vs默认值为8,gcc,Linux没有默认对齐数,对齐数是成员自身大小)

结构体总大小必须是最大对齐数的倍数

如果是嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数)

练习

//练习一
struct s1
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%d\n", sizeof(struct s1));
}

c1为首成员,对齐偏移量为0的位置,占1个字节。i为4个字节小于8,对齐数为4,从&c1+3的位置开始存放i。c2为1个字节小于8,对齐数为1,接在i后面存放。又因为它们加起来为9不是最大对齐数的倍数,所以要提升为12.

//练习二
struct s2
{
	char c1;
	char c2;
	int i;
};

int main()
{
	printf("%d\n", sizeof(struct s2));
}

 

//练习3
struct S3
{
	double d;
	char c;
	int i;
};

int main()
{
	printf("%d\n", sizeof(struct S3));
}

//练习4
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};

int main()
{
	printf("%d\n", sizeof(struct S4));
}

 

内存对齐的原因

平台原因:

不是所有的硬件平台都能访问任意地址上的数据。某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

性能原因:

数据结构应该尽量的在自然边界上对齐。原因就是为了访问未对齐的内存,处理器需要进行两次内存访问。而对齐的内存只需要访问一次。

总得来说就是内存对齐是用来拿空间换时间的做法。

所以在设计结构体的时候我们要尽量的满足对齐,又节约空间。做法就是让空间小的成员尽量在一起:

//错误的做法
struct S1
{
	char c1;
	int i;
	char c2;
};
	 
//正确的做法
struct S2
{
	char c1;
	char c2;
	int i;
};

修改默认对齐数

修改对齐数我们需要用到一个预处理指令:#pragma,它可以改变我们的默认对齐数:

#pragma pack(8)
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()

#pragma pack(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;
}

 在我们遇到对齐方式不合适的时候,我们就可以用pragma来改变默认对齐数。

 结构体传参

结构体传参有两种方法:一种是直接传参,一种是地址传参。一般来说是地址传参比较好。

因为:函数传参需要压栈,会有时间和空间的开销。如果传递的结构体对象过大,那么参数压栈的开销就会比较大,所以会导致性能的下降。

struct Stu
{
	char name[20];
	char sex[4];
	int age;
};
struct Stu s = { "zhangsan","nan", 20 };

//结构体传参
void print_f(struct Stu s)
{
	printf("%s\n", s.name);
}

//结构体地址传参
void print_t(struct Stu* s)
{
	printf("%d\n", s->age);
}

int main()
{
	print_f(s);//传结构体
	print_t(&s);//传地址
	return 0;
}

位段

位段的含义

段位和结构体是类似的,但是有两点不同:

段位的成员必须是int,unsigned int, signed int

段位的成员名后面有一个冒号和一个数字

举个栗子:

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

位段的内存分配

段位的成员是int unsigned int signed int 或者是char类型

段位的空间是按照需要以4个字节或者1个字节的方式开辟的。

段位涉及很多不确定的因素,段位是不跨平台的,注重可移植的程序需要避免使用段位。

栗子:

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

int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

位段的跨平台问题

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

位段的应用

它可以极大的减少数据的空间,是数据可以高速传递

枚举

 枚举就是可能的情况一一列举。比如:生活中的星期,月份,性别都可以一一列举

枚举类型的定义

enum day
{
	mon,
	tues,
	wed,
	thur,
	fri,
	sat,
	sun,
};

enum color
{
	red,
	green,
	blue
};

以上定义的day和color都是枚举类型,{}中的内容是枚举类型的可能取值,叫枚举常量。这些值都是有默认值的,默认从0开始,每次增加1,在定义的时候也是可以赋值的:

enum day
{
	mon = 1,
	sun = 7,
	sat = 6
};

枚举的优点

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

和#define的标识符比较枚举有类型检查,更加严谨

防止了命名污染(封装)

便于调试

使用方便,一次可定义多个常量

枚举的使用

int main()
{
	enum color str = red; //只有拿枚举类型给枚举常量赋值,才不会出现类型的差异
	str = 6;
	return 0;
}

联合体

联合体的定义

联合体是一种特殊的自定义类型。这种类型的变量包括了许多的成员,他的特点是这些成员共同使用一块空间。

举个栗子:

union un
{
	char c;
	int i;
};
//联合变量的定义
union un u;

联合的特点

联合体的成员公用一块空间,这样的联合体大小至少是最大成员的大小。

通过下面代码可以发现,它们公用一块空间,且还会改变对方的值

union un
{
	int i;
	char c;
};

int main()
{
	union un u;

	printf("%p\n", &(u.i));
	printf("%p\n", &(u.c));

	u.i = 0x11223344;
	u.c = 0x55;
	printf("%x\n", u.i);
	return 0;
}

联合体大小的计算

联合的大小至少是最大成员的大小

当最大成员的大小不是最大对齐数的倍数的时候,就要对齐到最大对齐数的整数倍

栗子:

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;
}


猜你喜欢

转载自blog.csdn.net/paperjie/article/details/131681821