欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【C语言总结】结构、联合、位段和枚举

程序员文章站 2024-03-22 10:58:58
...

1. 结构基础知识

聚合数据类型能够同时存储超过一个的单独数据。

C语言提供了两种类型的聚合数据类型,数组和结构

数组是相同元素的集合,它的每个元素是通过下标引用或指针间接访问来选择的。

结构也是一些值的集合,这些值称之为它的成员,但一个结构的各个成员可能具有不同的类型

数组元素可以通过下表访问,因为数组元素的元素长度相同。但是结构中,由于成员可能长度不同,所以不能使用下标来访问。结构成员是通过名字访问的

结构变量属于标量类型,结构也可以作为传递给函数的参数,还可以作为返回值从函数返回,相同类型的结构变量相互之间可以复制。

1.1结构声明

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

struct tag{member-list} variable-list;

下面有几个例子:

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

这个声明创建了一个名叫x的变量,它包含三个成员:一个整数、一个字符和一个浮点数

strcut {
    int a;
    char b;
    float c;
}y[20], *z;

上面这个声明创建了y和z,y是一个数组,包含了20个结构,z是一个指针,指向这个类型的结构。

标签字段允许为成员列表提供一个名字,这样它就可以在后续的声明中使用。例:

struct student {
    char name[20];
    char id[20];
    int age;
}std;

如果需要在多个源文件中使用同一种类型的结构,应该把声明用typedef形式的声明放在一个头文件中。

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

1.2结构成员

结构成员可以是标量、数组、指针甚至是其他结构

例子:

struct Complex {
    int f;
    char b;
    float c;
    int arr[20];
    int* p;
    struct SIMPLE s;
    struct SIMPLE sa[10];
    strcut SIMPLE *sp;
}Complex;

一个结构的成员名字可以和其他结构的成员的名字相同。

1.3结构成员的直接访问

结构变量的成员是通过点操作符访问的,点操作符接受两个操作数,左操作数是结构变量的名字右操作数就是需要访问的成员的名字。这个表达式的结果就是指定的成员。

例如上面的结构体,Complex.f就是访问的是第一个结构成员

s是一个结构名,Complex.s.a就是访问结构体SIMPLE的元素a

成员sa是一个结构数组,所以Complex.sa是一个数组名,它是一个指针常量,对这个数组进行下标引用操作:Complex.sa[1],访问的是这个结构数组中的第二个结构,因为访问的是结构,所以我们也可以用另一个点操作符取得它的成员之一。如:(Complex.sa[1]).c

下标引用和点操作符具有相同的优先级,所以我们可以省略所有的括号。

1.4结构成员的间接访问

struct COMPLEX *cp;

有了上面的声明之后,我们可以用:(*cp).f来访问结构COMPLEX的元素f。

但是这样写起来很复杂,而C语言提供了箭头操作符。箭操作符也接受两个操作数,但左操作数是一个指向结构的指针,箭头操作符对左操作数执行间接访问取得指针所指向的结构,然后和点操作符一样,根据右操作数选择一个指定的结构成员,如cp->f

1.5结构的自引用

在一个结构体内还可以包含一个类型为该结构本身的成员

struct SELF1 {
    int a;
    struct SELF1 b;
    int c;
};

struct SELF2{
    int a;
    struct SELF2* b;
    int c;
};

上面的SELF1是非法的,因为b 是一个完整的结构,但是SELF2是合法的。因为第一个编译器不知道第二个成员的大小,而第二个确实知道的。

1.6结构的初始化

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

struct INIT_EX {
    int a;
    short b[10];
    Simple c;
} X = {
      10;
      {1, 2, 3, 4, 5};
      {25, 'X', 1.9}
};

2. 结构指针和成员

有这么个例子:

typedef struct Simple {
        int a;
        char b;
        float c;
}Simple, *p1;
typedef struct Complex {
        int a;
        char b[3];
        Simple c;
        struct Complex* d;
}Complex, *p2;

2.1访问指针

p1和p2是一个指针变量,用于访问这个结构体。

表达式p1 + 1,这个表达式不是一个合法的左值,因为它的值不存储于任何可标识的内存位置。如果p1是一个指向结构数组的元素,这个表达式将指向该数组的下一个结构,但是仍然是非法的,我们不知道内存的下一个位置是什么东西。

2.2访问结构

可以使用*操作符对结构指针进行间接访问。表达式*p1作为右值是p1指向结构Simple。可以把这个表达式赋值给另一个类型相同的结构,也可以把它作为点操作符的左操作数访问一个指定成员,你也可以把它作为参数传递给函数,也可以把它作为函数的返回值返回

表达*p1 + 1是非法的,因为*p1指向一个结构。(*p1 + 1)也是非法的。

2.3访问结构成员

p1->a访问的是结构Simple的成员a,->操作符是对p1执行间接访问操作。

也可以使用Simple.a来访问成员a。

2.4访问嵌套的结构

p2->c,它的左值是整个结构,这个表达式可以用点操作符访问c的特定成员,如p2->c.a

3. 结构的存储分段

结构在内存中如何存储的呢?编译器按照成员列表的顺序一个接一个的给每个成员分配内存。只有当存储成员时需要满足正确的边界对齐要求时,成员之间才可能出现用于填充的额外内存空间

例如下面的结构体:

struct ALIGN {
    char a;
    int b;
    chr c;
};

如果某个机器的真心长度为四个字节,并且它的起始存储位置必须被4整除,那么这个结构体在内存中的存储如下图所示:

【C语言总结】结构、联合、位段和枚举

系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐要求,因此所有结构的起始存储位置必须是结构中边界要求最严格的数据类型所要求的位置。

因此成员b的存储,必须到达合适的边界才能存储。

结构体的内存对齐规则:

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

2.其他成员变量要对齐到某个数字(对齐数)的整数倍地址处

3.结构体总大小为最大对其数的整数倍

4.嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对其数的整数倍。

4. 作为函数参数的结构

结构变量是一个标量,它可以用于其他标量可以使用的任何场合

把结构体作为参数传递给函数是合法的,但是这种方法往往效率比较低,所以一般可以传递一个结构体指针,指针比整个结构小很多,把它压到堆栈上效率能提高很多。

向函数传递指针的缺陷在于函数现在可以对调用程序的结构变量进行修改。所以可以使用const关键字来防止修改。当然如果你希望函数修改结构的任何成员,就应该使用指针传递修改。

5. 位段

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

位段的声明和结构成员声明基本相同,但是有两个例外,首先,位段成员必须声明为int、signed int 或 unsigned int类型。其次在成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用位的数目。

把位段用singned或unsigned 整数显示的声明比较好

使用位段的代码不能跨平台移植,由于下面这些原因,位段在不同的系统中有不同的效果:

1.int位段被当作有符号数还是无符号数

2.位段中位的最大数目在不同机器上很可能不同

3.位段中的成员在内存中是从左向右分配的还是从右向左分配的在不同机器上可能不同

4.当一个声明指定了两个位段,第二个位段比较大,无法容纳与第一个位段剩余的位时,编译器有可能把第二个位段放在内存的下一个字,也可能 直接放在第一个位段后面,从而在两个内存位置的边界上重叠

下面是一个位段声明的例子:

struct CHAR {
    unsigned ch     :7;
    unsined font    :6;
    unsigned size   :29;
};
struct CHAR ch1;

这个例子说明了一个使用位段的理由,它能够把长度位奇数的数据包装在一起,节省存储内存空间

另一个使用位段的理由是它们可以很方便的访问一个整型值的部分内容。。

任何使用位段实现的任务都可以使用移位和屏蔽来实现。

6. 联合

和结构相比,联合可以说是另一种动物了。

联合的声明和结构类似,但是它的行为方式却和结构不同,联合的所有成员引用的是内存中相同的位置

当你想在不同时刻把不同的东西存储于同一个位置的话,就可以使用联合

例:

union {
    float f;
    int i;
}fi;

如果联合的各个成员具有不同的长度,联合的长度就是它的最长成员的长度

当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对其数的整数倍。

如果联合各个成员之间的长度差距特别悬殊,那么这样可能回浪费许多内存空间,这种情况下,更好的方法是在联合中存储指向不同成员的指针而不是直接存储成员本身

联合变量可以被初始化,但是这个初始值必须是联合第一个成员的类型,而且它必须位于一对花括号内,例如:

union {
    int a;
    float b;
    char c[4];
}x = {5};

7.枚举

在实际应用中,有的变量只有几种可能取值。如人的性别只有两种可能取值,星期只有七种可能取值。在 C 语言中对这样取值比较特殊的变量可以定义为枚举类型。

所谓枚举是指将变量的值一一列举出来,变量只限于列举出来的值的范围内取值。 枚举的定义枚举类型定义的一般形式为: enum 枚举名 { 枚举值表 };

枚举元素不是变量,而是常数

enum Day{
  mon,
  tue,
  wed,
  thur,
  fri,
  sat,
  sun
};

枚举的优点:

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

2.防止为了命名污染

3.便于调试

4.使用方便