文章目录
前言
一般我们在定义数据的时候有且仅能定义一种类型的变量,而在很多情况下我们需要可以囊括很多信息的集合来用。比如一个学生的信息,姓名、身高、年龄、学校等等这就是为何会有自定义类型。所以就可以概括,自定义类型就是将某个事物的共有属性集合在一起,声明一个*** 结构体、枚举、联合 ***类型来描述该事物。
一、结构体
1.1 结构体类型的声明
struct student
{ // 在定义时结构体的每一个成员都可以是一个独立的类型。
char name[20];
int age;
char sex[5];
char id[15];
};
//student太长了我们可以简写如下。
typedef struct student
{
char name[20];
int age;
char sex[5];
char id[15];
}stu;
//此时在主函数中定义结构体变量就可以直接。
int main()
{ //定义了一个名ss的结构体变量。
struct stu ss;
return 0;
}
//或者直接在声明的时候定义变量
1.2 结构体的自引用
我们是否可以像函数一样在结构体中嵌套结构体?
typedef struct student
{
char name[20];
int age;
char sex[5];
char id[15];
//结构体的自引用
struct student* ss;
}stu;
这样我们就可以在结构体内嵌套其他的结构体,这样就可以发生量级的变化。就好像由点到线、由线到面、由面到体的过程。
1.3 结构体变量的定义及初始化
//定义一
//直接在声明中定义
struct student
{
char name[20];
int age;
char sex[5];
char id[15];
}ss;
//结构体内嵌套初始化
struct student
{
char name[20];
int age;
char sex[5];
char id[15];
}ss={张三,13,男,233333};
//定义二
int mian()
{
//在函数体中定义
struct student ss;
//定义时直接初始化
struct strdent ss={name,age,sex,id};
return ;
}
1.4 结构体内存对齐
此时我们已经了解了结构体变量的声明、自引用、定义。
终归要存放到内存中去所以结构体变量是如何存入内存的,又有什么规则在其中呢。
接下来有几个例子可以帮助大家快速了解结构体是如何存入内存的以及它每个子变量存入的大小。
练习一:————————//答案放在本标题最后
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
输出是多少呢?
练习二:
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
练习三:
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
输出又是多少呢?
练习四:
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
答案:
- 12
- 8
- 16
- 32
看到这里各位是不是非常的迷惑!!
!!!重点来了
结构体变量储存规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
(VS中默认的值为8)- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
刚开始学的时候我也疑惑满满,为什么要有对齐原则,顺序储存不好吗,大家都知道每种类型在内存中储存的字节数是多少,这样岂不是多此一举!
后来了解的多了才懂得对齐规则的重要性。
1. 平台原因
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2.性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
通俗的来讲:
这就是一种用 空间 换取 时间 的做法。
极大的提高了程序运行的速度。
作为程序员既然我们不能改变规则(当然若有非常牛逼的大佬当我没说”🙈小猴子保命“),所以我们要学会利用规则。
在定义结构体的时候让占用空间小的成员集中在一起。
例:
struct S2
{
char c1;
char c2;
int i;
};//它的大小仅仅为8
1.5 修改默认对齐数
当我们任务对齐数太长,影响储存时,可以自己修改默认对齐数。
#pragma pack()
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
1.6 结构体传参
#include<stdio.h>
struct student
{
char name[20];
int age;
char sex[5];
char id[15];
};
void test(struct student* pc);
{
printf("%s",pc->name);
}
int mian()
{
struct student ss;
test(&ss);
return 0;
}
在传参时建议大家直接对地址进行操作,当然也可以直接传送整个结构体过去,不过这样当我们所定义的结构体变量内部太大时候,参数压栈就会又较大的开销,会导致pc机的性能下降。
结构体中还有个比较重要的分支——位段
位段与结构体类似,定义有所不同
struct A
{
int _x:5;
int _y:13;
int _z:12;
};
这是位段的基本格式,这里就不过多的介绍了。
总结位段:
与结构体相比,位段可以达到同样的效果,但是可以更好的去节省空间。
二、枚举
枚举是我们自定义类型第二个大的板块。
通俗的来讲就是将我们所需要的值意义列举出来。
2.1 枚举的声明
//在枚举中这些我们定义的变量值都会默认的从0开始自增1,也可以我们手动去为他们赋予初值和#define很类似
//比如定义颜色
enum Color
{
RED,
GREEN,
BLUE
};
2.2 枚举的优点
在声明中也说了枚举与宏定义十分的类似,那我们为啥那么要多此一举的去再了解一个新的同样功能的东西呢?
枚举相对于宏定义有以下有点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
2.3 枚举应用举例
#include <stdio.h>
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
int main()
{
enum DAY day;
day = WED;
printf("%d",day);
return 0;
}
运行结果
三、联合(共用体)
3.1 联合的声明
联合是一种特殊的自定义类型,与结构体类似。
不过它具有很轻的公开性
//联合类型的声明
union haha
{
char c;
int i;
};
//联合变量的定义
union haha ss;
//计算连个变量的大小
printf("%d\n", sizeof(ss));
联合体通俗的来讲就是联合体中所有的元素共同用一片空间,像之前的结构体、枚举他们各自的成员都有自己独立的内存空间,而联合体不同它是所有的的成员共同使用同一片空间。
3.2 联合体类型大小的计算
union SSC1
{
char c[5];
int i;
};
union SSC2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union SSC1));
printf("%d\n", sizeof(union SSC2));
return 0;
}
四、总结
自定义类型主要是为了让我们更加便利的使用C语言来实现各种复杂的问题,复杂问题简答化。本文主要介绍了三种类型如何声明,如何定义,以及各种关于自定义类型的规则吧,练习自定义变量的最好方法就是写出一个***通讯录***这是之前我写过的通讯录若有兴趣直接点击连接进入或者在我的博客里直接找到。
各位大佬一同进步!行文不易,互三互赞!