【C基础】14 结构、联合、枚举、类型定义

19 篇文章 6 订阅

常用技巧:
1、使用最后一个枚举量的值来记录此枚举类型值的个数(除自己以外)

enum COLOR {RED, YELLOW, GREEN, NumCOLORS};		//NumCOLORS = 3,其前面有3个值

2、声明枚举量的时候可以指定值

enum COLOR { RED = 1, YELLOW, GREEN = 5 }; 		// YELLOW = 2

说明:

  • 虽然枚举类型可以作为类型使用,但并不好用,因而实际使用很少;
  • 如果有意义上的排比名字,用类型比 const int 方便;
  • 枚举比宏好,枚举有 int 类型;
  • 枚举的主要作用是定义符号量,而非作为一种类型来使用。

一、结构

当需要存储一组相关数据的集合(如年月日)的时候,尤其是改组数据具有不同的数据类型时,引入结构十分有效。

重点在于以下几个概念的区分:
- 结构类型;
- 结构标记;
- 结构成员;
- 结构变量。

1.1 结构类型

1.1.1 声明格式

(1)格式一:

struct point {			//结构类型为struct point, 结构标记为point
	int x;				//x, y为结构成员
	int y;
};						//最后的分号不能漏
...
struct point p1, p2;		//p1,p2为结构变量

(2)格式二:

struct {				//匿名结构
	int x;
	int y;
} p1, p2;				//p1,p2为结构变量,里面有结构成员x和y

(3)格式三:

struct pointint x;
	int y;
}p1, p2;				//p1和p2都是struct point类型的结构变量

/* 等价于
struct point {
	int x;
	int y;
};
...
struct point p1;
struct point p2;
*/

(4) 格式四:类型定义

typedef struct{
	int x;
	int y;
} Point;					//Point为结构类型的名称
...
Point p1, p2;				//定义上述类型的结构变量p1和p2

说明:

  • 结构类型名也称为结构标记;
  • 和本地变量一样,在函数内部声明的结构类型只能在函数内部使用。因此,通常在函数外部声明结构类型;
  • 最后的分号一定不能漏
  • 结构类型有两种命名方式:struct 标记 {...};typedef struct {...} Name;。当结构成员含有指向同类型结构的指针时(例如链表的每个节点),需要使用第一种(使用结构标记)方式。
//同时含有标记名和typedef, 甚至可以同名, 此类声明是合法的
typedef struct point{
	float x;
	float y;
} point;

typedef struct p {
	float x;
	float y;
} Point;

1.1.2 结构变量

1.1.2.1 结构变量的声明

结构类型: struct 类型名(位于结构声明中)
结构变量: struct 类型名 结构变量名

  • 结构类型名也可称为结构标记(struct tag),无实际意义;
  • 结构变量可以在结构声明中声明(跟在}后面),也可在主函数中单独声明。
  • 结构变量、结构标记、结构成员不可混淆。
#include<stdio.h>

struct date {					//函数外声明,可全局使用, 结构标记为date
	int month;
	int day;
	int year;
};

int main(int argc, char* argv[]){
	struct date today;				//定义struct date类型的结构变量today
									//today中含有3个变量成员分别是month, day和year
	today.month = 07;
	today.day = 31;
	today.year = 2014;
	
	printf("Today's date is %i-%i-%i.\n", today.year, today.month, today.day);
	
	return 0
}
1.1.2.2 结构变量的初始化

struct date today = {07, 31, 2014}; (month=07, day=31, year=2014)
struct date thismonth = {.month=7, .year=2014};(day=0)【符号.不能漏

注意:结构类型的成员不能在声明的时候初始化

//错误
struct date {
	int month = 10;
	int day = 1;
	int year = 2022;
};

//正确
struct date {
	int month;
	int day;
	int year;
} date1 = {.month = 10; .day = 1; .year = 2022},	//逗号分隔
  date2 = {.month = 5;  .day = 5; };				//year = 0;

结构与数组:

  • 结构与数组类似;
    • 数组用 [] 运算符和下标访问其成员,如 a[0] = 10;
    • 结构用 . 运算符和结构变量访问其成员,优先级为1(最高级别),如 today.day , student.firstName , p1.x

1.1.3 结构的操作

  • 要访问整个结构,直接用结构变量的名字
  • 对整个结构可做赋值、取地址,也可以传递给函数参数。
    • 结构可以用 =运算符直接复制,且嵌套在结构内的数组也会因此直接被复制(单独的数组时不能直接复制的)且结构的直接复制仅适用于相同类型的结构变量之间进行。
struct {int a[10]; } a1, a2;
a1 = a2;					//利用结构复制数组
  • p1 = (struct point){5, 10}; //复合字面量赋值
  • p1 = p2; //相当于p1.x = p2.x; p1.y = p2.y;
  • 除了赋值 = 运算,C语言不提供其他作用于整个结构的操作。特别是不能用 ==!= 运算符来比较两个结构变量是否相等
#include<stdio.h>

struct date {
	int month;
	int day;
	int year;
};

int main(void){
	
	struct date today;						//定义一个结构变量 today 
	
	today = (struct date){07, 31, 2014};	//today赋值,右侧为复合字面量 
	
	struct date day;						//定义一个结构变量 day 
	
	day = today;
	day.year = 2015;						//变量单独赋值 
	
	printf("Today = %i-%i-%i\n", today.year, today.month, today.day);
	printf("Day = %i-%i-%i\n", day.year, day.month, day.day);
	
	return 0;
}

1.1.4 结构作为函数参数或返回值

(1) 作为参数:

//举例1:
typedef struct {
	int number;
	char name[NAME_LEN + 1];
	int on_hand;
} Part;

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

//举例2:
struct point{
	float x;
	float y;
};

print_point(struct point)
{
	printf("(%.2f, %.2f)\n", point.x, point, y);
	printf("\n");
}

(2) 作为返回值:

struct point update_coordinate(float x1, float y1)
{
	struct point p;
	p.x = x1;
	p.y = y1;
	return p;
}

1.1.5 复合字面量 (C99)

指定类型和其包含的元素而创建的没有名字的数组。
复合字面量:(类型名)初始化器

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

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

print_part( (Part){528, "Disk Drive", 10} );		//作为实际参数传递给函数
print_part( (Part){.on_hand = 10,
				   .name = "Disk Drive",
				   .number = 528,} );

Part part1 = (Part){528, "Disk Drive", 10};			//作为初始化器

Part part2;
part2 = (Part){528, "Disk Drive", 10};				//给变量赋值
  • 复合字面量不会提供完全的初始化,任何未出现在初始化器中的成员默认初始化为0。
Part part3 = (Part){528, "Disk Drive", 10};
part3 = (Part){.number = 500, .on_hand = 20};		//等价于part3 = (Part){500, 0, 20};		零件名成为空字符串
  • 指针指向符合字面量是合法的,符合字面量有地址。
print_point( &(Point){1.2, 3.4} );
  • 复合字面量是左值,可以修改。

1.1.6 匿名结构 (C1X)

一个结构或联合内包含一个匿名结构(没有名称,没有标记,只有成员列表)

struct t{
	int i;
	struct s {int j, k : 3;};			//k为结构位域的名称,详见《结构中的位域》
	struct {char c, float f;};			//匿名结构
	struct {double d;} s;
} t;

t.i = 2006;					
t.j = 5;			//非法
t.k = 6;			//非法
t.c = 'x';			//正确
t.f = 2.0;			//正确
t.s.d = 22.2;
  • 匿名结构的成员被当作隶属于包含该结构的上层结构的成员,但其初始化器仍需要包含花括号。单个成员赋值不需要花括号。
struct t{
	char c;
	struct {int x}; };
};

struct t t = {'x', 1};			//非法
struct t t = {'x', {1} };		//合法

1.1.7 嵌套的数组和结构

1.1.7.1 嵌套的结构
struct person_name{
	char first[FIRST_NAME_LEN + 1];
	char middle_initial;
	char last[LAST_NAME_LEN + 1];
};

struct student{
	struct person_name name;		//嵌套结构
	int id, age;
	char gender,
} student1, student2;

strcpy(student1.name.fisrt, "Fred");
printf("%s\n", student1.name);

struct person_name new_name;
student1.name = new_name;
1.1.7.2 结构数组

元素为结构的数组。

struct part{
	int number;
	char name[21];
	int on_hand;
};

struct point inventory[10];		//结构数组
print_part(inventory[i]);		//取结构数组的i号元素
inventory[i].x = 2.1;			//对数组元素的成员赋值
inventory[i].name[0] = '\0';
1.1.7.3 结构数组的初始化

初始化器本身含有花括号,每个元素是结构,其初始化器也要带有花括号,不同元素用逗号间隔。

struct dialing_code{
	char *country;
	int code;
};

//初始化
struct dialing_code country_codes1[] = 
{{"Argentina", 54}, {"Bangladesh",    880},
 {"Brazil",    55}, {"Burma(Myanmar)", 95},
 {"China",     86}, {"Colombia",       57}, };

//指示器与初始化器组合
struct dialing_code country_codes2[20] = 
{ [0].country = "Germany", [0].code = 49,
  [1].country[0] = '\0',  };

1.1.8 指针访问结构体

  • 与数组不同,结构变量的名称不是指针,不可作为地址,需使用&运算符
struct date *pDate = &today;					//访问结构变量,指针pDate指向结构变量today

printf("*pDay = %i-%i-%i\n", pDay->year, 
				pDay->month, pDay->day);		//访问结构成员(方法1)
				
printf( "Today: %i-%i-%i\n", (*pDate).year,
	          (*pDate).month, (*pDate).day );	//访问结构成员(方法2)
  • 访问成员的方法2中,取结构/联合成员 运算符 的优先级高于间接寻址运算符【.(1级) > *(2级)】因此需要圆括号。

1.2 结构与函数

1.2.1 结构作为函数参数

(1)格式

int numbersOfDays(struct date d)
  • 整个结构(包含各个成员)可作为参数,实现方式是在函数内新建并复制一个结构变量(克隆体),因此函数结束后原数据不会被修改;
  • 结构也可作为函数返回值
  • 与数组不同,数组传递指针,也返回指针。

(2)应用举例
   判断明天是哪一天

(3)输入结构

  • 一个结构无法直接被 scanf 读取,需要将结构拆分,分别读取(见上方应用举例)

1.2.2 读取结构函数

#include<stdio.h>

struct point {							//结构标号 
	int x;
	int y;
};

struct point getStruct(void);			//结构读取函数 
void output(struct point);				//结构输出函数 

int main(int argc, char* const argv[]){
	
	struct point y = {0, 0};			//结构变量的定义和初始化 
	
	y = getStruct();					//将读到的值传到y里面 
	output(y); 							//输出y的值 
	
	return 0;
}

struct point getStruct(void)
{
	struct point p;
	scanf(" %d %d", &p.x, &p.y);
	
	return p;
}

void output(struct point p)
{
	printf("%d,%d", p.x, p.y);
}

1.2.3 指向结构的指针

  • 结构作为函数参数,使用复制一个新的结构的方式,具有耗费时间和占用空间的缺点,尤其是对大型结构体。对于传指针具有更大的优势(类似数组)。
struct date {
	int year;
	int month;
	int day;
} myday;

struct date *p = &myday;			//指针定义和初始化

(*p).month = 12;					//方法1
p->month = 12;						//方法2(更简单)
  • ->表示指向的结构变量中的成员,读作arrow(箭头)。

1.3 结构中的结构

1.3.1 结构数组

struct date dates[100];
struct date dates[] = {
			{4,5,2005}, {2,4,2005}
};

1.4 内存对齐

一个结构所占据的字节数并非其所有成员单独所占字节数之和,需要考虑内存对齐问题。良好的成员排序也会为结构节省更多的空间。

例如:

struct {
	char a;
	int b;
	short c;
} s1;					//sizeof(s1) = 12

struct {
	char a;
	short c;
	int b;
} s2;					//sizeof(s2) = 8
  • 结构变量 s1 占据 12 个字节,s2 占据 8 个字节。而非 5 个字节,二者存储的变量种类完全一致。

关于内存对其参见博文 内存对其详解 (若有侵权,请联系删除)

1.5 结构中的位域

位域表示结构中某段二进制位 (字段) 的长度。

struct s {
	int a;					//结构成员
	int b : 3;				//位域
	char c : 5;				//位域
} s1;

详见 15 位运算 - 5 结构中的位域


二、联合

与结构类似,由一个或多个成员构成,但编译器只为最大的成员分配足够的内存空间。联合中的所有成员会功用同一个内存空间,因此会彼此覆盖。改变其中一个成员的值,就会改变其他成员的值。

struct {
	int i;
	double d;
} s;

union {
	int i;
	double d;
} u;

请添加图片描述

  • 每个结构成员占据不同的内存单元,没有重叠,具有不同的地址。每个联合成员具有相同的地址,占据的内存单元有重叠部分。
  • 联合的性质与结构几乎一样,结构和联合都可以使用赋值号=直接进行复制。
union {
	int i;
	double d;
} u = {.d = 1.2};						//初始化指示器

2.1 用联合来节省空间

联合的优点就是可以节省空间,用一个联合存储多个类型的数据,但每次都使用其中一个数据。

举例:存储不通过类型商品的商品信息

struct catalog_item{
	int stock_number;
	double price;
	int item_type;
	union {
		//图书
		struct {
			char title[TITLE_LEN + 1];
			char auther[AUTHER_LEN + 1];
			int num_pages;
		} book;
		//杯子
		struct {
			char design[DESIGN_LEN + 1];
		} cup;
		//衬衫
		struct {
			char design[DESIGN_LEN + 1];
			int colors;
			int sizes;
		} shirt;
		
	} item;
};

...
struct catalog_item c1;
...
printf("%s\n", c1.item.book.title);

此结构不必同时存储所有类型商品的信息,因此使用联合来合并存储不同商品的信息,如此便可有效减少所需的内存空间。

  • 以上名为 item 的联合中的 cup 结构和 shirt 结构的第一个成员是相匹配的,因此假设对其中一个 design 赋值,另一个 disign 也会变成同样的值。
strcpy(c1.item.cup.design, "Cat");
printf("%s", c1.item.shirt.design);		//输出有效的内容"Cat"

联合的两个或多个成员是结构,而这些结构最初的一个或多个成员时相匹配的(匹配成员的顺序相同,类型也兼容,名字可以不一样),如果当前某个结构中的匹配成员有效,则其他结构中的匹配成员也有效。


2.2 用联合来构造混合的数据结构

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

Number num_arr[100];	//此数组既可以存储 int 类型的值, 也可存储 double 类型的值

num_arr[0].i = 5;
num_arr[1].d = 123.4;

2.3 为联合添加“标记字段”

为联合添加标记段,用来指示最后修改的是联合中的哪个成员。将联合与标记段包含在同一个结构中(无重合)。

#define INT_KIND 1
#define DOUBLE_KIND 0

typedef struct{
	int kind;			//标记段
	union {
		int i;
		double d;
	} u;
} Number;

Number n;
n.kind = INT_KIND;
n.u.i = 45;

n.kind = DOUBLE_KIND;
n.u.d = 1.23

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

每次修改联合中的成员之前,先修改标记段中的内容。

可以使用枚举声明来标记字段,见本文 3.3 节。


2.4 匿名联合

匿名联合:没有名字;只有成员列表但没有标记。

struct t{
	int i;
	union{					//匿名联合
		char c;
		float f;
	};
};

union u{					//含标记,非匿名联合
	int i;
	union{					//匿名联合
		char c;
		float f;
	};
};

如何访问匿名联合的成员?
- 若某个匿名联合 U 是结构或联合 X 的成员,则 U 的成员被视为 X 的成员。
- 若含有多层嵌套,同样使用上述规则。

举例1:

struct t{
	int i;
	struct s{int j, k:3;};
	union {char c; float f};		//匿名联合
	struct {double d} s;
} t;

t.i = 6;			//合法
t.j = 5;					//非法
t.s.k = 4;			//合法
t.c = 'x';			//合法
t.f = 1.23;			//合法
t.s.d = 4.56;		//合法

举例2:

//WRONG!!!
struct tag {
	struct {int i};
	union { union{int i; float f}; double d; };
	char c;
};
  • 以上代码会报错,struct tag含有一个匿名结构和一个匿名联合,匿名结构中的 i ,和匿名联合中的 i, f, d 均被视为其直接成员,造成此结构中含有重复成员 ( Error: duplicate member ‘i’ )

三、枚举

枚举(enumeration)是一种用户定义的数据类型,是C语言为可能值较少的变量提供的一种专用类型。

  • 枚举值, 即枚举常量

  • 枚举常量既是每个枚举变量的所有可能的取值。

  • 枚举常量类似于宏定义的常量,但它遵循作用域规则(宏没有作用域)。

  • 枚举类型的声明形式和结构体或联合相似,但其元素由,分开,且使用方法完全不同。

3.1 枚举标记和枚举类型

enum suit {CLUBS, DIAMONDS, HEARTS, SPADES};		//suit为枚举标记, enum suit为枚举类型名

enum suit s1, s2;				//s1, s2为enum suit类型的枚举变量

typedef enum {CLUBS, DIAMONDS, HEARTS, SPADES} Suit;	//Suit为枚举类型
Suit s1, s2;					//s1, s2为Suit类型的枚举变量

应用举例:

//创建布尔类型
typedef enum {TRUE, FALSE} Bool;
Bool flag1, flag2;

3.2 枚举作为整数

C语言会把枚举常量和变量作为整数来处理。一般情况下,编译器会把整数 0,1,2,…赋给特定枚举中的常量。我们也可以为枚举常量自由选择不同的值。

enum suit {CLUBS, DIAMONDS, HEARTS, SPADES};	//CLUBS...分别表示0,1,2,3

enum suit{CLUBS = 1, DIAMONDS = 2, HEARTS = 3, SPADES = 4};
enum dept {RESEARCH = 20, PRODUCTION = 10, SALES = 20};			//合法

enum EGS_colors {BLACK, LT_GRAY = 7, DK_GRAY, WHITE};	//BLACK = 0, DK_GRAY = 8, WHITE = 9
  • 两个或多个枚举常量具有相同的值也是合法的。

  • 当没有指定枚举常量指定值时,每个枚举常量只比前一个大 1(和初始化器类似)。

  • C语言允许把枚举值与普通的整数进行混合:

int i;
enum {CLUBS, DIAMONDS, HEARTS, SPADES} s;
i = HEARTS;				//i = 2;
s = 0;					//s = CLUBS;
s++;					//s = DIAMONDS
i = s + 2;				//i = 3;

编译器把 s 当作整型变量来处理,CLUBS 等只是数 0,1,2,3的名字(类似宏)

  • 注意:枚举变量只能被赋予枚举值,否则会出错。例如上例中不能把4 赋给 s 。

  • 枚举常量可以作为数组下标:

enum weekdays {MONDAY, TUESDAY, WEDNSDAY, THURSDAY, FRIDAY};

const char* daily_specials[] = {
	[MONDAY] = "Beef",				//使用数组指示器
	[TUESDAY] = "BLTs",
	[WEDNSDAY] = "Pizza",
	[THURSDAY] = "Chicken",
	[FRIDAY] = "Cheese",
};

3.3 用枚举声明标记字段

typedef struct {
	enum {INT_KIND, DOUBLE_KIND} kind;
	union {
		int i;
		double d;
	} u;
} Number;
  • 使用枚举变量声明标记字段,避免了宏的使用,且让 kind 的含义更加明确。

3.4 其他说明

常用技巧:

  • 使用最后一个枚举量的值来记录此枚举类型值的个数(除自己以外)
enum COLOR {RED, YELLOW, GREEN, NumCOLORS};		//NumCOLORS = 3,其前面有3个值

说明:

  • 虽然枚举类型可以作为类型使用,但并不好用,因而实际使用很少;
  • 如果有意义上的排比名字,用类型比 const int 方便;
  • 枚举比宏好,枚举有 int 类型;
  • 枚举的主要作用是定义符号量,而非作为一种类型来使用。

四、类型定义 —— typedef

使用typedef来声明一个已有的数据类型的新名字。此后可以使用这个新名字来代替原来的数据类型来出现在变量定义和参数声明的地方了。这样可以增加程序可读性。

4.1 定义基本数据类型

typedef int Length;		//Length成为int的别名

Length a, b, len;		//定义a,b,len为int类型的变量

4.2 定义数组

typedef int a[10];		//定义a为10元素整型数组类型

a s1, s2;				//等价于 int s1[10], s2[10];

4.3 定义结构

typedef struct ADate{	//Date成为struct ADate的别名
	int year;
	int month;
	int day;
} Date;

Length a, b, len;		//定义a,b,len为int类型的变量
Date d = {9, 1, 2005};	//等价于struct ADate d = {9, 1, 2005};

4.4 定义指针

typedef void (*p);		//定义p为指向void型的指针类型

p p1, p2;				//等价于 void *p1, *p2;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值