欢迎来得小编的新的一篇文章,在这篇文章中我们主要讲解如下:
struct tag
{
member-list;
}variable-list;
struct Stu1
{
char name[20];//名字
int age;//年龄
char sex[10];//性别
char id[20];//学号
};
struct Stu1
{
char name[20];//名字
int age;//年龄
char sex[10];//性别
char id[20];//学号
}S1,S2;
在我们结构体创建的同时,我们也声明了 变量名,那么我们就可以把它认为是 全局变量,
因为宝子们想啊,它是在 main 外部的.
main 内部的,我们不妨把它看作是局部变量
注意注意一点的是,结构体声明在最后的花括号后一定要打上我们的分号 “;” 结尾。
#include
struct Stu1
{
char name[20];//名字
int age;//年龄
char sex[10];//性别
char id[20];//学号
}S1 = { "zhoushauibi",12,"nv","36517519" };
int main()
{
printf("name: %s\n", S1.name);
printf("age : %d\n", S1.age);
printf("sex : %s\n", S1.sex);
printf("id : %s\n", S1.id);
return 0;
}
#include
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[10];//性别
char id[20];//学号
};
int main()
{
//按照结构体成员的顺序初始化
struct Stu s = { "张三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
return 0;
}
看到这里,小爱同学有疑问啦,如果我不想要默认初始化,按照自己的顺序来初始化呢 !!!
请宝子们往下看小编的操作哦
#include
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[10];//性别
char id[20];//学号
};
int main()
{
//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "nv" };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);
return 0;
}
就问帅不帅啦啦啦
在声明结构的时候,可以不完全的声明。
et:
// 匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], * p;
上⾯的两个结构在声明的时候省略掉了结构体标签(tag)。
那么问题来了?
//在上⾯代码的基础上,下⾯的代码合法吗?
p = &x;
警告:
编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
#include
struct stu
{
char name[20];
int age;
int num;
};
void print1(struct stu x)
{
printf("传值调用:name->%s age->%d num->%d\n", x.name, x.age, x.num);
}
void print2(struct stu *p)
{
printf("传址调用:name->%s age->%d num->%d\n", p->name, p->age, p->num);
}
int main()
{
struct stu a = { "zhoushuaibi",19,848621 };
//传值调用
print1(a);
//传址调用
print2(&a);
return 0;
在上面小编利用了函数的 传值调用 和 传址调用
发现输出内容是一样的 ! ! !
本次小编最想说明的还是 传址调用 ,相信细心的小伙伴已经观察到了我利用了 “->” 这个符号,哈哈哈哈
其他它就是今天我们要讲解的 结构体成员访问操作符 ,
这里我们主要说明 传址调用 , 上面我们先创建了指针变量,我们都知道 指针 的作用 就是 指向地址 的 变量,
那么我们就利用指针来 -> 来指向我们想要的成员对象,来达到调用的效果.
看到上面这两种方法,友友们认为是传值好,还是传址好呢?
我认为嘛,如果我们从空间内存来看:
1.如果是传值的话 我们需要把整个结构体传过去,就需要开辟整个结构体大小的内存空间.
那如果结构体成员很多呢,有可能导致内存空间开辟过大就会出现问题.
2.如果是传址的话 我们只需要把首空间地址传过去就可以啦 !
综上所述: 我们因为考虑到内存空间的合理利用,我们还是优选传地址调用.
首先我们让宝子们看个东西:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
#include
int main()
{
printf("%zd\n", sizeof(struct S1));
printf("%zd\n", sizeof(struct S2));
return 0;
}
是不是感到很疑惑,这是为啥,这是为啥呢???
答案说简单不简单,说难不难,主要我们要理解这个结构体啊他是如何存储的,这个是最关键的!!!
我们已经掌握了结构体的基本使⽤了
现在我们深⼊讨论⼀个问题:计算结构体的⼤⼩。
这也是⼀个特别热⻔的考点: 结构体内存对⻬
⾸先得掌握结构体的对⻬规则:
<1>. 我们先标好偏移量
<2>. c1 的字节数与默认对齐数进行比较从而得到我们的较小值 1 ,直接填充在我们的偏移量为 0 的格上
<3>. 找准 对齐数 的倍数
<4>.结构体最终得到的内存是 最大偏移量 的整数倍
⼤部分的参考资料都是这样说的:
显而易见,我们的内存分配中,有多好空出来的内存,为什么要这样做呢?
总体来说:结构体的内存对⻬是 拿空间 来 换取时间 的做法.
比如用 int 来读取时,必然是4个字节4个字节读,如果黏在一起,不能完整的读取,就会多读几遍导致 时间的消耗 。
struct S3
{
double d;
char c;
int i;
};
struct S2
{
char c1;
struct S3 s3;
double d;
};
#include
int main()
{
printf("%zd\n", sizeof(struct S2));
return 0;
}
嵌套的结构体的内存是
结构体成员(包括本身的结构体成员和嵌套的结构体)中最大整数倍,千万不然认为是嵌套结构体的对齐数的整数的倍。
#pragma 这个预处理指令
可以改变编译器的默认对⻬数
#include
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
int main()
{
//输出的结果是什么?
printf("%zd\n", sizeof(struct S));
return 0;
}
#pragma pack () 这个预处理可以设置默认对齐数
() 内可以放设定对齐个数,如果为空即可还原对齐数.
#include
#pragma pack(1)
//设置默认对⻬数为1
#pragma pack()
//取消设置的对⻬数,还原为默认
struct S
{
char c1;
int i;
char c2;
};
int main()
{
//输出的结果是什么?
printf("%zd\n", sizeof(struct S));
return 0;
}
在这里我们就利用设置默认对齐数有效的进行认为的结构体内存分配啦 ! ! !
结构体讲完就得讲讲结构体实现 位段 的能⼒。
位段的声明和结构是类似的,有两个不同:
位段的成员必须是 int、unsigned int 或 signed int ,在 C99 中位段成员的类型也可以
选择其他类型。
位段的成员名后边有⼀个 冒号 和⼀个 数字
#include
//⼀个例⼦
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;
printf("%zd", sizeof(struct S));
return 0;
}
在这里小伙伴们是不是也疑问万分呢?
按道理来说整个结构体内存大小应为 4 个字节,可突然怎么就变成了 3 个呢 ???
位段,位段…
说白了就是按 二进制 中的比特位来人为控制 比特的长度 分配内存
上面是以 VS2022 为例的,它的分配规则是一旦该单位字节不够分配 ***比
特的长度***
就会浪费剩余的 比特的长度 重新开辟 单位字节 存放 比特的长度
但是呢?
VS 2022 是这样,但是在不同的编译环境是不一样的存储方式,也有可能不浪费剩余的 比特长度, 继续存放的.
所以就会出现以下问题 ! ! !
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//这是错误的
return 0;
}
这里是绝对不允许取地址的 ! ! !
因为啊,我们的 & 取出的是以字节为单位的内存空间,但这里的位段是以比特为单位的字节空间.
#include
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = { 0 };
//正确的⽰范
int a = 0;
scanf("%d", &a);
sa._b = a;
return 0;
}
最后小编再梳理一下本篇文章的主要内容:
1.结构体是怎么创建的 ,并 全局初始化 或 局部初始化 的,但也要注意有个 匿名声明 的玩意.
2.结构体访问操作符"->"的实际运用及其特点.
3.结构体是依据 内存对齐 来存储的,和实际所 内存对齐 存在的意义
4.位段的 概念理解 ,以及它的 实际的用法 及 特点 与 不足.
本次博文就到这里了,感觉各位小伙伴的赏脸品读小编写的拙作哦,
如果觉得小编写的还不错的咱可支持三关下,不妥当的咱评论区指正,希望我的文章能给各位家人们带来哪怕一点点的收获就是小编创作的最大动力