打开文件
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fildes, const char *mode);
FILE *freopen(const char *path, const char *mode,FILE *stream);
DESCRIPTION 描述
函数 fopen 打开文件名为 path 指向的字符串的文件,将一个流与它关联。
参数 mode 指向一个字符串,以下列序列之一开始 (序列之后可以有附加的字符):
r 打开文本文件,用于读。流被定位于文件的开始。
r+ 打开文本文件,用于读写。流被定位于文件的开始。
w 将文件长度截断为零,或者创建文本文件,用于写。流被定位于文件的开始。
w+ 打开文件,用于读写。如果文件不存在就创建它,否则将截断它。流被定位于文件的开始。
a 打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。流被定位于文件的末尾。
a+ 打开文件,用于追加
(在文件尾写)。如果文件不存在就创建它。读文件的初始位置是文件的开始,但是输出总是被追加到文件的末尾。
具体说一下截断和追加
所谓截断就是在新插入数据时会将原来的数据删除后在进行插入
直接在文件后面进行插入数据
打开文件上限
ulimit -a可以查看当前进程所有资源上限
可以看到当前进程 open file 上限为1024
但你自己写一个只打开文件的代码会发现,最多可以打开1021个文件,因为每个进程创建的时候,都会默认打开三个文件流,标准输入(stdin)、标准输出(stdout)、标准错误(strerr)
一直打开文件代码如下:
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5
6 {
7 int count =0;
8 while(1)
9 {
10 FILE* fp=fopen("my_file","a+");
11
12 if(!fp)
13 {
14 break;
15 }
16 ++count;
17 }
18 printf("count:%d\n",count);
19 return 0;
20 }
openfile
读文件和写文件
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
函数功能:把ptr所指向的数组中的数据写入到给定流stream中
ptr-- 这是指向要被写入的元素数组的指针。
size-- 这是要被写入的每个元素的大小,以字节为单位。
nmemb-- 这是元素的个数,每个元素的大小为 size 字节。
stream-- 这是指向FILE对象的指针,该FILE对象指定了一个输出流。
返回值:如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。 [2]
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4 int main()
5 {
6 FILE* fp=fopen("my_file","a+");
7
8 if(!fp)
9 {
10 printf("open error!");
11 }
12
13 const char* ptr="hello!\n";
14
15 fwrite(ptr,strlen(ptr),1,fp);
16
17 return 0;
18 }
19
fwrit演示
fseek函数
文件头(SEEK_SET),当前位置(SEEK_CUR),文件尾(SEEK_END)
功能:重定位流(数据流/文件)上的文件内部位置指针
注意:文件指针指向文件/流。位置指针指向文件内部的字节位置,随着文件的读取会移动,文件指针如果不重新赋值将不会改变或指向别的文件。
头文件:
#include <stdio.h>
用法:
int fseek(FILE *stream, long offset, int fromwhere);
第一个参数stream为文件指针
第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END依次为0,1和2.
简言之:
fseek(fp,100L,0);把stream指针移动到离文件开头100字节处;
fseek(fp,100L,1);把stream指针移动到离文件当前位置100字节处;
fseek(fp,-100L,2);把stream指针退回到离文件结尾100字节处。
描述:
函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere(偏移起始位置:文件头0(SEEK_SET),当前位置1(SEEK_CUR),文件尾2(SEEK_END))为基准,偏移offset(指针偏移量)个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
fseek函数和lseek函数类似,但lseek返回的是一个off_t数值,而fseek返回的是一个整型。
返回值:
成功,返回0,失败返回非0值,并设置error的值,可以用perror()函数输出错误。
关闭文件
C 库函数int fclose(FILE *stream)关闭流 stream。刷新所有的缓冲区。
声明编辑
下面是 fclose() 函数的声明。
int fclose(FILE *stream)
参数编辑
stream-- 这是指向 FILE 对象的指针,该FILE对象指定了要被关闭的流。
返回值编辑
如果流成功关闭,则该方法返回零。如果失败,则返回 EOF。
在调用fopen函数之后记得加一个fclose函数及可
open函数
int open(const char*pathname,int flags);
int open(const char*pathname,int flags,mode_t mode);
该函数有两个函数原型,其实是参数的数量问题。
1、int open(const char*pathname,int flags);
第一个参数是要打开的文件名,第二个参数是特殊常量,用于指定怎么打开文件。具体常量如下
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR: 读,写打开
这三个常量,必须制定一个且只能指定一个
O_CREAT: 若文件不存在,则创建它,需要使用mode选项。来指明新文件的访问权限
O_APPEND: 追加写,如果文件已经有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容
返回值:成功打开返回打开文件的文件描述符,是int类型,失败返回-1.
2、 int open(const char*pathname,int flags,mode_t mode);
前两个参数与上相同,第三个参数是设定该文件的权限,具体参数如下
S_IRUSR : 文件所有者有读(r)权限
S_IWUSR : 文件所有者有写(w)权限
S_IRGRP : 文件所属组有读(r)权限
S_IWGRP : 文件所属组有写(w)权限
S_IROTH : 文件所属other有读(r)权限
S_IWOTH : 文件所属other有写(w)权限
write函数
头文件:<unistd.h>
write有两种用法。一种是:ssize_t write(int fd, const void *buf, size_t nbyte);
fd:文件描述符;
buf:指定的缓冲区,即指针,指向要写入的数据;
*buf:写入的数据
nbyte:要写入文件指定的字节数,即写入数据的大小
返回值:写入文档的字节数(成功);-1(出错)
write函数把buf中nbyte写入文件描述符handle所指的文档,成功时返回写的字节数,错误时返回-1.
另一种是: write(const char* str,int n)
str是字符指针或字符数组,用来存放一个字符串。n是int型数,它用来表示输出显示字符串中字符的个数。
write("string",strlen("string");表示输出字符串常量
read函数
ssize_t read(int fd,void* buf,size_t count)
fd:文件描述符;
buf:指定的缓冲区,即指针,指向要写入的数据;
*buf:写入的数据
count:最大可以读多少字节,注意:需要预留\0的位置
返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0。
lseek函数
off_t lseek(int fd,off_t offset,int whence)
fd:文件描述符
offset:偏移量,正数表示正向偏移,负数表示负向偏移
origin:设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
close函数
int close(int fd)
该函数用来关闭已打开文件,指定参数fd为open()或creat()打开的文件描述符
返回值:关闭成功返回0,失败返回-1
写一个系统调用代码
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<string.h>
5 int main()
6
7 {
8 //flags:
9 //O_RDONLY: 只读打开
10 //O_WRONLY: 只写打开
11 //O_RDWR: 读,写打开
12 //这三个常量,必须制定一个且只能指定一个
13 //O_CREAT: 若文件不存在,则创建它,需要使用mode选项。> 来指明新文件的访问权限
14 //O_APPEND: 追加写,如果文件已经有内容,这次打开文件所 写的数据附加到文件的末尾而不覆盖原来的内容
15 //
16 int fd=open("./my_file",O_RDWR|O_CREAT,0664);
17 if(fd<0)
18 {
19 perror("open");
20 return 0;
21 }
22 printf("fd:%d\n",fd);
23 int write_size=write(fd,"hello-linux",11);
24 if(write_size<0)
25 {
26 perror("write");
27 return 0;
28 }
29 printf("write_size:%d\n",write_size);
30 lseek(fd,0,SEEK_SET);
31 char buf[1024];
32 memset(buf,'\0',sizeof(buf));
33 int read_size=read(fd,buf,sizeof(buf)-1);
34 if(read_size<0)
35 {
36 perror("read_size");
37 return 0;
38 }
39 printf("read_size:%d\n",read_size);
40 printf("buf :%s\n",buf);
41 close(fd);
42 return 0;
43 }
文件描述符:是数组fd_array(该数组的每个元素都对应一个文件信息)的下标,下标从0开始,所以文件描述符是一个正整数
程序猿通过文件描述符,找到fd_array数组中对应元素,从而操作文件,内核通过操作元素对应的文件信息来实现文件操作
文件描述符分配规则:最小未占用,即分配当前最小的数组小标且该数组小标当前未被占用,越小越优先分配
文件流指针:由于直接使用文件描述符进行操作不方便,也不安全,所以将其进行封装之后,就有了文件流指针
当进行运行时,会默认打开三个文件
标准输入 | 标准输出 | 标准错误 | 类型 |
---|---|---|---|
stdin | stdout | stderr | –> FILE* ---- 文件流指针 |
0 | 1 | 2 | –> int ---- 文件描述符 |
区别:
- 文件流指针时标准库操作句柄:FILE*,文件描述符时系统调用接口句柄:int fd
- 文件流指针是封装文件描述符的,文件流指针底层的_IO_FILE结构体当中,保存了文件描述符。
- 文件描述符是和内核关联,文件流指针是和所调用的库函数关联
- 缓冲区:库函数接口当中维护的一段内存,分为读缓冲区和写缓冲区,存在于文件流指针结构体中
缓冲区:库函数接口当中维护的一段内存,分为读和写缓冲区,存在于文件流指针结构体中
联系:
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针
读写缓冲区是库函数来维护的,并不是内核维护。进程终止时刷新缓冲区,就是操作该缓冲区,
_exit(系统调用)和exit(库函数)
_exit不会刷新缓冲区,_exit是内核的代码,根内核没有缓冲区
exit会刷新缓冲区,库函数缓冲区即读写缓冲区
由于打印内容实现写到写缓冲区当中,在使用文件流指针的时候,必须加上\n或者其他强制刷新缓冲区的操作
重定向
1.重定向符号
> 输出重定向到一个文件或设备 覆盖原来的文件
>! 输出重定向到一个文件或设备 强制覆盖原来的文件
>> 输出重定向到一个文件或设备 追加原来的文件
< 输入重定向到一个程序
2.标准错误重定向符号
2> 将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 b-shell
2>> 将一个标准错误输出重定向到一个文件或设备 追加到原来的文件
2>&1 将一个标准错误输出重定向到标准输出 注释:1 可能就是代表 标准输出
>& 将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 c-shell
|& 将一个标准错误 管道 输送 到另一个命令作为输入
3.命令重导向示例
在 bash 命令执行的过程中,主要有三种输出入的状况,分别是:
- 标准输入;代码为 0 ;或称为 stdin ;使用的方式为 <
- 标准输出:代码为 1 ;或称为 stdout;使用的方式为 1>
- 错误输出:代码为 2 ;或称为 stderr;使用的方式为 2>
重定向的本质:new_fd拷贝old_fd,(二者都是文件描述符)
4.重定向过程举例说明
将标准输出(stdout)的内容重定向到new_file当中,所以stdin的fd=1,假设当前目录下只有new_file这一个文件,则new_file的fd=3,
重定向过程如下:
1.new_file的文件描述符拷贝stdout的文件描述符
2.若拷贝成功,则关闭new_file的文件描述符
但以下两种情况除外:
①拷贝的是一个无效的文件描述符,则不会关闭new_file文件描述符
②拷贝的new_file文件描述符和stout文件描述符具有相同的值,则也不会关闭new_file文件描述符,new_file的文件描述符和stout的文件描述符指向同一个文件
5.dup()和dup2()
#include<unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
两个均为复制一个现存的文件的描述
两个函数的返回:若成功为新的文件描述,若出错为-1;
dup()函数返回的新的文件描述符是当前可用文件描述符中最小数值
dup()的举例
1 #include<stdio.h>
2 #include<fcntl.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 //flags:
8 //O_RDONLY: 只读打开
9 //O_WRONLY: 只写打开
10 //O_RDWR: 读,写打开
11 //这三个常量,必须制定一个且只能指定一个
12 //O_CREAT: 若文件不存在,则创建它,需要使用mode选项。> 来指明新?<M-C-G>件的访问权限
13 //O_APPEND: 追加写,如果文件已经有内容,这次打开文件所 写的数据附加到文件的末尾而不覆盖原来的内容
14 //
15 int fd;
16 int new_fd;
17 fd=open("dup",O_RDWR|O_CREAT,0666);
18 if(!fd)
19 {
20 printf("open error!!!");
21
22 }
23 printf("fd:%d\n",fd);
24 new_fd=dup(fd);
25 printf("new_fd:%d\n",new_fd);
26 write(new_fd,"abcde",5);
27 close(fd);
28 close(new_fd);
29
30 return 0;
31 }
dup2()与dup()的区别在于可以用newfd来指定新描述符数值,若newfd指向的文件已经被打开,会先将其关闭。若newfd等于oldfd,就不关闭newfd,newfd和oldfd共同指向一份文件
dup2()举例
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<fcntl.h>
4
5 int main()
6
7 {
8 int old_fd;
9 int new_fd;
10
11 old_fd=open("dup2",O_RDWR,"a+");
12 if(!old_fd)
13 {
14 printf("open error!!!");
15 }
16 printf("old_fd:%d\n",old_fd);
17
18 dup2(old_fd,new_fd);
19 printf("hello!!!\n");
20 return 0;
21 }
dup2(old_fd,new_fd)的意思:new_fd指向old_fd指向的文件描述符,即指向标准输出文件描述符指向dup2
静态库和动态库
静态库:在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中的这种库。
静态库特点是可执行文件中包含了库代码的一份完整拷贝
缺点就是被多次使用就会有多份冗余拷贝。
- windows环境下静态库的后缀为.lib,windows环境下的执行程序,编译阶段依赖静态库(.lib),程序运行阶段依赖动态库(.dll)
- linux环境下,静态库命名规范,必须是"lib[your_libary_name].a"lib为前缀,中间是静态库名,扩展名为.a
静态库的生成:生成静态库的时候,使用.o文件进行编译生成
ar -rc lib[your_libary_name].a [xxx].o [xxx].o
静态库的使用:
-L [path] 指定链接的库文件路径;
-l [your_libary_name]:指定链接库文件名称
ege:gcc main.c -o main -L [path] -l [your_libary_name]
动态库
- windows环境下动态库后缀.dll
- linux环境下,动态库命名规范,必须是"lib[your_libary_name].so"lib为前缀,中间是静态库,扩展名为.so
ldd file
列出file所需的所有共享库,具体细节可使用 man ldd
动态库的生成
-shared 生成共享库
-fPIC 产生位置无关的代码
动态库的使用
-L [path] 指定链接的库文件路径;
-l [your_libary_name]:指定链接库文件名称
如何让程序可以找到依赖的动态库
- 将动态库放到当前可执行程序的目录下
- 在环境变量当中设置动态库的搜索路径,设置环境变量LD_LIBRART_PATH
- 不要将其他库(自己写的库或者第三方库)和操作系统库文件放到一起