我们的讲师要求我们读取文本文件并将其存储到不同的结构和数组中,然后允许用户进行修改。但是当我使用fstream >> name >> day >>...>> roster
并逐行获取文本时。它总是出错,可能是因为每行的大小不同。
类头
Class Course
private:
string name;
int num_of_student;
string *roster;
Struct class_time
string day;
string time;
test.txt(第一行可以忽略)
<name><day><time><num_of_student><roster(student ids separate by space)>
CS T 2pm 3 01 02 03
Math TH 10am 2 03 04
我不知道如何读取这种类型的文本文件并将其存储到不同的数组或局部变量中。
更新:
这就是我所做的,并成功获取了文件中的每个变量。
num_courses(int){
// function get num of lines in text file
}
void load_Data(){
fstream read;
string name1, day1, time1;
int enroll;
int num_c; // number of courses
read.open("test.txt",ios::in);
if(!read.is_open()){
cout << "No file exist." << endl;
}
num_courses(num_c);
Course course[num_c];
class_time sch[num_c];
while(!read.eof()) {
for (int i = 0; i < num_c; i++) {
read >> name1 >> day1 >> time1 >> enroll;
course[i].name = name1;
sch[i].day = day1;
sch[i].time = time1;
course[i].num_of_student = enroll;
string ros[enroll];
for (int j = 0; j < enroll; j++) {
read >> ros[j];
course[i].roster[j] = ros[j];
}
}
}
现在,我想在修改这些变量后替换test.txt中的特定行,是否应该使用
ofstream
并删除文件中的所有内容,并替换为更新的课程信息。还是有一种简单的方法可以替换文本文件中的特定行? 最佳答案
好像您要读取csv数据。但是,您需要忽略标题行-
我建议使用“现代” C ++方法。
仍然所有谈论csv的人都链接到How can I read and parse CSV files in C++?,问题来自2009年,现在已经超过10年了。大多数答案也很老而且很复杂。因此,也许是时候改变了。
在现代C ++中,您具有遍历范围的算法。您通常会看到类似“ someAlgoritm(container.begin(),container.end(),someLambda)”的内容。这个想法是我们迭代一些相似的元素。
在您的情况下,我们遍历输入字符串中的标记,并创建子字符串。这称为标记化。
为此,我们有std::sregex_token_iterator
。并且因为我们已经为此目的定义了一些东西,所以我们应该使用它。
这个东西是一个迭代器。用于遍历字符串,因此使用sregex。开始部分定义了我们将在什么输入范围上进行操作,然后在输入字符串中存在一个std::regex
表示应匹配的内容或不匹配的内容。匹配策略的类型由last参数给出。
1->给我我在正则表达式中定义的内容
-1->给我基于正则表达式不匹配的内容。
因此,既然我们了解了迭代器,我们就可以std ::将令牌从迭代器复制到目标,即std::vector
的std::string
。而且由于我们不知道我们将拥有多少列,我们将使用std::back_inserter
作为目标。这将添加我们从std::sregex_token_iterator
获得的所有令牌,并将其附加到我们的std::vector<std::string>>
中。我们有多少列无关紧要。
好。这样的声明可能看起来像
std::copy( // We want to copy something
std::sregex_token_iterator // The iterator begin, the sregex_token_iterator. Give back first token
(
line.begin(), // Evaluate the input string from the beginning
line.end(), // to the end
re, // Add match a comma
-1 // But give me back not the comma but everything else
),
std::sregex_token_iterator(), // iterator end for sregex_token_iterator, last token + 1
std::back_inserter(cp.columns) // Append everything to the target container
);
现在我们可以了解此复制操作的工作原理。
下一步。我们想从文件中读取。文件内容也包含某种相同的数据。相同的数据是行。
如上所述,我们可以迭代相似的数据。是文件输入还是其他。为此,C ++具有
std::istream_iterator
。这是一个模板,作为模板参数,它获取应读取的数据类型,作为构造函数参数,它获取对输入流的引用。不管输入流是std::cin
还是std::ifstream
或std::istringstream
。各种流的行为都相同。并且由于没有SO的文件,因此我(在以下示例中)使用
std::istringstream
存储输入的csv文件。但是当然可以通过定义std::ifstream testCsv(filename)
打开文件。没问题。并使用
std::istream_iterator
遍历输入并读取相似的数据。在我们的案例中,一个问题是我们要遍历特殊数据而不是某些内置数据类型。为了解决这个问题,我们定义了一个Proxy类,该类为我们做内部工作(我们不希望知道如何封装在代理中)。在代理中,我们覆盖类型转换运算符,以将结果转换为
std::istream_iterator
的预期类型。最后一步。
std::vector
具有范围构造器。在定义std::vector
类型的变量时,还可以使用许多其他构造函数。但是出于我们的目的,此构造函数最合适。因此,我们定义了一个变量csv并使用其范围构造函数,并为其指定了范围的开始和范围的结束。并且,在我们的特定示例中,我们使用
std::istream_iterator
的begin和end迭代器。如果我们结合以上所有内容,则读取完整的CSV文件是一回事,这是通过调用其构造函数定义的变量。
请查看结果代码:
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <algorithm>
std::istringstream testCsv{ R"(CS T 2pm 3 01 02 03
Math TH 10am 2 03 04
)" };
// Define Alias for Easier Reading
using Columns = std::vector<std::string>;
using CSV = std::vector<Columns>;
// Proxy for the input Iterator
struct ColumnProxy {
// Overload extractor. Read a complete line
friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {
// Read a line
std::string line; cp.columns.clear();
std::getline(is, line);
// The delimiter
const std::regex re(" ");
// Split values and copy into resulting vector
std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
std::sregex_token_iterator(),
std::back_inserter(cp.columns));
return is;
}
// Type cast operator overload. Cast the type 'Columns' to std::vector<std::string>
operator std::vector<std::string>() const { return columns; }
protected:
// Temporary to hold the read vector
Columns columns{};
};
int main()
{
// Define variable CSV with its range constructor. Read complete CSV in this statement, So, one liner
CSV csv{ std::istream_iterator<ColumnProxy>(testCsv), std::istream_iterator<ColumnProxy>() };
// Print result. Go through all lines and then copy line elements to std::cout
std::for_each(csv.begin(), csv.end(), [](Columns& c) {
std::copy(c.begin(), c.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << "\n"; });
}
我希望解释足够详细,可以使您了解现代C ++可以做什么。
这个示例基本上并不关心其中有多少行和列。它将吃光所有东西。
请不要忘记使用
std::getline
读取真实文件的第一行并将其丢弃。