/**********8****************
*1.linux中一切皆文件
*2.两种开发思路:
* A.基于库的开发
* B.基于系统调用的开发
****************************/
/*-----------------day - 1 基于库的开发----------------------------*/
一、流:
将数据输入输出(传送过程)抽象为数据流,也叫流/*{{{*/
标准I/O库的所有操作都是围绕流(stream)来进行的。
标准IO库把一个打开的文件模型化为一个流
流的描述:在标准I/O中,流用FILE *来描述:
FILE *本质上是对应保存一个打开文件信息的内存区域首地址
FILE *结构体指针,也称为流指针,文件指针
三个默认打开的文件:标准输入文件 标准输出文件 标准出错文件
对应流指针 stdin stdout stderr/*}}}*/
二、格式化输出函数簇printf()/fprintf()/sprintf()
#include <stdio.h>
//向标准输出文件 输出格式字符串
int printf(const char *format, ...);
//向指定的文件(stdout,stderr通过流指针指定) 输出格式字符串
int fprintf(FILE *stream, const char *format, ...);
//向指定的内存区域 输出格式字符串
int sprintf(char *str, const char *format, ...);
例:将数字123转化为字符串"123"
sprintf(buf,"%d",123);
例:向标准输出 输出"hello world"
printf("hello world");
fprintf(stdout,"hello world");
向标准出错输出 "stderr : hello world"
fprintf(stderr,"stderr : hello world");
向数组buf中输出 "string:hello world"
sprintf(buf,"string:hello world");
练习-1:
输出上述内容,并要求只显示标准输出的内容,和只显示标准出错的内容
#include <stdio.h>
int main(int argc, const char *argv[])
{
//printf("hello world\n");
printf("hello world");
// fflush(stdout);
// while(1);
return 0;
}
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("hello world");
//sleep 1 seconds
sleep(1);
//标准输入获得数据导致其他所有行缓冲刷新
getchar();
while(1);
return 0;
}
#include <stdio.h>
int main(int argc, const char *argv[])
{
//printf("hello world\n");
write(1,"hello world\n",12);
return 0;
}
三、标准IO缓冲机制
三种类型的缓冲:全缓冲、行缓冲、不缓冲/*{{{*/
全缓冲:特点缓存满时刷新,进行实际的IO写操作。stdio库对一般文件均使用全缓冲,
linux下ext4格式文件系统全缓冲默认大小为4k
全缓冲刷新条件:
1.缓冲区满时会刷新;
2.程序正常结束时会刷新(即调用exit()函数)
3.程序中调用fflush()函数会强制刷新
行缓冲:特点输入输出时遇'\n'会刷新。 stdio库对与终端相关联的文件使用行缓冲,
典型的有标准输入文件和标准输出文件。linux下行缓冲大小为1k
行缓冲刷新条件
1.缓冲区满时会刷新;
2.程序正常结束时会刷新(即调用exit()函数)
3.程序中调用fflush()函数会强制刷新;
4.遇'\n'会刷新
/*{{{*/
5.当从一个不缓存或行缓存文件获得数据,会造成所有行缓存刷新
/*}}}*/
不缓冲:特点不使用缓冲
出错不会缓冲,典型的标准出错文件不缓冲/*}}}*/
四、标准I/O相关函数
1.打开文件
#include <stdio.h>/*{{{*/
FILE *fopen(const char *path, const char *mode);
功能:打开文件,获得流指针
参数:
@path:打开文件的路径,"/home/will/text.txt" ; 字符指针argv[1]
@mode:打开文件的方式 "r" "r+" "w" "w+" "a" "a+"
"r" :只读方式打开,要求文件存在
"r+" :读写方式打开,要求文件存在
"w" :以只写方式打开,如果文件不存在则创建文件;如果文件存在,则将文件内容删除,长度清0
"w+" :以读写方式打开,如果文件不存在则创建文件;如果文件存在,则将文件内容删除,长度清0
"a" :以追加写方式打开,如果文件不存在则创建文件
"a+" :以读写方式打开,追加写方式,如果文件不存在则创建文件
返回值:成功返回打开文件对应流指针;失败返回NULL,并且置errno
例:打开主目录下的test.c,以只读方式打开,
FILE *fp = fopen("/home/will/test.c","r");
$./a.out ./test.c
FILE *fp = fopen(argv[1],"r");
练习-2:/*{{{*/
1.以读写方式打开一个文件newfile,文件不存在则创建文件,存在则将文件内容清空
2.向文件写入"fopen newfile success."
3.查看newfile文件权限 ls -l/*}}}*/
#include <stdio.h>
#include <errno.h>
#include <string.h>
//argc 命令行参数的个数
//argv 命令参数的对应的字符串指针数组
//$./a.out a1 a2
int main(int argc, const char *argv[])
{
int i = 0;
FILE * fp = NULL;
if( argc != 2)
{
printf("Usage: %s file\n",argv[0]);
return -1;
}
#if 0
for(;i < argc;i++)
printf("argv[%d] = %s\n",i,argv[i]);
#endif
//./a.out file
//(fp= fopen(argv[1],"r")==NULL)
if((fp = fopen(argv[1],"w")) == NULL)
{
//printf("fopen fail:%d\n",errno);
//printf("fopen fail:%s\n",strerror(errno));
//向指定的流输出信息
//fprintf(fp,"foprn fail:%s\n",strerror(errno));
//fprintf(stdout,"fopen fail:%s\n",strerror(errno));
perror("fopen");
return -1;
}
return 0;
}
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *fp;
fp = fopen("newfile.txt","w");
return 0;
}
2.关闭打开的文件
#include <stdio.h>/*{{{*/
int fclose(FILE *fp);
功能:关闭指定的流
fclose()调用成功返回0,失败返回EOF,并设置errno
注意:
不要对已关闭的流在进行文件操作,不要对流进行重复关闭。
因为:
关闭流涉及到流对应的堆区FILE结构体空间和缓冲区空间释放,
所以不能对关闭的流进行任何操作,读写文件,包括关闭(不能重
复关闭流)/*}}}*/
3.读写文件
1)按字符读写
int fgetc(FILE *stream);/*{{{*/
功能:从指定流读入一个字符
参数:
@stream:指定读的流
返回值 :
成功 返回读到字符ascii码;
失败 返回EOF,读到文件末尾,出错
注意,不管是出错还是到达文件尾端,函数都返回同样的值。
为了区分这两种不同的情况,可以调用ferror()或feof()来具体判断
int fputc(int c, FILE *stream);
功能:向指定流写入指定字符
参数:
@c: 写入的字符
@stream: 指定的流
返回值:成功返回写入字符ascii;失败返回EOF
练习:按字符读写,完成文件拷贝,
注意源文件打开方式"r" ,目标文件打开方式"w"
#include <stdio.h>
void do_copy(FILE *fp_s,FILE *fp_d)
{
int c;
while((c = fgetc(fp_s)) != EOF)
fputc(c,fp_d);
return;
}
// ./a.out src dest
int main(int argc, const char *argv[])
{
FILE *fp_s = NULL;
FILE *fp_d = NULL;
if( argc != 3)
{
fprintf(stdout,"Usage:%s src dest\n",argv[0]);
return -1;
}
// open src file
if((fp_s = fopen(argv[1],"r") ) == NULL)
{
perror("fopen");
return -1;
}
// open dest file
if((fp_d = fopen(argv[2],"w") ) == NULL)
{
perror("fopen");
return -1;
}
//loop read write
do_copy(fp_s,fp_d);
//close
fclose(fp_s);
fclose(fp_d);
return 0;
}
分别拷贝文本文件和二进制文件
./copy src dest
diff src dest/*}}}*/
#include <stdio.h>
#include <errno.h>
#include <string.h>
int do_copy(FILE *fp_src,FILE *fp_dest)
{
char buf[4096];
//bug:拷贝是二进制文件
while(fgets(buf,sizeof(buf),fp_src) != NULL)
{
//只会将'\0'之前的字符写入文件
fputs(buf,fp_dest);
}
return 0;
}
//./a.out src_file dest_file
int main(int argc, const char *argv[])
{
FILE *fp_src;
FILE *fp_dest;
if(argc < 3)
{
fprintf(stderr,"Usage : %s src_file dest_file\n",argv[0]);
return -1;
}
fp_src = fopen(argv[1],"r");
if(fp_src == NULL){
fprintf(stderr,"Fail to fopen %s:%s\n",argv[1],strerror(errno));
return -1;
}
fp_dest = fopen(argv[2],"w");
if(fp_dest == NULL){
fprintf(stderr,"Fail to fopen %s:%s\n",argv[2],strerror(errno));
return -1;
}
do_copy(fp_src,fp_dest);
fclose(fp_src);
fclose(fp_dest);
return 0;
}
2)按行读写
char *fgets(char *s, int size, FILE *stream);/*{{{*/
功能:读入一行字符,最多读取size - 1,遇'\n'结束
参数:
@s :读入数据存储区域首地址
@size :读入字符个数限制
@stream :指定流
返回值:成功返回s;失败返回 NULL/*}}}*/
注意:<1>.读入'\n'也会存储,最终会追加'\0'/*{{{*/
<2>.最多读取size - 1个字符
<3>.读取结束
a.遇到换行符'\n'
b.读满size-1个字符
c.读到文件末尾/*}}}*/
int fputs(const char *s, FILE *stream);/*{{{*/
功能:向指定文件输出字符串
注意:
若遇到'\0'则写结束,fputs不会将'\0'写到文件中。
练习:用fgets和fputs拷贝文件 /*}}}*/
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buf[100] = {0};
while(fgets(buf,sizeof(buf),stdin) != NULL)
puts(buf);
return 0;
}
#include <stdio.h>
int main(int argc, const char *argv[])
{
int c = 0;
while((c = fgetc(stdin)) != EOF)
fputc(c,stdout);
return 0;
}
3)按对象进行读写fread()/fwrite()
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);/*{{{*/
实际读取对象的个数 fread(读取数据存储位置首地址ptr,一个对象大小size,要求读取对象个数nmemb,指定读取数据的流fp);
实际要求读取字节数 一个对象大小 * 要求读取对象个数 size * nmemb
注意:如果读到文件结尾,没有对象可读,返回0,读到结尾,对象个数不够,读取实际个数
例如:每次按照int型的对象读文件
int buf[10];
n = fread(buf,sizeof(int),sizeof(buf)/sizeof(int),fp);
如果文件大小只有20 bytes,此时n = 5
如: 文件大小为1025byte
char buf[1024];
n = fread(buf,sizeof(char),sizeof(buf),fp);
第一次: n = 1024;
第二次: n = 1;
第三次: n = 0;
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
实际写入对象的个数 fwrite(写入数据存储位置首地址ptr,一个对象大小size,要求写入对象个数nmemb,指定写入数据的流fp);
实际写入字节数 一个对象大小 * 实际写入对象个数 size * nmemb
注意:
循环读时返回值的判断问题,如果一次读取多个对象,则需要循环到读到0个对象为止。
否则会少最后一部分!
对象: 数据类型(基本类型,构造的类型)
对象的大小 : 数据类型的大小
注意:fread和fwrite也可以用来读写一般文件,但如果读写数据中没有固定结构,
要正确进行读写只能按照1个字节作为结构(以一个字节大小为一个对象大小)!否则读写数据会不完整!
注意读多少数据,写多少数据。
例:
struct Student
{
char name[20];
char sex;
float score[3];
};
struct Student s[2];
struct Student stu[3] = {{"Lee",'m',{90.5,89.5,79.5}},
{"zhang",'f',{78.0,89.0,100.0}},
{"zhao",'f',{100,50,79.5}}};
n = fwrite(stu,sizeof(struct Student),3,fp);/*{{{*/
n = fread(s,sizeof(struct Student),2,fp);
练习:利用fread()/fwrite()进行文件拷贝,
注意:fread()读到多少内容,fwrite()就写入多少内容!
读写普通文件(其中无固定大小的对象),应该按字节读写
提示:
char buf[100];
n = fread(buf,sizeof(char),sizeof(buf),rfp);
fwrite(buf,sizeof(char),n,wfp);/*}}}*/
4.标准IO中文件定位
定位本质上指修改内核中文件表项中的offset值,同一文件,读写共用一个offset/*{{{*//*{{{*//*}}}*/
读写完成后,offset会增加相应的读写字节数
定位:修改文件表项中的offset
int fseek(FILE *stream, long offset, int whence);
功能:定位指定的流,可实现文件中读写起点修改
参数:
@stream 指定流
@offset 相对偏移量,如果offset < 0 向前偏移,offset > 0向后偏移
@whence 相对偏移起点
SEEK_SET 以文件开头作为相对偏移起点 offset >= 0
SEEK_CUR 以当前位置作为相对偏移起点 offset 可正可负
SEEK_END 以文件结尾作为相对偏移起点 offset 可正可负
返回值:成功返回0,失败返回-1,并设置errno
获取当前offset值
long ftell(FILE *stream);
功能:获取当前文件位置偏移量
返回值:成功返回当前offset,失败返回-1L,并置errno
将文件位置指示器值置0,即定位到文件开头
void rewind(FILE *stream);
例:
1.定位到文件开头
A.fseek(fp,0,SEEK_SET);
B.rewind(fp);
2.定位到当前位置
fseek(fp,0,SEEK_CUR);
3.定位到结尾位置,向前10字节
fseek(fp,-10,SEEK_END);
4.从开头写了n个字节数据,想将n个字节数据读出来
n = fwrite(buf,1,100,fp);
//需要将offset置为写之前的值!
fseek(fp,-n,SEEK_CUR);
/*fseek(fp,0,SEEK_SET);*/
fread(buf,1,n,fp);
练习:
计算一个指定文件大小
fseek定位到文件结尾,ftell求位置/*}}}*//*}}}*/
#include <head.h>
struct student
{
char name[15];
int id;
int score;
};
//./a.out filename
int main(int argc, const char *argv[])
{
int n;
int fd;
struct student stu2;
struct student stu1 = {"大明",2,50};
if(argc < 2)
{
fprintf(stderr,"Usage : %s filename\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR | O_CREAT | O_TRUNC,0666);
if(fd < 0){
handler_error(argv[1]);
}
write(fd,&stu1,sizeof(stu1));
lseek(fd,0,SEEK_SET);
n = read(fd,&stu2,sizeof(stu2));
printf("n = %d\n",n);
printf("name:%s,id:%d,score:%d\n",stu2.name,stu2.id,stu2.id);
close(fd);
return 0;
}