目录
一、结构体
1.结构的声明
2.特殊的声明
3.结构的自引用
4.结构体变量的定义和初始化
5.结构体内存对齐(重点来了)
6.为什么会存在内存对齐
7.修改默认对齐数
8.结构体传参
二、位段
1.什么是位段
2.位段的内存分配
3.位段的跨平台问题
三、枚举
1.枚举类型的定义
2.枚举的优点
3.枚举的使用
四、联合(共用体)
在此之前简单地介绍了结构体的使用初阶结构体
首先 结构 ,结构是一些值得集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
//结构体的声明
struct tag
{
member - list;//成员列表
}variable-list;//变量列表
【例如】
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
在声明结构体的时候,可以不完全的声明。
如 匿名结构体类型
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20],*p;
//注意 不能这样写 p = &x;//编译器会把上述当成两个不同的结构体,所以是非法的
就是结构体中含有一个类型为结构体本身的成员,如果学过数据结构的话,可能会知道链表中的node节点 就用到了结构体的自引用。
结构体的自引用方式
struct Node
{
int data;//数据域
struct Node* next;//指针域,指向下一个节点
};
//另一种写法,使用类型重命名
typedef struct Node
{
int data;
struct Node* next;
}Node;//这样就可以 当写 struct Node 可以写为 Node
(1)声明类型的同时定义变量p1
//结构体变量的定义和初始化
struct Point
{
int x;
int y;
}p1;//声明类型的同时定义变量p1
(2)初始化,定义变量的同时赋值
struct Point p3 = {x,y};
struct Stu
{
char name[15];
int age;
};
struct Stu s = {"zhangsan",20};
(3)结构体嵌套初始化
struct Stu
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10,{4,5},NULL };//结构体嵌套初始化
先来个问题,如何计算结构体的大小呢?这个问题就涉及了结构体内存对齐
看下方代码,并试着计算这俩个结构体分别是多大
struct S1
{
char c1;
int a;
char c2;
};
struct S2
{
char c1;
char c2;
int a;
};
如果不了解结构体内存对齐的话,可能会认为这两个结构体不一样大吗?
当我们试着打印这两个结构体的大小
这时我们发现这两个结构体的大小不一样大,这是为什么呢?
看完接下来的结构体内存对齐,就明白了。
重点
要计算结构体的大小,那么我们就要掌握下方的
结构体对齐规则:
- 结构体的第一个成员 在 与结构体变量偏移量为0的地址处
- 其他成员变量要对齐到某个数字(即对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值(就是编译器的对齐数 与成员变量 这两个中较小的一方 )
注意:VS中对齐数默认值是8- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 如果有嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
根据上述结构体对齐规则,我们来分析下方代码
【1】
struct S1
{
char c1;//所占大小为1
int a;//所占大小为4
char c2;//所占大小为1
};
//该结构体的总体大小为?
该图解具详细
接着来分析 struct S2
【2】
struct S2
{
char c1;//所占大小为1
char c2;//所占大小为1
int a;//所占大小为4
};
//该结构体总体大小为?
图解具详细:
有了上述两个 我们接着再来看两个,巩固一下结构体内存
【3】根据结构体的内存对齐计算结构体的大小
struct S3
{
double d;
char c;
int i;
};
图解具详细:
【4】 计算结构体S4的大小
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char z;
struct S3 s3;
double d;
};
简单来说就是
所以,对于结构体的内存对齐 是 通过 空间 来换取 时间 的做法
我们可能之前会看到#pragma 这个预处理指令
#pragma 可以修改默认对齐数
#include
#pragma pack(1)//对齐数修改为4
struct S1
{
char c1;
int a;
char c2;
};
int main()
{
printf("%d",sizeof(struct S1));
}
这样经过修改默认对齐数,改为1。这样计算出来的结构体大小就是6了。
有了这个pragma,当结构体对齐方式不合适的时候我们就可以修改默认对齐数,进而来调整结构体的大小。
#include
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4},100 };
//结构体传参
void print1(struct S s)
{
printf("%d\n",s.num);
}
//结构体地址传参
print2(struct S* s)
{
printf("%d\n",s->num);
}
int main()
{
print1(s);//传结构体
print2(&s);//传地址
return 0;
}
注意: 因为函数传参的时候要压栈,会有时间和空间上的系统开销。而传结构体的地址的时候与会减少系统的开销。
位段的声明和结构体是类似的,但有两点不同
【例】
struct A {
int a : 2;
int b : 3;
int c : 4;
int d : 5;
};
位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的。
枚举就是列举
enum Color
{
RED,
GREEN,
BLUE
};
enum Sex
{
MALE,
FEMALE,
SECRET
};
enum Sex,enum Color都是枚举类型,{ }中的内容就是枚举类型可能取的值,也叫枚举常量
有些可以有值得,默认从0开始,一次递增1,定义的时候可以赋值
enum Color
{
RED=1,
GREEN=2,
BLUE=3
};
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
enum Color
{
RED = 1,
GREEN = 2,
BLUE = 3
};
enum Color clr = GREEN;//这样不会出现类型的差异
clr = 5;
联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)
#include
union Un
{
int i;
char c;
};
union Un un;
int main()
{
un.i = 0x11223344;
un.c = 0x55;
printf("%d\n",&(un.i));
printf("%d\n",&(un.c));
printf("%x\n",un.i);
return 0;
}
从第三个的打印结果我们可以看出来 联合体中的成员就是共用同一块内存空间。
来一个笔试题
判断当前计算机的大小端存储
//判断大小端的存储
#include
int check_sys()
{
int i = 1;
return *((char*)&i);
// 00 00 00 01 大端
// 01 00 00 00 小端
//地址 低---->高
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
上述代码就是我们通过取地址的方式来判断的
接下来我们来使用联合体来判断判断当前机器的字节序
//使用联合体来判断当前机器的字节序
#include
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;
//这里的联合体共用是同一块内存空间
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}