结构和其他数据形式

本文详细介绍了C语言中结构体的使用,包括结构体的声明、定义、初始化、访问成员、结构数组、嵌套结构、结构指针以及如何通过结构体传递信息给函数。还提到了枚举类型和typedef的用法,以及复杂声明的示例,展示了如何灵活地组合和操作数据结构。
摘要由CSDN通过智能技术生成

结构和其他数据形式

​ 设计程序时,最重要的步骤之一是选择表示数据的方法。在许多情况下,简单变量甚至是数组还不够。为此,C提供了结构变量(structure variable)提高你表示数据的能力,它能让你创造新的形式。

​ 每个部分都称为成员(member)或字段(field)。这3部分中,一部分储存书名,一部分储存作者名,一部分储存价格。下面是必须掌握的3个技巧:

​ 1.为结构建立一个格式或样式;
​ 2.声明一个适合该样式的变量;
​ 3.访问结构变量的各个部分。

1. 建立结构声明

结构声明(structure declaration)描述了一个结构的组织布局。

struct book {
	char title[MAXTITL];
	char author[MAXAUTL];
	float value;
};
//	首先是关键字 struct,它表明跟在其后的是一个结构,
//	后面是一个可选的标记(该例中是 book),稍后程序中可以使用该标记引用该结构。
//	所以,我们在后面的程序中可以这样声明:
struct book library;

2. 定义结构变量

//	在结构变量的声明中,struct book所起的作用相当于一般声明中的int或float。
//	例如,可以定义两个struct book类型的变量,或者甚至是指向struct book类型结构的指针:
struct book doyle, panshin, * ptbook;
//	结构变量doyle和panshin中都包含title、author和value部分。
//	指针ptbook可以指向doyle、panshin或任何其他book类型的结构变量

struct book library;
//	声明简化:
struct book {
	char title[MAXTITL];
	char author[AXAUTL];
	float value;
} library;

//	组合后的结构声明和结构变量定义不需要使用结构标记:
struct { /* 无结构标记 */
	char title[MAXTITL];
	char author[MAXAUTL];
	float value;
} library;
//	然而,如果打算多次使用结构模板,就要使用带标记的形式;或者,使用本章后面介绍的typedef。
//	这是定义结构变量的一个方面,在这个例子中,并未初始化结构变量。

2.1 初始化结构

struct book library = {
	"The Pious Pirate and the Devious Damsel",
	"Renee Vivotte",
	1.95
};
/*	
 *	我们使用在一对花括号中括起来的初始化列表进行初始化,各初始化项用逗号分隔。
 *	注意 初始化结构和类别储存期
 *	如果初始化静态存储期的变量(如,静态外部链接、静态内部链接或静态无链接),必须使用常量值。
 *  如果初始化一个静态存储期的结构,初始化列表中的值必须是常量表达式。
 *  如果是自动存储期,初始化列表中的值可以不是常量。
 */

2.2 访问结构成员

//	使用结构成员运算符———点(.)访问结构中的成员。
library.value	即访问 library 的 value 部分。

2.3 结构的初始化器

​ 结构的指定初始化器使用点运算符和成员名(而不是方括号和下标)标识特定的元素。

struct book surprise = { .value = 10.99};
//	可以按照任意顺序使用指定初始化器:
struct book gift = { 	.value = 25.99,
						.author = "James Broadfool",
						.title = "Rue for the Toad"};

//	另外,对特定成员的最后一次赋值才是它实际获得的值。
struct book gift= {		.value = 18.90,
						.author = "Philionna Pestle",
						0.25};
//	赋给value的值是0.25,因为它在结构声明中紧跟在author成员之后。
//	新值0.25取代了之前的18.9。

3. 结构数组

​ 显然,每本书的基本信息都可以用一个 book 类型的结构变量来表示。为描述两本书,需要使用两个变量,以此类推。可以使用这一类型的结构数组来处理多本书。

声明结构数组

struct book library[MAXBKS];
//	数组的每个元素都是一个book类型的数组。
//	数组名library本身不是结构名,它是一个数组名,该数组中的每个元素都是struct book类型的结构变量。

访问数组中的结构成员

//	为了标识结构数组中的成员,可以采用访问单独结构的规则:
//	在结构名后面加一个点运算符,再在点运算符后面写上成员名。
library[0].value /* 第1个数组元素与value 相关联 */
library[4].title /* 第5个数组元素与title 相关联 */
    
//	注意,数组下标紧跟在library后面,不是成员名后面:
library.value[2] // 错误
library[2].value // 正确
//	使用library[2].value的原因是:library[2]是结构变量名,正如library[1]是另一个变量名。
    
//	总结
library					// 一个book 结构的数组
library[2]				// 一个数组元素,该元素是book结构
library[2].title		// 一个char数组(library[2]的title成员)
library[2].title[4]		// 数组中library[2]元素的title 成员的一个字符

4. 嵌套结构

// friend.c -- 嵌套结构示例
#include <stdio.h>
#define LEN 20
const char * msgs[5] =
{
	" Thank you for the wonderful evening, ",
	"You certainly prove that a ",
	"is a special kind of guy.We must get together",
	"over a delicious ",
	" and have a few laughs"
};
struct names {         // 第1个结构
	char first[LEN];
	char last[LEN];
};
struct guy {          // 第2个结构
	struct names handle;   // 嵌套结构
	char favfood[LEN];
	char job[LEN];
	float income;
};
int main(void)
{
	struct guy fellow = {   // 初始化一个结构变量
		{ "Ewen", "Villard" },
		"grilled salmon",
		"personality coach",
		68112.00
	};
	printf("Dear %s, \n\n", fellow.handle.first);
	printf("%s%s.\n", msgs[0], fellow.handle.first);
	printf("%s%s\n", msgs[1], fellow.job);
	printf("%s\n", msgs[2]);
	printf("%s%s%s", msgs[3], fellow.favfood, msgs[4]);
	if (fellow.income > 150000.0)
		puts("!!");
	else if (fellow.income > 75000.0)
		puts("!");
	else
		puts(".");
	printf("\n%40s%s\n", " ", "See you soon,");
	printf("%40s%s\n", " ", "Shalala");
	return 0;
}

​ 首先,注意如何在结构声明中创建嵌套结构。和声明int类型变量一样,进行简单的声明:
​ struct names handle;
​ 该声明表明handle是一个struct name类型的变量。当然,文件中也应包含结构names的声明。

​ 其次,注意如何访问嵌套结构的成员,这需要使用两次点运算符:
​ printf(“Hello, %s!\n”, fellow.handle.first);
​ 从左往右解释fellow.handle.first:(fellow.handle).first
​ 也就是说,找到fellow,然后找到fellow的handle的成员,再找到handle的first成员。

5. 指向结构的指针

​ 至少有 4 个理由可以解释为何要使用指向结构的指针。

​ 第一,就像指向数组的指针比数组本身更容易操控(如,排序问题)一样,指向结构的指针通常比结构本身更容易操控。

​ 第二,在一些早期的C实现中,结构不能作为参数传递给函数,但是可以传递指向结构的指针。

​ 第三,即使能传递一个结构,传递指针通常更有效率。

​ 第四,一些用于表示数据的结构中包含指向其他结构的指针。

/* friends.c -- 使用指向结构的指针 */
#include <stdio.h>
#define LEN 20
struct names {
	char first[LEN];
	char last[LEN];
};
struct guy {
	struct names handle;
	char favfood[LEN];
	char job[LEN];
	float income;
};
int main(void)
{
	struct guy fellow[2] = {
		{ { "Ewen", "Villard" },
			"grilled salmon",
			"personality coach",
			68112.00
		},
		{ { "Rodney", "Swillbelly" },
			"tripe",
			"tabloid editor",
			432400.00
		}
	};
	struct guy * him;  /* 这是一个指向结构的指针 */
	printf("address #1: %p #2: %p\n", &fellow[0], &fellow[1]);
	him = &fellow[0];  /* 告诉编译器该指针指向何处 */
	printf("pointer #1: %p #2: %p\n", him, him + 1);
	printf("him->income is $%.2f: (*him).income is $%.2f\n",him->income, (*him).income);
	him++;       /* 指向下一个结构  */
	printf("him->favfood is %s: him->handle.last is %s\n",him->favfood, him->handle.last);
	return 0;
}

5.1 声明和初始化结构指针

struct guy * him;
//	首先是关键字 struct,其次是结构标记 guy,然后是一个星号(*),其后跟着指针名。
//	声明并未创建一个新的结构,但是指针him现在可以指向任意现有的guy类型的结构。
//	例如,如果barney是一个guy类型的结构,可以这样写:him = &barney;
//	和数组不同的是,结构名并不是结构的地址,因此要在结构名前面加上&运算符

​ 在有些系统中,一个结构的大小可能大于它各成员大小之和。这是因为系统对数据进行校准的过程中产生了一些“缝隙”。例如,有些系统必须把每个成员都放在偶数地址上,或4的倍数的地址上。在这种系统中,结构的内部就存在未使用的“缝隙”。

5.2 用指针访问成员

​ 第1种方法也是最常用的方法:使用->运算符。该运算符由一个连接号(-)后跟一个大于号(>)组成。我们有下面的关系:

​ 如果him == &barney,那么him->income 即是 barney.income
​ 如果him == &fellow[0],那么him->income 即是 fellow[0].income

​ ->运算符后面的结构指针和.运算符后面的结构名工作方式相同(不能写成him.incone,因为him不是结构名)。

​ 要着重理解him是一个指针,但是him->income是该指针所指向结构的一个成员。所以在该例中,him->income是一个float类型的变量。

​ 第2种方法是,以这样的顺序指定结构成员的值:如果him == &fellow[0],那么*him == fellow[0],因为&和*是一对互逆运算符。因此,可以做以下替代:

​ fellow[0].income == (*him).income
必须要使用圆括号,因为.运算符比*运算符的优先级高

//总之,如果him是指向guy类型结构barney的指针,下面的关系恒成立:
barney.income == (*him).income == him->income // 假设him == & barney

6. 向函数传递结构的信息

程序员可以选择是传递结构本身,还是传递指向结构的指针。如果你只关心结构中的某一部分,也可以把结构的成员作为参数。

6.1 传递结构成员

/* funds1.c -- 把结构成员作为参数传递 */
#include <stdio.h>
#define FUNDLEN 50
struct funds {
	char   bank[FUNDLEN];
	double  bankfund;
	char   save[FUNDLEN];
	double  savefund;
};
double sum(double, double);
int main(void)
{
	struct funds stan = {
		"Garlic-Melon Bank",
		4032.27,
		"Lucky's Savings and Loan",
		8543.94
	};
	printf("Stan has a total of $%.2f.\n",sum(stan.bankfund, stan.savefund));
	return 0;
}
/* 两个double类型的数相加 */
double sum(double x, double y)
{
return(x + y);
}

运行该程序后输出如下:
Stan has a total of $12576.21.
看来,这样传递参数没问题。注意,sum()函数既不知道也不关心实际的参数是否是结构的成员,它只要求传入的数据是double类型。
当然,如果需要在被调函数中修改主调函数中成员的值,就要传递成员的地址:
modify(&stan.bankfund);
这是一个更改银行账户的函数。
把结构的信息告诉函数的第2种方法是,让被调函数知道自己正在处理一个结构。

6.2 传递结构的地址

/* funds2.c -- 传递指向结构的指针 */
double sum(const struct funds *); /* 参数是一个指针 */

double sum(const struct funds * money)
{
	return(money->bankfund + money->savefund);
}

6.3 传递结构

/* funds3.c -- 传递一个结构 */
double sum(struct funds moolah); /* 参数是一个结构 */

double sum(struct funds moolah)
{
	return(moolah.bankfund + moolah.savefund);
}

7. 结构特性

​ 现在的C允许把一个结构赋值给另一个结构,但是数组不能这样做。也就是说,如果n_data和o_data都是相同类型的结构,可以这样做:

​ o_data = n_data; // 把一个结构赋值给另一个结构

​ 函数不仅能把结构本身作为参数传递,还能把结构作为返回值返回。把结构作为函数参数可以把结构的信息传送给函数;把结构作为返回值的函数能把结构的信息从被调函数传回主调函数。

7.1 结构和结构指针的选择

​ 把指针作为参数有两个优点:无论是以前还是现在的C实现都能使用这种方法,而且执行起来很快,只需要传递一个地址。缺点是无法保护数据。被调函数中的某些操作可能会意外影响原来结构中的数据。(可用const限定符解决这个问题。)

​ 把结构作为参数传递的优点是,函数处理的是原始数据的副本,这保护了原始数据。另外,代码风格也更清楚。两个缺点是:较老版本的实现可能无法处理这样的代码,而且传递结构浪费时间和存储空间。尤其是把大型结构传递给函数,而它只使用结构中的一两个成员时特别浪费。这种情况下传递指针或只传递函数所需的成员更合理。

7.2 结构中的字符数组和字符指针

#define LEN 20
struct names {
	char first[LEN];
	char last[LEN];
};
struct pnames {
	char * first;
	char * last;
};
struct names accountant;
struct pnames attorney;
puts("Enter the last name of your accountant:");
scanf("%s", accountant.last);
puts("Enter the last name of your attorney:");
scanf("%s", attorney.last);  /* 这里有一个潜在的危险 */

​ 对于会计师(accountant),他的名储存在accountant结构变量的last成员中,该结构中有一个储存字符串的数组。

​ 对于律师(attorney),scanf()把字符串放到attorney.last表示的地址上。由于这是未经初始化的变量,地址可以是任何值,因此程序可以把名放在任何地方。

​ 如果走运的话,程序不会出问题,至少暂时不会出问题,否则这一操作会导致程序崩溃。因此,如果要用结构储存字符串,用字符数组作为成员比较简单。用指向 char 的指针也行,但是误用会导致严重的问题。

8. 联合简介

​ 联合(union)是一种数据类型,它能在同一个内存空间中储存不同的数据类型(不是同时储存)。

union hold {
	int digit;
	double bigfl;
	char letter;
};
//	根据以上形式声明的结构可以储存一个int类型、一个double类型和char类型的值。
//	声明的联合只能储存一个int类型的值或一个double类型的值或char类型的值。

8.1 使用联合

union hold fit;   // hold类型的联合变量
union hold * pu;   // 指向hold类型联合变量的指针
fit.digit = 23; 	// 把 23 储存在 fit,占2字节
fit.bigfl = 2.0; 	// 清除23,储存 2.0,占8字节
fit.letter = 'h'; 	// 清除2.0,储存h,占1字节
pu = &fit;
x = pu->digit; 		// 相当于 x = fit.digit

​ 联合的另一种用法是,在结构中储存与其成员有从属关系的信息。例如,假设用一个结构表示一辆汽车。如果汽车属于驾驶者,就要用一个结构成员来描述这个所有者。如果汽车被租赁,那么需要一个成员来描述其租赁公司。可以用下面的代码来完成

struct owner {
	char socsecurity[12];
	...
};
struct leasecompany {
	char name[40];
	char headquarters[40];
	...
};
union data {
	struct owner owncar;
	struct leasecompany leasecar;
};
struct car_data {
	char make[15];
	int status; /* 私有为0,租赁为1 */
	union data ownerinfo;
	...
};
//	假设flits是car_data类型的结构变量,
//	如果flits.status为0,程序将使用flits.ownerinfo.owncar.socsecurity
//	如果flits.status为1,程序则使用flits.ownerinfo.leasecar.name

8.2 匿名联合

struct owner {
	char socsecurity[12];
	...
};
struct leasecompany {
	char name[40];
	char headquarters[40];
	...
};
struct car_data {
	char make[15];
	int status; /* 私有为0,租赁为1 */
	union {
		struct owner owncar;
		struct leasecompany leasecar;
	};
	...
};
//	现在,如果 flits 是 car_data 类型的结构变量
//	可以用flits.owncar.socsecurity 代替flits.ownerinfo.owncar.socsecurity

总结:结构和联合运算符
成员运算符:.
一般注释:
该运算符与结构或联合名一起使用,指定结构或联合的一个成员。如果name是一个结构的名称, member是该结构模版指定的一个成员名,下面标识了该结构的这个成员:

name.member
name.member的类型就是member的类型。联合使用成员运算符的方式与结构相同。

间接成员运算符:->
一般注释:
该运算符和指向结构或联合的指针一起使用,标识结构或联合的一个成员。假设ptrstr是指向结构的指针,member是该结构模版指定的一个成员,那么:
ptrstr->member
标识了指向结构的成员。联合使用间接成员运算符的方式与结构相同。

9. 枚举类型

​ 可以用枚举类型(enumerated type)声明符号名称来表示整型常量。

enum spectrum {
    red, 
    orange, 
    yellow, 
    green, 
    blue, 
    violet
};
enum spectrum color;

9.1 enum常量

​ 只要是能使用整型常量的地方就可以使用枚举常量。例如,在声明数组时,可以用枚举常量表示数组的大小;在switch语句中,可以把枚举常量作为标签。

9.2 默认值

​ 默认情况下,枚举列表中的常量都被赋予0、1、2等。因此,下面的声明中nina的值是3:
enum kids {nippy, slats, skippy, nina, liz};

9.3 赋值

//	在枚举声明中,可以为枚举常量指定整数值:
enum levels {low = 100, medium = 500, high = 2000};
//	如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被赋予后续的值。例如,假设有如下的声明:
enum feline {cat, lynx = 10, puma, tiger};
//那么,cat的值是0(默认),lynx、puma和tiger的值分别是10、11、12

10. typedef简介

​ typedef工具是一个高级数据特性,利用typedef可以为某一类型自定义名称。这方面与#define类似,但是两者有3处不同:

​ 与#define不同,typedef创建的符号名只受限于类型,不能用于值。

typedef unsigned char BYTE;
//	随后,便可使用BYTE来定义变量:
BYTE x, y[10], * z

11. 其他复杂的声明

int board[8][8];   // 声明一个内含int数组的数组
int ** ptr;       // 声明一个指向指针的指针,被指向的指针指向int
int * risks[10];   // 声明一个内含10个元素的数组,每个元素都是一个指向int的指针
int (* rusks)[10];  // 声明一个指向数组的指针,该数组内含10个int类型的值
int * oof[3][4];   // 声明一个3×4 的二维数组,每个元素都是指向int的指针
int (* uuf)[3][4];  // 声明一个指向3×4二维数组的指针,该数组中内含int类型值
int (* uof[3])[4];  // 声明一个内含3个指针元素的数组,其中每个指针都指向一个内含4个int类型元素的数组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值