流的读写

简介: 流的读写

按字符的形式实现输入/输出

字符输入/输出函数一次只能读写一个字符


fputc()

fputc() 函数用于向指定的流中写入一个字符,之所以不能描述为向文件中写入一个字符,完全是因为函数在对文件所对应的流指针操作时,首先会操作缓存区,最终再写入文件。但函数最终的运行结果已然是将字符写入文件。

#include <stdio.h>
int fputc(int c, FILE *stream);点击复制复制失败已复制


参数 c 用于表示写入的字符,函数原型定义参数 cint 型,而非 char 型,这是因为函数内部对该参数做了强制类型转换stream 则是文件相关联的流指针


使用示例如下

#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[]) {
  FILE *fp;
  if ((fp = fopen("test.txt", "w")) == NULL) {
    perror("fopen error");
    return -1;
  }
  if (fputc('a', fp) == EOF) {
    perror("fputc error");
  }
  fclose(fp);
  return 0;
}点击复制复制失败已复制


编译运行,将会向文件 test.txt 中写入字符 a ,可使用 $ od -c 查看文件中的数据,如下所示:

$ gcc main.c && ./a.out && od -c test.txt
0000000   a
0000001点击复制复制失败已复制


fgetc()

fputc() 相对应, fgetc() 用于从指定的流中读取一个字符。

#include <stdio.h>
int fgetc(FILE *stream);点击复制复制失败已复制


参数 stream 为已经打开的文件所对应的流指针。函数返回值为读取的字符,函数原型定义返回值的类型为 int 型,而非 char 型,同样也是因为函数内部做了强制类型转换。具体使用如下所示:

#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[]) {
  FILE *fp;
  int ch;
  if ((fp = fopen("test.txt", "r")) == NULL) {
    perror("fopen error");
    return -1;
  }
  if ((ch = fgetc(fp)) != EOF) {
    printf("ch = %c\n", ch);
  }
  fclose(fp);
  return 0;
}点击复制复制失败已复制


编译运行:

$ gcc main.c && ./a.out
ch = a点击复制复制失败已复制


按字符串的形式输入/输出

字符串输入/输出函数一次操作一个字符串


fputs()

#include <stdio.h>
int fputs(const char *s, FILE *stream);点击复制复制失败已复制


fputs() 函数用于向指定的流中写入字符串,不包含字符串的结束符 '\0' 。之所以不能描述为向文件写入,原因与读写字符一样,需要先操作缓存区


参数 s 指向需要写入的字符串, stream 为指定的流,具体使用如下所示:

#include <stdio.h>
#define N 32
#define errlog(errmsg)                                                         \
  perror(errmsg);                                                              \
  printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);                \
  return -1;
int main(int argc, const char *argv[]) {
  FILE *fp;
  char buf[N] = "hello world";
  if ((fp = fopen("test.txt", "w")) == NULL) {
    errlog("fopen error");
  }
  if (fputs(buf, fp) == EOF) {
    errlog("fputs error");
  }
  fclose(fp);
  return 0;
}点击复制复制失败已复制


编译运行,得到如下结果:

$ gcc main.c && ./a.out && od -c test.txt
0000000   h   e   l   l   o       w   o   r   l   d
0000013点击复制复制失败已复制


fgets()

fgets() 函数用来从指定的流中读取字符串,其操作要比 fputs() 函数复杂一些。在 Linux 官方手册中,该函数的使用定义为:从指定的流中最多读取 size-1 个字符的字符串,并在读取的字符串末尾自动添加一个结束符'\0' (表示字符串结束)。

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);点击复制复制失败已复制


参数 s 用于存储读取的字符串, size 为期望读取的字符,程序可以自动设置, stream 为指定的流指针。具体使用如下所示:

#include <stdio.h>
#define N 32
#define errlog(errmsg)                                                         \
  perror(errmsg);                                                              \
  printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);                \
  return -1;
int main(int argc, const char *argv[]) {
  FILE *fp;
  char buf[N] = "hello world";
  if ((fp = fopen("test.txt", "r")) == NULL) {
    errlog("fopen error");
  }
  if (fgets(buf, N, fp) != NULL) {
    printf("bug:%s\n", buf);
  }
  fclose(fp);
  return 0;
}点击复制复制失败已复制


编译运行:

$ gcc main.c && ./a.out
bug:hello world点击复制复制失败已复制


此时,运行结果并没有产生太多意外。程序设置的参数 size 的值为 32 (期望读取的字符个数为 32 ),远比文件中字符的个数多,因此将文件中的数据全部读取。这是,重新换一种做法,将参数 size 的值进行修改,不妨将其设置为 11 ,注意此时文件中的数据为 hello world ,加上单词中间的空格符,一共是 11 个字符。


重新运行改动后的示例,结果如下:

$ gcc main.c && ./a.out
bug:hello worl点击复制复制失败已复制


由运行结果可以看出,读取的字符串少了一个字符 'd' 。此时,运行结果正如 Linux 官方手册中描述的一致,只能读取 size-1 个字符。正是因为函数本身在读取字符串时,需要在文件的末尾自动添加一个结束符 '\0' ,表示字符串读取到此结束。因此读取了 size-1 个字符,最后一位被结束符替换。


fgets() 函数本身在读取字符串时,会自动加上结束符 '\0' ,占用一个字符位置,表示字符串读取结束。


问题到此依然没有结束,假设此时文件中的数据改变,使用编辑器写入两行数据,如下所示:

hello world
hello world
点击复制复制失败已复制


注意每一行后面都会有换行符,使用 $ od -c 命令查看,如下所示:

$ od -c test.txt
0000000   h   e   l   l   o       w   o   r   l   d  \n   h   e   l   l
0000020   o       w   o   r   l   d  \n
0000030点击复制复制失败已复制


由上述查询可知,此时文件中的数据为 24 个字符,注意 '\n' 换行符。这时如果选择将参数 size 设置为 32 ,那么是否会将文件中的数据全部读取?答案是不会,原因就是换行符发挥了作用,运行结果如下所示:

$ gcc main.c && ./a.out
bug:hello world
点击复制复制失败已复制


很明显,输出的是一行数据,空行说明读到了第一行的换行符。说明 fgets() 函数在读取到换行符 '\n' 时会自动结束,并在末尾补充一个结束符 '\0' ,表示读取结束,无论 size 有多大。


通过代码示例,可以进行简短的示例总结:

文件中的数据 参数 size 的大小 读取的结果
abcdef 6 abcde'\0'
abcdef >6 abcdef'\0'
abcdef'\n' 6 abcde'\0'
abcdef'\n' 7 abcdef'\0'
abcdef'\n' >7 abcdef'\n''\0'


示例

求一个文件的行数:

#include <stdio.h>
#include <string.h>
#define N 32
#define errlog(errmsg)                                                         \
  perror(errmsg);                                                              \
  printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);                \
  return -1;
int get_line(FILE *fp) {
  char buf[N] = "";
  int count = 0;
  while (fgets(buf, N, fp) != NULL) {
    if (buf[strlen(buf) - 1] == '\n')
      count++;
  }
  return count;
}
int main(int argc, const char *argv[]) {
  FILE *fp;
  int line = 0;
  if ((fp = fopen(argv[1], "r")) == NULL) {
    errlog("fopen error");
  }
  line = get_line(fp);
  printf("line = %d\n", line);
  fclose(fp);
  return 0;
}点击复制复制失败已复制


编译运行:

$ gcc main.c && ./a.out test.txt         
line = 2点击复制复制失败已复制


按数据大小的形式输入/输出

标准I/O也提供了按照数据块大小的形式对文件的输入/输出,而不管数据的格式如何。


fwrite()

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);点击复制复制失败已复制


fwrite() 函数被用来向指定的流中输入数据,根据 Linux 官方手册的说明,该函数功能为向指定的流指针 stream 中,写入 nmemb 个单元数据,单元数据的大小为 size ,参数 ptr 用来指向需要写入的数据。


注意

参数 nmemb 表示的是单元数据的个数,而非字符的个数。因此,单元数据是什么格式,完全由程序自行定义,可以是字符串、数组、结构体,甚至是共同体。


数据将采用结构体的形式输入文件。


新建 source.h 头文件,定义结构体如下所示:

#ifndef _SOURCE_H
#define _SOURCE_H
#define N 32
struct data{
  int a;
  char b;
  char buf[N];
};
#endif点击复制复制失败已复制


新建 main.c 文件,写入如下内容:

#include "source.h"
#include <stdio.h>
#include <string.h>
#define errlog(errmsg)                                                         \
  perror(errmsg);                                                              \
  printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);                \
  return -1;
struct data obj = {
    .a = 10,
    .b = 'q',
    .buf = "test",
};
int main(int argc, const char *argv[]) {
  FILE *fp;
  if ((fp = fopen("test.txt", "w")) == NULL) {
    errlog("fopen error");
  }
  if (fwrite(&obj, sizeof(struct data), 1, fp) < 0) {
    errlog("fwrite error");
  }
  fclose(fp);
  return 0;
}点击复制复制失败已复制


代码将头文件中定义的结构体进行初始化,并写入指定的流(文件)中,运行结果如下所示:

$ gcc main.c && ./a.out && od -c test.txt
0000000  \n  \0  \0  \0   q   t   e   s   t  \0  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000040  \0  \0  \0  \0  \0  \0  \0  \0
0000050点击复制复制失败已复制


注意

文件中的 '\0' 不是结束符,写入 test.txt 文件中的数据是一些机器码。其实也不难理解,结构体属于构造数据类型,它在内存中存储的形式与数组的线程存储不同。结构体存储需要按照字节对齐的形式存储。


fread()

fread() 函数被用来从指定的流中读取数据,其参数与 fwrite() 函数一致,不同的是数据传递的方向发生了变化。

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);点击复制复制失败已复制


参数 ptr 用来保存读取的数据, nmemb 表示读取的单元数据的个数。具体使用如下所示:

#include "source.h"
#include <stdio.h>
#include <string.h>
#define errlog(errmsg)                                                         \
  perror(errmsg);                                                              \
  printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);                \
  return -1;
int main(int argc, const char *argv[]) {
  FILE *fp;
  struct data obj;
  if ((fp = fopen("test.txt", "r")) == NULL) {
    errlog("fopen error");
  }
  if (fread(&obj, sizeof(struct data), 1, fp) > 0) {
    printf("a=%d b=%c buf=%s\n", obj.a, obj.b, obj.buf);
  }
  fclose(fp);
  return 0;
}点击复制复制失败已复制


编译运行,结果如下:

$ gcc main.c && ./a.out 
a=10 b=q buf=test
目录
相关文章
|
7月前
|
存储 C++ iOS开发
C++ 文件和流
C++ 文件和流
55 0
|
7月前
|
程序员 C++ iOS开发
c++文件和流
c++文件和流
42 0
47 # 实现可读流
47 # 实现可读流
43 0
46 # 可读流 readStream
46 # 可读流 readStream
47 0
|
缓存
缓存流
缓存流
35 0
|
存储 Java
流及其相关操作
流及其相关操作
|
存储 Java
16 文件与流
16 文件与流
61 0
|
Java
I/O流常用复制和读写文件
I/O流常用复制和读写文件
125 0
|
移动开发 Linux Windows
IO流概述分类、字节流写数据、字节流写数据的三种方式及写数据的两个小问题
IO流概述分类、字节流写数据、字节流写数据的三种方式及写数据的两个小问题的简单示例
127 0
IO流概述分类、字节流写数据、字节流写数据的三种方式及写数据的两个小问题
IO流的字节流的缓冲和非缓冲方式的区别及性能对比
IO流的字节流的缓冲和非缓冲方式的区别及性能对比
267 0