【C/C++学习】数据读入-格式1
掌握基本读入方法后,下面从txt文件读入较为复杂的数据。
1. 功能概述
1.1 功能效果
需要读入多列数据,由于每行数据都是不同的人记录的,可能存在:
- 数据是完备的:只是有的用制表符分开,有的用空格,甚至有多个制表符或空格连着的情况,要去掉这些分隔符正确读入;
- 数据是完备的:只是有多余的回车/空格,不能把这些读进去;
- 数据不完整:某处少输或多输了数,要报错并报位置,不包括上一条;
- 数据填错了:如果数据有很多行,难免敲入其他字符,存在非数字的情况,要报错并报位置。
1.2 使用方法
- 输入要求:首行需保证为表头,手动也是可以操作的;
- 读入结果:每列数据存为一个指针数组,有三种使用方式,考虑到多次调用效率,劝你使用方法2。
#include "MapColumn.h"//std,map,iostream,fstream
int main()
{
map<string, double*> mapMing;
MapColumn Ming(".\\ming.txt", mapMing);
/* 使用方法1 */
cout <<"使用方法1:"<<endl;
for (int i = 0; i < Ming.getSize(); i++) {
cout << mapMing.at("星期")[i] << "\t";
cout << mapMing.at("CSDN(小时)")[i] << "\t";
cout << mapMing.at("Bil(小时)")[i] << "\t";
cout << mapMing.at("DY(小时)")[i] << "\t" << endl;
}
/* 使用方法2 */
cout << "使用方法2:" << endl;
class takeHours {
public:
double *week;
double *CSDN;
double *Bil;
double *DY;
int row;
} takeHoursMing{
mapMing.at("星期"),
mapMing.at("CSDN(小时)"),
mapMing.at("Bil(小时)"),
mapMing.at("DY(小时)"),
Ming.getSize()
};
for (int i = 0; i < takeHoursMing.row; i++) {
cout << takeHoursMing.week[i] << "\t" ;
cout << takeHoursMing.CSDN[i] << "\t" ;
cout << takeHoursMing.Bil[i] << "\t" ;
cout << takeHoursMing.DY[i] << "\t" << endl;
}
/* 使用方法3 */
cout << "使用方法3:" << endl;
Ming.Disp();
return 0;
}
2. 注意事项
- 使用方法1编写方便,但是多次使用的话花费较长时间,使用方法3效率也高但不知道表头的名字,所以使用方法2通过map来映射;
- 要读懂程序需要结合上一篇文章,难点在于数据的检查,本程序有些情况未考虑,你可以基于此构造出更健全的程序。
3. 详细代码
MapColumn.h
/*
* Auther: sanfan66
* Date | Change
*--------------------------------------------
* 230926 | <record>: None→Version 0.00,Original finished
* | (describe): MapRow needs input-file with a header in front of the data
* | (describe): '\t'or' 'to separate the words
* | !warning!: ~MapRow may not called in some cases
*/
#pragma once
#include <fstream>//ifstream
#include <map>
#include <iostream>//cout
#include <string>//getline
#define EPS 1e-7
using namespace std;//map,string,ifstream
class MapColumn {
public:
MapColumn(const char *path, map<string, double*>&map_name);
~MapColumn();
int getNum(ifstream *file, const char *path);
double getData(ifstream *file, const char *path);
int getSize() const {
return rowData;
}
void Disp() const;
private:
int nHeader;
int rowData;
char **header_name;
double **lines_data;
};
MapColumn.cpp
#include "MapColumn.h"
MapColumn::MapColumn(const char *path, map<string, double*>&map_name) {
/* 打开文件、读入行数、读取数据、赋值地址 */
ifstream file;
file.open(path, ios::in);
if (!file.is_open()) {
cout << path << "无法打开" << endl;
system("Pause");
exit(0);
}
getNum(&file, path);
header_name = new char*[nHeader];
lines_data = new double*[nHeader];
for (int i = 0; i < nHeader; i++) {
header_name[i] = new char[100];
lines_data[i] = new double[rowData];
}
getData(&file, path);
for (int i = 0; i < nHeader; i++) {
map_name[header_name[i]] = lines_data[i];
}
file.close();
}
MapColumn::~MapColumn() {
/* 清理堆空间,new对应delete,new[]对应delete[] */
for (int i = 0; i < nHeader; i++) {
delete[] header_name[i];
header_name[i] = nullptr;
delete[] lines_data[i];
lines_data[i] = nullptr;
}
delete[] header_name;
header_name = nullptr;
delete[] lines_data;
lines_data = nullptr;
}
int MapColumn::getNum(ifstream *file, const char *path) {
/* 获取维度 */
int indexHeader = 0;
int indexData = 0;
int indexLines = 0;
string one_line;
while (file->peek() != EOF) {
getline(*file, one_line);
int isEnter = 1;//检查读取的行是否只有回车
int isSpace = 1;//检查读取的行是否只有空格
int nHeaderCheck = 0;//检查表头的个数与数据个数是否相等
char *buf = &one_line[0];
char *one_line_char = nullptr;
char *next_word_ptr = buf;
while (*next_word_ptr != '\0') { //取一行,每次以制表符或空格分隔取出一个词
isEnter = 0;//不是回车
one_line_char = strtok_s(buf, "\t ", &next_word_ptr);
if (one_line_char != nullptr) { //连续的分隔符会返回空指针
isSpace = 0;//不是空格
/* 检查表头的个数和数据的个数是否一致,及数据是否含有非法字符 */
switch (indexLines) {
case 0: { //第一行默认为表头
indexHeader++;
int HeaderIsNum = 0;
for (unsigned i = 0; i < strlen(one_line_char); i++) {
if ((one_line_char[i] - '0' > -EPS && one_line_char[i] - '9' < EPS)) {
HeaderIsNum++;
}
}
if (HeaderIsNum == strlen(one_line_char)) {
cout << path << "第" << indexHeader << "列是否缺少表头" << endl;
system("Pause"); //提示但不终止
}
break;
}
default: //第二行及以上默认为数据
nHeaderCheck++;
for (unsigned i = 0; i < strlen(one_line_char); i++) {
if (!((one_line_char[i] - '0' > -EPS && one_line_char[i] - '9' < EPS ) || one_line_char[i]=='.')) {
cout << path << "第" << indexLines + 1 << "行第"<< nHeaderCheck <<"个数据为非数据符号" << endl;
system("Pause");
exit(0);
}
}
break;
}
}
buf = nullptr;
}
if (indexLines > 0 && isEnter == 0 && isSpace == 0) {
indexData++;
if (nHeaderCheck != indexHeader) {
cout << path << "第" << indexLines + 1 << "行表头个数="<< indexHeader <<",数据个数="<< nHeaderCheck << endl;
system("Pause");
exit(0);
}
}
indexLines++;
}
rowData = indexData;
nHeader = indexHeader;
return 0;
}
double MapColumn::getData(ifstream *file, const char *path) {
/* 读取数据 */
file->clear();
file->seekg(0, ios::beg);
int indexData = 0;
int indexLines = 0;
string one_line;
while (file->peek() != EOF) {
getline(*file, one_line);
int isEnter = 1;
int isSpace = 1;
int indexHeader = 0;
char *buf = &one_line[0];
char *one_line_char = nullptr;
char *next_word_ptr = buf;
while (*next_word_ptr != '\0') { //取一行,每次以制表符或空格分隔取出一个词
isEnter = 0;
one_line_char = strtok_s(buf, "\t ", &next_word_ptr);
if (one_line_char != nullptr) { //连续的分隔符会返回空指针
isSpace = 0;
switch (indexLines) {
case 0: { //第一行默认为表头
strcpy_s(header_name[indexHeader], strlen(one_line_char) + 1, one_line_char);
break;
}
default: //第二行及以上默认为数据
lines_data[indexHeader][indexData] = atof(one_line_char);
break;
}
indexHeader++;
}
buf = nullptr;
}
if (indexLines > 0 && isEnter == 0 && isSpace == 0) {
indexData++;
}
indexLines++;
}
return 0;
}
/* 使用方法3 */
void MapColumn::Disp() const{
for (int i = 0; i < nHeader; i++) {
cout << header_name [i]<<"\t";
}
cout << endl;
for (int i =0 ; i< rowData; i++) {
for (int j = 0; j < nHeader; j++) {
cout << lines_data[j][i] << "\t" ;
}
cout<< endl;
}
return ;
}