1.知识点总结
1.1常用系统调用函数与I/O库函数
系统调用函数 | I/O库函数 | 作用 |
---|---|---|
open() | fopen() | 打开文件 |
read() | fread() | 读文件 |
write() | fwrite() | 写文件 |
lseek() | fseek() | 移动文件指针 |
close() | fclose() | 关闭文件 |
1.2 I/O库函数的算法
1.2.1 fread算法
在第一次调用fread()时,FILE结构体的缓冲区是空的,fread()使用保存的文件描述符fd发出一个
n = read(fd, fbuffer, BLKSIZE);
系统调用,用数据块填充内部的fbuf[]。然后,它会初始化fbufl]的指针、计数器和状态变量,以表明内部缓冲区中有一个数据块。接着,通过将数据复制到程序的缓冲区,尝满足来自内部缓冲区的 fread()调用。如果内部缓冲区没有足够的数据,则会再发出一 read()系统调用来填充内部缓冲区,将数据从内部缓冲区传输到程序缓冲区,直到满足所的字节数(或者文件无更多数据)。将数据复制到程序的缓冲区之后,它会更新内部缓冲区的指针、计数器等,为下一个fread()请求做好准备。然后,它会返回实际读取的数据对象数量。
1.2.2 fwrite算法
fwrite()算法与fread()算法相似,只是数据传输方向不同。最开始,FILE结构体的内缓冲区是空的。在每次调用fwrite()时,它将数据写入内部缓冲区,并调整缓冲区的指针、计数器和状态变量,以跟踪缓冲区中的字节数。 如果缓冲区已满,则发出write()系统调用,将整个缓冲区写入操作系统内核。
1.2.3 fclose算法
若文件以写的方式被打开,fclose()会先关闭文件流的局部缓冲区。然后,它会发出一个close(fd)系统调用来关闭 FILE 结构体中的文件描述符。最后,它会释放 FILE 结构体,并将FILE指针重置为NULL。
1.3 I/O库模式
fopen()中的模式参数可以指定为:"r"、"w"、"a",分别代表读、写、追加。每个模式字符串可包含一个+号,表示同时读写,或者在写入、追加情况下,如果文件不存在则创建文件。
模式 | 功能 |
---|---|
r+ | 表示读/写,不会截断文件。 |
w+ | 表示读/写,但是会先截断文件;如果文件不存在,会创建文件。 |
a+ | 表示通过追加进行读/写;如果文件不存在,会创建文件。 |
1.3.1 字符模式I/O
int fgetc(FILE *fp); //get a char from fp, cast to int.
int ungetc(int c, FILE *fp); //push a previously char got by fgetc() back to stream
int fputc(int c, FILE *fp); //put a char to fp
注意,fgetc()返回的是整数,而不是字符。这是因为它必须在文件结束时返回文件结束符。文件结束符通常是一个整数-1,将它与文件流中的任何字符区分开。
对于fp=stdin或stdout,可能会使用c=getchar();sputchar(c);来代替。对于运行时效来说,getchar()和putchar()通常不是getc()和putc()的的缩小版本。相反,可以将它们实现为宏,以避免额外的函数调用。
1.3.2 行模式I/O
char *fgets(char buf, int size, FILE *fp); //从fp中读取最多为一行(以n结尾)的字符。
int fputs(char *buf, FILE *fp); //将buf中的一行写入 fp 中。
当fp是stdin或stdout时,也可以使用以下函数,但他们并非fgets()和fputs()的缩减版本。
get(char *buf); // input line from stdin but without checking length
puts(char *buf); // write line to stdout
1.3.3 格式化I/O
格式化输入:(FMT=格式字符串)
scanf(char *FMT, &items); // from stdin
fscanf(fp, char *FMT, &items); // from file stream
格式化输出:
printf(char *FMT, &items); // to stdin
fprintf(fp, char *FMT, &items); // to file stream
1.3.4 内存中的转换函数
sscanf(buf, FMT, &items); // input from buf[ ] in memory
sprintf(buf, FMT, items); // print to buf[ ] in memroy
注意,sscanf()和sprintf()并非I/O函数,而是内存中的数据转换函数。例如,atoi()是一个标准库函数,将一串ASCII数字转换成整数,但是大多数Unix/Linux系统没有itoA()函数,因为转换可由sprintf()完成,所以不需要它。
1.3.5 其他I/O库函数
函数 | 功能 |
---|---|
fseek()、ftell()、rewind() | 更改文件流中的读/写字节位置。 |
feof()、ferr()、fileno() | 测试文件流状态。 |
fdopen() | 用文件描述符打开文件流。 |
freopen() | 以新名称重新打开现有的流。 |
setbuf()、 setvbuf() | 设置缓冲方案。 |
popen() | 创建管道,复刻子进程来调用sh。 |
1.3.6 限制混合fread-fwrite
当某文件流同时用于读/写时,就会限制使用混合fread()和fwrite()调用。规范要求每对fread()和fwrite()之间至少有一个fseek()和ftell()。
1.4 文件流缓流
每个文件流都有一个FILE结构体,其中包含个一内部缓冲区。对文件流进行读写需要遍历FILE结构体的内部缓存区。文件流可以使用三种缓冲方案中的一种。
- 无缓冲:从非缓冲流中写人或读取的字符将尽快单独传输到文件或从文件中传输。例如,文件流stderr通常无缓冲。到stderr的所有输出都会立即发出。
- 行缓冲:遇到换行符时,写入行缓冲流的字符以块的形式传输。例如,文件流stdout通常是行缓冲,逐行输出数据
- 全缓冲:写入全缓冲流或从中读取的字符以块大小传输到文件或从文件传输。这是文件流的正常缓冲方案。
通过fopen()创建文件流之后,在对其执行任何操作之前,用户均可发出一个
getvbuf(FILE *stream, char *buf, int node, int size)
调用来设置缓冲区(buf)、缓冲区大小(size)和缓冲方案(mode),它们必须是以下一个宏:
- _IONBUF:无缓冲。
- _IOLBUF:行缓冲。
- _IOFBUF:全缓冲。
1.5 变参函数
在I/O库函数中,printf()相当独特,因为多种不同类型的可变数量参数可以调用它。这是允许的,因为最初的C语言不是一种类型检查语言。目前,C语言和C + +会强执行类型检查,但是为了方便,这两种语言仍然允许参数数量可变的函数。这些函数必须至少个一个参数进行声明,后跟3个点,如:
int func(int m, int n...) //n=last specified parameter
在函数内部,可以通过C语言库宏访问参数:
enter code hervoid va_start(va_list ap, last); // start param list from last parameter
type va_arg(va_list ap, type); // type = next parameter type
va_end(va_list ap); // clear parameter list
2.实践内容
2.1 文本文件操作
2.1.1 打印文件内容
#include <stdio.h>
#include <string.h>
void main()
{
FILE *fp;
char ch;
fp = fopen("C:/lcy/ccode/1.txt", "r");
if (fp == NULL)
printf("can not open!\n");
else
{
fscanf(fp, "%c", &ch);
while (!feof(fp))
{
putchar(ch);
fscanf(fp, "%c", &ch);
}
fclose(fp);
}
printf("\n");
}
2.1.2 编写一个C程序,将文本文件中的字母由小写转换为大写
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
FILE *fp;
char Filename[50]="1.txt", ch;
if ((fp = fopen(Filename, "r")) == NULL)
{
printf("File open failed!\n");
exit(0);
}
while (!feof(fp))
{
ch = fgetc(fp);
if (ch != EOF)
{
if (ch >= 'a' && ch <= 'z')
{
ch -= 32;
}
printf("%c", ch);
}
}
}
2.1.3编写一个C程序,计算文本文件的行数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE *fp;
char Filename[50] = "1.txt", ch;
int a = 1;
if ((fp = fopen(Filename, "r")) == NULL)
{
printf("File open failed!\n");
exit(0);
}
while (!feof(fp))
{
ch = fgetc(fp);
if (ch != EOF)
{
if (ch == '\n')
{
a++;
}
}
}
printf("%d\n", a);
return 0;
}
2.1.4编写一个C程序,计算文本文件的单词数,单词由空格分隔。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE *fp;
char Filename[50] = "1.txt", ch;
int a = 1;
if ((fp = fopen(Filename, "r")) == NULL)
{
printf("File open failed!\n");
exit(0);
}
while (!feof(fp))
{
ch = fgetc(fp);
if (ch != EOF)
{
if (ch == ' ' || ch == '\n')
{
a++;
}
}
}
printf("%d\n", a);
return 0;
}
2.1.5 文件的写入
//C语言按字符串写入文件
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxn 1024
int main(){
char *str=(char*)malloc(sizeof(char)*maxn);
char *source="t.txt";
char *op="w+t";
FILE *fp=NULL;
fp=fopen(source,op);
puts("请输入信息:");
while(1){
if(strcmp(gets(str),"-1")==0) break;//输入-1结束
strcat(str,"\n");
fputs(str,fp);
}
if(ferror(fp)) puts("文件写入错误!");
fclose(fp);
return 0;
}
2.2 二进制文件操作
2.2.1 二进制文件的读写
#include<stdio.h>
int main()
{
// 文件指针pd
FILE *pd = NULL;
unsigned __int8 a[100];
unsigned __int8 b[100];
int i;
for (i = 0; i < 16; i++)
{
a[i] = 0xaf;
}
// 写入二进制文件
// 文件名:"test.bin", 访问方式:"wb"
pd = fopen("test.bin", "wb");
// 数据块首地址: "&a",元素大小: "sizeof(unsigned __int8)", 元素个数: "16", 文件指针:"pd"
fwrite(&a, sizeof(unsigned __int8), 16, pd);
fclose(pd);
// 读取二进制文件
// 文件名:"test.bin", 访问方式:"rb"
pd = fopen("test.bin", "rb");
// 数据块首地址: "&b",元素大小: "sizeof(unsigned __int8)", 元素个数: "16", 文件指针:"pd"
fread(&b, sizeof(unsigned __int8), 16, pd);
for (i = 0; i < 16; i++)
{
printf("b[%d] = 0x%x\n", i, b[i]);
}
fclose(pd);
}
2.3 二进制文件与文本文件之间的转化
2.3.1 文本文件转换为二进制
#include <stdio.h>
#include <string.h>
#define NSIZE 8
void text2bin(const char* sIn, const char* sOut) {
int count = 0;
int ch,a;
char temp;
FILE * fin = fopen(sIn, "r");
FILE * fout = fopen(sOut, "w");
while(fscanf(fin, "%c", &temp)!=EOF) {
ch=temp;
for (a = 7; a >= 0; a--)
{
fprintf(fout, "%d", ch >> a & 1);
}
fprintf(fout,"\n");
}
fclose(fin);
fclose(fout);
}
int main() {
text2bin("ascii.txt", "bin.txt");
printf("changing successfully!\n");
return 0;
}
2.3.2 二进制数据转化为文本文件
#include <stdio.h>
#include <string.h>
#define NSIZE 8
void bin2text(const char* sIn, const char* sOut) {
FILE * fin = fopen(sIn, "r");
FILE * fout = fopen(sOut, "w");
int i = 0, j = 0, iTemp = 0, flag = 0;
int ibina[NSIZE];
char cRead[NSIZE];
char str[8];
char cChar;
int a=0;
while(fscanf(fin, "%c", &str[a%8])!=EOF) {
if(a%8==7) {
iTemp = 1;
cChar = 0;
for (j = 7; j >= 0; j--) {
cChar += (str[j]-'0') * iTemp;
iTemp *= 2;
}
fprintf(fout, "%c", cChar);
}
a++;
}
fclose(fin);
fclose(fout);
}
int main() {
bin2text("bin.txt", "ascii1.txt");
printf("changing successfully!\n");
return 0;
}
2.4 数据结构文件的读写
2.4.1 申请节点空间
node *head =(node*)malloc(sizeof(node));
2.4.2 初始化链表
node *setLink()
{
node *p,*s,*s1;
s=(node*)malloc(sizeof(node));
s->data=4;
strcpy(s->name,"密码");
s1=(node*)malloc(sizeof(node));
s->next=s1;
s1->data=1;
strcpy(s1->name,"网空");
s1->next=NULL;
return s; //返回链表头节点
}
2.4.3 将链表中的数据储存在文件中
void save(node *head)
{
node *p=head;
FILE *w =fopen("data.txt","w");
if(w==NULL)
{
printf("打开文件失败!");
return;
}
while(p)
{
fprintf(w,"%d %s",p->data,p->name);
p=p->next;
fprintf(w,"\n");
}
printf("\n");
fclose(w);
return;
}
2.4.4 从文件中读取数据储存在链表中
node *take() //尾插法
{
node *head =(node*)malloc(sizeof(node));
int t;
char name[6];
node *p;
node *q;//始终指向为下一个节点
p=q=head;
FILE * r= fopen("data.txt","r");
if(r==NULL)
{
printf("打开文件失败!");
return NULL;
}
while(fscanf(r,"%d %s",&t,name)!=EOF)
{
q= (node*)malloc(sizeof(node));
q->data=t;
strcpy(q->name,name);
p->next=q;
p=q;
}
p->next=NULL;
return head;
}
2.4.5 运行结果
3.问题与解决思路
问题:在学习对于文件任意位置的读写时,我有点搞不清楚,如何实现这个功能,我有点无从下手
解决思路:
我通过了该网站学习了文件的读写规则:学习链接
在程序看来,文件就是由一连串的字节组成的字节流,每个字节都有一个位置编号,在第n个字节后面有一个文件结束标志EOF(Eed Of File)。
文件是一个字节流,读写哪个字节必须要指定这个字节的位置,这是由文件指针来决定的。如字节流有n个字节,p是指针位置(0<=p<=n-1),那么读写规则如下:
- 读规则:0<=p<=n-1时,指针指向一个文件字节,可以读出该字节,读完后指针会自动指向下一个字节,既p会自动加1;若p指向EOF的位置,则不能读出任何文件字节,EOF通常是循环读文件的循环结束条件。
- 写规则:0<=p<=n-1 时,指针指向一个文件字节,可以写入一个新的字节,新的字节将覆盖旧的字节,之后指针会自动指向下一个字节,既p会自动加1;若p指向EOF的位置,则新写入的字节会变成第n+1个字节,EOF向后移动一个位置,在字节流的末尾写入会加长文件字节流。
所以通过文件指针的改变,就可以对文件的任意位置进行读写