1.C文件操作
1.1文件的使用方式
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "w+");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//输入一个字符
fputc('a', pf);
//用完关闭文件
fclose(pf);
pf = NULL;
system("pause");
return 0;
}
vs2017
1.1.1写入一个字符
//写文件
fputc('a', pf);
1.1.2读取一个字符
//读取一个字符
int ch = fgetc(pf);
if (ch != EOF)
{
printf("%c\n", ch);
}
1.1.3连续每次读取一个字符
//文件中有abcdefg
int ch = fgetc(pf);
printf("%c\n", ch); //a
ch = fgetc(pf);
printf("%c\n", ch); //b
ch = fgetc(pf);
printf("%c\n", ch); //c
ch = fgetc(pf);
printf("%c\n", ch); //d
1.1.4覆盖并写入一行数据
fputs("hello world", pf);
1.1.5读取指定长度的数据
//定一一个数组
char arr[10] = { 0 };
fgets(arr, 5, pf); //将所读取的数据放入arr中
printf("%s\n", arr);
1.1.6将结构体信息写入文件中
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
Peo p = { "zhangsan", 18 };
fprintf(pf, "%s %d\n", p.name, p.age);
//用完关闭文件
fclose(pf);
pf = NULL;
system("pause");
return 0;
}
1.1.7读取文件信息到结构体变量中
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
Peo p = { 0 };
fscanf(pf, "%s %d", p.name, &p.age);
printf("%s %d", p.name, p.age);
//用完关闭文件
fclose(pf);
pf = NULL;
system("pause");
return 0;
}
1.1.8二进制写入文件
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "wb+");
if (pf != NULL)
{
Peo p = { "lisi", 19 };
fwrite(&p, sizeof(Peo), 1, pf);
fclose(pf);
pf = NULL;
}
system("pause");
return 0;
}
1.1.9读取二进制文件信息
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "rb+");
if (pf != NULL)
{
Peo p = { 0 };
fread(&p, sizeof(Peo), 1, pf);
printf("%s %d\n", p.name, p.age);
fclose(pf);
pf = NULL;
}
system("pause");
return 0;
}
1.1.10 sscanf()
#include<stdio.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
//定义一个字符串
char buffer[] = { "zhansan 19" };
//定义一个结构但不赋值
Peo p = { 0 };
sscanf(buffer, "%s %d", p.name, &p.age);
return 0;
}
1.1.11 sprintf()
#include<stdio.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
//定义一个结构
Peo p = { "zhangsan",19};
//定义一个字符串
char buffer[50] = { 0 };
sprintf(buffer, "%s %d\n", p.name, p.age);
return 0;
}
1.2文件的随机读写
1.2.1 fseek()
该函数可以从定位位置的偏移量处开始读写;
int fseek( FILE *stream, long offset, int origin );
文件流 偏移量 起始位置
返回值:
如果成功,fseek返回0;
否则,它返回一个非零值;
在无法查找的设备上,返回值未定义;
三种定位指针:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//开始多次读取
//定位指针:比如要读取从头开始向后偏移 2 个单位的一个字符
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
//第二次读取:要拿到当前文件指针所处位置向后偏移5个单位的字符
fseek(pf, 5, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
//第三次读取:要拿到文件流末尾向前偏移8个单位的一个字符
fseek(pf, -8, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);
system("pause");
return 0;
}
特别说明:
在每使用完一次fseek函数后,文件指针会自动向后移动一位:
1.2.2 ftell()
- 该函数可以返回文件指针相对于起始位置的偏移量;
- long int ftell ( FILE * stream );
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//开始多次读取
//定位指针:比如要读取从头开始向后偏移 2 个单位的一个字符
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
printf("%d\n", ftell(pf));
//第二次读取:要拿到当前文件指针所处位置向后偏移5个单位的字符
fseek(pf, 5, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
printf("%d\n", ftell(pf));
//第三次读取:要拿到文件流末尾向前偏移8个单位的一个字符
fseek(pf, -8, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);
printf("%d\n", ftell(pf));
system("pause");
return 0;
}
1.2.3 rewind()
- 让文件指针回到文件初始位置;
- void rewind ( FILE * stream );
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//开始多次读取
//定位指针:比如要读取从头开始向后偏移 2 个单位的一个字符
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
printf("%d\n", ftell(pf));
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);
printf("%d\n", ftell(pf));
system("pause");
return 0;
}
1.3二进制文件和文本文件
我们知道数据在内存中是以二进制形式存储的,对于文件而言:如果不加转换直接输出到外存就是二进制文件;如果要在外存上以ASCII码形式存储,就需要提前转换最后以ASCII码值形式存储的文件就是文本文件。
对于字符,一律使用ASCII码形式存储,但对于数值型数据,即可以使用ASCII码存储也可以使用二进制形式存储。
举例:
数字10000的两种存储形式:
二进制文件:
文本文件:
首先将10000分成'1','0','0','0','0', 这五个字符,用每个字符对应的ASCII码值进行转换:
由此可见:对于10000在数,如果以二进制形式存储占用4个字节,如果以ASCII码存储占用5个字节。试想:那对于数字1呢?
显而易见,二进制文件存储和文本文件存储对不同范围的数字可以做到节省空间。
1.4对二进制文件深入理解
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
typedef struct S
{
char name[10];
int age;
}Peo;
int main()
{
FILE* pf = fopen("test.txt", "wb");
int a = 10000;
if (pf != NULL)
{
fwrite(&a, 4, 1, pf);
fclose(pf);
pf = NULL;
}
system("pause");
return 0;
}
对于上面这段代码,我们知道是将数值10000放入了test.txt文件中,但我们无法直接看到它在文件中的真实值,于是使用vs的二进制编辑器即可查看:
1.5文件结束判定
1.5.1 feof()
该函数被许多人错误用来判断文件是否读取结束,其实它的作用是判断文件读取结束的原因;
文件读取结束有两种情况:1.读取过程中出现异常; 2.读取到文件末尾;
要找出文件读取是哪个原因,就分为以下情况:
文本文件:
如果用 fgetc() 读取,要判断 feof() 的返回值是否为EOF;
如果用 fgets() 读取,要判断 feof() 的返回值是否为NULL(0);
二进制文件:都是使用 fread() 读取,要判断其返回值与指定读取个数的大小,如果小于实际要读的个数,就说明发生读取异常,如果等于实际要读的个数,就说明是因读取成功而结束;
对于读取异常的判断,我们考虑判断 ferror() 函数的返回值:
若ferrror()为真——异常读取而结束;
若feof()为真——正常读取到尾而结束;
1.5.2文本文件的判断
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen is failed !");
return;
}
int c = 0;
//由于要检查EOF——EOF本质是0——所以是int
while (c = fgetc(pf) != EOF)
{
putchar(c);
}
//直到while不执行了—读取结束了—判断是什么原因结束的
if (ferror(pf))
{
printf("读取中出现错误\n");
}
else if (feof(pf))
{
printf("读取到文件尾\n");
}
fclose(pf);
pf = NULL;
return 0;
}
1.5.3二进制文件的判断
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "rb");
int arr[5] = { 0 };
if (pf == NULL)
{
return;
}
size_t num = fread(arr, sizeof(int), 5, pf);
if (num == 5)
{
//说明全部读取成功
printf("Array read successfully\n");
}
else
{
//说明读取不够指定长度—判断是什么原因
if (ferror(pf))
{
printf("读取中出现错误\n");
}
else if (feof(pf))
{
printf("读取到文件尾\n");
}
}
fclose(pf);
pf = NULL;
return 0;
}
2.C++文件操作
2.1打开和关闭文件
2.1.1打开文件
2.1.1.1示例一 打开一个输入文件流对象,并以读模式读取文件内容
ifstream inFile; //建立输入文件流对象
inFile.open("data.txt",ios::in); //连接文件,指定打开模式
// 也可以使用第二种方式打开,语句如下:
ifstream inFile("data.txt",ios::in);
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <fstream> // 包含文件流头文件
#include <iostream>
using namespace std;
int main() {
ifstream inFile; // 定义输入文件流对象
inFile.open("test.txt", ios::in); // 打开名为 "data.txt" 的输入文件,以读模式打开
if (!inFile) { // 如果打开文件失败
cerr << "Can't open file!" << endl; // 输出错误信息到标准错误流
return 1; // 返回 1,表示程序异常结束
}
// ... 在此处可以进行文件读取操作 ...
inFile.close(); // 关闭输入文件流
system("pause");
return 0; // 正常结束程序
}
2.1.1.2示例二 打开一个输入文件流对象,并以默认读模式读取文件内容:
ifstream inFile; //建立输入文件流对象
inFile.open("data.txt"); //没有指定打开模式,默认以in方式打开文本文件
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include <fstream> // 包含文件流头文件
#include <iostream>
using namespace std;
int main() {
ifstream inFile; // 定义输入文件流对象
inFile.open("test.txt"); // 打开名为 "data.txt" 的输入文件,以读模式打开
if (!inFile) { // 如果打开文件失败
cerr << "Can't open file!" << endl; // 输出错误信息到标准错误流
return 1; // 返回 1,表示程序异常结束
}
// ... 在此处可以进行文件读取操作 ...
inFile.close(); // 关闭输入文件流
system("pause");
return 0; // 正常结束程序
}
2.1.1.3示例三 打开一个输出文件流对象,并以输出和二进制模式写入文件内容
ofstream outFile; //建立输入文件流对象
outFile.open("c:\\c2019\\newfile",ios::out | ios::binary); //连接文件,指定打开模式
// 也可以使用如下语句打开文件:
ofstream outFile("c:\\c2019\\newfile",ios::out | ios::binary);
#include <fstream> // 文件流头文件
#include <iostream>
using namespace std;
int main() {
ofstream outFile; // 建立输出文件流对象
outFile.open("c:\\c2019\\newfile", ios::out | ios::binary); // 打开名为 "c:\c2019\newfile" 的输出文件,以输出和二进制模式打开文件流
if (!outFile) { // 如果打开文件失败
cerr << "Can't open file!" << endl; // 输出错误信息到标准错误流
return 1; // 返回 1,表示程序异常结束
}
// ... 在此处可以进行文件写入操作 ...
outFile.close(); // 关闭输出文件流
return 0; // 正常结束程序
}
2.1.2关闭文件
#include<iostream> // 输入输出流头文件
#include<fstream> // 文件流头文件
using namespace std;
int main()
{
ifstream inFile("c:\\tmp\\test.txt", ios::in); // 声明对象 inFile 并调用构造函数打开名为 "c:\tmp\test.txt" 的输入文件,以读取模式打开文件流
if (inFile) // 如果打开文件成功
{
cout << "成功打开文件: c:\\tmp\\test.txt\n";
inFile.close(); // 关闭输入文件流
}
else
cout << "打开文件失败: c:\\tmp\\test.txt\n";
ofstream outFile("test1.txt", ios::out); // 声明对象 outFile 并调用构造函数创建名为 "test1.txt" 的输出文件,以写入模式打开文件流
if (!outFile)
cout << "error1" << endl;
else {
cout << "成功打开文件: test1.txt\n";
outFile.close(); // 关闭输出文件流
}
fstream outFile2("tmp\\test2.txt", ios::out | ios::in); // 声明对象 outFile2 并调用构造函数创建名为 "tmp\\test2.txt" 的文件,以输入输出和写入模式打开文件流
if (outFile2) {
cout << "成功打开文件:tmp\\test2.txt\n";
outFile2.close(); // 关闭文件流
}
else
cout << "error2" << endl;
return 0; // 正常结束程序
}
2.2文件读写操作
2.2.1读写文本文件
2.2.1.1示例一 将用户从标准输入中输入的信息,以字符串形式写入到文本文件 “score.txt” 中
假定现在要实现一个程序,从键盘输入学生的学号、姓名和成绩,将它们存入文件 score.txt中。
可以使用文本文件保存数据,文件中每一行保存一名学生的成绩信息,学生
成绩信息的数据项之间通过空格符分隔,格式存储如下:学号 姓名 成绩
为了方便程序实现,假设学号不超过 10 个字节,姓名不超过 20 个字节,成绩为整型
对文本文件 score.txt 进行输入/输出:
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include <fstream> // 包含文件流头文件
#include <iostream>
using namespace std;
int main() // 主函数
{
char id[11], name[21];
int score;
ofstream outFile; // 创建输出文件流对象
outFile.open("score.txt", ios::out); // 以写方式打开文本文件
if (!outFile) // 判断文件是否打开成功
{
cout << "创建文件失败" << endl; // 文件打开失败
return 0; // 程序正常结束
}
cout << "请输入: 学号 姓名 成绩 (以Ctrl+Z结束输入)\n";
while (cin >> id >> name >> score) // 从标准输入读入数据
{
outFile << id << " " << name << " " << score << endl; // 将数据写入文件流
}
outFile.close(); // 关闭输出文件流
system("pause");
return 0; // 程序正常结束
}
2.2.1.2示例二 从文件 “score.txt” 中读取学生信息并输出到屏幕上
假定现在要实现一个程序,从键盘输入学生的学号、姓名和成绩,将它们存入文件 score.txt 中。
可以使用文本文件保存数据,文件中每一行保存一名学生的成绩信息,学生
成绩信息的数据项之间通过空格符分隔,格式存储如下:学号 姓名 成绩
为了方便程序实现,假设学号不超过 10 个字节,姓名不超过 20 个字节,成绩为整型
对文本文件 score.txt 进行输入/输出:
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include <fstream> // 包含文件流头文件
#include <iostream>
#include <iomanip> // 格式控制头文件
using namespace std;
int main() // 主函数
{
char id[11], name[21];
int score;
ifstream inFile; // 创建输入文件流对象
inFile.open("score.txt", ios::in); // 以读方式打开文本文件
if (!inFile) // 判断文件是否打开成功
{
cout << "打开文件失败" << endl; // 文件打开失败
return 0; // 程序正常结束
}
cout << "学生学号 姓名\t\t\t成绩\n"; // 输出表头
while (inFile >> id >> name >> score) // 从文件中读取数据
{
// 格式化输出文件中的数据
cout << left << setw(10) << id << " " << setw(20) << name << " " << setw(3) << right << score << endl;
}
inFile.close(); // 关闭输入文件流
system("pause");
return 0; // 程序正常结束
}
2.2.1.3示例三 从用户输入的文件名(默认为当前目录下的文件),按照行的方式读取文件内容,并在每一行的开头进行行号的输出
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include <fstream> // 包含文件流头文件
#include <iostream>
#include <iomanip> // 格式控制头文件
using namespace std;
int main() // 主函数
{
char ch, filename[20];
int count = 0; // 行号计数器
bool newline = true; // 开始一个新行的标志
cout << "请输入文件名: ";
cin >> filename;
ifstream inFile(filename, ios::in); // 以读方式打开文本文件
if (!inFile) // 判断文件是否打开成功
{
cout << "打开文件失败" << endl; // 文件打开失败
return 0; // 程序正常结束
}
while ((ch = inFile.get()) != EOF) // 从文件中读取每个字符
{
if (newline) // 若是新行开始,则显示行号
{
cout << setw(4) << ++count << ": "; // 输出行号并格式化对齐
newline = false; // 清除新行标志
}
if (ch == '\n') // 若为换行符,则表示将开始一个新行
newline = true; // 设置新行标志
cout << ch; // 输出字符
}
inFile.close(); // 关闭输入文件流
system("pause");
return 0; // 程序正常结束
}
2.2.2读写二进制文件
对二进制文件进行读写不能使用前面提到的类似于 cin、cout 从流中读写数据的方法。
C++ 用 binary 方式打开二进制文件,调用 ifstream 或 fstream 的 read() 成员函数从文件中读取数据,调用 ofstream 或 fstream 的 write() 成员函数向文件中写入数据。
2.2.2.1 用 ostream::write() 成员函数写文件
ofstream 和 fstream 的 write() 成员函数继承自 ostream 类,原型如下:
ostream & write(char * buffer, int nCount);
该成员函数将内存中 buffer 所指向的 nCount 个字节的内容写入文件,返回值是对函数所作用的对象的引用,如 obj.write(...) 的返回值就是对 obj 的引用。
该函数是非格式化操作,将 buffer 所指的数据按字节序列直接存入文件中。
在使用 write() 与 read() 进行数据读写时,不必在数据之间再额外 “插入” 分隔符,这是因为它们都要求提供第 2 个参数来指定读写长度。
#define _CRT_SECURE_NO_WARNINGS
#include<stdlib.h>
#include <fstream> // 包含文件流头文件
#include <iostream>
#include <iomanip> // 格式控制头文件
using namespace std;
class CStudent // 自定义学生类
{
public:
char id[11]; // 学号
char name[21]; // 姓名
int score; // 成绩
};
int main() // 主函数
{
CStudent stu; // 学生类对象
ofstream outFile("students.dat", ios::out | ios::binary); // 以二进制写方式打开文本文件
if (!outFile) // 判断文件是否打开成功
{
cout << "创建文件失败" << endl; // 创建失败
return 0; // 程序结束并返回值 0
}
cout << "请输入: 学号 姓名 成绩 (以Ctrl+Z结束输入)\n";
while (cin >> stu.id >> stu.name >> stu.score) // 输入学生信息
outFile.write((char*)&stu, sizeof(stu)); // 向文件中写入学生信息
outFile.close(); // 关闭文件
return 0; // 程序结束
}
2.2.2.2 用 istream::read() 成员函数读文件
ifstream 和 fstream 的成员函数read()实际上继承自类 istream,原型如下:
istream &read(char * buffer, int nCount);
该成员函数从文件中读取 nCount 个字节的内容,存放到 buffer 所指向的内存缓冲区中,返回值是对函数所作用的对象的引用。
该函数是非格式化操作,对读取的字节序列不进行处理,直接存入 buffer 中,由程序的类型定义解释。
2.2.2.3 用 ostream::gcount() 成员函数得到读取字节数
如果要知道每次读操作成功读取了多少个字节,可以在 read() 函数执行后立即调用文件流对象的成员函数 gcount( ),其返回值就是最近一次 read() 函数执行时成功读取的字节数。
gcount() 成员函数原型如下:
int gcount( );
2.2.3用成员函数 put() 和 get() 读写文件
成员函数 get() 和 put() 常用于读写字符或文本文件,但它们不仅仅可用于对字符的处理,而且对于二进制文件同样可以进行有效的处理。
函数 get() 有 3 种主要形式:
int get( );
istream& get(char &rch);
istream& get(char *pch, int nCount, char delim=’\n’);
函数 put() 的语法格式如下:
ostream& put(char ch);
2.2.4文本文件与二进制文件的异同
在输入/输出过程中,系统要对内外存的数据格式进行相文本文件是以 文本形式存储数据
其优点是具有较高的兼容性
缺点是存储一批纯数值信息时,要在数据之间人为地添加分隔符
应转换
文本文件的另一个缺点是不便于对数据进行随机访问
⚫ 二进制文件是以 二进制形式存储数据
其优点是便于对数据实行随机访问(相同数据类型的数据所占空间的大小均是相同的,不必在数据之间人为地添加分隔符)
在输入/输出过程中,系统不需要对数据进行任何转换
缺点是数据兼容性差
⚫ 通常纯文本信息(如字符串)以文本文件形式存储,而将数值信息以二进制文件形式存储。
2.3随机访问文件
2.3.1 类 istream 中与位置指针相关的函数
2.3.1.1移动读指针函数
istream & seekg(long pos);
- 该函数的功能是将读指针设置为 pos,即将读指针移动到文件的 pos 字节处。
istream & seekg(long offset, ios::seek_dir dir);
- 该函数的功能是将读指针按照 seek_dir 的指示(方向)移动 offset 个字节,其中 seek_dir 是在类 ios 中定义的一个枚举类型。
enum seek_dir {beg=0, cur, end};
seek_dir 的常量值含义如下:ios::beg:表示流的开始位置。此时,offset 应为非负整数。
ios::cur:表示流的当前位置。此时,offset 为正数则表示向后(文件尾)移动,为
负数则表示向前(文件头)移动。
ios::end:表示流的结束位置。此时,offset 应为非正整数。
2.3.1.2返回写指针当前位置的函数
long tellg( );
- 函数返回值为流中读指针的当前位置。
2.3.2 类 ostream 中与位置指针相关的函数
2.3.2.1移动写指针函数
ostream & seekp(long pos);
该函数的功能是将写指针设置为 pos,即将写指针移动到文件的 pos 字节处。
ostream & seekp(long offset, ios::seek_dir dir);
该函数的功能是将写指针按 seek_dir 指示的方向移动 offset 个字节。
2.3.2.2返回写指针当前位置的函数
long tellp( );
- 函数的返回值为流中写指针的当前位置。
- 注意:在类 fstream 中既提供了操作读指针函数 seekg() 和 tellg(),又提供了操作写指针的函数 seekp() 和 tellp(),实际上在文件中这两个指针是同一个指针。