一、标准IO介绍及缓冲区
1.文件
文件概念:
一组相关数据的有序集合
Linux下一切皆文件
文件类型:
常规文件 r
目录文件 d
字符设备文件 c
块设备文件 b U盘
管道文件 p
套接字文件 s
符号链接文件 l
系统调用:利用操作系统提供接口,将程序结果打印至显示器
库函数:
2.流
FILE流是一个结构体
流的缓冲类型:
全缓冲:流的缓冲区无数据或无空间时才执行实际I/O操作 全缓冲的大小是1025
行缓冲:当在输入和输出中遇到换行符('\n')时,进行I/O操作,当流和一个终端关联 时,典型的行缓冲
无缓冲:数据直接写入文件,流不进行缓冲
printf("hello world") while(1){ sleep(1); } //此时没有输出 printf("hello world\n") while(1){ sleep(1); } //此时输出hello world,因为行缓冲识别\n
标准I/O stdin,stdout,stderr
stdin:标准输入流
stdout:标准输出流
stderr:标准错误流
3.文件的打卡、关闭和代码实现
文件的打开:打开就是占用资源
文件的关闭 :关闭就是释放资源
下列函数可用于打开一个标准I/O流:
FILE *fopen(const char *path,const char *mode) //mode模式
成功时返回流指针;出错时返回NULL
//path:普通文件当前路径不需要加目录,其他要使用完整的路径
//mode:
//返回值:出错返回NULL,所以fopen函数必须判断是否为空
文件打开的模式(非常重要)
![]()
标准I/O——处理错误信息(perror strerror)
格式:void perror(const char*s)
char *strerror(int errno)
perror库函数 头文件stdio.h
strerror库函数头文件 errno.h string.h
fclose()函数的入参strea必须保证为空,否则出现段错误
4.标准IO的读写
①单个字符读写
流支持不同的读写方式:
读写一个字符:fgetc()/fputc()一次读/写一个字符
读写一行:fgets()和fputs()一次读/写一行
读写若干个对象:fread()/fwrite()每次读/写若干个对象,而每个对象具有相同的长度
输入字符
#include <stdio.h> int fgetc(FILE *steam); //是一个函数 int getc(FILE *stream); //是一个宏定义 int getchar(void); //只能读取标准输入(键盘)的内容,不能读取文件内容 为了既能读取有符号字符和无符号字符,所以使用int类型 成功时返回读取的字符;若到文件末尾或出错时返回FOF(-1); getchar()等同于fgetc(stdin) getc和fgetc区别是一个是宏一个是函数
注意:1.以上函数返回值时int类型不是char类型,主要是为了扩展返回值的范围,stdin是
FILE *的指针,是系统定义好的,指向的是标准输入(键盘输入);
2.打开文件后读取是从文件开头开始读。读完一个后读写指针(定义在FILE结构体中的)会后移,关闭fp后想继续读取需要重新打开,且打开后从头开始计算位置
3.调用getchar时编译运行时会阻塞,等待键盘输入
按字符输出
#include <stdio.h> int fputc(int c,FILE *stream); int putc(intc, FILE *stream); int putchar(int c); 成功时返回写入的字符;出错时返回EOF putchar(c)等同于fputc(c,stdout)
注意:返回参数和输入参数是int类型
e.g.
#include <stdio.h> int main(int argc, const char *argv[]) { FILE *fp; int rec; fp = fopen("1.txt","r"); if (fp == NULL) { perror("fopen"); return 0; } rec = fgetc(fp); if (rec == -1) { perror("fgetc"); fclose(fp); return 0; } printf("get char =%c\n",rec); fclose(fp); //注意:关闭fp后想继续读取需要重新打开,且打开后从头开始计算位置 fp = fopen("1.txt","r"); if (fp == NULL) { perror("fopen"); return 0; } rec = fgetc(fp); printf("get char =%c\n",rec); rec = fgetc(fp); printf("get char =%c\n",rec); rec = fgetc(fp); printf("get char =%c\n",rec); return 0; }
②按行输入输出
按行输入
#include <stdio.h>
char *gets(char *s):读取标准输入到缓冲区s
char *fgets(char *s,int size, FILE *stream)
成功时返回s,到文件末尾或出错时返回NULL
遇到'\n'或已输入size-1个字符时返回,总是包含'0'
注意事项:
1.gets函数已经被淘汰,因为会导致缓冲区溢出
2.fgets函数第二个参数,输入的数据超出size,size-1个字符会保存到缓冲区,最后 添加'\0',如果输入数据少于size-1,后面会添加换行符
按行输出
#include <stdio.h>
int puts(const char *s); --stdout
int fputs(const char *s, FILE *stream); ---可以输出到屏幕也可以输出到文件里
成功时返回非负整数;出错时返回EOF
puts将缓冲区s中的字符串输出到stdout,并追加'\n'
fputs将缓冲区s中的字符串输出到stream,不追加'\n'
③.二进制读写
文本文件和二进制文件的区别:
存储的格式不同:文本文件只能存储文本
标准IO-按对象读写:
#include <stdio.h>
size_t fread(void *ptr, size_t size,size_t n,FILE *fp);
size_t fwrite(const void *ptr, size_t size,size_t n,FILE *fp);
——void *ptr读取内容放的位置指针;
size_t size读取得块大小;
size_t n读的个数;
FILE *fp读取得文件指针
const void *ptr写文件内容得位置指针
成功返回读写的对象个数;出错时返回EOF,
既可以读写文本文件,也可以读写数据文件
效率高
PS: 文件写完后文件指针指向文件末尾,如果这个时候读,读不出内容
解决方法:移动文件指针,或者关闭文件后重新打开。
#include <stdio.h> #include <stdlib.h> #include <string.h> struct student{ char name[16]; int age; char sex[8]; }; int main(int argc, const char *argv[]) { FILE *fp; int ret; struct student stu1; struct student stu2; struct student stu3; struct student stu4; fp = fopen("1.bin","w"); if(fp == NULL) { perror("fopen"); return 0; } strcpy(stu1.name,"zhangsan"); stu1.age = 61; strcpy(stu1.sex,"male"); strcpy(stu2.name,"lisi"); stu2.age = 55; strcpy(stu2.sex,"male"); fwrite(&stu1,sizeof(stu1),1,fp); if(ret == -1){ perror("fwrite"); goto end; }else{ printf("write struct student success!\n"); } fwrite(&stu2,sizeof(stu2),1,fp); if(ret == -1){ perror("fwrite"); goto end; }else{ printf("write struct student success!\n"); } fclose(fp); fp = fopen("1.bin","r"); if(fp == NULL) { perror("fopen"); return 0; } ret = fread(&stu3,sizeof(stu1),1,fp); if(ret == -1){ perror("fread"); goto end; } ret = fread(&stu4,sizeof(stu2),1,fp); if(ret == -1){ perror("fread"); goto end; } printf("name=%s,age=%d,sex=%s\n",stu3.name,stu3.age,stu3.sex); printf("name=%s,age=%d,sex=%s\n",stu4.name,stu4.age,stu4.sex); end: fclose(fp); return 0; }
5.流刷新定位
fflush(流刷新)
#include <stdio.h> int fflush(FILE *fp) //成功时返回0;出错时返回EOF //将流缓冲区中的数据写入实际的文件 //Linux下只能刷新输出缓冲区,输入缓冲区丢弃 //如果输出到屏幕使用fflush(stdout)
#include <stdio.h> #include <unistd.h> int main(int argc,char *argv[]){ // printf("abcdefg"); // fflush(stdout); FILE *fp; fp=fopen("1.txt","w"); if(fp==NULL){ perror("fopen"); return 0; } fwrite("abcdef",7,1,fp); fflush(fp); while(1){ sleep(1); } }
定位流——ftell/fseek/rewind
#include <stdio.h> long ftell(FILE *stream) long fseek(FILE *stream, long offset, int whence); void rewind(FILE *stream); //ftell()成功时返回流的当前读写位置,出错时返回EOF //fseek()定位一个流,成功时返回0,出错时返回EOF //fseek参数 whence参数:SEEK_SET/SEEK_CUR/SEEK_END SEEK_SET:从距头文件开头offset位移量为新的读写位置 SEEK_CUR:以目前的读写位置往后增加offset个位移量 SEEK_END:将读写位置指向文件尾后再增加offset个位移量 //offset参数:偏移量,可正可负
PS:1.打开a模式fseek无效
2.rewind(fp)相当于fseek(fp,0,SEEK_SET);
3.这三个函数只适用2G一下的文件
#include <stdio.h> int main(int argc,char *argv[]){ FILE *fp; fp=fopen("1.txt","w"); if(fp==NULL){ perror("fopen"); return 0; } fwrite("abcdef",6,1,fp); printf("current fp=%d\n",(int)ftell(fp)); // fseek(fp,3,SEEK_SET); rewind(fp); printf("After rewind fp=%d\n",(int)ftell(fp)); fwrite("vvv",3,1,fp); }
6.格式化输入输出
格式化输出
#include <stdio.h> int fprintf(FILE *stream, const char * fmt, ...); int sprintf(char *s, const char *fmt, ...); //成功时返回输出的字符个数;出错时返回EOF
#include "stdio.h" int main(int argc,char *argv[]){ char buf[100]={0}; int year=2021; int month= 10; int day=1; int syear; int smonth; int sday; sprintf(buf,"%d-%d-%d",year,month,day); printf("%s\n",buf); sscanf(buf,"%d-%d-%d",&syear,&smonth,&sday); printf("%d,%d,%d\n",syear,smonth,sday); }
#include "stdio.h" int main(int argc,char *argv[]){ FILE *fp; int year=2021; int month=10; int day=1; fp=fopen("ftest.txt","w"); if(fp==NULL){ perror("fopen"); return 0; } fprintf(fp,"%d-%d-%d\n",year,month,day); fclose(fp); }
格式化输入
int fscanf(FILE *stream, const char *format, ...); int sscanf(const char *str,const char *format, ...);
#include "stdio.h" int main(int argc,char *argv[]){ FILE *fp; int year; int month; int day; fp=fopen("ftest.txt","r"); if(fp==NULL){ perror("fopen"); return 0; } fscanf(fp,"%d-%d-%d",&year,&month,&day); printf("%d,%d,%d\n",year,month,day); fclose(fp); }
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
#include <stdio.h> #include <time.h> #include <unistd.h> #include<string.h> int main(int argc, const char *argv[]) { FILE *fp; time_t ctime; struct tm *ctimestr; int linecount = 0; char buf[32]; fp = fopen("test.txt","a+"); if (fp == NULL) { perror("fopen"); return 0; } while(fgets(buf,32,fp) != NULL){ if(buf[strlen(buf)-1] == '\n'){ linecount++; } } while(1){ ctime = time(NULL); //printf("ctime=%d\n",(int)ctime); ctimestr = localtime(&ctime); printf("%04d-%02d-%02d %02d:%02d:%02d\n",ctimestr->tm_year+1960,ctimestr->tm_mon+1,ctimestr->tm_mday,ctimestr->tm_hour,ctimestr->tm_min,ctimestr->tm_sec); fprintf(fp,"%d,%04d-%02d-%02d %02d:%02d:%02d\n",linecount,ctimestr->tm_year+1960,ctimestr->tm_mon+1,ctimestr->tm_mday,ctimestr->tm_hour,ctimestr->tm_min,ctimestr->tm_sec); fflush(fp); linecount++; sleep(1); } fclose(fp); return 0; }
8.文件IO(系统IO/系统调用)
文件描述符fd
每个打开的文件都对应一个文件描述符;
文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符
文件描述符从0开始分配,依次递增
文件IO操作通过文件描述符来完成
0,1,2的含义?
文件IO的概念:
又称系统IO、系统调用,是操作系统提供的API接口函数
文件IO的API
open close read write
文件描述符概念
是0-1023的数字,表示文件。
0,1,2的含义是标准输入,标准输出和错误
文件IO-open
#include <fcntl.h> int open(const char *pathname,int flags); int open(const char *pathname,int flags,mode_t mode); //成功返回文件描述符;出错时返回EOF //打开文件时用使用两个参数,使用第一个,如果文件不存在需要创建文件,此时用第三个参数指定新文件的权限,(只有在建立新文件时有效)此外真正建文件时的权限会收到umask值影响,实际权限时mode-umaks //可以打开设备文件,但是不能创建设备文件(创建设备mknode驱动部分会讲)
![]()
与标准IO对比
![]()
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc,char *argv[]){ int fd; int ret; fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC, 0666); if(fd<0){ printf("open file err\n"); return 0; } printf("sucess,fd=%d\n",fd); ret= close(fd); if(ret<0){ printf("close failed\n"); } ret=close(fd); printf("ret=%d\n",ret); }
文件或目录的初始权限 = 文件或目录的最大默认权限 - umask权限
文件IO-close
close函数用来关闭一个打开的文件:
#include <unistd.h>
int close(int fd);
成功返回0;出错时返回EOF(关闭一个不存在文件时);
程序结束时自动关闭所有打开的文件;
文件关闭后,文件描述符不再代表文件;
四、静态库的使用
1、静态库
优点:
编译(链接)时把静态库中相关代码复制到可执行文件中
程序中已包含代码,运行时不再需要静态库
程序运行时无需加载库,运行速度更快缺点:
占用更多磁盘和内存空间
静态库升级后,程序需要重新编译链接创建静态库步骤:
1. 编写库文件代码,编译为.o 目标文件。
2. ar 命令 创建 libxxxx.a 文件
ar -rsv libxxxx.a xxxx.o
注意:1 静态库名字要以lib开头,后缀名为.a
2 没有main函数的.c 文件不能生成可执行文件。ar 参数:
c 禁止在创建库时产生的正常消息
r 如果指定的文件已经存在于库中,则替换它
s 无论 ar 命令是否修改了库内容都强制重新生成库符号表
v 将建立新库的详细的逐个文件的描述写至标准输出
q 将指定的文件添加到库的末尾t 将库的目录写至标准输出
ar -t libhello.a 查看库文件由谁生成
链接错误:
test.c:(.text+0x15):对‘hello’未定义的引用
collect2: error: ld returned 1 exit status
含义:表示hello函数在编译的源码内没有找到实现
解决:实现代码或者找到对应函数的库并且链接它。链接静态库:
gcc -o 目标文件 源码.c -L路径 -lxxxx
-L 表示库所在的路径
-l 后面跟库的名称举例:
编写应用程序test.c
#include <stdio.h>
void hello(void);
int main() {
hello();
return 0;
}编译test.c 并链接静态库libhello.a
$ gcc -o test test.c -L. -lhello (-L表示路径,后面加路径,. 代表当前路径, -l加文件名称表示链接的文件)
$ ./test
hello world
静态库将所有的代码打包到可执行文件中,不依赖库文件。单独的可执行文件仍然可以运行。
2、动态库(共享库)
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
程序不包含库中代码,尺寸小
多个程序可共享同一个库
程序运行时需要加载库
库升级方便,无需重新编译程序
使用更加广泛
动态库的生成步骤:
1. 生成位置无关代码的目标文件 (因为动态库可能加载到任何位置)
gcc -c -fPIC xxx.c xxxx.c ....
2. 生成动态库
gcc -shared -o libxxxx.so xxx.o xxx.o ....3. 编译可执行文件
gcc -o 目标文件 源码.c -L路径 -lxxxx
执行动态库的可执行文件错误 ./test: error while loading shared libraries: libmyheby.so: cannot open shared object file: No such file or directory 含义:可执行文件所使用的动态库找不到 解决办法: 找到动态库,添加到/usr/lib里面 或者使用export LD_LIBRARY_PATH=$LD_LIBRARY_PATH=:你的动态库目录 添加在~/.bashrc 文件里面 使用source ~/.bashrc 生效。 查看可执行文件使用的动态库: ldd 命令 : ldd 你的可执行文件
root@haas-virtual-machine:/mnt/hgfs/share/newIOP# ldd test
linux-vdso.so.1 => (0x00007fff6548d000)
libmyheby.so => /mnt/hgfs/share/newIOP/day5/libmyheby.so (0x00007f5c89521000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5c89144000)
/lib64/ld-linux-x86-64.so.2 (0x000055fe52211000)root@haas-virtual-machine:/mnt/hgfs/share/newIOP/day5# ldd test
linux-vdso.so.1 => (0x00007ffcb652c000)
libmyheby.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbeeffaf000)
/lib64/ld-linux-x86-64.so.2 (0x0000561003c3b000)