【C++笔记】04 结构体、共用体与枚举

 

1 结构体 struct

结构体相比于数组,有以下两点不同:

  • 结构体可以在一个结构中声明不同的数据类型;
  • 相同的结构体变量之间可以相互赋值。

C++中struct具有很多class的功能,但也存在不同点:

  • class的成员访问权限默认为private,而struct成员的访问权限默认为public
  • struct不能定义无参的构造函数

1.1 结构体定义

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

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

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

在结构体类型定义中需要注意以下几点:

  1. 结构体类型定义中不允许对结构体本身的递归定义,但可以使用指针指向本类型

    struct Person{
        类型名1 成员名1;
        类型名2 成员名2;
        ...
        类型名n 成员名n;
        struct Person* pPerson;//指向本类型的指针
    }
    
  2. 结构体定义中可以包含另外的结构体,即结构体是可以嵌套的;

  3. 结构体变量可以在定义时进行初始化赋值

1.2 结构体中的位域

C/C++允许指定占用特定位数的结构成员。位域的类型应为整型或枚举,加冒号,后面接一个数字。该数字指定了使用的位数,且可以使用没有名称的字段来提供间距,每个成员都被称为位域(bit field)。使用位域可以达到压缩数据的目的。位域的使用和结构体成员的使用相同。

struct Reg{
    unsigned int SN:4;
    unsigned int :4;
    bool good:4;
}

注:在进行赋值时,不能超过该位域的允许范围,超过时,会仅将等号右侧值的低位赋值给位域。

2 共用体 union

共用体中所有类型的变量共享同一段内存,以达到节省空间的目的。定义共用体的一般形式为:

union 共用体名{
    类型名1 成员名1;
    类型名2 成员名2;
    ...
    类型名n 成员名n;
}变量名;

共用体与结构体的区别

  • 在任何同一时刻,共用体中只存放了一个被选中的成员;而结构体的所有成员都存在。

  • 对于共用体的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了;而对于结构体的不同成员赋值是互不影响的。

  • 共用体中各成员共享一段内存空间,一个共用体的总长度等于各成员中最长的长度;结构体各成员有各自的内存空间,一个结构体变量的总长度是各成员长度之和(空结构除外,同时不考虑边界调整)。

    注:结构体占用内存可能超过各成员内存量总和。

union和struct都是从低地址开始存放。

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

3 大端存储格式与小端存储格式

大端存储格式:字数据的高字节存放在低地址中,而字数据的低字节存放在高地址中。

小端存储格式:字数据的低字节存放在低地址中,而字数据的高字节存放在高地址中。

例如:存储一个32位宽的数0x12345678

  • 大端存储格式:

    内存地址 0x4000 0x4001 0x4002 0x4003
    存放内容 0x12 0x34 0x56 0x78
  • 小端存储格式:

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

常用的x86架构是小端模式,而Sun的SPACRC采用大端模式。

判断大小端的方式

#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1

int TestByteOrder()
{
    short int word=0x0001;
    char* byte=(char*)&word;
    return (byte[0] ? LITTLE_ENDIAN: BIG_ENDIAN);
}

大端小端还会影响到位域成员的存放。当定义的数据结构中包含bit位域时,将按以下规则存放:

  1. 低字节都存放在低地址;
  2. 大端模式首先为字段的高bit位分配空间,小端模式首先为字段的低bit位分配空间;
  3. 大端模式首先存放地址的高bit位,小端模式首先存放地址的低bit位。

例如:

struct{
    short bit1:4;//假设bit1位$a0a1a2a3$
    short bit2:9;
    short bit3:3;
};

大端模式下在内存中存放的形式为:

       
bit1(4位,顺序为a0a1a2a3) bit2(高4位) bit2(低5位) bit3(3位)

小端模式下在内存中存放的形式为:

       
bit2(低4位) bit1(4位,顺序为a3a2a1a0) bit3(3位) bit2(高5位)

4 枚举 enum

定义枚举的一般形式为:

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

注:如果不给枚举常量赋初值,编译器会为每个枚举常量赋一个不同的整型值,第一个为0,第二个为1等。当枚举表中某个常量赋值后,其后的成员则按依次加1的规则确定其值。

5 sizeof运算符

sizeof是一个单目运算符,它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名,操作数的存储大小由操作数的类型决定。

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

5.1 sizeof的使用方法

1)用于变量或对象:可以不使用括号括住。

sizeof(var)  sizeof var

2)用于数据类型:必须用括号括住。

sizeof(type)

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

3)sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用(返回类型为void的不能计算) 。

4)C99标准规定,函数名称、不能确定类型的表达式以及位域成员不能计算sizeof,即以下写法均为错误:

  • 函数名称

    int foo() {return 1;}
    sizeof(foo);//错误
    
  • void

    void foo() {}
    sizeof(foo());//错误
    
  • 位域成员

    struct S{
        unsigned int f1 : 1;
        unsigned int f2 : 5;
    }s;
    sizeof(s.f1);//错误
    

5.2 sizeof的结果

sizeof操作符的结果类型为size_t,被定义为unsigned int类型。

sizeof(char):1
sizeof(short):2
sizeof(int/long/float):4
sizeof(double):8
sizeof(指针):4(x32)/8(x64)

这些简单的内置数据类型和系统相关,在不同的系统下取值可能不同。

注意:sizeof("\0")=2。

5.3 指针、引用、汉字及数组的sizeof操作

1)在32位机器系统下,指针变量的sizeof为4;在64位机器系统下,指针变量的sizeof为8。

2)所有引用的sizeof等同于被引用对象的sizeof

3)C/C++中一个汉字占两个字节,且字符串末尾有1个空字符。(Linux中如使用UTF-8,则每个汉字占3个字节)

4)对于数组,sizeof可直接计算数组大小。

int a[10];//sizeof(a):40
char b[]="hello";//sizeof(b):6
int* c = new int[50];//sizeof(c):4

数组做形参时,数组名称当作指针使用

void fun1(char a1[3])
{
    int c1=sizeof(a1);//c1=4
}
void fun2(char a2[])
{
    int c2=sizeof(a2);//c2=4
}

数组为高维数组时,占用空间=各维度之积* 数组所存放元素占用空间。

例如:32位机器上,int ** a[3][4],该数组占空间大小为3 * 4 * 4=48。

5.4 struct的空间计算

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

1)整体空间是占用空间最大的成员(的类型)所占字节数的整数倍,但在32位Linux+gcc环境下,若最大成员类型所占字节数超过4,如double是8,则整体空间是4的倍数即可;

2)数据对齐原则——内存按结构体成员的先后顺序排列,当排到该成员变量时,其前面已摆放的空间大小必须是该成员类型大小的整数倍,如果不够则补齐,依次向后类推,但在Linux+gcc环境下,若某成员类型所占字节数超过4,如double是8,则前面已摆放的空间大小是4的整数倍即可,不够则对齐。

struct s1{
    char a;//8(补齐至8的倍数)
    double b;//8
    int  c;//4
    char d;//4(补齐至8的倍数)
};//sizeof(s1):24
struct s2{
    char a;//1
    char b;//3(补齐至4的倍数)
    int  c;//4
    double d;//8
};//sizeof(s2):16

//若在Linux+gcc环境下,应分别为:20 16

struct s3{
    char c;
    int  i;
};//sizeof(s3):8
struct s4{
    char c1;
    s3   s;
    char c2;
};//sizeof(s4):16

5.4.1 结构体中含有结构体

当结构体中含有结构体时,我们称上例中s3为子结构体,s4为父结构体,此时的两项原则为:

1)整体空间是子结构体与父结构体中占用空间最大的成员(的类型)所占字节数的整数倍;但在Linux+gcc环境下,若最大成员类型所占字节数超过4,如double是8,则整体空间是4的倍数即可;

2)数据对齐原则——父结构体内存按结构体成员的先后顺序排列,当排到子结构体成员时,其前面已摆放的空间大小必须是该子结构体成员中最大类型大小的整数倍,如果不够则补齐,依次向后类推。在Linux+gcc环境下,若某成员类型所占字节数超过4,如double是8,则前面已摆放的空间大小是4的整数倍即可,不够则对齐。

5.4.2 结构体中含有数组

当结构体中含有数组时,计算sizeof并未将数组当成一个整体。

struct s5{
    char b;
    char a[8];
};//sizeof(s5):9

5.4.3 结构体中含有位域

当结构体中含有位域时,位域成员是不能单独计算sizeof值,而包含位域的结构体可以。使用位域的主要目的是压缩存储,大致规则为:

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

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

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

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

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

(VS2010环境下int a:4,如果后面不是位域,则占用4个字节,Dev-C++/Linux+gcc环境下,如果后面不是位域,仅占1个字节;在压缩情况下,一个字节可以容纳两个char

struct b1{
    char f1:3;
    char f2:4;
    char f3:5;
};//sizeof(b1):2

5.4.4 当使用#pragma pack对齐

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

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

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

这两个指令应搭配使用。

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

5.4.5 空结构体

当结构体为空时,sizeof值不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量如何区分呢?因此,“空结构体”也需要被存储,编译器就为其分配1个字节的空间用于占位

5.5 union的空间计算

结构体在内存组织上是顺序式的,共用体则是重叠式的,因此共用体的sizeof值为各成员sizeof的最大值

struct s1{
    double b;
};
union U{
    int i;
    char c;
    s1 s;
};//sizeof(U):8

union U2{
    char c[9];
    int vbh[2];
};//sizeof(U2):12
//要考虑对齐!

5.6 enum的空间计算

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

struct s1{
    enum e1{a=0,b,c,d,e,f};
    enum e2{x,y,z};
};//sizeof(s1):1
struct s2{
    enum e1{a=0,b,c,d,e,f}e_1;
    enum e2{x,y,z}e_2;
};//sizeof(s2):8

如果结构体中仅存在枚举声明,该结构体为空结构体,其sizeof值为1;如果结构体存在枚举变量,其内每个enumsizeof值为4。