1. 标准IO介绍及缓存区
标准IO介绍
-
目的:完成Linux下的APP开发
-
Linux下一切皆文件
-
Linux文件的种类 :
- 常规文件
- 目录文件
- 字符文件
- 块文件
- 链接文件(相当于windows快捷方式)
IO的概念
- I :input 输入设备 比如键盘鼠标都是Input设备
- O :output 输出设备 比如显示器,优盘,网口,既是输入也是输出
系统调用和库函数
- 系统调用就是操作系统提供的接口函数.
- 如果我们把系统调用封装成库函数就可以起到隔离的作用,提供程序的可移植性。
- Printf就是库函数然后调用了系统调用才在显示器上显示字符。
文件类型
-
FILE 流(stream)
-
标准IO采用结构体存放打开文件的相关信息
-
文本流和二进制流在window系统中二进制流换行符:\n,文本流换行符:\r \n。Linux下换行符:\n
-
l 缓冲区的概念:为了减少操作IO设备的次数,提高运行效率,在内存里面设置的缓冲区。
-
全缓冲:缓冲区满才输出
-
行缓冲:遇到换行符输出
-
流的缓冲类型:1 全缓冲,2 行缓冲,3 无缓冲
-
标准IO流:stdin,stdout,stderr
-
三种标准IO
-
标准输入流(键盘)
0
STDIN_FILENO
stdin
标准输出流(显示器)
1
STDOUT_FILENO
stdout
标准错误流
2
STDERR_FILENO
stderr
缓冲区实验
-
while死循环时加上sleep()<unistd.h>释放CPU,否则一直占用
-
printf要是输出必须行缓冲结束,比如换行或者行缓冲满
-
行缓冲大小 :1024
-
查看头文件方法:man 2 函数 ,或者 man 3 函数
2. 标准IO文件的打开和关闭
-
打开就是占用资源,关闭就是释放资源
打开文件函数
-
函数原型:FILE *fopen(const char *path,const char *mode);成功返回流指针,失败返回NULL
-
mode参数
-
“r” 或 “rb”
以只读方式打开文件,文件必须存在。
“r+” 或 ”r+b”
以读写方式打开文件,文件必须存在。
“w” 或 “wb”
以只写方式打开文件,若文件存在则文件长度清为0。若文件不存在则创建。
“w+” 或 “w+b”
以读写方式打开文件,其他同”w”。
“a” 或 “ab”
以只写方式打开文件,若文件不存在则创建;向文件写入的数据被追加到文件末尾。
“a+” 或 “a+b”
以读写方式打开文件。其他同”a”
处理错误信息函数
-
函数原型:void perror(const char * s) <stdio.h>
-
extern int errno;头文件 errno.h
-
函数原型:char * strerror(errno)<string.h>
-
perror和strerror 功能:打印系统的错误描述(注意:是系统错误,不是你自己代码错误)
文件关闭函数
-
函数原型:int fclose(FILE *stream)
-
fclose()调用成功返回0,失败返回EOF(-1),并设置errno
-
流关闭时自动刷新缓冲中的数据并释放缓冲区,比如:常规文件把缓冲区内容写入磁盘
-
当一个程序正常终止时,所有打开的流都会被关闭
-
fclose()函数的入参stream必须保证为非空,否则出现断错误。
3. 标准IO的读写
字符输入(读单个字符)
-
函数原型:int fgetc(FILE *stream);<stdio.h>
-
函数原型:int getc(FILE *stream); //宏<stdio.h>
-
函数原型:int getchar(void);<stdio.h>
-
成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1)
-
getchar()等同于fgetc(stdin)
-
getc和fgetc区别是一个是宏一个是函数。宏(getc)速度快,但是保证调用的stream值不能改变。
-
函数返回值是int类型不是char类型,主要是为了扩展返回值的范围。
-
tdin 也是FILE *的指针,是系统定义好的,指向的是标准输入(键盘输入)
-
打开文件后读取,是从文件开头开始读。读完一个后读写指针会后移。读写注意文件位置!
-
调用getchar会阻塞,等待你的键盘输入
字符输出(写单个字符)
-
函数原型:int fputc(int c, FILE *stream);<stdio.h>
-
函数原型:int putc(int c, FILE *stream);<stdio.h>
-
函数原型:int putchar(int c);<stdio.h>
-
成功时返回写入的字符;出错时返回EOF
-
putchar(c)等同于fputc(c, stdout)
-
遇到这种错误:Bad file descriptor,很可能是文件打开的模式错误(只读模式去写,只写模式去读)
行输入(读取整个行)
-
函数原型:char *gets(char *s); 读取标准输入到缓冲区s
-
函数原型:char *fgets(char *s, int size, FILE *stream);
-
成功时返回s,到文件末尾或出错时返回NULL
-
遇到’\n’或已输入size-1个字符时返回,总是包含’\0’
-
gets函数已经被淘汰,因为会导致缓冲区溢出
-
fgets 函数第二个参数,输入的数据超出size,size-1个字符会保存到缓冲区,最后添加’\0’,如果输入数据少于size-1 后面会添加换行符。
行输出(写整行)
-
函数原型:int puts(const char *s);<stdio.h>
-
函数原型:int fputs(const char *s, FILE *stream);<stdio.h>
-
成功时返回非负整数;出错时返回EOF
-
puts将缓冲区s中的字符串输出到stdout,并追加’\n’;fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’
4. 标准IO读写:二进制方式
二进制读写
-
系统中的文件分为二进制文件和文本文件
-
文本文件和二进制的区别:存储的格式不同:文本文件只能存储文本。
-
计算机内码概念:文本符号在计算机内部的编码(计算机内部只能存储数字0101001....,所以所有符号都要编码)
二进制读写函数格式:
-
函数原型:size_t fread(void *ptr, size_t size, size_t n, FILE *fp);<stdio.h>
-
void *ptr 读取内容放的位置指针 ;
-
size_t size 读取的块大小;
-
size_t n 读取的个数;
-
FILE *fp 读取的文件指针
-
-
函数原型:size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);<stdio.h>
-
void *ptr 写文件的内容的位置指针;
-
size_t size 写的块大小;
-
size_t n 写的个数;
-
FILE *fp 要写的文件指针
-
-
文件写完后,文件指针指向文件末尾,如果这时候读,读不出来内容。解决办法:移动指针(后面讲解)到文件头;关闭文件,重新打开
5. 流刷新定位
-
函数原型:int fflush(FILE *fp);
-
成功时返回0;出错时返回EOF
-
将流缓冲区中的数据写入实际的文件
-
Linux下只能刷新输出缓冲区,输入缓冲区丢弃
-
如果输出到屏幕使用fflush(stdout)
流的定位:
-
函数原型:long ftell(FILE *stream);
-
函数原型:long fseek(FILE *stream, long offset, int whence);
-
函数原型:void rewind(FILE *stream);
-
fseek 参数whence参数:SEEK_SET/SEEK_CUR/SEEK_END
-
SEEK_SET 从距文件开头 offset 位移量为新的读写位置
-
SEEK_CUR:以目前的读写位置往后增加 offset 个位移量
-
SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量
-
offset参数:偏移量,可正可负
注意事项:
1.文件的打开使用a模式 fseek无效
2.rewind(fp) 相当于 fseek(fp,0,SEEK_SET);
3.这三个函数只适用2G以下的文件
6. 格式化输入输出
格式化输出
-
int fprintf(FILE *stream, const char *fmt, …);
-
int sprintf(char *s, const char *fmt, …);
-
成功时返回输出的字符个数;出错时返回EOF
格式化输入
-
int fscanf(FILE *stream, const char *format, ...);
-
int sscanf(const char *str, const char *format, ...);
-
重点掌握sprintf 和sscanf
7. 标准IO练习
-
time()用来获取系统时间(秒数)
-
time_t time(time_t *seconds) 1970.1.1 0:0:0
-
localtime()将系统时间转换成本地时间
-
struct tm *localtime(const time_t *timer)
struct tm {
int tm_sec; /* 秒,范围从 0 到 59 */
int tm_min; /* 分,范围从 0 到 59 */
int tm_hour; /* 小时,范围从 0 到 23 */
int tm_mday; /* 一月中的第几天,范围从 1 到 31 */
int tm_mon; /* 月份,范围从 0 到 11 */
int tm_year; /* 自 1900 起的年数 */
int tm_wday; /* 一周中的第几天,范围从 0 到 6 */
int tm_yday; /* 一年中的第几天,范围从 0 到 365 */
int tm_isdst; /* 夏令时 */
};
注意:
-
int tm_mon; 获取的值要加1是正确的月份
-
int tm_year; 获取的值加1900是正确的年份
获取文件内的所有行数量:
while(fgets(buf,32,fp)!=NULL){
if(buf[strlen(buf)-1] =='\n'){ //注意判断是否是一行结束
linecount++;
}
}
-
写完文件记得fflush ,写到磁盘里面去。
-
标准IO磁盘文件的缓冲区一般为4096
-
注意和标准输出的全缓冲区别,标准输出是1024
8. 文件IO(概念、打开、读、写、关闭)
-
文件IO的概念:posix(可移植操作系统接口)定义的一组函数
-
Linux下, 标准IO基于文件IO实
-
文件IO不提供缓冲机制
-
标准IO和文件IO调用函数对比
-
文件IO的API:open close read read
-
文件描述符(file descriptor):每个打开的文件都对应一个文件描述符。文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符。文件描述符从0开始分配,依次递增。
文件IO 打开
-
int open(const char *pathname, int flags); 不创建文件
-
int open(const char *pathname, int flags, mode_t mode); 创建文件,不能创建设备文件
原型 | int open(const char *pathname, int flags, mode_t mode); | ||
参数 | pathname | 被打开的文件名(可包括路径名)。 | |
flags | O_RDONLY:只读方式打开文件。 | 这三个参数互斥 | |
O_WRONLY:可写方式打开文件。 | |||
O_RDWR:读写方式打开文件。 | |||
O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三的参数为其设置权限。 | |||
O_EXCL:如果使用O_CREAT时文件存在,则可返回错误消息。这一参数可测试文件是否存在。 | |||
O_NOCTTY:使用本参数时,如文件为终端,那么终端不可以作为调用open()系统调用的那个进程的控制终端。 | |||
O_TRUNC:如文件已经存在,那么打开文件时先删除文件中原有数据。 | |||
O_APPEND:以添加方式打开文件,所以对文件的写操作都在文件的末尾进行。 | |||
mode | 被打开文件的存取权限,为8进制表示法。 |
-
成功时返回文件描述符;出错时返回EOF
-
真正建文件时的权限会受到umask 值影响,实际权限是mode-umaks
-
可以打开设备文件,但是不能创建设备文件
-
umask概念:umask 用来设定文件或目录的初始权限
-
文件或目录的初始权限 = 文件或目录的最大默认权限 - umask权限
文件的关闭
-
int close(int fd)#include <unistd.h>
-
关闭后文件描述符不能代表文件
文件IO的读写和定位
-
ssize_t read(int fd,void *buf, size_t count); #include <unistd.h>
-
成功时返回实际读取的字节数;出错时返回EOF;读到文件末尾时返回0
-
count不应超过buf大小
文件IO的写
-
ssize_t write(int fd,void *buf, size_t count); #include <unistd.h>
-
成功时返回实际写入的字节数;出错时返回EOF
文件IO的定位
-
off_t lseek(int fd, off_t offset, intt whence);#include <unistd.h>
9. 目录操作和文件属性获取
打开目录
-
DIR *opendir(const char *name); #include<dirent.h>
-
DIR *fdopendir(int fd); #include<dirent.h>
-
使用文件描述符,要配合open函数使用
-
DIR是用来描述一个打开的目录文件的结构体类型
-
成功时返回目录流指针;出错时返回NULL
读取目录
-
struct dirent *readdir(DIR *dirp); #include <dirent.h>
-
struct dirent是用来描述目录流中一个目录项的结构体类型
-
包含成员char d_name[256]
-
成功时返回目录流dirp中下一个目录项;
-
出错或到末尾时时返回NULL
关闭目录
-
closedir函数用来关闭一个目录文件
-
int closedir(DIR *dirp); #include <dirent.h>
-
成功时返回0;出错时返回EOF
修改文件权限
-
chmod/fchmod函数用来修改文件的访问权限
-
int chmod(const char*path,mode_t mode); #include<sys/stat.h>
-
int fchmod(int fd, mode_t mode); #include <sys/stat.h>
-
成功时返回0;出错时返回EOF
-
注意:在vmware和windows共享的文件夹下,有些权限不能改变。
获取文件属性
-
int stat(const char *path,struct stat *buf); #include <sys/stat.h>
-
int lstat(const char *path,struct stat *buf);#include <sys/stat.h>
-
int fstat(int fd, struct stat *buf); #include <sys/stat.h>
-
成功时返回0;出错时返回EOF
-
如果path是符号链接stat获取的是目标文件的属性;而lstat获取的是链接文件的属性
10. 库的使用
-
库的概念:库是一个二进制文件,包含的代码可被程序调用
-
Windows和Linux下库文件的格式不兼容
-
Linux下包含静态库和共享库
/lib /usr/lib
静态库
-
静态库特点
-
编译(链接)时把静态库中相关代码复制到可执行文件中
-
程序中已包含代码,运行时不再需要静态库
-
程序运行时无需加载库,运行速度更快
-
占用更多磁盘和内存空间
-
静态库升级后,程序需要重新编译链接
-
-
创建静态库步骤:
-
1 . 编写库文件代码,编译为.o 目标文件。
-
2. ar 命令 创建 libxxxx.a 文件
-
ar -rsv libxxxx.a xxxx.o
-
ar 参数:
c 禁止在创建库时产生的正常消息 r 如果指定的文件已经存在于库中,则替换它 s 无论 ar 命令是否修改了库内容都强制重新生成库符号表 v 将建立新库的详细的逐个文件的描述写至标准输出 q 将指定的文件添加到库的末尾 t 将库的目录写至标准输出
-
-
静态库名字要以lib开头,后缀名为.a
-
没有main函数的.c 文件不能生成可执行文件。
-
nm:显示指定文件中的符号信息 -a 显示所有符号
链接静态库:
-
gcc -o 目标文件 源码.c -L路径 -lxxxx
-L 表示库所在的路径
-l 后面跟库的名称(lib之后的名称)
动态库
-
共享库特点
-
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
-
程序不包含库中代码,尺寸小
-
多个程序可共享同一个库
-
程序运行时需要加载库
-
库升级方便,无需重新编译程序
-
使用更加广泛
-
-
动态库的生成步骤:
-
1. 生成与位置无关代码的目标文件
-
gcc -c -fPIC xxx.c xxxx.c ....
-
-
2. 生成动态库
-
gcc -shared -o libxxxx.so xxx.o xxx.o ....
-
-
3. 编译可执行文件
-
gcc -o 目标文件 源码.c -L路径 -lxxxx
-
-
-
为了让系统能找到要加载的共享库,有三种方法 :
-
把库拷贝到/usr/lib和/lib目录下
-
在LD_LIBRARY_PATH环境变量中添加库所在路径(export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:你的动态库目录)
-
添加/etc/ld.so.conf.d/*.conf文件,执行ldconfig刷新
-
-
查看可执行文件使用的动态库:ldd 命令 : ldd 你的可执行文件
-
静态库和动态库的区别
-
1.静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”。
-
2、静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行;动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。
-