(C语言之路-----p10:自定义类型详解结构体,枚举,联合)

(C语言之路-----p10:自定义类型详解:结构体,枚举,联合)

对于这篇博客的所有的自定义类型,除了枚举类型以外,其他的类型要访问函数可以通过. 或者 -> 访问, .操作符可以直接访问自定义类型, 而 ->是通过自定义类型的指针来访问成员

一.结构体

结构体相对于整型,浮点型,指针这些单一的类型来说,他具有多个属性,他可以只含有以上的其中一个或者多个类型的属性,也可以是其他类型(结构体类型)的属性.

1.结构体类型的声明

struct tag
{
    member-list;//成员变量
};//表示我们已经向编译器说明了struct tag是一种变量,这个变量有若干个属性,这些属性就写在成员变量中

例如:

struct Stu
{
    char name[20];
    int age;
    double height, weight, score;
};//我们声明了一种类型是Stu,有name, age, height...属性

注意:1.在C语言中,结构体的成员不能是函数

2.结构体的成员不能是结构体本身(不包括指向自身的指针), 因为这样结构体的大小无法确定

3.结构体的成员可以是其他结构体,不过一般是指向其他结构体的指针,因为指针大小通常是4/8个字节,结构体的大小通常大于指针大小

2.结构体的实例化

如果把结构体的声明比作一张设计图纸,那么创建这种结构体的变量(即实例化)就是根据图纸建造建筑物的过程.

//第一种创建变量方式:在声明结构体类型的同时创建结构体变量
struct Stu
{
	char name[20];
    int age;
    double height, weight, score;
}xiaoming, grade3[100];
//第二种创建变量方式:创建变量与声明类型分开
struct Stu zhangsan = {0};
struct Stu Lisi = {"LISI", 18, 183.0, 63.0, 578.0};

3.特殊的实例化

在声明结构的时候,可以不完全地声明

struct 
{
    int a;
    long b;
    double c;
}x;
//此时创建了一个名为x的变量,类型是struct{...}类型,注意x是变量名,而不是类型名,这个类型没有名字,也就是说是匿名的,这种匿名的结构体类型只能被使用一次.所以对于只使用一次的结构体类型,我们给他声明成匿名即可
struct 
{
    int a;
    long b;
    double c;
}*p;

p = &x;//这条语句其实是不合法的,因为两个结构体类型不一致,就算成员相同,但由于匿名声明,所以类型不一致.

4.结构体内存对齐

我们知道系统内置数据类型都有固定的大小,那么对于自定义类型来说是否也是如此呢?

现在我们深入讨论一个问题:结构体的大小该如何计算?

这就涉及结构体内存对齐了.

我们先介绍一下对齐数的概念.

对齐数 = 编译器默认的对齐数该成员大小较小值

内存对齐的规则:

1.第一个成员在结构体变量偏移量为0处

2.其他成员变量对齐至对齐数的整数倍处

3.如果成员包含结构体,成员结构体对齐至自身的最大对齐数处.

4.结构体的大小须为自身最大对齐数的整数倍

#include <stdio.h>

int main()
{
    struct S1
    {
        char c1;
        int i;
        char c2;
    };
    printf("%d\n", sizeof(struct S1));//12

    struct S2
    {
        char c1;
        char c2;
        int i;
    };
    printf("%d\n", sizeof(struct S2));//8

    struct S3
    {
        double d;   //8
        char c;     //1
        int i;      //4
    };
    printf("%d\n", sizeof(struct S3));//16

    struct S4
    {
        char c1;        //1
        struct S3 s3;   //16
        double d;       //8
    };
    printf("%d\n", sizeof(struct S4));//32
}

image-20220128105614305

内存对齐存在的意义:

1.可移植性问题;

不是所有的硬件平台上都可以任意访问地址上的任意数据,某些硬件平台上只能在某些地址处取特定类型的数据,否则会出现硬件异常

2.性能问题

数据结构(特别是栈)应该尽可能地在自然边界上对齐.

因为为了访问未对齐的内存,处理器需要进行两次访问;而对齐的内存只需进行一次访问

在知道了内存对齐的规则之后,我们怎么才能尽可能地节约空间呢?
答案是:让尽可能多的占用空间少的成员集中在一块.

除了这个方法,我们还可以通过修改默认对齐数的方法来节约空间

vs中,对齐数默认是8,我们可以用#pragma预处理指令,改变默认对齐数.

#include <stdio.h>
#pragma pack(8) //设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()  //取消设置的默认对齐数,还原为默认
#pragma pack(1) //设置默认对齐数为1
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));//12
    printf("%d\n", sizeof(struct S2));//6
}

image-20220128111756210

5.结构体传参

#include <stdio.h>

struct S
{
    int data[1000];
    int num;
};

struct S s = {{1, 2, 3, 4}, 1000};
//结构体传参
void print1(struct S s)
{
    printf("%d\n", s.num);
}

//结构体地址传参
void print2(struct S *ps)
{
    printf("%d\n", ps->num);
}

int main()
{
    print1(s);  //传结构体
    print2(&s); //传地址
    return 0;
}

image-20220128112743747

上面的print1和print2哪个会更好一些?

那必然是print2

如果传参时传一整个结构体,那么形参在接受的时候就要在栈区里开辟很大的空间,而栈区空间是有限的,而传结构体指针形参接受时顶多开辟8个字节的空间,大大减少了空间浪费.其实数组传参也是同样的道理,如果数组过大,那么传整个数组过去时就容易爆栈,所以编译器就只会传递数组的地址.

二.位段

1.啥是位段

位段是由结构体实现的,位段相对于结构体来说可以更好地节约空间.

1.位段的成员必须是char, int , unsigned int, signed int

2.位段的成员名后面有一个冒号和一个数字

struct A
{
	int a:2;
    int b:5;
    int c:10;
    int d:30;
};

那么,既然位段是由结构体实现的,那么是否也存在内存对齐呢?

image-20220128115000829

可以看到,位段并不存在跟结构体一样的内存对齐.

2.位段的内存分配

位段的空间上是按照需求,以4个字节或者1个字节的方式来开辟的

以上面的代码为例子,a,b可以放在第一个开辟的int大小的空间中,(注意:冒号后面的数字表示需要的二进制位)剩下1个二进制位,已经放不下c了,所以c又开辟两个int大小的空间,剩下的空间不够放d,d又开辟4个int大小的空间,全部加起来就是8个字节的大小.

3.位段的跨平台问题

1.int位段被当成有符号数还是无符号数是不确定的.

2.位段中int大小不同(16位平台大小是16,32位平台是32)

3.位段中的成员在内存中从左向右还是从右向左分配不确定

4.当一个单位空间被开辟后没有完全被使用,剩下没有用的位是舍弃还是利用,C语言标准并未规定

4.位段的实际应用

虽然位段有跨平台的问题,但是这并不妨碍我们利用他节约空间.

位段的一个实际用途就是数据在网络上的运输,网络上的每一条数据都有一条属于他自己的"身份证",这些信息就由位段存储,如果只是用普通的结构体储存这些信息,那么网络上所有的信息加起来就会浪费很多的空间,由于网络运输就像是在高速公路上驾车,当每辆车更大,那么就更容易出现堵塞,也就是造成网络运输问题.

image-20220128120609023

三.枚举

枚举就是将一个整体的所有可能的情况聚集,然后用整型表达这些可能的情况

1.枚举类型的声明和实例化

#include <stdio.h>

//类型的声明
enum Day
{
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun
};

enum Sex
{
    Male,   //默认从0开始,默认往后每个成员递增1
    Female,
    Secret
};

enum Color
{
    Red = 1,    //可以修改默认值
    Blue = 2,
    Green = 4
};

int main()
{
    //实例化
    Day _1 = Mon;
    Day _2 = Tues;
    Day _3 = Wed;
    //...
}

image-20220128124639813

2.枚举的优点

很多人其实学了枚举类型以后,不知道有什么用,这不就是一个把一个名字替换成一个值吗?直接用#define预处理指令不就得了?

接下来,我们就比较枚举和#define,看一下枚举类型相对于#define来说有哪些好处

1.**枚举类型有类型检查,**相对于#define来说更加安全(后者是没有类型检查的, 所以就算你写出#define int long long这种看似离谱的代码, 编译器也不会报错, 仍然会真的把所有的int (声明为int的数据类型)替换成 long long)

2.防止命名污染.枚举类型具有封装的作用,就算两个相同的名字,也可以赋予其不同的值,而对于#define来说,要做到这件事就比较困难.

3.便于调试.在调试时,#define是预处理指令,所以在调试前的编译阶段会直接替换成替换值,有时候不利于调试,而枚举类型则是在调试运行时才替换的.

4.一次可以定义多个常量,使用方便

三.联合(共用体)

联合就是多个相同或者不同类型的数据共用一块相同的空间,在修改其中一个数据时,有可能会波及到另外一个数据

1.联合类型的声明和实例化

#include <stdio.h>

//声明
union Un
{
    char c;
    int b;
};

int main()
{
    //实例化
    Un un;
    un.c = 0x55;
    un.b = 0x11223344;
    printf("%x %x", un.c, un.b);
}

image-20220128131344251

利用联合来判断机器的大小端储存模式:

#include <stdio.h>

int check()
{
    union Un
    {
        int a;
        char b;
    } un;

    un.a = 1;
    return (int)un.b;
}
int main()
{
    if(check())
        puts("小端存储");
    else
        puts("大端存储");
}

image-20220128132156357

2.联合大小计算

1.联合的大小至少是最大成员的大小

2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍(注意,在数组计算对齐数的时候,是按每个元素的大小来计算的)

#include <stdio.h>

int main()
{
    union Un1
    {
        char c[5];  //1
        int i;      //4
    };
    union Un2
    {
        short c[7]; //2
        int i;      //4
    };
    //下面输出的结果是什么?
    printf("%d\n", sizeof(union Un1));//8
    printf("%d\n", sizeof(union Un2));//16
}

2.联合大小计算

1.联合的大小至少是最大成员的大小

2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍(注意,在数组计算对齐数的时候,是按每个元素的大小来计算的)

#include <stdio.h>

int main()
{
    union Un1
    {
        char c[5];  //1
        int i;      //4
    };
    union Un2
    {
        short c[7]; //2
        int i;      //4
    };
    //下面输出的结果是什么?
    printf("%d\n", sizeof(union Un1));//8
    printf("%d\n", sizeof(union Un2));//16
}

image-20220128132814742

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值