我可以在函数 getline 中使用 2 个或更多分隔符C++吗?
Can I use 2 or more delimiters in C++ function getline?
我想知道如何在getline函数中使用2个或更多分隔符,这是我的问题:
程序读取一个文本文件...每一行都像:
New Your, Paris, 100
CityA, CityB, 200
我正在使用getline(file,line),但是当我想得到CityA时,我得到了整行,然后是CityB,然后是数字;如果我使用","分隔符,我将不知道下一行是什么时候,所以我试图找出一些解决方案。
但是,我如何使用逗号和作为分隔符? 顺便说一句,我正在操作字符串类型,而不是字符,所以 strtok 是不可能的:/
一些划痕:
string line;
ifstream file("text.txt");
if(file.is_open())
while(!file.eof()){
getline(file, line);
// here I need to get each string before comma and n
}
您可以使用std::getline
读取一行,然后将该行传递给std::stringstream
并读取逗号分隔的值
string line;
ifstream file("text.txt");
if(file.is_open()){
while(getline(file, line)){ // get a whole line
std::stringstream ss(line);
while(getline(ss, line, ',')){
// You now have separate entites here
}
}
不,std::getline
() 只接受单个字符,以覆盖默认分隔符。std::getline()
没有多个备用分隔符的选项。
解析这种输入的正确方法是使用默认的std::getline
()将整行读入std::string
,然后构造一个std::istringstream
,然后进一步解析为逗号分隔的值。
但是,如果您真正解析逗号分隔的值,则应使用正确的 CSV 解析器。
通常,以分层、树状的方式解析字符输入会更直观、更高效,首先将字符串拆分为其主要块,然后继续处理每个块,将它们拆分为更小的部分,依此类推。
另一种方法是像strtok
一样进行标记化 - 从输入开始,一次处理一个令牌,直到遇到输入结束。 在解析简单输入时,这可能是首选,因为它很容易实现。 在解析具有嵌套结构的输入时,也可以使用此样式,但这需要维护某种上下文信息,这些信息可能会变得过于复杂,无法在单个函数或有限的代码区域中维护。
依赖C++ std 库的人通常最终会使用std::stringstream
和std::getline
来标记字符串输入。 但是,这只给你一个分隔符。 他们永远不会考虑使用strtok
,因为它是C运行时库中不可重入的垃圾。 因此,它们最终使用流,并且只有一个分隔符,因此必须使用分层解析样式。
但是 zneak 提出了std::string::find_first_of
,它接受一组字符并返回最接近包含该集合字符的字符串开头的位置。 还有其他成员函数:find_last_of
、find_first_not_of
等,它们似乎只是为了解析字符串而存在的。 但std::string
并没有提供有用的标记化功能。
另一种选择是<regex>
库,它可以做任何你想做的事情,但它是新的,你需要习惯它的语法。
但是,只需很少的努力,您就可以利用std::string
中的现有函数来执行标记化任务,而无需求助于流。 下面是一个简单的示例。get_to()
是标记化函数,tokenize
演示了如何使用它。
此示例中的代码将比strtok
慢,因为它不断擦除正在解析的字符串开头的字符,并且还会复制并返回子字符串。 这使得代码易于理解,但这并不意味着更有效的标记化是不可能的。 它甚至不会比这复杂得多 - 您只需跟踪当前位置,将其用作std::string
成员函数中的start
参数,并且永远不会更改源字符串。 毫无疑问,还有更好的技术存在。
若要理解示例的代码,请从底部开始,main()
在哪里以及可以看到如何使用函数的位置。此代码的顶部以基本的实用程序函数和愚蠢的注释为主。
#include <iostream>
#include <string>
#include <utility>
namespace string_parsing {
// in-place trim whitespace off ends of a std::string
inline void trim(std::string &str) {
auto space_is_it = [] (char c) {
// A few asks:
// * Suppress criticism WRT localization concerns
// * Avoid jumping to conclusions! And seeing monsters everywhere!
// Things like...ah! Believing "thoughts" that assumptions were made
// regarding character encoding.
// * If an obvious, portable alternative exists within the C++ Standard Library,
// you will see it in 2.0, so no new defect tickets, please.
// * Go ahead and ignore the rumor that using lambdas just to get
// local function definitions is "cheap" or "dumb" or "ignorant."
// That's the latest round of FUD from...*mumble*.
return c > ' ' && c <= ' ';
};
for(auto rit = str.rbegin(); rit != str.rend(); ++rit) {
if(!space_is_it(*rit)) {
if(rit != str.rbegin()) {
str.erase(&*rit - &*str.begin() + 1);
}
for(auto fit=str.begin(); fit != str.end(); ++fit) {
if(!space_is_it(*fit)) {
if(fit != str.begin()) {
str.erase(str.begin(), fit);
}
return;
} } } }
str.clear();
}
// get_to(string, <delimiter set> [, delimiter])
// The input+output argument "string" is searched for the first occurance of one
// from a set of delimiters. All characters to the left of, and the delimiter itself
// are deleted in-place, and the substring which was to the left of the delimiter is
// returned, with whitespace trimmed.
// <delimiter set> is forwarded to std::string::find_first_of, so its type may match
// whatever this function's overloads accept, but this is usually expressed
// as a string literal: ", n" matches commas, spaces and linefeeds.
// The optional output argument "found_delimiter" receives the delimiter character just found.
template <typename D>
inline std::string get_to(std::string& str, D&& delimiters, char& found_delimiter) {
const auto pos = str.find_first_of(std::forward<D>(delimiters));
if(pos == std::string::npos) {
// When none of the delimiters are present,
// clear the string and return its last value.
// This effectively makes the end of a string an
// implied delimiter.
// This behavior is convenient for parsers which
// consume chunks of a string, looping until
// the string is empty.
// Without this feature, it would be possible to
// continue looping forever, when an iteration
// leaves the string unchanged, usually caused by
// a syntax error in the source string.
// So the implied end-of-string delimiter takes
// away the caller's burden of anticipating and
// handling the range of possible errors.
found_delimiter = ' ';
std::string result;
std::swap(result, str);
trim(result);
return result;
}
found_delimiter = str[pos];
auto left = str.substr(0, pos);
trim(left);
str.erase(0, pos + 1);
return left;
}
template <typename D>
inline std::string get_to(std::string& str, D&& delimiters) {
char discarded_delimiter;
return get_to(str, std::forward<D>(delimiters), discarded_delimiter);
}
inline std::string pad_right(const std::string& str,
std::string::size_type min_length,
char pad_char=' ')
{
if(str.length() >= min_length ) return str;
return str + std::string(min_length - str.length(), pad_char);
}
inline void tokenize(std::string source) {
std::cout << source << "nn";
bool quote_opened = false;
while(!source.empty()) {
// If we just encountered an open-quote, only include the quote character
// in the delimiter set, so that a quoted token may contain any of the
// other delimiters.
const char* delimiter_set = quote_opened ? "'" : ",'{}";
char delimiter;
auto token = get_to(source, delimiter_set, delimiter);
quote_opened = delimiter == ''' && !quote_opened;
std::cout << " " << pad_right('[' + token + ']', 16)
<< " " << delimiter << 'n';
}
std::cout << 'n';
}
}
int main() {
string_parsing::tokenize("{1.5, null, 88, 'hi, {there}!'}");
}
这输出:
{1.5, null, 88, 'hi, {there}!'}
[] {
[1.5] ,
[null] ,
[88] ,
[] '
[hi, {there}!] '
[] }
我不认为这是你应该解决问题的方式(即使你可以做到); 相反:
- 使用每行中必须阅读的内容
- 然后用逗号将该行分开以获得所需的部分。
如果strtok
可以完成#2的工作,则始终可以将字符串转换为char数组。
- 当用户键入分隔符时,停止getline()输入
- 为什么在逗号分隔符上下文中将预增量的结果强制转换为void
- 如何在 c++ 中使用 ',' 作为 getline 分隔符
- 带有多个字符分隔符的正则表达式
- 使用 char 分隔符解析C++中的字符串,但将可重复的字符保留为每个解析的子字符串 (C++ STL) 中的分隔符
- 字符串开头的分隔符
- 尝试将 c 字符串数组与分隔符连接起来
- 正则表达式以匹配数字的重复模式,后跟任何类型的分隔符?
- 根据新的行分隔符从字符串中删除子字符串
- 同一分隔符之间的多个子字符串
- 通过分隔符分隔包含 UTF-16 BE 文本的uint8_t数组
- 字符串流分隔符
- 如何在 std::getline 中自定义分隔符
- getline() 上的分隔符无法正常工作
- 示例代码中使用分隔符将 std::string 拆分为 std::vector 的范围问题
- C++使用分隔符读取文件
- 使用连续分隔符和空最后一个字符进行拆分
- 到达分隔符时跳到文本文件的下一行
- 使用空格分隔符从文本文件将对象读入数组
- 使用正则表达式c++从单词和分隔符之间的字符串中提取所有子字符串