简单的文件操作
对文件操作大分为以下几个步骤
1.创建一个输入输出对象(ifstream or ofstream)
2.将对象与文件绑定(可用构造函数绑定)
注意:C++中不接受string对象作为文件名,可以用方法c_str()返回一个C风格的字符串
3.进行操作
4.关闭文件(自动会关闭,也可以手动关闭)
#include <iostream>
#include <fstream>
#include <string>
int main()
{
using namespace std;
string filename;
cout << "Enter name for new file: ";
cin >> filename;
//创建一个ofstream object向文件中写入
ofstream fout(filename.c_str());
//or fout.open(filename.c_str());
fout << "For your eyes only!\n";
cout << "Enter your secret number: ";
float secret;
cin >> secret;
fout << "Your secret number is " << secret << endl;
fout.close();
//创建一个ifstream object从文件中读取
ifstream fin(filename.c_str());
//or fin.open(filename.c_str());
cout << "Here are the contents of " << filename << ":\n";
char ch;
while(fin.get(ch))
cout << ch;
cout << "Done\n";
fin.close();
}
运行结果
其中close()将对象暂时取消与文件的关联,但并不清空流。
流状态的检查
像iostream那样,C++文件流从ios_base继承了表示流状态的成员,可用于判断流状态。
一些方法
ifstream fin;
fin.fail();
fin.good();
fin.is_open();
//或者这种形式
if(!fin)
上面的几种方法是等价的
但是is_open()的功能更加强大一些,他能检测到试图以不合适的模式打开文件,并判定为false
打开多个文件
每打开一个文件,程序都会多创建一个流。
用一个对象关联多次文件
ifstream fin;
fin.open("a.txt");
//do someting
fin.close();
fin.clear();
fin.open("other.txt");
//do something
fin.close();
fin.clear();
关闭文件的同时要清空对象对应的流状
其作用是能够节省资源,因为每次只用一个对象关联了一个文件,即只打开了一个流。
命令行处理技术
C++有一种让在命令环境(终端,cmd)中运行的程序能够访问命令行参数的机制,方法如下
int main(int argc, char * arvg[])
其中,两个参数的前者表示命令行参数的个数,包括命令本身。后者是一个指向字符指针的指针。就是一个二级指针,或者二维字符数组的数组名。
下面检查文件中的字符数量
平台:Windos
IDE:CLion
#include <iostream>
#include <fstream>
#include <string>
int main(int argc, char * argv[])
{
using namespace std;
if(argc == 1)
{
cerr << "Usage: " << argv[0] << " filename[s]\n";
exit(EXIT_FAILURE);
}
ifstream fin;
long count;
long total = 0;
char ch;
for(int file=1;file<argc;file++)
{
fin.open(argv[file]);
if(!fin.is_open())
{
cerr << "Could not open " << argv[file] << endl;
fin.clear();
continue;
}
count = 0;
while(fin.get(ch))
count++;
cout << count << " characters in " << argv[file] << endl;
total += count;
fin.clear();
fin.close();
}
cout << total << " character is all files\n";
return 0;
}
准备两个文件
在这里配置参数
运行结果
文件模式
和C的文件模式类似
这时候,经典的ios_base又来了,它定义了一些打开文件的模式
常量 | 含义 |
---|---|
ios_base::in | 读取文件 |
ios_base::out | 写入文件 |
ios_base::ate | 打开文件并且移到文件尾 |
ios_base::app | 追加到文件尾 |
ios_base::trunc | 如果文件存在,截短文件 |
ios_base::binary | 二进制文件 |
可用ifsteram和ofstream的构造函数指定打开模式(不指定调用默认构造函数)。
同样这种模式也基于掩码,因此可以进行位运算使用
ifstream默认调用ios_base::in
ofstream默认调用ios_base::write | ios_base::trunc
二进制文件
将数据存储在文件中时,可以将其存储为二进制格式或者文本格式。
文本格式是将所有的内容储存为文本
二进制格式指的是计算机存储的内部表示,因为计算机底层只能存储0和1
每种格式都有自己的优点
文本格式方便于读取,可直接看见文本中的内容,不像二进制那样,无法查看。方便从一个系统转移到另一个系统。(把底层的数据转换为文本的格式正是<<完成的工作)
二进制格式存储精确,速度更快,不需要进行额外的转换,可以按快女存储数据,更加节约内存。然而由于不同的操作系统内部表示不同,数据可能无法传输,或者同一个系统上的不同的编译器内部表示也可能不同
一些警告
类似C中的fwrite()的fout.wrtie()方法。不适用于有虚函数的类,因为虚函数使用虚函数表和一个隐藏的函数指针vptr,下一次运行时的虚函数表地址可能不同!
同样的也不适用于string对象!就像java的String类那样,对象中并没有具体的字符串,而是一个字符指针,下一次运行时,将会改变造成无法预测的结果!
一个程序
//from C++ Primer Plus
#include <iostream> // not required by most systems
#include <fstream>
#include <iomanip>
#include <cstdlib> // (or stdlib.h) for exit()
inline void eatline() { while (std::cin.get() != '\n') continue; }
struct planet
{
char name[20]; // name of planet
double population; // its population
double g; // its acceleration of gravity
};
const char * file = "planets.dat";
int main()
{
using namespace std;
planet pl;
cout << fixed << right;
// show initial contents
ifstream fin;
fin.open(file, ios_base::in |ios_base::binary); // binary file
//NOTE: some systems don't accept the ios_base::binary mode
if (fin.is_open())
{
cout << "Here are the current contents of the "
<< file << " file:\n";
while (fin.read((char *) &pl, sizeof pl))
{
cout << setw(20) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
fin.close();
}
// add new data
ofstream fout(file,
ios_base::out | ios_base::app | ios_base::binary);
//NOTE: some systems don't accept the ios::binary mode
if (!fout.is_open())
{
cerr << "Can't open " << file << " file for output:\n";
exit(EXIT_FAILURE);
}
cout << "Enter planet name (enter a blank line to quit):\n";
cin.get(pl.name, 20);
while (pl.name[0] != '\0')
{
eatline();
cout << "Enter planetary population: ";
cin >> pl.population;
cout << "Enter planet's acceleration of gravity: ";
cin >> pl.g;
eatline();
fout.write((char *) &pl, sizeof pl);
cout << "Enter planet name (enter a blank line "
"to quit):\n";
cin.get(pl.name, 20);
}
fout.close();
// show revised file
fin.clear(); // not required for some implementations, but won't hurt
fin.open(file, ios_base::in | ios_base::binary);
if (fin.is_open())
{
cout << "Here are the new contents of the "
<< file << " file:\n";
while (fin.read((char *) &pl, sizeof pl))
{
cout << setw(20) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
fin.close();
}
cout << "Done.\n";
// keeping output window open
// cin.clear();
// eatline();
// cin.get();
return 0;
}
就是用二进制写入数据用二进制读出,存储在文件中,非常简单。
随机读写
随机读写指可以直接移动到文件的某个位置,而不是历遍移动。
fstream类可以使用ifstream和ofstream的所有方法(开辟了两个流)。
想要进行随机访问可以进行以下的准本
1.使文件可读写
2.使用二进制模式
fstream finout.open(file, ios_base::in|ios_base::out|ios_base::binary);
fstream继承了两个方法,实际上这两个方法是模板类
seekg() 移动输入指针到指定位置,指针实际上指向的是缓冲区的数据,而不是实际的数据
seekp() 移动输出指针到指定位置,指针实际上指向的是缓冲区的数据,而不是实际的数据
实例化char之后的原型
istream& seekg(streamoff, ios_base::seekdir);
istream& seekg(streampos);
不用管上面的东西是什么意思
先看一下使用方法
fin.seekg(111);
则是从文件开始(从0开始)查看第112个字节。
移动到文件的开头
seekg(0);
streampos表示文件中的绝对位置。第二个函数原型得以解释
再来看看经典的ios_base中定义的一些常量,于第一个函数原型中的ios_base::seekdir对应
ios_base::beg 文件头
ios_base::cur 当前位置
ios_base::end 文件尾
streamoff表示相对于以上三个量的偏移量,第一个函数原型得以解释。
查看文件指针的当前位置
输入流: tellg(),返回一个streampos值
输出流: tellp(),返回一个streampos值
特别的创建fstream时,他们的返回值是一样的
假设移动到特定的结构体位置或类位置,可以这样做。
struct pl;
finout.seekg(sizeof p1);
一个小程序
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
struct Stu
{
int id;
char name[15];
double grade;
};
const char * file = "stu.bat";
int main()
{
using namespace std;
int n;
Stu s;
fstream finout;
//注意,在没有文件时不会自动创建
finout.open(file, ios_base::in | ios_base::out | ios_base::binary);
if(!finout.is_open())
{
cerr << "file open fail \nerror";
exit(EXIT_FAILURE);
}
cout << "请输入有几个学生";
cin >> n;
for(int i=0;i<n;i++)
{
cout << "请输入学号名字和成绩";
cin >> s.id >> s.name >> s.grade;
finout.write((char*) &s, sizeof s);
}
//因为要查看,所以移动到文件的头
finout.seekg(0);
char ch;
cout << "是否要查看二进制文件中的内容y/n: ";
while(cin.get(ch))
{
if(ch == 'n'||ch =='y')
break;
}
if(ch == 'y')
{
while(finout.read((char *)& s,sizeof s)) //这里读取到了文件尾
{
cout << setw(10) << s.id << setw(15) << s.name << setw(10) << s.grade << endl;
}
}
//消除eofbit,因为上面读取到了文件尾设置了eofbit
finout.clear();
cout << "是否要改写某个学生信息: ";
while(cin.get(ch))
{
if(ch == 'y'||ch == 'n')
break;
}
if(ch == 'y')
{
cout << "请输入学生的位置: ";
cin >> n;
if(n == 0)
{
cerr << "rang error" << endl;
exit(EXIT_FAILURE);
}
finout.seekp( n-1 * sizeof s);
cout << "请重新输入这个学生的信息:";
cin >> s.id >> s.name >> s.grade;
finout.write((char*)&s,sizeof s);
cout << "Done\n";
}
finout.flush();
finout.clear();
finout.close();
return 0;
}
注意fstream的一些错误条件,重新设置一些状态即可,运行结果不展示了。
临时文件
程序开发时会用到临时文件。
那么,如何保证临时文件的文件名不会重复呢?
首先,需要为 临时文件制定一个命名方案cstdio中的tmpnam()可以帮助我们。
tmpnam()函数创建一个临时的文件名,将他放在pszName指向的C风格字符串中。常量L_tepnam和TMP_MAX限制了文件名包含的字符数以及确保当前目录下不生成重复文件名下这个函数可被调用的最多次数
int main()
{
using namespace std;
cout << "L_tmpnam is " << L_tmpnam << endl;
cout << "TMP_MAX is " << TMP_MAX << endl;
char pszName [L_tmpnam]= {"\0"};
const int n = 10;
for(int i=0;i<n;i++)
{
tmpnam( pszName);
std::cout << pszName << std::endl;
}
return 0;
}
运行结果
内核格式化
众所周知,这样的读取是不行的
string s;
cin.getline(s,10);
但是这样的读取是可以的
string s;
getline(cin,s);
其实,字符串还有另外的读写方式。
iostream族链接了程序与终端的I/O
fstream族链接了程与文件的I/O
同样的C++提供了一个sstream族,链接string与程序的I/O
可以可以使用iostream中的方法对string进格式化行读写,被称为内核格式化
头文件中sstream中定义了一个从ostream派生来的ostringstream,可用于将信息写入string中,写入的信息保存在缓冲区,用.str()方法返回。
演示
#include <iostream>
#include <sstream>
int main()
{
using namespace std;
ostringstream strout;
string s;
getline(cin,s);
cout << s << endl;
//向字符串中输出,而不是向屏幕输出
strout << "hello world now use sstream input a string";
string result = strout.str();
cout << result << endl;
}
运行结果
类似的还有istringstream,将其关联一个string对象进行读取,就像读取文件那样
#include <iostream>
#include <sstream>
int main()
{
using namespace std;
string s = "1 1 4 5 1 4";
istringstream strin(s);
int n;
int sum = 0;
while(strin>>n)
{
sum += n;
}
cout << "Sum is " << sum;
}
运行结果
注意这种格式的读取
#include <iostream>
#include <sstream>
int main()
{
using namespace std;
string s = "hello world I love C++";
istringstream strin(s);
string words;
while(strin>>words)
{
cout << words << endl;
}
}
运行结果猜猜看
是这样的
原因是C++对 >> 进行了重载。