编程基础(2):文件读写


前言

专门列出一章文件读写,主要是因为之前的知识较为零散,想汇总起来以供参考。本文在初稿撰写时只加入了较为简单的C++标准下文件读写,后续会加入较为复杂的目录寻找等Python标准下的写作规范。


提示:以下是本篇文章正文内容,下面案例可供参考

一、C++文件读写(基于文件指针)

1.基本概念与主要使用函数

提到文件读写,较为简单并且基本够用的做法是使用一下四个函数:
(1)FILE *fp = fopen(filename.c_str(),_mode);
(2)int succeed = fscanf(FILE *fp,const string format,T *value);
(3)int succeed = fprintf(FILE *fp,const string format,T *value);
(4)fclose();
——————————
这个里面最重要的是中间的fscanf和fprintf。最初的fopen函数是打开了一个文件流。fscanf函数则是读入数据的主力,它搜索文件流中符合format形式的数据,赋值给value对应的指针,该函数遇到空格和换行就停止。fprintf函数是将value指针中存储的数据,按照format的形式,加入到输出文件流中,需要手动的添加换行符。因此,我们配合指针、数组,即可完成数据的读写。顺便说一下,笔者刚开始十分疑惑为什么一个文件读写的函数,会有一个int类型的返回值。其实这是有编程哲学的,这个函数返回的int值统计了数据被正确赋值到变量的个数,这对我们查找程序中的问题很有帮助。此外,很多函数写作的时候往往是bool类型,甚至直接就被写成void类型,因为这些函数的功能往往是对数据进行操作(一定要引用传递哦,否则会报“段错误:核心已转储”,经历过的人都懂),很难给出一个具体的返回值。对于这些函数来说,返回值的意义已经不大了,主要是为了查找问题,例如设定一个bool的函数,检查是否所有的数据都处理完毕,如果有问题的话,就返回False。

2.光束法平差文件读取使用案例

下面来看一个案例,完成对光束法平差文件的读写。该文件的基本数据结构如下:
在这里插入图片描述
光束法平差记录了相机、物方坐标点和投影的关系。这种文件一般有一个文件头,第一行是三个统计量,分别是相机总数、点号总数以及总参数量。接下来是每个物方点的经过某个相机投影后的像点坐标,刻画了投影关系,一行4个数据,总共nPoints行。而后是相机参数,一列9个为一组,记录了3个旋转角、3个平移量、1个焦距和两个2个变参数,共有nCameras组。最后是所有物方点的信息,一列3个为一组,分别为XYZ点,共有nPoints组。
——————————————
我们的目标是,读取这个文件,并且将相机参数、点以及参数存储进列表。
为了能够让我们的程序适应不同的BA问题(对应不同的BA数据文件),我们定义了一个BALProblem类,使用其成员变量来存储数据(这是一个非常常见的编程技巧),并在构造函数中读取文件、初始化成员变量。在这个类的最外面,设置一个检测机制的模板函数FscanOrDie(),通过其返回值判断文件读取是否有异常出现:代码如下(示例):

template<typename T>
void FscanOrDie(FILE *fptr,const char* format,T*value){
	int n_read = fscanf(fptr,format,value);
	if(n_read!=1){
		std::cerr << "Invalid UW data file. ";
	}
}

class BALProblem{
public:
	BALProblem(const string filename){
		FILE *fp = fopen(filename.c_str(),'r');
		FscanfOrDie(fp,"%d",&m_nCameras);
		FscanfOrDie(fp,"%d",&m_nPoints);
		FscanfOrDie(fp,"%d",&m_nObservations);
		//注意,这里一定要是指针
		
		std::cout << "Header: " << m_nCameras
              << " " << m_nPoints
              << " " << m_nObservations;
        
        //根据观测值数量记录相机编号和点编号列表
        camera_index_ = new int[m_nObservations];
        point_index_ = new int[m_nObservations];
        observations_ = new double[2 * m_nObservations];
		
		//读取数据
		for(int i=0;i<m_nObservations;++i){
			FscanfOrDie(fp,"%d",&camera_index_[i]);//或camera_index_+i
			FscanfOrDie(fp,"%d",&point_index_[i]);
			for(int j=0;j<2;++j){
				FscanfOrDie(fp,"%lf",observation_+2*i+j);
			}
		}
		m_nParameters = 9 * m_nCameras + 3 * m_nPoints;
		parameters_ = new double[m_nParameters];
		for(int p=0;p<m_nParameters;++p){
			FscanfOrDie(fp,"%lf",parameters_+p);
			//统一管理所有的参数
		}
	}
private:
	int m_nCameras;
	int m_nPoints;
	int m_nObservations;
	int m_nParameters;
	int *camera_index_;
	int *point_index_;
	double *observations_;
	double *parameters_;
};

注意fscanf赋值的必须是指针。下面给出写文件的代码:

void BALProblem::WriteToFile(const std::string &filename) const {
    FILE *fptr = fopen(filename.c_str(), "w");

    if (fptr == NULL) {
        std::cerr << "Error: unable to open file " << filename;
        return;
    }

    fprintf(fptr, "%d %d %d %d\n", m_nCameras, m_nCameras, m_nPoints, m_nObservations);

    for (int i = 0; i < m_nObservations; ++i) {
        fprintf(fptr, "%d %d", camera_index_[i], point_index_[i]);
        for (int j = 0; j < 2; ++j) {
            fprintf(fptr, " %g", observations_[2 * i + j]);
        }
        fprintf(fptr, "\n");
    }

    for (int i = 0; i < num_cameras(); ++i) {
        double angleaxis[9];
        if (use_quaternions_) {
            //OutPut in angle-axis format.
            QuaternionToAngleAxis(parameters_ + 10 * i, angleaxis);
            memcpy(angleaxis + 3, parameters_ + 10 * i + 4, 6 * sizeof(double));
        } else {
            memcpy(angleaxis, parameters_ + 9 * i, 9 * sizeof(double));
        }
        for (int j = 0; j < 9; ++j) {
            fprintf(fptr, "%.16g\n", angleaxis[j]);
        }
    }

    const double *points = parameters_ + camera_block_size() * num_cameras_;
    for (int i = 0; i < num_points(); ++i) {
        const double *point = points + i * point_block_size();
        for (int j = 0; j < point_block_size(); ++j) {
            fprintf(fptr, "%.16g\n", point[j]);
        }
    }
    fclose(fptr);
}

这个类中还给出了对外定义的get数据接口,这里就不再赘述了。结合fscanf函数和fprintf函数,以及最基本的数组创建,可以有效实现文件的读取。下面介绍另外一种常见的文件读取方式:基于流的。

二、C++文件读写(基于文件流)

基于文件流的方式更加普遍、高效与清晰。需要使用到的是iostream和fstream库。直接给出一个读取位姿的案例。这个案例的数据存储在estimated.txt文件中,每一行7个数据,分别是时间、xyz位置、以四元数格式记载的姿态。这种具有统一表格格式的的数据特别适合使用流数据来读取:

typedef vector<Sophus::SE3d, Eigen::aligned_allocator<Sophus::SE3d>> TrajectoryType;
TrajectoryType ReadTrajectory(const string &path) {
  ifstream fin(path);
  TrajectoryType trajectory;
  if (!fin) {
    cerr << "trajectory " << path << " not found." << endl;
    return trajectory;
  }

  while (!fin.eof()) {
    double time, tx, ty, tz, qx, qy, qz, qw;
    fin >> time >> tx >> ty >> tz >> qx >> qy >> qz >> qw;
    Sophus::SE3d p1(Eigen::Quaterniond(qx, qy, qz, qw), Eigen::Vector3d(tx, ty, tz));
    trajectory.push_back(p1);
  }
  return trajectory;
}

在读取流的过程中,行是fin指针中段循环的单位。如果要转换数据格式到Eigen等库时,每个小循环中就可以进行类型转换。

总结

本文总结了2种C++语言下的文件读取方式。可以看出,当文件较为简单时,采用统一的文件流形式可以简化代码,而文件较为复杂时,特别是数据结构各异时,编写fscanf函数组合的方式,具有更好的通用性。本文会持续更新,加入更多优秀的文件读写代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值