Linux 标准IO
标准IO介绍
所谓的IO就是指:input和output,即输入和输出。
如通过键盘鼠标输入数据,通过显示器、控制台、终端打印输出信息。
在操作系统中IO是基于文件来实现的。
文件是一组相关数据的有序集合
标准IO与缓冲机制
操作系统基于不同类型的文件进行IO操作。同时操作系统也提供了一系列丰富的函数,可供C\C++程序调用,这些丰富的函数集合,称之为:标准IO(由ANSI C标准定义)。
标准IO
标准IO库,基于缓冲机制,减少系统调用提高IO的性能。
无缓冲调用示例
有缓冲调用示例
即缓冲机制会将标准IO的输入输出先写进缓冲区中,达到一定条件时再进行系统调用。
文件流(FILE)的含义
FILE和流
标准IO的操作,都是基于一个结构体类型进行的,即FILE结构体对象。
FILE定义在stdio.h中,是一个结构体类型。
下面是他的定义:
我们打开一个文件时就会返回一个流
1 |
|
在操作系统中,我们一般将FILE称之为流(stream), 无论什么文件类型,IO操作所需的数据就像流水一样输入或输出。
流中的换行符和缓冲模式
在Windows系统中:
- 二进制流(FILE结构体指针),数据流中换行符基于\n
- 文本流( FILE结构体指针),数据流中换行符基于\r\n
在Linux系统中,不区分二进制流和文本流,换行符统一是\n
标准IO通过缓冲提高性能,应用在流(FILE结构体指针)中主要有3种缓冲模式:
- 全缓冲,缓冲区无书或无空间才执行实际的IO操作(即系统调用)
- 行缓冲,在输入和输出中遇到换行符,进行IO操作
- 无缓冲,直接系统调用进行IO操作,流不进行缓冲
预定义流
标准IO预定义了3个流,任何程序在运行的时候都会自动打开:
| 名称 | 文件描述符 | 文件描述符 | 流对象名称(FILE*) | 描述 |
|---|---|---|---|---|
| 标准输入流 | 0 | STDIN_FILENO | stdin | 向程序内输入数据,默认从键盘读取,也可以切换为其它文件,默认行缓冲模式(等待用户的回车),如果输入为文件等非交互设备,可能为全缓冲。 |
| 标准输出流 | 1 | STDOUT_FILENO | stdout | 程序对外输出数据,默认输出到屏幕上(控制台),也可以切换为其它文件,Linux下默认行缓冲模式,Windows经VS IDE测试默认无缓冲。 |
| 标准错误流 | 2 | STDERR_FILENO | stderr | 用于输出程序错误信息,默认输出到屏幕,可以切换为其它文件,默认无缓冲模式。 |
| stdout演示行缓冲 |
1 |
|
程序会先输出
1,3秒后2,3一起输出。
标准IO流的打开与关闭
流的打开和关闭
标准IO中提供了fopen函数,用于打开标准IO流,原型如下:
1 | FILE* fopen( |
打开成功返回流指针(FILE结构体指针);出错返回NULL。
打开模式
| 模式 | 含义 | 规则 |
|---|---|---|
| “r”或”rb” | 只读打开 | 文件必须存在 |
| “r+”或”r+b” | 可读可写打开 | 文件必须存在,从文件起始位置写入 |
| “w”或”wb” | 仅写打开 | 不存在则创建;存在会清空原有内容 |
| “w+”或”w+b” | 可读可写打开 | 不存在则创建;存在会清空原有内容 |
| “a”或”ab” | 仅写打开 | 不存在则创建;如存在且有内容,在文件末尾追加新内容 |
| “a+”或”a+b” | 可读可写打开 | 不存在则创建;如存在且有内容,在文件末尾追加新内容 |
| 当给定参数”b”时,表示以二进制方式打开(仅Windows有效),Linux忽略b参数。 |
流的关闭
标准IO中提供了fclose函数,用于关闭标准IO流,原型如下:
1 | int fclose( |
- 关闭成功返回整数0;关闭失败返回EOF(整数-1),并设置errno
- 关闭后自动刷新缓冲区数据并释放其空间
- 程序正常停止后,所有打开的流都会被关闭,流关闭后不可再操作
处理错误信息
在打开流的过程中,有可能出现错误,标准IO提供了3个可操作对象供处理错误信息。
extern int errno; 错误号 ->
#include <error.h>void perror(const char *s); 打印错误信息,输出用户提供字符串s和当前错误
#include <stdio.h>char * strerror(int errno); 根据错误号,返回错误信息字符串
#include <string.h>使用error错误号,需要包含 errno.h 头文件
使用perror函数,需要包含 stdio.h 头文件
使用strerror函数,需要包含 string.h 头文件
示例:
1 |
|
标准IO流的输入输出
按字符输入
标准IO支持按字符、按行、按对象的方式进行输入和输出,我们首先学习按字符输入的相关标准IO函数:
- int getchar(void);
- int fgetc(FILE * stream);
- int getc(FILE * stream);
此三个函数均来自头文件: stdio.h
getchar
getchar函数,支持从stdin(标准输入流,默认就是键盘)读取一个字符。
原型:int getchar(void); 读取成功返回字符,失败返回EOF(-1)
1 |
|
fgetc
fgetc函数,支持从流(各类文件)中读取一个字符。
原型:int fgetc(FILE * stream); 读取成功返回字符,失败返回EOF(-1)
读取标准输入流
1 |
|
读取文件流
1 |
|
getc
getc函数,支持从流(各类文件)中读取一个字符。
原型:int getc(FILE* stream) 读取成功返回字符,失败返回EOF(-1)
getc函数和fgetc函数的用法一致,但不同的点在于:
- fgetc是库(stdio.h)函数
- getc是一个宏函数
getc不可以传递带有副作用的表达式作为参数
读取操作和fgetc一样。
带有副作用的表达式
1 |
|
结果如下:
fgetc和getc区别
所以fgetc和getc的区别如下
功能:
- 两者完全一致
性能:
getc是宏函数,避免了调用函数的堆栈操作,所以getc在极限性能上高于fgetc
场景:
- 如无特殊需要,建议使用fgetc
- getc的性能提升很多时候用不到,但是传入带有副作用的参数,会导致预期结果不一致,而fgetc则一切正常
按字符输出
按字符输出在标准IO库中也有3个常用函数:
- int putchar(int c);
- int fputc(int c, FILE* stream);
- int putc(int c, FILE* stream);
需要包含 stdio.h,成功返回输出的字符;失败返回EOF(-1)
fputc (标准函数)和putc (宏函数)功能基本一致,区别和字符输入的fgetc和getc一样
putchar函数
puchar函数可以将一个字符输出到stdout(标准输出、默认就是控制台):
原型:int putchar(int c) 传入参数为被输出的字符
1 |
|
fputc和putc都是把字符输出到指定流中,略~
实现文件复制
1 |
|
按行输入
按行输入在标准IO中有2个函数:
#include <stdio.h>
- char* gets(char* s);
- char* fgets(char* s, int buf_size, FILE *stream);
成功输入则返回字符串s的指针,到文件末尾或出错返回NULL
gets不推荐使用,容易造成缓冲区溢出。
fgets函数
fgets函数用于从指定文件流读取一行内容(换行符为界定),将读入的内容记录到指定的字符串中:
原型:char* fgets(char* s, int buf_size, FILE* stream);
- 参数1:记录读入数据的字符数组
- 参数2:缓冲区大小(byte), 1个字符占用1个byte
- 参数3:文件流
同理,存入数据的字符数据最后1位用以记录\0, 有效记录字符数为buf_size-1
从文件test.txt中读取:
1 |
|
按行输出
有两个函数:
#include <stdio.h>
- int puts(const char* s);
- int fputs(const char* s, FILE* _Stream);
成功返回0,失败返回EOF(-1);
puts函数
puts函数用以将指定字符串输出到stdout:
原型:int puts(const char* s) 传入参数为被输出的字符串
1 |
|
fputs函数
原型: int fputs(const char* s, FILE* stream);
参数1:被输出字符串 , 参数2 : 输出文件流
1 |
|
按对象输入输出
标准IO也提供了按对象进行输入和输出的相关函数。
#include <stdio.h>
size_t fread(void* ptr, size_t size, size_t n, FILE* fp);
size_t fwrite(const void* ptr, size_t size, size_t n, FILE* fp);
size_t 是类型重定义,本体是 unsigned long long
两个函数的返回值是实际输入/输出的对象个数;出错则返回EOF(-1)
可以输入/输出任意类型的文件(文本或二进制)
按对象输入
fread函数
fread函数可以从任意类型的文件中读取数据
原型: size_t fread(void* ptr, size_t size, size_t n, FILE* fp);
- 参数1: 缓冲区指针,在调用函数前定义好,用来存入读取的数据
- 参数2: 1个对象的大小
- 参数3: 本次读取多少个对象
- 参数4: 文件流指针
工作流程:
- 读取指定的文件流指针
- 本次调用读取n个对象,每个对象
- 大小为size
- 将读取的内容,存入缓冲区指针
按对象输出
fwrite函数
fwrite函数可以从任意类型的文件中读取数据
原型:size_t fwrite(void* ptr, size_t size, size_t n , FILE* fp);
- 参数1: 缓冲区指针,被输出的数据
- 参数2: 1个对象的大小
- 参数3: 本次输出多少个对象
- 参数4: 文件流指针
工作流程:
- 输出内容到指定的文件流指针
- 本次调用输出n个对象,每个对象大小为size
对比标准IO的三种输入输出方式
| 函数 | 操作内容 | 适应文件类型 |
|---|---|---|
| getchar/fgetc/putchar/fputc等 | 单个字符 | 文本文件 |
| gets/fgets/puts/fputs等 | 一行字符(字符串) | 文本文件 |
| fread/fwrite | 任意类型 | 文本/二进制 |
文件IO介绍
文件IO介绍
POSIX标准
文件IO和标准IO不同,它们遵循不同的标准,标准IO遵循的是ANSI C标准,文件IO遵循的是POSIX标准。
1983年,美国国家标准协会(ANSI)组成了一个委员会,X3J11,为了创立 C 的一套标准。经过漫长而艰苦的过程,该标准于1989年完成,这个版本的语言经常被称作ANSI C,或有时称为C89。
POSIX:可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX )
什么是文件I/O?
- posix(可移植操作系统接口)定义的一组函数
- 不提供缓冲机制,每次读写操作都引起系统调用
- 核心概念是文件描述符
- 访问各类型文件
- Linux下,标准IO基于文件IO实现
| 标准IO | 文件IO |
|---|---|
| ANSI C标准 | POSIX标准 |
| 带缓冲 | 无缓冲 |
| 流FILE管理打开文件 | 通过文件描述符管理打开文件 |
文件描述符
- 每打开的文件都对应一个文件描述符
- 文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符。
- 文件描述符从0开始分配,一次递增,且每个进程独立分配,比如进程1描述符从0开始分配,进程2描述符也是从0开始分配。
- 文件IO操作通过文件描述符来完成
- 描述符0代表标准输入,描述符1代表标准输出,描述符2代表错误输出。
打开文件
open函数用于创建或打开文件
1 |
|
- 成功时返回文件描述符,出错时返回EOF
- 打开文件时使用两个参数
- 创建文件时使用第三个参数指定新文件的权限
- 设备文件只能通过open打开,而不能通过open创建
mode的含义
mode 参数是一个 mode_t 类型的值,表示新创建文件的权限设置。它指定了文件的访问权限,通常是一个八进制数,可以由以下几种权限组合而成:
- 用户权限(Owner permissions):
- S_IRUSR:用户可读(4)
- S_IWUSR:用户可写(2)
- S_IXUSR:用户可执行(1)
- 组权限(Group permissions):
- S_IRGRP:组可读(4)
- S_IWGRP:组可写(2)
- S_IXGRP:组可执行(1)
- 其他用户权限(Other permissions):
- S_IROTH:其他用户可读(4)
- S_IWOTH:其他用户可写(2)
- S_IXOTH:其他用户可执行(1)
比如mode 为0666, 表示的是用户权限为可读可写,组权限为可读可写,其他用户权限为可读可写。
关闭文件
1 |
|
- 成功时返回0; 出错时返回EOF;
- 程序结束时自动关闭所有打开的文件
- 文件关闭后,文件描述符不再代表文件,不要使用。
文件IO操作
读文件
read函数用来从文件中读取数据:
1 |
|
- 成功时返回实际读取的字节数,出错时返回EOF
- 读到文件末尾时返回0
- buf是接受数据的缓冲区
- count不应超过buf大小
1 |
|
写文件
write函数用来向文件写入数据:
1 |
|
- 成功时返回实际写入的字节数,出错时返回EOF
- buf是发送数据的缓冲区
- count不应超过buf大小
练习
1 | /* |
定位文件
lseek函数用来定位文件:
1 |
|
- 成功时返回当前文件读写位置,出错时返回EOF
- 参数offset和参数whence同fseek完全一样
- whence包括 SEEK_SET, SEEK_CUR,SEEK_END
总结
- read从描述符中读取内容到缓存中,接受三个参数,要读取的文件描述符,接受数据的缓存,以及读取多少字节
- write向描述符中写入数据,接受三个参数,第一个参数为要写入的文件描述符,第二个参数为写入数据来自的缓存,第三个参数为要写入多少字节。
- lseek定位文件,返回当前文件的读写位置,第一个参数为要定位的文件描述符,第二个参数为偏移量,第三个参数为位置宏
目录操作和文件属性
打开目录
opendir函数用来打开一个目录文件:
1 |
|
- DIR是用来描述一个打开的目录文件的结构体类型
- 成功时返回目录流指针;出错时返回NULL
读取目录
readdir用来读取目录流中的内容:
1 |
|
struct dirent是用来描述目录流中一个目录项的结构体类型- 包含成员char d_name[256] 参考帮助文档
- 成功时返回目录流dirp中下一个目录项
- 出错或者到末尾时返回NULL
关闭目录
closedir用来关闭一个目录文件:
1 |
|
- 成功返回0,失败返回EOF
练习
打印指定目录下所有文件名称:
1 |
|