*声明:文中给出的代码 往往 并非最佳解决方案,源于个人学习,仅供学习交流、抛砖引玉之用,请读者宽容看待,多多思考、多多突破。
*注:如遇安全性错误C4996可添加此行代码于文件头:#pragma warning(disable :4996)。
一、函数、类型 介绍 参考(相关权益归原作者所有):
外部函数:同在项目源文件夹中的代码文件,其中的函数如有需要是可以互相调用的。
无前缀的函数如:int MAX(){......}默认等同于extern int MAX(){......},是可以被此目录下其他代码文件调用的,但请注意,为规范编写、便于维护和团队合作,请不要直接引用其他源文件中的函数!每个源文件应有一个对应的 同名.h头文件来 注明其函数接口,需要调用时应直接引入相应头文件再调用,这样可以让编译器辅助检查函数的一致性。如需要设定为内部函数则应写为:static int MAX(){......},此时便不可被调用。此外static还有很多其他用途,更多可参考:
extern 与头文件(*.h)的区别和联系 | 菜鸟教程 (runoob.com)
C 语言中 static 的作用 | 菜鸟教程 (runoob.com)
二、案例解决方案代码分享:
1、随机点名(分为两文件部分)
文件:AddData.c
#include <stdio.h>
void AddData(FILE* fp)
{
int i;
char InPut[52];
printf("每条记录输入完后请回车以录入(最多999条数据!!)\n在“学号:”后输入“#”后回车结束录入\n");
for (i = 0;; i++) {
rewind(stdin);
printf("\n学号(不超过50位):\n");
fgets(InPut, 52, stdin); //fgets读入一行数据,会读入\n
if (InPut[0] == '#')
break;
fputs(InPut, fp); //写入一整行数据,如果数组内有\n,会写入\n,没有则不添加
printf("名字(不超过50位):\n");
fgets(InPut, 52, stdin);
fputs(InPut, fp);
} //重复读取判断并对文件进行写入。
fseek(fp, 0, 0); //写入完毕,指针回到开头。
printf("已新增%d条数据。\n", i);
}
文件:main.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
extern void AddData(FILE* fp); //声明从项目中另一个AddData.c文件引入的函数
typedef struct { //struct后一般要加类型名,此处对其整体进行了命名,所以并不必要,链表等结构体调用时需要使用此处类型名
char num[51];//学号
char name[51];//姓名
}Student; //自定义结构体并命名(命名用于方便书写类型名)
int NUM; //数据总条数
Student stud[500]; //用来存储数据
int RandomNUM(int list[])
{//数组的传递是传递第一个元素的地址,也可看作指向数组第一个元素的指针,不是值传递,所以最后数组会被改变,不用传回
int random;
srand((unsigned)time(NULL));
do {
random = rand() % NUM; //保证随机的学号在给定人数区间内1~(NUM-1)
} while (list[random] != 0); //避免重复点名
return (random);
}
void Tongj(int list[])
{
for (int i = -1; i++ < NUM;) {
if (list[i] != 2)
continue; //是0或者1就跳过重新i++并判断,由于人数较多,只输出缺勤人员
printf("%s%s", stud[i].num, stud[i].name); //输出人员信息
printf("缺\n\n");
}//如有需要 可以用循环判断+putchar避免输出字符串末尾的\n。而相反puts会自动多追加一个\n
}
int main()
{//a+表示直接在之前记录上追加、更新内容,无文件则创建;t表示以文本而非二进制格式读写,此命令会在文件目录下生成名为file.txt的文档来储存数据
FILE* fp = fopen("file.txt", "a+t"); //如有更复杂的需求,可用fseek库函数指回开头或调整位置
int list[500] = { 0 }, named = 0, random; //list相当于为所有数据重新对应了一个编号并储存统计信息
while (1) {
printf("开始点名?(添加记录请输入'g',输入其他直接开始)");
if (getchar() == 'g')
AddData(fp); //调用从项目中另一个AddData.c文件引入的函数
else
break;
}
for (NUM = 0; fgets(stud[NUM].num, 51, fp) && fgets(stud[NUM].name, 51, fp); NUM++);
//读入所有数据并用n记录总数据条数fgets读取错误等会返回空指针即NULL(0)用于判断。
do {
random = RandomNUM(list);
printf("学号:%s姓名:%s请报道!(1-到,2-没到,3-停止点名,输入其他重新随机)\n", stud[random].num, stud[random].name);
rewind(stdin);
switch (getchar()) {
case '1':list[random] = 1; break;
case '2':list[random] = 2; break;
case '3':continue; //continue可以跳过下面的if统计,直接到while的判断,由于到/缺必然被赋值,所以不赋值看作直接结束用于while判断
default:random = NUM; continue; //由于定义的RandomNUM函数取random时不会为NUM,此处借用来当while的判断符
}
if (++named == NUM) { //统计已点数据条数
printf("已全部点完\n");
Tongj(list); //输出统计结果
return 0;
}
} while (list[random] != 0 || random == NUM);
Tongj(list); //输出统计结果
return 0;
}
*注:两文件同时创建并放在项目源目录下即可。也可将AddData中的函数直接放到main.c中运行。
总结:
文件读写对于一个程序来说是完成更复杂任务的重要功能,使程序操作逻辑更贴近使用场景,有着重要意义,应给予重视并熟练掌握。而外部函数的引用对模块化开发有着重要帮助,虽并不必须,但可以提升程序可读性。