c++ - 讀取字串 - c擷取字串
如何迭代字符串的單詞? (20)
我正在嘗試迭代字符串的單詞。
可以假設該字符串由用空格分隔的單詞組成。
請注意,我對C字符串函數或那種字符操作/訪問不感興趣。 另外,請在答案中優先考慮優雅而不是效率。
我現在最好的解決方案是:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
string s = "Somewhere down the road";
istringstream iss(s);
do
{
string subs;
iss >> subs;
cout << "Substring: " << subs << endl;
} while (iss);
}
有沒有更優雅的方式來做到這一點?
STL沒有這樣的方法。
但是,您可以通過使用std::string::c_str()
成員來使用C的strtok()
函數,也可以編寫自己的函數。 以下是我在快速Google搜索後找到的代碼示例( “STL字符串拆分” ):
void Tokenize(const string& str,
vector<string>& tokens,
const string& delimiters = " ")
{
// Skip delimiters at beginning.
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first "non-delimiter".
string::size_type pos = str.find_first_of(delimiters, lastPos);
while (string::npos != pos || string::npos != lastPos)
{
// Found a token, add it to the vector.
tokens.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters. Note the "not_of"
lastPos = str.find_first_not_of(delimiters, pos);
// Find next "non-delimiter"
pos = str.find_first_of(delimiters, lastPos);
}
}
摘自: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html : http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html
如果您對代碼示例有疑問,請發表評論,我會解釋。
並且因為它沒有實現一個名為iterator或者重載的typedef
, <<
運算符並不意味著它是錯誤的代碼。 我經常使用C函數。 例如, printf
和scanf
都比std::cin
和std::cout
(顯著)快, fopen
語法對於二進制類型更友好,並且它們也傾向於生成更小的EXE。
不要因為這個“優雅而不是表現”的交易而被賣掉。
使用std::stringstream
,你可以完美地工作,並完全按照自己的std::stringstream
。 如果你只是尋找不同的做事方式,你可以使用std::find()
/ std::find_first_of()
和std::string::substr()
。
這是一個例子:
#include <iostream>
#include <string>
int main()
{
std::string s("Somewhere down the road");
std::string::size_type prev_pos = 0, pos = 0;
while( (pos = s.find(' ', pos)) != std::string::npos )
{
std::string substring( s.substr(prev_pos, pos-prev_pos) );
std::cout << substring << '\n';
prev_pos = ++pos;
}
std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
std::cout << substring << '\n';
return 0;
}
到目前為止,我使用了Boost那個,但是我需要一些不依賴它的東西,所以我來到這裡:
static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
std::ostringstream word;
for (size_t n = 0; n < input.size(); ++n)
{
if (std::string::npos == separators.find(input[n]))
word << input[n];
else
{
if (!word.str().empty() || !remove_empty)
lst.push_back(word.str());
word.str("");
}
}
if (!word.str().empty() || !remove_empty)
lst.push_back(word.str());
}
一個好處是,在separators
您可以傳遞多個字符。
又一種靈活而快速的方式
template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
const char* s = input;
const char* e = s;
while (*e != 0) {
e = s;
while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
if (e - s > 0) {
op(s, e - s);
}
s = e + 1;
}
}
將它與字符串向量一起使用(編輯:因為某人指出不繼承STL類... hrmf;)):
template<class ContainerType>
class Appender {
public:
Appender(ContainerType& container) : container_(container) {;}
void operator() (const char* s, unsigned length) {
container_.push_back(std::string(s,length));
}
private:
ContainerType& container_;
};
std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");
而已! 這只是使用標記器的一種方法,比如如何計算單詞:
class WordCounter {
public:
WordCounter() : noOfWords(0) {}
void operator() (const char*, unsigned) {
++noOfWords;
}
unsigned noOfWords;
};
WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t");
ASSERT( wc.noOfWords == 7 );
受想像力限制;)
如果需要使用非空格符號解析字符串,則stringstream可以很方便:
string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;
istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')
對於它的價值,這是從輸入字符串中提取標記的另一種方法,僅依賴於標準庫設施。 這是STL設計背後的力量和優雅的一個例子。
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
int main() {
using namespace std;
string sentence = "And I feel fine...";
istringstream iss(sentence);
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
ostream_iterator<string>(cout, "\n"));
}
可以使用相同的通用copy
算法將提取的令牌複製到容器中,而不是將提取的令牌複製到輸出流。
vector<string> tokens;
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter(tokens));
...或直接創建vector
:
vector<string> tokens{istream_iterator<string>{iss},
istream_iterator<string>{}};
我使用strtok自己滾動並使用boost來分割字符串。 我找到的最好的方法是C ++ String Toolkit Library 。 它非常靈活和快速。
#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>
const char *whitespace = " \t\r\n\f";
const char *whitespace_and_punctuation = " \t\r\n\f;,=";
int main()
{
{ // normal parsing of a string into a vector of strings
std::string s("Somewhere down the road");
std::vector<std::string> result;
if( strtk::parse( s, whitespace, result ) )
{
for(size_t i = 0; i < result.size(); ++i )
std::cout << result[i] << std::endl;
}
}
{ // parsing a string into a vector of floats with other separators
// besides spaces
std::string s("3.0, 3.14; 4.0");
std::vector<float> values;
if( strtk::parse( s, whitespace_and_punctuation, values ) )
{
for(size_t i = 0; i < values.size(); ++i )
std::cout << values[i] << std::endl;
}
}
{ // parsing a string into specific variables
std::string s("angle = 45; radius = 9.9");
std::string w1, w2;
float v1, v2;
if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
{
std::cout << "word " << w1 << ", value " << v1 << std::endl;
std::cout << "word " << w2 << ", value " << v2 << std::endl;
}
}
return 0;
}
該工具包比這個簡單的示例具有更大的靈活性,但它在將字符串解析為有用元素方面的實用性令人難以置信。
我喜歡以下內容,因為它將結果放入向量中,支持字符串作為delim並控制保持空值。 但是,它看起來並不那麼好。
#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
vector<string> result;
if (delim.empty()) {
result.push_back(s);
return result;
}
string::const_iterator substart = s.begin(), subend;
while (true) {
subend = search(substart, s.end(), delim.begin(), delim.end());
string temp(substart, subend);
if (keep_empty || !temp.empty()) {
result.push_back(temp);
}
if (subend == s.end()) {
break;
}
substart = subend + delim.size();
}
return result;
}
int main() {
const vector<string> words = split("So close no matter how far", " ");
copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}
當然,Boost有一個split()
,其工作方式與此類似。 並且,如果通過'white-space',你真的意味著任何類型的空白區域,使用Boost與is_any_of()
的分割效果很好。
我有一個解決這個問題的2行解決方案:
char sep = ' ';
std::string s="1 This is an example";
for(size_t p=0, q=0; p!=s.npos; p=q)
std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;
然後,您可以將其放在矢量中,而不是打印。
我用它來分隔字符串。 第一個將結果放在預先構造的向量中,第二個返回一個新向量。
#include <string>
#include <sstream>
#include <vector>
#include <iterator>
template<typename Out>
void split(const std::string &s, char delim, Out result) {
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
*(result++) = item;
}
}
std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
split(s, delim, std::back_inserter(elems));
return elems;
}
請注意,此解決方案不會跳過空標記,因此以下內容將找到4個項目,其中一個項目為空:
std::vector<std::string> x = split("one:two::three", ':');
短而優雅
#include <vector>
#include <string>
using namespace std;
vector<string> split(string data, string token)
{
vector<string> output;
size_t pos = string::npos; // size_t to avoid improbable overflow
do
{
pos = data.find(token);
output.push_back(data.substr(0, pos));
if (string::npos != pos)
data = data.substr(pos + token.size());
} while (string::npos != pos);
return output;
}
可以使用任何字符串作為分隔符,也可以與二進制數據一起使用(std :: string支持二進制數據,包括空值)
使用:
auto a = split("this!!is!!!example!string", "!!");
輸出:
this
is
!example!string
這是一個僅使用標準正則表達式庫的正則表達式解決方案。 (我有點生疏,所以可能會有一些語法錯誤,但這至少是一般的想法)
#include <regex.h>
#include <string.h>
#include <vector.h>
using namespace std;
vector<string> split(string s){
regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
regex_iterator<string::iterator> rend; //iterators to iterate thru words
vector<string> result<regex_iterator>(rit, rend);
return result; //iterates through the matches to fill the vector
}
這是一個拆分函數:
- 是通用的
- 使用標準C ++(沒有提升)
- 接受多個分隔符
忽略空標記(可以很容易地更改)
template<typename T> vector<T> split(const T & str, const T & delimiters) { vector<T> v; typename T::size_type start = 0; auto pos = str.find_first_of(delimiters, start); while(pos != T::npos) { if(pos != start) // ignore empty tokens v.emplace_back(str, start, pos - start); start = pos + 1; pos = str.find_first_of(delimiters, start); } if(start < str.length()) // ignore trailing delimiter v.emplace_back(str, start, str.length() - start); // add what's left of the string return v; }
用法示例:
vector<string> v = split<string>("Hello, there; World", ";,");
vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
這是另一種做法..
void split_string(string text,vector<string>& words)
{
int i=0;
char ch;
string word;
while(ch=text[i++])
{
if (isspace(ch))
{
if (!word.empty())
{
words.push_back(word);
}
word = "";
}
else
{
word += ch;
}
}
if (!word.empty())
{
words.push_back(word);
}
}
這是我最喜歡的迭代字符串的方法。 你可以隨心所欲地做任何事。
string line = "a line of text to iterate through";
string word;
istringstream iss(line, istringstream::in);
while( iss >> word )
{
// Do something on `word` here...
}
這類似於問題如何在C ++中對字符串進行標記? 。
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;
int main(int argc, char** argv)
{
string text = "token test\tstring";
char_separator<char> sep(" \t");
tokenizer<char_separator<char>> tokens(text, sep);
for (const string& t : tokens)
{
cout << t << "." << endl;
}
}
下面的代碼用於strtok()
將字符串拆分為標記並將標記存儲在向量中。
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
char one_line_string[] = "hello hi how are you nice weather we are having ok then bye";
char seps[] = " ,\t\n";
char *token;
int main()
{
vector<string> vec_String_Lines;
token = strtok( one_line_string, seps );
cout << "Extracting and storing data in a vector..\n\n\n";
while( token != NULL )
{
vec_String_Lines.push_back(token);
token = strtok( NULL, seps );
}
cout << "Displaying end result in vector line storage..\n\n";
for ( int i = 0; i < vec_String_Lines.size(); ++i)
cout << vec_String_Lines[i] << "\n";
cout << "\n\n\n";
return 0;
}
得到Boost!: - )
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace boost;
int main(int argc, char**argv) {
typedef vector < string > list_type;
list_type list;
string line;
line = "Somewhere down the road";
split(list, line, is_any_of(" "));
for(int i = 0; i < list.size(); i++)
{
cout << list[i] << endl;
}
return 0;
}
這個例子給出了輸出 -
Somewhere
down
the
road
最近我不得不將一個帶有駱駝字的單詞分成子字。沒有分隔符,只有上面的字符。
#include <string>
#include <list>
#include <locale> // std::isupper
template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
std::list<String> R;
String w;
for (String::const_iterator i = s.begin(); i < s.end(); ++i) { {
if (std::isupper(*i)) {
if (w.length()) {
R.push_back(w);
w.clear();
}
}
w += *i;
}
if (w.length())
R.push_back(w);
return R;
}
例如,這將“AQueryTrades”拆分為“A”,“查詢”和“交易”。該函數適用於窄字符串和寬字符串。因為它尊重當前的地點,所以它將“RaumfahrtÜberwachungsVerordnung”分為“Raumfahrt”,“Überwachungs”和“Verordnung”。
注意std::upper
應該作為函數模板參數傳遞。然後,這個函數的更一般化可以分割為分隔符","
,";"
或者" "
也可以分割。
這個答案接受字符串並將其放入字符串向量中。它使用boost庫。
#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));