C++正在从文件中读取填充记录



我正在尝试编写一个C++程序,该程序从下面给出的文件中获取填充记录列表:

Jackson 49292
Levy 40156
Indian River 138894
Liberty 8314
Holmes 19873
Madison 19115

有什么好办法处理印第安河案吗?这是我目前写的代码:

ifstream file("county_data-5.txt");
if(!file)
{
cout<<"nError: File not found!n";
}
else
{
string name ,string;
double pop;
while(!file.eof())
{
if(!file.eof())
{
//file>>name;
//file>>pop;
getline(file, string);
stringstream ss(string);
ss >> name >> pop;
insert(root,name, pop);
}
}
}
file.close();

有很多方法可以处理读取一个名称,该名称可能有未知数量的空格分隔部分和尾部数字。您可以简单地使用cstdio,用getline()读取每一行,然后用" %[^0-9] %zu"的格式字符串调用str.c_str()上的sscanf(),然后在分配给字符串之前修剪temporary_name中的尾部空白。

使用当前时代的C++,可以使用getline读取行,然后使用.find_first_of()成员函数定位字符串中的第一个数字。例如,您可以保留一个数字列表,例如const char *digits = "0123456789";,然后用line.find_first_of(digits);查找第一个数字。知道第一个数字在哪里,就可以使用.substr()成员函数来复制name,然后从末尾去掉后面的空白。

更重要的考虑是如何存储读取的所有值。如果您创建一个具有成员std:string name;size_t pop;的简单struct,那么您可以创建一个结构的std::vector,并使用.push_back()成员函数将从文件中读取的每个结构值的数据添加到结构的向量中。

结构的一个简单实现可以是:

struct population
{
std::string name;
size_t pop;

/* constructors */
population() { name = ""; pop = 0; }
population(const std::string& n, const size_t p) : name(n), pop(p) {}
};

为了简化从文件中读取,您可以创建>>的重载,它将从打开的文件流中读取一行数据,并为您分离为namepop<<的第二个重载将允许您以自己选择的合理格式输出结构。添加您可能拥有的过载:

/* struct to hold name population, 
* and overloads of operators >> and << to facilitate splitting name/hours.
*/
struct population
{
std::string name;
size_t pop;

/* constructors */
population() { name = ""; pop = 0; }
population(const std::string& n, const size_t p) : name(n), pop(p) {}

/* overloads of >> (separates name/pop) and << (outputs name/pop) */
friend std::istream& operator >> (std::istream& is, population& p) {
const char *digits = "0123456789";

std::string line {};
if (getline (is, line)) {                               /* read line */
size_t popbegin = line.find_first_of(digits);       /* find 1st [0-9] */
if (popbegin != std::string::npos) {                /* valdiate found */
std::string tmp = line.substr(0, popbegin);     /* get name */
while (isspace(tmp.back()))                     /* remove trailing */
tmp.pop_back();                             /* .. spaces */
p.name = tmp;                                   /* assign to name */
p.pop = stoul(line.substr(popbegin));           /* assign to pop */
}
}
return is;
}
friend std::ostream& operator << (std::ostream& os, const population& p) {
os << std::left << std::setw(32) << p.name << "  " << p.pop << 'n';
return os;
}
};

然后,在main()中,你所需要的就是验证你有一个作为参数传递的文件名,打开文件并验证它是否可以读取(比如std::ifstream f(,然后你的读取和值的分离被简化为一个简单的循环:

population p {};    /* instance of population struct to facilitate read from file */
std::vector<population> records {};         /* vector of population */

while (f >> p) {                            /* read population data from file */
records.push_back(p);                   /* add to population vector */
}

现在,在结构records的向量中存储了每个位置的所有位置和种群。总之,你可以做到:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <vector>
/* struct to hold name population,  
* and overloads of operators >> and << to facilitate splitting name/hours.
*/
struct population
{
std::string name;
size_t pop;

/* constructors */
population() { name = ""; pop = 0; }
population(const std::string& n, const size_t p) : name(n), pop(p) {}

/* overloads of >> (separates name/pop) and << (outputs name/pop) */
friend std::istream& operator >> (std::istream& is, population& p) {
const char *digits = "0123456789";

std::string line {};
if (getline (is, line)) {                               /* read line */
size_t popbegin = line.find_first_of(digits);       /* find 1st [0-9] */
if (popbegin != std::string::npos) {                /* valdiate found */
std::string tmp = line.substr(0, popbegin);     /* get name */
while (isspace(tmp.back()))                     /* remove trailing */
tmp.pop_back();                             /* .. spaces */
p.name = tmp;                                   /* assign to name */
p.pop = stoul(line.substr(popbegin));           /* assign to pop */
}
}
return is;
}
friend std::ostream& operator << (std::ostream& os, const population& p) {
os << std::left << std::setw(32) << p.name << "  " << p.pop << 'n';
return os;
}
};
int main (int argc, char **argv) {

if (argc < 2) { /* validate 1 argument given for filename */
std::cerr << "error: filename required as 1st argument.n";
return 1;
}

std::ifstream f (argv[1]);  /* open filename provided as 1st argument */

if (!f.is_open()) { /* validate file is open for reading */
std::cerr << "file open failed: " << argv[1] << 'n';
return 1;
}

population p {};    /* instance of population struct to facilitate read from file */
std::vector<population> records {};         /* vector of population */

while (f >> p) {                            /* read population data from file */
records.push_back(p);                   /* add to population vector */
}

for (const auto& loc : records)             /* output results */
std::cout << std::left << std::setw(32) << loc.name << loc.pop << 'n';
}

示例使用/输出

如果你的数据在文件dat/population.txt中,使用和结果将是:

$ ./bin/poprecords dat/population.txt
Jackson                         49292
Levy                            40156
Indian River                    138894
Liberty                         8314
Holmes                          19873
Madison                         19115

由于数据存储在struct的向量中,因此可以通过任何方式对向量进行排序来分析数据。

这只是解决这个问题的多种方法之一。仔细看看,如果你还有问题,请告诉我。

我想展示一个额外的解决方案,使用更现代的C++元素。并且,我将使用regex来描述什么是有效输入,什么是无效输入。

使用正则表达式,您可以详细定义允许或不允许的内容。我们可以非常严格,或者允许前导和尾随空格,或者多个空格或任何空白字符,或者我们希望的任何字符。因此,即使您有一个像Holmes Region 1 19873这样的县名称,我们也可以将其视为有效名称并提取正确的数据。

我不确定你是否理解正则表达式。无论如何我现在将为您的数据定义一个正则表达式。整个正则表达式是:

^s*(w+(s+w+)*)s+(d+)s*$
1      Begin of line
s*    Zero or more white spaces
(      Begin of a group. Later we will extract this groupd data (the county name)
w+    One or more characters, a-z, A-Z and _ (First county sub name)
(      Begin of optional group for county names with more sub names
s+    One or more whit spaces between county sub names
w+    One or more characters, a-z, A-Z and _ (additional county sub names)
)      ENd of group for additional county subnames (always having starting white spaces)
*      There may be 0 or more additionaly sub names for county
s+    One or more white spaces (in front of population count)
(      Start of group for population count. Will be extracted later
d+    One or more digits (So, we will make sure that this is a valid number)
)      End of Group for digits
s*    0 or more white spaces
$      End of line

因此,您可以看到,我们可以为指定的目的定义正则表达式。

至于程序结构的其余部分,一切都是一种不太标准的方法。

重要。在C++中,我们将数据和相应的方法放在一个类中。这包括IO功能。提取器操作器和插入器操作器也是如此。只有类应该知道如何读取和写入其数据。

因此,我们将简单地定义一个类";CountyPopulation";仅具有2个数据成员并且覆盖提取器和插入器运算符。

在提取器中,我们将读取完整的一行,并将其与正则表达式进行匹配。如果匹配,那么我们可以提取我们需要的2组。易于理解的

用于驱动程序代码。我们将打开源文件并检查它是否可以打开。然后,我们使用CTAD定义了一个std::vetcor,并使用它的范围构造函数来填充它。范围构造函数需要2个迭代器。为此,我们使用std::istream_iterator。整个构造将简单地为源行中的所有行调用我们类的提取器运算符。

这导致一行用于将完整文件读取到我们的std::vetcor中。

请参阅:

`#include <iostream>
#include <fstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iomanip>
struct CountyPopulation {
// Our Data
std::string county{};
unsigned long population{};
// Overwrite extractor
friend std::istream& operator >> (std::istream& is, CountyPopulation& cp) {
// Read a complete line
if (std::string line{}; std::getline(is, line)) {
// We want to evaluate the string using a regular expression
std::smatch sm; std::regex re{ R"(^s*(w+(s+w+)*)s+(d+)s*$)" };
// If the string matches our patternm, then we can copy the data
if (std::regex_match(line, sm, re)) {
cp.county = sm[1];
cp.population = std::stoul(sm[3]);
}
else std::cerr << "n*** Error: Invalid Data in line:  '" << line << "'n";
}
return is;
}
// Overwrite inserter
friend std::ostream& operator << (std::ostream& os, const CountyPopulation& cp) {
return os << std::left << std::setw(30) << cp.county << " --> " << cp.population << 'n';
}
};
int main() {
// Open file and check, if it could be opened
if (std::ifstream countyFileStream{ "r:\county_data-5.txt" }; countyFileStream) {
// Define a vector and use its range constructor to read all values from the file
std::vector population(std::istream_iterator<CountyPopulation>(countyFileStream), {});
// Show all read data on screen
std::copy(population.begin(), population.end(), std::ostream_iterator<CountyPopulation>(std::cout));
}
else std::cerr << "n*** Error: Could not open source filen";
return 0;
}

用C++17 编译和测试

正如MikeCAT在评论中指出的,while(!file.oef())是不对的。相反,您可以简单地执行while(file)。如果这些是人口记录,则可以使用int而不是double。此外,if语句是不必要的,因为您已经有了while循环。您还应该更改string的名称,以防止混淆。

最新更新