C语言输入/输出流和文件操作【一】

文件是什么?

文件是数据源的一种,最主要的作用是保存数据。

在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。例如:

  • 通常把显示器称为标准输出文件,printf 就是向这个文件输出数据;
  • 通常把键盘称为标准输入文件,scanf 就是从这个文件读取数据。

常见硬件设备所对应的文件

  • stdin 标准输入文件,一般指键盘;scanf()、getchar() 等函数默认从 stdin 获取输入。
  • stdout 标准输出文件,一般指显示器;printf()、putchar() 等函数默认向 stdout 输出数据。
  • stderr 标准错误文件,一般指显示器;perror() 等函数默认向 stderr 输出数据(后续会讲到)。
  • stdprn 标准打印文件,一般指打印机。

我们不去探讨硬件设备是如何被映射成文件的,大家只需要记住,在C语言中硬件设备可以看成文件,有些输入输出函数不需要你指明到底读写哪个文件,系统已经为它们设置了默认的文件,当然你也可以更改,例如让printf 向磁盘上的文件输出数据。

操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。文件在进行读写操作之前要先打开,使用完毕要关闭。

  • 所谓打开文件,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,这些信息会被保存到一个 FILE 类型的结构体变量中。

  • 关闭文件就是断开与文件之间的联系,释放结构体变量,同时禁止再对该文件进行操作。

  • 在C语言中,文件有多种读写方式,可以一个字符一个字符地读取,也可以读取一整行,还可以读取若干个字节。文件的读写位置也非常灵活,可以从文件开头读取,也可以从中间位置读取。

流是什么?

文件(保存在磁盘中)只有加载到内存中才可以进行处理,内存中的数据只有保存到文件(磁盘)中才可以不丢失。在这期间我们把数据在文件到内存中的传递叫做文件流。
在这里插入图片描述

文件是数据源的一种,除了文件,还有数据库、网络、键盘等,数据传递到内存也就是保存到C语言的变量(例如整数、字符串、数组、缓冲区等)。我们把数据在数据源和程序(内存)之间传递的过程叫做数据流(Data Stream)。相应的,数据从数据源到程序(内存)的过程叫做输入流(Input Stream),从程序(内存)到数据源的过程叫做输出流(Output Stream)
流的示意图

文本文件与二进制文件的区别?

(在C语言中)我们根据数据在文件存储形式的不同把文件文件和二进制文件。

  • 文本文件:把要存储的数据当成是一系列的字符组成,把每个字符的ASCII码值存入文件中。每个ASCII码值占一个字节(8bit)每个字节表示一个字符。因此文本文件也称为字符文件或ASCII文件,是字符序列文件。
  • 二进制文件:把数据对应的二进制形式存储到文件,是字节序列文件。
    文本文件与二进制文件的区别

C语言的文件操作

打开与关闭文件

打开文件
在C语言中,操作文件之前必须先打开文件;所谓“打开文件”,就是让程序和文件建立连接的过程。

打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。

FILE *fopen( const char *filename, const char *mode );(C99 前)

  • filename - 关联到文件系统的文件名
  • mode - 打开方式
  • 返回值 FILE(定义与stdio.h中的一种结构体类型后面会讲到) 打开文件失败返回NULL

文件打开方式
C文件打开方式
调用 fopen() 函数时必须指明读写权限,但是可以不指明读写方式(此时默认为"t")。

读写权限和读写方式可以组合使用,但是必须将读写方式放在读写权限的中间或者尾部(换句话说,不能将读写方式放在读写权限的开头)。例如:
将读写方式放在读写权限的末尾:“rb”、“wt”、“ab”、“r+b”、“w+t”、“a+t”
将读写方式放在读写权限的中间:“rb+”、“wt+”、“ab+”

整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是:

  • r(read):读
  • w(write):写
  • a(append):追加
  • t(text):文本文件
  • b(binary):二进制文件
  • +:读和写

关闭文件
文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。fclose() 的用法为:

int fclose( FILE stream )

  • stream - 需要关闭的文件流
  • 返回值 成功时为 ​0​ ,否则为 EOF 。

关闭给定的文件流。冲入任何未写入的缓冲数据到 OS 。舍弃任何未读取的缓冲数据。无论操作是否成功,流都不再关联到文件。若在 fclose 返回后使用指针 stream 的值则行为未定义。

示例

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <stdlib.h>
#define N 100
int main() {
    FILE* fp;
    char str[N + 1];
    if ((fp = fopen("text.txt", "r")) == NULL) {
        puts("文件打开失败");
        exit(0);
    }
    while (fgets(str,N,fp)!=NULL)
    {
        printf("%s", str);
    }
    fclose(fp);
    return 0;
}

文件的读写

以字符形式读写文件

int fgetc( FILE *stream );

  • stream - 读取字符的来源
  • 返回值 成功时为获得的字符,失败时为 EOF 。

int fputc( int ch, FILE *stream );

  • ch - 要被写入的字符
  • stream - 输出流
  • 返回值 成功时,返回被写入字符。失败时,返回 EOF 并设置 stream 上的错误指示器(见 ferror() )。

示例

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define N 100
int main() {
    FILE* fp;
    char ch;
    if ((fp = fopen("text.txt", "r+")) == NULL) {
        puts("文件打开失败");
        exit(0);
    }
    while ((ch = fgetc(fp))!=EOF)
    {
       // Sleep(100);
        putchar(ch);
    }
    while ((ch = getchar()) != '\n') {
        fputc(ch, fp);
    }
    fclose(fp);
    return 0;
}

以字符串形式读写文件

char *fgets ( char *str, int n, FILE *fp );

  • str 为字符数组
  • n 为要读取的字符数目
  • fp 为文件指针。
  • 返回值:读取成功时返回字符数组首地址,也即 str;读取失败时返回 NULL;如果开始读取时文件内部指针已经指向了文件末尾,那么将读取不到任何字符,也返回 NULL。

关于fgets()几点要注意的地方

  • fgets() 遇到换行时,会将换行符一并读取到当前字符串。而 gets() 不一样,它会忽略换行符。
  • 读取到的字符串会在末尾自动添加 ‘\0’,n 个字符也包括 ‘\0’。也就是说,实际只读取到了 n-1 个字符,如果希望读取 100 个字符,n 的值应该为 101。
  • 需要重点说明的是,在读取到 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管 n 的值多大,fgets() 最多只能读取一行数据,不能跨行。在C语言中,没有按行读取文件的函数,我们可以借助 fgets(),将 n 的值设置地足够大,每次就可以读取到一行数据

int fputs( char *str, FILE *fp );

  • str 为要写入的字符串
  • fp 为文件指针
  • 返回值 写入成功返回非负数,失败返回 EOF

注意:puts()往stdin输出时自带换行

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define N 100
int main() {
    FILE* fp;
    char str[N+1];
    if ((fp = fopen("text.txt", "r+")) == NULL) {
        puts("文件打开失败");
        exit(0);
    }
    while (fgets(str,N,fp)!=NULL)
    {
       //Sleep(100);
        printf("%s", str);
    }
    fclose(fp);
    return 0;
}

二进制方式读取文件

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );(C99 前)
size_t fread( void *restrict buffer, size_t size, size_t count,FILE *restrict stream );(C99 起)
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );(C99 前)
size_t fwrite( const void *restrict buffer, size_t size, size_t count, FILE *restrict stream );(C99 起)

  • buffer为内存区块的指针,它可以是数组、变量、结构体等。fread() 中的 buffer 用来存放读取到的数据,fwrite() 中的 buffer 用来存放要写入的数据。
  • size:表示每个数据块的字节数。
  • count:表示要读写的数据块的块数。
  • stream:表示文件指针(文件流)。

返回值:返回成功读写的块数(对象数),也即 count。如果返回值小于 count:

  • 对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。
  • 对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。
  • 若出现错误,则流的文件位置指示器的结果值不确定。若读入部分的元素,则元素值不确定。

示例

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
enum { SIZE = 5 };
int main(void)
{
    int a[SIZE] = { 1, 2, 3, 4, 5 };
    FILE* fp1 = fopen("text.txt", "wb");
    assert(fp1);
    int tem = fwrite(a, sizeof(a[0]), SIZE, fp1);
    printf("成功将%d个数据写入文件\n", tem);
    fclose(fp1);
    int b[SIZE];
    FILE* fp2 = fopen("text.txt", "rb");
    assert(fp2);
    tem = fread(b, sizeof b[0], SIZE, fp2);
    fclose(fp2);
    printf("成功读入%d个数据\n", tem);
    for (int i = 0; i < SIZE; i++) {
        printf("%d ", b[i]);
    }
  
    return 0;
}

格式化读写文件

int fscanf ( FILE *fp, char * format, … );
int fprintf ( FILE *fp, char * format, … );

  • fp 为文件指针
  • format 为格式控制字符串
  • … 表示参数列表
  • 返回值 fprintf() 返回成功写入的字符的个数,失败则返回负数。
  • 返回值 fscanf() 返回参数列表中被成功赋值的参数个数。

与 scanf() 和 printf() 相比,它们仅仅多了一个 fp 参数。

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define N 2
struct stu {
    char name[10];
    int num;
    int age;
    float score;
} boya[N], boyb[N], * pa, * pb;
int main() {
    FILE* fp;
    fp = fopen("test.txt", "wt+");
    assert(fp);
    pa = boya;
    for (int i = 0; i < N; i++) {
        scanf("%s %d %d %f", pa->name, &(pa->num), &(pa->age), &(pa->score));
        pa++;
    }
    pa = boya;
    for (int i = 0; i < N; i++) {
        fprintf(fp, "%s %d %d %f\n", pa->name, pa->num, pa->age, pa->score);
        pa++;
    }
    rewind(fp);//移动文件位置指示器到给定文件流的起始。
    pb = boyb;
    for (int i = 0; i < N; i++) {
        fscanf(fp, "%s %d %d %f\n", pb->name, &(pb->num), &(pb->age), &(pb->score));
        pb++;
    }
    pb = boyb;
    for (int i = 0; i < N; i++) {
        printf("%s %d %d %f\n", pb->name, pb->num, pb->age, pb->score);
        pb++;
    }
    fclose(fp);
    return 0; 
}

随机读写文件

前面介绍的文件读写函数都是顺序读写,即读写文件只能从头开始,依次读写各个数据。但在实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写。这种读写方式称为随机读写,也就是说从文件的任意位置开始读写。

实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。

void rewind ( FILE *fp );用来将位置指针移动到文件开头
int fseek ( FILE *fp, long offset, int origin );用来将位置指针移动到任意位置

  • fp 为文件指针,也就是被移动的文件。
  • offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。offset 为正时,向后移动;offset 为负时,向前移动
  • origin 为起始位置,也就是从何处开始计算偏移量。C语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾

每个位置都用对应的常量来表示

  • 起始点——常量名 ——常量值
  • 文件开头 SEEK_SET 0
  • 当前位置 SEEK_CUR 1
  • 文件末尾 SEEK_END 2

注意: fseek() 一般用于二进制文件,在文本文件中由于要进行转换,计算的位置有时会出错。

示例

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//ftell 返回当前的文件位置指示值
//
//fgetpos 获取文件位置指示器
//
//fseek 将文件位置指示符移动到文件中的指定位置
//
//fsetpos 将文件位置指示器移动到文件中的指定位置
//
//rewind 将文件位置指示器移动到文件首
#define N 3
struct stu {
    char name[10]; //姓名
    int num;  //学号
    int age;  //年龄
    float score;  //成绩
}boys[N], boy, * pboys;
int main() {
    FILE* fp;
    pboys = boys;
    fp = fopen("test.txt", "w+");
    puts("Input data:\n");
    for (int i = 0; i < N; i++) {
        scanf("%s %d %d %f", pboys->name, &pboys->num, &pboys->age, &pboys->score);
        pboys++;
    }
    pboys = boys;
    fwrite(pboys, sizeof(struct stu), N, fp);
    rewind(fp);
    fseek(fp, sizeof(struct stu), SEEK_SET);
    fread(&boy, sizeof(struct stu), 1, fp);
    printf("%s %d %d %f", boy.name, boy.num, boy.age, boy.score);
    fclose(fp);
    return 0;
}

C语言实现文件拷贝

#include "stdio.h"
#include "stdlib.h"
int main(int arg,char* argv[]){
    if(arg!=3){
        printf("input error!");
        exit(0);
    }
    FILE* fpr = fopen(argv[1],"rb");
    FILE* fpw = fopen(argv[2],"wb");
    if(fpr==NULL||fpw==NULL) return 1;
    fseek(fpr,0,SEEK_END);
    int len = ftell(fpr);
    rewind(fpr);
    for(int i = 0;i<len;i++){
        char ch = fgetc(fpr);
        fputc(ch,fpw);
    }
    fclose(fpr);
    fpr = NULL;
    fclose(fpw);
    fpw = NULL;
    return 0;
}

获取文件长度

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

int main(){
	FILE* fp = fopen("test.txt", "r");
	fseek(fp, 0, SEEK_END);
	int len = ftell(fp);
	rewind(fp);
	printf("文件的长度为:%d", len);//文件中有 0D 0A
	return 0;
}

C语言输入/输出流和文件操作【二】

参考文献

  1. C语言中文网/首页 > C语言入门 > 文件操作
  2. C++中文 - API参考文档
  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jie3606

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值