基础I/O 1

目录

1、c语言文件接口

1.1 fopen:

1.2 fclose

1.3 fwrite

1.4 fread

1.5 fseek

2、系统调用的文件接口

2.1 open

 2.1 write

2.3 read

2.4 lseek

2.5 close

2.6在调用读接口时,参数count预留一个“\0”的位置。

3.文件描述符

3.1文件描述符:文件描述符就是一个小正整数(没有负数)

3.2 内核理解文件描述

 3.3 文件流指针和文件描述符的区别

3.2.1.从源码角度理解文件流指针(FILE*)

 3.3.2.将文件流指针和文件描述符联系起来

4.重定向

4.1 重定向的符号

 4.2 重定向的接口

 4.3从内核角度理解重定向

5.静态库&动态库

5.1动态库

 5.2 静态库

6软硬连接文件

6.1软连接文件

 6.2硬链接文件


1、c语言文件接口

1.1 fopen:

FILE *fopen(const char *path,const char *mode) ;

path :带有路径的文件名称,(如果说不带有路径,打开的文件可以是在当前路径下)

mode :打开文件的方式

r :只读模式打开,文件流指向了文件起始位置。

r+:以可读可写的方式打开,文件流是指向了文件起始位置。

w:以只写方式打开,当前文件如果存在,则截断(清空文件内容)文件,当前文件不存在,则创建一个新文件。

w+:以可读可写的方式打开,当前文件如果存在,则截断(清空文件内容)文件;当前文件不存在,则创建一个新文件。

a:追加写,如果当前文件不存在,则创建文件如果;当前文件存在,则将文件流指针指向文件末尾进行写。

a+:可以读,也可以追加写,如果文件不存在,则创建文件,读的位置被初始化到文件的头,但是追加的写的时候,从文件的末尾开始追加。

返回值:

成功:返回文件流指针FILE*。

失败:返回NULL。

erron:系统当中的一个错误码,当我们调用一个函数的时候,如果该函数出错了,则会给errno赋值成为对应的错误码(整形),即errno-->error msg。perror 会将error msg打印出来:perror会直接去拿errno当中的值进行解析,解析完毕之后,进行打印。

 代码验证:

#include <stdio.h>

int main(){
   FILE* fp=fopen("file.txt","r");
   if(NULL == fp){
       printf("fopen open filr failed\n");
       perror("fopen:");
       return 0;
   }
   //文件打开成功
   printf("fopen open file success\n");
  return 0;
}

以r方式打开,要打开的文件存在

 以w方式打开,要打开的文件不存在,则文件会被创建出来

1.2 fclose

int fclose(FILE *fp);

关闭文件流(关闭文件)

fp :文件流指针

如果不关闭文件流指针,会造成内存泄漏,当文件句柄持续泄露的时候,最终进程就不能在打开新的文件了。

内存泄露:导致进程占用的内存资源过多,如果持续泄露,操作系统是没有办法满足进程申请内存资源的请求,操作系统就会强杀进程。

1.3 fwrite

size_t fwrite(const void *ptr, size_t size, size_t  nmemb, FILE *stream);

参数:

ptr:想往文件当中写什么内容。

size:定义往文件当中写的时候,一个块是多大,单位字节。

nmemb :期望写多少块。

stream:文件流指针。

例如:size设置为10,nmemb设置为10,即期望写10块(期望写100字节),但是,到底能不能写10块(能不能写100字节),取决于ptr指向的内容到底有没有100字节。

返回值:

返回成功写入到文件块的个数。

通常情况,代码调用时,这样设置:

size : 1 (1个块的大小是1字节)

nmemb :块的个数(期望写多少字节)

返回值:成功写入到文件当中的块的个数(成功写入到文件当中的字节数量)

#include <stdio.h>
#include <string.h>

int main(){
   FILE* fp=fopen("file.txt","w");
   if(NULL == fp){
       printf("fopen open filr failed\n");
       perror("fopen:");
       return 0;
   }
   //文件打开成功  
   printf("fopen open file success\n");
   const char* content="have a good day!";
   size_t w_size=fwrite(content,1,strlen(content),fp);
   printf("expect w_size=%ld\n",w_size);
   fclose(fp);
  return 0;
}

1.4 fread

size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);

参数:

ptr:将从文件当中读到的内容保存到ptr指向的内存空间当中,这个内存空间一定是要合法的(程序员自己申请的)。

size:定义从文件当中读的时候,一个块是多大,单位字节(通常定义为一个字节)。

nmemb :期望读多少块。

stream:文件流指针。

返回值:

返回成功读入的文件块的个数。

以w+方式打开,且file.txt中已有内容:

#include <stdio.h>
#include <string.h>

int main(){
   FILE* fp=fopen("file.txt","w+");
   if(NULL == fp){
       printf("fopen open filr failed\n");
       perror("fopen:");
       return 0;
   }
   //文件打开成功 
   printf("fopen open file success\n");

   //测试读
   char buf[1024];
   size_t r_size=fread(buf,1,sizeof(buf)-1,fp);
   printf("r_size=%ld\n",r_size);
   printf("buf=%s\n",buf);
   fclose(fp);
  return 0;
}

以w+方式打开,原文件中的内容被清空,读不到任何内容

 以r 或r+ 或 a+方式打开,就可以读到

 读写一起:

#include <stdio.h>
#include <string.h>

int main(){
   FILE* fp=fopen("file.txt","w+");
   if(NULL == fp){
       printf("fopen open filr failed\n");
       perror("fopen:");
       return 0;
   }
   //文件打开成功 
   printf("fopen open file success\n");
   //测试写
   const char* content="have a good day!";
   size_t w_size=fwrite(content,2,8,fp);
   printf("expect w_size=%ld\n",w_size);
   
   //测试读
   char buf[1024];
   size_t r_size=fread(buf,1,sizeof(buf)-1,fp);
   printf("r_size=%ld\n",r_size);
   printf("buf=%s\n",buf);
   fclose(fp);
  return 0;
}

 由于文件流指针在经过写操作之后,已经指向文件末尾,所以再进行读的时候,无法读到文件中的内容。(此时需要借助fseek)

1.5 fseek

 int fseek(FILE *stream, long offset, int whence);
作用:移动文件流指针的位置

参数:

stream:文件流指针

offset :偏移量

whence :将文件流指针偏移到什么位置(SEEK_SET:文件头部SEEK_CUR:当前文件流指针的位置SEEK_END:文件末尾)。

返回值: 成功:0 ;失败:-1。

#include <stdio.h>
#include <string.h>

int main(){
   FILE* fp=fopen("file.txt","w+");
   if(NULL == fp){
       printf("fopen open filr failed\n");
       perror("fopen:");
       return 0;
   }
   //文件打开成功 
   printf("fopen open file success\n");
   //测试写
   const char* content="have a good day!";
   size_t w_size=fwrite(content,2,8,fp);
   printf("expect w_size=%ld\n",w_size);
   
   //重置文件流指针
   fseek(fp,0,SEEK_SET);
   //测试读
   char buf[1024];
   size_t r_size=fread(buf,1,sizeof(buf)-1,fp);
   printf("r_size=%ld\n",r_size);
   printf("buf=%s\n",buf);
   fclose(fp);
  return 0;
}

2、系统调用的文件接口

2.1 open

lint open(const char *pathname,int flags);

int open(const char *pathname,int flags, mode_t mode);

参数:

pathname:要打开或创建的目标文件

flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags:

O_RDONLY:只读打开

O_WRONLY :只写打开

O_RDWR︰读,写打开

上述三个常量,必须指定一个且只能指定一个。

O_CREAT∶若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限。

O_APPEND:追加写

例如:O_RDWR | O_CREAT(这些参数选项是采用位图的方式进行运算的)

mode: 当创建以文件的时候,指定新创建文件的权限,传递一个8进制的数字(例如,0664)

返回值:

成功:返回新打开的文件描述符  ;失败:-1。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd=open("file.txt",O_RDWR,0664);
    if(fd<0){
     perror("open:");
     return 0;
    }
     
    //打开成功
    printf("fd=%d\n",fd);
    return 0;
}

参数flags改为O_RDWR | O_CREAT之后,若文件不存在,则创建它:

 2.1 write

 ssize_t write(int fd,const void *buf,size_t count);

参数:

fd:文件描述符

buf :将buf指向的内容写到文件当中去

count:期望写多少字节

返回值:

返回写入的字节数量

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd=open("file.txt",O_RDWR|O_CREAT,0664);
    if(fd<0){
     perror("open:");
     return 0;
    }
     
    printf("fd=%d\n",fd);
    //打开成功
    
    const char buf[]="helllo wold!";
    ssize_t w_size=write(fd,buf,strlen(buf));
    printf("w_size=%ld\n",w_size);
    close(fd);
    return 0;
}

2.3 read

ssize_t read (int fd, void *buf,size_t count) ;

参数:

fd:文件描述符

buf:将从文件当中读到的内容写到buf指向的空间当中去(提前进行开辟的空间)

count:期望读多少字节

返回值:

返回读到的字节数量

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd=open("file.txt",O_RDWR|O_CREAT,0664);
    if(fd<0){
     perror("open:");
     return 0;
    }
     
    printf("fd=%d\n",fd);
    //打开成功
   //写
   
    const char buf1[]="helllo wold!";
    ssize_t w_size=write(fd,buf1,strlen(buf1));
    printf("w_size=%ld\n",w_size);
    
    //读
    char buf[1024]={0};
    ssize_t r_size=read(fd,buf,sizeof(buf));
    printf("r_size=%ld buf=%s",r_size,buf);
    close(fd);
    return 0;
}

需要借助lseek函数才能将写的内容读出来。

2.4 lseek

off_t lseek (int fd  ,off_t offset, int whence) ;

参数:

fd:文件描述符。

offset:偏移量,单位字节。

whence:偏移的位置,SEEK_SET:文件头部;SEEK_CUR:当前文件流指针的位置;SEEK_END:文件末尾。

返回值:

成功:返回偏移的位置,单位字节。

失败:-1

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd=open("file.txt",O_RDWR|O_CREAT,0664);
    if(fd<0){
     perror("open:");
     return 0;
    }
     
    printf("fd=%d\n",fd);
    //打开成功
   //写
   
    const char buf1[]="helllo wold!";
    ssize_t w_size=write(fd,buf1,strlen(buf1));
    printf("w_size=%ld\n",w_size);
    
    lseek(fd,0,SEEK_SEET);
    //读
    char buf[1024]={0};
    ssize_t r_size=read(fd,buf,sizeof(buf));
    printf("r_size=%ld buf=%s",r_size,buf);
    
    close(fd);
    return 0;
}

2.5 close

 int close(int fd);

关闭文件描述符

2.6在调用读接口时,参数count预留一个“\0”的位置。

 在字符数字当中预留\0的位置,防止后续在访问的时候,越界访问,导致程序崩溃。

3.文件描述符


3.1文件描述符:文件描述符就是一个小正整数(没有负数)

1.打印观察文件描述符的值,查看/proc/[pid]/fd 文件夹下的文件描述符信息。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    int fd=open("file.txt",O_RDWR|O_CREAT,0664);
    if(fd<0){
        perror("open");
        return 0;
    }
    while(1){
        sleep(1);
    }
    return 0;
}


 
2.文件描述符的分配规则;最小未使用原则

假设1被关闭,现在又建了一个新的文件,则新文件的文件描述符就是1。

代码验证: 

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
    //关闭标准输入
    close(1);
    
    //等待30秒
    int count=30;
    while(count--){
        sleep(1);
    }
    
    //调用open函数创建文件
    int fd=open("file.txt",O_RDWR|O_CREAT,0664);
    if(fd<0){
        perror("open");
    }
    //不让程序退出,以便查看fd信息
    while(1){
        sleep(1);
    }
    return 0;
}

3.2 内核理解文件描述

1.从task_ struct的角度理解文件描述符在内核当中是什么

扩展:一个进程最多打开多少个文件描述符?

ulimit -a 查看:

 open files决定了一个进程能够打开的文件描述符的数量

1.系统当中针对一个进程打开的文件描述符数量是有限制的。
2.通过ulimit -a可以查看到“open files”的大小, 而“open files”的大小就是限制一个进程打开的文件描述符数量。
3.我的机器当中看到的“open files”的大小是100001, 也就是以为着,我的机器创建出来的进程最大打开的文件数量是100001。
4."open files" 的大小并不是没有办法进行改变的, 是可以通过ulimit -n [count]进行改变。
例如:ulimit -n 10000, 就相当于将一个进程打开文件描述符的数量,改成了10000。

 3.3 文件流指针和文件描述符的区别

3.2.1.从源码角度理解文件流指针(FILE*)

在文件/usr/include/stdio.h可以查看

使用struct _IO_FILE同样可以打开文件

而struct _IO_FILE结构体在文件/usr/include/libio.h可以查看

FILE本质上就是struct _ IO_ FILE {... }这个结构体

struct _IO_FILE {...}:

 

通过struct _IO_FILE获取文件描述符fd

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(){
    struct _IO_FILE* fp=fopen("1.txt","w+");
    if(NULL == fp){
        perror("fopen");
    } 

    printf("fopen success!\n");
    const char* content="hello";
    fwrite(content,1,strlen(content),fp);
    printf("_fileno=%d\n",fp->_fileno);
    while(1){
        sleep(1);
    }
    fclose(fp);
    return 0;

}

 3.3.2.将文件流指针和文件描述符联系起来

>1.读写缓冲区的问题
struct _ I0_ FILE { .... }这个结构体是C标准库当中的结构体,而该结构体当中维护的读写缓冲区,就是在进程终止那谈到的exit函数会刷新缓冲区的那个缓冲区。

>2.文件流指针对应的结构体struct_I0_FILE这个结构体内部的成员变量int _fileno保存了对应的文件描述符的数值。

4.重定向

4.1 重定向的符号

> : 清空重定向

>> :追加重定向

 4.2 重定向的接口

int dup2(int oldfd ,int newfd);

作用:将newfd的值重定向为oldfd,即newfd拷贝oldfd;

参数:oldfd和newfd均为文件描述符

成功:1、关闭newfd;2.让newfd指向oldfd对应的struct file*结构体:

失败:1.如果oldfd是一个非法/无效的文件描述符,则重定向失败,newfd没有变化;2.如果newfd和oldfd值相等,则什么是都不干。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
   int fd=open("1.txt",O_RDWR|O_CREAT,0664);
   if(fd<0){
       perror("open:");
       return 0;
   }

   //重定向
   dup2(fd,1);
   printf("beautiful!\n");
   return 0;
}

 4.3从内核角度理解重定向

 

5.静态库&动态库

什么是库:静态库和动态库都是程序代码(二进制文件)的集合。一 般为了方便将程序提供给第三方使用,就是 将程序编写成为库文件提供给第三方(用户)使用。好处:不会泄漏公司的源码。调用者不必关心内部实现,只需要关注如何使用(如何调用)即可。

5.1动态库

特征:
win:没有前缀,后缀为dll


linux:前缀为lib, 后缀为.so

 
生成:
使用gcc/g++编译器,增加两个命令行参数
-fPIC 
-shared 
生成动态库的代码当中不需要包含main函数(程序入口函数)

#include "../dyn_lib/print.h"
int main(){
    Print();
    return 0;
}

 使用:

编译可执行程序的时候,依赖动态库

 -L [path(指定动态库所在的路径)]

-l[动态库的名称 (丢掉前缀和后缀之后的名称) ] :指定编译可执行程序时,依赖的动态库是那个

 

都有哪些方式让程序可以找到动态库:
1.将动态库放到可执行程序的路径下(不推荐)

 
2.配置LD LIBRARY PATH
动态库的环境变量:LD_ LIBRARY_ PATH


推荐大家的方式,公司当中产品在客户机器部署的时候,常用的手段
3.放到系统库的路径下: /lib64 (极力不推荐) 

 5.2 静态库

特征:
win: 没有前缀,后缀为. lib 
linux :前缀为lib,后缀为. a
生成: 
第一阶段:使用gcc/g++将源代码编译成为目标程序(必须是.o文件)
第二阶段:使ar -rc命令编译目标程序成为静态库
ar -rc [静态库文件名称] [目标程序]

 如果在编译可执行程序的时候,使用到了静态库,则该静态库会被编译到可执行程序当中。并没有可执行程序依赖动态库动态库的问题。

 

6软硬连接文件

6.1软连接文件

软连接:目标文件的快捷方式

生成:
1n -s [源文件] [软连接文件]


注意事项: 
1.修改软连接文件,源文件也会被修改

源文件(test.c):

修改软连接文件(ln_test.c):

修改后,源文件(test.c):

2.源文件如果被删除,软连接文件还在的, 修改软连接文件, 会重新建立源文件, 重新建立链接关系 (此时这种连接关系不太稳定,如果修改软连接文件中的内容,源文件中不一定被修改)一定在删除源文件的时候,将软链接文件也删除掉(以防后患)

 

 6.2硬链接文件

硬链接:目标文件的替身

生成:ln [源文件] [目标文件]

猜你喜欢

转载自blog.csdn.net/sy2453/article/details/123415860