您的位置:首页 > 教育 > 培训 > C语言:IO操作

C语言:IO操作

2024/10/5 13:34:25 来源:https://blog.csdn.net/m0_58233509/article/details/139102558  浏览:    关键词:C语言:IO操作

引言

I/O操作是一切实现的基础。IO即为input &output

标准IO(stdio)

FILE类型贯穿始终,FILE是由typedef定义出来的

vii /usr/include/asm-generic/errno-base.h (errno定义的位置)

/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h 结构体所在位置(struct _IO_FILE)

fopen

打开一个文件。

errno是之前是一个整形现在一个预定义的宏,可以用gcc -E查看

perror函数:可以打印出错的信息

strerror:将errno的数据转为字符串输出。

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

返回:FILE指针,指针存在于堆区。

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{FILE*fp = fopen("temp","r");if(fp==NULL){perror("Error opening file");fprintf(stderr,"fopen() failed:%d %s\n",errno,strerror(errno));exit(1);}puts("OK");return 0;
}

最大的打开文件数量

count = 65533,除了文件操作默认还打开了stdio,stdout,stderr,一共65526,可以使用ulimit -a查看linux设置的系统上线。

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{int count = 0;FILE*fp;while(1){fp = fopen("temp","w");if(fp==NULL){perror("Error opening file");break;}count++;}puts("OK");printf("count = %d \n",count);return 0;
}

文件权限

4是读,2是写,1是可执行。文件权限为8进制数

0666 & ~umask

umask输出0002(是一个8进制数)000 000 010

取反相当于 111 111 101 

110 110 110 & 111 111 101 =  110 110 100

fclose

关闭一个文件,成功返回0,失败返回EOF宏

fclose(fp);

fgetc

diff命令:比较两个文件的区别,不输出代表内容一致。

getc是宏,fegtc是函数来使用。宏编译时被替换,占用编译,函数在运行,占用运行时间。返回值为int,读到EOF代表读取到了末尾。

getchar == getc == fgetc 

fputc

putchar == putc == fputc

mycopy

#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>int main(int argc,char **argv)
{FILE*fps,*fpd;int ch;if(argc <3){fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);exit(1);}fps = fopen(argv[1],"r");if(fps ==NULL){perror("fopen");exit(1);}fpd = fopen(argv[2],"w");if(fpd ==NULL){fclose(fps);perror("fopen");exit(1);}while(1){ch = fgetc(fps);if(ch == EOF)break;fputc(ch,fpd);}fclose(fpd);fclose(fps);return 0;
}

fgets

编辑器末尾会产生'\n'

stream = "abcd"

fgets(buf,5,stream);  

此时需要读取两次才会读取完毕。

fputs

mycopy_fgets

#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>int main(int argc,char **argv)
{FILE*fps,*fpd;int ch;char buf[1024];if(argc <3){fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);exit(1);}fps = fopen(argv[1],"r");if(fps ==NULL){perror("fopen");exit(1);}fpd = fopen(argv[2],"w");if(fpd ==NULL){fclose(fps);perror("fopen");exit(1);}while(1){if(fgets(buf,1024,fps)==NULL)break;fputs(buf,fpd);}fclose(fpd);fclose(fps);return 0;
}

fread

二进制流读取,架设数据量足够。

fread(buf,1,10,fp) -- 数据量不足,可以读取一部分

fread(buf,10,1,fp)--数据量不足,会读取失败。

fwrite

返回写入正常的个数

mycopy_fread

#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>int main(int argc,char **argv)
{FILE*fps,*fpd;int ch;int len;char buf[1024];if(argc <3){fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);exit(1);}fps = fopen(argv[1],"r");if(fps ==NULL){perror("fopen");exit(1);}fpd = fopen(argv[2],"w");if(fpd ==NULL){fclose(fps);perror("fopen");exit(1);}while(1){len = fread(buf,1,1024,fps);if(len ==0 )break;fwrite(buf,1,len,fpd);}fclose(fpd);fclose(fps);return 0;
}

printf

打印语句,同类函数族:
       int fprintf(FILE *stream, const char *format, ...);:在文件流输出
       int sprintf(char *str, const char *format, ...);,格式化输出       
       int snprintf(char *str, size_t size, const char *format, ...);指定大小

返回写入的字符个数

scanf

从终端获取数据,同类函数族:

       int scanf(const char *format, ...);
         int fscanf(FILE *stream, const char *format, ...);从指定流拿内容,放在。。。地址中
       int sscanf(const char *str, const char *format, ...);将长的字符串按照格式,解析到地址

遵循
       POSIX.1-2001, POSIX.1-2008, C89, C99.
 

fseek

操作文件位置指针,定位到指定位置

       int fseek(FILE *stream, long offset, int whence);

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • offset -- 这是相对 whence 的偏移量,以字节为单位。
  • whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一
  • 常量描述
    SEEK_SET文件的开头
    SEEK_CUR文件指针的当前位置
    SEEK_END文件的末尾

ftell

操作文件位置指针

       long ftell(FILE *stream);

返回给定流 stream 的当前文件位置。

rewind

操作文件位置指针

       void rewind(FILE *stream);
将文件内部的位置指针重新指向一个流(数据流/文件)的开头

fflush

刷新文件缓冲区

        int fgetpos(FILE *stream, fpos_t *pos) 获取流 stream 的当前文件位置,并把它写入到 pos。如果成功,该函数返回零。如果发生错误,则返回非零值

      int fsetpos(FILE *stream, const fpos_t *pos) 设置给定流 stream 的文件位置为给定的位置。参数 pos 是由函数 fgetpos 给定的位置。如果成功,该函数返回零值,否则返回非零值,并设置全局变量 errno 为一个正值,该值可通过 perror 来解释。

缓冲区

作用是:合并系统调用。

行缓存:换行的时候进行刷新,满了进行刷新,强制刷新

全缓存:满了刷新,强制刷新(默认,只要不是终端设备)

无缓存:stderr,需要立即输出

setvbuf,改缓冲区

getline

在标准C语言中,getline函数是不存在的。本文介绍的是LINUX下C语言的getline函数(在gcc编译器中,对标准库进行了扩展,加入了一个getline函数。),除此之位C++中还有两个getline函数,一个在stirng中,一个在iostream中。

       ssize_t getline(char **lineptr, size_t *n, FILE *stream);

以上函数均无法获取一行内容, getline是一个动态内存申请的函数。

需要定义宏:CFLAGS+= -D_GNU_SOURCE

可以参考文章:C/C++中的getline函数总结_fopen getline-CSDN博客

#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main(int argc,char *argv[])
{if(argc <2){fprintf(stderr,"Usage...\n");return -1;}FILE*fp = fopen(argv[1],"r");if(fp == NULL){perror("fopen");return -1;}char *linebuf=NULL; //必须初始化size_t linesize = 0; //必须初始化while(1){if(getline(&linebuf,&linesize,fp) < 0) //会产生内存泄露,因为是动态申请的malloc 和reallocbreak;printf("%ld-%ld:%p \n",linesize,strlen(linebuf),&linebuf);//linesize为生成的内存大小,第一次为120}// free(getline); 不建议使用fclose(fp);return 0;
}

临时文件

函数

tmpnam

       char *tmpnam(char *s);
       tmpnam, tmpnam_r - create a name for a temporary file
不是一个安全的操作,需要获取文件指针然后在打开文件,如果并发操作容易产生错误。

缺陷:不是原子操作(产生名字,再创建文件

tmpfile

       FILE *tmpfile(void);

安全的临时文件,产生匿名文件(ls -a看不到)。可以直接打开文件。不会产生冲突,也不用担心销毁的问题fclose会自动释放。

如何不冲突创建临时文件

/tmp目录是临时目录,使用tmpfile可以不冲突

及时销毁

tempfile生成的临时文件当文件关闭后自动删除---匿名文件(没名字:ls命令看不到)

问题

fseek和ftell的第二个参数是long类型,32位下是32字节,可能会溢出,引出:fseeko, ftello,把第二个参数定义为宏off_t。

#define _FILE_OFFSET_BITS 64 可以修改off_t为64位

gcc a.c -D__FILE_OFFSET_BITS=64

但是遵循的协议是       POSIX.1-2001, POSIX.1-2008, SUSv2.

系统调用IO/文件IO(sysio)

标准IO依赖于系统调用IO

文件描述符是在文件IO中贯穿始终的类型。

文件描述符的概念

本质是一个整型数,是数组的下标 ,存放的是结构体,使用数组指针进行封装,返回下标。数组的大小上限为ulimit -a中 open files的大小,使用时优先使用最小的那一个。每一个进程(启动的程序)都会存在这个数组。可以用以下程序进行测试。写的时候不会出现覆盖的情况。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>int main()
{FILE*fp = fopen("./temp","a+");int inode = fileno(fp);printf("inode = %d \n",inode);srand(time(NULL));char buf[16];sprintf(buf,"hello_%d \n",rand()%100 );fwrite(buf,1,strlen(buf),fp);
//    fflush(fp);while(1){int q = getchar();if(q == 'q')break;}return 0;
}

文件IO操作

open


       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);
r- >O_RDONLY
r+ -> O_RDWR
w -> O_WRONLY|O_CREAT|O_TRUNC
w+ -> O_RDWR|O_CREAT|O_TRUNC

mode_t:  8进制数

close

与fclose类似。

read

         #include <unistd.h>

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

write

        #include <unistd.h>

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

lseek

同seek

#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc,char **argv)
{int fps,fpd;int ret;char buf[128];int len,pos;if(argc <3){fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);exit(1);}fps = open(argv[1],O_RDONLY);if(fps <0){perror("open");exit(1);}fpd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0600 );if(fpd <0){close(fps);perror("open");exit(1);}while(1){len = read(fps,buf,128);if(len < 0){perror("read()");break;}if(len ==0)break;pos = 0;while(len > 0){ret = write(fpd,buf+pos,len);if(ret <0){perror("write()");exit(1);}pos += ret;len-=ret;}}close(fpd);close(fps);return 0;
}

文件IO与标准IO的区别

举个例子:传达室老大爷跑邮局。
区别:响应速度 & 吞吐量
标准IO  吞吐量大,文件IO响应速度快。
面试:如何是一个程序变快?
从吞吐量和响应速度两方面进行回答

用户体验:比较喜欢吞吐量。
提醒:标准IO和文件IO不可以混用。
函数:int fileno(FILE*fp) 将标准IO转为文件IO

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>int main()
{putchar('a');write(1,"b",1);putchar('a');write(1,"b",1);putchar('a');write(1,"b",1);exit(0);}

输出结果:bbbaaa 
因为文件IO比标准io快。
引入命令:strance:帮我门看一个可执行程序的系统调用是如何发生的。

IO的效率问题

time命令:查看程序执行的时间       

real    0m0.007s                user+sys +一点点调度等待 时间(真实时间)
user    0m0.007s          使用时间    
sys    0m0.000s     系统调用消耗时间

栈空间在 1024*8500左右时,运行会出现core的情况。

文件共享

含义:多个任务共同操作一个文件或者协同完成任务。
面试:写程序删除一个文件的第十行。
补充函数:truncate/ftruncate可以把文件截断到指定的长度

原子操作

不可分割的最小单位。不可分割的操作。
原子操作的作用:解决竞争和冲突。
如:tmpnam命令 在申请文件时候还没有创建,使用才创建,同时别人在申请是已经创建了会错误。

程序中的重定向

可以把原有的文件描述符stdout(1)关掉,然后使用open打开文件,此时文件所在表述符为1

dup

复制旧文件描述符,作为新的描述符(为当前最小的描述符)。不是原子操作

       int dup(int oldfd);

dup2

dup2是原子操作,把newfd最为old的副本,如果newfd被占用就关闭

       int dup2(int oldfd, int newfd);

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define FILE_NAME "/tmp/out"int main()
{int fd = open(FILE_NAME,O_WRONLY|O_CREAT|O_TRUNC,0600);printf("fd = %d \n",fd);//下面两步不原子,容易出现被别人打断的情况。
//    close(1);
//    int d = dup(fd);//dup2是原子操作int d = dup2(fd,1);printf("dup = %d \n",d);if(fd != 1){close(fd);}puts("hello! ");return 0;
}

同步

sync

用于跟设备通信会使用到。关机(解除设备挂载)

       sync, syncfs - commit filesystem caches to disk(将文件系统缓存提交到磁盘)
        void sync(void)

fsync

指定一个文件进行刷新,将处于核心状态的文件与存储设备同步

       int fsync(int fd);

fdatasync

       int fdatasync(int fd);

只刷数据不刷亚数据。
数据:文件中有效的内容。
亚数据:文件最后的修改时间、文件的属性。

fcntl

文件描述所变的魔术几乎都来源于该函数。

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

       int fcntl(int fd, int cmd, ... /* arg */ );

ioctl

设备相关的内容

/dev/fd/目录

虚目录:显示的是当前进程的文件描述符信息。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com