文件的概念和类型
Linux系统一切皆文件
概念
一组相关数据的有序集合
文件类型
- 常规文件 r
- 目录文件 d
- 字符设备文件 c
- 块设备文件 b
- 管道文件 p
- 套接字文件 s
- 符号链接文件 l
如何理解标准IO
标准I/O由ANSI C标准定义
主流操作系统上都实现了C库
标准I/O通过缓冲机制减少系统调用,实现更高的效率
IO的概念
- I input 输入设备 比如键盘鼠标都是Input设备
- O output 输出设备 比如显示器
- 优盘,网口,既是输入也是输出
系统调用和库函数
- 系统调用就是操作系统提供的接口函数
- 如果我们把系统调用封装成库函数就可以起到隔离的作用,提供程序的可移植性
- printf就是库函数然后调用了系统调用才在显示器上显示字符
流(FILE)的含义
就是数据的流,在程序中就是一个结构体
FILE
标准IO用一个结构体类型来存放打开的文件的相关信息
标准I/O的所有操作都是围绕FILE来进行
流(stream)
FILE又被称为流(stream)
文本流/二进制流
标准I/O预定义3个流
标准I/O预定义3个流,程序运行时自动打开
标准输入流(键盘) | 0 | STDIN_FILENO | stdin |
---|---|---|---|
标准输出流(显示器) | 1 | STDOUT_FILENO | stdout |
标准错误流 | 2 | STDERR_FILENO | stderr |
流的缓冲类型(重点)
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作
全缓冲的空间为 1 k 1k 1k
C程序结束或者缓冲区满,缓冲区的内容才会被打印,如下:
#include <stdio.h>
#include <unistd.h>
int main(int argc,char*argv[]){
int i=0;
for(i=0;i<1025;i++){ //i大于1024才会被打印
printf("a");
}
// printf("hello world\n");
while(1){
sleep(1);
}
}
行缓冲
当在输入和输出中遇到换行符(‘\n’)时,进行I/O操作
当流和一个终端关联时,典型的行缓冲
Windows 和linux的换行符区别
- Windows是\r\n
- Linux 是\n
无缓冲
数据直接写入文件,流不进行缓冲
文件的打开和关闭
打开就是占用资源
关闭就是释放资源
标准I/O – 打开文件
下列函数可用于打开一个标准I/O流:
FILE *fopen (const char *path, const char *mode);
- 成功时返回流指针;出错时返回NULL; FILE是文件类型
mode参数:
“r” 或 “rb” | 以只读方式打开文件,文件必须存在。 |
---|---|
“r+” 或 ”r+b” | 以读写方式打开文件,文件必须存在。 |
“w” 或 “wb” | 以只写方式打开文件,若文件存在则文件长度清为0。若文件不存在则创建。 |
“w+” 或 “w+b” | 以读写方式打开文件,其他同”w”。 |
“a” 或 “ab” | 以只写方式打开文件,若文件不存在则创建;向文件写入的数 据被追加到文件末尾。 |
“a+” 或 “a+b” | 以读写方式打开文件。其他同”a” |
fopen-示例
#include <stdio.h>
int main(int argc,char *argv[]){
FILE *fp;
fp = fopen("1.txt","r");
if(fp == NULL){
printf("Open filr failed\n");
//perror("(perror)fopen");
//printf("(strerror)fopen:%s\n",strerror(errno));
}
else{
printf("Open filr success\n");
}
return 0;
}
处理错误信息
errno 存放错误号,由系统生成
perror先输出字符串s,再输出错误号对应的错误信息
strerror根据错误号返回对应的错误信息
extern int errno;
void perror(const char *s);
char *strerror(int errno);
错误信息处理-示例
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[]){
FILE *fp;
fp = fopen("1.txt","r");
if(fp == NULL){
perror("(perror)fopen");
printf("(strerror)fopen:%s\n",strerror(errno));
}
else{
printf("Open filr success\n");
}
return 0;
}
标准I/O – 关闭文件
int fclose(FILE *stream);
- fclose()调用成功返回0,失败返回EOF,并设置errno
- 流关闭时自动刷新缓冲中的数据并释放缓冲区
- 当一个程序正常终止时,所有打开的流都会被关闭
- 流一旦关闭后就不能执行任何操作
标准I/O的读写
流支持不同的读写方式:
- 读写一个字符:fgetc()/fputc()一次读/写一个字符
- 读写一行:fgets()和fputs()一次读/写一行
- 读写若干个对象:fread()/fwrite() 每次读/写若干个对象,而每个对象具有相同的长度
打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
按字符输入
下列函数用来输入一个字符:
函数返回值设置成int型既能处理有符号数又能处理无符号数
函数返回值是int类型不是char类型,主要是为了扩展返回值的范围。
#include <stdio.h>
int fgetc(FILE \*stream);
int getc(FILE \*stream); //宏
int getchar(void);
- 成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1),
- getchar()等同于fgetc(stdin)
- getc()和fgetc()区别是一个是宏一个是函数
- 调用getchar会阻塞,等待你的键盘输入
示例:
FILE *fp;
int ch, count = 0;
if ((fp = fopen(argv[1], “r”)) == NULL) {
perror(“fopen”); return -1;
}
while ((ch = fgetc(fp)) != EOF) {
count++;
}
printf(“total %d bytes\n”, count);
按字符输出
下列函数用来输出一个字符:
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
- 成功时返回写入的字符;出错时返回EOF
- putchar©等同于fputc(c, stdout)
示例:
FILE *fp;
int ch;
if ((fp = fopen(argv[1], “w”)) == NULL) {
perror(“fopen”); return -1;
}
for(ch = ‘a’; ch <=‘z’; ch++) {
fputc(ch, fp);
}
按行输入
下列函数用来输入一行:
#include <stdio.h>
char *gets(char *s);
char *fgets(char *s, int size, FILE *stream);
- 成功时返回s,到文件末尾或出错时返回NULL
- gets不推荐使用,没有设置长度容易造成缓冲区溢出
- 遇到’\n’或已输入size-1个字符时返回,总是包含’\0’
- fgets 函数第二个参数,输入的数据超出size,size-1个字符会保存到缓冲区,最后添加’\0’,如果输入数据少于size-1 后面会添加换行符。
示例:
#define N 6
char buf[N];
fgets(buf, N, stdin);
printf(“%s”, buf);
假设键盘输入分别是:
- abcd<回车> buf中的内容是abcd
- abcdef<回车> buf中的内容是abcde
按行输出
下列函数用来输出字符串:
#include <stdio.h>
int puts(const char *s);
int fputs(const char *s, FILE *stream);
- 成功时返回非负整数;出错时返回EOF
- puts将缓冲区s中的字符串输出到stdout, 并追加’\n’
- fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’
示例:
puts(“hello world”);
FILE *fp;
char buf[] = “hello world”;
if ((fp = fopen(argv[1], “a”)) == NULL) {
perror(“fopen”);
return -1;
}
fputs(buf, fp);
注意:输出的字符串中可以包含’\n’,也可以不包含
按对象读写
下列函数用来从流中读写若干个对象:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t n, FILE *fp); (size_t = unsigned int)
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
void *ptr 读写内容放的位置指针
size_t size 读写的块大小
size_t n 读写的个数
FILE *fp 读写的文件指针
- 成功返回读写的对象个数;出错时返回EOF
- 既可以读写文本文件,也可以读写数据文件
- 效率高
注意事项:
文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。
解决办法:
- 移动指针(后面讲解)到文件头
- 关闭文件,重新打开
流的刷新和定位
刷新流
#include <stdio.h>
int fflush(FILE *fp);
- 成功时返回0;出错时返回EOF
- 将流缓冲区中的数据写入实际的文件
- Linux下只能刷新输出缓冲区,输入缓冲区丢弃
定位流
(long 32位返回2个G,这三个函数只适用2G以下的文件)
long ftell(FILE *stream);
- ftell() 成功时返回流的当前读写位置,出错时返回EOF
long fseek(FILE *stream, long offset, int whence);
- fseek()定位一个流,成功时返回0,出错时返回EOF
- whence参数:SEEK_SET/SEEK_CUR/SEEK_END
SEEK_SET:从距文件开头 offset 位移量为新的读写位置
SEEK_CUR:以目前的读写位置往后增加 offset 个位移量
SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量
- offset参数:偏移量,可正可负
- 打开a模式 fseek无效
void rewind(FILE *stream);
- rewind()将流定位到文件开始位置
- rewind(fp) 相当于 fseek(fp,0,SEEK_SET);
示例一(在文件末尾追加字符’t’)
FILE *fp = fopen(“test.txt”, “r+”);
fseek(fp, 0, SEEK_END);
fputc(‘t’, fp);
示例二(获取文件长度)
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
perror(“fopen”);
return -1;
}
fseek(fp, 0, SEEK_END);
printf(“length is %d\n”, ftell(fp));
判断流是否出错和结束
#include <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);
ferror()返回1表示流出错;否则返回0
feof()返回1表示文件已到末尾;否则返回0
格式化输入和输出
sprintf和sscanf常用,重点掌握sprintf 和sscanf
格式化输出
int printf(const char *fmt, …);
int fprintf(FILE *stream, const char *fmt, …);
int sprintf(char *s, const char *fmt, …);
成功时返回输出的字符个数;出错时返回EOF
格式化输入
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
练习题
题目
每隔1秒向文件test.txt中写入当前系统时间,格式如下:
1, 2014-10-15 15:16:42
2, 2014-10-15 15:16:43
该程序无限循环,直到按Ctrl-C中断程序
每次执行程序时,系统时间追加到文件末尾,序号递增
1, 2014-10-15 15:16:42
2, 2014-10-15 15:16:43
3, 2014-10-16 11:35:07
4, 2014-10-16 11:35:08
思路
time()用来获取系统时间(秒数)
time_t time(time_t *seconds) 返回值是从1970.1.1 0:0:0开始的秒数
localtime()将系统时间转换成本地时间
struct tm *localtime(const time_t *timer)
struct tm {
int tm_sec; /* 秒,范围从 0 到 59 */
int tm_min; /* 分,范围从 0 到 59 */
int tm_hour; /* 小时,范围从 0 到 23 */
int tm_mday; /* 一月中的第几天,范围从 1 到 31 */
int tm_mon; /* 月份,范围从 0 到 11 */
int tm_year; /* 自 1900 起的年数 */
int tm_wday; /* 一周中的第几天,范围从 0 到 6 */
int tm_yday; /* 一年中的第几天,范围从 0 到 365 */
int tm_isdst; /* 夏令时 */
};
注意:
int tm_mon; 获取的值要加1是正确的月份
int tm_year; 获取的值加1900是正确的年份
代码
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[]){
FILE *fp;
time_t ctime;
struct tm *ctimestr;
int linecount = 0;
char buf[32];
fp=fopen("test.txt","a+");
if(fp==NULL){
perror("fopen");
return 0;
}
//calculate test.txt line
while(fgets(buf,32,fp)!=NULL){
if(buf[strlen(buf)-1] =='\n'){
linecount++;
}
}
while(1){
ctime = time(NULL);
//printf("ctime=%d\n",(int)ctime);
ctimestr = localtime(&ctime);
printf("%04d-%02d-%02d %02d:%02d:%02d\n",ctimestr->tm_year+1900,ctimestr->tm_mon+1,ctimestr->tm_mday,
ctimestr->tm_hour,ctimestr->tm_min,ctimestr->tm_sec);
fprintf(fp,"%d, %04d-%02d-%02d %02d:%02d:%02d\n",linecount,ctimestr->tm_year+1900,ctimestr->tm_mon+1,ctimestr->tm_mday,
ctimestr->tm_hour,ctimestr->tm_min,ctimestr->tm_sec);
fflush(fp);
linecount++;
sleep(1);
}
fclose(fp);
}
注意(写完文件记得fflush,\n也不会读出缓冲区)
- 写完文件记得fflush ,写到磁盘里面去。
- 标准IO磁盘文件的缓冲区一般为4096
- 注意和标准输出的全缓冲区别,标准输出是1024