一、标准IO介绍及缓冲区
Linux系统中一切皆文件
1)IO的概念及介绍:IO就是输入输出,标准I/O由ANSI C标准定义,主流操作系统上都实现了C库,标准I/O通过缓冲机制减少系统调用,实现更高的效率
I input 输入设备 比如键盘鼠标都是Input设备
O output 输出设备 比如显示器
优盘,网口,既是输入也是输出
2)标准IO - 流:
FILE:标准IO用一个结构体类型来存放打开的文件的相关信息,标准I/O的所有操作都是围绕FILE来进行
流(stream):FILE又被称为流(stream) ,文本流/二进制流
3)流的缓冲类型:
流的概念:就是数据的流,在程序中就是一个结构体
缓冲区的概念:为了减少操作IO设备的次数,提高运行效率,在内存里面设置的缓冲区,全缓冲:缓冲区满才输出,行缓冲:遇到换行符输出
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作
行缓冲
当在输入和输出中遇到换行符(‘\n’)时,进行I/O操作 当流和一个终端关联时,典型的行缓冲
无缓冲
数据直接写入文件,流不进行缓冲
4)标准I/O –stdin,stdout,stderr:
标准IO预定义3个流程序运行时自动打开
标准输入流 | 0 | STDIN_FILENO | stdin |
标准输出流 | 1 | STDOUT_FILENO | stdout |
标准错误流 | 2 | STDERR_FILENO | stderr |
stdin/stdout默认行缓冲,stderr没有缓冲
二、文件的打开和关闭
概念:打开就是占用资源,关闭就是释放资源
文件的打开
1)文件的打开函数
FILE *fopen (const char *path, const char *mode);
Path: 普通文件当前路径不需要加目录,其他要使用完整的路径
Mode:打开的模式
返回值:出错返回NULL,所以使用fopen函数必须判断是否为空
2)打开模式
“r” 或 “rb” | 以只读的方式打开文件,文件必须存在。 |
“r+” 或 “r+b” | 以读写的方式打开文件,文件必须存在。 |
“w” 或 “wb” | 以只写的方式打开文件,文件若存在则文件长度清为 0 ,若文件不存在则创建文件 |
“w+” 或 “w+b” | 以读写的方式打开文件,其他于“w”相同 |
“a” 或 “ab” | 以只写的方式打开文件,若文件不存在则创建文件,向文件写入的数据追加到文件末尾 |
“a+” 或 “a+b” | 以读写的方式打开文件,其他于“a”相同 |
1)编译错误:
f_open.c:9:38: error: ‘errno’ undeclared (first use in this function)
printf("fopen:%s\n",strerror(errno));
error: ‘errno’ undeclared 表示errno变量没有定义
解决方法:如果是系统变量用include 头文件,如果是你自己的,自己手动定义。
f_open.c:10:29: warning: implicit declaration of function ‘strerror’ [-Wimplicit-function-declaration]
printf("fopen:%s\n",strerror(errno));
warning: implicit declaration of function ‘strerror’ 表示strerror函数隐示的声明
解决方法:include 添加对应的头文件。
2) perror 库函数 头文件stdio.h
strerror 库函数 头文件 errno.h string.h
perror和strerror 功能:打印系统的错误描述(注意:是系统错误,不是你自己代码错误)
文件的关闭
函数原型:int fclose(FILE *stream)
- fclose()调用成功返回0,失败返回EOF(-1),并设置errno
- 流关闭时自动刷新缓冲中的数据并释放缓冲区,比如:常规文件把缓冲区内容写入磁盘
- 当一个程序正常终止时,所有打开的流都会被关闭
- fclose()函数的入参stream必须保证为非空,否则出现断错误。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE * fp;
int fpret;
fp = fopen("2.txt", "r+");//fopen打开文件函数
if (fp == NULL){
// printf("file open failed\n");
perror("fopen");
printf("fopen:%s\n",strerror(errno));//报错信息打印
} else {
printf("file open success\n");
// perror("fopen"); 通常使用在报错语句上
fpret = fclose(fp);//fclose关闭文件函数
if (fpret == 0) {
printf("file close success\n");//返回0表示成功打印
} else {
perror("fclose");//关闭文件
}
}
return 0;
}
三、标准IO的字符的输入输出
字符的输入(单个字符)
int fgetc(FILE *stream);
int getc(FILE *stream); //宏
int getchar(void);
成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1),
getchar()等同于fgetc(stdin)
getc和fgetc区别是一个是宏一个是函数
注意事项:
1.函数返回值是int类型不是char类型,主要是为了扩展返回值的范围。
2 .tdin 也是FILE *的指针,是系统定义好的,指向的是标准输入(键盘输入)
3 .打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
4 .调用getchar会阻塞,等待你的键盘输入
字符的输出(单个字符)
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
成功时返回写入的字符;出错时返回EOF
putchar(c)等同于fputc(c, stdout)
注意事项:
1.返回和输入参数都是int类型
2.遇到这种错误:Bad file descriptor, 很可能是文件打开的模式错误(只读模式去写,只写模式去读)
行输入 (读取整个行)
char *gets(char *s); 读取标准输入到缓冲区s
char *fgets(char *s, int size, FILE *stream);
成功时返回s,到文件末尾或出错时返回NULL
遇到’\n’或已输入size-1个字符时返回,总是包含’\0’
注意事项:
1. gets函数已经被淘汰,因为会导致缓冲区溢出
2.fgets 函数第二个参数,输入的数据超出size,size-1个字符会保存到缓冲区,最后添加’\0’,如果输入数据少于size-1 后面会添加换行符。
行输出(写整行)
int puts(const char *s);
int fputs(const char *s, FILE *stream);
成功时返回非负整数;出错时返回EOF
puts将缓冲区s中的字符串输出到stdout,并追加’\n’
fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’
四、二进制读写
文本文件和二进制的区别:存储的格式不同:文本文件只能存储文本。
计算机内码概念:文本符号在计算机内部的编码(计算机内部只能存储数字0101001....,所以所有符号都要编码)
二进制读写函数格式:
size_t fread(void *ptr, size_t size, size_t n, FILE *fp);
void *ptr :读取内容放的位置指针
size_t size :读取的块大小
size_t n :读取的个数
FILE *fp :读取的文件指针
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
void *ptr :写文件的内容的位置指针
size_t size :写的块大小
size_t n :写的个数
FILE *fp :要写的文件指针
注意事项:
文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。
五、流的刷新及定位
流的刷新
int fflush(FILE *fp);
成功时返回0;出错时返回EOF
将流缓冲区中的数据写入实际的文件
Linux下只能刷新输出缓冲区,输入缓冲区丢弃
如果输出到屏幕使用fflush(stdout)
流的定位
long ftell(FILE *stream);
long fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);
fseek 参数(whence位置参数):SEEK_SET / SEEK_CUR / SEEK_END
SEEK_SET :从距文件开头 offset 位移量为新的读写位置
SEEK_CUR :以目前的读写位置往后增加 offset 个位移量
SEEK_END :将读写位置指向文件尾后再增加 offset 个位移量
offset参数 :偏移量,可正可负
注意事项:
1.文件的打开使用a模式 fseek无效
2.rewind(fp) 相当于 fseek(fp,0,SEEK_SET);
3.这三个函数只适用2G以下的文件
六、格式化输入输出
格式化输出
int fprintf(FILE *stream, const char *fmt, …);
int sprintf(char *s, const char *fmt, …);
以指定格式 “年-月-日” 分别写入文件和缓冲区
int year, month, date;
FILE *fp;
char buf[64];
year = 2014; month = 10; date = 26;
fp = fopen(“test.txt”, “a+”);
fprintf(fp, “%d-%d-%d\n”, year, month, date);
sprintf(buf, “%d-%d-%d\n”, year, month, date);
成功时返回输出的字符个数;出错时返回EOF
格式化输入
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE * fp;
int year ;
int month;
int day;
fp = fopen("ftset.txt", "r");
if(fp == NULL){
perror("fopen");
return 0;
}
fscanf(fp, "%d-%d-%d\n", &year, &month, &day);
printf("%d-%d-%d\n", year, month, day);
fclose(fp);
return 0;
}
重点掌握sprintf 和sscanf
七、标准IO练习
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是正确的年份
获取文件内的所有行数量:
while(fgets(buf,32,fp)!=NULL){
if(buf[strlen(buf)-1] =='\n'){ //注意判断是否是一行结束
linecount++;
}
}
写完文件记得fflush ,写到磁盘里面去。
标准IO磁盘文件的缓冲区一般为4096
注意和标准输出的全缓冲区别,标准输出是1024
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE * fp;
time_t ctime;
struct tm *ctimestr;
int linecount = 1;
char buf[32];
fp = fopen("1.txt", "a+");
if (fp == NULL){
perror("fopen");
return 0;
}
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("%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);
fprintf(fp,"%d. %04d-%02d-%02d %2d:%02d:%02d\n",linecount, ctimestr->tm_year+1900, ctimestr->tm_mon+1, ctimestr->tm_mday,
ctimestr->tm_hour, ctimestr->tm_min, ctimestr->tm_sec);
linecount++;
fflush(fp);
sleep(1);
}
fclose(fp);
return 0;
}
八、文件IO
文件IO的概念:
什么是文件IO,又称系统IO,系统调用
是操作系统提供的API接口函数。
POSIX接口 (了解)
注意:文件IO不提供缓冲机制
文件IO的API
open close read read
文件描述符概念:
英文:缩写fd(file descriptor)
是0-1023的数字,表示文件。
0, 1, 2 的含义 标准输入,标准输出,标准错误
文件IO 打开
open
int open(const char *pathname, int flags); 不创建文件
int open(const char *pathname, int flags, mode_t mode); 创建文件,不能创建设备文件
成功时返回文件描述符;出错时返回EOF
文件IO和标准的模式对应关系:
r | O_RDONLY |
r+ | O_RDWR |
w | O_WRONLY | O_CREAT | O_TRUNC, 0664 |
w+ | O_RDWR | O_CREAT | O_TRUNC, 0664 |
a | O_WRONLY | O_CREAT | O_APPEND, 0664 |
a+ | O_RDWR | O_CREAT | O_APPEND, 0664 |
umask概念:
umask 用来设定文件或目录的初始权限
文件的关闭
int close(int fd)
关闭后文件描述符不能代表文件
文件IO的读写和定位
容易出错点:
求字符串长度使用sizeof,对二进制数据使用strlen
printf 的字符最后没有’\0’
文件的读
read函数用来从文件中读取数据:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
成功时返回实际读取的字节数;出错时返回EOF
读到文件末尾时返回0
buf是接收数据的缓冲区 count不应超过buf大小
//从指定的文件中读取内容并统计大小
int main(int argc, char *argv[]) {
{
int fd, n, total = 0;
char buf[64];
if (argc < 2) {
printf(“Usage : %s <file>\n”, argv[0]); return -1;
}
if ((fd = open(argv[1], O_RDONLY)) < 0) {
perror(“open”); return -1;
}
while ((n = read(fd, buf, 64)) > 0) {
total += n;
}
……
文件的写
write函数用来向文件写入数据:
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
成功时返回实际写入的字节数;出错时返回EOF
buf是发送数据的缓冲区 count不应超过buf大小
int fd;
char buf[20];
if ((fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
perror(“open”); return -1;
}
while (fgets(buf, 20, stdin) != NULL) {
if (strcmp(buf, “quit\n”) == 0) break;
write(fd, buf, strlen(buf));
}
……
文件的定位
lseek函数用来定位文件:
#include <unistd.h>
off_t lseek(int fd, off_t offset, intt whence);
成功时返回当前的文件读写位置;出错时返回EOF
参数offset和参数whence同fseek完全一样