本章重点:
目录
一:结构体
1,1 结构体的基础知识:
1,2 结构的声明
例如描述一个学生:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s1,s2; //分号不能丢
//这里的s1,s2 值结构体类型的变量(全局的),后期可以直接使用
//也可以不写。也可以写多个。
1.3 特殊的声明
在声明结构的时候,可以不完全的声明。
//匿名结构体
//如果一个结构体,只想用一次就可以使用匿名结构体来定义。
struct
{
int a;
char b;
float c;
}x;//匿名结构体必须加结构体变量的。
//在使用的时候直接用变量就行。
//注意只能使用一次,就只能使用x这一个变量。
struct
{
int a;
char b;
float c;
}a[20], *p;
//如果这里让p = &x; 是不行的,类型不一样。
//匿名结构体的成员一样的时候,但是在编译器看来也是不同的类型的结构体
1.4 结构的自引用
在结构体中包含一个,类型为结构体的本身的成员 。
struct Node
{
int data;
struct Node* next;
};
//通过指针的形式 ,链接下一个节点。
//注意:错误实例:
struct Node
{
int data;
struct Node next;
};
//这里不知道sizeof(struct Node) 占多大内存
拓展:
typedef正常使用实例:
typedef struct stu
{
int data;//数据
struct stu* next;//指针
}stu;
//把struct stu 重新命名为stu
//这样在以后的书写的时候就会方便一点
int main()
{
struct stu n1;
stu n2;
//这里的 n1 和 n2 类型是一样的。
return 0;
}
对于一个匿名结构体也可使用typedef
typedef struct
{
int a;
char b;
}stu;
//这样重命名一个匿名结构体后,有了名字就不只是可以使用一次
//可以多次使用了
//上面和下面的是一样的东西。
typedef struct stu
{
int a;
char b;
}stu;
注意:
typedef struct
{
int data;//数据
stu* next;//指针
}stu;
//这个写法是错误的啊,
//这个就是先有蛋还是先有鸡的问题了
//如果结构体里面有自引用的时候就不能上面方法了
//应该用下面的方法
typedef struct stu
{
int data;//数据
struct stu* next;//指针
}stu;
还有一种用法:
typedef struct stu
{
int a;
char b;
}stu ,*pstu;
//这里的*pstu 是--> struct stu* 类型的
//意思就是对(struct stu*)结构体指针类型重命名为pstu*
1.5 结构体变量的定义和初始化
有了结构体类型,那如何定义变量,其实很简单
第一种:
struct book
{
char name;
float price;
char id;
}s1, s2, s3={“C语言”,55.5f,"zxf001"};
//这里的s1 ,s2,s3 就是在定义的时候就创建的变量
//这个的s1,s2 ,s3都是全局变量
//也可以在创建的同时给他初始化。
第二种:
struct book
{
char name;
float price;
char id;
};
int main()
{
struct book s1 = { "C语言", 66.6f, "zxd002" };
struct book s2;
struct book s3;
//可以在使用的时候在去创建变量也可以。
return 0;
}
结构体嵌套
struct book
{
char name[100];
float price;
char id[100];
};
struct test
{
struct book a;
struct test* next;
};
int main()
{
struct book s1 = { "C语言1", 55.5f,"zxf001" };//一般结构体初始化
struct test n = { {"C语言2", 66.6f, "zxf002"}, NULL };//结构体嵌套初始化
return 0;
}
注意事项:
struct book
{
char name[100];
float price;
char id[100];
}s1, s2, s3 = { "C语言1", 55.5f, "zxf003"};
int main()
{
//s2 = { "C语言2", 66.6f,"zxf002" };
//注意这里不能用这样的方式来初始化结构体。
//结构体应该在创建的时候,直接定义。不能先声明再初始化
//应该在声明的时候顺便初始化
s2.name = "C语言2";//注意这样定义结构体也是错误的
//name和id这里都是数组名 数组名隐式转化为指针,这里的指针是指针常量
//指针常量的指向是不能发生改变的,所以这样初始化数组的时候会会报错
//说表达式必须有可修改的左值
s2.price = 66.6f;
s2.id = "zxd002";//这里id也是同样的
//正确代码
strcpy(s2.name, "C语言2");
s2.price = 66.6f;
strcpy(s2.id, "zxf002");
printf("%s\n", s2.name);
printf("%f\n", s2.price);
printf("%s\n", s2.id);
return 0;
}
1.6结构体内存对齐
我们在深入讨论结构体内存大小的时候,就会涉及待结构体内存对齐。
引例:
typedef struct
{
char b;
int a;
char name;
}s1;
typedef struct
{
int a;
char b;
char name;
}s2;
int main()
{
s1 s;
printf("%d\n", sizeof(s1));//12
printf("%d\n", sizeof(s2));//8
return 0;
}
结构体内存对齐规则
1,结构体第一个成员存放在与结构体变量偏移量为0的地址处。
2,其他成员变量要对齐到与偏移量等于(对齐数)的整数倍的地址处。
3, 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4,如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
拓展:
1,offsetof - 宏
练习:
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));//12
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));//8
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));//16
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct s4));//32
为什么会存在内粗对齐呢?
1. 平台原因(移植原因):
2. 性能原因:
总体来说:
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
练习:
struct S3//8
{
char c;//对齐数1
int i;//对齐数4
};
#include<stdio.h>
struct S4
{
char c1;//对齐数1
struct S3 s;//对齐数4(max)
char d;//对齐数1
};
int main()
{
printf("%d\n", sizeof(struct S3));//8
printf("%d\n", sizeof(struct S4));//16
return 0;
}
struct S3//8
{
char c;//对齐数1
int i;//对齐数4
};
#include<stdio.h>
struct S4
{
char c1;//对齐数1
struct S3 s;//对齐数4
double d;//对齐数8
};
int main()
{
printf("%d\n", sizeof(struct S3));//8
printf("%d\n", sizeof(struct S4));//24
return 0;
}
1.7 修改默认对齐数
之前我们见过了 #pragma 这个预处理指令.这里我们再次使用,可以改变我们的默认对齐数。
举例:
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
//#pragma pack(0)
//设置默认对齐数为0,是没有意义的。
#pragma pack(1)//设置默认对齐数为1
//也就是不对齐。
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));//12
printf("%d\n", sizeof(struct S2));//6
return 0;
}
结论:
1,8结构体传参
直接上代码:
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* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
首选:print2的传参方式(传地址)
结论:
二,位段
结构体讲完就得讲讲结构体实现位段的能力。
2,1 什么是位段
例如:
#include<stdio.h>
struct A
{
int _a : 2;//代表_a这个成员只占2个bit位
int _b : 5;//_b这个成员只占5个bit位
int _c : 10;
int _d : 30;
};//:数字代表变量所占的bit位的意思。
//位段是通过结构体实现的。
//一共47位 8个字节就可以放得下
//内存对齐是牺牲空间保证效率。
//位段是牺牲效率节省空间的。
int main()
{
printf("%zd", sizeof(struct A));//8
}
这里a就是一位段,位段可以在满足实际的情况下节省不少空间。但是效率会比结构体低一些。
2,2 位段的内存分配。
举例:(以下演示的vs环境下的情况)
练习:
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
解析:
通过查看内存也是这样。
2,3位段的跨平台问题
总结:
2.4 位段的应用
上面的 4位版本号,4位首部长度------。等等数据是可以用位段来实现的,这样可以大大的节省空间。网络上数据包过大是非常影响这个传输速率的。使用位段效率会高一些。
三,枚举(enum)
枚举顾名思义就是一一列举。
把可能的取值一一列举
生活中:
性别有:男、女、保密,也可以一一列举。
月份有12个月,可以一一列举 这里就可以使用枚举了
这些东西可以使用枚举来列举。
3,1 枚举类型的定义
举例:
enum Sex//性别
{
male,//男
female,//女
secret//保密
};
//枚举常量都是有值的。
//默认数值是012---一直向后排。
int main()
{
enum Sex s1 = male;
enum Sex s2 = female;
enum Sex s3 = secret;
printf("%d", s1);//0
printf("%d", s2);//1
printf("%d", s3);//2
//枚举变量s1,s2,s3只能是以上列举的值不能是别的值。
return 0;
}
以上定义的 enum Sex 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
例如:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
//这里就是规定的数值。1 2 4
enum Sex
{
male = 1,
female = 3,
secret
};
//这里赋值前两个,后面默认加一 1 3 4
enum Sex
{
male = 1,
female ,
sec
};
// 1 2 3
3,2 枚举的优点
为什么使用枚举?我们可以使用 #define 定义常量,枚举的优点?
所以在定义常量的时候如果可以的话建议使用枚举来实现。
3,3 枚举的使用
enum Color//颜色
{
RED = 1,
GREEN = 2,
BLUE = 4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;//这种情况会出现强制类型转换,5是int 类型的。
//而clr是enum color 类型的。
四,联合(共用体)(union)
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
4,1 联合的定义
定义:
//联合类型的声明
union Un
{
char c;
int i;
};
//可以在;前面直接创建变量。和结构体相同。
int main()
{
//联合变量的定义
union Un u;
//计算一个变量的大小
printf("%d\n", sizeof(u));//4
printf("%p\n", &u);
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
//这三个都是一个地址。
//说明c和i 共用了第一个字节。
//为了不造成访问紊乱。
//在同一时间只会用其中的一个变量。
return 0;
}
在实际中就会有这样的场景。例如我们在描述学校里面一个人的身份的时候。人有教授,讲师。他是讲师就不是教授,是教授就不可能是讲师。在使用的时候就只能用其中的一个。
4,2 联合体的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)。
举例:(判断当前机器的大小端存储)
以前写的
int main()
{
int a = 1;
if (1 == *(char*)&a)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
改进版:
union Un
{
int a;
char b;
};
int main()
{
union Un u;
u.a = 1;
if (1 == u.b)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
//这里就是运用了 a 和 b 共用了同用了第一个字节。
//这里可以 typetef 重命名 简化类型。
4,3 联合大小的计算
联合体也是存在对齐地。
举例:
union Un1 //大小为8
{
char arr[5];//这是最大成员 5 不是最大对数4的整数倍、
//所以要对齐到4的整数倍 8
//数组的对齐数是数组成员地对齐数。
//这个联合体成员地对齐数就1
int i;//对齐数是4
};
union Un2 //大小为16
{
short arr[7];//这是最大成员 14 不是最大对数4的整数倍
//所以要对齐到4的整数倍 16
//数组的对齐数是数组成员地对齐数。
//这个联合体成员地对齐数就2
int i;//对齐数是4
};