C语言---自定义类型(上)(结构体类型)
结构体
结构体的定义与声明
结构体其实和数组一样,都是一些值的集合,只不过数组是一系类相同类型的值,而结构体里边的成员可以是不同的数据类型。
关于它的声明,所用到的关键字是struct。
声明的语法如下:
struct 结构体名
{
成员变量列表;
}变量名;
我先举个例子,就比如一个学生有名字、年龄、成绩等信息。那么此时就可以用结构体来描述它。
struct Stu
{char name[20];//名字int age;double score;
};
结构体变量的定义和初始化及其访问
定义
在刚才创建完成了结构体之后,我们就可以创建结构体的变量了。
一共有三种方式:下面以代码加注释的方式给出。
struct Stu
{char name[20];//名字int age;double score;
}stu1;//在定义结构体的时候就创建的全局变量struct Stu stu2;//创建的全局结构体变量int main()
{struct Stu stu3;//创建的局部结构体变量return 0;
}
初始化
刚才上边的代码已经创建了几个Stu类型的结构体变量,现在我给结构体变量stu3初始化,代码如下
//
stu3 = {"xxc",18,99.0};//注意,我们给它初始化的时候需要按照顺序,如果你想要按照自己的顺序去初始化,那就要用到结构体成员访问操作符 ( . )
stu3 = {.age = 20 , .name = "zhangsan" , .score = 98.5};
访问结构体变量
访问结构体的成员变量有两种方式,第一种比较的直接,我们用结构体变量.成员名的方式去访问结构体里的成员。第二种方式就要用到指针了,通过结构体指针->成员名的方式。代码如下:
#include<stdio.h>struct Stu
{char name[20];int age;float score;
};int main()
{//第一种方式打印struct Stu stu = { "zhangsan",21,98.4f };printf("%d %f\n", stu.age, stu.score);//第二种方式打印,先得到结构体的地址,再用指针的方式去打印struct Stu* p = &stu;printf("%d %f\n", p->age, p->score);return 0;
}
嵌套结构体与匿名结构体类型
嵌套结构体:
嵌套结构体,顾名思义就是结构体充当了结构体的成员变量,请看下边的代码(同时,我会将访问其成员变量也附加进去):
//注意:以下的代码只是演示,没有什么实际的含义
#include<stdio.h>
struct id
{char ID[25];int x;
};struct Stu
{char name[20];struct id identity;
};int main()
{struct Stu stu1 = { "zhangsan",{"123456",2} };return 0;
}
匿名结构体:
匿名结构体就是结构体在创建的时候没给它名字,如下
struct
{
char name[20];
int age;
};这样的话就不能创建结构体的变量了,只能用三种创建方法的第一种来创建,就是在定义的时候直接创建。并且你会发现当你取出他的地址赋值给一个匿名结构体类型的指针变量的时候是会失败的。因为C标准认为它们是两种不同的类型。所以往往匿名结构体类型只能用一次。
结构体的内存对齐
每一种数据只要存在在内存当中就肯定有大小,结构体也不例外,接下来所要讨论的就是结构体的内存大小,C语言给出了一套计算它大小的规则,我们称之为结构体的内存对齐。
对齐规则:
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
3.结构体的总大小为最大对齐数的整数倍
接下来我们先来解释一下几个名词的含义:
偏移量:我们可以借助offsetof宏来帮助我们理解,offsetof可以计算出结构体的成员相较于结构体起始位置的偏移量。offsetof有两个参数,第一个参数是结构体变量名字,第二个参数是结构体内部的成员名。
由上图我们可以知道每个结构体成员的偏移量是多少,如果你还没有概念,请不要着急,我会把所有名词解释完之后再次回过头来全部解释一下。
对齐数: 在我们常见的编译器vs里边,默认的对齐数是8,但在计算结构体大小的时候,我们需要将默认的对齐数与该结构体成员变量的大小做比较,取较小的那个作为我们计算大小时候的依据。
最大对齐数:结构体里边的每一个成员变量都有一个对齐数,它们之中最大的那个就叫做最大对齐数。
了解了以上的规则与概念之后,我们再将上边截图里边的结构体拿出来,来根据规则计算一下它的大小。
结构体嵌套结构体的内存对齐
当我们想要计算的结构体里边有结构体嵌套的时候,它的大小又该如何计算呢?
这时候,在上边规则的三条规则之下还有一条规则。
4.当存在嵌套结构体的时候,该结构体成员对齐到自己内部成员的最大对齐数的整数倍处,结构体的大小就是全部成员(包括嵌套结构体的成员)的最大对齐数的整数倍。
下边来看一个例子:
修改默认对齐数:
我们在上边已经提到过了,在vs里边的默认对齐数为8,当我们觉得这个默认对齐数不合适的时候,我们就可以用预处理指令#pragma来修改默认的对齐数。
#include<stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct test
{char c1;char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认对齐数为8的状态。
int main()
{struct test test2;printf("%d\n", sizeof(test2));return 0;
}
结构体传参
函数的传参分为两种,一种是传值,一种是传址。下边的代码将结构体的两种传参方式一并演示。
#include<stdio.h>
struct s1
{char a;int b;char c;
};void test1(struct s1 s)
{printf("test1的打印:%d\n", s.b);
}void test2(struct s1* ps)
{printf("test2的打印:%d\n", ps->b);
}int main()
{struct s1 sss;sss.b = 10;test1(sss);test2(&sss);return 0;
}
这里再补充一个点,利用地址传参可以很好的提升程序的运行效率,因为如果是传值调用的话,程序会在内存里又开辟一块空间来临时拷贝实参。
结构体实现位段
首先明确位段在定义时候的两个注意点
1.位段的成员必须是int,unsigned int,signed int,char,在C99中,位段成员的类型还可以是其他类型
2. 位段的成员名后边有一个冒号和一个数字。
如下就是一个基本的位段的定义
struct s
{int a : 2;int b : 4;int c : 6;
};
位段的内存分配:
在定义位段的时候,位段的每一个成员的冒号后边的数字代表的是比特位的意思,也就是在定义位段的成员变量的时候,已经把它所占的空间大小也限定住了。
位段的空间是按照4个字节或者1个字节的大小来开辟空间的。还有一个值得注意的点就是位段不具有移植性,当需要跨平台使用的不推荐使用位段。
接下来用一个例子来说明位段的内存分配:
由以上的例子可见,位段的作用本质上还是在节约内存空间的消耗。