目录
前言
大家好!我是Node_Hao,今天为大家带来的是c语言系列,自定义类型,话不多说咋们开始.
一、结构体的声明:
1.1结构体基础知识:
类比数组是一种存放相同元素的集合,而结构也是一种集合,集合中存放各种不同的元素.例如:常量,数组,指针,甚至是其他结构体.
1.2结构体变量的定义和初始化:
结构体定义相当于描绘出一个房子的平面图,而结构体初始化才相当于建起一座房子.一座房子当它有平面图时占地面积就已经很清楚了,所以结构体变量在定义的时候就已经确定内存大小了.
1.3特殊的声明:
在声明结构体时可以不完全声明(匿名结构体类型):该结构体类型在声明时都忽略其标签,因此定义的结构体变量只能使用一次.而且每个结构体变量之间无任何联系,所以不存在*p = &x这种写法.
#include<stdio.h>
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], * p;
1.4结构体的自引用:
既然结构体可以包含那么多的类型,是否也能包含结构体自身呢?下面我们来看几组代码:
struct Node
{
int data;
struct Node next;
};
这段代码是否可行呢?如果可以sizeof(struct Node)为多少呢?这里明显是不能通过编译器的,因为我们并不知道struct Node next中到底含有多少元素,也就无法知道其大小.
这里涉及到了后续的链表相关的知识,链表中含有数据域和指针域,数据域中存放自身的数值,而指针域中存放下一个元素的地址,正是这样的指针域才搭建起了链表之间的联系.
所以代码可以这样改写:结构体中只能包含结构体指针.
struct Node
{
int data;
struct Node* next;
};
1.5结构体内存对齐:
在掌握结构体的基础知识后,我们深入探讨一下结构体的内存大小.在计算前我们得先知道结构体内存大小的计算方法.
1.第一个成员在与结构体变量偏移量为0的地址处
2.其他成员变量要对齐到对齐数的整数倍处,
对齐数等于编译器默认对其数和成员大小的较小值.(vs默认对其数为8)
3.结构体总大小为最大对齐数的整数倍.(每个成员都有对齐数)
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在掌握计算方法后我们来看几个例子:
例1:普通结构体
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
已知编译器默认最大对齐数为8,c1和c2最大对齐数都是1,i的最大对齐数为4.将c1放于默认对齐数为0的位置,i放于其对其数的整数倍处,也就是偏移量为4的位置处,c2对齐数为1,放于偏移量为8的位置即可.结构体的总大小为成员最大对其数的整数倍,成员最大对齐数为4那么总大小可以为8.
例2:结构体中包含结构体
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
1.6为什么存在结构体内存对齐:
1.平台原因(移植原因):不是所有硬件平台都能访问任意位置的内存,某些硬件平台只能访问特定位置的数据类型.
2.性能原因:数据结构(尤其是栈)应尽可能在自然边界上对齐,原因在于为了访问未对齐的内存,处理器需要两次访问,而对齐的内存只需要访问一次.因为在32位机器上一次可以读取4个字节,如果偏移量0的位置为char,紧随其后又是一个int,那么我们第一次读取了char和部分int,第二次才能读取整个int.但如果内存对齐我们在自然边界一次就能对齐.
那么我们在设计结构体时既要满足内存对齐又要节省空间,只能将空间小的成员尽量集中在一起.
1.7修改默认对齐数:
#pragma这个预处理指令可以修改默认对其数.在对齐数不合适的时候我们可以自己修改默认对其数.当对其数为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;
}
1.8结构体传参:
结构体传参分为:1.传结构体 2.传地址.函数传参时是需要压栈的有时间和空间上的开销.如果传一个结构体对象时,结构体较大,参数压栈的系统开销较大,所以导致系统的性能下降.因此传址调用是首选.
#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* s) {
printf("%d\n", s->num);
}
int main() {
print1(s);
print2(&s);
return 0;
}
二、位段
2.1什么是位段:
位段的声明和结构体类似,但有两个不同:
1.. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2.位段的成员后面有一个冒号和分号
例如:
struct A {
int _a:2;
int _b:5;
int _c:10;
int _d:30;
}
printf("%d\n",sizeof(struct A));
那么struct A的大小又是多少呢? 由编译器的运行结果可知大小是8.具体原因在于位段的内存分配.
2.2位段的内存分配:
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型2. 位段的空间上是按照需要以 4 个字节( int )或者 1 个字节( char )的方式来开辟的。3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
例如:
#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;
return 0;
}
看完内存分布之后很多人会疑惑为什么不按小端存储呢?其实我们应该搞清楚一个概念,大小端存储是建立在多个字节序上的,类似char这样只有一个字节的,存储方式与二进制书写格式一致.
由以上内存分布我们可以看出 ,char类型的变量分配几个bit位的空间就开辟几个bit位空间,超出就会截取.例如:char a 只分配了3个bit位,而10的二进制位1010需要四个bit位所以就发生了截断,实际内存只存储了010.
2.3位段跨平台问题:
1. int 位段被当成有符号数还是无符号数是不确定的。2. 位段中最大位的数目不能确定。( 16 位机器最大 16 , 32 位机器最大 32 ,写成 27 ,在 16 位机器会出问题。3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的.
综上:与结构体相比位段可以达到同样的效果,且节省空间.但存在跨平台问题.
既然存在这么多限制又没有比结构体方便多少,那么位段真正的作用是什么呢?
2.4位段的应用:
位段的应用主要体现在网络上通讯上,例如张三给小美发消息:"你好呀!",看似简单的一句话在数据传输的过程中会包含很多的信息,如果我们不给这些信息设置具体大小 ,全部统一为4个字节.那么数据量就会变得异常之大,如果把网络传输比作高速公路,那么使用位段 高速公路上行驶的都是小轿车无比通畅,要是统一为int型 ,那就相当于全是大卡车,不仅行驶速度慢,还有很高的丢包率.
三、枚举
3.1枚举的定义:
枚举顾名思义就是把可能的取值一一列举
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
enum Day//星期
{
Mon,//0
Tues,//1
Wed,//2
Thur,//3
Fri,//4
Sat,//5
Sun//6
};
enum Sex//性别
{
MALE,//0
FEMALE,//1
SECRET//2
};
enum Color//颜色
{
RED,//0
GREEN,//1
BLUE//2
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量.
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值.
例如:
enum Color
{
RED=1,
GREEN=2,
BLUE=4
};
3.2枚举的优点:
有一定C语言基础的同学都知道,要定义常量我们可以用#define,那么为什么要用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
3.3枚举的使用:
#include<stdio.h>
enum Color {
READ = 2,
GREEN = 1,
BULE = 3
};
int main() {
enum Color s = READ;
return 0;
}
注意:只能拿枚举常量给枚举变量赋值才不会出现类型异常.
四、联合(公用体)
4.1 联合体的类型定义:
联合也是一种自定义类型,这种类型的变量包含一系列的成员,特征是这些成员公用一块空间.
例如:
#include<stdio.h>
union un {
char c;
int i;
};
int main() {
union un s;
s.c = 'a';
s.i = '8';
printf("%d\n", sizeof(s));//4
return 0;
}
4.2联合体的特点:
联合体是公用一块空间的,那么联合体的大小至少是最大成员变量的大小.
由这段代码的我们可以得出一个结论:结构体的存储方式类似于我们平时的合租房,无论指哪个人的地址只要是同一间房子的地址都是一样的,但这样也存在一个弊端.如果其中一个人要搬家其他人也要跟着走,这样别人的地址就发生了改变.
4.3 经典面试题(判断电脑大小端)
4.4联合体大小的计算
1.联合体的大小至少是最大成员的大小.
2.当最大成员不是最大对齐数的整数倍时,就要对其到最大对齐数的整数倍.
以上代码的最大成员为5,不是最大对其数的整数倍,要对其到最大对齐数的整数倍.所以必须创建8个字节的空间.(与结构的计算方法一致)
再看这组代码,最大成员为14,对齐到最大对齐数的整数倍处就是16.
总结
以上就是自定义类型的全部内容了,希望我的内容可以对你的学习有亿点点帮助,码字不易如果觉得文章不错麻烦关注一波,如有bug欢迎批评指正~