C语言之自定义类型

目录

1.结构体

 1.1 结构体的基础知识

 1.2结构的声明

1.3 特殊的声明(匿名)

 1.4结构的自引用

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

 1.6 结构体内存对齐(非常重要)

 1.7修改默认对齐数

1.8 结构体传参 

2.位段

2.1 什么是位段

2.2位段的内存分配 

2.3位段的跨平台问题 

3.枚举 

3.1 枚举类型的定义

 3.2枚举的优点

3.3枚举的使用 

3.4 枚举大小的计算

4.联合(共用体) 

4.1联合类型的定义

4.2联合的特点

4.3用联合来判断大小端

 4.4 联合大小的计算

这里特别注意:数组的对齐数是看类型的大小的


1.结构体

 1.1 结构体的基础知识

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

 1.2结构的声明

struct
tag
{
        member
-
list
;//成员列表
}
variable
-
list
;//变量列表

eg:描述一个学生

#include<stdio.h>
struct Stu
{
	char name[20];//姓名
	char sex[5];//性别
	int age;//年龄
	int height;//身高
}s2,s3,s4;//s2,s3,s4为全局变量 //分号不可省  //1

struct Stu s5;//2

int main()
{
	struct Stu s1;//结构体变量   //3
	return 0;
}

1.3 特殊的声明(匿名)

在声明结构的时候,可以不完全的声明。
//匿名结构体类型
struct
{
 int a;
 char b;
 float c; 
}x;

 声明的时候省略掉了结构体标签(tag)

struct//第1种
{
	char c;
	int a;
	double d;
}sa;//只可以在分号前定义变量

struct//第2种
{
	char c;
	int a;
	double d;
}*ps;

//struct//第三种
//{
	//char c;
	//int a;
	//double d;
//}*ps,sa;

int main()
{
	ps = &sa;//编译器会把上面的两个声明当成完全不同的两个类型(在两种不同的匿名结构体中),
    //在同一个匿名结构体中同时定义的两个变量是同种类型
	return 0;
上述代码,ps = &sa是否合法
警告
:编译器会把上面的两个声明当成完全不同的两个类型
所以是非法的。

 1.4结构的自引用

结构体中包含自身类型的指针成员变量

举例: 

struct Note
{
	int data;
	struct Note* next;
};

注意:匿名结构体类型不可以自引用 

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

//结构体的定义和初始化
struct Stu
{
	char name[20];
	char sex[5];
	int age;
	int height;
}s2;//声明类型的同时定义变量

struct Stu s3;//定义结构体类型变量s3
struct activity 
{
	char game[20];
	struct Stu s2;
}a2 = { "huoyingrenzhe",{ "lisi","nan",18.170} };//结构体嵌套初始化
int main()
{
	struct Stu s1 = { "lisi","nan",18.170};//初始化(定义变量的同时赋初值)
	struct activity a1 = { "huoyingrenzhe",{ "lisi","nan",18.170} };//结构体嵌套初始化
	return 0;
}

 1.6 结构体内存对齐(非常重要)

结构体的对齐规律

  1. 结构体的第一个成员,存放在结构体变量开始位置的0偏移量(第一个字节处)处
  2. (针对的是偏移量)从第二个成员开始,都要对齐到对齐数的整数倍的地址(偏移量)处
  3. (针对的是字节大小)结构体的总大小,必须是最大对齐数的整数倍
  4. 如果嵌套了结构体的情况
  • 嵌套的结构体(结构体中的那个结构体)对齐到自己的最大对齐数的整数倍处
  • 结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
  •  数组的对齐数看的是类型大小
  •  对齐数:成员自身大小和默认对齐数的较小值
  •  vs的默认对齐数是8   
  •  linux环境没有默认对齐数  对齐数为自身大小
  •  最大对齐数:是指所有成员的对齐数中最大的那个

 下面是3道关于计算结构体大小的例题,让我们一起来看下吧


//eg1 :求s1大小
struct s1   //12
{
	char c1;//1
	int i;//4
	char c2;//1
};

 

 

//eg2 :求s2大小
struct s2//8
{
	char c1;
	char c2;
	int i;
};

 

 

//eg3 嵌套结构体类型 (求s4大小)
struct s3//16
{
	double d;//8
	char c;//1
	int i;//4
};

struct s4 //32
{
	char c1;//1
	struct s3 s3;//16
	double d;//8
};

 

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

在设计结构体的时候,满足对齐,节省空间的方法是
让占用空间小的成员尽量集中在一起。

struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};

 S1S2类型的成员一模一样,但是S1S2所占空间的大小有了一些区别。

 1.7修改默认对齐数

#pragma 这个预处理指令,可以帮助我们改变默认对齐数。
#pragma pack(1) //设置默认对齐数为1
struct s1 //6
{
	char a;
	int i;
	char b;
};
#pragma pack() //取消设置的默认对齐数
int main()
{
    //不修改对齐数的结果为12
	printf("%dn", sizeof(struct s1));//结果为6
}
结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。

1.8 结构体传参 

咱们直接上代码

#include<stdio.h>
struct S 
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
 {
 printf("%dn", s.num);
}
//结构体地址传参
void print2(struct S* ps) 
{
 printf("%dn", ps->num);
}
int main()
{
 print1(s);  //传结构体
 print2(&s); //传地址
 return 0; 
}

关于结构体传参的方法有
1.传值
2.传址
虽然有两种方式,但我们首选传址
原因:结构体所占内存越大,传参时需要拷贝的形参也越大,而占用的是栈区的空间,过大会导致性能和效率上的损失

2.位段

2.1 什么是位段

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int、signed int ,
也可以是char类型(整型家族),但是如果一个成员是char类型,其它的都得是char类型) 。
2.位段的成员名后边有一个冒号和一个数字。
struct a
{
	int _a : 2;//这里的2代表2个比特位(1字节=8比特位)
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%dn", sizeof(struct a));//位段a的大小为8
}

这里全是int类型,先开辟4个字节的空间(32bit) ,_a、_b、_c共占17bit,还剩下15bit,但_d需要30bit,于是再开辟4个字节空间存_d

2.2位段的内存分配 

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

一个例子

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;

上面代码的空间是如何开辟的呢

2.3位段的跨平台问题 

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

3.枚举 

枚举顾名思义就是一一列举。
把可能的取值一一列举。
例如:工作日星期一到星期天这七天都可以一一列举

3.1 枚举类型的定义

拿星期举例:

#include<stdio.h>
enum Day//相当于等差数列 ,d=1
{
	Mon,
	Tues,
	Wed,
	Thir,
	Fri
};

int main()
{
	printf("%d %d %d %d %dn", Mon, Tues, Wed, Thir, Fri);
	return 0;
}

运行截图:

{}
中的内容是枚举类型的可能取值,也叫
枚举常量
这些可能取值都是有值的,默认从0开始,一次递增1(可看成a1=0,d=1的等差数列)

当然在定义的时候也可以赋初值,例如:

#include<stdio.h>
enum Color//颜色
{
    RED = 1,//定义+赋值
    GREEN,
    BLUE=4
};
int main()
{
    printf("%d %d %d", RED, GREEN, BLUE);
    return 0;
}

运行截图

 3.2枚举的优点

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

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

3.3枚举的使用 

enum Color
{
 RED=1,
 GREEN=2,
 BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;               //error  要拿{}内的枚举常量来赋值

3.4 枚举大小的计算

#include<stdio.h>
enum Color
{
    RED = 1,
    GREEN,
    BLUE=4
};
int main()
{
    enum Color c = RED;
    printf("%dn",sizeof(enum Color));//4
    printf("%dn", sizeof(c));//4
    return 0;
}

运行截图

 

总结:枚举变量的取值为{}内的任意一个值(有且只能有其中一个值),而这个值是int型的,在X86系统中,int型的数据占内存4个字节。所以sizeof(c) = 4,也就是枚举变量的大小为4。 枚举类型的大小也为4

4.联合(共用体) 

4.1联合类型的定义

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

#include<stdio.h>
union Un
{
	int i;
	char c;
};

int main()
{
	union Un un;
    printf("%dn",sizeof(un));
	printf("%dn", &(un.i));
	printf("%dn", &(un.c));
	un.i = 0x1122;
	un.c = 0x44;
	printf("%xn", un.i);
}

 运行截图

 由此可见:联合的大小至少为最大成员的大小,成员共用同一块空间(地址相同)

4.2联合的特点

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

4.3用联合来判断大小端

方法一:一般方法

#include<stdio.h>
int cheak()
{
	int a = 1;
	return (*(char*)&a);
}
int main()
{
	int ret = cheak_sys();
	if (1 == ret)
		printf("小端n");
	else
		printf("大端n");
	return 0;
}

方法二:联合

#include<stdio.h>
int cheak_sys()
{
	union Un//这里联合体只能使用一次(可以把Un删掉,改为匿名的(和结构体用法一样))--好处是万一与后面的变量重名时不会冲突
	{
		char c;
		int i;
	}u;
	u.i = 1;
	return u.c;
}
int main()
{
	int ret = cheak_sys();
	if (1 == ret)
		printf("小端n");
	else
		printf("大端n");
	return 0;
}

 4.4 联合大小的计算

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
#include<stdio.h>
union Un1
{
	char c[5];//5
	int i;//4->最大对齐数
};
union Un2
{
	short c[7];//14 
	int i;//4->最大对齐数
};
int main()
{
	printf("%dn", sizeof(union Un1));//8
	printf("%dn", sizeof(union Un2));//16
	return 0;
}

这里特别注意数组的对齐数是看类型的大小的

 

 感谢观看!

 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>