27-文件
格式化输入输出
- printf
%[lgags][width][.prec][hil]type - scanf
%[flag]type
printf[flags]
eg:
printf(“%9d”,123);
printf(“%-9d”,123);
输出结果:
它们都占九个字节,一个左对齐,一个右对齐
eg:
printf(“%+9d”,123);
printf(“%-+9d”,123);
printf(“%-+9d”,123);
printf(“%-9d”,123);
输出结果:
printf(“%09d”,123;
输出结果:000000123
注:-与0不能结合,在左对齐后无法添加0
printf[with][.prec]
eg:
printf(“%*d”,6,123);
printf(“%9.2d”,123.0);
输出结果:
printf[hil]
scanf[flag]
printf和scanf的返回值
- printf返回值:读入的项目数
- scanf返回值:输出的字符数
- 在要求严格的程序中,应该判断每次调用scanf和printf的返回值,从而了解程序运行中是否存在问题。
文件输入输出
- 用>和<做重定向
<指定文件做为输入
>指定把输出写到另一个文件去
eg:
FILE
- FILE* fopen(const char *restrict path , const char* restrict made); //第一个参数:文件名 , 第二个参数:看下图
- int fclose(FILE* stream);
- fscanf(FILE* , …)
- fprintf(FILE* , …)
打开文件的标准代码
FILE *fp = fopen("file","r");
if(fp){
fcsanf(fp,...);
fprintf(fp);
}else{
...;
}
例子:
#include <stdio.h>
int main(int argc , char const *argv[])
{
FILE *fp = fopen("12.h","r");
if(fp){
int num;
fscanf(fp,"%d",&num);
printf("%d\n",num);
fclose(fp);
}else{
printf("无法打开此文件\n");
}
return 0;
}
二进制文件
- 其实所有的文件最终都是二进制的
- 文本文件无非是用最简单的方式可以读写的文件
more,tail;cat;vi - 而二进制文件是需要专门的程序来读写的文件
- 文本文件的输入输出是格式化,可能经过转码
文本vs二进制文件
-
Unix喜欢用文本文件来做数据存储和程序配置
-
交互式终端的出现使得人们喜欢用文本和计算机“talk"
-
Unix的shell提供 了一些读写文本的小程序
-
Windows喜欢用二进制文件
DOS是草根文化,并不继承和熟悉Unix文化
PC刚开始的时候能力有限,DOS的能力更有限,二进制更接近底层 -
文本的优势是方便人类读写,而且跨平台
-
文本的缺点是程序输入输出要经过格式化,开销大
-
二进制的缺点是人类读写困难,而且不跨平台
int的大小不一 致,大小端的问题… -
二进制的优点是程序读写快
程序为什么要文件
-
配置
Unix用文本,Windows用注册表 -
数据
稍微有点量的数据都放数据库了
-
媒体
这个只能是二进制的
-
现实是,程序通过第三方库来读写文件,很少直接读写二进制文件了
二进制读写
- size_t fread(void restrict ptr , size_t size , size_t nitems , FILE restrict stream);
- size_t fread(void restrict ptr , size_t size , size_t nitems , FILE restrict stream);
注:这四个参数分别代表的意思是:指向要读或写的那块内存 ; 这块内存有多大 ; 有几个这样的内存 ; 文件指针 - 返回的是成功读写的字节数
- 因为二进制文件的读写一般都是通过对一个结构变量的操作来进行的,于是nitem就是用来说明这次读写几个结构变量
- 例子:
student.h文件
#ifndef _STUDENT_H_
#define _STUDENT_H_
const int STR_LEN = 20;
typedef struct _student {
char name [STR_LEN];
int gender;
int age;
}Student;
#endif
mian.h文件
#include <stdio.h>
#include "student.h"
void getList(Student aStu[], int number);
int save(Student aStu[], int number);
int main(int argc, char const *argv [])
{
int number = 0;
printf("输入学生数量:");
scanf ( "%d",&number) ;
Student aStu[number]; //建立结构数组astu
getList(aStu,number); //存入数据
if ( save(aStu,number) ) {
printf("保存成功\n" );
}else {
printf("保存失败\n");
}
return 0;
}
void getList( Student aStu[], int number){
char format[STR_LEN]; //定义字符串数组,长度为STR_LEN
sprintf(format,"%%%ds",STR_LEN-1); //向format输入字符串("%19s")
int i;
for ( i=0; i<number; i++ ) {
printf("第%d个学生:\n", i);
printf( "\t姓名:") ;
scanf( format, aStu[i].name) ; //用format代替"%19s",方便之后变化数字
printf("\t性别(0-男,1-女,2-其他):");
scanf ("%d", &aStu[i].gender) ;
printf( "\t年龄:");
scanf ( "%d", &aStu[i].age) ;
}
}
int save( Student aStu[], int number)
{
int ret = -1;
FILE*fp = fopen( "student.data", "w" ); //打开stdent.date文件并写
if ( fp ) {
ret = fwrite(aStu, sizeof( Student), number, fp); //将astu内的数据写入fp指向的文件(stdent.date)
fclose(fp);
}
return ret ==number;
}
在文件中定位
- long ftell(FILE* stream);
- int fseek(FILE* stream , long offset , int shence);
SEEK_SET : 从头开始
SEEK_CUT : 从当前位置开始
SEEK_END : 从尾开始(倒过来)
接着上一个例子:
#include <stdio.h>
#include "student.h"
void read(FILE *fp, int index);
int main(int argc, char const *argv []){
FILE*fp = fopen( "student.data", "r"); //打开刚刚写好的文件
if ( fp ) {
fseek( fp,0L,SEEK_END); //定位到文件结尾
long size = ftell(fp); //获取位置,返回结果文件大小
int number = size / sizeof(Student) ;
int index=0;
printf("有%d个数据,你要看第几个:", number);
scanf( "%d", &index) ;
read(fp, index-1); //输入的数据-1,因为计算机从0开始计数
fclose(fp);
}
return 0;
}
void read ( FILE *fp, int index){
fseek(fp, index*sizeof( Student),SEEK_SET); //定位到要看数据的位置
student stu; //定义结构体变量
if ( fread(&stu, sizeof(Student), 1,fp)== 1 ){
printf("第%d个学生:", index+1);
printf("\t姓名:%s\n" , stu.name);
printf("\t性别:");
switch ( stu.gender ) {
case 0: printf("男\n" );break;
case 1: printf("女\n"" ) ; break;
case 2: printf("其他\n"); break;
}
printf( "\t年龄:%d \n", stu.age );
}
}
可移植性
- 这样的二进制文件不具有可移植性
- 在int为32位的机器上写成的数据文件无法直接在int为64位的机器上正确读出
- 解决方案之一是放弃使用int,而是typedef具有明确大小的类型
- 更好的方案是用文本