C语言结构体

1.结构的定义

结构体可以存储超过一个的单独元素,与数组不同的是,它的每个元素可以有不同的数据类型,其中每个元素都是它的成员,可以通过名字访问。
结构用struct关键字声明,例如:

struct A
{
    char  a;
    int   b[10];    
    float c;
};

现在我们已经成功声明了一种结构体类型,接下来就可以利用它创建一个结构体变量:

    struct A a;

注意这里一定不能丢掉struct, 我们也可以声明的时候直接创建结构体变量:

struct A
{
    char  a;
    int   b[10];    
    float c;
}x,*y,z[3];

上述代码创建了一个x,y,z三个变量,x是一个该类型的结构体变量,y是一个指针变量,他可以指向一个该类型的结构体,z是一个数组,里面包含了3个该类型的结构体。

结构体的成员是通过 . 操作符来访问的,例如上述例子中x.a可以访问结构体x中的成员a,它是一个字符,x.b访问x的成员b,它是一个整形数组,我们可以通过下标访问这个数组中的元素,如x.b[5]访问b中下标为5的元素,x.c访问成员c,它是一个浮点数。

y是一个结构体指针,我们让y元素指向x,就可以用y来访问x的成员了,通过y访问x的成员有两种办法。第一种是通过解引用,用(*y)来替换上面的x,可以达到同样的效果,如(*y).b[5]可以访问x中数组b中下标为5的元素。因为.的优先级比*高,所以这里一定要给*y加上()。第二种方法是使用箭头操作符->,例如y->b[5]可以访问x中的数组b中下标为5的元素。
在声明时,我们也可以省略结构体类型名:

struct
{
    char  a;
    int   b[10];    
    float c;
};

这样的结构体我们只能在声明的时候创建变量,在声明结束之后,我们无法再利用该结构体类型创建变量。

2.结构的初始化

在创建一个结构体变量时,我们可以直接对其初始化,使用赋值运算符和一堆花括号,在花括号内部将每个成员依次写出,用逗号隔开,我们用上面声明的结构类型A创建一个变量来初始化:

    struct A a={'z',{1,2,3,4,5,6,7,8,9,10},1.3f};

这里我把数组b的内容用一对花括号括起来了,这不是必须的,在这里将它去掉并不会对程序运行造成什么影响,但是最好加上。加上花括号有两个好处,一是提高程序的可读性,我们能很容易将数组b中的元素和其他成员区分开,二是我们可以在对数组b后面的成员初始化的条件下对数组b进行不完全初始化,如:

    struct A a={'z',{1,2,3,4,5},1.3f};

这里我们对a中的成员a和成员c进行了初始化,并且对数组b进行了不完全初始化(只初始化了前5个元素),如果我们将数组b中元素外面的花括号去掉:

struct A a={'z',1,2,3,4,5,1.3f};

编译器会报这样一个警告:

warning C4244: “初始化”: 从“float”转换到“int”,可能丢失数据

这是因为编译器将成员c的初始值当成了数组b中的第6个元素,这样程序就不会按照我们预想的那样运行了。

3.结构体的typedef

在声明一个结构类型的时候,我们可以用typedef对其进行重命名,如:

typedef struct A
{
    char  a;
    int   b[10];    
    float c;
}A,*PA;

我们将该结构类型重命名为A,该类型的指针重命名为PA,接下来我们不需要使用struct就可以创建该类型的结构变量和指向带类型的指针了,如:

    A a={'z',{1,2,3,4,5,6,7,8,9,10},1.3f};
    PA pa = &a;

4.结构体的内存存储(内存对齐规则,为什么存在内存对齐?)

在结构体内,编译器会按照成员列表的顺序依次分配内存,但是每个成员都要考虑边界对齐要求,边界对齐也就是每个数据在内存中的起始位置要能够被它的对齐数整除,单独数据类型的对齐数就是这个类型本身的大小,整个结构体的对齐数就是其中最大的对齐数,比方说char类型大小为1字节,对齐数为1,起始位置能被1整除,int大小为4字节,对齐数为4,起始位置要能被4整除,然后结构体的总大小要能够被它的对齐数整除,如:

typedef struct A
{
    char a;
    double b;
    int c;
}A;

这个结构体类型的大小为24字节,对齐数是a、b、c的对齐数中最大的,也就是8,所以结构体起始位置能被8整除,a对齐数是1,在起始位置占了1字节,b对齐数是8,起始位置要能被8整除,所以a到b之间要空上7个字节的位置,c是对齐数为4,直接存储到b的后面,从存储a到c结束共占1+7+8+4=20个字节,结构体大小要能被它的对齐数也就是8整除,所以c后面要空上4个字节,最终整个结构体大小为24字节

我们将这个结构体中b和c的声明顺序调换一下:

typedef struct A
{
    char a;
    int c;
    double b;
}A;

这个结构体的大小就变成了16字节,同上,结构体对齐数是8,起始位置能被8整除,a在起始位置占1字节,c对齐数是4,起始位置要能被4整除,所以a后面要空上3个字节存储c,此时a和c共占1+3+4=8个字节,下个位置刚好能够被8整除,所以b直接存储到c的后面,目前整个结构体大小为1+3+4+8=16个字节,能够被8整除,后面不用再空上任何一个字节,所以这个结构体最终大小为16。

数组的对齐数是其中单个元素的对齐数:

typedef struct A
{
    char a;
    int  b[20];
    double c;
}A;

这个结构类型大小为96,a对齐数是1,数组b的元素的类型是int,所以对齐数是4,c的对齐数是8,所以结构体对齐数是8,起始位置能被8整除,a在起始位置占1字节,b起始位置要能被4整除,所以空上3字节,共占1+3+80=84字节,c起始位置要能被8整除,所以空上4字节,共占1+3+80+4+8=96字节,这个数能够被结构体的对齐数8整除,所以后面无需再空,结构体最终大小为96字节。

结构体的对齐数是它里面最大的那个对齐数:

typedef struct A
{
    char a[10];
    double  b;
}A;
typedef struct B
{
    char c;
    A a;
}B;

A的大小为24字节,对齐数为8,B包含一个char类型数据和一个A类型数据,char对齐数为1,A对齐数为8,所以B的对齐数是8,起始位置能被8整除,c在起始位置占1字节,A的起始位置要能被8整除,空上7字节,共1+7+24=32字节,32能够被8整除,所以B最终大小为32字节。

其实,系统会有默认的最大对齐数,所有的对齐数不得超过这个值,默认的最大对齐数不同机器可能会有所区别,一般是4或者8,以上所有结果都是在默认对齐数为8的机器上得到的,我们可以用#pragma pack(n)来修改这个值,如:

#pragma pack(4)
typedef struct A
{
    char a[10];
    double  b;
}A;
typedef struct B
{
    char c;
    A a;
}B;

一样的代码,将默认最大对齐数改为4后,A的大小变成20,B的大小变成24,首先,A中一个字符数组a和一个double类型数据b,a的对齐数是1,b的对齐数本来是8,但是这个值超过了系统当前的最大对齐数4,所以b最终的对齐数是4,A的对齐数也就是4,A的起始位置能被4整除,数组a在起始位置占10字节,空上2字节,之后b占8字节,所以A最终大小为20字节,B中包含一个字符c和一个结构体A,c对齐数是1,A对齐数是4,所以B的对齐数是4,起始位置能被4整除,c在起始位置占1字节,空上3字节,A占20字节,所以B的大小为24字节。

那么为什么要进行内存对齐呢?
因为内存对齐会提高访问效率。计算机读取数据并不是一个字节一个字节读取的,而是一块一块的读取,假设这个块是4个字节,0~3,4~7…对计算机来说就是一个个块,我们想要访问1里面的一个字符变量,计算机也会读取0~3这整个块,然后把1里面的内容取出来用,加入我们把一个整形变量存储在0~3里面,计算机直接读取0~3这个块,一次就完成了,假如我们把它存储在1~4里,计算机要取出0~3和4~7两个块才能得到完整的数据,效率自然会低一些。

5.结构实现位段,位段大小的计算。位段的数据存储。

位段是结构的一种,位段成员必须生命为int、signed int或unsigned int类型,并在成员的后面加上冒号和一个整数,表示该成员占用多少比特位。

struct BitField
{
    unsigned a : 5;
    unsigned b : 17;
    unsigned c : 7;
    unsigned d : 1;
};

位段的好处是能节省空间,上面这个例子,如果不使用位段的话,b的大小超过了一个短整型,必须要占用4字节,a,c,d各占1字节,再考虑到内存对齐,整个结构体需要8字节,而使用位段的话4字节就够了,当这类结构的数量及其庞大的时候,节省的空间就相当可观了。

6.位段的跨平台问题。

位段的跨平台性很差,注重可移植性的程序应该避免使用位段,原因有以下几点:

  1. int被当做有符号数还是无符号数(所以使用位段时应该尽量将该成员是有符号数还是无符号数写清楚)
  2. 位段中位的最大数目有限制,所以在32位机器上声明的位段可能在16位机器上无法运行
  3. 位段中的成员在内存中是从左往右分配还是从右往左分配
  4. 当第一个位段剩余的位不够存储第二个位段时,第二个位段是存在下一个字节的起始位置还是紧跟在第一个位段的后面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值