C# 认识结构体,结构体的对齐规则及使用(详解!详解!)


我们知道数组可以存放很多数据,但是一个数组只能存放一种同类型的数据,为了解决这个问题,就出现了结构体,结构体可以存放多种类型的数据。

结构体的声明及特殊声明

struct tag  // tag是结构体类型名,根据自己的需要自定义
{
    number-list;//结构体的成员列表
}variable-list; //variable-list表示变量列表,可有可无

示例:

struct Student
{
    //成员变量
    char name[20];
    int age;
    float score;
};

可以看到结构体确实可以存放许多不同类型的数据

对结构体进行特殊声明

  • 也叫不完全声明(不写结构体类型名)

示例:

struct 
{
    char name[20];
    int age;
}

像上面这样的结构体类型只能用一次(一般用于较小的工程中)

结构体变量的创建和初始化

以上面的示例为例子,对结构体变量进行创建和初始化:

#include <stdio.h>
struct Student
{
    //成员变量
    char name[20];
    int age;
    float score;
}s3,s4;//在这里也可以创建结构体变量
int main()
{
    //创建结构体变量
    struct Student s1 = 0;//struct Student 代表s1的类型,说明了s1是结构体变量
    //对结构体变量进行初始化
    s1 = {"lisi",19,90}; //这是按着成员变量的顺序进行初始化
    struct Student s2 = 0// s2.age = 100;//只对s2这个变量的成绩进行初始化
    //下面这条语句是不按成员变量的顺序进行初始化
    s2 = {.score = 98,.name = "wangwu",.age = 20};
    return 0;
}

访问结构体的成员

访问结构体成员,需要用到两个操作符,第一个操作符是“ . ”操作符,第二个操作符是“->”操作符

点(“ . ”)操作符:结构体变量 . 成员变量名
箭头(“ -> ”)操作符:结构体变量的地址 -> 成员变量名

示例:

#include <stdio.h>
struct Student
{
    char name[20];
    int age;
}s1,s2;
int main()
{
    s1 = {"lisi",19};
    s2 = {"wangwu",20};
    //运用点操作符访问
    printf("%s %d\n",s1.name,s2.age);
    //运通箭头操作符访问
    struct Student *ps2 = &s2;
    printf("%s %d\n",ps2 -> name,ps2 -> age);
    //用数组进行访问
    struct Student s[2] = {s1,s2};
    for(int i = 0; i < 2;i++)
    {
      printf("%s %d\n",s[i].name,s[i].age);
    }
    return 0;
}
结构体的自引用

用一个例子来讲解,如:定义一个链表,链表中有多个节点,如何对每个链表的节点进行访问

先用画图板给大家画一个链表:

在这里插入图片描述

struct Node
{
    int data;
    struct Node next;// next代表下一个节点
};

上面的代码其实是错误的,一个结构体中包含一个同类型的结构体,会使结构体成员变量的大小变得无穷大,一直访问下去,sizeof(struct Node)是多少是不可知的,所以这样的结构体自引用是不合理的。

正确的自引用:

struct Node
{
    int data;//数据域
    struct Node *next;//指针域
};

我们可以通过存放下一个结点的地址,来找到下一个节点,这样的sizeof(struct Node)是可知的。

结构体内存对齐(计算结构体大小)

定义一个结构体,计算该结构体的大小:

#include <stdio.h>
struct S1
{
    char c1;
    char c2;
    int n;
}s1;
int main()
{
    s1 = {0};
   //打印结构体的大小
   printf("%zd\n",sizeof(struct S1));
   return 0;
}

大家是否会认为该结构体的大小是:1+1+4=6
其实输出的结果是:8
出现这样的结果是因为,结构体成员在内存中是存在对齐现象的。

offsetof函数

在讲对齐现象之前,先给大家介绍一下offsetof函数,该函数是计算结构体成员相较于结构体变量起始位置的偏移量,可通过该函数来计算结构体的大小
示例:计算上面代码中结构体成员的偏移量

#include <stdio.h>
#include <stddef.h>//offsetof函数的头文件
struct S1
{
    char c1;
    char c2;
    int n;
};
int main()
{
   //打印结构体成员的偏移量
   //打印的是无符号整数,所以用%zd打印
   printf("%zd\n",offsetof(struct S1,c1));//偏移量是0
   printf("%zd\n",offsetof(struct S1,c2));//偏移量是1
   printf("%zd\n",offsetof(struct S1,n));//偏移量是4
   return 0;
}

输出的结果是:0 1 4

通过上面offsetof函数算出的c1,c2,n的偏移量,可得出结构体struct S1的大小
画一块内存帮助大家理解
在这里插入图片描述
可知,得到结构体变量的偏移量可算出结构体的大小

回到结构体内存对齐
结构体成员在内存中的对齐是有规则的,规则如下:

  1. 结构体的第1个成员要对齐到起始位置偏移量为0的地址处
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
    对齐数 = 编译器默认的一个对齐数与该成员变量的大小比较,谁小谁就是对齐数
    vs中默认的对齐数是8
    linux中gcc没有默认对齐数,对齐数就是成员自身的大小
  3. 结构体的总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的那个)的整数倍
  4. 如果嵌套了结构体情况,嵌套的结构体成员对齐到自己成员中最大的对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

举例:
还是用刚刚struct S1的代码进行举例,该结构体的大小是8

#include <stdio.h>
struct S1
{
    char c1;
    char c2;
    int n;
}s1;
int main()
{
    s1 = {0};
   //打印结构体的大小
   printf("%zd\n",sizeof(struct S1));
   return 0;
}

按规则来,第一条规则:结构体的第1个成员要对齐到起始位置偏移量为0的地址处

在这里插入图片描述
第二条规则:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,对齐数 = 编译器默认的一个对齐数与该成员变量的大小比较,谁小谁就是对齐数,vs中默认的对齐数是8。
在这里插入图片描述

第三条规则:结构体的总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的那个)的整数倍
在这里插入图片描述
第四条规则:如果嵌套了结构体情况,嵌套的结构体成员对齐到自己成员中最大的对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

//先定义一个嵌套结构体
struct S1
{
    char c1;
    char c2;
    int n;
};
struct S2
{
    char c3;
    struct S1 s1;
};

struct S2中嵌套了struct S1,由刚刚可知,struct S1的大小为8,还是一样借助画图板来分析struct S2的大小
在这里插入图片描述
要注意的是,当得出的结构体大小不是最大对齐数的整数倍时,需要继续增加字节数,达到整数倍的即可

关于结构体的知识就讲到这,还有不懂的或者错误的欢迎提出,一起加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值