1.本章学习内容
1.1 学习内容总结
- 结构体的定义和赋值
- 第一种定义
基础:一定要使用 struct 语句
- 第一种定义
struct tag {
member-list
member-list
member-list
...
};
-
tag 是结构体的昵称,标签
-
menber-list 是结构体成员,是标准的变量定义,例如 int i,float j,char str;一个结构体可以有不同类型的变量定义
它的功能是 在使用这个结构体时有
例如:
struct tag s1
struct tag s2
-
第二种定义
取名 typedef 我们一般创建结构体都是使用typedef
其使用是
typedef struct {
member-list
member-list
member-list
...
}variable-list;
这里不需要标签 tag 因为如果每次都是 struct tag来定义结构体变量的话就太麻烦了 所以引入typedef
variable-list 结构变量,定义在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量。
在定义结构体变量时 可以有
例如:
variable-list s1
variable-list s2
variable-list 由我们自己取名 尽量简洁~
总结:关键字struct和它后面的结构名一起组成一个新的数据类型名
- 结构体数组
- 引入:一个结构体变量可以存放一组信息,那么如果有十组信息该如何存放呢?
不可能是将上面程序复制粘贴十次的!这时候需要使用结构体数组,结构体数组的每一个元素都是一个结构体类型的变量,都包含结构体中所有的成员项。 - 例如:
我这里定义了一个结构体
- 引入:一个结构体变量可以存放一组信息,那么如果有十组信息该如何存放呢?
typedef struct
{
int num;//学号
char name[10];//姓名
int math, english, computer;//三门成绩
double average;//个人平均分
}STU;
我要存放十个学生的信息
则结构体变量定义是 STU stu[10]
这就定义了一个结构体数组,共有 10 个元素,每个元素都是一个结构体变量,都包含所有的结构体成员
结构体数组的引用与引用一个结构体变量在原理上是一样的。只不过结构体数组中有多个结构体变量,我们只需利用 for 循 环一个一个地使用结构体数组中的元素。
- 结构体数组的初始化
- 类似数值数组
例如:
STU stu[10]={{1,"陈莉莉",12,90,50},{2,"郝厉害",50,65,100}}//定义同时初始化,未定义的都为0
结构体变量 | 学号 | 姓名 | 数学 | 英语 | 计算机 | 个人平均分 |
---|---|---|---|---|---|---|
stu[0] | 1 | 陈莉莉 | 12 | 90 | 50 | |
stu[1] | 2 | 郝厉害 | 50 | 65 | 100 | |
stu[2] | 0 | 0 | 0 | 0 | 0 | |
…… | 0 | 0 | 0 | 0 | 0 | |
stu[9] | 0 | 0 | 0 | 0 | 0 |
- 结构体数组的排序做法
- 借用上面的例子,给十名同学按个人平均分的高低排序、并输出排序结果。
- 注:字符串的复制 要使用库函数strcpy() 字符串的比较 要使用库函数strcmp 这里不涉及,补充知识
这里涉及数组比较 - 思路:
- 例题 伪代码
STU stu[10]={给十位同学赋值};
for i=0 to 9
stu[i].average=(stu[i].math+stu[i].english+stu[i].computer)/3
end for//计算每个人的平均分
STU *p=stu;
for i=1 to 9
for j=0 to 9-i
if( (p+j)->average < (p+j+1)->average )//降序
STU temp;//这要交换整个结构体所以temp类型是STU
temp=*(p+j);
*(p+j)=*(p+j+1);
*(p+j+1)=temp;
end if
end for
end for
//按表输出
printf("学号 姓名 个人平均分
");
for i=0 to 9
printf("%-4d%4s%4f",(p+i)->num,(p+i)->name,(p+i)->average);
end for
-
结构体指针
- 结构体指针指向结构体变量。
例如:STU s1={1,"陈莉莉",12,90,50};
这里定义一个指针STU *p
p=&s1或者p=s1
- 结构体指针的使用
(1) 用*p
访问结构成员。如:
(*p).math = 100;
等价于s1.math=100
(2) 用指向运算符->
访问指针指向的结构成员。如:
p->math = 100;//这是常用方法 也比较方便
- 结构体指针指向结构体变量。
-
共用体、枚举类型做法
- 共用体:使几个不同的变量共占同一段内存的结构,称为“共用体”类型的结构。
定义:
- 共用体:使几个不同的变量共占同一段内存的结构,称为“共用体”类型的结构。
union 共用体名
{
成员表列;
}变量表列;
- 结构体类型变量所占内存长度是各成员占的内存长度之和。
- 共用体类型变量所占内存长度等于最长的成员的长度
两者用法差不多,但是其所占内存不同
例如:
union Data{
int n; // 占用 2字节
char ch; // 占用 1 字节
float x; // 占用 4 字节
} ;
这个变量类型内存占4字节
struct Data{
int n; // 占用 2字节
char ch; // 占用 1 字节
float x; // 占用 4 字节
} ;
这个结构体变量类型则占了2+1+4=7个字节的内存
-
枚举类型
枚举类型:enum weekday {sun, mon, tue, wed, thu, fri, sat};
枚举变量:enum weekday workday, week_end;- 注:枚举如果第一个元素不进行定义 则默认初始化为0 后面的元素依次+1
没有指定值的枚举元素 它的值默认是前一个元素的值+1 - 使用:若一个变量只有几种可能的值,可以定义为枚举类型。所谓“枚举”是指将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。
- 注:枚举如果第一个元素不进行定义 则默认初始化为0 后面的元素依次+1
-
文件读写
- 文件的定义:
FILE *FP
对文件进行处理需要:
1.定义文件指针
2.打开文件fp=fopen("stu.txt","r");
3.文件读写
fscanf(fp,"%s%s%d%d%d,p->……);
或者
fgets(str,100,fp);
4.关闭文件
fclose(fp);
- 文件的定义:
-
关于文件打开 **fopen("文件名","文件打开方式")
如果打开成功 就会返回文件缓冲区等地址
如果无法成功打开就会返回一个 NULL
exit(0):关闭所有打开的文件,并终止程序执行
如果括号里是非零参数 则是表示不正常结束否则是正常结束进程
文件打开方式参数表
文本文件 | |
---|---|
使用方式 | 含义 |
”r“ | 打开只读文件 |
”w“ | 建立只写新文件 |
”a” | 打开添加写文件 |
“r+” | 打开读/写文件 |
“w+” | 建立读/写新文件 |
“a+” | 打开读/写文件 |
慎重使用w,如果w打开一个文件,如果这个文件已经存在那么这个文件会被删除重新创建
-
文件的遍历
- feof(fp)
若返回到结尾则返回1
否则0
- feof(fp)
-
几个文件读写函数
- 字符读写函数
fgetc()
:从文本文件中读取字符
fputc()
:将字符读进文本文件中 - 字符串读写函数
fgets(s,n,fp)
:从文本文件中读取字符串
fputs(s,fp)
:将字符串s写入文本文件fp中 - 格式化读写函数
fscanf
:从文件中写入 遇到回车,空格,Tab时都会停止
fprintf
:将字符输出在文件中 - 检测文件读写出错函数 `ferror()``
- 清除末尾标志和出错标志函数
clearerr()
- 文件定位函数
fseek(fp,offset,from)
:
offser:移动偏移量,long型 有正负之分 -是向左边移动 否则是右边移动
from:开始位置 即起始位置 通常 首位,当前位置,结束位置分别由0,1,2表示或者常量SEEK_SET,SEEK_CUR,SEEK_END表示
ftell()
:获取文件当前位置;即这个位置距离开头位置的位移量
rewind()
:定位文件指针,使文件指向读写文件的首位置,即文件打开时的首位置
- 字符读写函数
1.2 本章学习体会
- 学习体会
能感觉到学的东西越来越难了,东西也越来越抽象了,代码量越来越大了,比如做一个很大的程序就会有一两千行的代码,很可怕,对代码量的要求也更高,特别是文件读写的函数特别多,不熟练就无法运用自如,文件的应用还是很多的,结构体还有延伸到枚举类型和共用体,觉得C语言真的特别多东西,乍一看是简单的但写出来实在还是很困难的,而且有利用到了递归知识,我对递归这个概念只能读,无法自己写一个递归程序,所以经过本章学习,我开始害怕后面的链表和数据结构了,担心跟不上进度,只能再接再厉了!
- 代码量
周次 代码量 合计 15 415 16 1578 1993
2.综合作业--“我爱成语”
本次作业要编写一个成语游戏。用户登录后,系统随机出若干个成语,如果答对则得分。用户得分写入成绩文件。系统能对用户成绩排名
2.1 文件介绍
2.1.1 头文件介绍
idiom.h
- 这个头文件主要用来存一个结构体 然后使用结构体数组将成语和释义分开 里面声明的函数都是游戏的主要函数
- 主要的函数声明
int GetIdiom(IDIOM *idioms, FILE *fp);//存放成语文件
void IdiomGame(IDIOM *idiom,int num);//游戏题目
void GetResult(char *result,int *count);//判断结果
void SearchIdiom(IDIOM *idioms,int num);//查询字典
int SolitaireIdiom();//成语接龙
-
头文件代码截图
-
menu.h
- 这个头文件主要是用来存用户的账号和密码的结构体 以及一些有关于登陆系统的检查函数和菜单相关函数 以及一个用户名的全局变量来确认当前操作的用户是谁
- 主要的函数声明
void LoginMenu();//登录界面
int CheckUser(USER userStr, FILE *fp);//匹配用户和密码
void GameMenu(IDIOM *idioms,int num);//游戏界面
-
头文件代码截图
-
ranking.h
- 这个函数主要是用来存一个结构体 其包括排名需要输出的内容 以及跟排名有关的函数
- 主要的函数声明
int GetFraction(int count);//计算得分
void EntryRank(int socre);//录入排名,对排名进行操作
void OutputRank();//输出排名
- 头文件代码截图
2.1.2 函数实现文件介绍
-
文件1:main.c
- 设计思路:
- 文件主要是有释义和成语 要将他们分别分装进结构体数组中
- 主要代码截图
- 设计思路:
-
文件2:Login.c
void LoginMenu() //这是一个登录系统 暂时没有注册功能 时间有限没有做这个拓展 同时记录当前登录的用户
- 主要代码截图
void GameMenu(IDIOM *idioms, int num) //这是游戏的进入界面 通过选择和各个功能环环相扣 这个界面结束意味着程序也结束了
- 主要代码截图
-
文件3:idiom.c
- 这个函数是我用来实现所有函数的 有登陆验证函数读写文件函数有游戏出题函数有成语接龙函数有排名函数等等
-
int GetIdiom(IDIOM *idioms, FILE *fp)
- 实现文件的读入 分别存进结构体数组中
- 主要代码截图
-
int CheckUser(USER userStr, FILE *fp)
- 用来验证登录用户密码
- 主要代码截图
-
void IdiomGame(IDIOM *idiom, int num)
- 用来实现成语挖空出题的
- 主要代码截图
-
void GetResult(char *result, int *count)
- 用来验证答案和记录正确的题数
- 主要代码截图
-
void SearchIdiom(IDIOM *idioms,int num)
- 是查询词语功能函数
- 主要代码截图
-
int SolitaireIdiom()
- 成语接龙小游戏函数
- 主要代码截图
-
int GetFraction(int count)
- 用来计算得分
- 主要代码截图
-
void EntryRank(int socre)
- 用来修改排名,对排名进行重新排序的
- 主要代码截图
-
void OutputRank()
- 输出排名
- 主要代码截图
2.2 运行结果
2.2.1 登陆界面
2.2.2 游戏界面
2.2.3 排名界面
-
原来的排名
-
登录cc 然后玩一场游戏
排名可以发生变化 -
运行效果展示
GIF
2.3 大作业总结
1.碰到的问题即解决方法
-
第一个问题在登录界面,我的登陆系统是设置了循环的 就是匹配用户密码之后 如果输入正确就成功进入游戏 否则重新输入用户名和密码
- 但是我发现 当我第一次输入用户名和密码正确时就能成功进入游戏 但是如果第一次输错 后面不管输入的用户名密码是正确的还是错误的 都会显示错误
- 问题在我查找的时候文件指针后移了 但是我没有关闭文件 在我在匹配完之后 关闭文件发现进入了死循环 还是无法改正问题
- 最后发现是在我的循环问题上 我的循环问题打开文件没有放在循环里 所以关闭文件后 第二个循环文件没有打开 所以死循环了
-
第二问题是在出题的时候 我是使用两个随机数来控制我的出题 结果我发现每次出来随机数就已经固定了每次的题目都是一样的
- 所以这个时候需要一个随机种子 进行每次播种 每次播种随机数就会发生变化
- 这个随机种子的位置应该放在循环开始
-
第三个问题是查询的时候 本来我是使用打开文件的做法 后来发现没啥头绪
- 因为前面我已经将成语和成语解释分开存在结构体数组中了 所以我只要对结构体数组进行操作即可
-
第四个问题是我在写排名是 无法获取系统的时间
- 这个问题的解决方法 我是在网上找了一个代码 是获取本地系统的时间的代码
struct tm *t;
time_t tt;
time(&tt);
t=localtime(&tt);
printf("%4d年%02d月%02d日 %02d:%02d:%02d
",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
- 第五个问题是我在安排排名的时候 我是使用当前用户是谁 如果他已经完成过了游戏有加分 则修改它的加分然后进行排序 但是这个当前用户 我无法获取
- 最后我把用户的结构体变量变成一个全局变量、这样才能在排名的函数里识别当前用户
- 第六个问题在于文件读写 出现了非常多错误
- 首先是文件的打开 我只有读取不能写入
- 后来我把r改成了r+之后 它只能写入当前用户和第二名的排名 后面的用户排名都变成了烫烫烫
- 最后的解决方法就是 文件读完 修改完了之后关闭文件 第二次再打开 打开方式改成w+ 这样才能将排好序的排名写进文件里
2.小结
经过这次的成语游戏大作业,我感受到了全局变量的重要性以及结构体的重要性,结构体真的会比简单的数组要方便得多,同时经过这次的成语游戏大作业,我对文件的读写等方面都更加的熟练了,因为时间的原因扩展功能中还有一项注册没写,但是我这里的思路应该是注册然后将输入的用户名和密码存入到文件中即可。其次我觉得很妙的是利用随机数来对成语进行挖空,值得注意的是一个汉字是占两个字节,所以随机数应是偶数而且是两个相邻的数为一个字。还有排名,特别要注意写入文件防止覆盖。