第十三章 结构和其他数据类型
13.1示例问题:创建图书目录
程序清单14.1
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月5日12:46:05
//book.c--仅包含一本书的图书目录
#include <stdio.h>
#define MAXTITL 41 //书名的最大长度+1
#define MAXAUTL 31 //作者名的最大长度+1
struct book //结构模板:标记为book
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
}; //结构模板结束
int main(void)
{
struct book library; //把library声明为book类型的变量
printf("Please enter the book title.\n");
gets(library.title); //访问title部分
printf("Now enter the author.\n");
gets(library.author);
printf("Now enter the value.\n");
scanf("%f", &library.value);
printf("%s by %s: $%.2f\n", library.title, library.author, library.value);
printf("%s: \"%s\"($%.2f)\n", library.author, library.title, library.value);
printf("Done.\n");
return 0;
}
结果:
Please enter the book title.
Chicken of the Alps
Now enter the author.
Bismo Lapoult
Now enter the value.
14.95
Chicken of the Alps by Bismo Lapoult: $14.95
Bismo Lapoult: "Chicken of the Alps"($14.95)
Done.
下面有3个必须要掌握的技巧:
●建立结构的格式或布局。
●声明遵循该布局的变量。
●获取对一个结构变量的各个部件的的访问。
13.2建立结构声明
声明:
struct book
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
该声明描述了一个由两个字符数组和一个float变量组成的结构。它并没有创建一个实际的数据对象,而是描述了组成这类对象的元素(有时候也把结构声明叫做模板,因为它勾勒出数据该如何存储)。struct表示接下来是一个结构。后面是一个可选的标记(book),它是用来引用该结构的快速标记。
声明:
struct book library;
13.3定义结构变量
声明的简化:
struct book
{
char title[MAXTITL];
char author[AXAUTL];
float value;
} library; //在定义之后跟变量名
声明结构的过程和定义结构变量的过程可以被合并成一步。如下所示,将声明和变量定义合并在一起,是不需要使用标记的一种情况。
struct //无标记
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
} library;
13.3.1初始化结构
struct book library =
{
"The Pirate and the Devious Damsel",
"Renee Vivotte",
1.95
};
每个初始化项目和要初始化的结构成员类型相匹配。编译器所需要的是用逗号分割每个成员的初始化项目。
★结构初始化和存储类时期
在“存储类、链接和内存管理”中曾提到,如果初始化一个具有静态存储时期(比如静态外部链接、静态内部链接和静态空链接)的变量,只能使用常量值。这条规则同样也适用于结构。如果初始化一个具有静态存储时期的结构,初始化项目列表中的值必须是常量表达式。如果存储时期是自动的,列表中的值就不必是常量了。
13.3.2访问结构成员
结构成员运算符(.)可用来访问结构中的各个成员。例如,library.value就是library中的value部分。
gets(library.value);
scanf("%s", &library.value);
struct book, newt;
gets(bill.title);
gets(new.title);
13.3.3结构的指定初始化项目
C99支持结构的指定初始化项目,其语法与数组的指定初始化项目相似。例如,只初始化book结构的value,可以这样做:
struct book suprise =
{
.value
};
可以按任意顺序使用指定初始化项目:
struct book gift =
{
.value = 25.99,
.author = "James Broadfool",
.title = "Rue for the Toad"
};
正像数组一样,跟在一个指定初始化项目之后的常规初始化项目为跟在指定成员后的成员提供了初始值。另外,对待特定成员的最后一次赋值是它实际获得的值。
13.4结构数组
★结构和内存
manybook.c程序创建了一个内含100个结构变量的数组。由于该数组是自动存储类别的对象,其中的信息被储存在栈(stack)中。如此大的数组需要很大一块内存,这可能会导致一些问题。如果在运行时出现错误,可能抱怨栈大小或栈溢出,你的编译器可能使用了一个默认大小的栈,这个栈对于该例而言太小。要修正这个问题,可以使用编译器选项设置栈大小为10000,以容纳这个结构数组;或者可以创建静态或外部数组(这样,编译器就不会把数组放在栈中);或者可以减小数组大小为16。为何不一开始就使用较小的数组?这是为了让读者意识到栈大小的潜在问题,以便今后再遇到类似的问题,可以自己处理好。
程序清单14.2 manybook.c程序
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月5日15:05:07
//manybook.c--包含多本书的图书目录
#include <stdio.h>
#define MAXTITL 40
#define MAXAUTL 40
#define MAXBKS 100 //最多可以容纳的图书册数
struct book //建立book模板
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
int main(void)
{
struct book library[MAXBKS]; //book结构数组
int count = 0;
int index;
printf("Please enter the book title.\n");
printf("Press [enter] at the start of a line to stop.\n");
while (count < MAXBKS && gets(library[count].title) != NULL && library[count].title[0] != '\0')
{
printf("Now enter the author.\n");
gets(library[count].author);
printf("Now enter the value.\n");
scanf("%f", &library[count++].value);
while(getchar() != '\n')
continue; //清空输入行
if(count < MAXBKS)
printf("Enter the next title.\n");
}
if (count > 0)
{
printf("Here is the list of your books: \n");
for (index = 0; index < count; index++)
printf("%s by %s: $%.2f\n", library[index].title, library[index].author, library[index].value);
}
else
printf("No books? Too bad.\n");
return 0;
}
结果:
Please enter the book title.
Press [enter] at the start of a line to stop.
My Life as a Budgie
Now enter the author.
Mack Zackles
Now enter the value.
12.95
Enter the next title.
Here is the list of your books:
My Life as a Budgie by Mack Zackles: $12.95
13.4.1声明结构数组
struct book library[MAXBKS];
这条语句声明library为一个具有MAXBKS个元素的数组,数组的每个元素都是book类型的结构。
13.4.2标识结构数组的成员
在结构名后加一个点运算符,然后是成员名。
library[0].value; //第一个数组元素的value成员
library[0].value; //表示第三个结构书本名称的第5个字符。
13.5嵌套结构
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月7日07:39:01
//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 //第一个结构
{
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 = //初始化第一个变量
{
{"Ewen", "Villard"},
"grilled salmon",
"personality coach",
58112.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 > 15000.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;
}
结果:
Dear Ewen,
Thank you for the wonderful evening, Ewen.
You certainly prove that a personality coach
is a special kind of guy. We must get together
over a delicious grilled salmon and have a few laughs! !
See you soon,
Shalala
13.6指向结构的指针
使用指向结构的指针的原因:第一,就像指向数组的指针比数组本身更容易操作一样,指向结构的指针通常都比结构本身更容易操作。第二,在一些早期的C实现中,结构不能作为参数被传递给函数,但指向结构的指针可以。第三,许多奇妙的数据表示使用了包含指向其他结构的指针的结构。
#define _CRT_SECURE_NO_WARNINGS 1
//friends.c -- 使用指向结构的指针
//2022年6月7日08:05:25
#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",
58112.00
},
{
{"Rodney", "Swillbelly"},
"tripe",
"tabloid editor",
232400.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;
}
结果:
address #1: 00CFFA1C #2: 00CFFA70
pointer #1: 00CFFA1C #2: 00CFFA70
him->income is $58112.00: (*him).income is $58112.00
him->favfood is tripe: him->handle.last is Swillbelly
13.6.1声明初始化结构指针
struct guy * him;
现在him可以指向任何现有的guy类型的结构。
him = &barney; //barney是一个结构
和数组不同,一个结构的名字不是该结构的地址,必须使用&运算符。在一些系统中,结构的大小有可能大于它内部个成员大小之和,那是因为系统对数据的对其存储需求会导致缝隙。例如,系统有可能必须把每个偶数地址的成员放在4的倍数的地址上,这样的结构就有可能在其内部存在存储间隙。
13.6.2使用指针访问成员
指针him现在指向结构fellow[0]。
第一种,也是最常用的方法,是使用一个新运算符:->。
第二种方法从下面的序列得出:如果him=&fellow[0],那么*him=fellow[0]。因此有,
fellow[0].income == (*him).income
13.7向函数传递结构信息
现在的C实现允许把结构作为参数传递,或把指向结构的指针作为参数传递。如果只关心结构的一部分,还可以将结构成员作为参数传递给函数。
13.7.1传递结构函数
只要结构成员是具有单个值的数据类型(即:int及其相关类型、char、float、double或指针),就可以把它作为参数传递给一个接受特定类型的函数。
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月7日08:52:59
//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",
3024.72,
"Lucky's Savings and Loan",
9237.11
};
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 $12261.83.
13.7.2使用结构地址
程序清单14.6 funds2.c程序
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月7日09:02:32
//funds2.c -- 传递指向结构的指针
#include <stdio.h>
#define FUNDLEN 50
struct funds
{
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(const struct funds *); //参数是一个指针
int main(void)
{
struct funds stan =
{
"Garlic-Melon Bank",
3024.72,
"Lucky's Savings and Loan",
9237.11
};
printf("Stan has a total of $%.2f.\n", sum(&stan));
return 0;
}
double sum(const struct funds* money)
{
return (money->bankfund + money->savefund);
}
结果:
Stan has a total of $12261.83.
13.7.3把结构作为参数传递
程序清单14.7 funds3.c程序
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月7日09:13:51
//funds3.c--把结构作为参数
#include <stdio.h>
#define FUNDLEN 50
struct funds
{
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(struct funds moolah); //参数是一个结构
int main(void)
{
struct funds stan =
{
"Garlic-Melon Bank",
3024.72,
"Lucky's Savings and Loan",
9237.11
};
printf("Stan has a total of $%.2f.\n", sum(stan));
return 0;
}
double sum(struct funds moolah)
{
return (moolah.bankfund + moolah.savefund);
}
结果:
Stan has a total of $12261.83.
13.7.4其他结构特性
现在的C允许把一个结构赋值给另一个结构,不能对数组这样做。如果n_data和o_data是同一类型的结构,可以像下面这样做:
o_data = n_data; //把一个结构赋值给另一个结构
这就使o_data的每个成员都被赋值成n_data相应成员的值,即使有一个成员是数组也照样完成赋值。也可以把一个结构初始化为另一个同样类型的结构:
struct names right_field =
{
“Ruthie","George"
};
struct names captain = right_field; //把一个结构初始化为另一个结构
现在的C(包括ANSI C)中,结构不仅可以作为参数传递给函数,也可以作为函数返回值返回。把结构作为函数参数可以将结构信息传递给一个函数,使函数返回结构可以将信息从被调函数传递给调用函数。同时,结构指针也允许双向通信,因此可以使用任一种方法解决编程问题。
程序清单14.8 names1.c程序
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月7日09:31:17
//names1.c -- 使用指向结构的指针
#include <stdio.h>
#include <string.h>
struct namect
{
char fname[20];
char lname[20];
int letters;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
int main(void)
{
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
return 0;
}
void getinfo(struct namect* pst)
{
printf("Please enter your first name.\n");
gets(pst->fname);
printf("Please enter your last name.\n");
gets(pst->lname);
}
void makeinfo(struct namect* pst)
{
pst->letters = strlen(pst->fname) + strlen(pst->lname);
}
void showinfo(const struct namect* pst)
{
printf("%s %s, yours name contains %d letters.\n", pst->fname, pst->lname, pst->letters);
}
结果:
Please enter your first name.
Viola
Please enter your last name.
Plunderfest
Viola Plunderfest, yours name contains 16 letters.
程序清单14.9 names2.c程序
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月7日10:26:50
//names2.c -- 传递和返回函数
#include <stdio.h>
#include <string.h>
struct namect
{
char fname[20];
char lname[20];
int letters;
};
struct namect getinfo(void);
struct namect makeinfo(struct namect);
void showinfo(struct namect);
int main(void)
{
struct namect person;
person = getinfo();
person = makeinfo(person);
showinfo(person);
return 0;
}
struct namect getinfo(void)
{
struct namect temp;
printf("Please enter your first name.\n");
gets(temp.fname);
printf("Please enter your last name.\n");
gets(temp.lname);
return temp;
}
struct namect makeinfo(struct namect info)
{
info.letters = strlen(info.fname) + strlen(info.lname);
return info;
}
void showinfo(struct namect info)
{
printf("%s %s, oyur name contains %d letters.\n", info.fname, info.lname, info.letters);
}
13.7.5结构,还是指向结构的指针
把指针作为参数有两个优点:无论是以前还是现在的C实现都能使用这种方法,而且执行起来很快,只须传递一个单个地址。缺点是无法保护数据。被调函数中的某些操作可能会意外影响原来结构中的数据。不过,ANSI C新增的const限定符解决了这个问题。例如,如果在程序清单14.8中,showinfo()函数中的代码改变了结构的任意成员,编译器会捕获这个错误。
把结构作为参数传递的一个优点是函数处理的是原始数据的副本,这就比直接处理原始数据安全。编程风格也往往更清晰。
传递结构的两个缺点是早期的C实现可能不处理这种代码,并且这样做浪费时间和空间。把很大的结构传递给函数,但函数只是用一个或两个结构成员,这尤其浪费时间和空间。在这种情况下,传递指针或只将所需的成员作为参数传递会更合理。
程序员为了追求效率而使用结构指针作为函数参数;当需要保护数据、防止意外改变数据时对指针使用const限定词。传递结构值是处理小型结构最常用的方法。
13.7.6在结构中使用字符数组还是字符指针
#define LEN 20
struct names
{
char first[LEN];
char last[LEN];
};
//改写
struct pnames
{
cahr * first;
cahr * last;
};
struct names veep = {"Talia", "Summers"};
struct pnames treas = {"Brad", "Fallingjaw"};
printf("%s and %s\n", veep.first, trea.first);
对于struct names变量veep来说字符串存储在结构内部;这个结构共分配了40字节来存放两个字符串。然而,对于struct pnames变量treas来说,字符串存储在编译器存储字符串常量的任何地方。这个结构中存放的只是两个地址而已。struct pnames结构不为字符串分配任何存储空间。它只适用于在另外的地方已经为字符串分配了空间(例如字符串常量或数组中的字符串)。简单地说,pnames结构中的指针应该只用来管理那些已创建的而且在程序其他地方已经分配过空间的字符串。
我们看看这种限制在什么情况下出问题。考虑下面的代码:
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 的指针也行,但是误用会导致严重的问题。
13.7.7结构、指针和malloc()
在结构中使用指针处理字符串的一个有意义的例子使用malloc()分配内存,并用指针来存放地址。这个方法的优点是可以请求malloc()分配刚好满足字符串需求数量的空间。
程序清单14.10 names3.c程序
#define _CRT_SECURE_NO_WARNINGS 1
//2022年6月8日08:08:28
//nmaes3.c -- 使用指针和malloc()函数
#include <stdio.h>
#include <string.h> //为了使用strcpy(),strlen()
#include <stdlib.h> //为了使用malloc(),free()
struct namect
{
char * fname; //使用指针
char * lname;
int letters;
};
void getinfo(struct namect *); //分配内存
void makeinfo(struct namect *);
void showinfo(const struct namect *);
void cleanup(struct namect *); //用完后释放内存
int main(void)
{
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
cleanup(&person);
return 0;
}
void getinfo(struct namect * pst)
{
char temp[81];
printf("Please enter your first name.\n");
gets(temp);
//分配用来存放名字的内存
pst->fname = (char *)malloc(strlen(temp) + 1);
//把名字复制到已分配的内存在
strcpy(pst->fname, temp);
printf("Please enter your last name.\n");
gets(temp);
pst->lname = (char *)malloc(strlen(temp) + 1);
strcpy(pst->lname, temp);
}
void makeinfo(struct namect* pst)
{
pst->letters = strlen(pst->fname) + strlen(pst->lname);
}
void showinfo(struct namect* pst)
{
printf("%s %s, your name contains %d letters.\n", pst->fname, pst->lname, pst->letters);
}
void cleanup(struct namect * pst)
{
free(pst->fname);
free(pst->lname);
}
结果:
Please enter your first name.
Australopithecines
Please enter your last name.
Mann
Australopithecines Mann, your name contains 22 letters.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)