I/O 输入输出
i:input 输入 o:output 输出
对文件的输入 :输出-》读文件
:输入-》写文件
文件存放在磁盘空间,断电不会丢失文件类型(bcd-lsp)
1.b(Block Device File):块设备文件
2.C(Character Device File):字符设备文件
3.d(Directory): 目录文件
4.-:普通文件
5.L(Link):链接文件
6.s(socket):套接字文件:用于网络通信(网络编程)
7.P(pipe):管道文件:用于进程间的通信方式
标准IO和文件IO
标准IO
标准IO是C库中定义的一组用于输入输出的函数接口。不同的操作系统只要移植了C库就可以使用,它是在系统调用之前做了一个二次封装,相当于是间接的进行了系统调用。可移植性强,可以在不同的系统环境下进行使用。
文件IO
内核向上提供的输出输出函数接口,叫做系统调用函数接口。基于内核,内核不同,系统调用函数接口不同,文件IO不同操作系统函数接口不通用。可移植性较差。
标准IO的调用逻辑
if是linux操作系统
{
调用linux的系统函数(文件io)
}
if是window操作系统
{
调用windows的系统函数(文件io)
}
if是macos操作系统
{
调用macos的系统函数(文件io)
}
缓存机制
缓存机制
正常的系统调用-》应用层读写文件-》调用内核层的函数接口-》内核层与硬件层交互-》拿到数据返回给应用层-》每次读写重复操作
带有缓存机制的调用-》应用层读写文件-》调用内核层的函数接口-》内核层与硬件层交互-》拿到数据返回给应用层的缓存区-》每次读写都从缓存区里拿数据
标准IO
标准IO的特点
1.标准IO是C库中提供的一组专门用于输入输出的函数接口
2.标准IO由ANSI C标准定义,不仅在Unix系统上,在很多操作系统上都实现了标准IO,可移植性强
3.标准IO通过缓存机制减少系统调用次数,提高效率
4.标准IO默认打开三个流:标准输入(stdin),标准输出(stdout),标准出错(stderr)。
5.标准I/O库的所有操作都是围绕流(stream)来进行的,在标准I/O中,流用FILE *来描述。
流
定义
所有的I/O操作仅是简单的从程序移进或者移出,这种字节流,就称为流。
分类
文本流/二进制流
流指针 FILE*
FILE* 是一个指向 FILE 结构体的指针,这个结构体由标准库定义,用于表示一个打开的文件或输入/输出流。
typedef struct _iobuf {
char *_ptr; // 文件输入的下一个位置
int _cnt; // 当前缓冲区的相对位置
char *_base; // 文件初始位置
int _flag; // 文件标志
int _file; // 文件有效性
int _charbuf; // 缓冲区是否可读取
int _bufsiz; // 缓冲区字节数
char *_tmpfname; // 临时文件名
} FILE;
缓存区分类
1.全缓存:基于文件
刷新缓存:
程序正常的退出
遇到(return)
遇到exit退出进程
fclose(关闭流指针)
fflush:强制刷新缓存区
缓存区满
2.行缓存:基于终端-》stdin/stdout
刷新缓存:
程序正常的退出
遇到(return)
遇到exit退出进程
fclose(关闭流指针)
fflush:强制刷新缓存区
缓存区满
\n刷新
3.不缓存:stderr
注意:缓存区只有在使用的时候才会被开辟
计算缓存区大小
fflush函数
#include <stdio.h>
int fflush(FILE *stream);
功能:刷新缓存区
参数:
stream:流 (NULL:刷新所有流)
返回值:成功0
失败EOF(-1),更新errno。
用法:fflush(NULL)
标准IO的函数接口
1. 打开文件:fopen,关闭文件:fclose
2. 读/写单个字符:fgetc,fputc
3. 读/写一个字符串:fgets,fputs
4. 读/写一个二进制文件:fread,fwrite
5.移动指针的函数:fseek
fopen打开文件
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:打开文件
参数:path:打开的文件的路径
mode:打开方式
打开方式:
r:只读,文件指针定位到文件开头(有文件)
r+:可读可写,文件指针定位到文件开头(有文件)
w:只写,文件不存在创建,存在清空,文件指针定位到文件开头
w+:可读可写,文件不存在创建,存在清空,文件指针定位到文件开头
a: 只写,文件不存在创建,存在追加(到文件末尾)
a+:可读可写,文件不存在创建,存在追加(到文件末尾)
读文件指针定位到文件开头。
返回值:成功-的到文件流指针
失败:NULL,更新errno
示例
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp = fopen("./test.c","w");
if (fp == NULL)
{
perror("fopen err");
return -1;
}
printf("fopen success\n");
return 0;
}
开多少个文件?(1024)一个文件能够重复打开吗?(可以)
注意:打开文件属于有限资源,使用完记得及时关闭
fclose关闭文件
int fclose(FILE *stream);
功能:关闭文件
参数:
stream:流指针
返回值:成功0,失败-1,更新errno
fclose(fp);
perror
#include <errno.h>
void perror(const char *s);
功能:根据errno值获取错误信息,将信息输出到终端
参数:S:提示内容
返回值:无
fgetc读单个字符
int fgetc(FILE *stream);
功能:从文件中读一个字符
参数:
stream:流指针(从那个文件读)
返回值:成功返回读到字符的ascii值,失败返回或读到文件结尾返回-1.
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp1 = fopen("./hello.c","r");
if (fp1 == NULL)
{
perror("fopen err");
return -1;
}
printf("fopen success\n");
int ch = fgetc(fp1);
printf("%c\n",ch);
fclose(fp1);
return 0;
}
fputc写单个字符
int fputc(int c, FILE *stream);
功能:向指定的文件中写入一个字符
参数:
c:要写入字符的ASCII值
stream:流指针
返回值:写入字符ASCII值
失败返回:EOF
用上面的fgetc和fputc实现复制的功能,将文件1中的内容读取复制到文件2
#include <stdio.h>
int main(int argc, char const *argv[])
{
// 打开文件流
FILE *fp_r = fopen("./test.txt", "r"); // 只读
FILE *fp_w = fopen("./test1.txt", "w"); // 只写
if (fp_r == NULL && fp_w == NULL)
{
perror("fopen err");
return -1;
}
printf("fopen success\n");
int ch = 0;
while ( (ch = fgetc(fp_r)) != -1)
fputc(ch,fp_w);
fclose(fp_r);
fclose(fp_w);
return 0;
}
fprintf
#include<stdio.h>
int fprintf(FILE *stream, const char *format, ...);
功能:向指定的文件以指定的格式写入数据
参数: stream :流指针
format:指定格式
...:多个参数
返回值:输出字符个数
失败返回:EOF
fgets读字符串
char *fgets(char *s, int size, FILE *stream);
功能:从文件中获取指定长度的字符串
参数: s:字符串存放的首地址
size:期望获取字符的个数
实际读size-1个字符,会自动补'\0',预留一个位置补'\0'.
文件中不满size-1个,有多少读多少,都会补'\0'.
当读到'\n',结束,不再读下一行内容,再次调用fgets继续从下一行开始读。
stream:文件流指针
返回值:
成功:返回获取成功字符串存放的首地址
失败或读到文件结尾返回NULL。
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp1 = fopen("./test.c","r");
if (fp1 == NULL)
{
perror("fopen err");
return -1;
}
int age = 18;
printf("fopen success\n");
char buf[64];
while (fgets(buf,sizeof(buf),fp1) != NULL)
printf("buf = %s\n",buf);
fclose(fp1);
return 0;
}
fputs写字符串
int fputs(const char *s, FILE *stream);
功能:向指定文件中输入一串字符
参数:
s:输入字符串的首地址
stream:文件流指针
返回值:成功返回输出字符个数
失败返回EOF
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp1 = fopen("./test.c","w");
if (fp1 == NULL)
{
perror("fopen err");
return -1;
}
int age = 18;
printf("fopen success\n");
char buf[64];
while (fgets(buf,sizeof(buf),fp1) != NULL)
{
fputs(buf,fp1);
}
fclose(fp1);
return 0;
}
文件IO
内核向上提供的输入输出函数接口,叫做系统调用函数接口。基于内核,内核不同,系统调用函数接口不同,文件IO不同操作系统函数接口不通用。可移植性较差。
特点
1.没有缓存机制,每次调用都会引起系统调用。
2.围绕文件描述符进行操作,文件描述符都是非负的整数,依次进行分配。
3.文件IO默认打开三个文件描述符,0(标准输入),1(标准输出),2(标准出错)
4.文件IO可以操作除了目录文件之外的任意类型的文件。
文件IO的函数接口
1.打开文件 open
2.关闭文件 close
3.读文件 read
4. 写文件 write
5. 偏移函数 lseek
open 打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
nt open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
flags:打开文件的方式
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
O_CREAT:创建
O_TRUNC:清 空
O_APPEND:追加
fopen() mode | open() flags |
---|---|
r | O_RDONLY |
w | O_WRONLY | O_CREAT | O_TRUNC |
a | O_WRONLY | O_CREAT | O_APPEND |
r+ | O_RDWR |
w+ | O_RDWR | O_CREAT | O_TRUNC |
a+ | O_RDWR | O_CREAT | O_APPEND |
三种参数的情况
当文件中存在O_CREAT时,使用函数
int open(const char *pathname, int flags, mode_t mode);
mode:创建文件的权限,实际权限的算法是:mode &~umask
umask:文件权限掩码,值为0002。比如:mode:0666的话,实际的权限为0664
权限掩码可更改;查看权限掩码:umask 修改文件权限掩码:umask 0000
修改后的权限掩码就可以与mode保持一致。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd = open("./cp.c", O_RDONLY);
if (fd == -1)
{
perror("open err");
return -1;
}
printf("fd = %d\n",fd);
return 0;
}
close关闭文件
#include <unistd.h>
int close(int fd);
功能:关闭文件
read读函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:读文件
参数:fd:文件描述符
buf:存放内容的首地址
count:期待读取字符的个数
返回值:成功实际读到字符的个数,读到文件结尾返回0
失败返回-1,更新errno
注意:
使用read函数时需要注意:read读文件,一般期待读多少字符就是读多少,不会自动补’\0‘,遇到\n也不会自动停止读取,会继续读下一行的内容。
'\0'需要自己补充,考虑预留一个字节补'\0'
使用技巧:
1.通过返回值作为实际读到的字符个数,后面补‘\0’;
char buf[32];
ret = read(fd,buf,31);
buf[ret] = '\0';
2.每次读内容放到数组之前先清空数组。
清空数组的函数
1. memset
void *memset(void *s, int c, size_t n);
功能:清空数组
参数:s:清空的数组首地址
c:一般写0
n:清空的字节数
2. bzero
void bzero(void *s, size_t n);
功能:清零函数
参数:
s:要清空内容的首地址
n:清空的字节数
返回值:无
write函数
ssize_t write(int fd, const void *buf, size_t count);
功能:写文件
参数:
fd:文件描述符
buf:写内容存放的首地址
count:期待写字符的个数
返回值:成功实际写字符的个数
失败返回-1,更新errno
ssize_t ret = read(fd1,buf,64)
write(fd2,buf,ret)
读多少写多少
利用read和write函数实现cp功能,命令行传参实现
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//判错
if (argc != 3)
{
printf("please filename\n");
return -1;
}
//打开两个文件
int fd_src = open(argv[1],O_RDONLY);
if (fd_src == -1)
{
perror("open err");
return -1;
}
int fd_dest = open(argv[2],O_WRONLY | O_TRUNC | O_CREAT,0666);
if (fd_dest == -1)
{
perror("open err");
return -1;
}
char buf[32];
ssize_t ret = 0;
//读取源文件,写入目标文件
while (ret = read(fd_src,buf,sizeof(buf)))
{
write(fd_dest,buf,ret);
}
close(fd_src);
close(fd_dest);
return 0;
}
lseek函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:将文件指针移动到指定位置
参数:
fd:文件描述符
offset:偏移量 +向后 -向前
whence:相对位置
SEEK_SET:开头
SEEK_CUR:当前
SEEK_END:结尾
返回值:成功:当前位置(基于文件开头)
失败:-1,更新errno
off_t len = lseek(fd,0,SEEK_END)
标准IO和文件IO的区别
标准IO | 文件IO | |
---|---|---|
概念 | C库定义的一组用于输入输出的函数 | 系统中定义的一组用于输入输出的函数 |
特点 | 1)有缓冲机制2)围绕流进行操作,FILE *3)默认打开了三个流:stdout/stdin/stderr | 1)无缓冲机制2)围绕文件描述符操作,非负整数3)默认打开了三个文件描述符:0/1/24)可以操作任意类型的文件 |
函数 | 打开文件:fopen关闭文件:fclose读写操作:fgetc fputc fputs fgets fread fwrite 定位操作:fseek rewind ftell时间:time localtime其他: fflush fprintf perror | 打开文件:open关闭文件:close读写操作:read write定位操作:lseek |
目录操作函数
1. 打开目录:opendir
2. 关闭目录:closedir
3. 读目录:readdir
4. 修改文件路径;chdir
5. 获取文件属性:stat
opendir打开目录
1.opendir 打开目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
功能:打开目录文件
参数:name:文件名
返回值:成功返回目录流指针,失败NULL,更新errno
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
DIR *dirp = opendir("./");
if (dirp == NULL)
{
perror("open err");
return -1;
}
return 0;
}
closedir关闭文件目录
closedir 关闭目录
int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流指针
返回值:成功0,失败-1,更新errno
readdir读取目录内容
#include <dirent.h>
readdir 读目录文件
struct dirent *readdir(DIR *dirp);
功能:读目录文件
参数:dirp:目录流指针
返回值:成功返回结构体指针,读到文件结尾返回NULL
失败NULL,更新errno
struct dirent {
ino_t d_ino; /*文件的inode */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* 文件类型,并不支持所有文件类型 */
char d_name[256]; /*文件名 */
};
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
struct dirent *dir;
DIR *dirp = opendir("./");
if (dirp == NULL)
{
perror("open err");
return -1;
}
//读取文件夹中的内容
while ((dir = readdir(dirp)) != NULL)
{
printf("filename = %s\n",dir->d_name);
}
closedir(dirp);
return 0;
}
chdir修改当前所处的路径
chdir 修改当前所处路径
#include <unistd.h>
int chdir(const char *path);
功能:改变当前所处的路径
参数:path:修改的路径、
返回值:成功0
失败:-1,更新errno
stat获取文件属性
基本用法
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
功能:获取文件属性
参数:
pathname:文件
buf:获取到属性存放的位置
返回值:成功0,失败-1,更新errno
struct stat {
dev_t st_dev; /* 包含文件的设备ID */
ino_t st_ino; /* 文件的inode号 */
mode_t st_mode; /* 文件的类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 用户ID */
gid_t st_gid; /* 组ID */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* 大小 */
blksize_t st_blksize; /* 文件系统IO块的大小 */
blkcnt_t st_blocks; /* 512b分配的数量 */
struct timespec st_atim; /* Time of last access 上次访问时间*/
struct timespec st_mtim; /* Time of last modification 上次修改时间 */
struct timespec st_ctim; /* Time of last status change上次状态更改时间 */
}
例子 :获取文件的inode号
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//通过stat函数获取文件的信息
struct stat sb;
if (stat("./1.c",&sb) == -1)
{
perror("stat err");
return -1;
}
printf("inode = %ld\n",st_ino);
printf("dev = %ld\n",sb.st_dev);
return 0;
}
获取文件类型
在man手册的第七页,打开man 7 inode
The following mask values are defined for the file type:
对于该文件类型,定义了以下掩码值:
st_mode 包含的是文件类型和权限:
文件类型:
S_IFMT 0170000 bit mask for the file type bit field 文件类型位字段的位掩码
S_IFSOCK 0140000 socket-套接字
S_IFLNK 0120000 symbolic link-连接文件
S_IFREG 0100000 regular file-普通文件
S_IFBLK 0060000 block device块设备
S_IFDIR 0040000 directory目录
S_IFCHR 0020000 character 字符设备
S_IFIFO 0010000 FIFO管道
使用规则
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//通过stat函数获取文件的信息
struct stat sb;
if (stat("./1.c",&sb) == -1)
{
perror("stat err");
return -1;
}
printf("mode = %#o\n",sb.st_mode);
return 0;
}
100666
001 000 000 110 110 110
&
001 111 000 000 000 000
170000
=
001 000 000 000 000 000
100000
stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG)
{
printf(“普通文件\n”);
}
因为使用频繁,所以有了更简便的方法
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
用法
stat(pathname, &sb);
if (S_ISREG(sb.st_mode))
{
printf(“普通文件”);
}
if (S_ISFIFO(sb.st_mode))
{
printf("管道文件");
}
其他结构体成员
#include <pwd.h>
1.getpwuid:通过用户id获取用户名
struct passwd *getpwuid(uid_t uid);
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
getpwuid(sb.st_uid)->pw_name
#include <grp.h>
2.getgrgid:通过组id获取组名
struct group *getgrgid(gid_t gid);
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* group members */
};
3. ctime();将时间转转换为字符串表示格式
char *ctime(const time_t *timep);
如:转换为-"Wed Jun 30 21:49:08 1993\n"
例子
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
int main(int argc, char const *argv[])
{
// 通过stat函数获取文件的信息
struct stat sb;
if (stat("./1.c", &sb) == -1)
{
perror("stat err");
return -1;
}
//printf("mode = %#o\n", sb.st_mode);
if ((sb.st_mode & S_IFMT) == S_IFREG)
{
printf("-");
}
if (sb.st_mode & S_IRUSR)
{
putchar('r');
}
else
{
putchar('-');
}
if (sb.st_mode & S_IWUSR)
{
putchar('w');
}
else
{
putchar('-');
}
printf("%s ", getpwuid(sb.st_uid)->pw_name);
printf("%s ", getgrgid(sb.st_gid)->gr_name);
printf("%s ", ctime(&sb.st_mtime));
return 0;
}
例子
模仿 ls -l
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
int main(int argc, char const *argv[])
{
struct stat *sb = (struct stat *)malloc(sizeof(struct stat));
struct dirent *myls;
DIR *dirp = opendir("./");
if (dirp == NULL)
{
perror("open err");
return -1;
}
while ((myls = readdir(dirp)) != NULL)
{
if (stat(myls->d_name, sb))
{
perror("stat err");
return -1;
}
if (myls->d_name[0] == '.')
continue;
printf("%s", S_ISREG(sb->st_mode) ? "-" : S_ISDIR(sb->st_mode) ? "d"
: S_ISCHR(sb->st_mode) ? "c"
: S_ISBLK(sb->st_mode) ? "b"
: S_ISFIFO(sb->st_mode) ? "p"
: S_ISLNK(sb->st_mode) ? "L"
: S_ISSOCK(sb->st_mode) ? "s"
: "error\n");
sb->st_mode &S_IRUSR ? putchar('r') : putchar('-');
sb->st_mode &S_IWUSR ? putchar('w') : putchar('-');
sb->st_mode &S_IXUSR ? putchar('x') : putchar('-');
sb->st_mode &S_IRGRP ? putchar('r') : putchar('-');
sb->st_mode &S_IWGRP ? putchar('w') : putchar('-');
sb->st_mode &S_IXGRP ? putchar('x') : putchar('-');
sb->st_mode &S_IROTH ? putchar('r') : putchar('-');
sb->st_mode &S_IWOTH ? putchar('w') : putchar('-');
sb->st_mode &S_IXOTH ? putchar('x') : putchar('-');
printf(" %ld %s %s ", sb->st_nlink, getpwuid(sb->st_uid)->pw_name,
getgrgid(sb->st_gid)->gr_name);
printf("%7ld ", sb->st_size);
printf("%.*s ", 12, ctime(&sb->st_mtime)+4);
printf("%s\n", myls->d_name);
}
free(sb);
sb = NULL;
closedir(dirp);
return 0;
}