は多くの場合、階層的に文字入力を解析し、より直感的かつ効率的です、文字列をメジャーブロックに分割した後、各ブロックを処理し、小さな部分に分割するなど、ツリーのような方法で処理します。
これに代わる方法は、strtok
のようにトークン化することです - 入力の始めから、入力の終わりまで一度に1つのトークンを処理します。単純な入力を構文解析するときには、これは簡単に実装できるので、これが望ましい場合があります。このスタイルは、ネストされた構造で入力を解析するときにも使用できますが、単一の関数や限られたコード領域内で維持するには複雑すぎるコンテキスト情報を維持する必要があります。
C++標準ライブラリに頼っている人は、たいていstd::stringstream
と、std::getline
と一緒に文字列入力をトークン化してしまいます。しかし、これはデリミタを1つだけ与えます。彼らはstrtok
の使用を考慮しません。なぜなら、それはCランタイムライブラリからの非リエントラントなジャンクです。したがって、それらはストリームを使用して終了し、デリミタが1つのみで、階層的な解析スタイルを使用する義務があります。
しかし、zneakはstd::string::find_first_of
になりました。これは文字セットを取り、セットから文字を含む文字列の先頭に最も近い位置を返します。また、他のメンバー関数、find_last_of
,find_first_not_of
などがあります。これは文字列を解析する目的のために存在するようです。しかし、std::string
は、便利なトークン化機能を提供するには不足しています。
もう1つのオプションは、任意の操作を行うことができる<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 > '\0' && 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 = '\0';
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 << "\n\n";
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}!] '
[] }
を参照してください[なぜEOFループでは間違っている](http://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop -condition-considered-wrong) – WhiZTiM
'strtok'はできませんが、' find_first_of'があります。 – zneak
あなたのフォーマットが基本的に裸のCSVです - あなたは[this](http://stackoverflow.com/questions/1120140/how-can-i-read-and-parse-csv -files-in-c?lq = 1)。 –