1.什么是文件
(1)程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
(2)数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
2.文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。文件名包含3部分:文件路径+文件名主干+文件后缀例如:c:\code\test.txt
为了方便起见,文件标识常被称为文件名
3.文件的打开和关闭
3.1文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
3.2 文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
使用方法:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* ps = fopen("test.txt", "r");
if (ps == NULL)
{
printf("%s", strerror(errno));
return 1;
}
fclose(ps);
ps = NULL;
return 0;
}
4. 文件的顺序读写
使用方法:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* ps = fopen("text.txt", "w");
if (ps == NULL)
{
printf("%s", strerror(errno));
}
fputc('a', ps);
fclose(ps);
ps = NULL;
return 0;
}
如上图可见,程序要求达到的写入‘a’的目的已经达到。
(1)fprintf:将内容写入文件
#include<stdio.h>
#include<string.h>
#include<errno.h>
struct S
{
char name[20];
int age[10];
int score;
};
int main()
{
struct S s = { "zyx",20,100};
FILE* ps = fopen("text.txt", "w");
if (ps == NULL)
{
printf("%s", strerror(errno));
}
fprintf(ps, "%s %d %d", s.name, s.age, s.score);
return 0;
}
(2)fscanf:将文件读出内存
与上面函数类似。
5.scanf/fscanf/sscanf
printf/fprintf/sprintf 的对比
6. 文件的随机读写
6.1 fseek
这个函数就是让文件在指定位置读取。
int fseek ( FILE * stream, long int offset, int origin );
6.2 ftell
返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
6.3 rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );
7. 文件读取结束的判定
7.1 被错误使用的feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)例如:
fgetc判断是否为EOF.fgets判断返回值是否为NULL.2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。
使用方法:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* ps = fopen("test.txt", "r");
if (ps == NULL)
{
printf("%s", strerror(errno));
return 1;
}
int str = fgetc(ps);
while (str != EOF)
{
printf("%c", str);
}
if (ps == NULL)
{
printf("%s", strerror(errno));
return 1;
}
else if (feof(ps))
printf("End of file reached successfully");
fclose(ps);
ps=NULL;
return 0;
}
8. 文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
9.预处理详解
9.1 预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
10.#define
10.2 #define 定义宏
使用方法:
#include<stdio.h>
#define SARE(X) X*X
int main()
{
int r = SARE(5);
printf("%d", r);
return 0;
}
10.3 #define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
10.4 #和##
使用方法:
#include<stdio.h>
#define Paint(N)printf("the value of "#N" the is %d\n ",N)
int main()
{
int a = 10;
Paint(a);
return 0;
}
“#N”---->""N""
#include<stdio.h>
#define PAINT(N,FLOAT)printf("the value of "#N" is "FLOAT"\n",N)
int main()
{
int a = 10;
PAINT(a,"%d");
float b = 3.14f;
PAINT(b,"%lf");
return 0;
}
"##"的用法
##可以把两个字符串合为一个。Class加上Num=106合为Class106输出100.
10.5 带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能
出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:
x+1;//不带副作用
x++;//带有副作用
10.6 宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个.
原因有二:
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。
宏是类型无关的。
宏的缺点:当然和函数相比宏也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序
的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏的巧妙应用:
malloc(40,int);参数部分传类型。
#include<stdio.h>
#include<stdlib.h>
#define MALLOC(num,type)(type*)malloc((num)*sizeof(type))
int main()
{
int* p = MALLOC(40, int);
return 0;
}
宏和函数的对比:
代码简单用宏,代码复杂用函数。
10.7 命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:
把宏名全部大写
函数名不要全部大写
10.8#undef
这条指令用于移除一个宏定义
10.9条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
如:
1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
#include<stdio.h>
//#define _DEBUG_
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i < 10; i++)
{
arr[i] = i;
#ifdef _DEBUG_
printf("%d\n", arr[i]);
#endif
}
return 0;
}
2.
多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
#include<stdio.h>
#define M 5
int main()
{
#if M==5
printf("哈哈\n");
#elif M<5
printf("呵呵\n");
#else M>5
printf("搜搜\n");
#endif
printf("嘻嘻\n");
return 0;
}
3.
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
#include<stdio.h>
#define M 100
int main()
{
#if defined(M)
printf("%d\n", M);
#endif
return 0;
}
4.
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
例子:类似if语句。
10.10文件包含
我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。
因此,我们可以利用#define来防止头文件被包含多次。
如:
#pragma once
或
#ifnded _TEST_H_
#define _TEST_H_
int ADD(int x,int y)
#endif