C语言_自定义类型知识总结

目录:

1、结构体

(1)结构体的声明
(2)结构体成员的访问
(3)结构体的初始化
(4)结构体内存对齐
(5)位段,位段大小的计算

2、枚举

3、联合

一、结构体

C语言允许用户自己指定这样一种数据结构,它由不同类型的数据组合成一个整体,以便引用,这些组合在一个整体中的数据是互相联系的,这样的数据结构称为结构体.

结构是一些值的集合,这些值成为成员变量,结构体每个成员可以是不同类型的变量。

1、结构体声明:

在声明结构体时, 必须列出它所包含的所有成员, 这个列表必须包括每个成员的类型和名字。

        struct tag//tag表示结构体的名字
        {
            member_list;//结构体成员
        }variable_list;//结构体变量

例如:结构体描述一个学生

        struct stu
        {
            char name[20];
            int age;
            char sex[5];
            char id[20];
        } ;//注意:此处分号不能丢

下面对结构体声明的语法进行解释:
这里有几个例子:

struct 
{
    int a;
    char b;
    float c;
}x;
//这个声明创建了一个名叫x的变量, 它包含三个成员,
// 一个整数, 一个字符和一个浮点数
struct 
{
    int a;
    char b;
    float c;
}y[20], *z;
//这个声明创建了y和z。
//y是一个数组,它包含了20个结构。
//z是一个指针,它指向这个类型的结构

警告:
这两个声明被编译器当做两个完全不同的类型,即使他们成员列表完全相同。因此变量y和z的类型和x的类型不同,所以下面这条语句:

            z = &x;

是非法的。

但是这是不是意味着某种特定类型的所有结构都必须使用一个单独的声明来创建呢?
事实并非如此。标签tag允许为成员列表提供一个名字,这样他就可以在后续的声明中使用。标签允许多个声明使用同一个成员列表,并且创建同一种类型的结构,看下面这个例子:

    struct SIMPLE
    {
        int a;
        char b;
        float c;
    };

这个声明把标签SIMPLE和成员列表联系在一起,该声明并没有提供变量列表,所以它并没有创建任何变量。
注意: 标签只是标识了一种模式,用于声明未来的变量,但无论是标签还是模式,其本身都不是变量。

struct SIMPLE x;
struct SIMPLE y[20], *z;

这些是用标签定义的结构体变量,它创建的变量和之前创建x,y,z变量一样,但存在一个重要的区别——现在的x, y, z是同一个结构体类型的变量。

声明结构体时还可以使用另一种技巧——用typedef创建一种新的类型,看下面的例子:

        typedef struct 
        {
            int a;
            char b;
            float c;
        }simple;

这种声明和声明一个结构体标签效果几乎相同。区别就在于 simple 现在是一个类型名,而不是一个标签,所以后续的声明可以向下面这样:

        simple x;
        simple y[20], *z;

提示: 如果你想在多个原文件中使用同一种类型的结构,你应该把标签声明或typedef形式的声明放在一个头文件中,当源文件需要这个声明时可以使用 #include ” “指令把那个头文件包含将来。

2、结构体成员的访问:
(1)、结构体变量访问成员: 结构体变量是通过(.)操作符访问成员的。
例如:
这里写图片描述
用变量s访问成员:

            strcpy(s.name, "zhangsan");//使用 . 访问name

(2)结构体指针访问结构体成员:

        struct S
        {
            char name[20];
            int age;
        };
        //……
        void print(struct S *ps)
        {
            printf ("name = %s, age = %d", (*ps).name, (*ps).age);
            printf ("name = %s, age = %d", ps->name, ps->age);
        }

3、结构体变量的初始化:
结构体的初始化方式和数组的初始化方式相似。一个位于一对花括号内部,由逗号分隔的初始值列表可用于结构体各个成员的初始化,这些值根据结构体成员给出的顺序写出,如果初始列表的值不够,剩下的结构体成员将使用缺省值进行初始化。例如:

struct aaa
{
    int x;
    int y;
};
//……
struct aaa a = {1, 2};
printf ("%d %d\n", a.x, a.y);

如果结构体中包含数组或结构体成员,其初始化方式类似于多维数组的初始化,一个完整的聚合类型成员的初始值列表课嵌套于结构体的初始值列表内部。例如:

#include <stdio.h>
#include <stdlib.h>
struct bbb
{
    char name[20];
    int num;
};
struct aaa
{
    int x;
    int y;
    struct bbb z;
};
int main()
{
    struct aaa a = {1, 2, {"哈哈", 5}};
    printf ("%d %d %s %d\n", a.x, a.y, a.z.name, a.z.num);
    system ("pause");
    return 0;
}

这里写图片描述

4、结构体内存对齐
之前我们已经掌握了结构体的基本使用了,现在我们来深入了解一下:结构体的大小
先看下面一段代码:

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

struct S1
{
    char a1;
    int a;
    char a2;
};
struct S2
{
    char b1;
    char b2;
    int b;
};

int main()
{
    printf ("sizeof (struct S1) = %d\n", sizeof (struct S1));
    printf ("sizeof (struct S2) = %d\n", sizeof (struct S2));

    system ("pause");
    return 0;
}

运行结果:
这里写图片描述

为什么结构体成员的类型都是一样的,只是换了一下顺序,大小就不一样了呢???

这就要涉及到结构体内存对齐的问题了!!!

那么结构体大小又该如何计算呢?

首先我们来看看对其规则:

(1)、第一个成员在与结构体变量偏移量为0的地址处;
(2)、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小 两者中的较小值。(vs中默认对齐数为8, Linux中默认对齐数为4)
(3)、结构体总大小为最大对齐数(每个成员变量都有一个对齐数,其中最大的一个就是最大对齐数)的整数倍。
(4)、如果嵌套了结构体的情况, 嵌套的结构体对齐到自己最大对齐数的整数倍地址上,结构体的整体大小就是所有最大对齐数(包含结构体的对齐数)的整数倍。

了解了这些规则,就可以算一下上面两个结构体的大小了:
这里写图片描述
为什么会存在内存对齐呢???

(1)、平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常;
(2)、性能原因:
数据结构(尤其是栈)应该尽可能的在自认边界上对齐,原因在于:为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存仅需要一次访问。

总体来说:内存对齐是拿空间换取时间的做法。那么在设计结构体的时候,既要满足内存对齐,又要节省空间,该如何做到???

应该尽量让占空间小的成员集中在一起

5、位段,位段大小的计算

在有些时候,我们给结构体一个int型的成员,这个成员只需要占用几个比特位,但是一个int型变量占32个比特位,这样的话就会造成大量的空间浪费,因此就需要用到位段。

位段的声明和结构体类似,但它的成员是一个或多个位的字段。这些不同啊长度的字段实际存储于一个或多个整形变量中。

位段的声明和任何普通的结构体成员相同, 但有两个例外:(1)是成员必须是int, signed int , 或unsigned int类型;(2)其成员名后面是一个冒号和一个整数,这个整数指定该位段占用位的数目。
比如:

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

那位段
A的大小是多少呢???

        printf ("sizeof (struct A) = %d\n", sizeof (struct A));
//结果是:
//sizeof (struct A) = 8
//请按任意键继续. . .

位段的内存分配:

(1)位段的成员可以是int, signed int, unsigned int或者是char(属于整形家族)类型;
(2)位段的空间上是按照需要以四个字节(int)或者一个字节(char)的方式来开辟的;
(3)以 上面那个位段的例子来说:它先开辟一个int的空间(4个字节,32个比特位)。a:2—>2个比特位,还剩30个比特位;b:5—>5个比特位,还剩25个比特位;c:10—>占10个比特位,还剩15个比特位;c:30要占30个比特位,前面的15个不够用,浪费掉这15个重新开辟一个int的空间再占用新空间的30个比特位,所以结果是两个int类型,占八个字节;

(4)位段设计很多不确定因素,位段是不跨平台的,注重可移植程序应该避免使用位段

位段的跨平台问题:

(1)、int型位段被当做有符号数还剩无符号数不确定(由编译器决定)
(2)、位段中最大位的数目不确定(16位机器最大是16,32位机器最大是32,如果写成17,则在16位的机器上会出现问题)
(3)、位段中的成员在内存中从右向左分配还是从左向右分配C语言标准尚未定义
(4)、当一个结构体中包含两个位段,第二个位段比较大,无法容纳于第一个位段剩余的位时是舍弃剩余的还是利用,这也不确定。

总结:

跟结构体相比,位段可以达到同样的效果,还可以很好的节省空间,但是跨平台还存在问题。

二,枚举

枚举:顾名思义就是一一列举(把可能的取值一一列举)
比如:一周的星期一到星期天是7天,可以一一列举

枚举类型的定义:

    enum  day
    {
        Mon,
        Tues,
        wed,
        Thur,
        Fir,
        sat,
        sun
    };//{}里面的是枚举类型的可能取值,也叫枚举常量。它们默认是从0开始,依次递增1;

也可以在定义的时候顺便赋值:

    enum  color
    {
        red = 1,
        green = 6;
        biue = 7;
    };

枚举的优点:

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

3、联合

联合:也是一种自定义类型,它也包含一系列成员,它的特征是这些成员共用同一块内存空间,所以也叫共用体。

联合的声明:

    union Un
    {
        char c;
        int a;
    };

//联合变量的定义

    union Un un;

//计算联合变量的大小

    printf ("%d\n", sizeof (un));

联合的特点:

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

联合大小的计算:

(1)联合的大小至少是最大成员的大小
(2)当最大成员大小不是最大成员大小的整数倍时,就要对齐到最大对齐数的整数倍
比如:

    union Un1
    {
        char c[5];//对齐数是一
        int i;//对齐数是4
    };
    printf ("%d\n", sizeof (union Un1));
//运行结果:
//8
//请按任意键继续. . .
    union Un2
    {
        short c[7];//对齐数是2
        int i;//对齐数是4
    };
    printf ("%d\n", sizeof (union Un2));
//运行结果:
//16
//请按任意键继续. . .

最后,新人操作,如果有写的不对的地方,希望大家提出来。

猜你喜欢

转载自blog.csdn.net/A__B__C__/article/details/80609909