预处理和文件操作
预处理是在编译前所做的工作,编译器自动调用预处理程序对源码中以’#’开头的预处理部分进行处理,处理完毕后,进入源码的编译阶段。
一、宏定义
宏定义,又称为宏替换,自定义一个宏(要符合标识符的命名规则),用于替换任意数据、标识符或者表达式。
1、无参宏定义
定义无参宏的基本格式:#define 宏名 宏替换 比如:
不能给宏定义的常量赋值 如:
#define A 35 //宏A就代表整型数据35. #define TEST "abddgdsfdlfj" //宏TEST就代表字符串"abddgdsfdlfj" #define TEST abddgdsfdlfj //宏TEST就代表abddgdsfdlfj #define I int //宏I就代表int(相当于给数据类型取别名)
不能给宏定义的常量赋值 如:
A = 66; // 错误,不能给宏定义常量赋值
2、带参宏定义
定义带参宏的基本格式: #define 宏名(参数表) 宏替换 带参宏可以像函数一样调用,比如:
1. #define M(a,b) a*b-2 2. K = M(1,2) * 4; //这里的M(1,2)会被替换成1*2-2 3. //运算过程:K = 1*2-2*4 = 2-8 = -6 4. #define MAX(x,y) x?y:x,y //求最大值
注意:宏定义是替换,其在替换完成前并不会计算。 宏定义尽量用大写,使其在程序中容易辨别区分。
3、常量的定义
除了宏定义以外,还可以通过const关键字定义常量: 普通常量: < const> <数据类型> <常量名> = <常量值>; 如:
const int a = 30; //定义一个常量a,其值等于30
定义成常量后,值不可被改变 如:
a=40; //错误,不能给常量赋值
二、文件包含
1、包含库函数头文件
我们想要用库函数就需要包含头文件,也就是文件包含,当然也可以编写自定义头文件,包含自己编写的头文件。如:
1. #include <stdio.h> 2. //包含系统头文件用<>,只会在系统头文件中找 3. #include "name.h" 4. //包含自定义头文件用””,先在自定义头文件中找,找不到就会在系统头文件中找
文件包含允许嵌套,即在一个被包含文件中可以包含其它文件。
2、头文件的重复包含
头文件的嵌套包含可能会引起头文件的重复包含,从而出现函数和变量的重定义问题 所以需要避免头文件重复包含,某些宏定义语句可以防止头文件重复包含,如:
#pragma once //防止头文件重复包含,不让文件的内容被包含两次,在头文件最前面添加
#pragma once是vs独有的,有使用平台的限制,其他平台可能不存在
3、C程序的分文件编写
多文件编程就是把多个头文件(.h文件)和源文件(.c文件)组合在一起构成一个程序,这是C语言的重点,也是C语言的难点。 C语言头文件的编写是其中的重点内容,有很多细节需要注意,文件编程既涉及到了内存,也涉及到了编译原理,有的甚至会让我们感觉奇怪。 学会了多文件编程,就可以使用C语言来开发中大型项目了。 在开发中大型项目中,一般用头文件封装函数的声明,在源文件里完成函数功能的定义。
三、文件操作
1、什么是文件?
文件有不同的类型,在程序设计中,主要用到两种文件:
(1)程序文件。
包括源程序文件(后缀名为.c)、目标文件(后缀名为.obj)、可执行文件(后缀名为.exe)等。这一类型的文件主要用于存储程序代码。
(2)数据文件。
此文件的内容不是程序,而是程序运行时读写的数据,比如程序运行过程中输出到磁盘或其他设备上的数据,或在程序运行过程中供程序读取的数据。 这里C语言的文件操作主要是对数据文件的操作。 在之前程序中所处理的数据的输入和输出都是以终端为对象的,都是从键盘输入数据,然后运行结果输出到终端显示器上。实际上,我们有时候需要将一些数据(程序运行的最终结果或者中间数据)保存起来,方便以后需要时再调用,而这就需要用到磁盘文件了。
2、文件的概念
1)文件名
每一个文件都需要一个唯一的文件标识,以便用户使用,就像我们的变量名一样,同一程序中不能有相同的变量名。 文件标识也称为文件名,它由3部分组成: ①文件路径:表示文件在外存设备中的存储位置; ②文件名主干:表示文件的名字,可由用户自定义,命名规则应遵循标识符的命名规则。 ③文件后缀:表示文件的性质,也称为文件的格式,用于描述文件的类型(如txt、ppt等)。
文件路径能唯一标识文件在外存中的位置。 如:D:\C++\VSproject\TEXT\text.c
2)文件的分类
根据数据的组织形式,数据文件可分为ASCII文件和二进制文件。 数据在内存中是以二进制形式存储的,如果不加转换的输出到外存,就是二进制文件,可以认为它是存储在内存的数据的映像,所以称之为映像文件。如果要求在外存上以ASCII码形式存储,就需要在存储前进行转换。ASCII文件又称为文本文件,每一个字节存放一个字符的ACSII码。
3)文件存储方法的区别
一个数据在磁盘上存储,字符一律以ASCII形式存储,数值型既可以用ASCII形式存储也可以用二进制形式存储。如整数10000,用ASCII码形式存储在磁盘上占5个字节(每个字符占一个字节),而用二进制形式存储在磁盘上只占4个字节(00000000 00000000 00100111 00010000)。用ASCII形式存储时字符与字节一一对应,一个字节代表一个字符,便于逐个处理,但占的存储空间较多,而且处理的时候要花费转换时间(二进制与ASCII码之间的转换)。二进制形式存储就相当于直接把内存中的内容原封不动存储在磁盘上,由于不需要转换,所以二进制文件更加方便计算机处理。
3、指向文件的指针
C语言要想操作内存,需要用到各种数据类型的指针,而文件也是需要调用到内存中才能够使用,所以就需要用到“指向文件的指针”,就是“文件类型的指针”,简称“文件指针”。 每一个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的相关信息(如文件的名字、文件的状态和文件的位置等)。这些信息是保存在一个结构体变量中的,此结构体类型是由系统声明的,取名为FILE,其被包含在stdio.h头文件中。
1)文件指针的定义
由于文件类型已经在stdio.h头文件中有声明了,所以我们不需要另外声明,直接使用就行了。 文件类型变量的定义格式为:FILE 文件变量名; 如:
FILE f1;
定义一个结构体变量f1,用它来存放一个文件的相关信息。这些信息是在打开文件时由系统根据文件的情况自动放入的,用户不必过问。 我们一般不会用文件变量来访问文件,而使用文件指针来访问文件。
文件指针的定义格式为:FILE *文件指针名;如:
FILE *fp;
定义一个指针fp用于指向FILE类型的数据,可以使fp指向某一个文件在内存中的文件信息区(结构体变量),通过此文件信息区能够访问此文件。也就是说通过文件指针变量能够找到并可以操作其指向的文件。
2)文件的打开与关闭
①使用fopen函数打开文件
C语言规定用文件标准输入输出函数fopen来实现打开文件。 fopen函数的调用方式为: fopen(文件名,使用文件的方式); 例如:fopen(“text1.txt”,”r”); 意思是以只读的方式打开名为“text1.txt”件。 此时fopen函数返回的是“text1.txt”文件的起始地址,我们通常将fopen函数的返回值赋值给一个文件指针,用文件指针指向此文件的地址。 例如:FILE *fp;//定义一个文件指针fp fp=fopen(“text1.txt”,”r”);//使fp指向文件“text1.txt”的首地址 这样fp就与文件“text1.txt”有联系了。
在打开一个文件时,通知编译系统一下3个信息:打开的文件名称、文件的打开方式、使用哪个文件指针指向被打开的文件。
②文件的打开方式
文件打开方式与含义如下表所示:
③使用fclose函数关闭文件
在使用完一个文件之后,为了防止它被误用,应该关闭它。“关闭”就是撤销文件信息区和文件缓冲区,使指针不再指向此文件了,也无法操作此文件了,除非重新打开此文件,使指针指向此文件。 关闭文件用fclose函数。 fclose函数的一般形式为: fclose(文件指针); 例如:
fclose (fp)
在每次程序终止之前都要养成习惯关闭所有的文件。
当fclose函数成功关闭文件时,返回0;否则返回EOF(-1)。
4、顺序读写文件
文件打开完成之后就可以对它进行读写操作了,这就有使用到常用的文件操作函数了。
1)字符输入和输出函数
使用字符读取函数fgetc从文件读取一个字符
ch=fgetc(fp)
从文件指针fp指向的位置读取一个字符存入字符变量ch中, 读取成功返回所读的字符,失败则返回为你文件结束
标志EOF(-1)。
使用字符写入函数fputc向文件写入一个字符
fputc(ch,fp);
向文件指针fp指向的位置写入字符ch, 写入成功返回输出的字符,失败则返回EOF(-1)。
2)字符串输入和输出函数
使用字符串读取函数fgets从文件读取一个字符串
fgets(str,n,fp)
从文件指针fp指向的位置读取一个长度位n-1的字符串(最后一位赋值‘\0’,用作字符串结束标志),存放在字符数组str中。 读取成功返回地址str,失败则返回NULL。
使用字符串写入函数fputs向文件写入一个字符串
fputs(str,fp)
把str所指向的字符串写入文件指针fp指向的位置, 写入成功返回0,否则返回非0值。
3)格式化输入和输出函数
使用格式化输出函数fprintf向文件写入数据 fprintf(文件指针,“格式化字符串”,输出列表); 例如:
fprintf(fp,"%d,%c",x,y);
将变量x以整型的形式写入文件指针fp指向的位置,把变量y以字符的形式写入文件指针fp指向的位置。使用格式化输入函数fscanf从文件读取数据 fscanf(文件指针,“格式化字符串”,输入列表); 例如:
fscanf(fp,"%d,%f",&x,&y);
从文件指针fp指向的位置读入一个整型数据和一个单精度型数据,分别存入变量x和变量y中。
4)以二进制的形式读写数据
fread(buffer,size,count,fp); buИer是一个地址(数组),用于存储从文件读取出来的数据,size为需要读取的字节数,count为需要读取数据项的个数(每个数据项的大小为size)。fwrite(buffer,size,count,fp); 从文件指针fp所指向的文件中读取size个count大小的数据放入数组buffer中。
5、随机读写文件
1)文件指针位置标记及定位
文件指针打开文件后默认指向文件的开头,也就是第一个数据的位置,此后每一次读取或写入,文件指针自动向后移动到与数据大小对应文件位置。
2)强制使文件指针指向文件开头
使用rewind函数强制使文件指针fp指向文件开头的位置。 如:
rewind(fp);
3)使文件指针指向文件中任意位置
使用fseek函数使文件指针指向文件中任意位置。 fseek(fp,位移量,起始点); 起始点用0、1、2代替,0代表文件开始位置,1代表当前位置,2代表文件末尾位置。 位移量是指以起始点为基础,向前移动的字节数。位移量应为long型数据(在末尾加上一个字母L就表示是long型)。 例如:
1. fseek(fp,100,0); //将文件指针fp向后移动到离文件开头100个字节处; 2. fseek(fp,50,1); //将文件指针fp向后移动到离当前位置50个字节处; 3. fseek(fp,-10,2); //将文件指针fp向前移动到离文件末尾10个字节处;
fseek一般用于二进制文件。
用rewind和fseek函数可以实现文件的随机读写。
四、条件编译
条件编译也就是根据不同的条件编译不同代码段。
1、#if……#else的使用
1. #if 表达式 2. //判断表达式的逻辑值(真或假),若逻辑值为真,则编译代码段1,否则编译代码段2. 3. 代码段1; 4. #else 5. 代码段2; 6. #endif
2、#ifdef……#endif的使用
1. #ifdef 宏名 2. //如果定义了宏"宏名",则编译代码段. 3. 代码段; 4. #endif 5. #ifdef 宏名 6. //如果定义了宏"宏名",则编译代码段1,否则编译代码段2; 7. 代码段1; 8. #else 9. 代码段2; 10. #endif
3、#ifndef……#endif的使用
1. #ifndef 宏名 2. //如果没有定义宏"宏名",则编译代码段 3. 代码段; 4. #endif 5. #ifndef 宏名 6. //如果没有定义宏"宏名",则编译代码段1,否则编译代码段2; 7. 代码段1; 8. #else 9. 代码段2; 10. #endif