文件IO.

一、标准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)
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值