C语言学习之结构体
在C语言中,我们已经学了好几种类型的数据。比如整型int、char、short等,浮点型double、float等。但是这些都是基本数据类型,而这些数据类型应用在实际编程里显然是不够用的。比如我们没有办法用一旦数据类型来定义一个”人“的属性。因此这里我们需要一个框,将不同的数据类型放在一起,这就是结构体,接下来我们将深入学习结构体的知识。
结构体
结构体的创建与初始化
结构体是一些值的集合,这些值被称之为成员变量。结构的每个成员都是不同类型的变量。
语法结构:
struct name//名字自定义
{member-list;//一个或者多个成员变量
}variable-list//可以省略。是结构体变量列表
举例说明
struct person
{char name[20];int age;float height;
};
int main()
{struct person p1 = {"诗华",18,1.72};return 0;
}
结构体的特殊声明
声明结构体的时候可以不完全声明。
但是匿名结构体的声明其后必须创建变量
struct//匿名结构体类型
{char name[20];int age;float height;
}s1,s2;
匿名结构体类型只能用一次。
但是切记这种方法不行
拓展:数据结构简单科普
链表是一种数据结构。数据结构是描述数据在内存当中的存储结构,对数据的操作就是对数据结构的操作。
大体上数据结构分为以下几种
结构的自引用
在结构体中包含一个类型为该结构本身的成员是否可以呢?
上图报错的原因:结构体内不能包含同类型的自己。
如果想正确表达,需要这样
struct Node
{int data;struct Node *next;
};int main()
{printf("%zd\n",sizeof(struct Node));/*struct person p1 = {"诗华",18,1.72};*/return 0;
}
结果为:
而在上图中,int data为数据域,struct Node * next为指针域。
也可以简化为:
typedef struct Node
{int data;struct Node *next;
}Node;
之后如果使用结构体定义变量可以直接写为
Node n;
但是这么些是绝对错误的
typedef struct
{int data;Node *next;
}Node;
结构体内存对齐
我们学会结构体的基本使用后,那么问题来了,结构体的大小是怎么计算的呢?我们举例分析。
#include<stdio.h>
struct s1
{char c1;//1char c2;//1int n;//4
};
struct s2
{char c1;//1int n;//4char c2;//1
};
struct s3
{double d1;//8int n;//1char c1;//4
};
struct s4
{double d1;//8struct s3 S;//16char c2;//1
};
int main()
{printf("%zd\n",sizeof(struct s1));printf("%zd\n", sizeof(struct s2));printf("%zd\n",sizeof(struct s3));printf("%zd\n",sizeof(struct s4));return 0;
}
结果为:
为什么是这样呢?
结构体成员在存储的时候有内存对齐的现象。
这里介绍一个新概念:offsetof——一个宏,可以计算出一个结构体成员相较于结构体起始位置的偏移量。(其实之前博主的博客预处理那节讲解过:C语言学习之预处理指令-CSDN博客)
内存对齐的规则
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数被的地址处
对齐数=编译器默认的一个对齐数与该成员变量大小的较小值。
VS中默认的值为8
Linux中gcc编译器没有默认对齐数,对齐数就是成员变量自身的大小
3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大值)的整数倍
4.如果嵌套了结构体的情况下,嵌套的结构体成员对齐到自己成员中最大对其数的整数倍,结构体的整体大小就是所有最大对其数(含嵌套结构体中成员的对齐数)的整数倍。
为什么会存在内存对齐?
1.不是所有的硬件平台都能访问任意地址的任意数据;某些硬件平台只能在某些地址处去某些特定类型的数据。
2.数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅仅需要一次访问。
总结来说:结构体的内存对齐是拿空间换取时间的做法
那在设计结构体的时候,我们既要满足对齐,又要节省空间如何做到呢“
让占用空间小的成员尽量集中在一起。
struct A
{char c1;char c2;int n;
};
struct B
{char c1;int n;char c2;
};
int main()
{printf("%zd\n",sizeof(struct A));printf("%zd\n",sizeof(struct B));return 0;
}
修改默认对齐数
#pragma这个预处理指令可以改变编译器默认对齐数量。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#pragma pack(1)//设置结构体默认对齐数为1
struct S
{char c1;int n;char c2;
};
#pragma pack()//恢复默认对齐数int main()
{printf("%d\n",sizeof(struct S));return 0;
}
结构体传参
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4,5} ,1000 };
//结构体传参
void print1(struct S s)
{printf("%d\n",s.num);
}
void print2(struct S *ps)
{printf("%p\n",ps->num);
}
int main()
{print1(s );print2(&s);return 0;
}
上面的print1和print 2哪个更好呢?答案是print2
原因:
函数传参的时候,参数是需要压栈的,会有时间和空间上系统的开销。
如果传递一个结构体对象时,结构体体积过大,参数压栈的系统开销过大,会导致性能下降。
结论:结构体传参的时候,要穿结构体的地址
结构体实现位段
位段
位段的声明和结构必须要类似,有两个不同。
1.位段的成员必须是int,unsigned int或者signed int 。在C99中位段成员类型也可以是其他类型
2.位段的成员名之后有一个冒号和一个数字
#include<stdio.h>
//结构体
struct A
{int a;int b;int c;int d;
};
//位段
struct B
{int _a:2;int _b : 3;int _c : 10;int _d : 5;
};
int main()
{printf("%zd\n", sizeof(struct A));printf("%zd\n", sizeof(struct B));return 0;
}
结构体和位段的大小分别为:
位段的空间是如何开辟的呢?
位段的内存分配
1.位段的成员可以是:int,unsigned int或者signed int或者char类型
2.位段的空间上按照需要以4个字节和1个字节的方式开辟
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植程序的程序避免使用。
//位段
struct B
{int _a:2;int _b : 3;int _c : 10;int _d : 30;
};
int main()
{/*printf("%zd\n", sizeof(struct A));*/printf("%zd\n", sizeof(struct B));return 0;
}
1.int位段被当成有符号还是无符号不确定
2.位段最大位的数目不确定(16位机器最大16,32位机器最大32.写成27的话16 位机器会出问题)
3.位段成员在内存中从左向右分配还是右向左分配是不确定的
4.当一个结构体包含两个位段的时候,第二个位段成员比较大,无法荣达第一个剩余的位时是舍弃剩余位置还是保留不确定
总结:与结构相比,位段可以达到同样的效果,更加节省空间不过会有跨平台的问题。
位段的应用
在网络协议的IP数据报中,用位段可以更节省空间的达到想要的效果,对网络畅通很有帮助。
位段使用的注意事项
位段的几个成员共同用一个字节,这样有i协成员起始位置并不是某个字节起始位置,那么这些位置只是没有地址的。内存中每个字节分配一个地址,一个字节内部的比特位是没有地址的。
所以不能对位段的成员使用&*操作符,这样就不能直接使用scanf直接给位段的成员输入值。只能先输入放在一个变量里然后赋值给位段的成员
struct B
{int _a:2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct B s = {0};scanf("%d",&sa.b);//这是错误的//正确示范int b = 0;scanf("%d",b);sa._b = b;return 0;
}
感谢看到这里的读者朋友,求一个赞谢谢。