自定义类型:结构体,枚举,联合
本章重点
结构体
结构体类型的声明和变量的定义
结构的自引用
结构体初始化
结构体内存对齐
结构体传参
结构体实现位段(位段的填充&可移植性)
枚举
枚举类型的定义
枚举的优点
枚举的使用
联合
联合类型的定义
联合的特点
联合大小的计算
结构体
结构的基础知识:结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体类型的声明和变量的定义
struct tag
{
member-list;//成员列表
}variable-list;//变量列表
简单例子:
创建学生s1,s2,s3,s4,s5
例如描述一个学生:
声明一个结构体类型
声明一个学生类型,是想通过学生类型来创建学生变量(对象)
描述学生:名字+年龄+性别+学号
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s4,s5,s6;//分号不能丢
//s4,s5,s6全局变量
struct Stu s3;//s3全局变量
int main()
{
//创建的结构体变量
//s1,s2局部变量
struct Stu s1;
struct Stu s2;
}
特殊的声明
在声明结构的时候,可以不完全的声明。
使用场景:通常在只需要一次的结构体使用
简单例子:
//匿名结构体类型:没有名字,省略掉了结构体标签(tag)
struct
{
int a;
char b;
float c;
}x;//这能在这里创建变量
struct
{
int a;
char b;
float c;
}* px;//匿名结构体指针
int main()
{
px=&x;//警告: 编译器会把上面的两个声明当成完全不同的两个类型,是非法的!!!
}
结构的自引用
数据结构:数据在内存中存储的结构
链表:1 -> 2 -> 3 -> 4 -> 5
|-------------------------------------------------------|
|数值域 指针域(存放下一个数据的地址) |
|-------------------------------------------------------|
//err代码1
结构中包含一个类型为该结构本身的成员是 错误 的
struct Node
{
int data;
struct Node next;
};
否则sizeof(struct Node)=无穷大
正确的自引用方式:
//代码2
struct Node
{
int data;
struct Node* next;
};
重命名:给struct Node起一个小名叫Node
typedef struct Node
{
int data;
struct Node* next;
}Node;
结构体变量初始化
//初始化:定义变量的同时赋初值。
#include <stdio.h>
struct Point
{
int x;
int y;
};
struct Node //类型声明
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL };//结构体嵌套初始化
struct Node n2 = { 10, {4,5}, NULL };//初始化
int main()
{
struct Node n3 = { 10, {4,5}, NULL };//初始化
printf("%d %d %d %s", n1.data, n1.p.x, n1.p.y, n1.next);
return 0;
}
//结果:10 4 5 (null)
结构体内存对齐
问题:请计算结构体的大小,4个printf的结果是多少?
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
struct S3
{
double d;
char c;
int i;
};
struct S4//结构体嵌套问题
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
printf("%d\n", sizeof(struct S4));
return 0;
}
答案:
12
8
16
32
解析:求结构体大小标准步骤
struct S1//12
{
char c1;
int i;
char c2;
};
1.求出成员大小
char c1; //1字节
int i; //4字节
char c2; //1字节
2.求出各自对齐数
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
gcc没有默认对齐数
以VS为例:
char c1; //1字节/8字节-->1字节
int i; //4字节/8字节-->4字节
char c2; //1字节/8字节-->1字节以gcc为例:
char c1; //1字节
int i; //4字节
char c2; //1字节
3.求出结构体总大小
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
结构体总大小:4的整数倍
4.画出内存结构
第一个成员在与结构体变量偏移量为0的地址处
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
char c1; //1的整数倍
int i; //4的整数倍
char c2; //1的整数倍
5.算出结构体总大小
结构体总大小:4的整数倍
存完char c2后偏移量为9,不是4的整数倍,所以为12
| |
|...... |
|------------|<----假设从这里开始放(偏移量为0)
| char c1 |
|------------|偏移量为1
| 浪费 |
|------------|偏移量为2
| 浪费 |
|------------|偏移量为3
| 浪费 |
|------------|偏移量为4<----4的整数倍
| int i |
|------------|偏移量为5
| int i |
|------------|偏移量为6
| int i |
|------------|偏移量为7
| int i |
|------------|偏移量为8<----1的整数倍
| char c2 |
|------------|偏移量为9
| 浪费 |
|------------|偏移量为10
| 浪费 |
|------------|偏移量为11
| 浪费 |
|------------|偏移量为12
|...... |
| |
S2求法同上:
struct S2//8
{
char c1;
char c2;
int i;
};
struct S3//16
{
double d;
char c;
int i;
};
1.求出成员大小
double d; //8
char c; //1
int i; //4
2.求出对齐数
double d; //4
char c; //1
int i; //4
3.求出结构体总大小
4的整数倍
4.画出内存结构
double d; //4
char c; //1
int i; //4
| |
|...... |
|---------|<----假设从这里开始放(偏移量为0)
|double d |
|---------|偏移量为1
|double d |
|---------|偏移量为2
|double d |
|---------|偏移量为3
|double d |
|---------|偏移量为4
|double d |
|---------|偏移量为5
|double d |
|---------|偏移量为6
|double d |
|---------|偏移量为7
|double d |
|---------|偏移量为8<----1的整数倍
|char c |
|---------|偏移量为9
| 浪费 |
|---------|偏移量为10
| 浪费 |
|---------|偏移量为11
| 浪费 |
|---------|偏移量为12<----4的整数倍
|int i |
|---------|偏移量为13
|int i |
|---------|偏移量为14
|int i |
|---------|偏移量为15
|int i |
|---------|偏移量为16
struct S4//32
{
char c1;
struct S3 s3;
double d;
};
1.求出成员大小
char c1;//1
struct S3 s3;//16
double d;//8
2.求出对齐数
char c1;//1
struct S3 s3;//8
double d;//4
3.求出结构体总大小
8的整数倍
4.画出内存结构
char c1;//1
struct S3 s3;//8
double d;//4
| |
|...... |
|------------|<----假设从这里开始放(偏移量为0)
|char c1 |
|------------|偏移量为1
| 浪费 |
|------------|偏移量为2
| 浪费 |
|------------|偏移量为3
| 浪费 |
|------------|偏移量为4<----4的整数倍
| 浪费 |
|------------|偏移量为5
| 浪费 |
|------------|偏移量为6
| 浪费 |
|------------|偏移量为7
| 浪费 |
|------------|偏移量为8<----8的整数倍
|struct S3 s3|
.............
|struct S3 s3|
|------------|偏移量为24<----4的整数倍
| double d |
..............
| double d |
|--over------|偏移量为32<----8的整数倍
int main()
{
printf("%d\n", sizeof(struct S1));//12
printf("%d\n", sizeof(struct S2));//8
printf("%d\n", sizeof(struct S3));//16
printf("%d\n", sizeof(struct S4));//32
return 0;
}
为什么存在内存对齐?
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地对齐
访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。
例如:win32平台,32根地址线,32根数据线-->一次读4字节
01 01 00 00 |00
char int |
一次读取 |二次读取
01 00 00 00 01 00 00 00
char --浪费-- int
一次读取
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起
修改默认对齐数
#pragma 这个预处理指令可以改变我们的默认对齐数
例子:
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
offsetof
偏移量求取宏
offsetof简单例子
#include <stdio.h>
#include<stddef.h>//offsetof头文件
struct S
{
char c;
int i;
double d;
};
int main()
{
printf("%d\n",offsetof(struct S,c));//0
printf("%d\n",offsetof(struct S,i));//4
printf("%d\n",offsetof(struct S,d));//8
return 0;
}
百度笔试题:
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
考察: offsetof 宏的实现
结构体传参
#include <stdio.h>
struct S
{
char c;
int i;
double d;
};
//初始化传地址
void Init(struct S* ps)
{
ps->c = 'w';
ps->i = 100;
ps->d = 3.14;
}
//结构体传值
void Print1(struct S tmp)
{
printf("%d %c %lf\n",tmp.c,tmp.i,tmp.d);
}
//传地址
void Print2(struct S* ps)
{
printf("%d %c %lf\n", ps->c ,ps->i ,ps->d);
}
int main()
{
struct S s;
Init(&s);
//不推荐:传结构体
//若结构体过大,临时拷贝的系统开销大,会导致性能的下降
Print1(s);
//推荐:传地址,4-8字节
Print2(&s);
return 0;
}
位段
节省空间的结构体
什么是位段?
结构体实现 位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int (其实char也可以) (通常位段的类型是相同或相似的成员)
2.位段的成员名后边有一个冒号和一个数字。
位段简单例子
struct A
{
//类型 变量名:所占的bit位
//所占的bit位<=类型bit位(win32:int-32bit)
int a:2;
int b:5;
int c:10;
int d:30;
};
位段的内存分配
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
例子1:
struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};
共计47bit
1字节==8bit
由于位段类型为int,所以一次开辟4字节==32bit
开辟32bit
{
a-2bit
b-5bit
c-10bit
剩余32-17=15bit < d所需要的30bit
在VS编译器下浪费掉15bit
再开辟32bit
}
再开辟32bit
{
d-30bit
剩余2bit被浪费
}
printf("%d\n", sizeof(struct A));//共计8字节
例子2
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
int main()
{
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
调试->窗口->内存
位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,
但是可以很好的节省空间,但是有跨平台的问题存在。
位段的应用:网络数据的传输
枚举 即为一 一列举
枚举类型的定义
//颜色
enum Color//枚举类型
{
//枚举常量
RED,
GREEN,
BLUE
};
int main()
{
//枚举类型 变量名 = 赋值;
enum Color c =BLUE;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
printf("%d %d %d\n",RED,GREEN,BLUE);
//结果0 1 3
return 0;
}
枚举常量取值都是有值的,默认从0开始,一次递增1
当然在定义的时候也可以赋初值,下一个默认为前一个加1
例如
enum Color//颜色
{
RED = 1,
GREEN,//(默认为2)
BLUE = 4
};
枚举的大小为4字节,枚举类型无法自己指定
枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名冲突(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
#define 定义常量,例如:
#define RED 0
#define GREEN 1
#define BLUE 2
联合(共用体)
联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,
特征是这些成员公用同一块空间(所以联合也叫共用体)。
简单例子
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));//4字节
联合的特点
联合的成员是共用同一块内存空间的,&u == &(un.i) == &(un.c)
一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
百度面试题:
判断当前计算机的大小端存储
方法一:强制类型转化
#include<stdio.h>
int check_sys()
{
int a=1;
return *(char*)&a;
}
int main()
{
if(check_sys())
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
方法二:联合
#include<stdio.h>
int check_sys()
{
union Un
{
char c;
int i;
}u;
u.i=1;
return u.c;
}
int main()
{
if(check_sys())
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
联合大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,
就要对齐到最大对齐数的整数倍
简单例子:
#include <stdio.h>
union Un
{
int i;
//自身大小4字节
//默认对齐数8
//min{自身大小,默认对齐数}==4
//对齐数==4
char arr[5];
//自身大小5字节
//元素类型大小1字节
//默认对齐数8
//min{元素类型大小,默认对齐数}==1
//对齐数==1
//最大成员大小(5)不是最大对齐数(4)的整数倍
//对齐到最大对齐数的整数倍(8)
};
int main()
{
printf("%d\n", sizeof(union Un));
}
/*
结果:8
*/