自定义类型——结构体

文章目录

结构体的介绍

定义和声明结构体

结构体成员的初始化

访问结构体成员

结构体变量访问

结构体指针访问

结构体传参

结构体的内存对齐

规则

规则1

规则2

规则3

规则4

VS环境下修改默认对齐数


结构体的介绍

        在c语言中,有着各种各样的数据类型,比如:char、short、int、long、float、double等等,但是这些类型不能够完全描述我们生活中的一些事物的属性,比如一个学生的基本信息,包含其姓名、性别、年龄、学号等等,用char不能完全描述这些东西,用int也不能……用上述任何一个数据类型都不能准确描述学生的属性,此时就需要自己定义一个类型来描述,结构体就发挥其作用,它允许存储不同类型的数据项。

定义和声明结构体

        定义结构,必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct tag {

        member-list;

        member-list;

        member-list;

        ...

} variable-list ;

tag 是结构体标签。

member-list 是标准的变量定义,比如 int i; 或者 float f;也可以称其为结构体的成员。

variable-list 结构变量,定义在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量,可有可无。

        举一个实例,要记录学生的姓名、性别、年龄、学号的结构体变量:

struct  student
{
        char  name[20];
        char sex;
        int age;
        char num[20];
}a,b;  //这里的a、b两个变量可有可无

struct student x;//声明结构体变量,结构体本质上也是一种数据类型,所以其声明和int、double等类型类似,都是 struct+标签+变量名;  

        此外,还可以设计匿名结构体,但是值得注意的是,在自定义匿名结构体的时候,不能够忘了要在结尾的分号前设计需要数量的结构体变量,同样记录学生的姓名、性别、年龄、学号的结构体变量:

struct
{
        char  name[20];
        char sex;
        int age;
        char num[20];
}a,b;   //这里的结构体变量必须要有!!!需要多少个变量就设计多少个变量

           也可以使用typedef关键字来定义结构体的名称,比如:

typedef struct student
{
        char  name[20];
        char sex;
        int age;
        char num[20];
}stu;   //这里是对该结构体的新命名,必须要写在这里,写在末尾的分号前

struct student a;//传统声明方法
stu b;           //typedef 后,可以选择的第二种声明方法,因为stu是该结构体的新名字

        除了这些之外,还需要注意的是,定义了两个结构体,那么这两个结构体在编译器看来就是完全不同的两个数据类型,即使两个结构体内的成员一摸一样,那也是两个类型,比如:

struct student1
{
    char name[20];
    int age;
}*ps;

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

ps=&a;//这样子是错误的,因为struct student1 和 struct student2 是不同的类型

        还有就是,如果一个结构体A包含在另一个结构体B之内,那么结构体A要么在B之前定义,要么在结构体B之前声明:

struct B; //对结构体B的声明,因为其包含在结构体A中且在结构体A之后定义

struct A
{
    char a;
    int b;
    struct B c;
};

struct B
{
    double a;
    float x;
    struct A y;//结构体A虽然包含在结构体B之内,但是A在B之前定义,所以不用声明结构体A
};

        最后,一个结构体里面的成员如果是结构体本身,那么如何设计呢?像下图一样直接设计一个自身结构体变量是错误的做法,从其内存大小方面来看,sizeof(struct student)该如何计算呢?一个struct student里面有第二个struct student ,第二个里面有第三个……这样无限循环,无法计算大小,从这一方面就可以看出这种写法是行不通的。

struct student1
{
    char name[20];
    struct student a;//结构体内部成员是结构体本身
};

        正确 的写法如下,结构体内部设计指向该结构体类型的指针,非指针成员被称作数据域,指针成员被称作指针域,在以后的数据结构方面有用到较多。

struct student1
{
    char name[20];
    struct student *a;//结构体成员是自身结构体的指针
};

结构体成员的初始化

        初始化结构体有两种方法:

        由于结构体里面往往不止一个成员,所以初始化的时候要用  { }  大括号将其括起来。

        第一种方式:  { .结构体成员名=, .结构体成员名=值, ……};    

        第二种方式:{ , 值, ……};

        要注意的是,结构体成员是int、double等类型时, 直接写数据就行,但是如果是char类型的话,只有一个字符那么用 ' ' 单引号括起来,字符串用双引号括起来。具体如下

#include<stdio.h>

struct people
{
	int a;
	float b;
	char c;//char类型且只有一个字符
};

struct test
{
    int a;
    char x[10];//char类型且是字符串
};

int main()
{
	struct people a = { .a = 10,.b = 6.6,.c = 'a' };//这是一种初始化方式
	printf("%d %.2lf %c\n", a.a, a.b, a.c);

	struct people b = { 10,6.6,'a' }; //这是第二种结构体初始化方式,个人习惯用这一种
	printf("%d %.2lf %c", b.a, b.b, b.c);

    struct test arr[3]={{1,"abcdefghij"},{2,"usingforar"},{3,"helloworld"}};  //字符串的初始化是 ""
    printf("%d %s",arr[0].a,arr[0].x);
	return 0;
}

访问结构体成员

结构体变量访问

        使用成员访问运算符 . 来访问结构体成员,一般是"结构体变量名.结构体成员名"。

#include<stdio.h>

struct book
{
    char auther[20];
    int price;
};

int main()
{
    struct book a;//定义结构体变量
    a.auther="zhangsan";  //访问结构体成员
    a.price=30;            
    printf("%s  %d",a.auther,a.price);  //打印
    return 0;

}

结构体指针访问

        使用 -> 来访问结构体成员,一般也是 “结构体变量名->结构体成员名”,也可以使用先解引用,再用 . 来访问,格式为“(*结构体变量名).结构体成员名” 。

#include<stdio.h>

struct book
{
    char auther[20];
    int price;
};

int main()
{
    struct book *a;//定义结构体指针变量
    a->auther="zhangsan";  //访问结构体成员
    (*a).price=30;         //第二种访问方式,先解引用,然后再用.            
    
    printf("%s  %d",a->auther,a->price);  //打印

    
    return 0;

}

结构体传参

        当结构体作为函数参数时,在函数内部访问任然和上面两个方式一样。

#include<stdio.h>

struct book
{
    char auther[20];
    int price;
};

void test1(struct book x)
{
    printf("%s",x.auther);//传值和结构体变量访问方法一样
}

void test2(struct book* x)
{
    printf("%s\n",x->auther);//传地址和结构体指针访问方法一样
    printf("%d",(*x).price);
}

int main()
{
    struct book *a;//定义结构体指针变量
    a->auther="zhangsan";  
    (*a).price=30;                  
    
    struct book b={"lisi",20};
   
    test1(b);//传值
    test2(a);//传地址
    return 0;
}

结构体的内存对齐

规则

1、结构体的第一个成员直接对齐到 相对于结构体变量起始位置 偏移量为0的地址处
2、从第二个成员开始,每个成员要对齐到【对齐数】的整数倍处
【对齐数】—— 结构体成员自身大小和默认对齐数的较小值
VS情况下默认对齐数:8      Linux环境默认不设对齐数(对齐数是结构体成员自身大小)
3、如果嵌套结构体,那么嵌套结构体对齐到自己的最大对齐数的整数倍处
4、结构体的总大小必须是最大对齐数的整数倍

偏移量:在这里指以结构体变量起始位置为起点,某个内存(单位是字节)的位置为终点,起点和终点的距离(单位是字节),偏移量从0开始。(下面第一张图最左边有介绍)

对齐:在这里我理解为结构体成员存放的起始位置,比如成员a要对齐到4的整数倍,那么无论变量a占用多少个空间,其存放的第一个空间都要再偏移量为4*n的地方,详细见下方解读。

占用空间小的结构体成员尽量放在一起,这样能很好的节约结构体空间

规则1

         现在对这四个规则还没有了解,咱们一个一个慢慢来看,首先第一个规则容易理解,第一个成员直接对齐到偏移量为0的地址处,无论第一个成员是什么类型,如下图中间部分,结构体struct book第一个成员是int类型,其占四个字节,那么就从起始位置开始(偏移量为0),四个字节空间(标红)存放int  a; 下图最右边部分,struct book结构体第一个成员是char类型,char类型的数据占1个字节的内存空间,那么就从起始位置开始(偏移量为0),一个字节空间(标红)存放char a;:

 规则2

         在介绍规则2之前我们要先了解什么是对齐数,首先对齐数的定义是结构体成员自身大小和默认对齐数的较小值,结构体成员自身大小并不难知道,比如int类型占4个字节,double类型占8个字节等等,默认对齐数就有一些讲究了,在VS环境下的默认对齐数是8,即只要结构体成员自身大小小于等于8,那么这个成员对齐数就等于自身大小;结构体成员自身大小大于8,那么这个成员的对齐数就是8。但是在Linux环境下,没有默认对齐数,所以对齐数是结构体成员自身大小,比如在Linux环境下,int类型对齐数是4,char类型对齐数是1,double类型对齐数是8等等,都是自身大小。

        比如下面这张图,中间部分,结构体第二个成员是char b;该成员自身大小是1(char类型所占空间是1个字节),VS环境下默认对齐数是8,两者取较小的,那么该成员对齐数就是1,那么这个成员就要对齐到1的整数倍处,所以从偏移量为4的地方开始存放,标记为蓝色部分。而结构体第三个成员是int c;自身大小时4,VS环境默认对齐数是8,所以该成员对齐数是4,所以该成员要对齐到4的整数倍处,看偏移量,4的整数倍,首先看到4,发现4已经被使用了,不行,再看到8,发现还没有使用过,于是从偏移量为8的地方开始存放,需要4个字节,标记为黄色部分

        下面此图右边部分,结构体第二个成员int b;对齐数不难算出是4,那么从偏移量4*n的地方开始存放,发现偏移量为4的地方可以存,所以从此处存放int b;占用四个内存空间。第三个成员也是int类型,同样从偏移量为4*n的地方开始存放,从偏移量为8处开始存,占四个内存空间。

        我们可以用offsetof宏来验证规则2,offsetof是计算一个结构成员相对于结构开头的字节偏移量,其头文件是<stddef.h>,包含两个参数,一个是结构体标签,另一个是结构体内部成员名。此段代码运行结果如下。

#include<stdio.h>
#include<stddef.h>
struct book1
{
	int a;
	char b;
	int c;
};
struct book2
{
	char a;
	int b;
	int c;
};
int main()
{   
	printf("第一个的偏移情况:\n");
	printf("%d\n", offsetof(struct book1,a));  
	printf("%d\n", offsetof(struct book1, b));
	printf("%d\n", offsetof(struct book1, c));
	printf("第二个的偏移情况:\n");
	printf("%d\n", offsetof(struct book2, a));  
	printf("%d\n", offsetof(struct book2, b));
	printf("%d\n", offsetof(struct book2, c));
    return 0;
}

 

规则3

        出现嵌套结构体的情况,那么嵌套结构体也是外层结构体内的一个成员,也应该有一个对齐数,那么该嵌套结构体的对齐数就是自己内部成员的对齐数的最大值(不包括默认对齐数)。比如下方设计的struct B结构体,其内部嵌套了一个struct  A  c;结构体变量,这个成员的对齐数要看struct A结构体内部的对齐数,其内部三个对齐数分别是2、4、1,最大值是4,所以该成员的对齐数是4,要从偏移量是4*n的地方开始存放第三个成员,但是存放第三个成员,是相当于把struct A结构体所占空间里的内容全部拷贝到struct B里面,struct A的内部成员不需要在struct B的标准下再次对齐,只需要把struct A c;看成一个整体,其整体大小是多少,在struct B里面就占多少空间,其内存结构如下方图片。

#include<stdio.h>
struct A
{
    short a;    //对齐数为2
    int b;      //对齐数为4
    char c;     //对齐数为1
};

struct B
{
    int a;      //对齐数为4
    char b;     //对齐数为1
    struct A c; //对齐数为4   struct A  c;也是struct B的一个成员,所以也有对齐数
};

int main()
{
	printf("%d\n", sizeof(struct A));
	printf("%d\n", sizeof(struct B));
	return 0;
}

        上面一段代码放到VS环境下运行,运行结果如下,暂时先不用理解struct A的空间大小为什么是12,只需要知道其空间大小是12即可,下文会详细讲其原因,下方内存布局图片所示,struct A的12个空间,看作一个整体,一起放到struct B里面,struct A有多大,其在struct B内部就占多少空间,如绿框标出的:

  

 规则4

        结构体总大小必须是最大对齐数的整数倍,这个就不难理解了,如上图的struct A结构体类型,其三个成员大小分别是2、4、1,不难发现,struct A结构体大小不是简单的2+4+1=7,也不是简单的如上图左边所示的,到最后一个黄色区域为止,一共占了9个内存空间大小就是9。其内存大小是12,就是因为有规则4的存在,总大小必须是结构体内部成员最大对齐数的整数倍,其内部成员最大对齐数是4,那么struct A的总大小必须是4*n(n为整数),而根据其内存分布,已经占了9个内存空间,比9大的且是4的倍数的数字里面,最小的是12,所以其总内存是12。

        而对于上图的struct B而言,第一个成员占4个字节内存,第二个成员对齐数为1,而前四个内存空间已经使用,所以在偏移量为1*n的地方开始存,发现偏移量为4处可以存,第三个成员是一个结构体变量,其对齐数是自己内部成员对齐数的最大值,struct A内部最大对齐数是4,所以第三个成员的对齐数是4,从偏移量为4*n的地方开始存,发现只有从偏移量为8开始存,占12个空间,struct B其内部成员对齐数分别是4、1、4,,所以最大对齐数是4,那么struct B的总大小必须是4*n,而由图可知,将struct A放进去之后,正好占了20个内存空间,是4的倍数,所以其大小就是20。(此过程是一套完整的分析过程)

VS环境下修改默认对齐数

        VS环境下的默认对齐数是8,但是可以通过人为操作来改变这个值。如下代码和运行结果。

#include<stdio.h>
#include<stddef.h>

struct test2
{
	char a;
	int b;
	char c;
};

#pragma pack(1)//设置默认对齐数,pack()括号内的值就是修改后的默认对齐数的值
struct test1
{
	char a;
	struct test2 b;
	int c;
};
#pragma pack()//恢复默认对齐数

int main()
{   
	printf("第一个的偏移情况:\n");
	printf("%d\n", offsetof(struct test1,a));  
	printf("%d\n", offsetof(struct test1, b));
	printf("%d\n", offsetof(struct test1, c));
	printf("第二个的偏移情况:\n");
	printf("%d\n", offsetof(struct test2, a));  
	printf("%d\n", offsetof(struct test2, b));
	printf("%d\n", offsetof(struct test2, c));
    return 0;
}

         分析:struct test1 结构体的默认对齐数被改成了1,由于任意的数据类型,其所占空间至少是1个字节,所以不管struct test1 结构体内部成员是什么,它们的对齐数都是1,其第一个成员是char类型,所以占一个字节的内存,第二个成员默认对齐数是1,所以从偏移量为1的地方开始存储,第二个成员大小是12,故存放了12个字节的空间,然后第三个成员默认对齐数也是1,故从偏移量为13处开始存。

        好了,关于结构体的所有基本知识到这里就结束啦,如果有写的不好的地方欢迎评论区多多指正!!!喜欢的话请一键三连哦!!!

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力努力再努力.xx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值