数据结构 -- 结构体Struct

C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。结构体的定义形式为:

struct 结构体名{
    结构体所包含的变量或数组
};

1. 定义结构体变量

结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间。结构体变量才包含了实实在在的数据,需要内存空间来存储。

  • 方式一:先定义结构体,再定义结构体变量
//定义stu结构体
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
};

//定义两个结构体变量
struct stu stu1, stu2;

stu为结构体名,里面包含name、num、age、group、score这5个成员。stu1stu2则为两个stu类型的结构体变量。

  • 方式二:在定义结构体的同时定义结构体变量
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1 stu2;

直接将变量放在结构体的最后即可。

如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名。

  • 注意:在结构体内部定义结构体
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    struct sub1{
        char group;  //所在学习小组
    } sub1;
    struct sub2{
        float score;  //成绩
    };
};

如上所示,在stu结构体里还定义了『结构体变量sub1』和『结构体sub2』,由于sub2没有定义变量,所以其内部成员score即为母结构体stu的成员变量。

2. 成员的获取和赋值

使用点号.获取结构体变量的单个成员,然后再进行赋值操作。

//给结构体成员赋值
stu1.name = "Tom";
stu1.num = 12;
stu1.age = 18;
stu1.group = 'A';
stu1.score = 135;

//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);

//打印结果
Tom的学号是12,年龄是18,在A组,今年的成绩是135!

也可以在定义结构体变量时整体赋值:

struct{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };

3. 结构体的内存分配

结构体中各成员在内存中是按顺序依次存储的,成员之间不互相影响,各占用不同的内存空间。结构体变量占用的内存大于等于所有成员占用的内存的总和,因为成员在存储时需要遵循结构体的内存对齐规则,成员之间可能会存在裂缝。

结构体内存对齐

4. 计算结构体的内存大小

先来看看结构体的内存对齐规则:

1. 数据成员对⻬规则:结构体的第⼀个数据成员会存放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组, 结构体等)的整数倍开始存储(⽐如int为4字节,则要从4的整数倍地址开始存储)。

2. 结构体作为成员:如果⼀个结构里有某些结构体成员,则结构体成员要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储。(struct a⾥存有struct b,b⾥有char, int, double等元素,那b应该从8的整数倍开始存储.)

3. 总内存对齐:结构体的总⼤⼩,也就是sizeof的结果,必须是其内部最⼤成员所占内存大小的整数倍,不⾜的要补⻬。若结构体a里包含结构体成员b,则需要将a的其他成员和b里的成员相比,得到最大成员内存,再按照最大内存的倍数进行补齐。

看完内存对齐规则是不是感觉有点绕?不急,接下来通过分析具体例子来理解这个规则。

示例1:含有多种数据类型成员

struct Struct {
    double a;   //8 (0-7)
    char b;     //1 [8 1] (8)
    int c;      //4 [9 4] 9 10 11 (12 13 14 15)
    short d;    //2 [16 2] (16 17)
} struct1;

//输出内存大小
printf("struct1 = %lu \n", sizeof(struct1));

//输出结果
struct1 = 24    

输出结果分析:

  • double a:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。
  • char b:b只占用1个字节内存,会按顺序存放在位置8处。
  • int c:c占用4字节内存,本该从位置9开始存放,但根据规则1可知,c必须从4的整数倍开始存储,而9不是4的整数倍,需要后移到位置12才开始存储,所以c存放在结构体内存中的12-15的位置。
  • short d:d占用2字节内存,可以接着从位置16开始存储,所以存放在16-17的位置。

根据上面的分析可知,struct1的成员总共需要18字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为24。

sizeof:是一个运算符,用来计算传进来的数据类型占用多大的内存,在编译时即可完成运算。比如:sizeof(int)为4字节,sizeof(double)为8字节。

示例2:交换成员位置

struct Struct {
    double a;   //8 (0-7)
    int c;      //4 (8 9 10 11)
    char b;     //1 (12)
    short d;    //2 [13 2] (14 15) - 16
} struct2;

//输出内存大小
printf("struct2 = %lu \n", sizeof(struct2));

//输出结果
struct2 = 24    

这次在示例1中struct的基础上交换了成员b和c的位置,输出结果就不一样了,分析如下:

  • double a:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。
  • int c:c占用4字节内存,可以接着从位置8开始存储,所以c存放在结构体内存中的8-11的位置。
  • char b:b只占用1个字节内存,可以直接存放在位置12处。
  • short d:d占用2字节内存,本该从位置13开始存放,根据规则1可知,由于13不是2的倍数,需要后移到位置14开始存储,所以存放在14-15的位置。

根据上面的分析可知,struct2的成员总共需要16字节内存,根据规则3,struct2的内存大小必须是8(double a)的整数倍,所以最后内存大小为16。

示例3:结构体嵌套结构体

直接在struct1里加上一个struct2成员,然后输出内存大小

//在最后加上成员e
struct Struct1 {
    double a;   // 8
    char b;     // 1
    int c;      // 4
    short d;    // 2
    struct Struct2 e;  //16
} struct1;

//输出内存大小
printf("struct1 = %lu \n", sizeof(struct1));

//输出结果
struct1 = 40

从之前struct1的分析可知,a、b、c、d实际占用18字节(位置0-17),那成员e就需要从位置18开始存放。由于e是个结构体,根据规则2,当结构体作为成员时,需要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储。结构体e中内存占用最大的元素是double a,为8字节,所以e就需要从8的整数倍地址开始存储,即后移到位置24开始存储,e本身占用16字节内存,所以存放位置是24-39。

根据上面的分析可知,struct1的成员总共需要40字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为40。

对于这个示例,大家可以改变成员e在Struct1中的位置,或者更改struct1和e里的成员类型,再看下输出结果和自己计算的结果是否相同。

推荐阅读

1. 数据结构 -- 共用体Union
2. 数据结构 -- 位域

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355