1.结构体声明和定义,初始化
- 首先对结构体的定义是什么? 结构体用来描述一个复杂对象,里面可以包含多个属性(类型) char,int,float,double,short,long 等等
- 结构体是一些值的集合,是成员变量,可以是很多种类型 标量 数组 指针,甚至可以是结构体
- 假如是一个人,人是一个复杂对象,单单用一个类型很难描述,所有有了结构体,让我们自己来定义类型
1.2结构体的声明和定义
- 这个是基本结构解释
struct tag //第一个参数是表示结构体,第二个结构体的名字
{
member-list;//结构体成员,一个或者多个
}variable-list;//创建结构体同时声明了一个变量,是全局变量
2. 那么如何创建一个结构体的类型?下面创建的就是一个结构体类型,描述着各种属性
struct student
{
char name[20];//名字
int age;//年龄
int high;//身高
float weight;//体重
};
3.竟然结构体类型创建好了,接下来就是结构体初始化,还有声明变量
- s1 和 s2 是在创建结构体的同时一同创建的全局变量 s2 是正好初始化了值
- s3 和 s1 是一个意思 都是全局变量
- LocalA 是结构体的初始化,按照顺序的,是局部变量的初始化
- 还有LocalB 另外一种,不用按照顺序初始化。不过要使用 .age等操作才可以访问到结构里的成员
struct student
{
char name[20];//名字
int age;//年龄
int high;//身高
float weight;//体重
}s1 s2 = { {"yyy"}, 18 ,170 ,75.5f};//全局变量初始化
struct student s3;//这里和s1创建的都是全局变量
int main()
{
struct student LocalA = { {"ddd"}, 18 ,170 ,75.5f};
struct student LocalB = { .age = 19,.high = 177,.weight = 68.8f, .name = "jiangwen" };//局部结构体变量
//68.8 确实是编译器默认是double类型的值,假如赋值给float的,68.8f才是正确的
printf("%s %d %d %f", LocalA.name, LocalA.age, LocalA.high, LocalA.weight);
LocalA.age = 25;
return 0;
}
1.3结构体的访问
- 如何获取到访问到?,并且对其进行修改和输出
- LocalA.age = 25;像这样的 " . "操作符也可以放到结构体并且对其修改
-
就是上面的printf 函数输出,注意哈:对于字符串的输出仅仅用字符的首地址就够了,至于首元素的概念以及理解。可以去看看我主页的博客
-
对于前面的结构体,不做过多的做解释重点还是后面内容
1.4结构体的间接访问
struct student
{
char name[20];//名字
int age;//年龄
int high;//身高
float weight;//体重
}sd = {.name = "wer"};//创建变量全局变量的同时进行赋值
{
struct student* p1= &sd;
printf("%s", p1->name);
return 0;
}
-
结构体的地址,通过 “->”,并且用(“->”)间接引用符号对它进行访问。然后输出 wer
2.匿名结构体和结构体的自引用
- 匿名结构体,就是省去了结构体的名字,没有了名字就意味着只能用一次
- p1不用初始化也行,用 p1.a = 12;这样 赋值也可以
-
struct { int a; int b; }p1 = {14, 145};//这样用 int main() { printf("%d %d", p1.a, p1.b); return 0; }
-
你看这个结构体变量都没有都不能初始化
2.1结构体自引用
//第一部分
typedef struct node
{
int a;
int b;
}Node;//对这个结构体类型,重新定义类型,这个结构体还没有给值呢
//第二部分
struct Node //下面的两步操作等价与上面的操作
{
int a;
int b;
};
//对这个结构体类型重新定义类型
typedef struct node ber;
int main()
{
Node str = { 12,10 };
printf("%d %d", str.a, str.b);
return 0;
}
-
typedef 对这个类型重新命名,意思就是说重命名的这个node有了这个结构体的这个类型
-
使用的方法就是main函数这样
-
那么啥是结构体自引用?就像是链表一样全部给你链接起来通过这个地址。
-
为什么要链起来在结构体里面再创建一个结构体不好吗?,因为用sizeof的时候不好判断大小,假如嵌套了很多结构体怎么办?
-
至于怎么使用我也不太清楚,鄙人知识有限,等后面学数据结构我再来解释,这里先给个伏笔
struct node
{
int a;
struct node* be//自己包含了自己的相关信息
};
3.结构体内存对齐
-
- 首先把第一个数放到起始位位置为0的地址处
- 然后去比对齐数,取小的对齐数 —>接下来就是放到对齐数的倍数处
- 结构体总大小就是,成员最大对齐数的整数倍
- 假如嵌套了结构体,嵌套结构体最大对齐数和默认对齐数比,然后放(放的是嵌套结构体)在对齐数的倍数上
- 结构体嵌套结构体的大小是所有最大对齐数的整数倍,当然也包括嵌套结构体里的最大对齐数
- 下面是例子,s2,左边是嵌套了结构体的情况,s1是结构体结算过程
3.1为什么有内存对齐
- 平台原因(移植原因):有些硬件平台不能随便访问地址上的数据,有些硬件平台只能在特定的地方访问特定的数据,否则会有硬件异常
- 性能原因:在访问数据时,如果没有内存对齐,假如是32位他可以一次访问4个字节,假如前面还放了一个char类型的,访问后面是就会访问不全,导致还要访问一次,举个例子
这样做其实就是拿空间换时间的做法
3.2修改默认对齐数
- #pragma 这个预处理指令,可以用来修改默认对齐数
- 没有修改前的总大小12,修改了默认对齐数,之后总大小6
- #prama pack(1),意思是把默认对齐修改为1 #prama pack()关闭修改
-
#pragma pack(1) struct S1 { char c1; int i; char c2; }s1; #pragma pack() int main() { printf("%zd", sizeof(s1)); return 0; }
4.结构体传参
- 这里先介意不用先看代码,主要想表达的意思是,当函数调用结构体的时候,是结构体拷贝过去更好呢? ,还是结构体传地址更好呢?
- 个人解释:可以看到,结构体里面有个int数组,假如是传值调用也就是拷贝过去,传递数组的过程中由于数组太大了,就会导致及浪费时间也浪费空间
- 不像地址,地址大小无非就是4/8个字节大小,往往这样会更加节约空间和时间
- 还有一种情况就是假如,传过去的值被修改怎么办,不用慌 就用个const修饰一下嘛
- 偏专业一点的解释:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
- 传参的时候结构体过大,就会导致压榨开销大,导致性能下降
- 所以 传地址更好,地址只有4/8个字节
-
struct str { int a[1000]; char* name; int height; }; struct str pe = { {1,2,3,4,5},"yyw",170}; //结构体传参 void print1(struct str pe) { for (int i = 0; i < 5; i++) { printf("%d ", pe.a[i]); } printf("%s\n", pe.name); printf("%d\n", pe.height); } //结构体传地址 void print2(const struct str *p)//const在 * 左边修饰指针指向的值不可以被更改 { for (int i = 0; i < 5; i++) { printf("%d ", p->a[i]); } printf("%s\n", p->name); printf("%d\n", p->height); } int main() { print1(pe); print2(&pe); return 0; }
5.结构体实现位段(位域)
1. 什么是位段
- 位段的成员必须是int unsigned int 或者 signed int ,C99中位段的成员的类型也可以是其他的
- 尾段成员名后面有一个冒号和一个数字 ,_a : 7;表示开辟7个bit位节空间
- 下面例子大小是8,位段:假设int类型的,先给你4个字节用,用完再申请4个字节的空间 ,下面计算的总大小是47个字节,因此需要两个int类型的大小
-
struct A { int _a : 7; int _b : 3; int _c : 15; int _d : 22; };
2. 位段的内存分配
- 位段成员 int,unsigned int ,signed int 或者是 char 等类型
- 位段是需要以4个字节(int)或者1个字节(char)的方式来开辟
- 位段的不确定因素太多,不适合跨平台,要避免使用
struct a
{
char a : 5;
char b : 3;
char c : 7;
char d : 1;
};
int main()
{
struct a Wd = { 0 };
Wd.a = 31;
Wd.b = 7;
Wd.c = 2;
Wd.d = 1;
printf("%zd\n", sizeof(struct a));
return 0;
}
- 先对代码进行解释:a 开辟的空间是 5个bit位,下面b c d开辟方式都是一样的
- 竟然只开辟这么些空间,那就必定会导致溢出,怎么溢出的呢,来看图
- 因为内存是怎么开辟的具体没有定义,在vs中是以从后向前的开辟的
- 橙色是a开辟的空间,红色是b开辟的,那黄色和粉色就不用说了吧
- 当给a赋值的时候,就发现31的二进制是11111 巧了刚好能存下来,但是到b这里就不行了b的二进制是1000,可是我只给b开辟了三个bit位的空间啊,这个时候就导致了溢出,因为存不下了,后面字节分析吧,粉色是c。
3. 位段的跨平台问题
- int 位段被当成有符号数还是无符号数,到底怎么样不知道
- 位段中的最大位的的数目无法确定,假如在16位机器上可能会出问题的,因为16位机器上int 是2个字节,32位和64位是int长度是64个字节
- 位段在内存里面分配空间时,不知道是从左向右还是从右向左分配,标准没有定义
- 当⼀个结构包含两个位段,第⼆个位段成员比较大,没办法容纳第一个位段的剩余位时,舍弃还是保留呢?不确定
- 总结:跟结构相比,位段也可以达到同样的效果,还可以节省空间,但是有跨平台问题
4. 位段的应用:IP数据报,严格划分了空间,对网络的通常有帮助
5. 位段使用的注意事项
- 不能对位段成员取地址&谁都不可以,因为有可能位段成员共用一块空间,共用一块地址
- 位段的成员尽量是同一种类型
- 可以先把值给变量,然后再赋值给位段,不能直接取位段的成员
加油!!!