结构体:
将多个基本类型聚合在一起(也包括指针和数组),形成一个新的自定义类型,这就是结构体。
//一个描述飞机航班的结构体
struct flightType
{
char flightNum[7];
int altitude;
int longitude;
int latitude;
int heading;
double airspeed;
};
struct flightType plane; //声明一个flightType类型的变量,名称为plane
结构体变量的空间分配并没有什么特殊之处,都是局部变量分配在栈空间中,全局变量分配在全局数据空间中,但是结构体内部成员和函数的变量在栈空间的分布有所区别。
如下:
int x;
struct flightType plane;
int y;
在栈中的内存布局图如下
发现有何区别了吗?
是的,函数的局部变量的偏移量0到负值,地址从高往低,而结构体内部的成员地址偏移与之相反,是从低到高的。
typedef:
typedef提供了一种为已有数据重新命名的机制。typedef即可以重命名基本类型,又可以重命名结构体
typedef int Color; //Color做为int的一个别名
typedef struct flightType Flight; //将struct flightType命名为Flight
Flight plane; //等价于struct flightType plane
结构体数组:
Flight planes[100]; //100架飞机的数组
Flight *planePtr;
planePtr = plane; //将指针指向数组的第一个元素
int longitude = (*planePtr).longitude;
int longitude1 = planePtr->longitude; //这两句话是等价的
表达式"->"等价于间接引用符"*",但是"*"可以用于任何类型的指针,而"->"只能用于结构体中成员的引用。
动态内存分配:
C程序中,内存对象在内存中分配地方只有三种可能:栈(局部变量)、全局数据区(全局变量)、堆(动态数据对象)。
在实际情况中,我们无法事先就确定好数组的具体长度,这样就导致我们无法通过直接声明一个具体长度数组(分配太长造成内存空间浪费,分配太少造成内存不够,引发程序错乱甚至崩溃),动态内存分配应运而生。
动态内存分配就是在系统中,有个叫做“内存分配器”的程序,管理一个叫做“堆”的内存空间,程序通过内存分配器申请一段大小确定的连续内存,如果堆中空间足够,则分配器返回指向该地址的指针(起始地址)。堆中已分配的内存空间将永久保留直到程序主动释放。“内存回收器”接收程序的释放请求,将空间归还给堆,供下次申请使用。
Flight *planes; //声明一个Flight指针
int planeCount;
scanf("%d", &planeCount);
planes = malloc(sizeof(Flight) * planeCount); //申请一段连续的内存块
free(planes); //释放申请的指向planes的内存块
通过调用malloc申请空间,参数是内存空间大小,sizeof的作用是返回其参数(一个内存对象或者类型)占用的内存空间大小(目的是计算工作交给编译器完成,不用程序员自己手动计算)。
通过free释放掉malloc申请到的空间块,参数是一个指向这段内存块的指针。
注意,通过malloc申请了内存块,使用完毕后一定记得通过free来释放掉占用的内存区域,不然就造成内存泄露了,程序离开作用区域后指针已经销毁,这样就没有指针指向它了,但是这段空间还始终被占用着无法进行再次分配。
链表:
另一个非常重要的数据结构就是链表,在数组中,一个元素和其下一个元素在内存空间中是连续的,链表每个元素也有下一个,但他们的内存地址并不一定连续。
链表也是有头有尾,链表的头节点(head)由一个头指针指向它,尾节点(tail)指针指向NULL。
根据这个特性,就可以得出一些对链表操作的结论。
链表非常适合于插入和删除(只需改变相关的节点,而不用移动其他节点),但无法做到像数组一样的访问“随机性”,链表的每次访问必须从头部开始。