[C/C++基础–笔试突击] 3.结构体、共用体、枚举

概述:

结构体和数组主要有两点不同,首先结构体可以在一个结构中声明不同的数据类型,其次相同结构的结构体变脸是可以相互赋值的。

共用体(联合体)和结构体都是由多个不同的数据类型成员组成,但在任何同一时刻,共用体值存放了一个被选中的成员。而结构体的所有成员都存在。

C++的枚举(enum)工具提供了另外一种可以替代const来创建符号常量的方式,枚举表是枚举常量的集合。

3.1 结构体struct

结构体类型变量的定义一般形式为:

struct 结构体类型名{
   类型1 成员名1;
   类型2 成员名2;
   ...
   类型n 成员名n;   
};

定义结构体类型。只说明该类型的组成情况,并没有分配内存空间。只有当定义属于结构体类型的变量时,系统才会分配空间给该变量。

有以下几点需要注意:

1)结构体类型定义中不允许对结构体本身进行递归定义。但可以使用指针指向本类型,以下定义的最常用的:

struct person{
   类型1 成员名1;
   类型2 成员名2;
   ...
   类型n 成员名n;   
   struct person *per; // 指向本类型的指针
};

2)结构体是可以嵌套的,即结构体定义中可以包含另外的结构体。

3)结构体变量可以再定义时进行初始化赋值(按照声明顺序,后面未赋初值的数值型和字符型,系统自动赋零):

struct person{
   char name[20];
   char sex;
}boy1 = {"zhangsan", 'M'};

结构体中的位字段

有些信息在存储时,并不需要占用一个完整的字节,而只需要占几个或一个二进制位。为了节省存储空间,C语言提供了一种数据结构,称为位域位段

C与C++允许制定占用特定位数的结构成员。字段的类型为整型或枚举,接下来为冒号,冒号后面是一个数字,它指定了使用的位数,且可以使用没有名称的字段来提供间距。每个成员都被成为位字段。

赋值的时候不能超过位域的允许范围,超过时,仅将等号右侧值得低位赋值给位域

3.2 共用体

定义形式为:

union 共用体名 {
  数据类型  成员名;
  数据类型  成员名;
  ...
  数据类型  成员名;    
}变量名;

共用体的用途之一是当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。

注:结构体占用内存可能超过各成员内存量总和;共用体占用内存为各成员中占用最大者内存

共用体union的存放顺序是所有成员都从低地址开始存放(结构体也是),该特性经常与小端存储格式、大端存储格式一起考察。

端存储格式(Big-endian):数据的高字节存储在低地址中,而低字节则存放在高地址中;

端存储格式(Little-endian):数据的低字节存储在低地址中,而高字节则存放在高地址中。

例如:32bit的数0x12345678 假设从地址0x4000开始存放

Little-endian:

内存地址 0x4000 0x4000 0x4001 0x4002
存放内容 0x78 0x56 0x34 0x12

Big-endian:

内存地址 0x4000 0x4000 0x4001 0x4002
存放内容 0x12 0x34 0x56 0x78

区别是字中的字节的存储顺序不同,而字与字之间的存储顺序是相同的。

我们常用的X86结构式小端模式,而Sun的SPARC采用大端模式。

如下面的例子(小端系统):

union Student {
    int i;
    unsigned char ch[2];
};

int main() {
    Student student;
    student.i = 0x1420;
    printf("%d  %d", student.ch[0], student.ch[1]);
    return 0;
}

输出的结果为:32 20。

小端系统中,十六进制20存放在低地址处,十六进制14存放在高地址处。由于union成员从低地址开始存放,故ch[0]中的内容是十六进制20,即十进制32;ch[1]中的内容是十六进制14,即十进制20。

下一个例子,假设在一个32位环境,CPU为Little-edian模式,所有参数用栈传递,则下面的输出结果是:

int main(){
    long long a = 1, b = 2, c = 3;
    printf("%d  %d  %d\n", a, b, c);
    return 0;
}

解答:1 0 2。首先long long是8个字节,int是4个字节,printf函数式最后一个元素先入栈,即c先入,然后b、a,此时栈的情况为:

0x01000000 0x00000000 0x02000000 0x00000000 0x03000000 0x00000000

左侧为栈顶,右侧为栈底,因为是Litte-edian,故低字节为字地址。输出时,先输出栈顶,此时输出格式为%d,仅输出4个字节,也就是1、0、2。

3.3 枚举

定义的基本格式为:

enum 枚举类型名{枚举常量1[=整形常量], 枚举常量2[=整形常量], ...} [变量名列表]

注:若不赋初值,则编译器会为每一个枚举常量赋一个不同的整型值(当枚举表中某个常量赋值后,其后的成员则按次序一次加1确定其值)。

enum color {red, orange, yellow, green, blue};

如上例子中,red~blue分别为0~4。

3.4 sizeof运算符

笔试中常考的...

sizeof是一个单目运算符,并不是函数。以字节形式给出了其操作数的存储大小。

需要牢记的是,sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,会忽略其括号内的各种运算,如sizeof(a++),其中的"++"不执行。

3.4.1 sizeof的使用方法

1)用于变量

2)用于数据类型

实际上,sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值起始都是一致的。

其中,C99规定,函数、不能确定类型的表达式以及位域成员不能被计算sizeof值:

1)int foo() {return 1;}
   sizeof(foo); // 错误,函数名称不能被计算
2)void foo2(){ }
   sizeof(foo2()); //错误, 返回类型为void,不能确定类型
3)int foo3(){return 1;}
   sizeof(foo3());  // 正确 返回sizeof(int)的大小,但是不会调用函数
4)struct S{
     unsigned int f1 : 1;
   unsigned int f2 : 5;  
  };
   sizeof(S.f1); // 错误,位域成员不能计算sizeof值

3.4.2 sizeof的结果

一般的在32或64位编译环境中:

sizeof(char) : 1

sizeof(short) : 2

sizeof(int) : 4

sizeof(long) : 4

sizeof(float) : 4

sizeof(double) : 8

sizeof(*p) : 4 (64位为8)

3.4.3 struct的空间计算

struct的空间计算较为复杂,总体上遵循两个原则:

1)整体空间是占用空间最大的成员(的类型)所占字节数的整数倍(Linux+gcc环境例外,当最大类型超过4的时候,也只需要是4的倍数即可)。

2)数据对齐原则----内存按结构体成员的先后顺序排列,当排到该成员变量时,其前面已拜访的空间大小必须是该成员类型大小的整数倍,如果不够则补齐。依次向后类推(Linux仍例外)。

第一个例子如下(Win32环境):

struct s1 {
    char a;
    double b;
    int c;
    char d;
};

struct s2 {
    char a;
    char b;;
    int c;
    double d;
};
cout<<sizeof(s1)<<endl // 语句1
cout<<sizeof(s2)<<endl // 语句2

语句1输出24,语句2输出16。因为语句1在第二个double的时候就要对齐,所以需要三个double的长度,语句2在第4个double才需要对齐,只需要两个double的长度。

1. 含结构体的结构体的空间计算

struct s3 {
    char c;
    int i;
};

struct s4 {
    char c1;
    s3 s;
    char c2;
};
cout<<sizeof(s3)<<endl; // 语句1
cout<<sizeof(s4)<<endl; // 语句2

语句1输出8,语句2输出16.

当结构体中含有结构体时,例如本例子中,成s3为子结构体,s4为父结构体,上面的两个原则应对应的改为:

1)整体空间是子结构体与父结构体中占用空间最大的成员(的类型)所占字节数的整数倍(Linux下特例)。

2)数据对齐原则----父结构体内存按结构体成员的先后顺序排列,当排到子结构体成员时,其前面摆放好的空间大小必须是该子结构体成员中最大类型大小的整数倍,如果不够则对齐,依次向后类推(Linux下特例)。

2. 含数组的结构体的空间计算

在结构体中,数组是按照单个变量一个一个进行摆放,而不是视为整体。

struct s1 {
    char a[8];
    int b;
};

sizeof(s1)的结果为12,可知组数中元素为单个摆放的,如果将a和b的顺序调换,则结果仍为12。

3. 含位域结构体的空间计算

位域的主要作用是为了压错存储,所以不同的编译会有不同的压缩方式来节省空间。

由于位域成员不能单独取sizeof值,但是可以讨论含有位域的结构体的sizeof,其规则大概如下。

1)如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof的大小,则后面的字段将紧邻前面一个字段存储,直到不能容纳位置。

2)如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof的大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍。

3)如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++与gcc采取压缩方式。

4)如果位域字段之间穿插着非位域字段,则不进行压缩。

5)整个结构体的总大小为最宽基本类型成员大小的整数倍。

看一下下面的例子来理解的更深刻:

struct a {
    int f1 : 3;
    char b;
    char c;
};

a答案为:4 f1只占用了3位,不足一个字节,后面的可以继续放置。

struct b {
    char f1 : 3;
    char f2 : 4;
    char f3 : 5;
};

b答案为:2 位域类型为char,第一个字节仅能容下f1和f2(8位为一个字节),所以f2被压缩到第一个字节中。而f3只能从下一个字节开始。

struct c {
    char f1 : 3;
    short f2 : 4;
    char f3 : 5;
};

c答案为:6或2 相邻的位域字段类型不同,规则3,VC6的结果为6,Dev-C++/gcc压缩,结果为2。

struct d {
    char f1 : 3;
    char f2;
    char f3 : 5;
};

d答案为:3 由于非位域字段在其中,规则4,不会压缩。

4. 使用"#pragma pack"时结构体空间的计算

一般可以通过下面的方法来改变默认的对齐条件:

使用伪指令#pragma pack(n),编译器将按照n个字节对齐;

使用伪指令#pragma pack(),取消自定义字节对齐方式。

通常的用法:

pragma pack(n),n为字节对齐数,取值1、2、4、8、16等,默认是8,如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应以此值为准,即结构体成员的偏移量应该取二者的最小值,同时结构体的大小也应是所有偏移中的最大值的整数倍即可。

#pragma pack(push) // 将当前pack设置压栈保存
#pragma pack(2) // 必须在结构体定义之前使用

struct s1 {
    char c;
    int i ;
};

struct s2 {
    char c2;
    s1 s;
    char c3;
};

#pragma pack(pop) // 恢复先前的pack设置

上面的例子中,计算sizeof(s1)时候,c占一个字节,然后min(2, sizeof(i)) 的值为2,所以s1的偏移量为2,c占一位,后面留空,记上sizeof(i)为6,能被2整除,所以sizeof(s1)为6。

对于sizeof(s2),s的偏移量为2,c3的偏移量为1,c2占一个字节,之后的一个字节留空,加上sizeof(c3)等于9,不能被2整除,添加一个字节填充,所以sizeof(s2)为10。

5. 空结构体的大小

“空结构体”(不含数据成员)的大小不为0,而为1!!!

3.4.4 union的空间计算

结构体在内存组织上是顺序式的,联合体则是重叠式的,各成员共享一段内存,所以整个联合体的sizeof就是每个成员sizeof的最大值。

struct s1 {
    double b;
};

union u {
    int i;
    char c;
    s1 s;
};

上面的例子中,sizeof(u)为8(union也需要考虑对齐,对齐原则与struct相同) 。

3.4.5 枚举的空间计算

enum只是定义了一个常量集合,里面没有“元素”,而枚举类型是当作int类型存储的,故枚举类型的sizeof值都为4。

enum number{one, two, three, four} num;

则sizeof(number)和sizeof(num)都为4。

最后再补充一句:函数以及类型重定义不占字节~~~

到此,有关笔试中sizeof相关的知识点都说的差不多了,难的部分主要是Big-endian和Little-endian的存储上,多画画图就能写出来了,最重要的就是要分清类别,一点点的算,不明白的可以一起交流,希望大家能有所收获

返回目录 -> C/C++基础知识概述
原文链接: https://www.cnblogs.com/TinyBobo/p/4725616.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/220502

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 上午10:56
下一篇 2023年2月13日 上午10:56

相关推荐