这几天学习了一下C语言的文件操作以及编译和链接,今天我想来分享一下所学。
1.为什么使用文件?
假如没有“文件”这件事情,我们写的程序都会在内存里面存放,而一旦断电程序就没有了。但如果我们有文件,我们就可以存放这些程序,进行长久的保存。
2.什么是文件?
文件是存放在硬盘上的数据,存放在外存中,需要使用时调入内存进行使用。我们分为数据文件和程序文件两类。
2.1.程序文件
程序文件有3类:源程序文件、目标程序文件、可执行程序。
2.2.数据文件
是指在程序运行时产生的数据。
2.3.文件名
每一个文件要有唯一的文件标识,便于用户使用。
文件包含以下3个部分,先举例c:\code\text.txt
其中c:\code\是文件路径,text是文件名主干,.txt是文件名后缀。
3.二进制文件和文本文件?
二进制文件是以二进制形式存放数据,文本文件是以ascii码形式存放数据,即:存放10000(文本文件)消耗5字节,而存放10000(二进制文件)消耗4字节。
4.文件的打开和关闭
我们用fopen函数来打开文件,fclose函数来关闭文件。
具体fopen函数、fclose函数可以参考cplusplus.com来查询使用方法。
#include<stdio.h>
#pragma warning(disable:4996)
int main()
{
FILE* pf = fopen("新建 DOC 文档.doc","w");
fclose(pf);
pf = NULL;
return 0;
}
其中w是写入文件,会覆盖以前的文件;r是只读,wb是写入二进制文件。rb是只读二进制文件。
5.文件的顺序读写
下面我用代码来说明以下10种函数:fgetc,fputc,fgets,fputs,fprintf,fscanf,sscanf,sprintf,fread,fwrite
#include<stdio.h>
#pragma warning(disable:4996)
int main()
{
FILE* pf = fopen("新建 DOC 文档.doc","w");
FILE* pf2 = pf;
if (pf == NULL)
{
perror("fopen");
return 1;
}
int t = 0;
for (t = 'b'; t < 'z'; t++)
{
fputc(t, pf);
}
int num = 0;
pf = pf2;
while (fgetc(pf) != EOF)
{
num++;
}
printf("%d", num);
fclose(pf);
pf = NULL;
return 0;
}
其中fgetc函数的功能是从文本读取数据,适用于所有输入流。
fputc函数的功能是输出到文本,适用于所有输出流。
#include<stdio.h>
int main()
{
FILE* pf = fopen("test2.doc", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写一行
fputs("hello world\n", pf);
fputs("hehe\n", pf);
fclose(pf);
pf = NULL;
return 0;
}
#include<stdio.h>
int main()
{
FILE* pf = fopen("test2.doc", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[20] = "abcdeftttttttt";
fgets(arr,5,pf);//类似于有限制的完全替换
printf("%s\n", arr);
fclose(pf);
pf = NULL;
return 0;
}
其中fgets函数的功能是从文件中读取字符串,适用于所有输入流;
fputs函数的功能是输出字符串到文件中,适用于所有输出流。
#include<stdio.h>
struct Stu
{
int a;
float b;
char arr[20];
};
int main()
{
FILE* pf = fopen("test2.doc","r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
struct Stu s = {0};
fscanf(pf,"%d %f %s", & (s.a), &(s.b), s.arr);//额外小心与scanf的对比
printf("%d %f %s\n", s.a, s.b, s.arr);
fclose(pf);
pf = NULL;
return 0;
}
#include<stdio.h>
struct S
{
int n;
float f;
char arr[20];
};
int main()
{
struct S s = {0};
FILE* pf = fopen("test2.doc", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf, "%d %f %s", &(s.n), &(s.f), s.arr);
printf("%d %f %s\n", s.n, s.f, s.arr);
fclose(pf);
pf = NULL;
return 0;
}
其中fscanf函数的功能是从文件中读取特定格式,适用于所有输入流。
fprintf函数的功能是输出到文件中的特定格式,适用于所有输出流。
#include<stdio.h>
struct S
{
int n;
float f;
char arr[20];
};
int main()
{
struct S s = { 100, 3.14f, "zhangsan"};
char arr[30] = { 0 };
sprintf(arr, "%d %f %s", s.n, s.f, s.arr);
printf("%s\n", arr);
//从arr 这个字符串中提取出格式化的数据
struct S t = { 0 };
sscanf(arr, "%d %f %s", &(t.n), &(t.f), t.arr);
printf("%d %f %s\n", t.n, t.f, t.arr);
return 0;
}
#include<stdio.h>
struct Stu
{
int a;
float b;
char arr[11];
};
int main()
{
struct Stu s = { 12,3.14f,"helloworld" };
char str[11] = { 0 };
sprintf(str,"%d %f %s",s.a,s.b,s.arr);//把s的格式弄到str指向的空间
printf("%s\n", str);//打印验证
struct Stu t = { 0 };
sscanf(str,"%d %f %s",&(t.a),&(t.b),t.arr);//把str里面的格式数据弄到t的结构体里面
printf("%d %f %s\n", t.a, t.b, t.arr);
return 0;
}
sscanf函数的功能是从字符串中读取格式化数据,适用于所有输入流。
sprintf函数的功能是以字符串格式输出数据,适用于所有输出流。
#include<stdio.h>
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,0};
FILE* pf = fopen("test1.doc", "wb");
/*FILE* pff = NULL;*/
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(arr1,sizeof(int),10,pf);//以二进制写到file里面去
fclose(pf);
pf = NULL;
/*FILE* pff = fopen("test1.doc", "rb");*/
int arr2[10] = { 0 };
FILE*pff = fopen("test1.doc", "rb");//此处要改文件操作,那么上一步需要先关闭一下文件,以满足文件开关的基本逻辑
if (pff == NULL)
{
perror("fopen");
return 1;
}
fread(arr2, sizeof(int),10, pff);
int t = 0;
for (t = 0; t < 9; t++)
{
printf("%d ", arr2[t]);
}
fclose(pff);
pff = NULL;
return 0;
}
fread函数的功能是文件本身读取外界二进制数据,只适用于文件。
fwrite函数的功能是文件输出二进制数据给外界,只适用于文件。
6.文件的随机读写
下面有fseek、ftell、rewind函数,下面我用代码来介绍。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
FILE* pf = fopen("test1.doc", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//定位文件指针
//fseek(pf, 6,SEEK_SET);
//fseek(pf, -3, SEEK_END);
int pos = ftell(pf);
int ch = fgetc(pf);
pos = ftell(pf);
printf("%c\n", ch);//自0位置偏移到1位置处
fseek(pf, -2, SEEK_END);//自当前1位置处偏移到6位置处
pos = ftell(pf);
ch = fgetc(pf);//自6位置处偏移到7位置处,但此处取的是6位置处的元素
pos = ftell(pf);
printf("%c\n", ch);//
printf("%d\n", pos);//0起始6偏移+1偏移=7
rewind(pf);//复原
pos = ftell(pf);
ch = fgetc(pf);
pos = ftell(pf);
printf("%c\n", ch);//a首元素
fclose(pf);
pf = NULL;
return 0;
}
fseek函数的功能是偏移文件中的“指针”进行读取,ftell函数的功能是返回文件中的“指针”位置,rewind函数的功能是返回指针位置到起始位置0处。
7.文件读取结束的判定
我们用feof函数(判断是否遇到文本尾部结束)、ferror函数(判断是否读取错误)、fread函数(判断返回值是否和想要交换数据的量)传文件指针来根据返回值来判断文件读取是否完全。
8.文件缓冲区
文件其实是有缓冲区的,缓冲区属于内存的一部分,程序是要在内存中通过文件缓冲区再输到硬盘上的。
9.编译环境和运行环境
翻译环境分为编译环境和链接环境两部分。
10.编译环境:预编译+编译+汇编+链接
整套流程大致为:.c文件先经过预处理(或预编译)将头文件包含,删除#define,删除注释等生成中间的.i文件,随后通过编译(词法分析、语法分析、语义分析)把C语言代码翻译成汇编代码生成中间的.s文件,再通过汇编把汇编代码编译成二进制代码,成为.o或.obj的目标文件,最后通过链接来找到各个元素对应的编号连成一条线来合并相关地址,最终生成.exe的可执行程序。