文件读写

上一章我们讲解了 C 语言处理的标准输入和输出设备。本章我们将介绍 C 程序员如何创建、打开、关闭文本文件或二进制文件。

一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节。C 语言不仅提供了访问顶层的函数,也提供了底层(OS)调用来处理存储设备上的文件。本章将讲解文件管理的重要调用。

打开文件

您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:

  1. FILE *fopen( const char * filename, const char * mode );

在这里,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:

模式 描述
r 打开一个已有的文本文件,允许读取文件。
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:

  1. "rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

关闭文件

为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:

  1. int fclose( FILE *fp );

如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。

C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。

写入文件

下面是把字符写入到流中的最简单的函数:

  1. int fputc( int c, FILE *fp );

函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。您可以使用下面的函数来把一个以 null 结尾的字符串写入到流中:

  1. int fputs( const char *s, FILE *fp );

函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。您也可以使用

  1. int fprintf(FILE *fp,const char *format, …)

函数来把一个字符串写入到文件中。尝试下面的实例:

注意:请确保您有可用的 tmp 目录,如果不存在该目录,则需要在您的计算机上先创建该目录。

/tmp 一般是 Linux 系统上的临时目录,如果你在 Windows 系统上运行,则需要修改为本地环境中已存在的目录,例如: C:\tmpD:\tmp等。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. FILE *fp = NULL;
  5. fp = fopen("/tmp/test.txt", "w+");
  6. fprintf(fp, "This is testing for fprintf...\n");
  7. fputs("This is testing for fputs...\n", fp);
  8. fclose(fp);
  9. }

当上面的代码被编译和执行时,它会在 /tmp 目录中创建一个新的文件 test.txt,并使用两个不同的函数写入两行。接下来让我们来读取这个文件。

读取文件

下面是从文件读取单个字符的最简单的函数:

  1. int fgetc( FILE * fp );

fgetc() 函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回 EOF

下面的函数允许您从流中读取一个字符串:

  1. char *fgets( char *buf, int n, FILE *fp );

函数 fgets() 从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf,并在最后追加一个 null 字符来终止字符串。如果这个函数在读取最后一个字符之前就遇到一个换行符 '\n' 或文件的末尾 EOF,则只会返回读取到的字符,包括换行符。

您也可以使用:

  1. int fscanf(FILE *fp, const char *format, ...)

fscanf函数从文件中读取字符串,但是在遇到第一个空格和换行符时,它会停止读取。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. FILE *fp = NULL;
  5. char buff[255];
  6. fp = fopen("/tmp/test.txt", "r");
  7. fscanf(fp, "%s", buff);
  8. printf("1: %s\n", buff );
  9. fgets(buff, 255, (FILE*)fp);
  10. printf("2: %s\n", buff );
  11. fgets(buff, 255, (FILE*)fp);
  12. printf("3: %s\n", buff );
  13. fclose(fp);
  14. }

当上面的代码被编译和执行时,它会读取上一部分创建的文件,产生下列结果:

1: This

2: is testing for fprintf...

3: This is testing for fputs...

首先,fscanf() 方法只读取了 This,因为它在后边遇到了一个空格。其次,调用 fgets() 读取剩余的部分,直到行尾。最后,调用 fgets() 完整地读取第二行。

之所以会从上一次读取的位置继续读,是因为它会有一个指针指向上一次读取到那个位置,下一次再读取的时候直接从这个指针的位置开始,但前提是你没有在中途调用fclose函数来关闭文件。

二进制 I/O 函数

下面两个函数用于二进制输入和输出:

  1. size_t fread(void *ptr, size_t size_of_elements,
  2. size_t number_of_elements, FILE *a_file);
  3. size_t fwrite(const void *ptr, size_t size_of_elements,
  4. size_t number_of_elements, FILE *a_file);

这两个函数都是用于存储块的读写 - 通常是数组或结构体。

预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。

所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:

指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

预处理器实例

分析下面的实例来理解不同的指令。

  1. #define MAX_ARRAY_LENGTH 20

这个指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 替换为 20。使用 #define 定义常量来增强可读性。

  1. #include <stdio.h>
  2. #include "myheader.h"

第一行指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。

第二行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。

  1. #undef FILE_SIZE
  2. #define FILE_SIZE 42

这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。

  1. #ifndef MESSAGE
  2. #define MESSAGE "You wish!"
  3. #endif

这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。

  1. #ifdef DEBUG
  2. /* Your debugging statements here */
  3. #endif

这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果您向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。它定义了 DEBUG,您可以在编译期间随时开启或关闭调试。

预定义宏

ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。

描述
_DATE_ 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。
_TIME_ 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。
_FILE_ 这会包含当前文件名,一个字符串常量。
_LINE_ 这会包含当前行号,一个十进制常量。
_STDC_ 当编译器以 ANSI 标准编译时,则定义为 1。

让我们来尝试下面的实例:

  1. #include <stdio.h>
  2. main()
  3. {
  4. printf("File :%s\n", __FILE__ );
  5. printf("Date :%s\n", __DATE__ );
  6. printf("Time :%s\n", __TIME__ );
  7. printf("Line :%d\n", __LINE__ );
  8. printf("ANSI :%d\n", __STDC__ );
  9. }

当上面的代码(在文件 test.c 中)被编译和执行时,它会产生下列结果:

File :test.c

Date :Dec 12 2019

Time :16:34:13

Line :8

ANSI :1

预处理器运算符

C 预处理器提供了下列的运算符来帮助您创建宏:

宏延续运算符(\)

一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。例如:

  1. #define message_for(a, b) \
  2. printf(#a " and " #b ": We love you!\n")

字符串常量化运算符(#)

在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。例如:

  1. #include <stdio.h>
  2. #define message_for(a, b) \
  3. printf(#a " and " #b ": We love you!\n")
  4. int main(void)
  5. {
  6. message_for(Carole, Debra);
  7. return 0;
  8. }

当上面的代码被编译和执行时,它会产生下列结果:

Carole and Debra: We love you!

标记粘贴运算符(##)

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。例如:

  1. #include <stdio.h>
  2. #define tokenpaster(n) printf ("token" #n " = %d", token##n)
  3. int main(void)
  4. {
  5. int token34 = 40;
  6. tokenpaster(34);
  7. return 0;
  8. }

当上面的代码被编译和执行时,它会产生下列结果:

token34 = 40

这是怎么发生的,因为这个实例会从编译器产生下列的实际输出:

printf ("token34 = %d", token34);

这个实例演示了 token##n 会连接到 token34 中,在这里,我们使用了字符串常量化运算符(#)标记粘贴运算符(##)

defined() 运算符

预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。下面的实例演示了 defined() 运算符的用法:

  1. #include <stdio.h>
  2. #if !defined (MESSAGE)
  3. #define MESSAGE "You wish!"
  4. #endif
  5. int main(void)
  6. {
  7. printf("Here is the message: %s\n", MESSAGE);
  8. return 0;
  9. }

当上面的代码被编译和执行时,它会产生下列结果:

Here is the message: You wish!

参数化的宏

CPP 一个强大的功能是可以使用参数化的宏来模拟函数。例如,下面的代码是计算一个数的平方:

  1. int square(int x) {
  2. return x * x;
  3. }

我们可以使用宏重写上面的代码,如下:

  1. #define square(x) ((x) * (x))

在使用带有参数的宏之前,必须使用 #define 指令定义。参数列表是括在圆括号内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格。例如:

  1. #include <stdio.h>
  2. #define MAX(x,y) ((x) > (y) ? (x) : (y))
  3. int main(void)
  4. {
  5. printf("Max between 20 and 10 is %d\n", MAX(10, 20));
  6. return 0;
  7. }

当上面的代码被编译和执行时,它会产生下列结果:

Max between 20 and 10 is 20


参考自:https://www.runoob.com/cprogramming/c-tutorial.html

C语言笔记 10_文件读写&预处理器的更多相关文章

  1. C学习笔记(9)--- 预处理器,头文件

    1.预处理器: 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤.简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理. 我们将把 C 预处理 ...

  2. 转载-Python学习笔记之文件读写

    Python 文件读写 Python内置了读写文件的函数,用法和C是兼容的.本节介绍内容大致有:文件的打开/关闭.文件对象.文件的读写等. 本章节仅示例介绍 TXT 类型文档的读写,也就是最基础的文件 ...

  3. 【java学习笔记】文件读写(IO流)

    1.字节流 FileInputStream.FileOutputStream ①FileInputStream import java.io.FileInputStream; public class ...

  4. Python学习笔记九-文件读写

    1,读取文件: f=open('目录','读写模式',encoding='gbk,error='egiong') 后三项可以不写但是默认是' r'读模式:open函数打开的文件对象会自动加上read( ...

  5. python学习笔记 IO 文件读写

    读写文件是最常见的IO操作.python内置了读写文件的函数. 读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统完成的,现代操作系统不允许普通的程序直接对磁盘进行操作,所以, 读写 ...

  6. hadoop笔记-hdfs文件读写

    概念 文件系统 磁盘进行读写的最小单位:数据块,文件系统构建于磁盘之上,文件系统的块大小是磁盘块的整数倍. 文件系统块一般为几千字节,磁盘块一般512字节. hdfs的block.pocket.chu ...

  7. 《C和指针》 读书笔记 -- 第14章 预处理器

    1.相邻字符串常量被自动链接为一个字符串:"my""name"="myname" 2.##把位于两边的符号连接成一个符号: #define ...

  8. java笔记----property文件读写

    package com.test.property; import java.io.BufferedInputStream; import java.io.File; import java.io.F ...

  9. iOS笔记之文件读写

    最近做的项目中要存储一组图片数据,图片带有name,date等属性,处理办法是讲image图片直接存在沙盒documents文件中,再使用plist文件存储图片属性和image路径. 存入图片: - ...

随机推荐

  1. Codeforces Round #614 (Div. 2) C - NEKO's Maze Game

    题目链接:http://codeforces.com/contest/1293/problem/C 题目:给定一个 2*n的地图,初始地图没有岩浆,都可以走, 给定q个询问,每个询问给定一个点(x,y ...

  2. JUC-线程间通信

    面试题: 两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z, 要求用线程间通信 线程间通信:1.生产者+消费者2.通知等待唤醒机制 多线程编程模版中 1.判断 ...

  3. repeater列表中直接修改状态

    <asp:Repeater ID="RepeaterArticleList" runat="server" onitemdatabound="R ...

  4. t-SNE

    Don't look back. Don't hesitate, just do it. t-SNE原理 from here. 1. tsne is strictly used for visuali ...

  5. Java中的IO、NIO、File、BIO、AIO详解

    java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?         Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包 ...

  6. Spring核心知识

    目录 Spring 概述 依赖注入 Spring beans Spring注解 Spring数据访问 Spring面向切面编程(AOP) Spring MVC Spring 概述 1. 什么是spri ...

  7. 当要打开PDB时为何会有Warning: PDB altered with errors.

    对PDB执行 alter pluggable database pdbprod2 open; 操作后提示:Warning: PDB altered with errors. 来自AskScuti博客园 ...

  8. 02-Java基础语法【数据类型转换、运算符、方法入门】

    重点知识记录 01.数据类型转换 当数据类型不一样是,将会发生数据类型转换. 1)自动类型转换(隐式): 特点:代码不需要进行特殊处理,自动完成: 规则:数据范围从小到大:byte < shor ...

  9. IntelliJ IDEA 2017.3百度-----树状结构

    ------------恢复内容开始------------ ------------恢复内容结束------------

  10. &nbsp;&laquo;&raquo;&lt;&gt;

     空格   «双小于 »双大于 <小于 >大于