文件的输入输出实现 都是根据系统函数操作的 学习有关函数的使用为主要目标
文件一唯一的名字作为标记的数据集合
操作系统是以文件为单位对数据进行管理的
由于与主机相连的输入输出设备也是从设备上读取数据和将数据写到设备上所以输入输出设备也看作是文件 完整的磁盘文件名的组成:
盘符:路径\(基本)文件名.扩展名
由于文件是受操作系统管理的所以建立联系和断开联系需要提出合通知操作系统
文件分类
二进制文件 文本文件
文件数据的存储方式
text文件 以ascll码的形式按字节存储的又叫ASCLL文件
以在内存中的二进制属性是存储的文件称为二进制文件
二进制文件占用内存少 用户可以直接阅读文件内容 速度快
顺序文件和随机文件
按照文件内数据处理方式的不同
顺序文件是按照数据存储的顺序连续的处理(读或写)每一个数据 为了处理文件中某个数据必须从文件的第一个数据开始顺序取完指定数据前所有的数据才能处理该数据
随机文件处理数据时可以在文件中任意指定的位置读写数据
缓冲文件和非缓冲文件系统
按系统对文件的支持方式不同 分为缓冲文件系统和非缓冲文件系统
缓冲文件系统 在进行i/o操作 系统自动为每个打开的文件开辟一个内存缓冲区 数据的输入和输出都是通过缓冲区去进行 也就是说 输出数据时数据先送到内存缓冲区 缓冲区装满或文件关闭时 才将缓冲区的数据送到磁盘 输入数据时 则将一批数据从磁盘送到缓冲区 以后从缓冲区读取数据 如果缓冲区 没有要读取的数据时 就到磁盘上再度一批数据送到缓冲区 采用缓冲区的目的是匹配快速的cpu和慢速的磁盘操作 提高cpu的工作效率 减少访问磁盘的次数
写入到内存程序数据区 然后 写入到内存缓存区 最终写到外存(硬盘 ) 读的过程相反
所谓非缓冲文件系统指系统不自动开辟内存缓冲区 而是程序自己为每个文件设定缓冲区 1983年ANSI c标准不采用非缓存文件系统
文件型指针
在ANSI 文件 I/O系统中 每个被使用的文件 都在内存开辟一个区 用于存放于文件相关的信息 如文件号(文件在操作系统中被管理的的代号) 文件的读写状态 文件缓冲区的地址 以及当前的读写缓冲区数据的位置 这些信息存放在一个结构体型变量 这个结构体类型是系统定义的并通过 typedef 将该结构型起名为FILE FILE结构型定义在stdio.h文件中 定义:
typedef struct
{
int _fd;//文件号
int _cleft;//缓存区剩下的字符
int _mode;//文件操作模式
char *nextc;//下一个字符的位置
char *buff;//文件缓存区的位置
}FILE;
程序使用这个文件 首先必须定义一个FILE型结构的指针变量这个指向FILE 结构型的指针成为文件指针 通过这个指针可以实现对文件的操作 定义文件指针变量的一般形式:
FILE *文件型指针名
可以同时定义多个文件型指针
打开文件和关闭文件
打开文件的意思时使定义的文件型指针指向打开的文件 包括为文件型变量分配内存空间 在内存为文件建立缓存区 将文件和缓冲区的相关信息写入文件型变量的各成员中 做准备
文件使用完毕 系统将缓存区中的数据做相应的处理 然后释放缓冲区 这个过程叫做关闭文件
文件打开由系统函数 fopen();文件关闭 fclose()
打开文件函数
格式为
FILE *fopen(char *filename,char *mode)
其中filename是字符型指针 他指向的字符串是要打开的文件名参数mode 也是字符型指针 它指向的字符串时文件的使用方式 称为打开模式
这两个参数的实参可以是字符串常量 可以是指向字符串的指针 也可以是字符数组的首地址
文件的使用方式字符串共有12个 其中6个适用于文本的 6个用于二进制文件的
mode | 功能 |
---|---|
r | 打开一个已经存在的文本文件 只能读取(输入)数据 |
w | 打开一个文本文件 只能写入(输出)数据 |
a | 打开一个已存在的文本文件 向文件为追加(写)数据 |
r+ | 打开一个已存在的文本文件 既可以读也可以写 |
w+ | 打开一个文本文件既可以读也可以写 |
a+ | 打开一个已存在的文本文件 可读数据也可以向文件尾写数据 |
rb | 打开一个已存在的二进制文件只能读取数据 |
wb | 打开一个二进制文件只能写入数据 |
ab | 打开一个已存在的二进制文件既可以读也可以写 |
rb+ 或r+b | 打开一个已存在二进制文件 既可以读也可以写 |
wb+或w+b | 打开一个二进制文件 既可以读也可以写 |
ab+或a+b | 打开一个已存在二进制文件 可以读取数据 也可以向文件尾 写数据 |
标准设备文件
标准输入设备为键盘 标准输出设备为显示器 因为软件技术中 输入输出设备也看作是文件所以也称键盘和显示器因为标准设备文件
当程序运行是系统自动打开标准设备文件无需再执行打开和关闭文件操作
程序运行时系统共打开5个标准设备文件
用户可以像使用指针一样直接使用这些设备
文件号 | 文件指针 | 标准文件名称 | 文件号 | 文件指针 | 标准文件名称 |
---|---|---|---|---|---|
0 | stdin | 标准输入 | 3 | stdaux (auxiliary辅助设备) | 标准辅助(辅助设备端口) |
1 | stdout | 标准输出 | 4 | stdprn | 标准打印 |
2 | stderr | 标准错误(指显示器) |
文件的读和写
成功打开文件后就可以读和写了
使用以下函数实现
(1)字符文件读写函数 fgetc()和fputc();
(2)数据块文件读写函数 fread()和fwrite()
(3)格式文件读写函数 fscanf() 和fprintf()
(4)字符串文件读写函数fgets()和fputs()
所有关于读写的系统函数军定义在头文件stdio.h
还提供有文件测试函数读和文件随即定位函数
字符文件读写函数
写字符文件函数
fputc 将字符ch写到文件指针 fp所指向的文件的当前写指针的位置 函数的格式为 int fputc(int ch ,File *fp)
其中参数 fp为文件指针 他的值是执行函数fopen_s打开文件时得到的 ch为字符变量 其值即为要写进文件的字符 他虽然被定义为int类型但是仅使用其低八位(前八位) 在正常调用下 函数返回读取字符的ascll码值 出错返回EOF(-1) EOF是头文件stdio.h里定义的宏
#include<stdio.h>
#include<iostream>
using namespace std;
#define gg(a) #a"123";a //注意注释一定是在宏定义之前
int main()
{
return 0;
}
#include<fstream>
#include<iostream>
using namespace std;
#define D(A) T<<#A<<endl; A //#字符串化
int main()
{
ofstream T("format.out");
D(int i = 47;)
D(float f = 2300114.232343;)
const char* s = "Is there any more?";
D(T.setf(ios::unitbuf);)
D(T.setf(ios::showbase);)
D(T.setf(ios::uppercase | ios::showpos);)
D(T << i << endl;)
D(T.unsetf(ios::showbase);)
D(T.setf(ios::dec, ios::basefield);)
D(T.setf(ios::left, ios::adjustfield);)
D(T.fill('0');)
D(T << "fill char " << T.fill() << endl;)
D(T.width(10);)
T << i << endl;
D(T << i << endl;)
D(T.unsetf(ios::showpos);)
D(T.setf(ios::showpoint);)
D(T << "prec =" << T.precision() << endl;)
D(T.setf(ios::scientific, ios::floatfield);)
D(T << endl << f << endl;)
D(T.setf(ios::fixed, ios::floatfield);)
D(T << f << endl;)
D(T.precision(20);)
D(T << "prec =" << T.precision() << endl;)
D(T << endl << f << endl;)
D(T.setf(ios::fixed, ios::floatfield);)
D(T << f << endl;)
D(T.width(10);)
T << s << endl;
D(T.width(40);)
T << s << endl;
D(T.setf(ios::left, ios::adjustfield);)
D(T.width(40);)
T << s << endl;
return 0;
}
int main()
{
FILE* fin, * fout;
//rewind(fin);//指针指向文件的开头//指针的地址就是指向指针的指针的地址
char str[80]="i/o stream ok to school";
char str1[80];
if (fopen_s(&fin,"fin.dat","w"))
{
printf("no scram\n");
exit(1);
}
gets_s(str);
fputs(str,fin);
fclose(fin);
if (fopen_s(&fin, "fin.dat", "r"))
{
printf("no scram\n");
exit(1);//该函数
}
fgets(str,15,fin);
printf("%s\n",str);
fclose(fin);
return 0;
}
从键盘输入的一个字符串存入磁盘文件
(1)定义一个文件指针fp
(2)用写模式打开或建立文件 并检查是否打开正确
(3)将从键盘读入的字符串用fputc函数写入(输出)到文件
(4)关闭文件
读字符文件函数
fgetc()
格式
int fgetc(FILE *fp)
正常调用下函数返回所写字符的字符码值 出错或当前位置是文件结尾 返回EOF
基本技能:while循环读字符
文件尾测试函数 错误测试函数 文件头定位函数
int feof(FILE *fp)
功能测试fp所指向的文件的最后一次操作时 是否已经达到文件尾 如果达到返回一个真值 否则 返回0
更改while 语句即可
效果一样
当以输入方式打开一个二进制文件时 可能读到与EOF等价的整数值 所以在读二进制文件时必须使用函数feof()
int ferror (FILE *fp)
功能:测试文件指针fp所指向文件在最后一次操作中是否发生错误 如果发生了错误 函数返回非零值(错误代码)因此都可以用此函数确定每次输入或输出操作后是否发生了作物 如能在每次文件操作之后都立即调用此函数可及时发现问题 否则错误可能会被丢失
void rewind(FILE *fp)
功能 将文件内部指针置于fo所知文件的开头
当文件中若干或全部数据被读过后 又要从头读数据时 就需要将内部指针定位到文件的开头
字符串文件读写函数
写 char fput(char str,FILEfp)
其中参数str是 字符型指针 可以是字符串常量 或存放字符串的数组首地址
fp是文件型指针 功能:将参数str 指向的字符串(舍掉结束标记‘\0’)写入fp指向的文件 文件内部指针自动后移动一个(注意是)字符串 的位置
执行正确时返回最后写入的字符 错误时返回EOF(-1)
读 char fgets(charstr,int length ,FILE *fp)
其中参数str是字符串指针 可以存放字符串的字符型数组首地址 也可以指向某个存放字符串的内存区域的指针 length 为字符串长度 可以是整型常量 变量或表达式 fp为FILE文件指针变量
功能 从fp所指向文件的当前位置读取 length-1个字符在其后加上一个字符串结束标记‘\0’组成字符串 存入str所指定的内存区 如果再读够length-1个字符之前遇到回车符 则读到回车符为止补上字符串结束标记’\0’ 组成字符串 含回车符 回车符后面的字符不在读取 如果读到前(length-1)个字符遇到文件尾 不再读在读取字符的后面补充上字符串结束标记’\0’ 组成字符串
数据块文件读写函数
数据块文件读写函数用于二进制文件的读和写
unsigned fwrite(void *buffer,unsigned num_bytes,unsigned count ,FILE fp)
其中参数buffer是指向将要写到文件中的数据块指针 可以是存放数据的变量地址或数组首地址 num_bytes 是无符号整型 可以是常量变量 或表达式 他写入文件的每个数据所占用的字节数 count是无符号整型 可以是常量变量或表达式 他是写入文件的数据个数 fp只想预先打开的文件的指针
功能
将buffer指向的count 个数据(每个数据占用num_bytes字节 )写入fp指向的文件 fwrite 一次写入文件的字节数 为countnum_bytes, 函数返回count写入文件数据的个数 写入发生错误则返回NULL(0)
#include<stdio.h>
#include<iostream>
using namespace std;
//#define gg(a) #a"123";a //注意注释一定是在宏定义之前
int main()
{
FILE* fp;
float fnum[10];
int i;
for (i = 0; i < 10; i++)
{
fnum[i] = i / 2.0;
}
if (fopen_s(&fp, "fn", "wb"))//返回1打开失败
{
printf("no scram\n");
exit(1);//出错关闭
}
fwrite(fnum,sizeof(fnum),1,fp);
fclose(fp);
return 0;
}
这里面如果向多次调用fwrite可以同样实现
为了判断fwrite确实写入了参数规定的个数 可更改程序。
数据块文件读函数
unsigned fread(void buffer ,unsigned num_bytes, unsigned count ,FILEfp)
二者近似 上代码对比
#include<stdio.h>
#include<iostream>
using namespace std;
//#define gg(a) #a"123";a //注意注释一定是在宏定义之前
int main()
{
float fnum1[10];
FILE* fp;
int i;
if (fopen_s(&fp, "fn", "rb"))
{
printf("no scram\n");
exit(1);
}
fread(fnum1,sizeof(fnum1),1,fp);//函数返回值也是count同样也可以检验
printf("\n");
for (i = 0; i < 10; i++)
{
printf("%g ",fnum1[i]);
}
fclose(fp);
return 0;
}
再来个例题
结构型x 定义一个含有三个成员(int i,float f,char c)的结构型x 将两个结构型数据写入磁盘文件fnum 然后从文件中读出数据并输出到显示器
在这里 两次打开文件两次关闭文件 可以用模式wb+ 则只需要打开一次文件关闭一次文件 文件打开后先进行写 然后进行读 最后关闭 但是还有一个问题必须考虑 当程序完成写操作后 内部读写文件指针已经指向文件尾 这时如果进行读操作 读不到正确数据 为了成功 必须令内部指针指向文件的头 用到系统函数 void rewind(FILE *fp)
两个程序分别给出
#include<stdio.h>
#include<iostream>
using namespace std;
//#define gg(a) #a"123";a //注意注释一定是在宏定义之前
int main()
{
struct x
{
int i;
float f;
char c;
}a1[2], a[2] = { {1,1.1,'a'} ,{2,2.2,'b'} };
FILE* p;
int k;
if (fopen_s(&p, "fnum", "wb"))
{
printf("no scram\n");
exit(1);
}
fwrite(a,sizeof(a),1,p);
//fwrite(a, sizeof(struct x),2,p)
fclose(p);
if (fopen_s(&p, "fnum", "rb"))
{
printf("no scram\n");
exit(1);
}
fread(a1,sizeof(a1),1,p);
fclose(p);
for (k = 0; k < 2; k++)
{
printf(" %d %f %c",a1[k].i,a1[k].f,a1[k].c);
//printf(" %d %f %c", (a1+k)->i, (a1 + k)->f, (a1 + k)->c);
}
return 0;
}
wb+;
#include<stdio.h>
#include<iostream>
using namespace std;
//#define gg(a) #a"123";a //注意注释一定是在宏定义之前
int main()
{
struct x
{
int i;
float f;
char c;
}a1[2], a[2] = { {1,1.1,'a'} ,{2,2.2,'b'} };
FILE* p;
int k;
if (fopen_s(&p, "fnum", "wb+"))
{
printf("no scram\n");
exit(1);
}
fwrite(a, sizeof(a), 1, p);
rewind(p);
//fwrite(a, sizeof(struct x),2,p)
fread(a1, sizeof(a1), 1, p);
fclose(p);
for (k = 0; k < 2; k++)
{
printf(" %d %f %c", (a1+k)->i, (a1 + k)->f, (a1 + k)->c);
}
return 0;
}
格式化读写文件
格式化写函数 int fprint(FILE *fp,char *control_string,e1,e2,e3,···en)
其中参数fp是文件型指针 control_string 是存放格式字符串的字符常量 或者是存放格式字符串的数组首地址 后者是指向格式字符串的指针变量 e1 e2 ····e n 是要写入文件的各个数据 也可以是表达式 本函数使用的格式字符串与printf中使用的完全一样
功能是 计算各个表达式 e1 e2 ···en 的值 按照control_string 指定的格式 写入fp指向的文件 如果操作正确 则返回写入的表达式数目n 错误返回EOF
fscanf()
int fscanf(FILE *fp,char *control_string ,e1,e2,e3···en)
同理 不同的是 e1,e2 ···en是 与格式字符串匹配的变量地址或数组首地址 和scanf一样的格式字符串
功能 从fp指向的文件中 按照control_string 规定的字符串 读取n个数据 存入e1 e2···en 地址中 操作正确 返回所读数据的数目
上代码了解的清楚
#pragma warning(disable:4996)
#include<stdio.h>
#include<iostream>
using namespace std;
//#define gg(a) #a"123";a //注意注释一定是在宏定义之前
int main()
{
FILE* fp;
char str[10];
float fval;
int k;
if (fopen_s(&fp, "data", "w"))
{
printf(" no scram\n");
exit(1);
}
printf("ok please enter key:");
fscanf_s(stdin, "%c %g %d\n", &str, 10, &fval, &k);//这个狠和fscanf不一样的地方是 字符串后面跟一个size
fprintf(fp, "%c %g %d\n", str, fval, k);
fclose(fp);
return 0;
}
编写一个程序将上面建立的文件data 读出并输出到打印机 打印机作为标准输出设备文件
#pragma warning(disable:4996)
#include<stdio.h>
#include<iostream>
using namespace std;
//#define gg(a) #a"123";a //注意注释一定是在宏定义之前
//这里由于stdio.h里面没有定义stdprn 这个标准输出设备文件指针 所以需要自己定义或修改
int main()
{
FILE* fp,*stdprn;
char str[10];
float fval;
int k;
if (fopen_s(&stdprn, "data2", "w+"))
{
printf("no scram\n");
exit(1);
}
if (fopen_s(&fp, "data", "r"))
{
printf("no scram\n");
exit(1);
}
fscanf_s(fp,"%s,%g,%d",&str,10,&fval,&k);
fprintf(stdprn,"%s %g %d",str ,fval,k);
fclose(fp);
return 0;
}
文件的定位与文件的随机存取
i/o操作 前面的都是从文件的头开始顺序进行的 这样的i/o操作称为顺序存取
相应的文件叫做顺序文件 将一个数据写到文件的任意指定位置或者读文件中的任意指定位置的数据这样的i/o操作叫做随机存取操作相应的文件叫做随机文件
i/o系统在文件中设置有一个内部位置指针 内部指针 用来指向当前的读写位置 对于顺序存取的文件每次读写一个数据 就得人为的改变位置指针所指向的位置 这个操作就称为文件的读写定位 文件的读写定位可通过调用文件随即定位系统函数实现
文件随即定位函数
int fseek(FILE *fp ,long offset_bytes,int origin)//寻找
其中参数fp是打开文件得到的文件指针 offset_bytes 是一个长整型数 表示 从origin为起始位置 的偏移字节数 origin 是确定起始位置的参数 其含义取值 和宏名 有表 所示 这些宏名定义在sstdio.h中 在函数中既可以使用宏名 也可以使用宏名的值作为参数
origin | orgin宏名 | 宏名对应值 |
---|---|---|
文件头 | SEEK_set | 0 |
当前位置 | SEEK_cur | 1 |
文件尾 | SEEK_END | 2 |
函数功能 根据偏移量和起始位置 设置fp所指文件当前的读写位置 当偏移量为正数时 将内部指针从origin位置想文件尾方向移动offset_bytes个字节 当偏移量为负值 将内部指针 origin位置指向文件头方向移动number_bytes个字节偏移量可用函数sizeof确定
函数调用成功返回0 否则返回非零
随机文件一般都是二进制文件因为二进制文件中的数据元素是等长的 数据的存储位置很容易计算出来 对于文本文件 数据元素的长度是不等的与数据的位数有关不容易确定某数据的存储位置 所以文本格式的随机文件应用较少
代码
在一个fdata文件有六个结构美型数据将用户指定的数据输出到屏幕显示
#pragma warning(disable:4996)
#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
struct data
{
int a;
float b;
char c;
};
FILE* fp;
int i;
data d[6] =
{
{1,1.1,'a'} ,{2,2.2,'b'}, {3,3.3,'c'}, {4,4.4,'d'}, {5,5.5,'e'} ,{6,6.6,'f'}
};
if (fopen_s(&fp, "fdata", "wb"))
{
printf("no scram\n");
exit(1);//不正常强制退出
}
for (i = 0; i <= 5; i++)
{
fwrite(&d[i],sizeof(d[i]),1,fp);
}
fclose(fp);
return 0;
}
对程序文件fdata进行随机读取 首先通过键盘输入要读的数据的序号(0-5)
第二步根据输入的数据设置函数fseek的参数 使内部文件位置指针所指向所要操作的数据 第三步用函数fread读文件中指定的数据 第四步 将读出的数据显示到显示屏
int main()
{
struct data
{
int a;
float b;
char c;
}d[6];
FILE* fp;
int i;
if (fopen_s(&fp,"fdata", "rb"))
{
printf("no scram\n");
exit(0);//正常退出
}
printf("Enter a numb 0-5:");
scanf("%d",&i);
fseek(fp, i * sizeof(struct data),0);
fread(&d[i],sizeof(d[i]),1,fp);
printf("%d %f %c",d[i].a,d[i].b,d[i].c);
fclose(fp);
return 0;
}
有文本 存字符串 进行熟练操作。
#pragma warning(disable:4996)
#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
//更改文本文件的字符
char str1[80];
wchar_t str2[80];
FILE* fp;
if (fopen_s(&fp, "strfile.txt", "r+"))
{
printf("no scarm\n");
exit(0);
}
fseek(fp,3,SEEK_SET);
fputc('D',fp);
fseek(fp, 2, SEEK_CUR);
fputc('G',fp);
fseek(fp,-4, SEEK_CUR);//解决下面那个坑的一种方法寻找内部指针
//fclose(fp);
fgets(str1, 8, fp);//这里有个坑 加上'\0'终止字符最大8个字符 而且这个如果没有fseek(fp, 0, SEEK_CUR);就会更改文本为中文乱码
/*一种解决(没有成功解决) 将文件编码改为ANSI这个标准的 这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。
//在简体中文Windows操作系统中,ANSI 编码代表 GB2312编码;
ANSI编码作为中国以及部分亚太地区的多字符编码格式,Windows系统和OS X都是提供原生支持的。但是即便如此,许多国外开发者仍然在开发笔记或者文字录入类应用的时候将ANSI编码完全忽略,只加入全球通用的UTF-8编码。
*/
//fgets(str1, 8, fp);
printf_s("%s",str1);
fclose(fp);
return 0;
}
上面那个代码有个很恶心的地方 这个东西 貌似编码也改了 还是不行
当前位置函数 在程序运行中随着对文件的操作 文件的内部指针 位置不断变动有时可能需要知道当前指针所指位置 函数 ftell的作用是获取文件当前的操作读或写的位置
long ftell (file *fp)
他返回的是fp所指向的文件中的读或写的位置 如果返回值为-1则表示出错