结构体【重要】
继续进行C语言学习之旅,现在已经快进入了C的核心了,所以需更加细心地学习,言归正传,开始学习:
什么是结构体呢?
定义结构体类型:
注意:结构体最后的分号不能省略
这时编译就出错了:
结构体变量:
当我们定义好了一个结构体类型之后,就可以使用它,类似于基本类型一样,但是:会有一些差别。
①使用方式一【用得较少】:
注意:struct关键字不能省略,代表该变量是一种结构体类型:
这时编译就出错了:
②使用方式二【用得较少】:
我们可以将结构体声明在函数体内,声明之时就给出变量:
③使用方式三【通常用这种方式】:
从使用方式一中可以看出,使用起来不是那么方便,有没有解决办法呢?答案当然有,也就是第三种方式,用typedef为现有类型创建一个新的名字,或称为类型别名
另外,我们可以简化这种方式,在定义结构体类型之后,就给出别名,还不用单独多出一行来处理【在编程中能用少的代码来实现同样的功能,是我一直追求的!!】
编译也是成功的:
提示:对于用tydef给一个类型定义别名,一般命名是以_t结尾:
结构体中可以嵌套结构体:
#include <stdio.h> struct point { int x; int y; }; typedef struct point point_t; typedef struct circle { point_t center;//嵌套结构体 int radius; } circle_t; int main(void) { return 0; }
结构体变量初始化:
①第一种初始化:
#include <stdio.h> struct point { int x; int y; }; typedef struct point point_t; typedef struct circle { point_t center; int radius; } circle_t; int main(void) { circle_t c1 = {{1, 2}, 3}; printf("(%d,%d) %d\n", c1.center.x, c1.center.y, c1.radius); return 0; }
说明:访问结构体里面的元素,采用“.”:
②第二种初始化:
#include <stdio.h> struct point { int x; int y; }; typedef struct point point_t; typedef struct circle { point_t center; int radius; } circle_t; int main(void) { circle_t c1 = {1, 2, 3}; printf("(%d,%d) %d\n", c1.center.x, c1.center.y, c1.radius); return 0; }
③第三种初始化:
#include <stdio.h> struct point { int x; int y; }; typedef struct point point_t; typedef struct circle { point_t center; int radius; } circle_t; int main(void) { point_t p1 = {1, 2}; circle_t c1 = {p1, 3}; printf("(%d,%d) %d\n", c1.center.x, c1.center.y, c1.radius); return 0; }
我们知道数组是不能整体赋值的,这个在http://www.cnblogs.com/webor2006/p/3452229.html有说明,下面再回忆一下:
要真的想把一个数组的值赋给另外一个数组,只能采用遍历的方式:
而对于字符数组,也是无法直接整体赋值的:
编译,这时会出错:
如果要从一个字符数组的内容拷贝到另外一个数组,则需要利用库函数来实现:
编译,运行:
但是结构体中的数组是可以相互赋值的:
#include <stdio.h> typedef struct test { int a[3]; int b; char c[10]; } test_t; int main(void) { test_t t = {{4, 5, 6}, 100, "hello"}; test_t t2; t2 = t;//结构体整体赋值 printf("%d %d %d %d %s\n", t2.a[0], t2.a[1], t2.a[2], t2.b, t2.c); return 0; }
结构体数组:
由于结构体也是一种数据类型,所以很容易地可以想到它可以有数组:
结构体内存对齐【这个概念重要】:
看如下图:
从结果来看,并非按我们推算的来计算结构体的大小,这是因为结构体里面的数据是按对齐的方式来存放的,把数据地址对齐到2、4、8(这里称为对齐模数,不同数据类型的对齐模数是不一样的)的整个倍,这就是所谓的“结构体内存对齐”
可能有些抽象,至于怎么对齐,下面会详细介绍,这阶段先初步解释上程序中16的字节数是怎么得来的。
下面通过一个图来说明意义:
先讨论对齐的方式存放:
而如果不以对齐的方式存放呢?
可见,这种方式的效率远不如对齐的,所以对齐的重要意义为:
这是关键点,先说下规则:
①:第一个数据成员对齐至偏移值为0的地方。
②:接下来的数据成员对齐至对齐模数的整数倍,每个数据成员都有自己的对齐模数。
③:对齐模数取数据成员的大小与param pack(n)(对齐值,gcc默认为4,微软的vc默认是8)的较小值。
④:整个结构体的大小对齐至最大对齐模数的整数倍。
下面通过设置param pack(n)来看下,结构体的大小是否发生了变化:
运行结果:
这时,我们来算一下,这个值是怎么得来的:
如果我们将对齐值改为1呢?
运行结果:
通过上面的分析就可以很清楚地推出为啥输出是10个字节的大小了
如果将对齐值改为8呢?
运行结果:
这是为什么呢?原因是因为gcc中的对齐值只能是1、2、4,不支持8,只有在vc上才会支持。
结构体位字段:
联合体:
“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。【来自于百度百科】
下面用一个实例来体会一下联合体:验证一下机器是小端字节序还是大端字节序?
先介绍一下什么是“小端字节序”和“大端字节序”
接着用联合体来编程验证:
好了,结构体这一章是非常重要的,涉及到的细节也是挺多的,需好好消化。