目录
1.2fgetc、fputc、fgets、 fputs、fread、fwrite函数
前言
文件IO主要包括标准C库提供的函数、和Linux系统自带的文件IO函数,了解文件缓存的细节及不同函数的区别;掌握目录文件的相关操作函数
提示:以下是本篇文章正文内容,下面案例可供参考
一、标准C库I/O
文件 I/O(Input/Output)类型:
(1)标准C IO
#include <stdio.h>
标准C库为我们提供一堆库函数,可以对文件操作(打开文件,读取文件内容,写入文件内容,关闭文件)
(2)Linux 文件IO
系统调用
Linux系统自带的一堆函数,可以对文件操作(打开文件,读取文件内容,写入文件内容,关闭文件)
1.1fopen、fclose函数
1、fopen函数 (f file的简写)
(1)功能:打开文件
(2)头文件及函数原型
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
FILE* fp = fopen("hello.c","r"); //打开文件,以相对路径 只读 的方式打开hello.c
FILE* fp = fopen("/home/linux/0703/IO/hello.c","r");//打开文件,以绝对路径的方式打开
(3)参数说明
const char *path //打开文件的名字(可以是相对路径也可以是绝对路径)
const char *mode //打开文件的方式
r ---> read w ---> write a ---> append 追加
"r" 以只读的方式打开文件 "r+" 以可读可写的方式打开文件
"w" 以只写的方式打开文件,如果文件存在,清空并打开,如果不存在,创建并打开
"w+" 以可读可写的方式打开文件,如果文件存在,清空并打开,如果不存在,创建并打开
"a" 以只写的方式打开文件,如果文件存在,打开,接着尾巴写,如果不存在,创建并打开
"a+" 以可读可写的方式打开文件,如果文件存在,打开,接着尾巴写,如果不存在,创建并打开
(4)返回值
FILE * 指针,文件流指针,FILE是一个结构体,每打开一个文件,内核就会开辟出一块空间,用来保存这个文件得到相关信息
成功:返回 FILE *(非空), FILE* fp 流指针fp,指向的就是被打开的文件
失败:返回 NULL
(5) 实例
FILE *fp = fopen("hello.c","r"); //打开文件,以相对路径的方式打开
FILE *fp = fopen("/home/linux/0703/IO/hello.c","r");//打开文件,以绝对路径的方式打开
**********************************案例代码***************************************
#include <stdio.h>
int main(int argc, const char *argv[])
{
//1.相对路径的方式打开当前目录下的hello.c
FILE* fp = fopen("hello.c","r");
if(fp == NULL)
{
printf("打开失败!!\n");
}
else
{
printf("打开成功!!\n");
}
return 0;
}
**********************************************************************************
2、fclose函数
(1)功能:关闭文件,同时会刷新缓存区,将缓存区的内容清空
(2)头文件及函数原型
#include <stdio.h>
int fclose(FILE *fp);
(3)参数说明:FILE *fp //fopen的返回值
(4)返回值
成功: 返回 0
失败: 返回 -1
1.2fgetc、fputc、fgets、 fputs、fread、fwrite函数
fgetc、fputc函数
3、fgetc函数 file get char //咬文嚼字读
(1)功能:每次读取一个字符
(2)头文件及函数原型
#include <stdio.h>
int fgetc(FILE* stream);//stream 文件流
(3)参数说明
FILE *stream //fopen函数的返回值
(4)返回值
成功:实际读取的字符
失败:返回 EOF(-1) ,当读取到文件的尾巴的时候,返回EOF -1
#define EOF -1//宏定义
(5)实例
char ch; //char可以理解为特殊的整数
FILE *fp = fopen("hello.c","r");
ch = fgetc(fp);
/代码演示/
#include <stdio.h>
int main(int argc, const char *argv[])
{
char ch;//用来保存读取出的字符
//1.相对路径的方式打开当前目录下的hello.c
FILE* fp = fopen("./01-test.c","r");
if(fp == NULL)
{
printf("打开失败!!\n");
}
else//打开成功
{
printf("打开成功!!\n");
#if 0
ch = fgetc(fp);//调用一次,读取出一个字符,保存到ch中
printf("%c",ch);//打印
ch = fgetc(fp);//继续向后读取一个字符,保存到ch中
printf("%c",ch);//打印
ch = fgetc(fp);//继续向后读取一个字符,保存到ch中
printf("%c",ch);//打印
#endif
//思考:如何将01-test.c中的全部内容读取出来
//循环读取,那么什么时候读取到尾巴???,让循环结束,当fgetc函数的返回值是-1的时候
while(1)
{
ch = fgetc(fp);//调用一次,读取出一个字符,保存到ch中
//每读取一次,判断一下是否读取到文件的尾巴,如果读取到文件的尾巴,循环结束
if(ch == EOF)//if(ch == -1)
{
break;//结束循环
}
printf("%c",ch);//打印
}
//读写文件的前提条件是 文件必须是打开的,而且有对应的读写权限
fclose(fp);//关闭文件
}
return 0;
}
************************************************************************************
4、fputc函数 //fputc写入一个字符 咬文嚼字写
(1) 功能:向文件中写入一个字符
(2) 头文件及函数原型
#include <stdio.h>
int fputc(int c, FILE *stream);
(3) 参数说明:
int c //将要被写入文件的字符
FILE *stream //fopen返回值,写入文件流指针指向的那个文件 流指针代表的就是打开的那个文件
(4)返回值:
成功:
返回写入的字符
失败:
返回 -1
(5)实例
char ch = 'A'; //将要被写入文件的字符
fputc(ch,fp);
//案例代码
#include <stdio.h>
int main(int argc, const char *argv[])
{
//因为要写入一个字符,所以用的是"w"
FILE* fp = fopen("./xixi.txt","w"); //如果文件不存在,有创建功能
if(fp == NULL)
{
printf("fopen failed!!\n");
return -1;//提前结束程序
}
//程序如果能够执行到这,说明没有执行return -1, 打开文件成功
char ch = '#';//ch中保存的是即将写入文件的字符
fputc(ch,fp);//调用一次,写入一个字符
ch = 'A';
fputc(ch,fp);//再调用一次,继续向后写入一个字符
ch = 'B';
fputc(ch,fp);//再调用一次,继续向后写入一个字符
//关闭文件
fclose(fp);
return 0;
}
************************************************************************************
练习:用命令行传参./a.out a.c b.c,实现内容拷贝
#include <stdio.h>
int main(int argc, const char *argv[])
{
//容错判断
if(argc != 3)
{
printf("忘记传递参数了!! ./a.out a.c b.c \n");
return -1;
}
char ch;//用来保存读取出的字符
//以读的方式打开一个文件,以写的方式打开另一个文件
FILE* fpr = fopen(argv[1],"r");
FILE* fpw = fopen(argv[2],"w");
if(fpr == NULL || fpw == NULL)
{
printf("fopen failed!!\n");
return -1;
}
//循环拷贝,每读取一个字符,就立刻写入另一个文件
while(1)
{
ch = fgetc(fpr); //读取一个字符保存到ch中
if(ch == EOF)//读取到文件的尾巴
break;
fputc(ch, fpw); //将刚读取出的字符ch,写入到文件中
}
fclose(fpr);
fclose(fpw);
return 0;
}
fgets、 fputs、fflush函数
**********************************************************************************
5、fgets函数
(1)功能:每次读取一行字符串
(2)头文件及函数原型
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
(3)参数说明:
char buf[100]; //用来保存读取出来的字符串
fgets(buf, 100, fp);
char *s //读取的数据存放的位置
int size //一次最多读取100个字符
FILE *stream //fopen函数的返回值
(4)返回值:
成功:char * 返回值指向读取出字符串的指针
失败:返回 NULL ,读取到文件尾返回NULL
(5) 实例
char buf[100]; //用来保存读取的一行
fgets(buf, 100, fp);
//案例代码
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };//用来保存读取出的一行字符串
FILE* fp = fopen("./02-test.c","r");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
#if 0
fgets(buf, 100, fp);//调用一次fgets函数,就读取一行,存储到buf中
printf("%s",buf);//将buf打印
fgets(buf, 100, fp);//调用一次fgets函数,就继续向后读取一行,存储到buf中
printf("%s",buf);//将buf打印
fgets(buf, 100, fp);//调用一次fgets函数,就继续向后读取一行,存储到buf中
printf("%s",buf);//将buf打印
#endif
char* ret = NULL;//用来保存fgets的返回值,来判断是否读取到文件尾巴
//方法一
/*
while(1)
{
ret = fgets(buf, 100, fp);
if(ret == NULL)//读取到文件尾巴
{
break;
}
printf("%s",buf);
}
*/
//方法二
while(fgets(buf, 100, fp) != NULL)//fgets函数返回值 == NULL 循环结束
{
printf("%s",buf);
}
fclose(fp);
return 0;
}
**********************************************************************************
6、fputs函数
(1)功能:每次写入一行
(2)头文件及函数原型
#include <stdio.h>
int fputs(const char *s, FILE *stream);
(3)参数说明:
char buf[100] = "hello world!!!";
fputs(buf, fp);//将buf数组中存储的字符串,写入到fp指向的打开的那个文件中
const char *s //即将写入文件的字符串存放的位置
FILE *stream //fopen函数的返回值
(4)返回值:
成功: >= 0
失败:返回 EOF
(5)实例:
char buf[100] = "hello world";
fputs(buf,fp);
//案例代码
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = "hello world";//即将写入文件中的字符串
int i;
FILE* fp = fopen("haha.txt","w");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
//将一个字符串写入到haha.txt中 循环输入3个名字,写入到haha.txt中
for(i = 0; i < 3; i++)
{
printf("请输入要写入的名字:\n");
scanf("%s",buf);//你输入两次之后,ctrl+c,程序直接结束,没有刷新缓存区,所以前两次的名字没有写入成功
fputs(buf, fp);
}
fclose(fp);//关闭文件,同时刷新缓存区
return 0;
}
//上面的程序,输入两次名字后,按ctrl + c,前两次的名字没有写入成功,证明标准IO存在缓存区
//flush函数,刷新缓存区
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char buf[100] = "hello world";//即将写入文件中的字符串
int i;
FILE* fp = fopen("haha.txt","w");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
//将一个字符串写入到haha.txt中
//循环输入3个名字,写入到haha.txt中
for(i = 0; i < 3; i++)
{
printf("请输入要写入的名字:\n");
scanf("%s",buf);//你输入两次之后,ctrl+c,程序直接结束,没有刷新缓存区,所以前两次的名字没有写入成功
//要将写入的名字换行
strcat(buf,"\n");//在输入的字符串尾巴后,加上一个\n
fputs(buf, fp);
//我们如何实现,每写入一个名字,就立刻写入haha.txt,人为刷新缓存区,fflush函数
fflush(fp);//刷新缓存区
}
fclose(fp);//关闭文件,同时刷新缓存区
return 0;
}
---------------------------------练习 复制文件------------------------------------
上一个练习是用 fgetc 和 fputc实现文件拷贝
用fgets和fputs,命令行传参 实现文件的拷贝
//./a.out a.c b.c
#include <stdio.h>
int main(int argc, const char *argv[])
{
//容错判断
if(argc != 3)
{
printf("忘记传递参数了!! ./a.out a.c b.c \n");
return -1;
}
char buf[100] = { 0 };//用来保存读取出的每行字符串
FILE* fpr = fopen(argv[1],"r");
FILE* fpw = fopen(argv[2],"w");
if(fpr == NULL || fpw == NULL)
{
perror("fopen failed");
return -1;
}
//循环读取拷贝
//方法一
/*
while(1)
{
//思想:循环,读取一行,就立刻写入一行
char* ret = fgets(buf, 100, fpr);
if(ret == NULL)//每读取一次,对返回值进行一次判断,是否读取到文的尾巴
{
break;
}
fputs(buf,fpw);
}
*/
//方法二
while(fgets(buf, 100, fpr) != NULL)//读取一行,立刻写入一行,读取到文件尾巴,循环结束
{
fputs(buf,fpw);
}
//关闭文件
fclose(fpr);
fclose(fpw);
return 0;
}
fread、fwrite、memset函数
**********************************************************************************
fgetc fputc //一次读写一个字符 咬文嚼字
fgets fputs //一次读写一行字符串 一次一行
fread fwrite//一次读取写入一大块 (fread 就不会受到 \n的影响)
7、fread函数
(1)功能: 按块读 每次读取一段(注意一段以块数为单位)
(2)头文件及函数原型
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
有类型的指针,可以赋值给无类型的指针
(3)参数说明:
void *ptr //读取出的数据存放的位置
size_t size //一块的大小(一块占几个字节)
size_t nmemb //一次最多读取多少块
FILE *stream //fopen函数的返回值
char buf[100] = { 0 };
buf ---> char*
fread(buf, 1, 100, fp);//一块的大小是1个字节,一次最多读取100块
fread(buf, 10, 10, fp);//一块的大小是10个字节,一次最多读取10块
int b[10];
b ---> int*
fread(b, 4, 10, fp); //一块的大小是4个字节,一次最多读取10块
(4)返回值:
成功:实际读取的 块数 (当前不行,不能将块数改为字节数)
失败: 小于0
-------------------例子1次读1000字节------------------------
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[1000] = { 0 };//用来保存读取到的内容
FILE* fp = fopen("./01-test.c","r");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
//fread一次,就将 01-test.c的全部内容读取出来了,是buf数组足够大
//01-test.c文件中的字符 < 1000个
int ret = fread(buf, 1, 1000, fp);//1块的大小是1个字节,一次最多读取1000块
printf("实际读取的块数是%d\n",ret);
printf("%s",buf);
return 0;
}
--------------------例子分批次读-------------------------------
//假设你的buf不1000,不足够装下01-test.c文件的全部内容,我们需要用循环读取
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };//用来保存读取到的内容
FILE* fp = fopen("./01-test.c","r");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
//用于buf数组,不足够大,一次不能装下01-test.c全部内容,所以需要循环读取
while(fread(buf, 1, 100, fp) > 0)//只要fread函数返回值 > 0 ,就说明实际读取到了内容
{
printf("%s",buf);
//为了解决还有残留物的bug,我们需要每次读取之后,清空一次buf数组,要不然buf数组中,有可能存有上一次读取的残留物
memset(buf, 0, sizeof(buf));//将buf数组的整个清空,全部赋值为'\0'
}
fclose(fp);
return 0;
}
******************************************************************************
8、fwrite
(1)功能:按块写 (可以以二进制的方式写入)
(2)头文件及函数原型
#include <stdio.h>
//void* 无类型指针,可以被其他的有类型的指针赋值
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
(3)参数说明:
char buf[100] = "hello world!!";
fwrite(buf,1, 100, fp);
const void *ptr //即将写入文件数据存放的位置, 需要的是指针,存储位置的首地址
size_t size //实际写入过程中,1块的大小
size_t nmemb //实际要写入的块数
FILE *stream //fopen函数的返回值
(4)返回值:
成功:实际写入的块数
失败:小于0
对比: fwrite(buf, 1, 100, fpw) 和 fwrite(buf, 1, strlen(buf), fpw)区别
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char buf[100] = "hello world!!";
FILE* fpw = fopen("./haha.c","w");
if(fpw == NULL)
{
perror("fopen failed");
return -1;
}
// fwrite(buf, 1, 100, fpw);//写成100,代表100块,将整个buf数组的全部内容,100块都写进去了
fwrite(buf, 1, strlen(buf), fpw);//只将buf数组的前13块,写入文件中
fclose(fpw);
return 0;
}
文件分为两种类型
(1) 可以用记事本打开,并且不是乱码的,是可见字符,这种文件叫文本文件, 里面保存的是ascii码
hello.c a.txt
(2) 二进制文件,可执行文件,如果用记事本打开,是乱码
int a[10] = {11,22,33,44,55,66,77,88,99,100};
int b[10] = { 0 };
//1. 先将a数组中的内容写入到一个文件 a.txt中
fwrite(a, 4, 10, fp); //一块的大小是4个字节,一次写入10块
//2. 将a.txt中的内容读取出来,写入到b数组中
fread(b, 4, 10, fp);//一块的大小是4个字节,最多读取10块
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
int i;
int a[10] = {11,22,33,44,55,66,77,88,99,100};
int b[10] = { 0 };
//将a数组中的内容写入到文件a.txt中
FILE* fp = fopen("./a.txt","w");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
fwrite(a, 4, 10, fp); //一块的大小是4个字节,一次写入10块
//写完之后,文件指针,在文件的尾巴
//关闭文件
fclose(fp);
fp = fopen("./a.txt","r");//关闭文件之后,再次打开文件,文件指针默认在文件的头
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
//将a.txt中的整数,读取到b数组中
fread(b, 4, 10, fp); //一块的大小是4个字节,一次最多读取10块
fclose(fp);
for(i = 0; i < 10; i++)
{
printf("%d\n",b[i]);
}
return 0;
}
printf sprintf fprintf 函数
///printf sprintf fprintf 函数///
int fprintf(FILE *stream, const char *format, ...);
int printf(const char *format, ...);
int sprintf(char *str, const char *format, ...);
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };
int a = 10;
//printf("a is %d\n",a);//将"a is 10"这个字符串,打印输出到屏幕上
sprintf(buf,"a的值是%d\n",a);//将"a的值是10"这个字符串打印输出到buf数组中,相当于给buf数组赋值
//sprintf已经将原本打印输出到屏幕中字符串,打印输出到buf数组中
//把buf数组打印
printf("buf is %s",buf);
//fprintf
FILE* fp = fopen("xixi.c","w");
//条件判断,暂时不写
fprintf(fp,"a ------> %d\n",a);
//将"a ------> 10"这个字符串打印输出到fp流指针,指向的打开文件,就相当于将"a-->10"这个字符串,
//写入xixi.c文件
return 0;
}
1.3fseek、ftell
9、fseek()函数
(1)功能:移动文件指针的位置
(2)头文件及函数原型
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
(3)参数说明:
FILE *stream //fopen函数的返回值
long offset //偏移量 正数向后移动,负数向前移动
int whence //相对的基准位置
SEEK_SET //相对于文件的首
SEEK_END //相对于文件的尾巴
SEEK_CUR //相对于当前文件指针的位置 current
fseek(fp,100,SEEK_SET); //相对于文件的首,向后移动100个位置
fseek(fp,-100,SEEK_END); //相对于文件的尾,向前移动100个位置
fseek(fp,100,SEEK_CUR); ///相对于当前文件指针的位置,向后移动100个位置
fseek(fp,-100,SEEK_CUR); ///相对于当前文件指针的位置,向前移动100个位置
fseek(fp, 0, SEEK_SET); //没有偏移量,相当于将文件指针,移动到文件的首
fseek(fp, 0, SEEK_END); //没有偏移量,相当于将文件指针,移动到文件的尾巴
(4)返回值:
成功: 0
失败: EOF
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("test.c","w+");//不存在(创建) + 存在(清空) + 读写
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
fputc('A',fp);
fputc('B',fp);
//因为写入文件之后,文件指针在文件的尾巴,要想从头读取,需要将文件指针
//移动到文件的首部
//fseek(fp, 0, SEEK_SET);//没有偏移量,相当于移动的文件的首部
//fseek(fp, -2, SEEK_END);//相对于文件的尾巴,向前移动2个位置
//写入A和B之后,当前的文件指针,在文件的尾巴
fseek(fp, -2, SEEK_CUR);//相对于当前文件指针的位置,向前移动2个
char ch = fgetc(fp);
printf("ch is %d\n",ch);
printf("%c",ch);
fclose(fp);
return 0;
}
***********************************************************************************
10、ftell()函数
(1)功能:获取文件指针的位置
(2)头文件及函数原型
#include <stdio.h>
long ftell(FILE *stream);
(3)参数说明:
FILE *stream //fopen函数的返回值
(4)返回值
成功:返回当前文件指针的位置
失败:EOF
#include <stdio.h>
int main(int argc, const char *argv[])
{
long post;//保存文件指针的位置
FILE* fp = fopen("test.c","w+");//不存在(创建) + 存在(清空) + 读写
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
//刚打开文件,获取下文件指针的位置
post = ftell(fp);
printf("post is %ld\n",post);//post is 0
fputc('A',fp);//写入字符'A'后,文件指针,向后移动一个位置
//写入一个字符后,再次获取文件指针的位置
post = ftell(fp);
printf("post is %ld\n",post);//post is 1
fputc('B',fp);
post = ftell(fp);
printf("post is %ld\n",post);//post is 2
//因为写入文件之后,文件指针在文件的尾巴,要想从头读取,需要将文件指针
//移动到文件的首部
//fseek(fp, 0, SEEK_SET);//没有偏移量,相当于移动的文件的首部
//fseek(fp, -2, SEEK_END);//相对于文件的尾巴,向前移动2个位置
//写入A和B之后,当前的文件指针,在文件的尾巴
fseek(fp, -2, SEEK_CUR);//相对于当前文件指针的位置,向前移动两个
char ch = fgetc(fp);
printf("ch is %d\n",ch);
printf("%c",ch);
post = ftell(fp);
printf("post is %ld\n",post);//post is 1
fclose(fp);
return 0;
}
1.4综合练习
练习:统计文件行数
#include "my.h"
int main(int argc, const char *argv[])
{
char ch;
char buf[100] = { 0 };
int count = 0;//用来统计行数
FILE* fp = fopen("./01-test.c","r");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
//方法一 fgetc实现
#if 0
while((ch=fgetc(fp)) != EOF)
{
if(ch == '\n')
count++;
}
printf("row is %d\n",count);
#endif
while(fgets(buf, 100, fp) != NULL)
{
count++;
}
printf("row is %d\n",count);
fclose(fp);
return 0;
}
练习:用fread和fwrite实现文件拷贝 ./mycp aaa bbb // ./mycp a.c b.c
#include "my.h"
int main(int argc, const char *argv[])
{
if(argc != 3)
{
printf("忘记传递参数了!! ./a.out a.c b.c");
return -1;
}
int ret;
char buf[100]={0};
FILE* fp=fopen(argv[1],"r");
FILE* fv=fopen(argv[2],"w");
if(fp == NULL || fv == NULL)
{
perror("fopen failed");
return -1;
}
//每次实际读取多少块,就写入多少块
//fread函数的返回值就是每次实际读取的块数
while((ret=fread(buf,1,100,fp))>0)
fwrite(buf,1,ret-1,fv);//参数是ret,代表实际写入的块数,实际读取多少块,就写入多少
fclose(fp);
fclose(fv);
return 0;
}
****************************************综合测试***********************************
编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样
//test.txt 不存在创建,追尾写内容
./a.out
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
ctrl + c
该程序应该无限循环,直到按Ctrl-C中断程序。下次再启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
./a.out ,接着上一次的尾巴,接着写,你需要统计行号,fgets,一次读取一行,统计文件中的原有行数
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04
***************************************代码****************************************
#include <time.h>
#include <stdio.h>
struct tm{
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
};
int main(int argc, const char *argv[])
{
//1.获取距今的秒数
time_t raw_time;//用来保存秒数
struct tm* tp = NULL;//用来保存指向中文时间结构体的指针
int line = 0;//用来记录行号
char buf[100] = { 0 };//用来保存读取到的一行内容
//2.先打开test.txt,统计test.txt中的已经存在内容的行数,方便追尾写
FILE* fp = fopen("test.txt","a+");//注意此处是a+
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
while(fgets(buf, 100, fp) != NULL)
line++;
printf("line is %d\n",line);//line is 5
while(1)
{
time(&raw_time);//要写在循环的里面,调用一次,得到一次距今的秒数,再转换为年 月 日
//将秒数转为中文格式的时间
tp = localtime(&raw_time);
//打印
// printf("%d,%02d-%02d-%02d %02d:%02d:%02d\n",++line,tp->tm_year+1900,tp->tm_mon+1,tp->tm_mday,
// tp->tm_hour,tp->tm_min,tp->tm_sec);
fprintf(fp,"%d,%02d-%02d-%02d %02d:%02d:%02d\n",++line,tp->tm_year+1900,tp->tm_mon+1,tp->tm_mday,
tp->tm_hour,tp->tm_min,tp->tm_sec);
/*
//将格式化好的字符串,存储到buf数组中,然后再将buf数组写入文件
sprintf(buf,"%d,%02d-%02d-%02d %02d:%02d:%02d\n",++line,tp->tm_year+1900,tp->tm_mon+1,tp->tm_mday,
tp->tm_hour,tp->tm_min,tp->tm_sec);
fputs(buf, fp);
*/
//因为标准IO存在缓存区,所以每写一次,需要刷新缓存区
fflush(fp);//刷新缓存区
sleep(1);
}
return 0;
}
二、Linux I/O
2.1open、read、write、close
##############################################################################
linux 文件IO
POSIX 可移植性操作系统规范 规定的一些列对文件操作的函数(打开、读写、关闭)
没有缓存
打开 读 写 关闭 Linux系统自带的,是系统调用
##############################################################################
1、open函数
(1)功能:打开文件文件
(2)头文件及函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
(3)参数说明:
const char *pathname //打开文件的名称,可以用相对路径也可以用绝对路径
int flags //打开文件的方式 (读 写 清空 追尾写)
O_RDONLY //只读 read
O_WRONLY //只写 write
O_RDWR //读 + 写
O_CREAT //创建
O_TRUNC //清空
O_APPEND //追加尾巴
mode_t mode //文件的权限 0666 ,当第二个参数中有O_CERAT的时候,需要加第三个参数,创建文件的权限
(4)返回值:
成功 :返回值是int类型,文件描述符(对应的就是open函数打开的那个文件的标识)
int fd //返回值 ,如果成功是非负的正整数
失败:-1
(3)案例
int fd = open("hello.c",O_RDONLY);//以只读的方式,打开当前下的hello.c文件
int fd = open("hello.c",O_RDONLY | O_CREAT,0666); //如果文件不存在,创建并打开
------------------------------------例子代码----------------------------------------
///my.h
#ifndef _MY_H
#define _MY_H //如何避免头文件的重复包含
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#endif
#include "my.h"
//在预处理阶段,会将my.h中的内容全部拷贝过来
int main(int argc, const char *argv[])
{
//打开文件
int fd = open("./haha.c",O_RDWR | O_CREAT | O_TRUNC, 0666);//以可读可写的方式打开,如果文件存在,清空并打开,不存在,创建并打开
if(fd == -1)
{
perror("打开失败原因");
return -1;
}
printf("打开成功!!!\n");
printf("fd is %d\n",fd); //fd is 3 ,为什么是从3开始的呢???后面讲
//关闭文件
close(fd);
return 0;
}
***********************************************************************************
2、close(int fd);
功能: 关闭文件
参数:open函数的返回值
返回值:
成功:0 失败:-1
************************************************************************************
3、read函数
(1) 功能:
从文件中读取数据(按字节)
(2) 头文件及函数原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
(3) 参数说明:
int fd //open函数的返回值
void *buf //读出数据存放的位置
size_t count //最多读取的字节数
char buf[100] = { 0 };//用来保存读取到的内容
int ret = read(fd, buf, 100);//ret 代表read实际读取到的字节数
printf("%s",buf);
(4) 返回值:
成功:只要函数的返回值 > 0 ,就说明读取到了内容 实际读取到的字节数
失败:-1
(5)实例
char buf[100] = { 0 };
read(fd,buf,sizeof(buf));
------------------------------------------------------------------------------------
---------------------------------分批次读取实例 -------------------------------------
#include "my.h"
//在预处理阶段,会将my.h中的内容全部拷贝过来
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };//用来保存读取到的文件内容
//打开文件
int fd = open("./haha.c",O_RDWR);//以可读可写的方式打开
if(fd == -1)
{
perror("打开失败原因");
return -1;
}
printf("打开成功!!!\n");
printf("fd is %d\n",fd);
//写一个while循环,将文件的内容全部读取出来
while(read(fd, buf, sizeof(buf)) > 0)//只要函数的返回值>0,就说明读取到了内容
{
printf("%s",buf);
memset(buf, 0, sizeof(buf));//最后一次读取,实际读取的字节数可能 != 100,之后覆盖前面的一部分,后面的
//是上一次的残留物,所以每次读取之前,都清空buf数组,避免有残留物的bug
}
//关闭文件
close(fd);
return 0;
}
------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
char buf[100];
fread(buf, 1, sizeof(buf), fp) 返回值是实际读取到的块数,对象数,记录数 ,不能说字节数
int b[10];//sizeof(b) == 40
fread(b, 4, 10, fp); 1块的大小是4个字节, 一次最多读取10块
read(fd, buf, sizeof(buf)); 返回值 实际读取到的字节数
------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
练习:实现如果文件不存在,创建,文件存在,输出文件内容 文件名从参数中传递过来
./a.out a.c
#include "my.h" //在预处理阶段,会将my.h中的内容全部拷贝过来
int main(int argc, const char *argv[])
{
if(argc != 2)
{
printf("忘记传递参数了!! ./a.out a.c\n");
return -1;
}
char buf[100] = { 0 };//用来保存读取到的文件内容
//打开文件
int fd = open(argv[1],O_RDONLY | O_CREAT, 0666);//如果文件存在,以只读方式打开,不存在,创建并以只读方式打开
if(fd == -1)
{
perror("打开失败原因");
return -1;
}
printf("打开成功!!!\n");
printf("fd is %d\n",fd);
//写一个while循环,将文件的内容全部读取出来
while(read(fd, buf, sizeof(buf)) > 0)//只要函数的返回值>0,就说明读取到了内容
{
printf("%s",buf);
memset(buf, 0, sizeof(buf));//最后一次读取,实际读取的字节数可能 != 100,之后覆盖前面的一部分,后面的
//是上一次的残留物,所以每次读取之前,都清空buf数组,避免有残留物的bug
}
//关闭文件
close(fd);
return 0;
}
************************************************************************************
4、write函数
(1)功能:写入数据(按字节)
(2)头文件及函数原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
(3) 参数说明:
int fd //open函数的返回值
const void *buf //即将写入文件的数据,存放的位置
size_t count //实际写入文件的字节数
char buf[100] = "hello world!!";
write(fd, buf, sizeof(buf));//将整个buf数组中的100个字节都写入文件
write(fd, buf, strlen(buf)); //只将buf数组中的前 strlen(buf)个字符,写入文件
(4) 返回值:
成功:实际写入的字节数
失败: -1
(5)实例
char buf[100] = "hello";
write(fd,buf,sizeof(buf));
//对比 write(fd, buf, sizeof(buf)) 和 write(fd, buf, strlen(buf)) 的区别
#include "my.h"
int main(int argc, const char *argv[])
{
char buf[100] = "hello world!!";
int fd = open("wuwu.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd == -1)
{
perror("open failed");
return -1;
}
//write(fd, buf, sizeof(buf));//将整个buf数组的100字节写入文件
write(fd, buf, 13);//13可以改写为strlen(buf) 将整个buf数组的前13个字符,写入文件
close(fd);
return 0;
}
//案例代码
#include "my.h"
int main(int argc, const char *argv[])
{
int i;
char buf[100] = "hello world!!";
int fd = open("wuwu.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd == -1)
{
perror("open failed");
return -1;
}
for(i = 0; i < 3; i++)
{
printf("请您输入要写入的名字:\n");
scanf("%s",buf); //输入两次名字后,按ctrl + c,结束程序,前两次的名字,写入文件成功,证明 Linux文件IO无缓存区
write(fd, buf, strlen(buf));//调用写入,立刻写入文件,Linux文件IO,因为没有缓存区
}
close(fd);
return 0;
}
------------------------------------------------------------------------------------
结论:证明 Linux文件IO无缓存区
之前所学的标准IO是有缓存区的,如果提前按了ctrl + c,前两个名字是写入不成功的
2.2perror、lseek函数
5、perror函数(根据errno的值,输出错误信息)
(1)功能:能够将程序执行失败的具体错误原因提示出来
linux系统里有一个全局变量,errno用来保存错误信息编号,错误输出信息的打印根据错误编号而来
(2)头文集及函数原型
#include <stdio.h>
void perror(const char *s);
perror("打开失败的原因是");
(3) 参数说明:
char *s //打印的错误提示
//案例代码
#include <stdio.h>
int main(int argc, const char *argv[])
{
//1.相对路径的方式打开当前目录下的hello.cc
FILE* fp = fopen("hello.cc","r");
if(fp == NULL)
//你知道打开失败的具体原因吗???
//通过perror函数,可以打印输出 打开失败的具体原因,方便改错
perror("打开失败的原因是");
else
printf("打开成功!!\n");
return 0;
}
//strerror函数,也能打印错误提示信息
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
//1.相对路径的方式打开当前目录下的hello.cc
FILE* fp = fopen("hello.cc","r");
if(fp == NULL)
//你知道打开失败的具体原因吗???
perror("打开失败的原因是");
printf("errno is %d\n",errno);//errno是一个全局变量
printf("打开失败的原因是:%s\n",strerror(errno));
//errno是个全局变量 记录的是错误信息的编号
//strerror函数的返回值,就是那个错误原因字符串的首地址
else
printf("打开成功!!\n");
return 0;
}
#######练习###### 求出某个文件大小
#include <stdio.h>
int main(int argc, const char *argv[])
{
int count = 0;//统计字符的个数
FILE* fp = fopen("./fseek.c","r");
if(fp == NULL)
{
perror("fopen failed");
return -1;
}
/*
//方法一
char ch;
while(fgetc(fp) != EOF)//一次读取一个字符
{
count++;
}
printf("size is %d\n",count);
*/
//方法二
//先将文件指针移动到文件的尾巴
fseek(fp, 0, SEEK_END);
//文件指针尾巴对应的下标位置,就是整个文件的大小
printf("size is %ld\n",ftell(fp));
return 0;
}
***********************************************************************************
6、lseek()函数
(1)功能:移动文件指针,同时能够获取文件指针的位置
lseek功能 = fseek功能 + ftell功能
(2)头文件及函数原型
#include <sys/types.h>
#include <unistd.h>
typedef long off_t;
off_t lseek(int fd, off_t offset, int whence);
(3)参数说明:
int fd //open函数的返回值
off_t offset //偏移量 正数先后移动 负数向前移动
int whence //相对于基准位置
lseek(fd,100,SEEK_SET); //相对于文件的首,向后移动100个位置
lseek(fd,-100,SEEK_END); //相对于文件的尾,向前移动100个位置
lseek(fd,100,SEEK_CUR); ///相对于当前文件指针的位置,向后移动100个位置
lseek(fd,-100,SEEK_CUR); ///相对于当前文件指针的位置,向前移动100个位置
lseek(fd, 0, SEEK_SET); //没有偏移量,相当于将文件指针,移动到文件的首
lseek(fd, 0, SEEK_END); //没有偏移量,相当于将文件指针,移动到文件的尾巴
(4)返回值:
成功:返回当前文件指针的位置
失败:-1
2.3stdin、stdout、stderr
标准输入、标准输出、标准错误输出
FILE* fp = fopen();
一个程序,在运行的时候,会自动打开三个 流 标准输入流、标准输出流、标准错误输出流
./a.out
Linux系统中一切皆文件
标准输入流 stdin 默认是键盘 文件描述符 0
标准输出流 stdout 默认是终端 文件描述符 1
标准错误输出流 stderr 默认是终端 文件描述符 2
error stdin\stdout stderr
代码举例///
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };
//1.键盘文件,从键盘输入中读取内容到buf数组中
fgets(buf, 100, stdin);//stdin标准输入流,默认是键盘,效果类似于scanf("%s",buf)
// printf("buf is %s",buf);
//2.将buf数组中的内容,写入到终端,写入终端,就相当于在终端打印
fputs(buf,stdout);//stdout 标准输出流,默认是终端
fputs(buf,stderr);//stderr 标准错误输出流,默认是终端
return 0;
}
2.4标准C库IO与linux文件IO比较
标准C库IO与linux文件IO比较:
标准I/O fopen fclose fgetc fputc fgets fputs fread fwrite fflush fprintf fseek ftell
操作文件的时候,除了fopen函数以为,每个函数都要使用fopen函数的返回值,流指针 FILE* fp
流指针,对应的是打开的那个文件
Linux文件I/O open close read write lseek
操作文件的时候,除了open函数以为,每个函数都要使用open函数的返回值,文件描述符 int fd
文件描述符,对应的是打开的那个文件
(1) 标准I/O 有缓存区 Linux文件I/O没有缓存区
(2) 函数返回值类型不同,标准I/O函数的返回值是 FILE* fp 流 Linux文件I/O返回值是int fd 文件描述符
(3) 标准I/O C库提供的函数, LInux文件I/O是 Linux系统自带的 系统调用
三、文件缓存
3.1无缓存、行缓存、全缓存
(1)全缓存
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = "hello world!!";//注意此处,没有换行符\n,等到缓存区满,一次性打印输出一堆
while(1)
{
fputs(buf, stdout);//将buf数组的内容,写入到标准输出
sleep(1);
}
return 0;
}
(2)行缓存
行缓存:存在的前提条件是写入的文件是终端
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = "hello world!!\n";//注意此处有\n,写入文件是终端,行缓存,直接打印输出
while(1)
{
fputs(buf, stdout);//将buf数组的内容,写入到标准输出
sleep(1);
}
return 0;
}
(3)无缓存
标准错误输出是无缓存
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = "hello world!!";//注意此处没有有\n,
while(1)
{
fputs(buf, stderr);//将buf数组的内容,写入到标准错误输出,没有缓存,立刻打印输出
sleep(1);
}
return 0;
}
四、目录文件函数
4.1stat函数
stat函数: 可以查看普通文件的属性,也可以查看目录文件(文件夹子)的属性
(1)功能:获取文件属性的
(2)头文件及函数原型
#include <unistd.h>
int stat(const char *path, struct stat *buf);
struct stat s = { 0 }; //s是想用来保存文件属性相关信息的结构体
//想要调用stat函数,实现修改结构体变量s的值,需要采用值传递还是地址传递????
//要采用地址传递,因为地址传递可以修改实参变量的值
//调用完stat函数之后,结构体变量s,不再是空,存上了文件的属性信息
//s的类型是struct stat &s的类型是 struct stat*
stat("./hello.c",&s); //相对路径
stat("/home/linux/hello.c",&s); //绝对路径
一个函数想要给他的调用者传递值 1. return value; 返回值的形式 2. 参数上的地址传递
(3)参数说明:
const char *path //获取文件属性的那个文件的名字,可以用相对路径也可以用绝对路径
struct stat *buf
(4)返回值:
成功:返回: 0
失败:返回: -1
struct stat {
mode_t st_mode; //文件对应的模式,文件,目录等
ino_t st_ino; //inode节点号
dev_t st_dev; //设备号码
dev_t st_rdev; //特殊设备号码
nlink_t st_nlink; //文件的连接数
uid_t st_uid; //文件所有者
gid_t st_gid; //文件所有者对应的组
off_t st_size; //普通文件,对应的文件字节数
time_t st_atime; //文件最后被访问的时间
time_t st_mtime; //文件内容最后被修改的时间
time_t st_ctime; //文件状态改变时间
blksize_t st_blksize; //文件内容对应的块大小
blkcnt_t st_blocks; //文件内容对应的块数量
};
*********************读出文件的最后修改时间和大小**********************
#include "my.h"
int main(int argc, const char *argv[])
{
//定义一个文件属性结构体变量,用来保存文件的属性信息
struct stat s = { 0 };
int ret = stat("./01-test.c",&s);//参数上的地址传递的方式得到文件的属性
if(ret == -1)
{
perror("stat failed");
return -1;
}
printf("size is %ld\n",s.st_size);//文件大小 因为s是结构体变量,访问成员变量用.
printf("last time is %s",ctime(&s.st_mtime));//文件最后的访问时间
return 0;
}
4.2opendir、readdir、closedir
************************************************************************************
opendir()函数//打开的是目录文件
(1)功能:打开目录文件 directory
(2)头文件及函数原型
#include <dirent.h>
DIR *opendir(const char *name);
(3)参数说明:
const char *name //打开的目录文件的名字,可以是相对路径也可以是绝对路径
(4)返回值:
成功:DIR* //打开成功,返回值是 DIR*,指向这个打开的目录文件的指针
失败:NULL
************************************************************************************
readdir()函数
(1)功能:读取目录文件,每次读取出一个文件
(2)头文件及函数原型
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
(3) 参数说明:
DIR *dirp //opendir 函数的返回值
(4) 返回值:
成功:struct dirent * //返回值是读取出的文件的信息结构体的首地址
//既然我们有了结构体的首地址,就可以访问成员变量
失败:返回 NULL //读取到目录文件尾巴的时候,返回NULL
#include <dirent.h>
struct dirent
{
long d_ino; /* inode number 索引节点号 */
off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
unsigned short d_reclen; /* length of this d_name 文件名长 */
unsigned char d_type; /* the type of d_name 文件类型 */
char d_name[NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
}
************************************************************************************
closedir()函数
(1)功能:关闭目录文件
(2)头文件及函数原型
#include <dirent.h>
int closedir(DIR *dirp);
(4) 返回值:
DIR *dirp //opendir函数的返回值
(4) 返回值:成功:0 失败:-1
closedir(dp);
///
例子:打开目录,读出目录里面所有的文件(不包括隐藏文件)
/my.h///
#ifndef _MY_H
#define _MY_H
//如何避免头文件的重复包含
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#endif
#include "my.h"
int main()
{
struct dirent* ds = NULL;
//打开目录文件
DIR* dp = opendir("/home/linux/22021班"); //打开 22021班 这个目录文件
if(dp == NULL)
{
perror("opendir failed");//打开失败
return -1;
}
//读取目录文件中的文件,调用一次readdir函数,只能读取一个文件
/*
ds = readdir(dp);
printf("filename is %s\n",ds->d_name);//调用一次readdir函数,读取一个文件
ds = readdir(dp);
printf("filename is %s\n",ds->d_name);//调用一次readdir函数,继续向后读取下一个文件
ds = readdir(dp);
printf("filename is %s\n",ds->d_name);//调用一次readdir函数,继续向后读取下一个文件
ds = readdir(dp);
printf("filename is %s\n",ds->d_name);//调用一次readdir函数,继续向后读取下一个文件
*/
//循环读取,将整个目录文件中的所有文件读取出来
while((ds=readdir(dp)) != NULL)//当readdir返回值 == NULL,读取到目录尾巴,循环结束
{
//过滤一下,不显示隐藏文件
if(ds->d_name[0] == '.')//以.开头的是隐藏文件
{
continue;//结束本次循环
}
printf("filename is %s\n",ds->d_name);
//你有了文件的名字
//stat(路径+文件的名字);
}
//关闭目录文件
closedir(dp);
return 0;
}
-----------------------------------------------------------------------------------
练习:实现列出某个目录中所有文件属性(文件名,文件大小,文件最后修改时间) stat()
//获取属性,需要调用stat函数,使用stat函数,就需要获取文件属性文件的名字
//读取目录文件 opendir readdir closedir
目录名由命令行参数传入 ./a.out /home/linux
文件名:%s 文件大小%ld 文件修改时间%s
#include "my.h"
int main(int argc, char* argv[])
{
if(argc != 2)
{
printf("忘记传递参数了!!\n ./a.out /home/linux");
return -1;
}
int ret;
char pathname[100] = { 0 };//用来保存路径+文件名字
struct stat s = { 0 };//用来保存文件的属性
struct dirent* ds = NULL;
//打开目录文件
DIR* dp = opendir(argv[1]);
{
perror("opendir failed");//打开失败
return -1;
}
//循环读取,将整个目录文件中的所有文件读取出来
while((ds=readdir(dp)) != NULL)//当readdir返回值 == NULL,读取到目录尾巴,循环结束
{
//过滤一下,不显示隐藏文件
if(ds->d_name[0] == '.')//以.开头的是隐藏文件
{
continue;//结束本次循环
}
//printf("filename is %s\n",ds->d_name);
sprintf(pathname,"%s/%s",argv[1],ds->d_name);
//%s /home/linux
//%s haha.c 将路径和文件的名字拼接成一个完整的路径+文件名,保存到pathname中
//获取每一个文件的属性
ret = stat(pathname, &s);//直接获取属性,获取是当前目录下的文件名字,所以找不到文件
if(ret == -1)
{
perror("stat failed");
return -1;
}
printf("%s %ld %s\n",ds->d_name,s.st_size, ctime(&s.st_mtime));
}
//关闭目录文件
closedir(dp);
return 0;
}
///关于sprintf函数的再次理解
sprintf可以个格式化出一个你想要的字符串,保存到一个数组中
#include <stdio.h>
int main(int argc, const char *argv[])
{
char pathname[100];
//sprintf可以个格式化出一个你想要的字符串,保存到一个数组中
char *p = "/home/linux";
char d_name[] = "haha.c";
//printf("%s/%s",p,d_name);//打印输出到屏幕上
//不是打印输出,而是合成一个新的字符串,存到pathname数组中
sprintf(pathname,"%s/%s",p,d_name);//将原本打印输出到屏幕上的字符串,打印输出到pathname数组中,相当于给pathname数组赋值
printf("pathname is %s\n",pathname);
return 0;
}
***********************************************************************************
总结
这里对文章进行总结:文件I/O主要包括标准C库提供的函数、和Linux系统自带的文件I/O函数,了解文件缓存的细节及不同函数的头文件、返回值、参数及意义区别;掌握目录文件的相关操作函数,记不住的情况下,可使用man命令,查看函数的详细信息!