C语言:自定义类型详解

编辑: admin 分类: c#语言 发布时间: 2021-12-12 来源:互联网
目录
  • 一、结构体
    • 1.结构体变量的定义及初始化
    • 2.结构体内存对齐
    • 3.为什么要内存对齐呢?
  • 二、位段
    • 1.什么是位段
    • 2.位段的内存分配
  • 三、枚举
    • 1.枚举的定义
    • 2.枚举的优点
  • 四、联合(共用体)
    • 1.联合类型的定义
    • 2.联合的特点
    • 3.联合大小的计算
  • 总结

    一、结构体

    1.结构体变量的定义及初始化

    直接上代码:

    struct Point {
    	int x;
    	int y;
    }p1;     //创建结构体时顺便创建变量,分号一定不能掉
    struct Point p2;    //单独创建变量
    struct Point p3 = { 1,2 };   //创建变量时顺便赋值
    struct Node {
    	char str[20];
    	struct Point p;     //结构体嵌套
    }n1 = { "abcd",{3,4} };
    int main() {
    	printf("%s\n", n1.str);   //结构体访问时,用.或者->,变量访问用.,指针访问用->
    	printf("%d\n", n1.p.x);
    	printf("%d\n", n1.p.y);
    	return 0;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5K0M4H1-1632400370757)(C:\Users\10371\AppData\Roaming\Typora\typora-user-images\image-20210923190038919.png)]

    struct是创建结构体的关键字,Point是结构体的名字,p1为结构体Point的一个变量,x,y称为结构体Point中的成员变量,变量的创建有两种形式,第一,可以在创建结构体时一起创建,第二,单独创建,创建规则为类型+名称,对于结构体的赋值,可以在创建变量的时候顺便赋值,可以先创建变量再单独赋值。结构体的访问有两种方式,当使用变量进行访问时,用.(点),再选择该变量对应的属性;当使用指针进行访问时,用->,再选择对应的属性即可。

    2.结构体内存对齐

    当我们想去计算结构体占内存大小的时候,就需要知道结构图内存对齐这一概念,我们先来看两个例子:

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9hpNrap-1632400370761)(C:\Users\10371\AppData\Roaming\Typora\typora-user-images\image-20210923190325486.png)]

    结果表明,A和B两个结构体所占内存大小并不相等,但是其内部成员变量是一样的,只是顺序不一样。造成结果不同的原因就是因为内存对齐,我们来介绍结构体内存对齐的规则:

    1.第一个成员在与结构体变量偏移量为0的地址处。

    其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。在VS编译器中,默认对齐数是8。

    结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

    如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

    3.为什么要内存对齐呢?

    从上面的结果我们大概就能猜到,为了节省空间。总的来说,主要原因有两点:

    1.平台原因:

    某些硬件平台只能在特定的地址处取地址,没有内存对齐,取值时可能会出错。

    2.性能原因:

    对于不对齐的情况,在读取数据时可能要读取两次,以下图为例:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jchW9qWz-1632400370763)(C:\Users\10371\AppData\Roaming\Typora\typora-user-images\image-20210923192716935.png)]

    在一个32位平台上,在不对齐的情况下,如果想要读取int的4个字节,第一次会读取到char的一个字节,和int的后3个字节(小端),需要再读取一次,才能将int的4个字节完全读取出来;相比较而言,如果是在内存对齐的情况下,只需要读取一次就可以把int的4个直接读取出来。

    二、位段

    结构体还有实现位段的能力,问题来了,什么是位段呢?

    1.什么是位段

    位段的声明和结构体是类似的,但有两点不同:

    • 位段的成员必须是 int、unsigned int 或signed int 。
    • 位段的成员名后边有一个冒号和一个数字。

    例如:

    struct A
    {
     int _a:2;
     int _b:5;
     int _c:3;
     int _d:4;
    };
    

    A就是一个位段类型,想要知道A的大小,同样可以用sizeof来求。

    2.位段的内存分配

    拿上面的位段A来说,会先在内存中开辟一个4字节的空间,冒号后面的数字表示该成员变量所占内存的大小,单位为bit,位段中的成员在内存中从左向右分配, 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。总的来说,跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。光说不太好理解,我们来看下图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ey3DDX7A-1632400370764)(C:\Users\10371\AppData\Roaming\Typora\typora-user-images\image-20210923200518727.png)]

    当然,位段虽然比结构体更节省内存,但其存在跨平台问题,需要谨慎使用。

    三、枚举

    1.枚举的定义

    enum Day//星期
    {
     Mon,  //默认情况下Mon值为0,后面的成员变量的值依次递增
     Tues,
     Wed,
     Thur,
     Fri,
     Sat,
     Sun
    };
    

    当然也可以在定义的时候赋值,例如:

    enum Color//颜色
    {
     RED=1,
     GREEN=2,
     BLUE=4
    };
    

    2.枚举的优点

    我们知道,#define可以定义常量,那为什么要用枚举呢?

    枚举的优点:

    1.增加代码的可读性和可维护性

    2.和#define定义的标识符比较枚举有类型检查,更加严谨。

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

    既然存在,就有其存在的道理,有些时候#define更方便,有时候枚举更方便,我们要学会合理使用

    四、联合(共用体)

    1.联合类型的定义

    union Un  //声明
    {
     char c;
     int i;
    };
    union Un un;  //定义变量
    printf("%d\n", sizeof(un));  //计算共用体的大小
    

    2.联合的特点

    联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为 联合至少得有能力保存最大的那个成员)。

    union Un
    {
     int i;
     char c;
    };
    union Un un;
    // 下面输出的结果是一样的吗?
    printf("%d\n", &(un.i));
    printf("%d\n", &(un.c));
    //下面输出的结果是什么?
    un.i = 0x11223344;
    un.c = 0x55;
    printf("%x\n", un.i);
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6pp4XA6e-1632400370765)(C:\Users\10371\AppData\Roaming\Typora\typora-user-images\image-20210923202148509.png)]

    从结果可以看出,i和c的地址是一样的,因为他们共用一块空间,当分别给i、c赋值时,后赋值的c会覆盖之前i的部分值。

    3.联合大小的计算

    • 联合的大小至少是最大成员的大小。
    • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

    例如:

    union Un1
    {
     char c[5];
     int i;
    };
    union Un2
    {
     short c[7];
     int i;
    };
    //下面输出的结果是什么?
    printf("%d\n", sizeof(union Un1));  //8,c占5个字节,比i大,最大对齐数位4,需要为4的倍数,所以为8
    printf("%d\n", sizeof(union Un2));  //16,c占14个字节,最大对齐数为4,所以为16
    

    总结

    本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注海外IDC网的更多内容!

    【自由互联:韩国服务器 转载请保留连接】