• 自定义类型:结构体


    一:引入

    C语言中有内置类型,如int ,char,float,long,long long等,这些内置类型可以直接使用。
    如果我们想要描述一个学生,比如学生的名字,年龄,分数等,只有一个内置类型是无法描述清楚的,所以引入结构体的使用。

    二:结构体类型的声明

    1:正常声明

    结构体是自定义类型
    结构体是一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量。
    例如描述一个同学:

    struct Stu//
    {
        char name[20];//名字
    
        int age;//年龄
       
        float score;//分数
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    {}里面的内容就是结构体的成员。

    2:特殊声明

    在声明结构体的时候可以不完全声明。

    struct
    {
    	int a;
    	char b;
    	float c;
    }s = { 0 };//这种特殊的声明只能此时初始化
    struct
    {
    	int a;
    	char b;
    	float c;
    }*ps;
    int main()
    {
    	ps = &s;//error,虽然结构类型的成员一模一样,但编译器依然认为=两边是不同的指针类型
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    结构体未创建变量的时候,是不占用内存空间的,创建变量后,才开始占用空间。

    1:结构体变量的创建

    #include 
    struct stu
    {
    	char name[20];
    	int age;
    	float score;
    }s4, s5;//结构体变量的创建s4,s5
    int main()
    {
    	struct stu s1, s2, s3; //结构体变量的创建s1,s2,s3
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    //匿名结构体不能直接创建变量
    struct
    {
    	char a;
    	int b;
    	char c;
    }s = { 0 };//匿名结构体变量只能这样创建
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2:结构体变量的初始化

    #include 
    struct stu
    {
    	char name[20];
    	int age;
    	float score;
    }s2 = { "lisi",21,89.5f };//结构体变量的创建及初始化
    int main()
    {
    	struct stu s1 = { "zhangsan",20,85.5f };//结构体变量的创建及初始化
    	struct stu s2={.age=22,.name="wanger",.score=98.5f};
    	//特殊初始化
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    struct Stu//struct是结构体类型的关键字
        //struct Stu是用户定义的结构体类型
    {
        //name,age,score都是都是结构体成员名
        char name[20];//名字
    
        int age;//年龄
       
        int score;//分数
    
    }s;//声明的同时定义结构体变量s
    //结构体类型不占用空间,相当于盖房子时所使用的图纸
    struct Stu s = { "zhangsan",20,84 };//结构体变量的初始化
    //结构体变量占用空间,相当于盖好了的房子
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    三:结构体访问操作符

    结构体成员访问操作符有两个,一个是 . 一个是->
    形式如下:
    结构体变量.成员变量名
    结构体指针->成员变量名

    #include 
    struct stu
    {
    	char name[20];
    	int age;
    	int score;
    };
    int main()
    {
    	struct stu s = { "zhangsan",20,90 };
    	struct stu* p = &s;
    	printf("%d %d\n", s.age,s.score);
    	printf("%d %d\n", p->age, p->score);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    四:结构体内存对齐

    结构体的大小与结构体内存对齐有关。

    1:对齐规则

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

    #include 
    struct S1
    {
    	char c1;
    	char c2;
    	int a;
    };
    
    struct S2
    {
    	char c1;
    	int a;
    	char c2;
    };
    
    
    int main()
    {
    	struct S2 s2 = { 'a', 100, 'b'};
    	printf("%zd\n", sizeof(s2));
    
    	struct S1 s1 = { 'a', 'b', 100 };
    	printf("%zd\n", sizeof(s1));
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述
    在这里插入图片描述
    例2:结构体的嵌套

    #include 
    struct S3
    {
        double d;
        char c;
        int i;
    };
    
    struct S4
    {
        char c1;
        struct S3 s3;
        double d;
    };
    
    int main()
    {
        printf("%zd\n", sizeof(struct S3));//?
        printf("%zd\n", sizeof(struct S4));//?
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    在这里插入图片描述

    2:为什么存在内存对齐?

    1:平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
    2:性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于为了访问未对齐的内存,处理器需要两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作数 来读或者写值了。否则我们可能执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
    总体来说:结构体的内存对齐是拿空间来换取时间的做法。
    在设计结构体的时候,我们既要满足对齐,又要节省空间,可以让内存占用少的成员尽量放在一起。

    #include 
    struct S1
    {
    	char c1;
    	char c2;
    	int a;
    };
    
    struct S2
    {
    	char c1;
    	int a;
    	char c2;
    };
    
    
    int main()
    {
    	struct S2 s2 = { 'a', 100, 'b'};
    	printf("%zd\n", sizeof(s2));//12
    
    	struct S1 s1 = { 'a', 'b', 100 };
    	printf("%zd\n", sizeof(s1));//8两个char类型的数据放在了一起
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3:修改默认对齐数

    #pragma这个预处理命令可以改变编译器的默认对齐数。
    #pragma pack()//取消设置的默认对齐数,还原为系统默认的对齐数

    #include 
    #pragma pack(1)//将默认对齐数设置为1
    struct s
    {
    	char c1;
    	int i;
    	char c2;
    };
    
    int main()
    {
    	printf("%d ", sizeof(struct s));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    五:结构体传参

    #include 
    struct S
    {
    	int age;
    	int num;
    };
    struct S s = { 20,100 };
    void print1(struct S t)
    {
    	printf("%d\n", t.num);
    }
    void print2(struct S* ps)
    {
    	printf("%d\n", ps->num);
    }
    int main()
    {
    	print1(s);//传递的是结构体变量,也就是传值调用
    	print2(&s);//传递的是结构体变量的地址,也就是传址调用
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    传递结构体变量的时候,形参是实参的一份临时拷贝,形参会单独占用空间,传数据
    也需要时间,如果实参数据较大,那么浪费的空间和时间将非常大;如果传递的是地址,最多也就占用8个字节。
    函数传参的时候,参数需要压栈,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能的下降。

    六:结构体实现位段

    位段是基于结构体的,结构体可能浪费空间,而位段可以节省空间。位段的位指的是二进制位。

    1:位段的声明:

    #include 
    struct A
    {
    	int a : 2;//a占2个bit位
    	int b : 4;//b占4个bit位
    	int c : 7;//c占7个bit位
    };
    int main()
    {
    	printf("%d ", sizeof(struct A));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2:位段的内存分配:

    1:位段的成员可以是int ,unsigned int,signed int,char类型。
    2:位段的空间上是按照需要4个字节(int)或一个字节(char)来开辟的。
    3:位段涉及很多不确定的因素,位段是不可以跨平台的,如果想要跨平台需要使用不同的代码。(当开辟了内存后,内存中的每个bit从左向右使用还是从右向左使用,不确定;当前面的内存使用后,剩下的空间不能够放下一个成员时,剩下的空间是否使用,也不确定)。
    下面主要讲述在VS编译器下位段的内存分配。
    在VS编译器下,每个bit位从右向左使用,使用后剩余的空间不能够放下下一个元素时,该剩余空间将不再使用,需要再开辟一个字节的空间,放下一个元素。

    #include 
    struct S
    {
    	char a : 3;//开辟第一个字节,a占3个bit位,还剩5个比特位
    	char b : 4;//剩下的5个bit位能够放下b,还剩下1个bit位
    	char c : 5;//剩下的1个比特位 不能够放下c,再开辟一个新的字节放c,
    	//放c后还剩下3个字节,剩下的3个字节放不下d
    	char d : 4;//再开辟一个新的字节放d
    };
    
    int main()
    {
    	struct S s = { 0 };
    	printf("%d ", sizeof(s));//3
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3:位段的跨平台问题

    1:int位段被当成有符号数还是无符号数是不确定的。
    2:位段中最大的数目不能确定(16位机器下最大是16,32位机器下是32,写成33,在16位和32位的机器下都会报错)
    在这里插入图片描述
    3:位段中的成员在内存中是从左向右分配还是从右向左分配不确定。
    4:当一个结构包含2个位段,第二个位段的成员比较大,第一个位段剩下的空间无法容纳第二个位段时,是舍弃剩余的位还是使用,这是不确定的。
    总之:位段可以节省空间,但有跨平台问题,想要解决跨平台问题,需要写不同的代码

    4:位段的应用

    在网络协议中,IP数据报的格式,其中很多的属性只需几个bit位就能够描述,在这里使用位段,可以节省很多空间,网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。
    在这里插入图片描述

    5:位段的使用注意事项:

    内存中一个字节有一个编号(地址),位段可能几个成员共用一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置是没有地址的。所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能先输入到一个变量里,然后赋值给位段的成员。
    在这里插入图片描述

    #include 
    struct S
    {
    	char a : 3;
    	char b : 5;
    	int c : 7;
    };
    int main()
    {
    	struct S s = { 0 };
    	int c = 0;
    	//scanf("%d", &s.c);
    	scanf("%d", &c);
    	s.c = c;
    	printf("c=%d ", s.c);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    一种文件切片上传策略
    python-opencv之图像滤波(blur,boxFilter,GaussianBlur,medianBlur)
    DAM Finance获得来自Arrington和Moonbeam生态增长基金的战略投资
    async-validator.js数据校验器
    flutter系列之:flutter中的变形金刚Transform
    AN动画基础——父子级关系
    AI虚拟主播开发实战(附源码)
    for..of的用法
    qt day 5
    Maven工程打jar包的N种方式
  • 原文地址:https://blog.csdn.net/2302_77978695/article/details/132945487