C和指针之结构体和联合体

时间:2022-09-05 21:00:42
1、结构体基础知识
  聚合数据类型(aggregate data type)能够同时存储超过一个的单独数据。C语言提供了两种类型的聚合数据结构:数组和结构体。
  数组是相同类型的数据元素的集合,它的每个元素都是通过下标引用或者指针间接访问来选择的;结构也是一些值的集合,这些值称为它的成员(member),结构体的每个成员可以是不同类型的数据,要访问结构体中的数据,是通过成员名来访问的。
  结构变量属于标量类型,所以你可以像对待其他标量类型一样执行相同类型的操作。结构体可以作为传递给函数的参数,也可以作为返回值从函数返回,相同类型的结构体变量之间可以相互赋值。你也可以申明指向结构体的指针,取一个结构体变量的地址,还可以声明结构体数组。
1)声明结构体
typedef struct tagTEST_DATA
{
    int a;
    int *p;
    char b;
    char c[];
}ST_TEST_DATA;
2)结构体成员的访问
  点操作符(.):对结构体成员进行直接访问。点操作符接受两个操作数,左操作数就是结构变量的名字,右操作数就是需要访问的成员的名字。
  箭头操作符(->):对结构体成员进行间接访问。和点操作符一样,箭头操作符接受两个操作数,但是左操作数必须是一个指向结构的指针,右操作数也是需要访问的成员的名字。
 
2、结构体的存储分配
typedef struct tagST_TEST
{
    char a;
    int b;
    char c
}ST_TEST;
结构体内存分配遵循边界对齐原则。
1)普通数据成员对齐原则:第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
2)结构体成员对齐原则:如果一个结构里有某些结构体成员,则该结构体成员要从其内部最大元素大小的整数倍地址开始存储。
3)结构体大小对齐原则:结构体大小也就是sizeof的结果,必须是其内部成员中最大的对齐参数的整数倍,不足的要补齐。
 
以如上结构体ST_TEST为例:
  成员a、c为char型,各占一个字节,b为int型,占4个字节,如果你认为结构体ST_TEST占了6个字节的空间,那么你就大错特错了。因为没有遵循结构体边界对齐原则。下面我们来分析一下。按照边界对齐原则,结构体成员要从内部最大元素大小的整数倍地址开始存储,例子结构体中的成员最大的是int型的b,占4个字节,所以,char a和char c都要占4个字节,第一个字节为数据a、c,后三个为空。所以ST_TEST结构体共占了12个字节的空间。
如果将结构体改为如下形式:
typedef struct tagST_TEST1
{
    int b;
    char a;
    char c;
}ST_TEST1;
  此时,int型的b占4个字节,char a,char c各占一个,但是在其后要补两个空字节(此时的对齐原则按照int型的4字节计算,但是,两个char连在一起,可以算一个整体,共占4个字节),此时结构体ST_TEST1总共占了8个字节的空间,比ST_TEST少了4个字节。
  以后在定义结构体时要考虑边界对齐原则,可以通过改变成员的先后顺序来达到节省存储空间的目的。但是这样做有时有会破坏程序的可维护性和可读性,所以多做编程练习,在存储空间和可读性中找到平衡,提高自己的编程水平。
  sizeof操作符能够得出一个结构体的整体长度,包括因边界对齐原则而跳过的那些字节。
  offsetof宏则能够得出某个成员的实际位置,需要考虑边界对齐因素。offsetof(type, member)(定义于stddef.h)type是结构体的类型,member是需要的成员名,返回值为size_t类型的指定成员开始存储的位置距离结构体开始存储的位置的偏移的字节数。如offsetof(struct ST_TEST, b),返回值为4。
 
3、作为函数参数的结构
  结构体变量是一个标量,它可以用于其他标量可以使用的任何场所。因此把结构体作为一个参数传递给函数是合法的。
  将一个结构体直接作为参数传递给函数是一种及其低效的做法,因为C语言的参数传值调用方式要求把参数的一份拷贝传递给函数,要想把结构体直接传递给函数,就需要把整个结构体都复制到堆栈中,这显然非常浪费存储空间。所以传递一个指向结构的指针给函数,效率要高很多。在函数中要访问结构成员只需要用间接访问的方式就可以了。
 
4、联合体(共用体)
  在结构体中,结构的各成员顺序排列存储,每个成员都有自己独立的存储位置。联合(union)变量的所有成员共享同片存储区/内存。因此联合变量每个时刻里只能保存它的某一个成员的值。
联合变量也可以在定义时直接初始化,但这个初始化只能对第一个成员进行。
联合体的主要特征:
  union中可以定义多个成员,union的大小由最大的成员的大小决定;
  union成员共享同一块大小的内存,一次只能使用其中的一个成员; 
  对union某一个成员赋值,会覆盖其他成员的值(但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节);
  union量的存放顺序是所有成员都从低地址开始存放的。