【C/C++基础】11_用户自定义数据类型

1. 结构体类型

1.1 结构体类型定义的一般形式

       在实际问题中,一组数据往往具有不同的数据类型。例如,在学生登记表中,姓名应为字符型;学号可为整型或字符型;年龄应为整型;性别应为字符型;成绩可为整型或实型。显然不能用一个数组来存放这一组数据。因为数组中各元素的类型和长度都必须一致,以便于编译系统处理。为了解决这个问题,C语言中给出了另一种构造数据类型——“结构(structure)”或叫“结构体”。 它相当于其它高级语言中的记录。“结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或者又是一个构造类型。结构既是一种“构造”而成的数据类型,那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。

定义一个结构的一般形式为:

struct结构名

    {成员表列};

成员表列由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:

    类型说明符 成员名;

每一个成员也称为结构体的一个域。成员表又称为域表。成员名的命名应符合标识符的书写规定。例如:

   struct stu

   {

       int num;

       char name[20];

       char sex;

       float score;

};

在这个结构定义中,结构名为stu,该结构由4个成员组成。第一个成员为num,整型变量;第二个成员为name,字符数组;第三个成员为sex,字符变量;第四个成员为score,实型变量。应注意在括号后的分号是不可少的。结构定义之后,即可进行变量说明。凡说明为结构stu的变量都由上述4个成员组成。由此可见, 结构是一种复杂的数据类型,是数目固定,类型不同的若干有序变量的集合。

       在C语言中,结构体的成语只能是数据。C++加以了扩充,结构体的成员既可以是数据成员,又可以是函数成员,以适应面向对象的程序设计。由于C++提供了类类型,一般情况下,不必使用带函数成员的结构体。

1.2 结构类型变量的定义

说明结构变量有以下三种方法。以上面定义的stu为例来加以说明。

1.   先定义结构,再说明结构变量。

如:

struct stu

   {

       int num;

       char name[20];

       char sex;

       float score;

   };

   struct stu boy1,boy2;

说明了两个变量boy1和boy2为stu结构类型。也可以用宏定义使一个符号常量来表示一个结构类型。

例如:

#define STUstruct stu

STU

   {

       int num;

       char name[20];

       char sex;

       float score;

   };

STU boy1,boy2;

2.   在定义结构类型的同时说明结构变量。

例如:

struct stu

   {

       int num;

       char name[20];

       char sex;

       float score;

}boy1,boy2;

这种形式的说明的一般形式为:

struct结构名

    {

成员表列

}变量名表列;

3.   直接说明结构变量。

例如:

struct

   {

       int num;

       char name[20];

       char sex;

       float score;

}boy1,boy2;

这种形式的说明的一般形式为:

struct

    {

成员表列

}变量名表列;

第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量。三种方法中说明的boy1,boy2变量都具有下图所示的结构。


说明了boy1,boy2变量为stu类型后,即可向这两个变量中的各个成员赋值。在上述stu结构定义中,所有的成员都是基本数据类型或数组类型。

4. 说明:

(1)结构体类型的结构可以根据需要进行设计出许多不同的结构体类型。

(2)类型与变量是不同的概念。只能对结构体变量中的成员赋值,对在编译时类型不分配存储空间。

(3)结构体中的成员可以单独使用,他的作用相当于普通变量。

(4)成员也可以又是一个结构,即构成了嵌套的结构。例如,下图给出了另一个数据结构。

按图可给出以下结构定义:

struct date

{

       int month;

       int day;

       int year;

   };

   struct{

       int num;

       char name[20];

       char sex;

       struct date birthday;

       float score;

   }boy1,boy2;

首先定义一个结构date,由month(月)、day(日)、year(年) 三个成员组成。在定义并说明变量 boy1 和 boy2 时,其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名,互不干扰。

1.3 结构体类型变量的初始化

(1)和其他类型变量一样,对结构变量可以在定义时进行初始化赋值。

【例11.1】对结构变量初始化。

main()

{

    struct stu    /*定义结构*/

    {

      int num;

      char *name;

      char sex;

      float score;

    }boy2,boy1={102,"Zhangping",'M',78.5};

 boy2=boy1;

 printf("Number=%d\nName=%s\n",boy2.num,boy2.name);

 printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);

}

(2)可以采用声明类型与定义变量分开的形式,在定义变量时进行初始化。

如:stu student2={10002,"Wang Li", 'F',20};

1.4 引用结构体变量

在程序中使用结构变量时,往往不把它作为一个整体来使用。在ANSI C中除了允许具有相同类型的结构变量相互赋值以外,一般对结构变量的使用,包括赋值、输入、输出、运算等都是通过结构变量的成员来实现的。

表示结构变量成员的一般形式是:

    结构变量名.成员名

例如:

boy1.num          即第一个人的学号

boy2.sex          即第二个人的性别

如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。

例如:

boy1.birthday.month

即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。

(1)可以将一个结构体变量的值赋给另一个具有相同结构的结构体变量。

(2)可以引用一个结构体变量中的一个成员的值。

(3)如果成员本身也是一个结构体类型,则要用若干个成员运算符,一级一级地遭到最低一级的成员。

(4)不能将一个结构体变量作为整体进行输入和输出。

(5)对结构体变量的成员可以向普通变量一样进行有关的运算。

(4)可以引用结构体变量成员的地址,也可以引用结构体变量的地址。结构体变量的地址主要用作函数参数,将结构体变量的地址传递给形参。

1.5 结构体数组

数组的元素也可以是结构类型的。因此可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的下标结构变量。在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。方法和结构变量相似,只需说明它为数组类型即可。

1. 定义结构体数组

例如:

struct stu

   {

       int num;

       char *name;

       char sex;

       float score;

}boy[5];

定义了一个结构数组boy,共有5个元素,boy[0]~boy[4]。每个数组元素都具有struct stu的结构形式。对结构数组可以作初始化赋值。

        2. 结构体数组的初始化

例如:

struct stu

   {

       int num;

       char *name;

       char sex;

       float score;

   }boy[5]={

             {101,"Liping","M",45},

            {102,"Zhangping","M",62.5},

             {103,"Hefang","F",92.5},

             {104,"Chengling","F",87},

             {105,"Wangming","M",58};

}

当对全部元素作初始化赋值时,也可不给出数组长度。

如: stu students[]={{    },{    },...,{    }};

声明结构体类型和定义结构体数组机器初始化的操作也可以分开进行。

1.6 结构指针变量的说明和使用

1. 指向结构体变量的指针

一个指针变量当用来指向一个结构变量时,称之为结构指针变量。结构指针变量中的值是所指向的结构变量的首地址。通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相同的。

结构指针变量说明的一般形式为:

    struct结构名 *结构指针变量名

例如,在前面的例题中定义了stu这个结构,如要说明一个指向stu的指针变量pstu,可写为:

    struct stu *pstu;

当然也可在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同,结构指针变量也必须要先赋值后才能使用。

赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量。如果boy是被说明为stu类型的结构变量,则:

    pstu=&boy

是正确的,而:pstu=&stu是错误的。

结构名和结构变量是两个不同的概念,不能混淆。结构名只能表示一个结构形式,编译系统并不对它分配内存空间。只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间。因此上面&stu这种写法是错误的,不可能去取一个结构名的首地址。有了结构指针变量,就能更方便地访问结构变量的各个成员。

其访问的一般形式为:

    (*结构指针变量).成员名

或为:

       结构指针变量->成员名

和   结构体变量名.成员名 三者等价。

例如:

(*pstu).num

或者:

   pstu->num

应该注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。如去掉括号写作*pstu.num则等效于*(pstu.num),这样,意义就完全不对了。

2. 指向结构数组的指针

指针变量可以指向一个结构数组,这时结构指针变量的值是整个结构数组的首地址。结构指针变量也可指向结构数组的一个元素,这时结构指针变量的值是该结构数组元素的首地址。

设ps为指向结构数组的指针变量,则ps也指向该结构数组的0号元素,ps+1指向1号元素,ps+i则指向i号元素。这与普通数组的情况是一致的。

【例11.2】用指针变量输出结构数组。

struct stu

{

   int num;

   char *name;

   char sex;

   float score;

}boy[5]={

         {101,"Zhou ping",'M',45},

         {102,"Zhang ping",'M',62.5},

         {103,"Liou fang",'F',92.5},

         {104,"Cheng ling",'F',87},

         {105,"Wang ming",'M',58},

       };

void main()

{

 struct stu *ps;

 printf("No\tName\t\t\tSex\tScore\t\n");

 for(ps=boy;ps

 printf("%d\t%s\t\t%c\t%f\t\n",ps->num,ps->name,ps->sex,ps->score);

}

应该注意的是,一个结构指针变量虽然可以用来访问结构变量或结构数组元素的成员,但是,不能使它指向一个成员。也就是说不允许取一个成员的地址来赋予它。因此,下面的赋值是错误的。

ps=&boy[1].sex;

而只能是:

   ps=boy;(赋予数组首地址)

或者是:

ps=&boy[0];(赋予0号元素首地址)

3. 用结构体变量和指向结构体变量的指针构成链表

可在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内又存放第三个结点的首地址,如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。下图为最一简单链表的示意图。

图中,第0个结点称为头结点,它存放有第一个结点的首地址,它没有数据,只是一个指针变量。以下的每个结点都分为两个域,一个是数据域,存放各种实际的数据,如学号num,姓名name,性别sex和成绩score等。另一个域为指针域,存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。

例如,一个存放学生学号和成绩的结点应为以下结构:

   struct stu

   { int num;

     int score;

     struct stu *next;

}

前两个成员项组成数据域,后一个成员项next构成指针域,它是一个指向stu类型结构的指针变量。

链表的基本操作对链表的主要操作有以下几种:

1.  建立链表;

2.  结构的查找与输出;

3.  插入一个结点;

4.  删除一个结点;

【例11.3】静态链表的建立

#include

using namespace std;

struct student{

int num;

float score;

struct student * next;

};

int main()

{

    Student a,b,c;

     Student *p,*head;

     a.num=10001;a.score=90;

     b.num=10002;b.score=91;

     c.num=10003;c.score=92;

     head=&a;

     a.next=&b;

     b.next=&c;

     c.next=NULL;

     p=head;

     do{

      cout<num<<" "<score<

       p=p->next;

    }while(p!=NULL);

    return 0;

}

1.7 结构体类型数据作为函数参数

(1)结构体变量名作函数参数:用结构体变量作实参时,采取的是“值传递”的方式。

(2)用指向结构体变量的指针作实参,将结构体变量的地址传给形参。

(3)用结构体变量的引用作函数形参,它就成为实参的别名。引用主要用作函数参数,它可以提高效率,而且保持程序良好的可读性。

2. 动态存储分配

在数组一章中,曾介绍过数组的长度是预先定义好的,在整个程序中固定不变。C语言中不允许动态数组类型。

例如:

int n;

scanf("%d",&n);

int a[n];

用变量表示长度,想对数组的大小作动态说明,这是错误的。但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题,用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。

2.1 C语言常用的内存管理函数

以下四个函数的声明在stdlib.h头文件。

1.  分配内存空间函数malloc

调用形式:

  (类型说明符*)malloc(size)

功能:在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。

“类型说明符”表示把该区域用于何种数据类型。

(类型说明符*)表示把返回值强制转换为该类型指针。

“size”是一个无符号数。

例如:

          pc=(char*)malloc(100);

表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。

2.   分配内存空间函数 calloc

 calloc 也用于分配内存空间。

调用形式:

  (类型说明符*)calloc(n,size)

功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。

(类型说明符*)用于强制类型转换。

calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。

例如:

     ps=(struet stu*)calloc(2,sizeof(structstu));

其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。

3.  释放内存空间函数free

调用形式:

  free(void*ptr);

功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域。

4. 使用realloc函数

函数原型:

void *realloc(void *p, unsigned int size);

功能:如果已经通过malloc函数或calloc函数获得了动态空间,想要改变其大小,可以使用realloc函数重新分配。用realloc函数将p指向的动态空间的大小改为size,p的值不变。如果重新分配不成功,返回NULL。

【例11.4】分配一块区域,输入一个学生数据。

void main()

{

   struct stu

   {

     int num;

     char *name;

     char sex;

     float score;

   }  *ps;

   ps=(struct stu*)malloc(sizeof(struct stu));

   ps->num=102;

   ps->name="Zhang ping";

   ps->sex='M';

   ps->score=62.5;

   printf("Number=%d\nName=%s\n",ps->num,ps->name);

   printf("Sex=%c\nScore=%f\n",ps->sex,ps->score);

   free(ps);

}

2.2 C++用new和delete运算符进行动态分配和撤销存储空间

注意:new和delete是运算符,执行效率高。

new运算符使用的一般格式:

new 类型[初值];

分配不成功,会返回一个空指针NULL.用new分配数组空间时不能指定初值。

delete运算符使用的一般格式:

delete 指针变量;(对变量)

delete[] 指针变量;(对数组)

3. 共用体类型

共用体也是一种构造数据类型,它是将不同类型的变量存放在同一内存区域内。共用体也称为联合(union)。共用体的类型定义、变量定义及引用方式与结构相似,但它们有着本质的区别:结构变量的各成员占用连续的不同存储空间,而共用体变量的各成员占用同一个存储区域。

3.1 共用体变量的定义

共用体变量的定义与结构变量定义相似,首先,必须构造一个共用体数据类型,再定义具有这种类型的变量。

共用体类型定义的一般方法:

union 共用体名

 { 共用体成员表 } ;

其中,共用体成员表是对各成员的定义,形式为:类型说明符 成员名;

与定义结构变量一样,定义共用体变量的方法有以下三种:

1先定义共用体类型,再定义该类型数据

例如:

        union data {

        char n[10];

        int a;

        double f;};

      union data x,y[10];

2)在定义共用体类型的同时定义该类型变量

例如:

       union data {

        char n[10];

        int a; double f; }x,y[10];

3不定义共用体类型名,直接定义共用体变量

例如:

       union Data{

        char n[10];

        int a;

        double f; }x,y[10];

定义了共用体变量后,系统就给它分配内存空间。因共用体变量中的各成员占用同一存储空间,所以,系统给共用体变量所分配的内存空间为其成员中所占用内存空间最多的成员的单元数。共用体变量中各成员从第一个单元开始分配存储空间,所以各成员的内存地址是相同的。

3.2 共用体变量的引用

定义了共用体变量后,即可使用它。其引用方式与结构体相似,使用成员运算符 “ . ”。若需对共用体变量初始化,只能对它的第一个成员赋初始值。

例如:union Data x={"zhangsan"};是正确的,而union data x={"zhangsan",12,40000, 78,5};是错误的。

虽然共用体数据可以在同一内存空间中存放多个不同类型的成员,但在某一时刻只能存放其中的一个成员,起作用的是最后存放的成员数据,其他成员不起作用,如引用其他成员,则数据无意义。
例如,对data类型共用体变量,有以下语句:
x.a=100;  strcpy(x.n,"zhangsan");  x.f=90.5;

则只有x.f是有效的,x.a与x.n目前数据是无意义的,因后面的赋值语句将前面共用体数据覆盖了。

3.3 共用体的特点

(1)同一段内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中的一个成员,而不是同时存放几个。因为在同一瞬时存储单元只能有唯一的内容,即在共用体变量中只能存放一个值。
(2)可以对共用体变量初始化,但初始化表中只能有一个常量。
(3)共用体变量中起作用的成员是最后一次赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元的值就被取代。
(4)共用体变量的地址和它的各成员地址都是同一地址。如 &a.i, &a.ch, &a.f 都是同一值。
(5)不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
(6)共用体类型可以出现在结构体类型中,也可以定义共用体数组。
(7) 需要对同一段空间安排不同的用途,这时使用共用体类型比较方便。

4. 枚举类型

在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。

4.1 枚举类型的定义和枚举变量的说明

1.  枚举的定义枚举类型定义的一般形式为:

    enum枚举名{ 枚举值表 };

在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。

例如:

   enum weekday{ sun,mou,tue,wed,thu,fri,sat };

该枚举名为weekday,枚举值共有7个,即一周中的七天。凡被说明为weekday类型变量的取值只能是七天中的某一天。

C++允许不写关键字enum,但保留了C的用法。

2.  枚举变量的说明

如同结构和联合一样,枚举变量也可用不同的方式说明,即先定义后说明,同时定义说明或直接说明。

设有变量a,b,c被说明为上述的weekday,可采用下述任一种方式:

enum weekday{ sun,mou,tue,wed,thu,fri,sat };

enum weekday a,b,c;

或者为:

enum weekday{ sun,mou,tue,wed,thu,fri,sat }a,b,c;

或者为:

enum { sun,mou,tue,wed,thu,fri,sat }a,b,c;

4.2 枚举类型变量的赋值和使用

枚举类型在使用中有以下规定:

1.  枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。

  例如对枚举weekday的元素再作以下赋值:

sun=5;

mon=2;

sun=mon;

都是错误的。

2.  枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2…。如在weekday中,sun值为0,mon值为1,…,sat值为6。

【例11.5】

main(){

    enum weekday

    { sun,mon,tue,wed,thu,fri,sat } a,b,c;

    a=sun;

    b=mon;

    c=tue;

    printf("%d,%d,%d",a,b,c);

}

说明:

(1)只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如:

    a=sum;

    b=mon;

是正确的。而:

    a=0;

    b=1;

是错误的。

(2)如一定要把数值赋予枚举变量,则必须用强制类型转换。

如:

    a=(enum weekday)2;

其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于:

    a=tue;

还应该说明的是枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。

(3)枚举值可以用来做比较判断,按整数的规则进行比较。


5. 类型定义符typedef

5.1 用typedef声明新的类型名

C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符typedef即可用来完成此功能。例如,有整型量a,b,其说明如下:

    int a,b;

其中int是整型变量的类型说明符。int的完整写法为integer,为了增加程序的可读性,可把整型说明符用typedef定义为:

typedef int INTEGER

这以后就可用INTEGER来代替int作整型变量的类型说明了。

例如:

    INTEGER a,b;

它等效于:

    int a,b;

用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。

例如:

    typedef char NAME[20];    表示NAME是字符数组类型,数组长度为20。然后可用NAME 说明变量,如:

    NAME a1,a2,s1,s2;

完全等效于:

    char a1[20],a2[20],s1[20],s2[20]

又如:

    typedef struct stu

    { char name[20];

      int age;

      char sex;

  } STU;

定义STU表示stu的结构类型,然后可用STU来说明结构变量:

STU body1,body2;

typedef定义的一般形式为:

typedef 原类型名  新类型名

其中原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。

有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。

5.2 说明 

(1)用typedef只是对已经存在的类型增加一个类型名,而没有创造新的类型;

(2)不可以用typedef来定义变量;

(3)用typedef声明数组类型、字符串类型和指针类型,使用比较方便;

(4)typedef有利于程序的通用与移植。

6. 内存对齐

对于大部分程序员来说,“内存对齐”对他们来说都应该是“透明的”。“内存对齐”应该是编译器的 “管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。

6.1 内存对齐的原因

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

6.2 对齐规则

        每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

        有虚函数的话就有虚表,虚表保存虚函数地址,一个地址占用的长度根据编译器不同有可能不同,vs里面是8个字节,在devc++里面是4个字节。类和结构体的对齐方式相同,有两条规则
    1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

    2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行

    3、结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

6.3 试验

我们将用典型的struct对齐来说明。首先我们定义一个struct:
#pragma pack(n) /* n = 1, 2, 4, 8, 16 */
struct test_t {
 int a;
 char b;
 short c;
 char d;
};
#pragma pack(n)
首先我们首先确认在试验平台上的各个类型的size,经验证两个编译器的输出均为:
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4

我们的试验过程如下:通过#pragma pack(n)改变“对齐系数”,然后察看sizeof(struct test_t)的值。


(1)、1字节对齐(#pragma pack(1))
输出结果:sizeof(struct test_t) = 8 [两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(1)
struct test_t {
 int a;  /* 长度4 < 1 按1对齐;起始offset=0 0%1=0;存放位置区间[0,3] */
 char b;  /* 长度1 = 1 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
 short c; /* 长度2 > 1 按1对齐;起始offset=5 5%1=0;存放位置区间[5,6] */
 char d;  /* 长度1 = 1 按1对齐;起始offset=7 7%1=0;存放位置区间[7] */
};
#pragma pack()
成员总大小=8
2) 整体对齐
整体对齐系数 = min((max(int,short,char), 1) = 1

整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 8 /* 8%1=0 */ 

什么是“圆整”?
举例说明:如上面的8字节对齐中的“整体对齐”,整体大小=9 按 4 圆整 = 12
圆整的过程:从9开始每次加一,看是否能被4整除,这里9,10,11均不能被4整除,到12时可以,则圆整结束。

(2)、2字节对齐(#pragma pack(2))
输出结果:sizeof(struct test_t) = 10 [两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(2)
struct test_t {
 int a;  /* 长度4 > 2 按2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 2 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
 short c; /* 长度2 = 2 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 2 按1对齐;起始offset=8 8%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9
2) 整体对齐
整体对齐系数 = min((max(int,short,char), 2) = 2
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 10 /* 10%2=0 */

(3)、4字节对齐(#pragma pack(4))
输出结果:sizeof(struct test_t) = 12 [两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(4)
struct test_t {
 int a;  /* 长度4 = 4 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 4 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
 short c; /* 长度2 < 4 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 4 按1对齐;起始offset=8 8%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9

2) 整体对齐
整体对齐系数 = min((max(int,short,char), 4) = 4
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 12 /* 12%4=0 */

(4)、8字节对齐(#pragma pack(8))
输出结果:sizeof(struct test_t) = 12 [两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(8)
struct test_t {
 int a;  /* 长度4 < 8 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 8 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
 short c; /* 长度2 < 8 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 8 按1对齐;起始offset=8 8%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9
2) 整体对齐
整体对齐系数 = min((max(int,short,char), 8) = 4
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 12 /* 12%4=0 */

(5)、16字节对齐(#pragma pack(16))
输出结果:sizeof(struct test_t) = 12 [两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(16)
struct test_t {
 int a;  /* 长度4 < 16 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 16 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
 short c; /* 长度2 < 16 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 16 按1对齐;起始offset=8 8%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9
2) 整体对齐
整体对齐系数 = min((max(int,short,char), 16) = 4
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 12 /* 12%4=0 */
(6)、结论
8字节和16字节对齐试验证明了“规则”的第3点:“当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果”。

你可能感兴趣的:(C/C++程序设计)