前言
目录
1----结构体
1-1结构体的声明
结构体的表示形式
假如要描述一个学生,就可以使用结构体类型~~
//结构体的声明
struct students
{
char name[20];//姓名
int age;//年龄
float score;//成绩
char id[20];//身份证号
};//注意:分号不能丢
注意:分号不能丢
在声明结构体时可以使用typedef创建一种新的类型
比如:
typedef struct students
{
char name[20];//姓名
int age;//年龄
float score;//成绩
char id[20];//身份证号
}stu;//分号不能丢
这个技巧和声明一个结构标签的效果相同,区别于stu现在是一个类型名但不是结构标签~~
1-2结构体变量的创建和初始化
例如:初始化学生的信息,一起看看代码吧~~
//结构体的创建和初始化
#include<stdio.h>
struct students
{
char name[20];//姓名
int age;//年龄
float score;//成绩
char id[20];//身份证号
};//分号不能丢
int main()
{
//1-按结构体成员的顺序初始化
//struct students s = { "张三",18,90.5f,"123456789"};
//2-按指定的顺序初始化
struct students s = { .name="张三",.age=18,.score=90.5f,.id="123456789" };
printf("name:%s\n", s.name);
printf("age:%d\n", s.age);
printf("score:%f\n", s.score);
printf("id:%s\n", s.id);
return 0;
}
1-3结构体的特殊声明
在声明结构的时候,可以不完全的声明。(缺少标签)——匿名结构体
比如下面的结构体就是一个匿名结构体:
struct
{
char b;
int a;
float c;
}x;
但匿名结构体也存在问题
例如:
//匿名结构体只能使用一次
struct
{
char b;
int a;
float c;
}x;
struct
{
char b;
int a;
float c;
}*ps;
#include<stdio.h>
int main()
{
ps = &x;
return 0;
}
输出结果:
这段代码与匿名结构体有关
上面的结构在声明的时候省略了结构体的标签。
那么
分析:
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次!!
对匿名结构体的改进
可以使用typedef重命名匿名结构体,这样就可以消除匿名结构体只能使用一次的缺陷!!
当这个结构体只使用一次是可以考虑使用匿名结构体,否则不要考虑!!
当然匿名结构体也可以重命名~~
//改善
typedef struct
{
char b;
int a;
float c;
}stu;//匿名结构体类型也可以重新命名
#include<stdio.h>
int main()
{
stu s;
return 0;
}
当然匿名结构体也可以进行初始化~~
struct
{
int a;
char b;
double c;
}s = {18,'A',3.14};//在这里创建变量,也可以初始化,没有标签名
#include<stdio.h>
int main()
{
printf("%c %d %lf", s.b, s.a, s.c);
return 0;
}
输出结果:
1-4 结构体的自引用
在结构体中包含一个类型为该结构体本身的成员可以吗?
比如:定义一个链表的节点,其中节点的结构是:(数据域+指针域)/(数据和地址)
struct Node {
int data;
struct Node next;
};
那么试试计算链表节点这个结构体的大小吧~~
其实这样的引用方式是错误的,因为一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大,这是错误的!!
那么看看正确的自引用方式吧~~
struct Node {
int data;//数据
struct Node* next;//指针
};
在结构体自引用的过程中,使用typedef对匿名结构体重命名,也容易产生问题,看看下面的代码可行吗?
typedef struct {
int data;//数据
Node* next;//指针
}Node;
其实这样是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的!!
解决方案:定义结构体不要使用匿名结构体!!
实例代码如下:
typedef struct Node {
int data;//数据
struct Node* next;//指针
}Node;
关于结构体的自引用,有两种写法。完整代码如下~~
//结构体的自引用
//匿名的结构体类型是不能实现这种结构体的自引用效果的
#include<stdio.h>
typedef struct Node {
int data;//数据
struct Node* next;//指针
}Node;
// 写法1
struct Node {
int data;//数据
struct Node* next;//指针
};//写法2
int main()
{
struct Node Node1;
Node node1;
return 0;
}
1-5计算结构的大小
想知道结构体的大小我们得学习结构体内存对齐~~
1-5-1对齐规则
首先我们得掌握结构体的对齐规则
比如:偏移量是4的倍数,地址便是4的倍数~
试着做这两道练习,检验你是否真的会计算结构体大小~~
注意:VS对齐数一般设为2的整数倍
练习1——(规则1,2,3)
//计算结构体的大小
//内存对齐
#include<stdio.h>
struct S2
{
char c1;//1 8 1
char c2;//1 8 1
int i;//4 8 4
};
int main()
{
printf("%zd\n", sizeof(struct S2));
return 0;
}
输出结果:
解析:
练习2——(规则4)
#include<stdio.h>
struct S3
{
double d;//8 8 8
char c;//1 8 1 所有最大对齐数中最大的是8,那么结构体的大小就是8的倍数
int i;//4 8 4
float e;//4 8 4
};//大小为24
struct S4
{
char c1;//1 8 1
struct S3 s3;//看s3里的最大对齐数-8
double d;//8 8 8
};
int main()
{
printf("%zd\n", sizeof(struct S4));//返回size_t
return 0;
}
输出结果:
解析:
S3结构体大小的计算
S4结构体大小的计算
1-6内存对齐的原因
1、平台原因
2、性能原因
在设计结构体是要满足内存对齐,又要节省空间
可以让占用空间小的成员,尽量集中在一起~~
下面这段代码就可以说明这个方法~~
尽管类型成员相同,但它们所占的空间大小有所不同
struct S1
{
char c1;//1
int i;//4
char c2;//1
};
struct S2
{
char c1;//1
char c2;//1
int i;//4
};
int main()
{
printf("%zd\n", sizeof(struct S1));//12
printf("%zd\n", sizeof(struct S2));//8
return 0;
}
输出结果
1-7修改默认对齐数
#include<stdio.h>
#pragma pack(1)
//#pragma pack(2)
//设置默认对齐数
struct S1
{
char c1;//1 1 1 1 2 1
int i;//1 4 1 2 4 2
char c2;//1 1 1 1 2 1
//对齐数为1时,每个成员对齐到数字1上,说明没有对齐
//那么连续存放就OK,最终结构体的大小只要是1的倍数就OK
};
#pragma pack()//取消设置的对齐数,还原为默认的
int main()
{
printf("%zd\n", sizeof(struct S1));//3
return 0;
}
1-6结构体传参
#include<stdio.h>
//struct book {
// char name[20];
// int price;
//}s = {"简爱",100};//初始化写法1
struct book {
char name[20];
int price;
};
struct book s={ "简爱",1000 };//初始化写法2
void Print(struct book* ps)
{
printf("name->%d\n", ps->price);
}
int main()
{
Print(&s);
return 0;
}
//结构体传参的时候要传结构体的地址!!
输出结果:
结构体传参的时候要传结构体的地址的原因:
1-7位段
1-7-1认识位段
位段是专门用来节省内存的。
位段声明和结构是类似的,但有所差异~
位段的声明
struct A
{
int _a ;
int _b ;//4个字节占32个比特位
int _c ;
int _d ;
int _e ;
};
那么位段的大小怎么计算呢?
例如
位段——“位”:二进制位
struct A
{
int _a : 2;//_a只占2个比特位
int _b : 5;
int _c : 10;
int _d : 30;
int _e : 31;
};
A就是一个位段,那么A的大小是多少呢?
//位段——位:二进制位
struct A
{
int _a : 2;//_a只占2个比特位
int _b : 5;
int _c : 10;
int _d : 30;
int _e : 31;
};
#include<stdio.h>
int main()
{
printf("%zd\n", sizeof(struct A));//12
return 0;
}
输出结果
为什么是12呢?想知道这个得知道位段的内存分配~~
1-7-2位段的内存分配
注重可移植性的程序应该避免使用位段。由于下面这些与实现有关的依赖性,位段在不同的系
统中可能有不同的结果。
就这上面的代码,解释上这段吧~~
那么现在可以解释为什么A的大小是12字节了。
图解:
为了再次认识位段的内存分配,看看下面这段代码吧~~
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;
//这个位段的空间是如何开辟的呢?
解析:
例如:存数字0,1,2,3
00-0,01-1,10-2,11-2,两个二进制位就够了,但有30个比特位浪费了
此时就可以使用位段——在一定程度节省内存空间
C语言没有规定标准,剩余空间不够时,是浪费还是继续存放
这就取决于编译器
VS上数据从右往左存,剩余空间不够时,是浪费
#include<stdio.h>
struct S
{
char a : 3;//010
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;//1010发生截断,存的是010
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
图解
调试过程
1-7-3位段的跨平台问题
1-7-4位段的使用的注意事项
//位段的使用
//使用位段的结构体类型中的成员类型应该相同
struct A
{
int _a : 2;//_a只占2个比特位
int _b : 5;
int _c : 10;
int _d : 30;
int _e : 31;
};
#include<stdio.h>
int main()
{
struct A sa = { 0 };
int b = 0;
scanf("%d", & b);
sa._b = b;
printf("%zd", sizeof(struct A));
return 0;
}
1-7-5位段的拓展
2----联合体
2-1联合体类型的声明
//联合体类型的声明
#include<stdio.h>
union un {
char a;
int b;
};
int main()
{
union un u = { 0 };//联合体变量的定义
//计算联合体变量的大小
printf("%zd\n", sizeof(u));
return 0;
}
输出结果:
为什么输出结果是4呢?
那要看联合体类型的特点呢~~
2-2联合体的特点
看看这两段代码,相信你会对此有深入的理解。
第一段代码
#include <stdio.h>
//联合体的声明
union Un
{
char c;
int i;
};
int main()
{
//创建联合体变量
union Un un = {0};
// 它们的地址大小一样?
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
printf("%p\n", &un);
return 0;
}
输出结果:
再看看第二段代码
#include <stdio.h>
union Un//联合体的声明
{
char c;
int i;
};
int main()
{
union Un un = { 0 };//联合体变量的创建
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
输出结果:
从输出结果可以看出,第一段代码输出的地址都一样,而从第二段代码输出结果可以分析,将i的第四个字节的内容修改为55了。
因此可以画出联合体(un)的内存发布图
2-2-1对比结构体和联合体的内存发布情况
我们再对比一下相同成员的结构体和联合体的内存发布情况。
结构体
struct S
{
char c;
int i;
};
内存分布情况:
联合体:
union un
{
char c;
int i;
};
内存发布情况:
2-3联合体的大小计算
试着计算下面两个联合体的大小吧~~
// 计算联合体的大小
#include<stdio.h>
union Un1
{
char c[5];//1 8 1
int i;//4 8 4
};
union Un2
{
short c[7];//2 8 2
int i;//4 8 4
};
int main()
{
union Un1 u1 = { 0 };
union Un2 u2 = { 0 };
printf("%zd\n", sizeof(u1));
printf("%zd\n", sizeof(u2));
return 0;
}
输出结果:
解析
联合体Un1的内存发布
联合体Un2的内存发布
2-3-1实际运用
比如有一个礼物兑换单,礼物兑换单中三个商品:书,杯子,衬衫。
每一种商品都有库存量,价格,商品类型和商品类型相关的其他信息
如果将这些信息简单的一 一罗列在一个结构体中,用起来很方便,但这样使得结构体的大小偏大,比较浪费内存,因为对于兑换单中的商品来说,只有部分属性信息是常用的,比如商品为书,就不需要design,colour,size……
所以可以把公共属性单独写出来,剩余属于各种商品本身的属性使用联合体联合起来,这样在一定程度上节省了内存~~
struct gift_list
{
//公共属性
int stock_number;//库存量
double price;//价格
int type;//商品类型
//每个商品具有的自己的属性
union {
struct {
char book_name[20];//书名
char author[20];//作者
int page;//页数
}book;
struct {
char design[20];//设计
}cup;
struct {
char design[20];//设计
char colour[10];//颜色
char size[10];//尺寸
}shirt;
};
};
2-4联合体的经典练习
写一个小程序判断,你的机器是大端模式还是小端模式
在写程序之前,先来回顾一下大端和小端模式
2-4-1 方法1--联合体
练习判断一个机器是大端还是小端
根据联合体的内存发布特点可以轻松解决
写法1
#include<stdio.h>
int check_sys()
{
union {
int a;
char b;
}sa;
sa.a = 1;
return sa.b;
}
int main()
{
if (check_sys())
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
2-4-2方法2--字节
练习判断一个机器是大端还是小端
判断一个字节存储的是1还是0
//写法2
#include<stdio.h>
int check_sys()
{
int i = 1;
return (*(char*)(&i));
}
int main()
{
if (check_sys())
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
本电脑的输出结果:
3----结构体与联合体区别(重点)
C语言中结构体(struct)与联合体(union)是两种不同的数据结构,它们的主要区别在于内存利用、成员访问和用途。具体分析如下:
3-1. 内存利用方面
3-2. 成员访问方面
3-3. 用途方面
3-4 总结
4----枚举
4-1枚举的声明
比如:性别:男、女,可以一 一列举
三原色:红、绿、蓝,也可以一 一列举~
这些数据的表示就可以使用枚举!!
比如三原色使用枚举类型表示:
//枚举类型的声明
enum Colour {
RED ,
GREEN ,
BLUE ,
};
比如:
enum Colour{
RED = 2,
GREEN = 4,
BLUE = 6,
};
4-2枚举类型的优点
之前我们学习了#define定义常量,那么枚举类型与之有什么不同呢?
4-3枚举类型的使用
enum Colour{
RED = 2,
GREEN = 4,
BLUE = 6,
};
enum Colour clr = GREEN;//使用枚举常量给枚举变量赋值
C语言中可以拿整数给枚举变量赋值,但在C++中不行!!
5----自定义类型的总结
6----警告的总结
7----编程提示的总结
制作不易,老铁们三连吧,别下次一定了!!