我正在尝试编写一个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) {}
};
为了简化从文件中读取,您可以创建>>
的重载,它将从打开的文件流中读取一行数据,并为您分离为name
和pop
。<<
的第二个重载将允许您以自己选择的合理格式输出结构。添加您可能拥有的过载:
/* 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 编译和测试
while(!file.oef())
是不对的。相反,您可以简单地执行while(file)
。如果这些是人口记录,则可以使用int
而不是double
。此外,if
语句是不必要的,因为您已经有了while
循环。您还应该更改string
的名称,以防止混淆。