翁老师说现在很少有人直接用C语言来操作文件来储存或者读取数据了,稍微有点量的用数据库。个人觉得C语言实现文件操作还是挺有意思的。做个练习——一个简单的学生信息系统
头文件
#ifndef _STUDENT_H_
#define _STUDENT_H_
#define STR_SIZE 20
/*声明学生结构体*/
typedef struct _student{
int id;
char name[STR_SIZE];
int gender;
int age;
}STUDENT;
#endif
void getlist(STUDENT *std,int num);//读入学生信息
int save(STUDENT *std,int num);//储存至文件
void read(FILE *fp,int cnt);//读取数据
main函数,确定学生数量,调用getlist(),用数组存储(也可用链表);调用save(),将数据保存至文件;调用read(),读取数据。
#include <stdio.h>
#include <stdlib.h>
#include "student.h"
int main(int argc, char *argv[]) {
int num=0;
printf("请输入学生数量:");
scanf("%d",&num);
STUDENT std[num];//学生结构数组
getlist(std,num);//读取数据,也就是给给各元素的成员赋值
printf("是否保存数据?(1-保存,0-取消)");
int flag=-1;
scanf("%d",&flag);
if(flag){
if(save(std,num)){
printf("保存成功\n");
}else{
printf("保存失败\n");
}
}
/*读取*/
FILE *fp=fopen("student.date","r");
if(fp){
//fseek()函数可将文件指针指向某位置,SEEK_SET-文件头,SEEK_END-文件末,SEEK_CUR-文件当前位置,第二个参数是偏移字节数,+-决定偏移方向
fseek(fp,0,SEEK_END);
//ftell()函数,定位文件指针位置,返回文件头到该位置的字节数
int num=ftell(fp)/sizeof(STUDENT);
printf("共有%d个数据,要看第几个?",num);
int cnt;
scanf("%d",&cnt);
read(fp,cnt);
fclose(fp);
}
return 0;
}
分别用二进制模式和文本模式写入、读取文件
void getlist(STUDENT *std,int num){
char format[STR_SIZE];
sprintf(format,"%%%ds",STR_SIZE-1); //利用格式化输出,做一个格式字符串“%19s”
for(int i=0;i<num;i++){
printf("请输入第%d个学生的信息\n\t学号:",i+1);
scanf("%d",&std[i].id);
printf("\n\t姓名:");
scanf(format,std[i].name);//格式字符串的地址替代“%s”
printf("\n\t性别(0-女,1-男):");
scanf("%d",&std[i].gender);
printf("\n\t年龄:");
scanf("%d",&std[i].age);
}
}
int save(STUDENT *std,int num){
int ret=0;
FILE *fp=fopen("student.date","w");
if(fp){
ret=fwrite(std,sizeof(STUDENT),num,fp);//二进制写入文件
//文本写入文件
// for(int i=0;i<num;i++){
// fprintf(fp,"学号:%d",std[i].id);
// fprintf(fp,"\n姓名:%s",std[i].name);
// fprintf(fp,"\n性别:%s",(std[i].gender==1?"男":"女"));
// fprintf(fp,"\n年龄:%d",std[i].age);
// fprintf(fp,"\n");
// }
// ret=1;//文本模式下赋值1,代表执行到此,写入成功
fclose(fp);
}else{
printf("文件打开失败!");
}
// return ret;
return ret==num;//二进制写入文件fwrite()返回值为写入 某个结构体(某大小字节块) 的个数
}
/*读取文件内数据*/
void read(FILE *fp,int cnt){
//以文本模式读取文件内容,一次读入100个字符,fgets遇行末‘\0’停止、遇到换行停止。
// char s[100];
// while(fgets(s,100,fp)){
// printf("%s\n",s);
// }
//以二进制模式读取文件内容,可用fseek()函数定位至第几个结构。
STUDENT std;
fseek(fp,(cnt-1)*sizeof(STUDENT),SEEK_SET);
fread(&std,sizeof(STUDENT),1,fp);
printf("学号:%d\n",std.id);
printf("姓名:%s\n",std.name);
printf("性别:%s\n",std.gender==1?"男":"女");
printf("年龄:%d\n",std.age);
}
注意:r+和w+的读写模式,在第一次读取或者写入后,文件指针会指向读取或者写入的当前位置,无法继续进行反向操作。只有rewind()重置指针位置至文件头才能继续。
w和w+会清空原文件内容;
a和a+是在文件末追加内容,不会清空原文件内容;
r+会覆盖原文件内容。
补充知识点,格式化输入输出:
一、格式化输出
printf("%d",i)中,%d为格式化输出字符串在%和d中间可以添加4种参数:
%[flag][width][.prec][hil]type
1、flag
flag | 含义 |
- | 左对齐 |
+ | 显示+-符号 |
空格 | 正数留空 |
0 | 0填充所占位数 |
例:
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char const *argv[]){
int a=50,b=-100;
printf("|%-+9.5d|\n|%+-9.4d|\n",a,b);
system("pause");
return 0;
}
2、width和.prec
width/.prec | 含义 |
number | 输出所占字符数 |
.number | 对于float保留几位小数 对于int是填补0后共占用几个字符数 |
* | 将number作为参数替代字符串中的* |
.* | 将.number作为参数替代字符串中的.* |
例:
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char const *argv[]){
float a=50,b=-100;
printf("|%-+*.*f|\n|%+-*.*f|\n",9,6,a,9,5,b);
system("pause");
return 0;
}
由此可得当[width]和[.prec]冲突时,以[.prec]为主,width自动增加。
3、HIL
类型修饰(HIL) | 含义 |
hh | 以单字节形式输出 |
h | short |
l | long |
ll | long long |
L | long double |
hh以单字节形式输出:例如一个整形占4个字节,hh将最小字节上的内容输出。
例:12345 二进制最小字节内容为0011 1001,则输出为57。
但:我的电脑hhd单字节格式输出不正常, 不知道什么原因。。。
当printf("%hhd",(char)a);输出单字节整数时,对a进行强制类型转换,输出57正确。
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char const *argv[]){
int a=12345;
printf("%i\n",a);//a可以是8/10/16进制的任何一种
printf("%u\n",a);//unsigned 无符号整形
printf("%o\n",a);//八进制整形
printf("%x\n",a);//十六进制整形
printf("%hhd\n",(char)a);//单字节整形
unsigned int mask=1;
for(mask=1u<<31;mask;mask>>=1){//二进制
printf("%d",a&mask?1:0);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char const *argv[]){
int a=54321;
printf("%hhi\n",a);//a可以是8/10/16进制的任何一种
printf("%hhu\n",a);//unsigned 无符号整形
printf("%#o\n",a);//八进制整形 #可以显示8和16进制的前缀
printf("%#x\n",a);//十六进制整形
printf("%hhd\n",(char)a);//单字节整形
unsigned int mask=1;
for(mask=1u<<31;mask;mask>>=1){//二进制
printf("%d",a&mask?1:0);
}
return 0;
}
4、利用格式化输出可以自己制作格式化输出字符串。
例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define len 20
int main(int argc,char const *argv[]){
float a=50,b=-100;
char format[len];
sprintf(format,"%%%ds",len-1);//自定义一个格式化字符串"%19s"输出到format里面.
printf("%s\n",format);
char name[len];
scanf(format,name);
printf("%s\n",name);
return 0;
}
二、格式化输入
scanf("%d",&a);
%[flag]type
flag | 含义 | flag | 含义 |
* | 跳过 | l | long |
number | 输入最大字符数 | ll | long long |
hh | char | L | double long |
h | short |
type | 含义 | type | 含义 |
d | int | a/e/f/g | float |
i | 可8可10可16 | c | char |
u | unsigned int | s | 字符串 |
o | 8进制 | p | 指针 |
x | 16进制 | [^。] | 。前的所有字符 |
*跳过输入指定格式;i的输入格式可为10、8、16三种进制
例:
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char const *argv[]){
int num;
scanf("%*d%i",&num);
printf("%d",num);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#define len 20
int main(int argc,char const *argv[]){
char str[len];
scanf("%*[^.].%[^.].",str);
//%*[^.].跳过.前面所有字符并读掉.;%[^.].读取.前面的所有字符放入str,并读掉.
printf("%s",str);
return 0;
}
三、scanf和printf的返回值
scanf返回值为整形,返回读取的变量个数。
printf返回值为整形,返回输出的字符个数。
#include <stdio.h>
#include <stdlib.h>
#define len 20
int main(int argc,char const *argv[]){
char str[len];
int a=scanf("%*[^.].%[^.].",str);
int b=printf("%s\n",str);
printf("a:b=%d:%d",a,b);
return 0;
}
a为读取的变量个数,在程序中第一个字段跳过,第二个字段存入了str也就是读取了一个字符数组。b为输出的字符个数,在程序中输出了str,也就是san以及\n换行共4个字符。
四、其他输入输出方式
C语言除了scanf和printf外,还有getchar和putchar用于读取和输出单个字符;gets和puts用于读取和输出字符串;fgets和fputs也是用来读取和输出字符串;fscanf和fprintf用来和文件交互,读取或者输入。用法如下:
1、int c=getchar(void)读入单个字符,存为int型;int num=putchar(c),输出单个字符,返回该字符的int型值。
2、char *str=gets(char *str);读入字符串,存入str.该函数遇到换行符结束读取,会将换行符读掉,并在字符串末尾添加\0;该函数无法控制读取内容长度,存在越界风险。
int n=puts(char *str);输出字符串,自动换行,返回非负整数表示输出成功,EOF表示失败。
3、char *str=fgets(char *str,int ,FILE *);从文件中或者缓冲区stdin读取字符串,存入str字符数组中,读取长度为str长度-1,自动添加\0终止符。遇到\n终止读取,如果\n在字符数组长度范围内,换行符会被存入str并以\0结尾,如果\n刚好落在字符数组最后一位,将被转换为\0。当缓冲区字符串长度超过str长度,剩余的字符会留在缓冲区。
int n=fputs(char *str,FILE*);向文件或者输出流输出字符串str,不会自动换行,返回非负整数表示成功,EOF表示失败。
4、int n=fscanf(FILE*,格式化字符串,存入地址);从文件流或者输入流读取内容,可以是任意类型格式化字符串,存入相应的变量地址,返回值是读取变量的数量。当读取字符串时,遇到任意空字符(空格、制表符、换行符)均结束读取,空字符依然存在流中。返回值为读取数据个数,失败或者结尾返回EOF。
int n=fprintf(FILE*,format,变量名);向文件或者缓冲区输出数据,可以是任意类型变量,配合相应的格式化字符串。当输出字符串时,遇到\0结束,返回值为输出字符个数,失败返回EOF。
5、int getc(FILE *stream)和int putc(FILE *stream)两个函数,基本功能和getchar()/putchar一样,不仅用于终端单个字符的读取和输出,还能用于文件中单个字符的输入和输出。