Linux C | Linux标准I/O编程

1.Linux标准I/O编程

1.1 linux系统调用和用户编程接口

1.1.1 系统调用

操作系统基本上都支持多任务,即同时可以运行多个程序。如果允许程序直接访问系统资源,肯定会带来很多问题。

因此,所有软硬件资源的管理和分配都由操作系统负责。程序要获取资源(如内存分配、读写串口)必须通过操作系统来完成,及用户向操作系统发出服务请求,操作系统收到请求后执行相关的代码来处理。

linux系统调用按照功能大致可分为进程控制进程间通信文件系统控制存储管理网络管理套接字控制用户管理等几类。

1.1.2 用户编程接口

为什么不直接使用系统调用接口?

(1)系统调用接口功能非常简单,不能满足程序的需求;

(2)不同操作系统的系统调用接口不兼容,程序移植时工作量大。

1.2 linux标准I/O概述

1.2.1 标准I/O的由来

标准I/O指的是ANSI C中定义的用于I/O操作的一系列函数。

只要操作系统中安装了C库,标准I/O函数就可以调用。换句话说,如果程序中使用的是标准I/O函数,那么源代码不需要修改就可以在其他操作系统下编译运行,具有更好的可移植性。

除此之外,使用标准I/O可以减少系统调用的次数,提高系统效率。标准I/O函数在执行时也会用到系统调用。在执行系统调用时,Linux必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态。如果频繁的执行系统调用会增加系统的开销。为了避免这种情况,标准I/O使用时在用户空间创建缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际的文件,从而减少了使用系统调用的次数。

1.2.2 流的含义

标准I/O的核心对象就是流。当用标准I/O打开一个文件时,就会创建一个FILE结构体描述该文件(或者理解为创建一个FILE结构体和实际打开的文件关联起来)。我们把这个FILE结构体形象的称为流。标准I/O函数都基于流进行各种操作。

标准I/O中流的缓冲类型有以下三种:

(1)全缓冲:;

(2)行缓冲:;

(3)无缓冲:;

1.3 标准I/O编程

1.3.1 流的打开

#include <stdio.h>

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

FILE *fdopen(int fd, const char *mode);

FILE *freopen(const char *path, const char *mode, FILE *stream);

fopen函数语法要点:
所需头文件#include <stdio.h>
函数原型FILE *fopen(const char *path, const char *mode);
函数参数path:包含要打开的文件路径及文件名;mode:文件打开方式
函数返回值成功:指向FILE的指针;失败:NULL
mode的取值说明:
r      Open text file for reading.  The stream is positioned at the beginning of the file.// 打开只读文件,该文件必须存在
		
r+     Open for reading and writing.  The stream is positioned at the beginning of the file.// 打开可读写文件,该文件必须存在
		
w      Truncate file to zero length or create text file for writing.  The stream is positioned at the beginning of the file.// 打开只写文件,若文件存在则文件长度为0,即会擦除写文件以前的内容;若文件不存在则建立文件

w+     Open for reading and writing.  The file is created if it does not exist, otherwise it is truncated.  The stream is  positioned at the beginning of the file.// 打开可读写文件,若文件存在则文件长度为0,即会擦除写文件以前的内容;若文件不存在则建立文件

a      Open  for  appending (writing at end of file).  The file is created if it does not exist.  The stream is positioned at the end of the file.//以附加的方式打开只写文件

a+     Open for reading and appending (writing at end of file).  The file is created if it does not exist.  The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.//以附加的方式打开可读写的文件

更多说明,详见:[fly@fly-vm 01-linuxStdIO]$ man fopen

1.3.2 流的关闭

#include <stdio.h>

int fclose(FILE *stream);

说明:函数返回值,成功返回0,失败返回EOF

该函数将流的缓冲区内的数据全部写入文件中,并释放相关资源。

1.3.3 错误处理

       #include <stdio.h>

       void perror(const char *s);

       #include <errno.h>

       const char * const sys_errlist[];
       int sys_nerr;
       int errno;       /* Not really declared this way; see errno(3) */

示例:
/*******************************************************************
 *   > File Name: perror.c
 *   > Author: fly
 *   > Create Time: 2022年09月06日 星期二 23时13分12秒
 ******************************************************************/

#include <stdio.h>

int main(int argc, char* argv[])
{
    FILE *fp;

    if((fp = fopen("test.txt", "r")) == NULL)
    {
        perror("file to open");
        return (-1);
    }

    fclose(fp);

    return 0;
}
编译/运行:
[fly@fly-vm 01-linuxStdIO]$ make
gcc -o perror perror.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./perror
file to open: No such file or directory

// 返回错误码对应的错误信息字符串
#include <string.h>

char *strerror(int errnum);

int strerror_r(int errnum, char *buf, size_t buflen);
/* XSI-compliant */

char *strerror_r(int errnum, char *buf, size_t buflen);
/* GNU-specific */

char *strerror_l(int errnum, locale_t locale);

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

strerror_r():
The XSI-compliant version is provided if:
(_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE
    Otherwise, the GNU-specific version is provided.

示例:
/*******************************************************************
 *   > File Name: strerror.c
 *   > Author: fly
 *   > Create Time: 2022年09月07日 星期三 22时42分48秒
 ******************************************************************/

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

int main(int argc, char* argv[])
{
    FILE *fp;
    if((fp = fopen("test.txt", "r")) == NULL)
    {
        printf("fail to open: %s\n", strerror(errno));
        return (-1);
    }

    fclose(fp);

    return 0;
}
编译、运行:
[fly@fly-vm 01-linuxStdIO]$ make strerror
gcc -o strerror strerror.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./strerror
fail to open: No such file or directory

1.3.4 流的读写

1.按字符(字节)输入、输出
       #include <stdio.h>

       int fgetc(FILE *stream);//从指定流中读取一个字符

       int getc(FILE *stream);//从指定流中读取一个字符

       int getchar(void);//从stdin中读取一个字符

       #include <stdio.h>

       int fputc(int c, FILE *stream);//向指定流输出一个字符

       int putc(int c, FILE *stream);//向指定流输出一个字符

       int putchar(int c);//向stdout输出一个字符
示例:
/*******************************************************************
 *   > File Name: fputs.c
 *   > Author: fly
 *   > Create Time: 2022年09月07日 星期三 23时01分01秒
 ******************************************************************/

#include <stdio.h>

int main(int argc, char* argv[])
{
    int c;

    while(1){
        c = fgetc(stdin);
        if((c >= '0') && (c <= '9')) fputc(c, stdout);
        if(c == '\n') break;
    }

    return 0;
}
编译、运行:
[fly@fly-vm 01-linuxStdIO]$ make fputs
gcc -o fputs fputs.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./fputs
asdffg1233334
1233334[fly@fly-vm 01-linuxStdIO]$
2.按行输入、输出
#include <stdio.h>

char *gets(char *s);

char *fgets(char *s, int size, FILE *stream);

gets函数容易造成缓冲区溢出,不推荐使用。

fgets从指定流中读取一个字符串,当遇到\n或读取了size-1个字符后返回。注意fgets不保证每次都能读出一行。


       #include <stdio.h>

       int fputs(const char *s, FILE *stream);

       int puts(const char *s);
示例:
/*******************************************************************
 *   > File Name: fgets.c
 *   > Author: fly
 *   > Create Time: 2022年09月07日 星期三 23时21分33秒
 ******************************************************************/

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

int main(int argc, char* argv[])
{
    int line = 0;
    char buf[128];
    FILE *fp;

    if(argc < 2)
    {
        printf("Usage: %s <file>\n", argv[0]);
        return -1;
    }

    if((fp = fopen(argv[1], "r")) == NULL)
    {
        perror("fail to fopen");
        return -1;
    }


    while(fgets(buf, 128, fp) != NULL)
    {
        if(buf[strlen(buf) - 1] == '\n') line ++;
    }

    printf("The line of %s is %d.\n", argv[1], line);

    return 0;
}
编译、运行:
[fly@fly-vm 01-linuxStdIO]$ make fgets
gcc -o fgets fgets.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./fgets fgets.c
The line of fgets.c is 38.
3.以指定大小为单位读写文件
#include <stdio.h>

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

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

1.3.5 流的定位

每次打开的流的内部都有一个当前读写位置。流在打开时,当前读写位置为0,表示文件的开始位置。

每读写一次后,当前读写位置自动增加实际的读写的大小。在读写流之间可先对流进行定位,即移动到指定的位置再操作。


#include <stdio.h>

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

long ftell(FILE *stream);
示例:
/*******************************************************************
 *   > File Name: ftell.c
 *   > Author: fly
 *   > Create Time: 2022年09月07日 星期三 23时45分49秒
 ******************************************************************/

#include <stdio.h>

int main(int argc, char* argv[])
{
    FILE *fp;

    if(argc < 2)
    {
        printf("Usage: %s <file>\n", argv[0]);
        return -1;
    }

    if((fp = fopen(argv[1], "r")) == NULL)
    {
        perror("fail to open");
        return -1;
    }

    fseek(fp, 0, SEEK_END);
    printf("The size of %s is %ld.\n", argv[0], ftell(fp));

    return 0;
}
编译、运行:
[fly@fly-vm 01-linuxStdIO]$ make ftell
gcc -o ftell ftell.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./ftell ftell.c
The size of ./ftell is 656.

1.3.6 格式化输入、输出

       #include <stdio.h>

       int scanf(const char *format, ...);
       int fscanf(FILE *stream, const char *format, ...);
       int sscanf(const char *str, const char *format, ...);

       #include <stdarg.h>

       int vscanf(const char *format, va_list ap);
       int vsscanf(const char *str, const char *format, va_list ap);
       int vfscanf(FILE *stream, const char *format, va_list ap);

       #include <stdio.h>

       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);
       int dprintf(int fd, const char *format, ...);
       int sprintf(char *str, const char *format, ...);
       int snprintf(char *str, size_t size, const char *format, ...);

       #include <stdarg.h>

       int vprintf(const char *format, va_list ap);
       int vfprintf(FILE *stream, const char *format, va_list ap);
       int vdprintf(int fd, const char *format, va_list ap);
       int vsprintf(char *str, const char *format, va_list ap);
       int vsnprintf(char *str, size_t size, const char *format, va_list ap);

1.4 示例

1.4.1 文件的复制

检查参数-》打开源文件-》打开目标文件-》循环读写文件-》关闭文件。


代码1(需要调试):
/*******************************************************************
 *   > File Name: mycopy.c
 *   > Author: fly
 *   > Create Time: 2022年09月07日 星期三 23时56分22秒
 ******************************************************************/

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

#define N 64

int main(int argc, char* argv[])
{
    int n;
    char buf[N];
    FILE *fps, *fpd;

    if(argc < 3)
    {
        printf("Usage: %s <src_file> <dst_file>\n", argv[0]);
        return -1;
    }

    if((fps = fopen(argv[1], "r")) == NULL){
        fprintf(stderr, "fail to fopen %s : %s\n", argv[1], strerror(errno));
        exit(-1);
    }

    if((fpd = fopen(argv[2], "w")) == NULL)
    {
        fprintf(stderr, "fail to fopen %s : %s\n", argv[2], strerror(errno));
        fclose(fps);
        exit(-1);
    }

    while((n = fread(buf, 1, N, fps)) >= 0)
    {
        fwrite(buf, 1, N, fpd);
    }

    fclose(fps);
    fclose(fpd);

    return 0;
}
编译、运行:
[fly@fly-vm 01-linuxStdIO]$ make mycopy
gcc -o mycopy mycopy.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./mycopy mycopy.c test1.txt
^C

运行发现程序一直没有结束,也就几百字节的数据,复制需要这么久?最开始,怀疑复制大文件,BUF设置小,读取效率慢,导致复制时间久。于是换了个小文件,发现也是如此。

于是打开复制后的文件查看:

[fly@fly-vm 01-linuxStdIO]$ vim test1.txt

发现了如下问题,复制目标文件一直在死循环写入如下:

fpd);
 }

 fclose(fps);
 fclose(fpd);

 return 0;
}

检查代码,发现问题出在这一句“ while((n = fread(buf, 1, N, fps)) >= 0)”上面;当fread读取完源文件后,没有读到新的数据,就一直返回0,也就会一直死循环向目标文件写最后一次BUF里面的数据;

做如下修改:

while((n = fread(buf, 1, N, fps)) > 0)
重新编译、运行:
[fly@fly-vm 01-linuxStdIO]$ make mycopy
gcc -o mycopy mycopy.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./mycopy mycopy.c test.txt
[fly@fly-vm 01-linuxStdIO]$ diff mycopy.c test.txt
48a49,55
> fpd);
>     }
>
>     fclose(fps);
>     fclose(fpd);
>
>     ret
\ No newline at end of file

发现会多复制一些东西,怀疑最后一次复制,BUF不干净,做了如下修改;

 while((n = fread(buf, 1, N, fps)) > 0)
 {
     fwrite(buf, 1, N, fpd);
     bzero(buf, N);
 }

编译、运行,还是存在问题,如下:

[fly@fly-vm 01-linuxStdIO]$ make mycopy
gcc -o mycopy mycopy.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./mycopy mycopy.c t2.txt
[fly@fly-vm 01-linuxStdIO]$ diff mycopy.c t2.txt
Binary files mycopy.c and t2.txt differ
[fly@fly-vm 01-linuxStdIO]$ vim -O mycopy.c  t2.txt
2 files to edit
[fly@fly-vm 01-linuxStdIO]$ ls -l mycopy.c t2.txt
-rw-rw-r-- 1 fly fly 1056 99 23:00 mycopy.c
-rw-rw-r-- 1 fly fly 1088 99 23:15 t2.txt

在这里插入图片描述

最终修改如下:

buf读到多少字节,最终就向目标文件写入多少字节,也就不用每次写完后把buf清零;及读到n个字节就写入n个字节。

    while((n = fread(buf, 1, N, fps)) > 0)
    {
        fwrite(buf, 1, n, fpd);
    }

[fly@fly-vm 01-linuxStdIO]$ make mycopy
gcc -o mycopy mycopy.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./mycopy mycopy.c t3.txt
[fly@fly-vm 01-linuxStdIO]$ diff mycopy.c t3.txt
[fly@fly-vm 01-linuxStdIO]$ ls -l mycopy.c  t3.txt
-rw-rw-r-- 1 fly fly 1033 99 23:18 mycopy.c
-rw-rw-r-- 1 fly fly 1033 99 23:18 t3.txt
代码2(最终版):
/*******************************************************************
 *   > File Name: mycopy.c
 *   > Author: fly
 *   > Create Time: 2022年09月07日 星期三 23时56分22秒
 ******************************************************************/

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

#define N 64

int main(int argc, char* argv[])
{
    int n;
    char buf[N];
    FILE *fps, *fpd;

    if(argc < 3)
    {
        printf("Usage: %s <src_file> <dst_file>\n", argv[0]);
        return -1;
    }

    if((fps = fopen(argv[1], "r")) == NULL){
        fprintf(stderr, "fail to fopen %s : %s\n", argv[1], strerror(errno));
        exit(-1);
    }

    if((fpd = fopen(argv[2], "w")) == NULL)
    {
        fprintf(stderr, "fail to fopen %s : %s\n", argv[2], strerror(errno));
        fclose(fps);
        exit(-1);
    }

    while((n = fread(buf, 1, N, fps)) > 0)
    {
        fwrite(buf, 1, n, fpd);
    }

    fclose(fps);
    fclose(fpd);

    return 0;
}

1.4.2 循环记录系统时间

打开文件-》获取系统时间-》写入文件-》延时1S-》返回第2步(获取系统时间)。


代码3:
/*******************************************************************
 *   > File Name: mytime.c
 *   > Author: fly
 *   > Create Time: 2022年09月09日 星期五 23时54分20秒
 ******************************************************************/

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    FILE *fp;
    time_t t;

    if(argc != 2)
    {
        printf("Usage: %s <file>\n", argv[0]);
        exit(-1);
    }

    if((fp = fopen(argv[1], "w")) == NULL)
    {
        perror(" fail to fopen ");
        exit(-1);
    }

    while(1)
    {
        time(&t);
        fprintf(fp, "%s", ctime(&t));
        fflush(fp);
        sleep(1);
    }

    fclose(fp);

    return 0;
}

编译/运行:
[fly@fly-vm 01-linuxStdIO]$ make mytime
gcc -o mytime mytime.c -g -Wall -lm
[fly@fly-vm 01-linuxStdIO]$ ./mytime time.txt
^C
[fly@fly-vm 01-linuxStdIO]$ cat time.txt
Fri Sep  9 23:55:19 2022
Fri Sep  9 23:55:20 2022
Fri Sep  9 23:55:21 2022
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值