C的结构体联合体和枚举总结

简单说下数组特性:

  • 数组所有的元素具有相同的数据类型
  • 选择数组元素需要指明元素的位置(下标)

结构体

结构体和数组有很大不同。结构体的元素(C 语言中的说法是成员)可以具有不同类型。而且每个结构成员都有名字,访问结构体成员需要指明结构成员的名字而不是位置。

在一些编程语言中,经常把结构体称为记录(record),把结构体的成员称为字段(field)。

1. 结构变量的声明

假如需要记录存储在仓库中的零件编号,名称和数量。我们可以使用结构体:

struct{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part1, part2;

struct{...}指明类型,part1,part2是这种类型的变量。

结构体在内存中是按照声明顺序存储的。

2. 结构变量的初始化

我们可以在定义结构体的同时初始化:

struct{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part1 = {528, "Disk drive", 10}
 part2 = {914, "Printer cable", 5};

初始化式中的值必须按照结构体成员的顺序进行显示。

结构初始化式遵循的原则类似于数组的。初始化式必须是常量(C99 中允许使用变量)。初始化式中的成员可以少于它所初始化的结构,“剩余的”成员用 0 作为初始值。特别的,剩余的字符串应为空字符串。

3. 指定初始化(C99)

特性和数组一样,比如:

struct {
	int number;
	char name[NAME_LEN + 1];
	int on_hand;
}part1 = {.name = "Disk", 123};

number 被默认为 0,.name 直接跳过 number 初始化 name,123 初始化的成员为 .name 后一个成员。

4. 对结构的操作

访问成员方式如下:

printf("Part number: %d\n", part1.number);
printf("Part name: %s\n", part1.name);
printf("Quantity on hand: %d\n", part.on_hand);

结构的成员是左值,所以可以出现在赋值运算的左侧:

part1.number = 258;
part1.on_hand++;

.其实就是一个 C 语言的运算符。.运算符的优先级几乎高于所有其他运算符,所以思考:

scanf("%d", &part1.on_hand);

&计算的是 part1.on_hand的地址

赋值运算:

part1 = part2;

等价于:

part1.number = part2.number;
strcpy(part1.name, part2.name);
part1.on_hand = part2.on_hand;

如果这个结构内含有数组,数组也会被复制。

但是不能使用 == 和 != 运算符判定两个结构是否相等。

二 结构类型

如果我们要在程序的不同位置声明变量,我们就需要定义表示一种结构类型的名字。

试思考:

struct{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part1;

在程序的某处,为了描述一个零件,我们写了上面的代码。但是,现在在程序的另一处有需要一个零件,直接增加一个变量:

struct{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part1, part2;

这种方式固然可行,但是有些“呆”。

那么,如果我们再次定义一个相同的“零件类型”:

struct{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part2;

请注意:part1 和 part2 具有不同的类型

1. 结构标记的声明

结构标记(struct tag)用来标识某一种特定的结构名称。下面的例子声明了名为 part 的结构类型:

struct part{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
};

注意:花括号后的分号不可少


如果忽略了分号,可能回得到含义模糊的出错信息,比如:

struct part{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}

f(){
  ...
  return 0;
}
 

由于前面的结构声明没有正常终止,所以编译器会假设函数 f 返回值是 struct part 类型的,所以直到 f 中的第一条 return 语句才会发现错误。


声明变量:

struct part part1, part2;

注意:不能省略 struct

也因为结构标记只有在 part 前放置 struct 才有意义,所以声明名为 part 的变量是完全合法的。(但是容易混淆)

声明结构标记和结构变量可以放在一起:

struct part{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part1, part2;

所有声明为 struct part类型的结构彼此兼容。

2. 结构类型的定义

使用 typedef定义名为 part 的结构类型:

typedef struct {
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part;

如此,我们就可以像上面那样声明结构变量:

part part1, part2;

因为类型名为 part 所以书写 struct part 是不合法的。

如果你也想可以使用 struct part,那你可以这样声明:

typedef struct part{
    int number;
    char name[NAME_LEN + 1];
    int on_hand;
}part;
3. 结构作为参数和返回值

结构作为参数

函数:

void print_part(struct part p){
    printf("Part number: %d\n", p.number);
	printf("Part name: %s\n", p.name);
	printf("Quantity on hand: %d\n", p.on_hand);
}

调用方式:

print_part(part1);

结构作为返回值

函数:

struct part build_part(int number, const char* name, int on_hand){
    struct part p;
    
    p.number = number;
    strcpy(p.name, name);
    p.on_hand = on_hand;
    
    return p;
}

调用方式:

part1 = build_part(527, "Disk", 10);

给函数传递结构和从函数返回结构都需要生成结构所有成员的副本,这回可能会产生一定数量的系统开销。为了避免这种开销,常传递或返回指向结构的指针来代替传递或返回结构本身。下一节中,我们将会看到这样的应用。

4. 复合字面量(C99)

略。

三 嵌套的结构和结构数组

1. 嵌套的结构

把一种结构嵌套在另一种结构中经常是非常有用的。比如:

定义一个结构存储一个人的姓名:

struct person_name{
    char first[FRIST_NAME_LEN + 1];
    char last[LAST_NAME_LEN + 1];
};

定义一个结构存储学生信息:

struct student{
  struct person_name name;
  int ID, age;
  char gender;
}student1;

访问 student1 的名和姓需要应用两次.:

strcpy(student1.name.first, "Fred");
2. 结构数组

声明一个数组用来存储 100 个零件信息:

struct part Part[100];

访问零件数组中下标为 i 的元素的结构成员:

Part[i].number = 883;

使存储在零件数组中下标为 i 的元素的姓名变为空字符串,可以写成:

Part[i].name[0] = '\0';
3. 结构数组的初始化

初始化结构数组与初始化多维数组的方法非常相似。比如:

struct person_name{
    char first[FRIST_NAME_LEN + 1];
    char last[LAST_NAME_LEN + 1];
}name[] = { {"San", "Zhang"}, {"Si", "Li"} };

与数组一样,指定初始化(C99)也适用于这种情况。

结构变量 s 和 联合变量 u 只有一处不同:s 的成员存储在不同的内存地址中;u 的成员存储在同一内存地址中。如图:

如果把一个值存储到u.d中,那么先前存储在 u.i中的值会丢失。类似的,改变 u.i也会影响u.d

联合的性质几乎和结构一样。

联合的初始化方式和结构也很相似,但是,只有联合的第一个成员可以获得初始值。例如,如下初始化方式可以使得联合 u 的成员 i 的值为 0:

union {
    double d;
    int i;
}u = {0};

注意:花括号是必需的

指定初始化(C99):

union {
    double d;
    int i;
}u = {.i = 3};

只能初始化一个成员,不一定是第一个。

4. 使用联合节省空间

有三种商品,每种商品都有库存,价格;这些商品还具有以下其他特性:

  • 书籍:书名,作者,页数
  • 杯子:设计
  • 衬衫:设计,可选颜色,可选尺寸

假如我们设计包含上面特性的结构:

struct catlog_item{
  	int stock_number;
    double price;
    int item_type;
    char title[TITLE_LEN + 1];
    char author[AUTHOR_LEN + 1];
    int num_page;
    char design[DESIGN_LEN + 1];
    int colors;
    int sizes;
};

item_type的值是 BOOK,MUG,SHIRT 之一。

上面这种结构体比较浪费空间,因为对于某种特定商品,结构中只有部分字段是有用的。(当然你也可以定义三个结构体,我也建议这么做。)

现在我们引用联合:

struct catlog_item{
  	int stock_number;
    double price;
    int item_type;
    
    union{
        struct{
        	char title[TITLE_LEN + 1];
    		char author[AUTHOR_LEN + 1];
   		    int num_page;
        }book;
        
        struct{
        	char design[DESIGN_LEN + 1];    
        }mug;
        
        struct{
            char design[DESIGN_LEN + 1];
    		int colors;
    		int sizes;
        }shirt;
        
    }item;
}catlog;

书籍名称可以用以下方式显示:

printf("%s\n", catlog.item.book.title);

把值存储在联合的一个成员中,然后访问另一个成员通常是不可取的。但是,如果联合的两个或多个成员是结构,而且这些结构最初的一个或多个成员是匹配的(顺序相同,类型兼容,名字可以不一样)。如果当前某个结构有效,其他结构中的匹配成员也有效。

联合 item 中,mug 和 shirt 第一个字段是匹配的。比如,如果我们给 mug 的成员 design 赋值:

strcpy(catlog.item.mug.design, "Cat");

结构 shirt 的第一个成员也具有相同的值:

printf("%s", catlog.item.shirt.design); //Cat
5. 使用联合构造混合的数据结构

假设需要数组元素是 int 值和 double 值的混合。因为数组元素必须是相同类型,我们可以应用联合数组:

typedef union{
    int i;
    double d;
}Number;

Number number_array[1000];

number_array[0].i = 1;
number_array[1].d = 1.1;
6. 为联合添加“标记字段”

联合面临的主要问题是:不容易确定联合最后改变的成员,因此对联合成员的访问可能是无意义的。

前面程序中 item_type 就是标记字段,用来帮助我们确定当前商品种类。

为了记录这种信息,我们可以把联合嵌入一个结构中,此结构还有另一个成员:“标记字段”或者“判别式”,用来提示当前存储在联合中的内容。比如定义如下结构:

#define INT_KIND 0
#deinf DOUBLE_KIND 1

typedef struct{
    int kind;
    union{
        int i;
        double d;
    }u;
}Number;

当需要访问存放在联合中的成员时,可以使用函数:

void print_number(Number n){
	if(n.kind == INT_KIND)
		printf("%d", n.u.i);
	else
        printf("%f", n.u.d);
}

注意:每次对联合成员赋值,都需要由程序改变标记字段的内容

五 枚举

C 语言为具有可能值较少的变量提供了一种专用类型 —— 枚举类型(enumeration type)

定义扑克花色:

enum{
    CLUBS,
    DIAMONDS,
    HEARTS,
    SPADES,
}s1;

CLUBS 的值为 0,DIAMAND 值为 1,后面的每个增加 1 ,以此类推。

如果没有枚举类型,我们需要一个个的来 #define

#define CLUBS 0
#define DIAMANDS 1
#define HEARTS 2
#define SPADES 3

这样无疑会增加程序的复杂度,也会降低同种情况的联系,让程序变得难以阅读。

0. 枚举类型声明

1)

enum suit{
    CLUBS,
    DIAMONDS,
    HEARTS,
    SPADES,
};
enum suit s1, s2;

2)

typedef enum{
    CLUBS,
    DIAMONDS,
    HEARTS,
    SPADES,
}Suit;
Suit s1, s2;

C89 中,使用枚举创建布尔类型:

typedef enum{TRUE, FALSE}Bool;

如果要使用枚举变量:

Suit suit = CLUBS;
Bool flag = TRUE;

枚举类型的变量可以赋值为任意枚举列出来的枚举常量。但是枚举常量可以赋值给普通整型变量,普通整型变量也可以赋值给枚举类型的变量。这是因为 C 语言对于枚举和整数的使用比较混乱,没有明确界限。

1. 枚举作为整数

在系统内部,C 语言会把枚举变量和常量作为整数来处理。默认情况下,编译器将 0,1,... 赋值给枚举常量。

我们可以为枚举常量自由选择不同的值。现在假设希望用 1 到 4 代表牌的花色,我们可以这样定义:

enum suit{
    CLUBS = 1,
    DIAMONDS = 2,
    HEARTS = 3,
    SPADES = 4,
};

我们知道后一个枚举常量比前一个大 1,所以,我们也可以简化为:

enum suit{
    CLUBS = 1,
    DIAMONDS,
    HEARTS,
    SPADES,
};

也可以换为任意整数:

enum suit{
    CLUBS = 10,
    DIAMONDS = 20,
    HEARTS = 15,
    SPADES = 40,
};
2. 使用枚举声明“标记字段”

现在我们可以不用宏的值来表示标记字段的含义了:

typedef struct{
    enum {INT_KIND, DOUBLE_KIND} kind;
    union{
        int i;
        double d;
    }u;
}Number

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值