文件:一组相关数据的有序集合
文件分类:常规文件(r)、目录文件(d)、字符设备文件(c)、块设备文件(b)、管道文件(p)、套接字文件(s)、符号链接文件(l)。
1. 标准IO
标准IO用一个结构体存放打开的文件的信息,标准IO的所有操作都是基于FILE这玩意!
标准IO通过内部缓存机制减少系统调用,具有更高的效率。(注:正常流程是app通过系统调用对磁盘数据进行读写操作,而系统调用是有开销的,频繁的系统调用必然导致效率的降低;而通过标准IO,app开辟一块临时缓存,app先通过一次系统调用将磁盘数据拷贝至临时缓存中,之后的读写操作是app直接与临时缓存进行交互,极大地减少系统调用次数,提高效率)
1.1 标准IO——流(stream)
流分类:文本流、二进制流,在Linux下不用考虑二者区别,但是在Windows下需要。
缓冲类型:
全缓冲:当流的缓冲区无数据或者无空间时,执行IO操作(打开文件时默认为全缓冲)
行缓冲:输入或输出中遇到换行符(“\n”)时,执行IO操作(与终端相关的为行缓冲)
无缓冲:数据直接写入文件,不进行缓冲
在程序运行前,会自动创建3个流:stdin, stdout, stderr.
标准IO是行缓冲类型!!!
1.2 标准IO——函数介绍及应用
1. fopen()
流指针 = 打开文件(文件路径+文件名, 打开方式),只有文件名时默认为当前路径
2. perror() / strerror()
3. fclose()
成功返回0,失败返回EOF,并设置errno;流关闭时自动刷新缓存中的数据并释放缓存区。
4. fgetc() / fputc() 与 fgets() / fputs()
fgetc() / fputc() 按照字节读写
fgets() / fputs() 按照行读写
示例1:利用fputc()输出a-y至文件中
示例2:
1 #include <stdio.h>
2
3 /* 要求利用fgetc以及fputc实现文件复制
4 * 要求通过命令行参数传递源文件和目标文件名
5 */
6
7
8 int main(int argc, char *argv[])
9 {
10 FILE *fp_src, *fp_dts;
11 int ch;
12
13 if(argc < 3){
14 printf("Usage: %s <src_file> <dts_file>\n", argv[0]);
15 return -1;
16 }
17
18 if((fopen(argv[1], "r")) == NULL){
19 perror("fopen src_file");
20 return -1;
21 }
22
23 if((fopen(argv[2], "w")) == NULL){
24 perror("fopen dts_file");
25 return -1;
26 }
27
28 while((ch = fgetc(fp_src)) != EOF){
29 fputc(ch, fp_dts);
30 }
31
32 fclose(fp_src);
33 fclose(fp_dts);
34
35 return 0;
36 }
~
5. fread() / fwrite() 读写流
读写若干个对象,且每个对象长度相同,该对象可以是单个字节、整数、结构体等
相比于前面介绍的fgetc()/fputc()效率更高
示例:
int s[10];
if(fread(s, sizeof(int), 10, fp) < 0){
perror("fread");
return -1;
}
struct people {
int age;
char *name;
} p[] = {{22, "zhangsan"}, {32, "lisi"}};
fwrite(p, sizeof(struct people), 2, fp);
1 #include <stdio.h>
2
3 /* 要求利用fread以及fwrite实现文件复制
4 * 要求通过命令行参数传递源文件和目标文件名
5 */
6
7 #include <stdio.h>
8 #define N 1024 //定义缓存区最大存储字节
9
10 int main(int argc, char *argv[])
11 {
12 FILE *fp_src, *fp_dts;
13 int ch;
14 char buf[N];
15 int n; //记录读取字符的个数
16
17 if(argc < 3){
18 printf("Usage: %s <src_file> <dts_file>\n", argv[0]);
19 return -1;
20 }
21
22 if((fopen(argv[1], "r")) == NULL){
23 perror("fopen src_file");
24 return -1;
25 }
26
27 if((fopen(argv[2], "w")) == NULL){
28 perror("fopen dts_file");
29 return -1;
30 }
31
32 while((n = fread(buf, 1, N, fp_src)) > 0){
33 fwrite(buf, 1, n, fp_dts); //以字符为单位读取到buf中并写入目标文件中
34 }
35
36 fclose(fp_src);
37 fclose(fp_dts);
38
39 return 0;
40 }
6. fflush() 刷新流
- 将流缓冲区内的数据写入到实际文件
- Linux下只能刷新输出缓存区
return (0 | EOF)
7. ftell() / fseek() / rewind() 定位流
ftell() 成功时返回流的当前读写位置的值,错误返回EOF (文件打开时默认读写为0)
fseek() 用于设定流的读写位置,whence—基准位置,offset—偏移量,return(0 | EOF)
rewind() 用于直接将流定位至起始位置
/*
* 示例1:在文件末尾追加字符
*/
#include <iostream>
int main()
{
FILE *fp;
fp = fopen("a.txt", "r+");
if(!fp){
perror("fopen");
return -1;
}
fseek(fp, 0, SEEK_END); //定位至文件末尾
fputc('c', fp) //追加一个字符‘c’
return 0;
}
/*
* 示例2:获取文件长度
*/
#include <iostream>
int main()
{
FILE *fp;
fp = fopen("a.txt", "r+");
if(!fp){
perror("fopen");
return -1;
}
fseek(fp, 0, SEEK_END); //定位至文件末尾
printf("file size : %d\n", ftell(fd)); //获取当前读写位置的值
return 0;
}
8. fprintf() / sprintf() 标准输出流
sprintf() 是将格式化的字符串输出到指定的缓存区中
fprintf() 相比于 printf() 多了一个参数FILE *stream, 即 fprintf() 可以将格式化后的字符串输出到指定的流里,而 printf() 只能进行标准输出,也就是说下面两个输出是等价的
printf("I am 23 years old.\n");
fprintf(stdout, "I am 23 years old.\n");
1 /*
2 * 示例:
3 * 将“年-月-日”分别写入到文件与缓存中
4 */
5
6 #include <stdio.h>
7
8 int main()
9 {
10 FILE *fp;
11 int year = 2023;
12 int month = 8;
13 int day = 22;
14 char buf[32];
15
16
17 if((fp = fopen("a.txt", "r+")) == NULL){
18 perror("fopen");
19 return -1;
20 }
21
22 fprintf(fp, "%d-%d-%d\n", year, month, day); //输出到指定流
23 sprintf(buf, "%d-%d-%d\n", year, month, day); //输出到缓冲区
24
25 printf("buf content: %s", buf);
26 return 0;
27 }
1 /*
2 * 示例:
3 * 将“标号,年-月-日 时:分:秒”这种格式的时间写入到文件中
4 * 5s执行一次
5 */
6
7 #include <stdio.h>
8 #include <unistd.h> //sleep
9 #include <time.h> //timer / localtime
10 #include <string.h>
11
12 int main()
13 {
14 FILE *fp;
15 int line = 0;
16 char buf[64];
17 time_t t;
18 struct tm *tp;
19
20 //采用a+模式,在流最后追加数据
21 if(!(fp = fopen("a.txt", "a+"))){
22 perror("fopen");
23 return -1;
24 }
25
26 while(!(fgets(buf, 64, fp))){
27 if(buf[strlen(buf)-1] == '\n') //buf最后一个为结束符,结束符前面一个为换行符
28 line++; //如果等于'\n',说明读取完一行,计数++
29 }
30
31 while(1){
32 time(&t); //获取系统时间
33 tp = localtime(&t); //转化为本地时间
34 fprintf(fp, "%02d, %d-%02d-%02d %02d:%02d:%02d\n", ++line, tp->tm_year+1900, tp->tm_mon+1,
35 tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); //输出到流
36 fflush(fp); //刷新
37 sleep(5);
38 }
39
40 return 0;
41 }
2. 文件IO
- 文件IO遵循posix规范,标准IO遵循ASCII规范
- 文件IO没有缓存区,标准IO有缓存区
- 文件IO通过文件描述符来表示打开的文件,标准IO通过流(FILE结构体)
- 文件IO可以访问不同的文件类型(Linux下的7种文件类型均可),标准IO只能访问普通文件或终端文件
- Linux下,标准IO是基于文件IO实现的,文件IO更底层
2.1 文件IO——文件描述符(fd)
文件描述符与文件一一对应,是一个非负整数,从0开始分配,依次递增。每个程序中的文件描述符是独立的,互不影响(程序A中有文件描述符4,程序B中也可以有4)。
2.2 文件IO——函数介绍及应用
1. open()函数
NAME
open - open and possibly create a file
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
DESCRIPTION
The open() system call opens the file specified by pathname. If the specified file does not exist, it may optionally (if O_CREAT is specified in flags) be created by open().
RETURN VALUE
The return value of open() is a file descriptor, a small, nonnegative integer.
2. close()函数
NAME
close - close a file descriptor
SYNOPSIS
#include <unistd.h>
int close(int fd);
DESCRIPTION
close() closes a file descriptor, so that it no longer refers to any file and may be reused.
RETURN VALUE
close() returns zero on success. On error, -1 is returned, and errno is set appropriately.
3.read()函数
NAME
read - read from a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
RETURN VALUE
On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.
On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes.
4.write()函数
NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
DESCRIPTION
write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.
RETURN VALUE
On success, the number of bytes written is returned (zero indicates nothing was written).
On error, -1 is returned, and errno is set appropriately.
5.lseek()函数
NAME
lseek - reposition read/write file offset
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
DESCRIPTION
lseek() repositions the file offset of the open file description associated with the file
descriptor fd to the argument offset according to the directive whence as follows:
SEEK_SET: The file offset is set to offset bytes.
SEEK_CUR: The file offset is set to its current location plus offset bytes.
SEEK_END: The file offset is set to the size of the file plus offset bytes.
RETURN VALUE
Upon successful completion, lseek() returns the resulting offset location as measured in bytes from the beginning of the file. On error, the value (off_t) -1 is returned and errno is set to indicate the error.
1 /* 示例:
2 * 利用文件IO实现文件复制
3 */
4
5 #include <stdio.h>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <errno.h>
9
10 #define N 32
11
12 int main(int argc, char **argv)
13 {
14 int fd_src, fd_dts, n;
15 char buf[N];
16
17 if(argc < 3){
18 printf("Usage: %s <fd_src> <fd_dts>\n", argv[0]); //提示用法
19 return -1;
20 }
21
22 if(!(fd_src = open(argv[1], O_RDONLY))){
23 fprintf(stderr, "open %s: %s\n", argv[1], strerror(errno)); //打开文件fd_src
24 return -1;
25 }
26
27 if(!(fd_dts = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666))){
28 fprintf(stderr, "open %s: %s\n", argv[2], strerror(errno)); //打开文件fd_dts,只:写|不存在则创建|存在则清空原有内容
29 return -1;
30 }
31
32 while((n = read(fd_src, buf, N)) > 0){ //先将fd_dts里的内容读到buf中
33 write(fd_dts, buf, n); //再将buf中的内容写到fd_dts中
34 }
35
36 close(fd_src);
37 close(fd_dts);
38
39 return 0;
40 }
3. 库的制作与使用
库是一个二进制文件,包含的代码可以直接被程序调用(不用自己去实现,提高开发效率),如标准c库、数学库、线程库...
库有源码,可以下载后编译;也可以直接安装二进制包
Linux系统中库默认安装位置: /lib & /usr/lib
Linux下包含静态库和共享库
3.1 静态库
程序在编译(链接)时把静态库中相关代码复制到可执行文件中
- 程序中已包含代码,运行时不需要静态库;
- 程序运行时无需加载库,运行速度更快;
- 占用更多的磁盘和空间;
- 静态库升级后,程序需要重新编译链接。
静态库的创建
book@yike:~/yike/static_lib$ cat hello.c
#include <stdio.h>
// 创建库之前一定要先将库函数实现
void hello(void)
{
printf("hello\n");
}
book@yike:~/yike/static_lib$ cat test.c
#include <stdio.h>
void hello(void); //先申明一下,不然下面调用会报错
int main()
{
hello(); //调用静态库函数
return 0;
}
book@yike:~/yike/static_lib$
book@yike:~/yike/static_lib$ gcc -c hello.c #生成.o文件
book@yike:~/yike/static_lib$ ls
hello.c hello.o
book@yike:~/yike/static_lib$ ar crs libhello.a hello.o #生成静态库文件,其名必须以lib开头,
#以.a作为后缀,否则无法识别
book@yike:~/yike/static_lib$ ls
hello.c hello.o libhello.a
book@yike:~/yike/static_lib$ gcc -o test test.c -L. -lhello #-L是是为了指定链接的库的路径,
#.表示当前路径,-l表示库函数名
book@yike:~/yike/static_lib$ ls
hello.c hello.o libhello.a test test.c
book@yike:~/yike/static_lib$ ./test
hello
3.2 共享库
编译(链接)时仅记录用到哪个共享库中的哪个符号,不赋值共享库中相关代码
- 程序中不包含库中代码,尺寸小
- 多个程序可以共享同一个库
- 程序运行时需要加载库
- 库升级方便,无需重新编译程序
3.2.1 共享库创建
book@yike:~/yike/shared_lib$ ls
bye.c hello.c test.c
book@yike:~/yike/shared_lib$ cat bye.c
#include <stdio.h>
// 创建库之前一定要先将库函数实现
void bye(void)
{
printf("bye\n");
}
book@yike:~/yike/shared_lib$
book@yike:~/yike/shared_lib$ cat hello.c
#include <stdio.h>
// 创建库之前一定要先将库函数实现
void hello(void)
{
printf("hello\n");
}
#-fPIC 告诉编译器生成位置无关代码,从而允许库加载到程序中不同位置被运行
book@yike:~/yike/shared_lib$ gcc -c -fPIC hello.c bye.c
book@yike:~/yike/shared_lib$ ls
bye.c bye.o hello.c hello.o test.c
#-shared生成动态库 lib开头,.so.x作为后缀,其中.x表示版本号(动态库更新)
book@yike:~/yike/shared_lib$ gcc -shared -o libcommon.so.1 hello.o bye.o
book@yike:~/yike/shared_lib$ ls
bye.c bye.o hello.c hello.o libcommon.so.1 test.c
book@yike:~/yike/shared_lib$ ln -s libcommon.so.1 libcommon.so #为共享库文件创建链接文件
book@yike:~/yike/shared_lib$ ls
bye.c bye.o hello.c hello.o libcommon.so libcommon.so.1 test.c
book@yike:~/yike/shared_lib$ cat test.c #测试程序
#include <stdio.h>
//函数申明
void hello(void);
void bye(void);
int main()
{
hello(); //调用静态库函数
bye();
return 0;
}
book@yike:~/yike/shared_lib$ gcc -o test test.c -L. -lcommon #编译测试程序
book@yike:~/yike/shared_lib$ ls
bye.c bye.o hello.c hello.o libcommon.so libcommon.so.1 test test.c
book@yike:~/yike/shared_lib$ ./test #执行测试程序
./test: error while loading shared libraries: libcommon.so: cannot open shared object file: No such file or directory #这里报错的原因是该动态库不在系统的搜索路径里,需要添加才可
book@yike:~/yike/shared_lib$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. #添加搜索路径
book@100ask:~/yike/shared_lib$ ./test
hello
bye
book@100ask:~/yike/shared_lib$
3.2.2 共享库加载:
- 将库拷贝至/usr/lib和/lib目录下(不推荐)
- 上面演示的那样修改环境变量,添加库所在的路径(只限于当前终端)
- 添加/etc/ld.so.conf.d/***.conf文件,在该文件中添加自己创建的库的路径,如book@yike:~/yike/shared_lib,然后执行ldconfig刷新(永久的方法,推荐)