Вступление
Распространенной задачей в программировании является разделение строки с разделителями на массив токенов. Например, может потребоваться разбить строку, содержащую пробелы, на массив слов. Это одна из областей, в которой языки программирования, такие как Java и Python, превосходят C ++, поскольку оба этих языка программирования включают поддержку этого в своих стандартных библиотеках, а C ++ - нет. Однако в C ++ эту задачу можно решить разными способами.
В Java эту задачу можно решить с помощью метода String.split следующим образом.
String Str = "The quick brown fox jumped over the lazy dog.";
String[] Results = Str.split(" ");
В Python эту задачу можно решить с помощью метода str.split следующим образом.
Str = "The quick brown fox jumped over the lazy dog."
Results = Str.split()
В C ++ эта задача не так проста. Это все еще можно сделать множеством разных способов.
Использование библиотеки времени выполнения C
Один из вариантов - использовать библиотеку времени выполнения C. Следующие функции библиотеки времени выполнения C могут использоваться для разделения строки.
К сожалению, между платформами существуют различные различия. Это требует использования директив препроцессора C для определения того, что подходит для текущей платформы.
Следующий код демонстрирует эту технику.
char* FindToken( char* str, const char* delim, char** saveptr) { #if (_SVID_SOURCE || _BSD_SOURCE || _POSIX_C_SOURCE >= 1 \ || _XOPEN_SOURCE || _POSIX_SOURCE) return ::strtok_r(str, delim, saveptr); #elif defined(_MSC_VER) && (_MSC_VER >= 1800) return strtok_s(token, delim, saveptr); #else return std::strtok(token, delim); #endif }
wchar_t* FindToken( wchar_t* token, const wchar_t* delim, wchar_t** saveptr) { #if ( (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) \ || (defined(__cplusplus) && (__cplusplus >= 201103L)) ) return std::wcstok(token, delim, saveptr); #elif defined(_MSC_VER) && (_MSC_VER >= 1800) return wcstok_s(token, delim, saveptr); #else return std::wcstok(token, delim); #endif }
char* CopyString(char* destination, const char* source) { return std::strcpy(destination, source); }
wchar_t* CopyString(wchar_t* destination, const wchar_t* source) { return std::wcscpy(destination, source); }
template <class charType> size_t splitWithFindToken( const std::basic_string<charType>& str, const std::basic_string<charType>& delim, std::vector< std::basic_string<charType> >& tokens) { std::unique_ptr<charType[]> ptr = std::make_unique<charType[]>( str.length() + 1); memset(ptr.get(), 0, (str.length() + 1) * sizeof(charType)); CopyString(ptr.get(), str.c_str()); charType* saveptr; charType* token = FindToken(ptr.get(), delim.c_str(), &saveptr); while (token != nullptr) { tokens.push_back(token); token = FindToken(nullptr, delim.c_str(), &saveptr); } return tokens.size(); }
Использование класса basic_istringstream
Решение 1. std :: istream_iterator
Пожалуй, самый простой способ решить эту задачу - использовать класс basic_istringstream следующим образом.
template <class charType>
size_t splitWithStringStream(
const std::basic_string<charType>& str,
std::vector< std::basic_string<charType> >& tokens)
{
typedef std::basic_string<charType> my_string;
typedef std::vector< std::basic_string<charType> > my_vector;
typedef std::basic_istringstream<
charType, std::char_traits<charType> >
my_istringstream;
typedef std::istream_iterator<
std::basic_string<charType>, charType,
std::char_traits<charType> >
my_istream_iterator;
tokens.clear();
if (str.empty())
{
return 0;
}
my_istringstream iss(str);
std::copy(
my_istream_iterator{iss}, my_istream_iterator(),
std::back_inserter<my_vector>(tokens));
return tokens.size();
}
Функцию splitWithStringStream
можно использовать следующим образом.
std::string str("The quick brown fox jumped over the lazy dog.");
std::vector<std::string> tokens;
size_t s = splitWithStringStream(str, tokens);
Функция splitWithStringStream
имеет то преимущество, что не использует ничего, кроме функций, которые являются частью стандартной библиотеки C ++. Чтобы использовать его, вам просто нужно включить следующие заголовки стандартной библиотеки C ++: алгоритм, итератор, sstream, строка и вектор.
Альтернативный вариант функции следующий.
template <class charType>
size_t splitWithStringStream1(
const std::basic_string<charType>& str,
std::vector< std::basic_string<charType> >& tokens)
{
typedef std::basic_string<charType> my_string;
typedef std::vector< std::basic_string<charType> > my_vector;
typedef std::basic_istringstream<
charType, std::char_traits<charType> >
my_istringstream;
typedef std::istream_iterator<
std::basic_string<charType>, charType,
std::char_traits<charType> >
my_istream_iterator;
tokens.clear();
if (str.empty())
{
return 0;
}
my_istringstream iss(str);
std::vector<my_string> results(
my_istream_iterator{iss}, my_istream_iterator());
tokens.swap(results);
return tokens.size();
}
У функций splitWithStringStream
и splitWithStringStream1
есть два недостатка. Во-первых, функции потенциально неэффективны и медленны, поскольку вся строка копируется в поток, который занимает столько же памяти, сколько и строка. Во-вторых, они поддерживают только строки, разделенные пробелами.
Решение 2. std :: getline
Следующая функция позволяет использовать в качестве разделителя символ, отличный от пробела.
template<typename charType>
size_t splitWithGetLine(
const std::basic_string<charType>& str,
const charType delim,
std::vector< std::basic_string<charType> >& tokens)
{
typedef std::basic_string<charType> my_string;
typedef std::basic_istringstream<
charType, std::char_traits<charType> >
my_istringstream;
tokens.clear();
if (str.empty())
{
return 0;
}
my_istringstream iss(str);
my_string token;
while (std::getline(iss, token, delim))
{
tokens.push_back(token);
}
return tokens.size();
}
Эту функцию можно использовать следующим образом.
std::wstring str(L"This is a test.||This is only a test.|This concludes this test.");
std::vector<std::wstring> tokens;
size_t s = splitWithGetLine(str, L'|', tokens);
Обратите внимание, что это решение не пропускает пустые токены, поэтому в приведенном выше примере будут токены, содержащие четыре элемента, один из которых будет пустой строкой.
Эта функция, как и функции splitWithStringStream
и splitWithStringStream1
, потенциально неэффективна и медленна. Это позволяет указать символ-разделитель. Однако он поддерживает только один ограничивающий символ. Эта функция не поддерживает строки, в которых можно использовать несколько символов-разделителей.
Чтобы использовать функцию splitWithGetLine
, вам просто нужно включить следующие заголовки стандартной библиотеки C ++: алгоритм, итератор, sstream, строка и вектор.
Использование только членов класса basic_string
Решение 1
Эту задачу можно выполнить, используя только функции-члены класса basic_string. Следующая функция позволяет указать символ-разделитель и использует только члены find_first_not_of, find и substr класса basic_string. Функция также имеет необязательные параметры, которые позволяют указать, что пустые токены должны игнорироваться, и указать максимальное количество сегментов, на которые должна быть разбита строка.
template<typename charType>
size_t splitWithBasicString(
const std::basic_string<charType>& str,
const charType delim,
std::vector< std::basic_string<charType> > &tokens,
const bool trimEmpty = false,
const size_t maxTokens = (size_t)(-1))
{
typedef std::basic_string<charType> my_string;
typedef typename my_string::size_type my_size_type;
tokens.clear();
if (str.empty())
{
return 0;
}
my_size_type len = str.length();
// Skip delimiters at beginning.
my_size_type left = str.find_first_not_of(delim, 0);
size_t i = 1;
if (!trimEmpty && left != 0)
{
tokens.push_back(my_string());
++i;
}
while (i < maxTokens)
{
my_size_type right = str.find(delim, left);
if (right == my_string::npos)
{
break;
}
if (!trimEmpty || right - left > 0)
{
tokens.push_back(str.substr(left, right - left));
++i;
}
left = right + 1;
}
if (left < len)
{
tokens.push_back(str.substr(left));
}
return tokens.size();
}
Эта функция не страдает от тех же потенциальных проблем с производительностью, что и потоковые функции, и позволяет вам указать символ-разделитель. Однако он поддерживает только один ограничивающий символ. Эта функция не поддерживает строки, в которых можно использовать несколько символов-разделителей.
Чтобы использовать функцию splitWithBasicString
, вам просто нужно включить следующие заголовки стандартной библиотеки C ++: string и vector.
Решение 2
Иногда в строке, которую нужно разделить, используется несколько разных символов-разделителей. В других случаях может быть просто невозможно заранее узнать наверняка, какие символы-разделители используются. В этих случаях вы можете знать, что ограничивающий символ может быть одним из нескольких возможных. В этом случае необходимо, чтобы функция могла принимать строку, содержащую все возможные символы-разделители. Этого также можно добиться, используя только функции-члены класса basic_string.
Следующая функция позволяет указать символ-разделитель и использует только члены find_first_not_of, find_first_of и substr класса basic_string. Функция также имеет необязательные параметры, которые позволяют указать, что пустые токены должны игнорироваться, и указать максимальное количество сегментов, на которые должна быть разбита строка.
template<typename charType>
size_t splitWithBasicString(
const std::basic_string<charType>& str,
const std::basic_string<charType>& delim,
std::vector< std::basic_string<charType> >& tokens,
const bool trimEmpty = false,
const size_t maxTokens = (size_t)(-1))
{
typedef std::basic_string<charType> my_string;
typedef typename my_string::size_type my_size_type;
tokens.clear();
if (str.empty())
{
return 0;
}
my_size_type len = str.length();
// Skip delimiters at beginning.
my_size_type left = str.find_first_not_of(delim, 0);
size_t i = 1;
if (!trimEmpty && left != 0)
{
tokens.push_back(my_string());
++i;
}
while (i < maxTokens)
{
my_size_type right = str.find_first_of(delim, left);
if (right == my_string::npos)
{
break;
}
if (!trimEmpty || right - left > 0)
{
tokens.push_back(str.substr(left, right - left));
++i;
}
left = right + 1;
}
if (left < len)
{
tokens.push_back(str.substr(left));
}
return tokens.size();
}
Использование Boost
Boost - это набор проверенных кросс-платформенных библиотек C ++ с открытым исходным кодом, которые предназначены для дополнения и расширения стандартной библиотеки C ++. Boost предоставляет как минимум два метода разделения строки.
Решение 1
Один из вариантов - использовать функцию boost :: algorithm :: split в Библиотеке алгоритмов Boost String.
Чтобы использовать функцию разделения, просто включите boost / algorithm / string.hpp, а затем вызовите функцию следующим образом.
std::string str(" The quick brown fox\tjumped over the lazy dog.");
std::vector<std::string> strs;
boost::split(strs, str, boost::is_any_of("\t "));
Решение 2
Другой вариант - использовать Библиотеку Boost Tokenizer. Чтобы использовать библиотеку Boost Tokenizer, просто включите boost / tokenizer.hpp. Затем вы можете использовать Boost Tokenizer следующим образом.
typedef boost::char_separator<char> my_separator;
typedef boost::tokenizer<my_separator> my_tokenizer;
std::string str(" The quick brown fox\tjumped over the lazy dog.");
my_separator sep(" \t");
my_tokenizer tokens(str, sep);
my_tokenizer::iterator itEnd = tokens.end();
for (my_tokenizer::iterator it = tokens.begin(); it != itEnd; ++it)
{
std::cout << *it << std::endl;
}
Использование библиотеки C ++ String Toolkit
Другой вариант - использовать C ++ String Toolkit Library. В следующем примере показано, как можно использовать функцию strtk :: parse для разделения строки.
std::string str("The quick brown fox jumped over the lazy dog.");
std::vector<std::string> tokens;
strtk::parse(str, " ", tokens);
Другие варианты
Конечно, есть много других вариантов. Не стесняйтесь обращаться к веб-страницам, перечисленным в разделе ссылок ниже, для многих других вариантов.
Резюме
В этой статье я рассмотрел несколько вариантов разделения строк в C ++.
- Использование библиотеки времени выполнения C.
- 3 варианта выплевывания строк Использование класса basic_istringstream.
- 2 варианта разделения строк Использование только членов класса basic_string.
- 2 варианта разделения строк Используя Boost.
- Использование библиотеки C ++ String Toolkit.
Код для классов basic_istringstream, basic_string и Boost вместе с полным примером, демонстрирующим использование функций, можно найти на Ideone.
использованная литература
- Переполнение стека: разделение строки в C ++
- Переполнение стека: как токенизировать строку в C ++?
- Переполнение стека: как разделить строку с помощью istringstream с другим разделителем, кроме пробела?
- Как разбить строку в C ++
Первоначально опубликовано на yekneb.com 24 октября 2018 г.