目录
一.结构体
1.1什么是结构体
1.2结构体的声明
1.3结构体成员的类型与创建变量
1.4结构体成员的访问
1.5结构体和typedef
错误使用☀️
正确使用☁️
不同点⚡
1.6特殊的声明
匿名结构体定义变量
匿名结构体成员相同但类型不同
1.7结构的自引用
1.8结构体变量的定义和初始化✈️
1.9结构体内存对齐
1.9.1先放int,再放char
1.9.2调换顺序:先放char,再放int
如何计算两个结构体变量的偏移量差距
1.9.3先放两个char,再放一个int
1.9.4先放一个double,再放一个char,最后放一个int
1.9.5嵌套结构体计算
1.9.6回到整个题目
2.0修改默认对齐数⭕
2.1 结构体传参⭕
二.位段
2.1 什么是位段⁉️
2.2 位段的内存分配❗
2.3 位段的跨平台问题➰
2.4 位段的应用✅
三. 枚举
3.1 枚举类型的定义❎
3.2 枚举的优点❗
四. 联合(共用体)
4.1 联合类型的定义
4.2 联合的特点⭕
4.3 联合大小的计算 ⭕
先放一个char数组,再放一个int变量
先放一个short数组,再放一个int变量
同一种类型的数据的集合是数组,和数组不同,结构体是多种类型的数据的集合。
现实生活中,我们要汇总学生的体检信息时,我们会为名字、身高、体重分别单独建表吗?显然不会。通常是给每人发一张"体检卡",在资料上面分别记录着名字、身高、体重等信息。如果一个班上有60个学生,那么50张"体检卡"即为一个集合。
下图即为"体检卡":
上图4个数据进行结构体的声明
- char[64]型的姓名
- int型的身高
- float型的体重
- long型的奖学金
其中,结构体的名字student称为结构名。{}中声明的name、height等称为结构体成员
注意结构体声明的末尾也要加上分号。
结构的成员可以是标量、数组、指针,甚至是其他结构体。
#include
//定义学生类型
struct Stu
{
//成员变量
char name[20];
int age;
float weight;
} s4, s5, s6;//全局变量
int main()
{
//int num = 0;
//通过类型创建变量
struct Stu s1;//局部变量
struct Stu s2;
struct Stu s3;
return 0;
}
我们可以看到 s 有成员name和age
那我们如何访问s的成员?
struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员
struct Book
{
char name[20];
int price;
};
void print(struct Book* p)
{
printf("%s %d\n", p->name, p->price);
}
int main()
{
struct Book b1 = {"C语言", 66};
struct Book b2 = { .price = 80, .name = "java"};
//结构体变量.结构体成员
printf("%s %d\n", b1.name, b1.price);
printf("%s %d\n", b2.name, b2.price);
//结构体指针->结构体成员
//struct Book* p1 = &b1;
print(&b1);
return 0;
}
typedef声明是创建数据类型的同义词的声明(而非创建新的数据类型)
typedef struct
{
int data;
Node* next;
}Node;
应该先有一个完整的类型名才能使用Node,而成员变量里面已经使用了,存在先后顺序问题
typedef struct Node
{
int data;
struct Node* next;
}Node;
为简化创建变量的过程,可以讲类型名重定义,就会使用到typedef
typedef struct Book
{
char name[20];
int price;
}Book;//不能再将全局变量加在这里后面
Book b3;//全局
Book b4;//全局
int main()
{
struct Book b1;
struct Book b2;
Book b3;//struct Book的别名
return 0;
}
对类型名的重命名也可以用到
struct Book
{
char name[20];
int price;
};
typedef struct Book Book;
这里是两个不同的东西
struct
{
char c;
int a;
double d;
}s1;//匿名结构体变量
typedef struct
{
int data;
char c;
} S;//结构体类型,本来是没有名字的,现在重新起名字
struct //省略了结构名 struct+结构名=类型名
{
char c;
int a;
double d;
}s1;//匿名结构体只能这样定义变量
int main()
{
struct s2;//错误,不能这样写
return 0;
}
匿名结构体是一次性用品
struct
{
char c;
int a;
double d;
}s1;
struct
{
char c;
int a;
double d;
}* ps;
int main()
{
ps = &s1;//err
return 0;
}
- 相同结构体类型:
通过同一个结构体 定义出来的变量 就相当于 int a; int b
a和b是两个不同的变量,但是它们的类型都是int类型
- 结构体类型不同: (上图就是结构体类型不一样)
用一个结构体类型的指针 指向另一个结构体类型的指针时 编译器会报出警告,类型不兼容
虽然两个匿名结构体类型的成员都完全相同,但是他们的类型还是不同的
//这是一个错误的示范
struct Node
{
int data;
struct Node n;//自己类型的变量
};
这里是结构体中的一个成员是它这个结构体本身。 那它无限套娃 永远也停止不了
这属于线性数据结构,一个结点包括数据域和指针域
正确写法:
//当一个结构体要找到另外一个同类型的结构体的时候
//应该在自己类型里面包含一个自己类型的指针,而不是自己类型的变量
struct Node
{
int data;//4
struct Node* next;//4/8
};
int main()
{
struct Node n1;
struct Node n2;
n1.next = &n2;
return 0;
}
struct S
{
int a;
char c;
}s1;
struct S s3;//这样定义也是全局变量
struct B
{
float f;
struct S s;
};
int main()//在这里定义的全是局部变量
{
//int arr[10] = {1,2,3};
//int a = 0;//有了值才有确定性
//结构体怎么初始化呢?
struct S s2 = {100, 'q'};
struct B sb = { 3.14f, {200, 'w'}};//结构体的嵌套初始化正常来说要按照顺序,
//不按照顺序初始化
struct S s3 = {.c = 'r', .a = 2000};//指定顺序
printf("%f,%d,%c\n", sb.f, sb.s.a, sb.s.c);//结合体成员的访问,后面两个是因为是嵌套了一个结构体
return 0;
}
另一个例子:
struct S
{
char c;
char arr[10];
int* p;
}s1, s2;
struct S s3;
struct B
{
int a;
struct S s;
double d;
}sb1, sb2;
struct B sb3;
int main()
{
struct S s4 = {'c', "zhangsan", NULL};
int a = 10;
struct B sb4 = { 100, {'q', "lisi", &a}, 3.14};
return 0;
}
这也是一个特别热门的考点: 结构体内存对齐
#include
struct S1
{
int a;
char c;
};
struct S2
{
char c1;
int a;
char c2;
};
struct S3
{
char c1;
int a;
char c2;
char c3;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
return 0;
}
运行结果:
按以往的知识来说,从S1到S3的值分别推测为5,6,7,可是结果却不是这样的,并且S3比S2还多了一个成员变量,但是它们的大小却是相等的
为什么呢?计算结构体,需要用到以下规则
struct S1
{
int a;
char c;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
那么我们讲char c和int a的位置调换一下,结果又会不会不一样呢?
struct S1
{
char c;
int a;
};
int main()
{
printf("%d\n", sizeof(struct S2));
return 0;
}
c和a之间真的浪费了3个字节的空间吗?
#include
#include
struct S
{
char c1;
int a;
};
int main(){
struct S s = {0};
printf("%d\n", offsetof(struct S, c1));//0
printf("%d\n", offsetof(struct S, a));//4
return 0;
}
方法1:取地址计算,4和8之间差4
可以发现它们之间差了4个字节
方法2:offsetof函数
使用之前要引用一下头文件
可以看到是返回成员变量的偏移量
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S2));
return 0;
}
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S3));
return 0;
}
执行效果:
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
//计算嵌套结构体总结构体的大小
printf("%d\n", sizeof(struct S4));
return 0;
}
#include
struct S1
{
int a;
char c;
};
struct S2
{
char c1;
int a;
char c2;
};
struct S3
{
char c1;
int a;
char c2;
char c3;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
return 0;
}
为什么存在内存对齐?
大部分的参考资料都是如是说的:
让占用空间小的成员尽量集中在一起
#include
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
调整一下顺序就可以减少空间的浪费
之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。
#pragma pack(1)//设置默认对齐数为1
struct S
{
char c1;//1 1 1
int i; //4 1 1
char c2;//1 1 1
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S));
return 0;
}
因为它们每一个对齐数都是1,所以只要是1的倍数的偏移量位置,它们都可以存放,所以就不会有空余的内存
结论:
函数压栈问题
结构体传参时,结构体压栈所需要的空间开销就会大一些
若是传地址,无非就是4/8byte,开销会大大减少
例子1:
struct S
{
int arr[100];
int n;
};
void print1(struct S ss)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", ss.arr[i]);
}
printf("\n%d\n", ss.n);
}
void print2(struct S* ps)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n%d\n", ps->n);
}
int main()
{
struct S s = { {1,2,3,4,5}, 100 };
print1(s);
print2(&s);
return 0;
}
例子2:
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)//这里加一个const避免不小心改掉实参
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
位段 :二进制位
1. 位段的成员必须是 int 、 unsigned int 或 signed int 。2. 位段的成员名后边有一个冒号和一个数字。
比如:
//位段 - 二进制位
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};//47bit --> 6*8=48 --> 6byte ?
int main()
{
struct A sa = { 0 };
printf("%d\n", sizeof(sa));
return 0;
}
A就是一个位段类型。以上结果猜测为47bit,可是结果却是8byte -->64bit,还浪费了空间?
struct A
{
int _a;
int _b;
int _c ;
int _d;
};//这里面是结构体成员这里每一个int 占4byte,总共16byte -->128bit对比64bit都节省了一半的空间
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. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
一周的星期一到星期日是有限的 7 天,可以一一列举性别有:男、女、保密,也可以一一列举。
月份有 12 个月,也可以一一列举
这里就可以使用枚举了
enum Day // 星期{Mon ,Tues ,Wed ,Thur ,Fri ,Sat ,Sun};enum Sex // 性别{MALE ,FEMALE ,SECRET} ;enum Color // 颜色{RED ,GREEN ,BLUE};
enum Color // 颜色{RED = 1 ,GREEN = 2 ,BLUE = 4};
#include
enum Sex
{
MALE,
FEMALE,
SECRET
};
int main()
{
enum Sex s = FEMALE;//1,若赋值成1在c语言中是没问题的,在c++会出现错误,就拿上面定义的可能取值给它赋值
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
}
为什么使用枚举?
1. 增加代码的可读性和可维护性
enum OPTION { EXIT,//表示0 PLAY,//1 ADD,//2 DEL//3 };
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
#define EXIT 0 #define PLAY 1
- #define宏常量是在预编译阶段进行简单替换,枚举常量则是在编译的时候确定其值。
3. 防止了命名污染(封装)
4. 便于调试
- 一般在编译器里,可以调试枚举常量,但是不能调试宏常量
5. 使用方便,一次可以定义多个常量
- 枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个
- 枚举可以自增1,这样不用每一个值都定义,而宏必须每个值都定义
- 枚举是一个集合,代表一类值,像你代码中的颜色归为一类,方便使用,而宏不能形成集合。
union Un
{
char c;
int i;
};
int main()
{
union Un u;
printf("%d\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &(u.i));
printf("%p\n", &(u.c));
return 0;
}
union Un
{
char arr[5];//1 4 1 -->共5个字节
int n;// 4 8 4
};
int main()
{
printf("%d\n",sizeof(union Un));
return 0;
}
union Un
{
short s[7];
int n;
};
int main()
{
printf("%d\n", sizeof(union Un));
return 0;
}
同理:结果必须也是4的倍数,short数组已经占了14个byte,所以联合体大小为16byte
本章完。欢迎各位大佬补充!