自定义类型相关知识剖析

1.结构体

结构体: C语言允许用户创建由不同类型数据组成的数据结构,它称为结构体。

  • 结构体类型的创建
    直接上代码
struct Stu
{
    char name[20];
    int age;
    char sex[5];
};

上面就定义了一个名为Stu的结构体,里面的成员有姓名、年龄和性别。这些成员可以是不同类型的变量。

  • 定义结构体变量
    1.先声明结构体类型,再定义该类型的变量
    举个例子吧!
#include<stdio.h>
#include<stdlib.h>
struct Stu
{
    char name[20];
    int age;
    char sex;
    char addr[30];
};
int main()
{
    struct Stu s;
    system("pause");
    return 0;
}

这里先声明了一个名为struct Stu类型的的结构体,然后用这个结构体类型名创建结构体变量s。

2.在声明类型的同时定义变量
例如:

struct student
{
    char name[20];
    char sex[5];
    int age;
    float score;
}s1,s2;

这里声明student这个结构体类型的时候,直接在后面定义了结构体变量s1,s2。注意:大括号后面的分号不能丢掉。
3.不指定类型名而直接定义结构体类型变量
例如:

struct
{
    char name[20];
    char sex[5];
    int age;
    float score;
}s1,s2;

这里指定了一个无名的结构体类型(匿名结构体)。这个时候不能用这个结构体类型去直接定义变量,因此在声明的时候直接定义结构体类型变量。
那这些成员放在结构体里面,譬如想访问其中一个成员的时候,怎么访问呢?

  • 结构体变量的初始化及引用
    1.在定义结构体变量时可以对它的成员初始化
    举个例子:
#include<stdio.h>
#include<stdlib.h>
struct Stu
{
    char name[20];
    int age;
    char sex[5];

};
struct
{
    char name[20];
    char sex[5];
    int age;
    float score;
}s1 = {"liming","男",22,78.5};
int main()
{
    struct Stu s = {"HelloKitty",18,"女"};
    system("pause");
    return 0;
}

2.也可以引用结构体变量中成员的值,引用的方式为
结构体变量名.成员名
举个例子:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
    char name[20];
    int age;
    char sex[5];

};
int main()
{
    struct Stu s;
    strcpy(s.name, "hellokitty");
    system("pause");
    return 0;
}

有时候我们要用一个指针去访问结构体成员,这个时候该如何访问呢?
举个例子:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
    char name[20];
    int age;
}*p;

int main()
{
    printf("name=%s age=%d\n", (*p).name, (*p).age);
    printf("name=%s age=%d\n", p->name, p->age);
    system("pause");
    return 0;
}
  • 结构体的自引用
    结构体可以自己引用自己吗?也就是说该结构体中的成员可以是自己吗?
    举个例子:
struct Node
{
    int data;
    struct Node*next;
};

这说明结构体是可以自引用的,但是在自引用的时候必须是以指针的形式。

  • 结构体的内存对齐
    结构体的内存对齐是一个非常重要的知识点。
    下面我们来看一个问题:
struct S
{
    char c1;
    int i;
    char c2;
};
printf("%d\n",sizeof(struct S));

这里输出的结果是什么?是6吗?实际上并不是我们所想的6,而是12。这就牵扯到结构体的内存对齐了。

  • 什么是内存对齐?为什么存在内存对齐?
    当一个结构体变量定义完之后,其在内存中的存储并不等于其所包含的所有元素宽度之和。这就是因为存在内存对齐。
    存在内存对齐的原因:

    1. 平台原因(移植原因)
      不是所有的硬件平台都能访问任意地址上的数据的,某些硬件平台只能在某些地址处取特定类型的数据,否则会出现硬件异常。

    2. 性能原因
      数据结构(尤其是栈)应该尽可能的在自然边界上对齐。
      原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而内存对齐访问仅仅需要一次访问。
      下面我们画图来仔细分析一下上面代码中结构体在内存中的存储方式:
      这里写图片描述

    这里这个结构体在内存中的存储,浪费了一些空间,这样做的目的是为了提高访问时的速度,也就是说结构体的内存对齐是用空间来换取时间的方法。
    结构体的对齐规则:

    1.结构体的第一个成员存放在与结构体变量偏移量为0的地址处
    
    2.结构体的其他成员要对齐到对齐数的整数倍的地址处。对齐数:编译器默认的一个对齐数与该成员大小的较小值。VS中默认的值为8,Linux中默认的值为4。
    
    3.结构体的总大小为最大对齐数(所有成员对齐数中较大的值)的整数倍。
    
    4.如果一个结构体中嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的大小就是所有最大对齐数(包括嵌套结构体的对齐数)的整数倍。
    

2.位段

什么是位段?
位段和结构体的结构和声明都是类似的,但存在两个不同之处:

  1. 位段的成员必须是int、unsigned int、char或者signed int;
  2. 位段的成员名后面有一个冒号和数字
    举个例子:
#include<stdio.h>
#include<stdlib.h>
struct B
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};
int main()
{
    printf("%d\n", sizeof(struct B));
    system("pause");
    return 0;
}

这里输出的这个位段的大小是多少呢?其实是4个字节。我们来画图具体分析一下:

这里写图片描述

总结:位段的内存分配

  1. 位段的成员可以是int 、unsigned int、signed int和char类型的
  2. 位段的空间上是按照需求以4个字节(int)或者一个字节(char)的方式来开辟的。
  3. 位段涉及很多不确定的因素,位段是不跨平台的。
    知道了位段的内存分配之后我们再来研究一下位段的填充方式:
    举个例子:
#include<stdio.h>
#include<stdlib.h>
struct B
{
    char a : 3;
    char b : 4;
    char c : 5;
    char d : 4;
};
int main()
{
    struct B s = { 0 };
    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;
    system("pause");
    return 0;
}

这里的空间是如何开辟的呢?
我们来画图分析一下:
这里写图片描述

那在内存中是不是我们所想的这种存储方式呢?我们调试该程序,在内存窗口查看一下,如下图所示:
这里写图片描述
事实证明就如我们所想的一样。

  • 位段的跨平台问题

1.int位段被当做有符号数还是无符号数是不确定的
2.位段中最大位的数目不确定。(16位机器最大16,32位机器最大32)
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4.当一个结构体包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位是,是舍弃剩余的位还是利用,这个是无法确定的。


3.枚举

所谓枚举是指将变量的值一一列举出来,变量只限于列举出来的值的范围内取值。 定义一个变量是枚举类型,可以先定义一个枚举类型名,然后再一一列出它的可能取值。
举个例子:

#include<stdio.h>
#include<stdlib.h>
enum sex
{
    MALE,
    FEMALE,
    SECRET
};
int main()
{
    printf("%d\n", MALE);
    printf("%d\n", FEMALE);
    printf("%d\n", SECRET);
    system("pause");
    return 0;
}

这里定义了一个enum sex类型的枚举类型,里面的类型是枚举类型的可能取值,也叫枚举常量。这里输出的是什么呢?
这里写图片描述

从输出来看这些枚举常量都是有值的,默认从0开始,逐一递增,也可以在定义的时候直接赋初值。
我们在定义常量的时候可以使用#define,为什么还要使用枚举呢?
我们来看看枚举相较于#define的优点:

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符相比,枚举具有类型检查,更加严谨
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

4.联合(共用体)

union,中文名“联合体、共用体”,在某种程度上类似结构体的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。联合的声明和联合成员的引用和结构体都是类似的。
但是联合体的成员在存放时是共用同一块空间的,节省了内存空间。
举个例子:

#include<stdio.h>
#include<stdlib.h>
union Un
{
    char c;
    int i;
};
int main()
{
    union Un un;
    printf("%d\n", sizeof(un));
    system("pause");
    return 0;
}

这里输出是多少呢?我们都说了联合的成员是共用同一块空间的,这样一个联合体变量的大小,至少是最大成员的大小。那这里输出的应该是4。
这里写图片描述
但是一个联合变量的大小也不一定是最大成员的大小
举个例子:

#include<stdio.h>
#include<stdlib.h>
union Un
{
    char c[5];
    int i;
};
int main()
{
    union Un un;
    printf("%d\n", sizeof(un));
    system("pause");
    return 0;
}

这里输出的是什么呢?是4吗?
这里写图片描述
这里存放了一个char类型的数组,我们可以将它看成是5个char类型的元素,放到联合体中。我们先看一下联合大小的计算规则:

  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
    现在我们来分心一下上面的例子,这里最大成员是这个char类型的数组,大小为5,这个char类型的数组对齐数为1,那这里的最大对齐数是4,5不是4的整数倍,所以要对齐到4的整数倍,所以输出的是8。

  • 联合和大小端
    当我们判断当前计算机大大小端存储时,可以用指针的方法来做也可以用联合来实现一个程序。如下:

#include<stdio.h>
#include<stdlib.h>

int check_sys()
{
    union Un
    {
        char c;
        int i;
    }un;
    un.i = 1;
    return un.c;
}
int main()
{
    int ret = check_sys();
    if (ret == 1)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    system("pause");
    return 0;
}

这里写图片描述

这里输出的是小端说明我的计算机是以小端的方式存储的。

猜你喜欢

转载自blog.csdn.net/liuwenjuan_cherry/article/details/80247104
今日推荐