C++文件类(文件流类)及用法详解
Posted c语言-小新
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++文件类(文件流类)及用法详解相关的知识,希望对你有一定的参考价值。
《C++输入输出流》一章中讲过,重定向后的 cin 和 cout 可分别用于读取文件中的数据和向文件中写入数据。除此之外,C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:
- ifstream:专用于从文件中读取数据;
- ofstream:专用于向文件中写入数据;
- fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。
值得一提的是,这 3 个文件流类都位于 <fstream> 头文件中,因此在使用它们之前,程序中应先引入此头文件。
这 3 个文件流类的继承关系,如图 1 所示。
图1:C++类库中的流类
可以看到,ifstream 类和 fstream 类是从 istream 类派生而来的,因此 ifstream 类拥有 istream 类的全部成员方法。同样地,ofstream 和 fstream 类也拥有 ostream 类的全部成员方法。这也就意味着,istream 和 ostream 类提供的供 cin 和 cout 调用的成员方法,也同样适用于文件流。并且这 3 个类中有些成员方法是相同的,比如 operator <<()、operator >>()、peek()、ignore()、getline()、get() 等。
值得一提的是,和 <iostream> 头文件中定义有 ostream 和 istream 类的对象 cin 和 cout 不同,<fstream> 头文件中并没有定义可直接使用的 fstream、ifstream 和 ofstream 类对象。因此,如果我们想使用该类操作文件,需要自己创建相应类的对象。
为什么 C++ 标准库不提供现成的类似 fin 或者 fout 的对象呢?其实很简单,文件输入流和输出流的输入输出设备是硬盘中的文件,硬盘上有很多文件,到底应该使用哪一个呢?所以,C++ 标准库就把创建文件流对象的任务交给用户了。
fstream 类拥有 ifstream 和 ofstream 类中所有的成员方法,表 2 罗列了 fstream 类一些常用的成员方法。
成员方法名 | 适用类对象 | 功 能 |
---|---|---|
open() | fstream ifstream ofstream | 打开指定文件,使其与文件流对象相关联。 |
is_open() | 检查指定文件是否已打开。 | |
close() | 关闭文件,切断和文件流对象的关联。 | |
swap() | 交换 2 个文件流对象。 | |
operator>> | fstream ifstream | 重载 >> 运算符,用于从指定文件中读取数据。 |
gcount() | 返回上次从文件流提取出的字符个数。该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。 | |
get() | 从文件流中读取一个字符,同时该字符会从输入流中消失。 | |
getline(str,n,ch) | 从文件流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 '\\0'。 | |
ignore(n,ch) | 从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。 | |
peek() | 返回文件流中的第一个字符,但并不是提取该字符。 | |
putback(c) | 将字符 c 置入文件流(缓冲区)。 | |
operator<< | fstream ofstream | 重载 << 运算符,用于向文件中写入指定数据。 |
put() | 向指定文件流中写入单个字符。 | |
write() | 向指定文件中写入字符串。 | |
tellp() | 用于获取当前文件输出流指针的位置。 | |
seekp() | 设置输出文件输出流指针的位置。 | |
flush() | 刷新文件输出流缓冲区。 | |
good() | fstream ofstream ifstream | 操作成功,没有发生任何错误。 |
eof() | 到达输入末尾或文件尾。 |
表 2 中仅列举的了部分常用的成员方法,更详细的介绍,读者可查看 C++标准库手册。
这里就以 fstream 类举例,简单演示一下如何使用表 2 中的一些成员方法操作文件:
-
#include <iostream> #include <fstream> using namespace std; int main() const char *url ="http://c.biancheng.net/cplus/"; //创建一个 fstream 类对象 fstream fs; //将 test.txt 文件和 fs 文件流关联 fs.open("test.txt", ios::out); //向test.txt文件中写入 url 字符串 fs.write(url, 30); fs.close(); return 0;
执行程序,该程序同目录下会生成一个 test.txt 文件,该文件的内容为:
http://c.biancheng.net/cplus/
注意,初学者只需借助注释看懂程序执行流程即可,具体的代码实现不必深究,后续章节会做详细讲解。
值得一提的是,无论是读取文件中的数据,还是向文件中写入数据,最先要做的就是调用 open() 成员方法打开文件。同时在操作文件结束后,还必须要调用 close() 成员方法关闭文件。关于如何使用 open() 函数打开一个文件,下一节会做详细介绍。
❥关于C++之stringstream典型用法
typedef basic_stringstream<char> stringstream;
类描述:
流类对字符串进行操作。
此类的对象使用包含字符序列的字符串缓冲区。可以使用成员str 作为string对象直接访问此字符序列。
可以使用输入和输出流上允许的任何操作来插入和/或从流中提取字符。
一个典型使用示例:
下面图片展示的是一个3D模型的OBJ格式的文件内容:
蓝色v 开头的是三角形顶点坐标(x, y, z);
红色vt 开头的是所有顶点的纹理坐标(纹理坐标列表比顶点列表长的原因是一些顶点参与多个三角形,并且在这些情况下可能使用不同的纹理坐标);
绿色vn 开头的是顶点法向量(该列表通常也比顶点列表长,尽管在该示例中不是这样,同样是因为一些顶点参与多个三角形,并且在那些情况下可能使用不同的法向量);
紫色f 开头的是面。
[面]格式表示的含义:
格式:f 顶点1/纹理1/法向量1 顶点2/纹理2/法向量2 顶点3/纹理3/法向量3,
其中:顶点1(纹理1,法向量1) — 顶点2(纹理2,法向量2) — 顶点3(纹理3,法向量3) 构成一个三角形。
现在编写程序,解析出OBJ文件中所有的顶点、顶点的纹理坐标、顶点的法向量,分类存储,以便可以用OpenGL程序绘制这个3D模型。
// parser.h
#ifndef PARSER_H_
#define PARSER_H_
#include <vector>
class ModelParser
private:
std::vector<float> vVals;// 所有单个的[顶点坐标]值
std::vector<float> vtVals;// 所有单个的[纹理坐标]值
std::vector<float> vnVals;// 所有单个的[法向量]值
std::vector<float> triangleVerts;// #三角形#数据
std::vector<float> textureCoords;// #纹理坐标#数据
std::vector<float> normals;// #法向量#数据
public:
void parseOBJ(const char* filePath);
int getNumVertices();
std::vector<float> getVertices();
std::vector<float> getTextureCoordinates();
std::vector<float> getNormals();
#endif
#include <fstream>
#include <stringstream>
#include <string>// std::string, std::stoi
#include "parser.h"
using namespace std;
void ModelParser::parseOBJ(const char* filePath)
float x, y, z;
string content;
ifstream fileStream(filePath, ios::in);
string line = "";
while (!fileStream.eof())
getline(fileStream, line);
if (line.compare(0, 2, "v ") == 0)
stringstream ss(line.erase(0, 1));
ss >> x; ss >> y; ss >> z;
vVals.push_back(x);
vVals.push_back(y);
vVals.push_back(z);
if (line.compare(0, 2, "vt") == 0)
stringstream ss(line.erase(0, 2));
ss >> x; ss >> y;
vtVals.push_back(x);
vtVals.push_back(y);
if (line.compare(0, 2, "vn") == 0)
stringstream ss(line.erase(0, 2));
ss >> x; ss >> y; ss >> z;
vnVals.push_back(x);
vnVals.push_back(y);
vnVals.push_back(z);
if (line.compare(0, 2, "f ") == 0)
string oneData, v, vt, vn;
stringstream ss(line.erase(0, 2));
for (int i = 0; i < 3; i++)
getline(ss, oneData, ' ');
stringstream oneDataS(oneData);
getline(oneDataS, v, '/');
getline(oneDataS, vt, '/');
getline(oneDataS, vn, '/');
int vertIndex = (stoi(v) - 1) * 3;
int textureIndex = (stoi(vt) - 1) * 2;
int normalIndex = (stoi(vn) - 1) * 3;
triangleVerts.push_back(vVals[vertIndex]);
triangleVerts.push_back(vVals[vertIndex + 1]);
triangleVerts.push_back(vVals[vertIndex + 2]);
textureCoords.push_back(vtVals[textureIndex]);
textureCoords.push_back(vtVals[textureIndex + 1]);
normals.push_back(vnVals[normalIndex]);
normals.push_back(vnVals[normalIndex + 1]);
normals.push_back(vnVals[normalIndex + 2]);
int ModelParser::getNumVertices() return (triangleVerts.size() / 3);
vector<float> ModelParser::getVertices() return triangleVerts;
vector<float> ModelParser::getTextureCoordinates() return textureCoords;
vector<float> ModelParser::getNormals() return normals;
本文不涉及OpenGL,下面用纯C++,给OpenGL编程做好数据准备:
// encapsulation.h
#include <vector>
#include <glm\\glm.hpp>// glm::vec2、glm::vec3
using namespace std;
class EncapModel
private:
int numVertices;
vector<glm::vec3> vertices;
vector<glm::vec2> texCoords;
vector<glm::vec3> normalVecs;
public:
ImportedModel()
ImportedModel(const char *filePath);
int getNumVertices();
vector<glm::vec3> getVertices();
vector<glm::vec2> getTextureCoords();
vector<glm::vec3> getNormals();
;
#include <glm\\glm.hpp>
#include "imported.h"
using namespace std;
EncapModel::EncapModel(const char *filePath)
ModelParser modelParser = ModelParser();
modelParser.parseOBJ(filePath);
numVertices = modelParser.getNumVertices();
std::vector<float> verts = modelParser.getVertices();
std::vector<float> tcs = modelParser.getTextureCoordinates();
std::vector<float> normals = modelParser.getNormals();
for (int i = 0; i < numVertices; i++)
vertices.push_back(glm::vec3(verts[i*3], verts[i*3+1], verts[i*3+2]));
texCoords.push_back(glm::vec2(tcs[i*2], tcs[i*2+1]));
normalVecs.push_back(glm::vec3(normals[i*3], normals[i*3+1], normals[i*3+2]));
int EncapModel::getNumVertices() return numVertices;
vector<glm::vec3> EncapModel::getVertices() return vertices;
vector<glm::vec2> EncapModel::getTextureCoords() return texCoords;
vector<glm::vec3> EncapModel::getNormals() return normalVecs;
至此,封装完成,可以直接获所有顶点的三维(vec3)坐标数据、所有顶点的二维(vec2)纹理坐标数据、所有顶点的三维(vec3)法向量数据。第个顶点vec3(x,y,z)都对应有一个纹理坐标vec(s,t)和一个法向量vec3(nx,ny,nz)。解析、封装完毕!
示例中一些成员函数分析:
#include <string>// getline、stoi
✎[v 1.000000 -1.000000 -1.000000]
line.compare(0, 2, "v ")
line.erase(0, 1)
✎[f 2/1/1 3/2/1 4/3/1]
getline(ss, oneData, ' ');
stringstream oneDataS(oneData);
getline(oneDataS, v, '/');
getline(oneDataS, vt, '/');
getline(oneDataS, vn, '/');
int vertIndex = (stoi(v) - 1) * 3;
——————————————————————————————
函数原型:int compare(size_t pos, size_t len, const string& str) const;
函数原型:string& erase(size_t pos = 0, size_t len = npos);
函数原型:istream& getline(istream&& is, string& str, char delim);
函数原型:int stoi(const string& str, size_t* idx = 0, int base = 10);// stoi:StringToInteger
##############################
#include <stringstream>
stringstream ss(line.erase(0, 1));
ss >> x; ss >> y; ss >> z;
——————————————————————————————
构造函数原型:
explicit stringstream(const string& str,
ios_base::openmode which = ios_base::in | ios_base::out);
std::istream::operator>>
运算符>>是从istream类继承而来,并未重载之,故和istream中的>>具有完全相同的功能和特性。
比如,和cin>>一样,默认使用空白(空格、制表符和换行符)来确定字符串的结束位置,并清除遇到的空白缓冲。
stringstream类还有一个公有成员str,用来get/set内容。
string str() const;
void str(const string& s);
————————————————————————————
第一种形式(1)返回一个字符串对象,其中包含流当前内容的副本。
第二种形式(2)将s设置为流的内容,丢弃之前的任何内容。
对象保留其打开模式:如果这包括ios_base::ate,则写入位置将移动到新序列的末尾。
在内部,该函数调用其内部字符串缓冲区对象的str成员。
// stringstream::str
#include <string>
#include <iostream>
#include <sstream>// std::stringstream, std::stringbuf
int main ()
std::stringstream ss;
ss.str("Example string");
std::string s = ss.str();
std::cout << s << '\\n';// 打印:Example string
return 0;
以上是关于C++文件类(文件流类)及用法详解的主要内容,如果未能解决你的问题,请参考以下文章
C++通过jsoncpp类库读写JSON文件-json用法详解