🚩write in front🚩 🔎大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 🏅2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4~2021|2022博客之星TOP100|TOP63~周榜159 ⌁ 总榜751~ 🆔本文由 謓泽 原创 CSDN首发🙉 如需转载还请通知⚠ 📝个人主页-謓泽的博客_CSDN博客 📃 📣系列专栏-【C】系列_謓泽的博客-CSDN博客🎓 ✉️我们并非登上我们所选择的舞台,演出并非我们所选择的剧本
目录
在这里用简短的话先说说结构体↓ 结构体实际上是一些值的集 合,结构的每个成员是不同的变量。所以在这里结构体实际上也是复杂对象类型称之为构造类型,我觉得可以把这个构造类型看成是一个项目的总共。而基本类型就是小的项目。 而在这里很多人可能会联想到数组,但是数组是一组相同类型的元素集合。而我们结构体可以是不同类型的元素的集合。在这里用玩具盒子来表示结构体名,用其它玩具表示每个不同の成员,如下图所示:↓
上述在这里书是一个复杂对象,我们要创建书的类型那么就是一个结构体类型。 那么我们来写一个"书"的结构体类型代码。如 ↓ 所示
#include<stdio.h>
typedef struct BOOK
{
char book_title[20];
char name[20];
char PH[20];
int Pricing;
int id;
}BOOK;
int main(void)
{
BOOK s = { "xxxxx", "张三", "aaaaa", 20, 123456 };
printf("book_title:%s\n", s.book_title);
printf("name:%s\n", s.name);
printf("PH:%s\n", s.PH);
printf("Pricing:%d\n", s.Pricing);
printf("id:%d\n", s.id);
return 0;
}
运行结果 ↓ book_title:xxxxx name:张三 PH:aaaaa Pricing:20 id:123456
struct stu* p;
如上述代码这是一个结构体指针变量说明结构体指针变量p指向(->)的是一个结构体类型变量地址也就是保存x的地址。
注意:(->)指向操作符是一种通过指针的方式去访问结构体内的成员一种便捷写法的反方式。
示例代码如下↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//结构体类型制作
struct stu
{
int id;
char c;
}*p;
int main(void)
{
struct stu age = { 18,'C' };//结构体变量以及初始化
p = &age; //结构体指针
printf("id:%d\n", p->id);
printf("c:%c\n", p->c);
return 0;
}
运行结果🖊
id:18
c:C
在声明结构体的时候,实际上可以不完全的进行声明。
struct
{
char book_title[20];
char name[20];
char PH[20];
int Pricing;
int id;
}BOOK;
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct
{
int id;
char c;
}BOOK;
int main(void)
{
printf("%d\n", BOOK.id = 18);
printf("%c\n", BOOK.c = 'c');
}
运行结果:18
c
上述代码是个什么意思,实际上就是我们的结构体名是没有起名字的。但是整体来说我选中的还是依旧是一个结构体类型,那么这种叫做什么呢? → 匿名结构体类型。 注意🔥:匿名结构体创建完之后只能使用一次,具有局限性。 而用这个匿名结构体类型我们是直接创建了一个全局变量BOOK,和前面代码当中是一个意思只不过我们把结构体的标签也就是结构体当中的名给省略掉了。 那么下面再来说说匿名结构体指针,如下代码所示 ↓
struct
{
char book_title[20];
char name[20];
char PH[20];
int Pricing;
int id;
}*BOOK1;
*是匿名结构体的指针,用指针类型创建了BOOK1,那么此时我们可以用 BOOK1 = &BOOK吗?如 ↓ 代码所示:
#include<stdio.h>
struct
{
char book_title[20];
char name[20];
char PH[20];
int Pricing;
int id;
}BOOK;
struct
{
char book_title[20];
char name[20];
char PH[20];
int Pricing;
int id;
}*BOOK1;
int main(void)
{
BOOK1 = &BOOK;
return 0;
}
在这里编译器会出现一个警告,如果你强行这样去写也是运行的过去の。
在这里看来,编译器虽然你们当中的结构体成员变量都是一模一样的,但编译器还是会认为这两个是不同的类型。那么 *BOOK1 就不能存放 BOOK 的地址了。
结构体自己引用其实就是:结构体里面是可以包含结构体的。
struct Book
{
int id;
struct Book book;
}
在上述代码中创建了一个结构体,在里面当中也创建了一个结构体。 上述代码是错误的,因为它死递归了,没有限制大小,它的大小会一直增加下去,取决于你创建的结构体。
那么结构体该如何实现自引用。 如下代码所示↓
struct Book
{
int id;
struct Book* book;//指针变量大小固定可算
}
上述代码才是自引用使用正确的, 自引用不是包含同类型的结构体变量,而是包含同类型的结构体指针。看到这个如果学过数据类型的小伙伴们可以快速的理解。
数据域:存放的是我所要存储的数据。 指针域:是为了找到下一个的节点的地址。
sizeof(表达式)→获取某个数据类型所占用空间的 字节数 或者 是表达式的长度。 那么我们来看看结构体内存的大小是多少,如下代码所示↓
#include<stdio.h>
struct B1
{
int name;
char c;
};
struct B2
{
char c1;
struct B1
char c2;
};
int main(void)
{
struct B1 s1 = { 0 };
printf("s1:%d\n", sizeof(s1));
struct B2 s2 = { 0 };
printf("s2:%d\n", sizeof(s2));
return 0;
}
运行结果↓ s1:8 s2:12
这个时候张三就可能会有疑问了,说结构体B1不应该是5个字节吗,而B2应该是9个字节才对啊。 为什么会是这个样子阿~ 这个实际上就是结构体内存对齐的问题了,让我们来看看这到底是为什么。 从上面我们知道结构体存储跟我们实际想象💭的是不一样的,这里其实是在结构体的内存对齐当中是有结构体自己的规则的。
我们以上述结构体类型B为示例。下面来说说结构体内存对齐的规则。
如图表示↓
所以这里的结构体内存占了12个字节,如果上面这个多去理解下,还是能够很容易理解的||ヽ(* ̄▽ ̄*)ノミ|Ю
当然如果有嵌套情况,像下面代码一样。
#include<stdio.h>
struct B1
{
double pai;
char c;
};
struct B2
{
char c1;
struct B1;
char c2;
};
int main(void)
{
struct B2 s = { 0 };
printf("%d", sizeof(s));
return 0;
}
运行结果↓ 32 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
平台原因→移植原因:不是所有的硬件平台上都能访问任意地址上的任意数据的,某些硬件平台上只能在某些地址处某些特定的数据,不然会抛出硬件异常。 性能原因:数据结构尤其是栈,应该尽可能地在自然边界对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。 总的来说→结构体的内存对齐是拿空间来换取时间的做法。
当然在设置结构体的时候我用成员较小的话存放在一起这样是可以节省内存空间的。 如下代码所示 ↓
#include<stdio.h>
struct B1
{
int name;
char c;
char c1;
};
int main(void)
{
struct B1 s1 = { 0 };
printf("s1:%d\n", sizeof(s1));
return 0;
}
在上面当中提到过vs的默认对其数是⑧,那么我们是否或者怎么样才能够修改这个默认对其数。如下代码所示 ↓
#include<stdio.h>
struct S8
{
char c;//0
//浪费1~3
int name;//4~7
char c1;//8
//浪费9~11
//总共字节大小为12
};
int main(void)
{
printf("S8:%d\n", sizeof(struct S8));
return 0;
}
运行结果↓ S8:12
那么上述代码当中的默认对齐数是8,注-vs默认对齐数都是8。那么我们如何把默认对齐数修改成大小为2❓那么如下所示↓
#pragma pack (2)
在这里我们加上这句代码的话就可以把我们默认对齐数从⑧修改成②了 如下代码所示↓
#include<stdio.h>
#pragma pack (2)
struct S8
{
char c;
int name;
char c1;
};
#pragma pack ()//把修改的给取消掉
int main(void)
{
printf("S8:%d\n", sizeof(struct S8));
return 0;
}
运行结果↓ S8:8 那么在这里我们的程序运行结果就是为8了,那么这是为什么。如下图列表所示↓
那么当我们上面改成默认对齐数是1的话,那所有的都会是1的倍数了。 大小就不存在什么浪费不浪费了。
该函数的同文件是→#include<stddef.h> offsetof()函数声明如下 ↓
offsetof (type,member)
如下代码所示 ↓
#include<stdio.h>
#include<stddef.h>
struct S8
{
char c;//0
//浪费1~3
int name;//4~7
char c1;//8
//浪费9~11
};
int main(void)
{
printf("偏移量→%d\n", offsetof(struct S8,c));
printf("偏移量→%d\n", offsetof(struct S8, name));
printf("偏移量→%d\n", offsetof(struct S8, c1));
return 0;
}
运行结果如下 ↓ 偏移量→0 偏移量→4 偏移量→8
由于结构体的传参部分在上一篇结构体当中说过,这里就不再过多追述。
说完结构体那么再来说说什么是位端的概念,以及位段是什么。 位段和结构体的声明都是类似的,但是有两点不同↓
那么下述代码当中这个结构体B就是一个位段类型。
#include<stdio.h>
struct S
{
int a : 2; //(1)
int b : 5; //(2)
int c : 10;//(3)
int d : 30;//(4)
};
int main(void)
{
printf("%d\n", sizeof(struct S));
return 0;
}
运行结果↓ 8
那么在上述一共有四十七位比特位。在这里我们要直到1个字节 = 8比特位。
struct S
{
int a : 2; //(1)
int b : 5; //(2)
int c : 10;//(3)
int d : 30;//(4)
};
首先看到 int a 开辟4个字节,有32个比特位。那么 abc 加起来一共有17个比特位。 那么我们用 32 🗡 17 = 15,那么这里就还有15个比特位。 在这里15个比特位不够 int b 当中的30个比特位,那么就再开辟4个字节为32个比特位存放 int b 当中的30个比特位。 那么在这里我们到底是用了32个比特位当中,还是先用到15个比特位再用到32个当中的比特位。 这个实际上在C语言当中是不确定的因素,因此为什么说位段是不跨平台的。因为在不同的平台下的实现方法是不一样的,gcc、vs、dev...这些平台的实现都是不同的。
总结→和结构体相比,位段是可以达到同样的效果,具有很好的节省空间,但是它是有跨平台的问题存在的。