C语言-文件操作基础
格式化输入输出
printf 与 scanf 函数的格式占位符详细说明——
(1)printf格式占位符:%[flags][width][.prec][hlL]type
[flags] 包含4类功能符号: - 左对齐 、 + 带符号 、 (space) 留空 、 0 左侧空位补零
【补充说明】
(1)可以同时用多个,如±表示带符号且左对齐;
(2)用了左对齐后,留空、补零就没用了;
(3)不配合 [width] 使用时就只有 (space) 和 + 有效果,所以一般配合width一起用。
[width] 数据占位宽度(至少),可以用 * 表示下一个参数是占位宽度,输出超过宽度时按实际输出。
[.perc] 数据小数点后保留位数,体现精度,也可以用 * 表示下一个参数是小数点后位数。其值一般小于(width - 1),不然没有实际意义,小数点也是算1位的。
[hlL] 类型修饰分5种: hh 单个字节(地址低位) 、 h short型 、 l long 、 ll long long 、 L long double
type
符号 用于
i 或 d int
u unsigned int
o 八进制
x 或 X 十六进制(小写、大写)
f 或 F float (有效精度6)
e 或 E 指数 (科学计数法)
g 或 G float
a 或 A 十六进制浮点数
c char
s 字符串
p 指针/地址
n 读入/写出的个数
(2)scanf格式占位符:%[flag]type
[flag]符号种类:
符号 含义
* 跳过一个当前类型
数字 最大字符数,输入超过时截断
hh char
h short
l long、double
ll long long
L long double
type
符号 用于
d int
i 整数,默认十进制(0或0x开头表示八进制或十六进制)
u unsigned int
o 八进制
x 十六进制
a e f g float
c char
s 字符串
p 指针/地址
[...] 所允许的字符,如 %[^,],
(3)printf和scanf的返回值
printf函数的返回值是输出的字符数(\n等符号也计入计算);
scanf函数的返回值是读入的变量数。
要求严格的程序中应判断每次调用scanf和printf的返回值是否发生异常,采取相应动作及时处理。
文件输入输出
文本文件读写
(1)特殊的方式
格式化输入输出配合 符号 > 和 < 重定向功能实现文件的读写 —— linux系统或者dos系统编译文件时,通过符号 > 可以将程序输出写入指定文件,通过符号 < 可以从指定文件将数据读入程序。
linux文本操作命令:打开:more 、 od 或 tail 打开写入:cat 实现完整文件操作:vi
(2)一般的方式 借助 <stdio.h> FILE
函数: fopen fclose fscanf读取 fprintf写入
二进制读写
函数: fread fwrite 一般用于对n个结构变量操作,指针参数在最后,函数返回成功读写的字节数
ftell fseek 文件中定位
SEEK_SET从头开始 SEEK_CUR从当前位置开始 SEEK_END从尾开始(倒过来)
其实所有类型的文件最终都是二进制文件(转码),二进制文件需要专门的程序读写。
交互式终端使人们喜欢使用文本和计算机交谈(UNIX的shell提供读写文本的小程序,常用文本文件做数据存储和程序配置,windows常用二进制文件:PC开始时能力有限,DOS能力更有限,使用更接近底层的二进制文件做系统配置(注册表是一个超大的二进制文件)等等)。文本的优势是方便人读写且可跨平台,缺点是程序开销大;二进制文件可读差,不跨平台,大小端问题,int大小不一致,然而其效率很高,程序读写快。
配置文件一般是文本文件或二进制文件;
数据量稍大时都由数据库处理;
媒体文件只能是二进制的;
现实中程序通过第三方库来读写文件,很少直接读写二进制文件了。
以可移植性而言,二进制文件不具备可移植性,解决方案是使用typedef具有明确大小的类型或用文本文件。
文件操作例子
打开文件的代码样板
#include <stdio.h>
int main() {
/*
* r 打开只读
* r+ 打开读写,从文件头开始
* w 打开只写。不存在则新建,存在则清空
* w+ 打开写,并且可读。不存在则新建,存在则清空
* a 打开追加。不存在则新建,存在则从文件尾开始
* 另外,w和a后面可以加x:表示只新建,如果文件存在则不能打开
*/
FILE *fp = NULL;
fp = fopen("fltest.in", "r"); //第一个参数是文件名,第二个参数是打开方式
//判断文件是否正确打开了
if ( fp ) {
int num = 0;
fscanf(fp, "%d", &num); //从文件读入
printf("num = %d\n", num);
//文件使用完毕记得关闭文件
fclose(fp);
}
else {
printf("打开文件失败\n");
}
return 0;
}
补充:fscanf遇到空格结束一次读操作(字符串读入需要注意位置),将fp设为stdin时可以实现scanf相同的效果。
文件写入(保存)与读取的例子 ——
#include <stdio.h>
#define STR_LEN 20
//const int STR_LEN = 20;
typedef struct _person {
char name[STR_LEN];
int gender;
int age;
} Person;
void getList(Person arr[], int length);
int save(Person arr[], int length);
void read(FILE *fp, int index);
int main() {
int number = 0;
printf("输入人员信息记录数量:");
scanf("%d", &number);
Person person_Array[number];
getList(person_Array, number);
if ( save(person_Array, number) ) {
printf("保存成功\n");
}
else {
printf("保存失败\n");
}
//查看文件内容
FILE *fp = NULL;
fp = fopen("person.data", "r");
if ( fp ) {
fseek(fp, 0L, SEEK_END); //指针从文件末尾移动0,就是指针在文件末尾不动
long size = ftell(fp); //获取当前所在位置-末尾,就是文件大小
int number = size / sizeof(Person);
int index = 0;
printf("有%d个数据,你要看第几个:", number);
scanf("%d", &index);
read(fp, index-1);
fclose(fp);
}
return 0;
}
void getList(Person arr[], int length) {
char format[STR_LEN];
sprintf(format, "%%%ds", STR_LEN-1); //"%19s"
int i;
for ( i = 0; i < length; i++ ) {
printf("第%d个人员:\n", i+1);
printf("\t姓名:");
scanf(format, arr[i].name);
printf("\t性别(0-女,1-男):");
scanf("%d", &arr[i].gender);
printf("\t年龄:");
scanf("%d", &arr[i].age);
}
}
int save(Person arr[], int length) {
int ret = -1;
FILE *fp = NULL;
fp = fopen("person.data", "w");
if ( fp ) {
ret = fwrite(arr, sizeof(Person), length, fp);
fclose(fp);
}
else {
printf("打开文件失败\n");
}
return ret == length;
}
void read(FILE *fp, int index) {
fseek(fp, index * sizeof(Person), SEEK_SET);
Person person;
if ( fread(&person, sizeof(Person), 1, fp) == 1) {
printf("第%d个人员:", index+1);
printf("\t姓名:%s\n", person.name);
printf("\t性别:");
switch ( person.gender ) {
case 0: printf("女\n"); break;
case 1: printf("男\n"); break;
}
printf("\t年龄:%d\n", person.age);
}
}