文件基础
概念:一组相关数据的有序集合
文件类型:
常规文件r
目录文件d
字符串设备文件c
块设备文件 b
管道文件p
套接字文件s
符号链接文件l
系统调用和库函数
内核显卡驱动
把数据扔给内核,内核提供接口
标准I/O-流
FILE
标准io用一个结构体类型来存放打开的文件相关信息
标准I/O的所有操作都是围绕FILE来进行
流(stream)
FILE又被称为流(stream)
文本流/二进制流
windows
二进制流:换行符<- -> '\n'
文本流:换行符 <- -> '\r' '\n'
linux
换行符<- -> '\n'
流的缓冲类型
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作
行缓冲
当在输入和输出中遇到换行符('\n')时进行I/O操作
当流和一个终端关联时,典型的行缓冲
无缓冲
数据直接写入文件,流不进行缓冲
标准I/O-stdin,stdout,stderr
标准I/O预定义3个流,程序运行时自动打开
标准输入流 0 STDIN_FILENO stdin(linux系统定义好的结构体)
标准输出流 1 STDOUT_FILENO stdout
标准错误流 2 STDERR_FILENO stderr
stdin/stdout默认是行缓冲
缓冲区实验(4097可以打印,因为超过缓冲区)printf遇到\n结束
文件的打开和关闭
打开就是占用资源
关闭就是释放资源
下列函数可用于打开一个标准I/O流:
FILE *fopen (const char *path,const char *mode);
成功时返回流指针,出错时返回NULL;所以使用fopen函数必须判断是否是空的
d.关闭文件 int fclose(FILE *stream);
fclose()调用成功返回0,失败返回EOF,并设置errno
流关闭时自动刷新缓存中的数据并释放缓冲区
当一个程序正常终止时,所有打开的流都会被关闭
流一旦关闭后就不能执行任何操作
fclose()函数入参stream必须保证非空,否则出现段错误。
标准I/O读写
读写单个字符
字符输入:
int fgetc(FILE *stream);
int getc(FILE *stream); // 宏
int getchar(void);
成功返回读取的字符,若到文件末尾或出错时返回EOF(-1)
getchar()等同于fgetc(stdin)
getc和fgetc区别是一个是宏一个是函数
注意:函数返回值是int类型而不是char类型,主要是为了扩展返回值的范围。
注意:stdin也是FILE*的指针,是系统定义好的,指向的是标准输入(键盘输入)
打开文件后读取,是从文件开头开始读。读完一个后,读写指针会后移。读写注意文件位置
字符输出:
int fputc(int c, FILE *stream)
int putc(int c, FILE *stream);
int putchar(int c);
成功时返回写入的字符,出错时返回EOF
putchar(c)等同于fputc(c, stdout)
注意:返回和输入都是int类型
遇到Bad file descriptor 一般是读写模式用错
按行读写文件
按行输入
char *gets(char *s);//读取标准输入到缓冲区s
char *fgets(char *s, int size, FILE *stream);//遇到'\n'或已输入size-1个字符时返回,总时包含'\0'
成功时返回s,到文件末尾出错时返回NULL
注意事项:gets函数已经被淘汰,因为会导致缓冲区溢出,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
二进制读写
文本文件和二进制的区别:存储格式不同(文本文件只能存储文本),除了文本文件都是二进制文件
计算机内码概念
下列函数用来从流中读写若干个对象
size_t fread(void *ptr, size_t size, size_t n, FILE *fp);//ptr读的内容,size一次读取的块大小,n是读取的个数,fp要读的文件指针
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);// 同读,fp要写的文件指针
成功返回读写的对象个数,出错时返回EOF。既可以读写文本文件,也可以读写数据文件,效率高。
注意事项:文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。
解决办法:移动指针到文件头(后面讲解):关闭文件,重新打开
文件流的刷新和定位
刷新流int fflush(FILE *fp);
成功时返回0,出错时返回EOF
将流缓冲区中的数据写入实际的文件
linux下只能刷新输出缓冲区,输入缓冲区丢弃
如果输出到屏幕使用fflush(stdout)/fflush(fp)
定位流-ftell/fseek/rewind
long ftell(FILE *stream);
成功时返回流当前读写位置,出错时返回EOF
long fseek(FILE *stream,long offset, int whence);
定位一个流,成功时返回0,出错时返回EOF
void rewind(FILE *stream);
whence参数:SEEK_SET/SEEK_CUR/SEEK_END(这三个函数只适用2G以下的文件)
SEEK_SET从据头文件开头offset位移量为新的读写位置
SEEK_CUR以目前的读写位置往后增加offset个位移量
SEEK_END将读写位置指向文件尾后再增加offset个位移量
offset参数:偏移量,可正可负
打开a模式fseek无效,rewind(fp)相当于fseek(fp,0,SEEK_SET);
rewind()将流定位到文件开始位置
读写流时,当前读写位置自动后移
示例一(在文件末尾追加字符't')
FILE *fp = fopen("test.txt", "r+");
fseek(fp, 0, SEEK_END);
fputs('t', fp);
格式化输入和输出
sprintf
int sprintf(char *s, const char *fmt,...);
fprintf
int fprintf(FILE *stream, const char *fmt,...);
成功时返回输出的字符个数,出错时返回EOF,使用起来很方便,强烈推荐
fscanf
int fscanf(FILE *stream, const char *format, ...);
sscanf
int sscanf(const char *str, const char * format,...);
什么是文件I/O?
posix(可移植操作系统接口)定义的一组函数
文件IO不提供缓存冲机制,每次读写操作都引起系统调用
核心概念是文件描述符
每个打开的文件都对应一个文件描述符。
文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符。(是0-1023的数字,表示文件)
文件描述符从0开始分配,依次递增
文件IO操作通过文件描述符来完成
0:标准输入 stdin 1:标准输出 stdout 2:标准错误 stderr
访问各种类型文件,Linux下,标准IO基于文件IO实现
标准IO | 文件IO(低级IO) | |
打开 | fopen,freopen,fdopen | open |
关闭 | fclose | close |
读 | getc,fgetc,ggetchar,fgets,gets,fread | read |
写 | putc,fputc,putchar,fputs,puts,fwrite | write |
open函数用来创建或打开一个文件
#include<fcntl.h>
int open(const char *pathname, int flags);//不创建文件
int open(const char *pathname,int flags, mode_t mode);//创建文件,但是不能创建设备文件
pathname文件名,可包括路径名
flags:
O_RDONLY:只读方式打开文件
O_WRONLY:可写方式打开文件
O_RDWR:读写方式打开文件
O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三的参数为其设置权限
O_EXECL:如果用O_CREATE时文件存在,则返回错误信息。这一参数可测试文件是否存在。
O_NOCTTY:使用本参数时,如文件为终端,那么终端不可作为调用open()系统调用的哪个进程控制终端
O_TRUNC:如果文件已经存在,那么打开文件时先删除文件中的原有数据
O_APPEND:以添加方式打开文件,所以对文件的些操作都在文件的末尾进行
r | O_RDONLY |
r+ | O_RDWR |
w | O_WRONLY|O_CREAT|O_TRUNO,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 |
mode:被打开文件的存取权限,为8进制表示法。
成功时返回文件描述符,出错时返回EOF
打开文件时使用两个参数
创建文件时第三个参数指定新文件的权限,(只有在建立新文件时有效)此外真正建文件时的权限会受到umask值影响,实际权限是mode-umask
umask:用来设定文件或目录的初始权限
文件和目录的真正初始权限
文件或目录的初始权限=文件或目录的最大默认权限-umask
可以打开设备文件,但是不能创建设备文件(创建设备文件mknode驱动部分会讲)
close函数用来关闭一个打开的文件:
#include<unistd.h>
int close(int fd);
成功时返回0;出错时返回EOF(-1)
程序结束时自动关闭所有打开的文件
文件关闭后,文件描述符不再代表文件
文件IO的读写定位
读取文件
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count);
成功时返回实际读取的字节数,出错时返回EOF
读到文件末尾时返回0
buf是接收数据的缓冲区
count不应超过buf大小
写入文件
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
成功时返回实际读取的字节数,出错时返回EOF
buf是接收数据的缓冲区
count不应超过buf大小
定位文件
#include<unistd.h>
off_t lseek(int fd, off_t offset, intt whence);
成功时返回当前文件读写位置;出错时返回EOF
参数offset和whence同fseek相同
目录操作和库的使用
访问目录
opendir函数用来打开一个目录文件
#include<dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd);使用文件描述符,要配合open函数使用
DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针,出错时返回NULL
readdir函数用来读取目录流中的内容
#include<dirent.h>
struct dirent *readdir(DIR *dirp);
struct dirent是用来描述目录流中一个目录项的结构体类型
包含成员char d_name[256] 参考帮助文档
成功时返回目录流dirp中下一个目录项
出错或到末尾时返回NULL
closefir函数用来关闭一个目录文件
#include<dirent.h>
int closedir(DIR *dirp);
成功时返回0,出错时返回EOF
修改文件访问权限-chomod/fchmod
#include<sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
成功时返回0,出错返回EOF
root和文件所有者能修改文件的访问权限:chmod("test.txt", 0666);
获取文件属性-stat/lstat/fstat
#include<sys/stat.h>
int stat(const char *path,struct stat*buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd,struct stat *buf);
成功时返回0,出错时返回eof
如果path是符合链接stat获取的是目标文件的属性,而lstat获取的是连接文件的属性
struct stat结构体s是存放文件属性的结构体类型
mode_t st_mode; 类型和访问权限
uid_t st_uid; 所有者id
uid_t st_gid;用户组id
off_t st_size; 文件大小
time_t st_mtime;最后修改时间
struct stat{
dev_t st_dev;//文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬链接数目,刚建立的文件值为1
uid_t st_uid; // 用户id
gid_t st_gid; // 组ID
dev_t st_rdev; // (设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; // 文件字节数(文件大小)
unsigned long st_blksize; // 块大小(文件系统的I/O缓冲区大小)
unsigned long st_blocks; // 块数
time_t st_atime;//最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; // 最后一次改变时间(指属性)
}
文件类型-通过系统提供的宏判断文件类型:
S_IFMT 0170000 文件类型的位遮罩
S_ISREG(st_mode) 0100000 是否常规文件
S_ISDIR(st_mode) 0040000 是否目录
S_ISCHR(st_mode) 0020000 是否字符设备
S_ISBLK(st_mode) 0060000 是否块设备
S_ISFIFO(st_mode) 0010000 是否FIFO文件
S_ISLNK(st_mode) 0120000 是否链接文件
S_ISSOCK(st_mode) 0140000 是否SOCKET文件
文件访问权限-通过系统提供的宏来获取文件访问权限
S_IRUSR 00400 bit:8 所有者有读权限
S_IWUSR 00200 7 所有者拥有写权限
S_IXUSR 00100 6 所有者拥有执行权限
S_IRGRP 00040 5 群组拥有读权限
S_IWGRP 00020 4 群组拥有写权限
S_IXGRP 00010 3 群组拥有执行权限
S_IROTH 00004 2 其他用户拥有读权限
S_IWOTH 00002 1 其他用户拥有写权限
S_IXOTH 00001 0 其他用户拥有执行权限
静态库和动态库使用
库的概念
库是一个二进制文件,包含的代码可被程序调用:
标准c库
数学库
线程库...
库有源码,可下载后编译;也可以直接安装二进制包
/lib
/usr/lib
库是事先编译好的,可以复用的代码。在os上运行的程序基本上都要使用库。使用库 提高开发效率。Windows和Linux下库文件格式不兼容。Linux下包含静态库和共享库。
静态库特点
编译(链接)时把静态库中相关代码复制到可执行文件中
程序中已经包含代码,运行时不再需要静态库。
程序运行时无需加载库,运行速度更快
占用更多磁盘和内存空间。静态库升级后
程序需要重新编译链接
静态库创建
命令:ar rsv libhello.a hello.o (假如hello.o文件) /// 用lib开头,静态库是.a
ar参数:
c禁止在创建库时产生的正常消息
r 如果指定的文件已经存在于库中,则替换它
s 无论ar命令是否修改了库内容都强制重新生成库符号表
v将建立新库的详细的逐个文件的描述写至标准输出
q将指定的文件添加到库的末尾
t将库的目录写至标准输出
步骤:
编写库文件代码,编译为.o文件
ar 命令 libxxx.a xxx.o
静态库要以lib开头,后缀名为.a
没有main函数的.c文件不能生成可执行文件
链接静态库
编译test.c并链接静态库libhello.a
gcc -o test test.c -L. -lhello(不用libhello直接小写L+hello)
./test
动态库(共享库)特点
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
程序中不包含库中代码,尺寸小
多个程序可共享同一个库
程序运行时需要加载库
库加载方便,无需重新编译程序
使用更加广泛
动态库生成步骤
生成位置无关的代码目标文件
使用gcc -c -fPID xxx.c xxx.c ....
生成动态库
gcc -shared -o libxxx.so xxx.o xxx.o ...
链接动态库
gcc -o test test.c -L. -lxxxx
如何找到共享库
为了让系统能找到要加载的共享库,有三种方法:
把库拷贝到/usr/lib和/lib下
在LD_LIBARY_PATH环境变量中添加库所在路径
添加/etc/ld.so.conf.d/*.conf文件,执行ldconfig刷新
~/.bashrc文件里面
source ~/.bashrc生效
ldd查看可执行文件的动态库