笠笠笠各位巨佬大家好,我是猪皮兄弟笠笠笠
今天我们学习的内容是自定义类型。
前言这里是下面要讲的知识内容拾拾拾
结构体的很基础的知识这里就不再多说了,今天讲的内容包括匿名结构体、结构体的自引用、结构体内存对齐、结构体传参、位段、枚举、联合
一、结构体 1.⛄匿名结构体
struct//匿名结构体
{
char name[20];
float price;
char id[12];
}ss;//匿名结构体只能用一次,因为名字都没有,无法创建新的变量
//只能在匿名结构体的定义后面创建全局变量
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20],*p;
//如果匿名结构体的成员一样,在编译器看来,也是不同类型的结构体
//所以,p=&x这是非法的
typedef struct
{
int data;
Node*next;
}Node
//error
//结构体重命名,要有了
struct Node
{
int data;
struct Node*next;
};
//这个结构体才能进行重命名
2.⛄结构体的自引用
结构体的自引用是后面学习数据结构的基础。
在32位平台,指针的大小是4个字节
在64位 平台,指针的大小是8个字节
struct Node
{
int data;//数据域
//struct Node n; //n里面又有n,所以结构体的大小未知,是编译不过去的,所以用指针
struct Node *next;//指针域;
};
typedef struct Node
{
int data;
struct Node*next;
}Node,*pNode;
//*pNode是对struct Node*重命名
3.⛄结构体内存对齐
结构体内存对齐用于计算结构体的大小
规则如下
1.第一个成员在与结构体变量偏移量为0的地址处存放
2.其他成员要对齐到某个数字(对齐数)的整数倍的地址处
对齐数=编译器默认的一个对齐数与该成员 大小的较小值
VS中默认值为8
3.结构体的大小为最大对齐数(每个成员都有一个对齐数)的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的(自身结构体的最大对齐数)最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的最大对齐数)的整数倍
在Linux环境下,是没有默认对齐数的,成员自身的大小就是它的对齐数.
我们可以用offsetof来看到成员相对于起始位置的偏移量
offsetof其实是一个宏,offsetof的使用需要包含头文件stddef.h
struct s1
{
char c1;
int i;
char c2;//0-8是九个,不是最大对齐数的整数倍---12
};
struct s2
{
char c1;
char c2;
int i;
};
struct s3
{
double d;
char c;
int i;
};
//offsetof - 宏
//计算结构体成员相对于起始位置的偏移量
struct s4
{
char c1;
struct s3 s3;
double d;
};
#include
int main()
{
struct s1 s1;
struct s2 s2;
struct s3 s3;
struct s4 s4;//s4中的最大对齐数是8而不是16
printf("%dn",sizeof(s1));//12个字节0-11
printf("%dn",sizeof(s2));//8个字节0-7
printf("%dn",sizeof(s3));//16个字节0-15
printf("%un",offsetof(struct s1,i));
printf("%d",sizeof(s4));//32
return 0;
}
那么为什么存在内存对齐?
1.平台原因
不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(比如只能在4的倍数的地址上读取)
2.性能原因
数据结构尤其是栈应该尽可能地在边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对于对齐的内存访问,只需要一个访问就可以搞定(32位机器一次访问四个字节是硬件决定的.)
总体来说,内存对齐是拿空间换取时间的一种做法
所以在未来设计结构体的时候,我们既要满足对齐,又要节省空间
就可以让占用空间小的成员尽量集中在一起(因为结构体内存对齐)
修改默认对齐数通过#pragma pack(修改数) #pragma pack()恢复默认对齐数,在合适的时候更改默认对齐数,不能乱改
#pragma pack(4)//这个就是把默认对齐数改为4
struct S
{
char c;
double d;
};
#pragma pack()//这个是吧对齐数数改为默认对齐数
int main()
{
struct S s;
printf("%dn",sizeof(s));//默认对齐数为8时是16 对齐数修改为4时是12
return 0;
}
4.⛄结构体传参
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.位段的成员必须是int /unsigned int /signed int /也可以是char(因为char属于整形家族)
2.位段的成员名后面有一个冒号和一个数字
struct AA
{
int _a;//INT_MIN~INT_MAX
int _b;
int _c;
int _d;
};//16个字节
struct A
{
int _a : 2;//_a这个成员只占两个bit位 范围0-3
int _b : 5;//_b这个成员只占5个bit位
int _c : 10;
int _d : 30;
};//8个字节
int main()
{
printf("%dn",sizeof(struct A));
return 0;
}
位段不存在内存对齐,因为
在条件符合的情况下用位段来节省空间
因为位段是用来节省空间,所以位段不存在内存对齐
内存对齐是牺牲空间来换取效率,而位段是为了节省空间
位段的内存分配
1.位段的成员可以是int/unsigned int/signed int/char(char属于整形家族)
2.位段的空间上是暗中需要以4个字节(int)或者1个字节(char)的方式来开辟的
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
因为剩下来的15个bit不知道_d会不会用,所以是不跨平台的,编译器规定的方式不同
这也说明了它为什么不开辟6个字节就够了而要去开辟8个字节的空间
成员的存储是从开辟的第一个字节的左边开始还是右边开始也没有规定
上面只是猜测,对abcd都设为0,通过内存来观察
位段的跨平台问题
虽然位段帮我们节省了空间,但是却有跨平台问题
1.位段被当成有符号还是无符号的是不确定的
比如说开辟4个字节的空间,当成有符号处理还是无符号处理是不确定的
2.位段中最大位的数目不能确定
16位机器 sizeof(int)//2byte
32位和64位机器,sizeof(int)//4byte
按照32位机器来写 写大了的时候在16位机器上是会出问题的
3.位段中的成员在内存中从左向右分配还是从右向左分配是不确定的
4.当一个结构包含两个位段时,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这也是不确定的
二、枚举
enum Sex
{
MALE,
FAMALE,
SCRETE
};
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Color
{
RED=5,//默认从零开始,这样就可以改
GREEN,//6
BULE//7
};
int main()
{
enum Color c = 5;
printf("%d",c);
enum Sex s = MALE;
enum Sex s2 = FAMALE;
enum Day d = Fri;
enum Color c = GREEN;
return 0;
}
我们可以用#define来定义常量,那么为什么要用枚举?
枚举的优点
1.增加代码的可读性和可维护性
2.和#define定义的标识符常量比较,枚举有类型检查,更加严谨
3.防止命名的污染(封装)//只能在枚举当中使用,而不像#define 到处都可以使用
4.便于调试
test.c--------------------------------------test.exe
预编译 编译 汇编 链接
预编译阶段完成了#define符号的替换,也就是说预编译完#define RED 5就删了,下面的RED都变成了 5,调试的是处理过后的代码,看到的是未调试的代码
使用枚举的时候,RED就是RED,方便调试
5.使用方便,一次可以定义多个常量
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以也叫共用体)
联合的关键字 union
union Un
{
char c;
int i;
};
int main()
{
union Un u;
printf("%dn",sizeof(u));
printf("%pn",&u);
printf("%pn",&(u.c));
printf("%pn",&(u.i));
return 0;
}
对联合体来说,比如说上面这个联合体,我只使用一个,不会两个同时使用,所以可以内存共用比如说身份的差异。
联合的特点
联合的成员是共用一块空间的,这样一个联合变量的大小,至少是最大成员的大小,这并不是说联合是为了节省空间,共用一块空间是联合的特点
check_sys()
{
union Un
{
char c;
int i;
}u;
u.i = 1;
return u.c;
}
int main()
{
//int i = 0;
小端01 00 00 00
//if (1 == *(char*)&i)
//{
// printf("小端n");
//}
//else
// printf("大端n");
if(1==check_sys())
printf("小端n");
else
printf("大端n");
return 0;
}
//注释部分为之前判断大小端的方式
//未注释部分为用联合判断大小端的方式
联合大小的计算
联合也是存在对齐的
存在数组的时候,对齐数也是前面的类型,char的对齐数是1,int的对齐数是4
最大对齐数是4,所以最大的是5,但是最大对齐数是4,所以要是最大对齐数的整数倍,也就是8
四、五彩斑斓的一些废话
创作不易,提前感谢各位老铁的一键三连,我会和大家一起进步,拿下offer。如果觉得这篇文章有什么帮助的话,点点点。谢谢大家的支持,你的支持就是我前进的动力!!!



