c博客06-2019-结构体&文件

1.本章学习总结

1.1 学习内容总结

  • 结构体的概念、定义以及成员赋值
    结构类型是一种允许程序员把一些数据分量聚合成一个整体的数据类型。结构类型定义的一般形式为
struct 类型名
{
    类型名 结构成员名1;
    类型名 结构成员名2;
    ···
    类型名 结构成员名n;
}

结构变量可以通过结构成员操作符‘.’来对其成员进行引用,例如:s1.average。之后的赋值操作类似于同类型变量的赋值操作。而且如果两个结构变量的类型相同,亦可以直接赋值,如:'max=s1',其中max和s1为同类型的结构变量,这样子可以将一个结构变量的所有成员都复制给另一个。

  • 结构类型的嵌套
    一个较大的实体可能由多个成员构成,而这些成员中有些又可能由一些更小的成员构成,这时就可以利用到结构类型的嵌套,就是结构体之中又有结构体。例如:
struct address
{
    char city[10];
    char steet[20];
    int code;
    int zip;
};
struct nest_student
{
    int num;
    char name[10];
    struct address addr;
    int computer,english,math;
    double average;
};

值得注意的是,在使用结构体嵌套之前应先定义内结构体。

  • 结构变量定义和初始化

    1.单独定义
    单独定义是指先定义一个结构类型,再定义这种结构类型的变量。例如:

struct student
{
    int num;
    char name[10];
    struct address addr;
    int computer,english,math;
    double average;
};
struct student s1,s2;

关键字struct和结构名必须联合使用才能够表示一个数据类型名。

2.混合定义
混合定义是指在定义结构类型的同时定义结构变量。例如:

struct student
{
    int num;
    char name[10];
    struct address addr;
    int computer,english,math;
    double average;
}s1,s2;

3.无类型名定义
无类型名定义是指在定义结构变量的时省略结构名。例如:

struct
{
    int num;
    char name[10];
    struct address addr;
    int computer,english,math;
    double average;
}s1,s2;
  • 结构数组
    结构数组是结构和数组的结合体,与普通数组的不同之处在于每个数组元素都是一个结构类型的数据,包括多个成员项。具体定义和初始化如下:
    struct student students[50]={{101,"zhang",76,85,78},{102,"wang",83,92,86}}
    对结构数组元素成员的引用时通过使用数组下标与结构成员操作符"."相结合的方式来实现,对数组成员的使用方法与同类型的变量完全相同。例如:
    students[10].num, strcpy(students[10].name,"zhang")

  • 结构指针
    指针可以指向任何一种类型的变量,结构指针就是指向结构类型变量的指针。例如:

struct student students={101,"zhang",76,85,78},*p;
p=&students;

定义了结构指针后,可以通过结构变量s1直接访问结构成员,也可以通过指针变量P间接访问它所指向的结构变量中的各个成员。具体有两种形式。

1.用p访问结构成员,如:(*p). num=101。注意:**成员运算符“.”的优先级高于“”的优先级**
2.用指向运算符->访问指针指向的结构成员,如p->num= 101

以上两种形式最终得到的效果是一样的,但在使用结构指针访问结构成员时,通常使用指向运算符->。
结构指针也可以作为函数参数,直接传地址,比起传进去整个结构体,效率更高,代价更小,例如:
int update_score ( struct student * p, int n, int num, int course, int score );

  • 结构体数组排序做法
    一般而言有三个步骤:
    1.定义一个结构类型临时变量
    2.确定排序的规则
    3.选择排序的方法(选择排序或者冒泡排序)
    结构体数组选择排序的一个实例如下:
void Sort(struct student* p, int n)
{
	int i;
	int j;
        int index;
	struct student temp;

	for (i = 0;i < n;i++)
	{
		for (j = i + 1;j < n;j++)
		{
			if (p[i].sum < p[j].sum)
			{
				index=j;
			}
		}
                temp = p[i];p[i] = p[index];p[index] = temp;
	}
}
  • 共用体做法
    共用体的一般声明形式如下所示:
    union 联合名
    {
    成员声明
    成员声明
    ···
    }变量列表;
    上面的形式可以用来定义名为“联合名”的共用体,该联合包含所有列出的成员变量,其变量的定义和结构相似。联合中的所有成员共享同一块内存空间,C语言编译程序保证分配给联合的内存能够容纳其最大的成员变量。实例如下:
union shared
{
    longlong int i;
    long int w[2];
} swap={ Oxfffffff} ;

结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。因而编程者应该保证访问联合变量时采用的成员变量和最后一次向其中存人值时采用的成员变量相同。

  • 枚举类型做法
    定义枚举数据类型的一般格式如下所示:
    enum 枚举名 {枚举值1,枚举值2,...} 变量列表;
    例如:enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
    编译程序将从0开始逐个给枚举值赋值。如果某个枚举值标识符后面跟有等号和常量表达式,那么编译程序就将该常量表达式的值作为该枚举值的值。该枚举值后面的枚举值从这个枚举值开始逐个加1,重新编号。编译程序将枚举值当作常量。某个枚举变量的值只能是定义时列出的枚举值之一。这样可以限制枚举变量的值,更加安全,而且使用特定的符号表示某个常量使得代码的可读性更高。

  • 文件读写
    文件在读写之前需要选择打开的方式
    文本文件(ASCI):

    使用方式|含义
    :-😐:-:
    "r"|打开文本文件进行只读
    "w"|建立新文本文件进行只写
    "a"|打开文本文件进行追加
    "r+"|打开文本文件进行读/写
    "w+"|建立新文本文件进行读/写
    "a+"|打开文本文件进行读/写/追加
    二进制文件:

    使用方式|含义
    :-😐:-:
    "rb"|打开二进制文件进行只读
    "wb"|建立二进制文件进行只写
    "ab"|打开二进制文件进行写/追加
    "rb+"|打开二进制文件进行读/写
    "wb+"|建立二进制新文件进行读/写
    "ab+"|打开二进制文件进行读/写/追加

打开文件后选择适合的读写操作函数来操作文件的数据,常用的函数如下:

1.字符方式文件读写函数:fgetc ()和fputc ();
2.字符串方式文件读写函数: fputs ()和fgets ();
3.格式化方式文件读写函数: fscanf ()和fprintf ( );
4.数据块方式文件读写函数: fread ()和fwrite( )。

  • 文件中数据如何读进结构体数组

步骤如下:

1.开辟结构体
2.打开文件
3.根据文件内的数据选择适合的读取操作函数
4.关闭文件

具体实现代码如下:

typedef struct student
{
	int num;
	char name[20];
	int score;
}STU, *STULINK;

FILE*fp = NULL;
STU students[50];

fp = fopen("stu.txt", "r+");
	if (fp == NULL)
	{
		printf("open file error!");
		exit(1);
	}

GetData(fp, students);

if (fclose(fp) != 0)
	{
		printf("cannot close the file!");
		exit(1);
	}
void GetData(FILE*fp, STULINK students)
{
        int i=0;

	while (!feof(fp))
	{
		fscanf(fp, "%d%s%d", &students[i].num, students[i].name, &students[i].score);
		i++;
	}
}

1.2 本章学习体会

  • 学习感受
    这学期也是接近了尾声,C的课程教学也基本结束了,经过这一个学期的学习,我已然不是当初的小白了,现在我对编程有了自己的理解和感悟,已经能够运用学习到的知识来写一些小游戏了,很高兴自己能够走到今天,庆幸自己学到了一些东西,希望自己对编程的热爱能够贯彻大学四年。

  • 计算这两周代码量

    代码量来源|2019-c11-递归函数及结构体|我爱成语大作业|总计
    :-😐:-😐:-:
    代码量|187|942|1129


2.综合作业--“我爱成语”

2.1.文件介绍

头文件介绍

idiom.h

结构体:

结构体 功能
IDIOM 存储成语及其解释
SALON 存储接龙成语
USER 存储用户数据

函数声明:

函数声明 功能
FILE* OpenFile(FILE* fp, const char* fileName) 打开文件
void CloseFile(FILE*fp) 关闭文件
IDIOM* CreateIdiomsLink(FILE* fp, int* num) 建立链表存储成语+释义
SALON* CreateIdiomSalonLink(FILE* fpSalon, int* salonNum) 建立链表存储接龙成语
void DelSalonLink(SALON* idioms) 销毁接龙成语链表
void DelIdiomLink(IDIOM* idioms) 销毁成语+释义链表
USER* CreateUserLink(FILE* fpUser, int* userNum) 建立链表存储用户数据
void DelUserLink(USER* users) 销毁用户数据链表
void Sort(USER* users, int response) 对用户数据排序
void PrintUserData(USER* users) 输出用户数据
void UpdateUserData(FILE* fpUser, USER* users, int userNum) 更新用户数据到文件中
void GuessIdiom(IDIOM* idioms, USER* loginUser, int idiomNum) 猜成语游戏
void IdiomDictionary(IDIOM* idioms) 成语字典
IDIOM* PreciseSearch(IDIOM* idioms) 精确查询
void FuzzySearch(IDIOM* idioms) 模糊查询
void SalonGameMenu() 接龙游戏菜单
int IsRight_Man_Machine(SALON* idiomsSalon, char* answer, char* question, int questionLen) 人机模式下判断回答的正确性
int IsRight_Man_Man(SALON* idiomsSalon, char* answer, char* question, int questionLen, int* wrong, int* score) 人人模式下判断回答的正确性
void IdiomSalonGame(SALON* idiomsSalon, USER* loginUser, int salonNum) 成语接龙游戏
void Man_MachinePlay(SALON* idiomsSalon, USER* loginUser, int salonNum) 人机模式
void Man_ManPlay(SALON* idiomsSalon, USER* loginUser, int salonNum) 人人模式
SALON* GetAnswer(SALON* idiomsSalon, char* question, int questionLen) 机器作答
int GuaranteeValid(char left, char right) 保证输入有效
void CorrectMessage() 正确信息
void IncorrectMessage() 错误信息
int Menu() 主菜单
int RegisterOrLogin(FILE* fpUser, char* userName) 登陆注册界面
int LoginMenu(FILE* fpUser, char* userName) 注册
void Register(FILE* fpUser, char* userName) 登录
USER* IdentifyUser(USER* users, char* userName) 确定登录的用户
  • 头文件代码截图

函数实现文件介绍

文件1:main.c

  • 文件功能及设计思路
    先只打开用户数据文件,进行登录信息验证或者注册。若成功登录,那么继续打开相关文件,读取数据到链表中。通过菜单来访问各个模块的功能。退出程序时,更新用户数据到文件中,以供下次使用,并销毁链表,关闭文件。
    • 相关流程如下:
打开文件userdata.txt
if 登录成功 then
	打开文件233个成语+释义,清洗版.txt
	打开文件13355个成语无释义.txt
	建立users、idioms和idiomsSalon链表
	确定登录用户
	do
	展示菜单
	    case 1:猜成语游戏
	    case 2:成语接龙游戏
	    case 3:成语字典
	    case 4:查看排名
	    case 0:退出程序
	while 不选择退出程序
	更新用户数据
	销毁三个链表
	关闭三个文件
end if
  • 主要代码截图

文件2:菜单.c

1.int Menu()

  • 设计思路及功能
    作为主菜单,让用户知道下一步如何操作
    • 相关流程如下:
输出菜单
记录用户选择
返回选择
  • 代码截图

2.int RegisterOrLogin(FILEfpUser,charuserName)

  • 设计思路及功能
    注册登录选择界面,使用switch语句选择进入的模块
    • 相关流程如下:
输出菜单
记录选择
switch (选择)
{
	case 1:进入注册
	case 2:进入登录
	case 0:退出
}
  • 代码截图

3.void Register(FILE* fpUser, char* userName)

  • 设计思路及功能
    用户注册模块,比对是否已经存在该用户名,若存在重新输入;若不存在,则注册成功进入登录界面
    • 相关流程如下:
用户输入要注册的用户名和密码
遍历userdata.txt文件寻找是否已存在
if 不存在 then
	进入登录
else 
	输出错误信息并重新注册
end if
  • 代码截图

4.int LoginMenu(FILE* fpUser, char* userName)

  • 设计思路及功能
    用户输入用户名和密码,比对是否与用户数据相匹配,若匹配则进入主界面,且限制仅能尝试三次
    • 相关流程如下:
用户输入用户名和密码
比对文件中的数据
匹配进入主界面
不匹配重新输入(至多三次机会)
  • 代码截图

5.void SalonGameMenu()

  • 设计思路及功能
    成语接龙游戏开头语
  • 代码截图

文件3:读取输出成语和用户信息.c

1.FILE* OpenFile(FILE* fp, const char* fileName)

  • 设计思路及功能
    传入文件名,打开文件
  • 代码截图

2.void CloseFile(FILE* fp)

  • 设计思路及功能
    关闭文件
  • 代码截图

3.IDIOM* CreateIdiomsLink(FILE* fp,int *num)

  • 设计思路及功能
    将一行的成语及其解释读入到idiom数组,并将':'改为'\0',将'\0'之前的成语名放入ptr->idiomName,之后的放入ptr->meaning,不断循环建立链表,直至文件结束
    • 相关流程如下:
创建头节点
while 未到文件尾 do
	读入数据到idiom
	在idiom中找到并将:改为'\0'
	复制成语名和释义到链表中
	成语数++
	前后节点建立关系
	尾指针移动到下一个节点
end while
尾指针的下一节点赋值为NULL
返回头节点
  • 代码截图

4.SALON* CreateIdiomSalonLink(FILE* fpSalon, int* salonNum)

  • 设计思路及功能
    从文件中读取成语放入链表之中
  • 代码截图

5.USER* CreateUserLink(FILEfpUser,int userNum)

  • 设计思路及功能
    从文件中读取用户数据放入链表之中
  • 代码截图

6.void DelSalonLink(SALON* idioms)

  • 设计思路及功能
    销毁链表
  • 代码截图

7.void DelIdiomLink(IDIOM* idioms)

  • 设计思路及功能
    销毁链表
  • 代码截图

8.void DelUserLink(USER* users)

  • 设计思路及功能
    销毁链表
  • 代码截图

9.USER* IdentifyUser(USERusers,charuserName)

  • 设计思路及功能
    根据用户名遍历users链表,确定登录用户
  • 代码截图

10.void UpdateUserData(FILE* fpUser, USER* users,int userNum)

  • 设计思路及功能
    运用fprintf函数输出链表内容到文件中,更新用户数据
  • 代码截图

11.void Sort(USER* users,int response)

  • 设计思路及功能
    运用选择排序法,交换两个节点的数据,实现从高到低排序
    • 相关流程如下:
//选择排序
for ptr1从users->next开始到尾节点或者交换十次 do
	记录ptr1
	for ptr2从ptr1->next开始到尾节点 do
		if 排序猜成语游戏排行榜 then
			if 有score更大 then
				记录ptr2
			end if
		else if 排序成语接龙(人机模式)游戏排行榜 then
			if 有score更大 then
				记录ptr2
			end if
		else if 排序成语接龙(人人模式)游戏排行榜 then
			if 有score更大 then
				记录ptr2
			end if
		end if
	end for
交换两个结构体的数据(而非交换节点)
交换回它们的next指针
cnt++
end for
  • 代码截图

12.void PrintUserData(USER* users)

  • 设计思路及功能
    控制格式结合for循环输出排行榜
  • 代码截图

文件4:读取输出成语和用户信息.c

1.void GuessIdiom(IDIOM* idioms, USER* loginUser,int idiomNum)

  • 设计思路及功能
    处理成语,输出题目,用户作答错误三次游戏结束
    • 相关流程如下:
输出菜单
i=1
for 直至用户错误三次 do
	生成随机数index、begin和end
	根据index的值随机移动链表到任意一个节点
	将给节点的成语复制到idiom中
	计算idiom的长度
	根据begin和end的值随机隐藏成语的两个字
	if 用户作答正确 then
		加五分,鼓励
	else 
		提示错误,显示答案
	end if
end for
if 此次得分高于历史最高得分 then
	输出相应信息
end if
输出此次得分
  • 代码截图

2.void IdiomDictionary(IDIOM* idioms)

  • 设计思路及功能
    选择查询方式,找到输出对应信息,未找到输出提示信息
    • 相关流程如下:
输出菜单
do
	输出菜单并记录选择
	switch (选择)
		case 1:精确查询
		case 2:模糊查询
		case 3:清空查询信息
		case 0:退出
while 不退出
  • 代码截图

3.IDIOM* PreciseSearch(IDIOM* idioms)

  • 设计思路及功能
    输入需要查找的成语,遍历链表查找,找到返回对应结构体地址,找不到返回NULL
  • 代码截图

4.void FuzzySearch(IDIOM* idioms)

  • 设计思路及功能
    比对关键字,匹配则输出,不匹配则输出提示信息
    • 相关流程如下:
输入关键字
for循环遍历链表
	for遍历成语
		if 成语中包含关键字 then
			输出成语并标记找到过
			退出循环
		end if
	end for
end for
if 未找到 then
	输出提示信息
end if
  • 代码截图

5.void IdiomSalonGame(SALON* idiomsSalon, USER* loginUser,int salonNum)

  • 设计思路及功能
    输出菜单,根据用户选择进入相应的模块
  • 代码截图

6.void Man_MachinePlay(SALON* idiomsSalon, USER* loginUser, int salonNum)

  • 设计思路及功能
    开头随机出题,使用GetAnswer辅助机器作答,直至机器无法继续作答或者用户错误三次为止
    • 相关流程如下:
生成随机数index
根据index的值跳到链表的一个节点
出题
do
	读取用户答案
	调用函数IsRight_Man_Machine,并赋值给error
	if error==1 then
		加5分
		调用GetAnswer接下一个成语,并赋值给question
		if question不为空 then
			输出下一个成语
		else
			输出提示信息
			加50分作为奖励
			退出循环
		end if
	else
		if error==0 回答不是成语
		else (error==-1) 首字就错
		调用GetAnswer接下一个成语,并赋值给question
		if question不为空 then
			帮助用户作答,输出下一个成语
		else
			输出提示信息
			加10分作为奖励
			退出循环
		end if
while 错误次数小于3
if 此次得分高于历史最高得分 then
	输出相应信息
end if
输出此次得分	
  • 代码截图

7.void Man_ManPlay(SALON* idiomsSalon, USER* loginUser, int salonNum)

  • 设计思路及功能
    A,B轮流作答,用IsRight_Man_Man辅助判断是否为有效答案,直至错误三次为止
    • 相关流程如下:
生成随机数index
根据index的值跳到链表的一个节点
出题
do
	读取A的答案
	while 前后成语相同 do
		输出信息
		重新作答
	end while
	error = IsRight_Man_Man
	if error == 1(机器无法作答)then
		break
	end if
	if wrong == 3 (已错误三次)
		break
	end if
	读取B的答案
	while 前后成语相同 do
		输出信息
		重新作答
	end while
	error = IsRight_Man_Man()
	if error == 1(机器无法作答)then
		break
	end if
while 错误次数小于3
if 此次得分高于历史最高得分 then
	输出相应信息
end if
输出此次得分
  • 代码截图

8.int IsRight_Man_Machine(SALON* idiomsSalon, char* answer, char* question, int questionLen)

  • 设计思路及功能
    依次判断尾字和首字是否相同,成语是否存在
    • 相关流程如下:
if 前一个成语的尾字和答案的首字不相同 then
	返回-1
end if
for 遍历idiomsSalon链表
	if 有成语与答案相同 then
		break
	end if
end for
if 到了尾节点 then
	返回0
end if
有效作答返回1
  • 代码截图

9.int IsRight_Man_Man(SALON* idiomsSalon, char* answer, char* question, int questionLen,int *wrong,int *score)

  • 设计思路及功能
    基于IsRight_Man_Machine,对人人模式作答正确性进行判断,并输出相应信息
    • 相关流程如下:
error = IsRight_Man_Machine
if error == 1 (正确)then
	加5分
else
	if error == 0 (不是成语) 输出信息
	else (首字即错) 输出信息
	machineAnswer = GetAnswer
	if machineAnswer不为空 then
		将answer修改为正确答案
		输出信息
	else 
		输出信息
		加十分作为奖励
		返回1
	end if
end if
  • 代码截图

10.SALON* GetAnswer(SALON* idiomsSalon,char*question,int questionLen)

  • 设计思路及功能
    遍历idiomsSalon链表,寻找符合要求的成语,找到返回结构体的地址,找不到返回NULL
  • 代码截图

11.int GuaranteeValid(char left,char right)

  • 设计思路及功能
    保证输入有效性
    • 相关流程如下:
读取输入
while 不属于给定区间 do
	输出提示信息
	重新输入
end while
return (字符转数字)
  • 代码截图

2.2.运行结果

1.登录界面

  • 选择界面
  • 登陆界面

2.游戏界面

  • 猜成语游戏界面
    • 游戏开始界面
    • 游戏内界面
  • 成语接龙
    • 选择界面
    • 人机模式
    • 人人模式
  • 成语字典
    • 精确查询

    • 模糊查询

3.排名界面

  • 选择界面
  • 猜成语游戏排行榜
  • 成语接龙(人机模式)排行榜
  • 成语接龙(人人模式)排行榜

2.3大作业总结

1.碰到问题及解决办法

问题 解决办法
错误读取文件数据 读取数据时严格按照数据的格式读取
输出用户需要查看的信息时直接被清空 使用system("pause")来暂时中断程序运行
双向链表难以通过交换节点排序 改用单向链表,并且通过交换两个节点的数据来实现排序
输入字符程序就会崩溃 自己写一个可以保证输入有效性的函数

2.小结

  • 要灵活地运用形参和函数返回值,加强各个函数之间地联系,从而完成某一个功能
  • 在写完一些比较关键的函数时,应对其进行调试,确保关键的节点不会出差错
  • 写程序之前应该对自己的程序框架有一个清晰的认识,将其分解成一个个小模块来完成
  • 要善于借助网络上的资源,多方寻求解决问题的办法,一个问题往往有多种解法,选其最优
  • 灵活应用各种读写操作函数,分析数据来选择最合适的读写操作函数

posted @ 2019-12-15 18:13  朱振豪  阅读(550)  评论(0编辑  收藏  举报