如何在C++中从字符串中删除子字符串

4

我有一个字符串 s = "home/dir/folder/name"

我想把 s 拆分成 s1 = "home/dir/folder"s2 = "name"

我尝试了以下代码:

char *token = strtok( const_cast<char*>(s.c_str() ), "/" );
std::string name;
std::vector<int> values;
while ( token != NULL )
{
    name=token;

    token = strtok( NULL, "/" );
}

现在的s1=name。 那么s2呢?


1
请发布正确的代码 - 这段代码无法编译。首先,您在 std::string name 上缺少了一个分号。 - user195488
2
永远不要强制转换 std::string::c_str() 的结果以去除 const。这个 const char * 在任何合理的方式下都是不可修改的。因此,如果你真的需要使用 strtok,你必须进行复制 - Evan Teran
6个回答

6
我建议不要使用 strtok。建议使用 Boost Tokenizer 替代(这里有一些示例)。
或者,如果只是想查找最后一个 '/' 的位置,可以使用 std::string::rfind
#include <string>
#include <iostream>

int main() {
  std::string s = "home/dir/folder/name";
  std::string::size_type p = s.rfind('/');
  if (p == std::string::npos) {
    std::cerr << "s contains no forward slashes" << std::endl;
  } else {
    std::string s1(s, 0, p);
    std::string s2(s, p + 1);
    std::cout << "s1=[" << s1 << "]" << std::endl;
    std::cout << "s2=[" << s2 << "]" << std::endl;
  }
  return 0;
}

谢谢!在s1中我会有文件名,在s2中会有路径,对吗? - sunset
2
Boost tokenizer 可能有点过头了,但如果他经常做这种事情,Boost 文件系统有各种有用的函数。 - James Kanze
@sunset: 根据您的规范,反过来做(我想要将字符串s分成s1="home/dir/folder"和s2="name")。 - NPE

4

如果您的目标只是获取字符串中最后一个\/的位置,您可以使用string::find_last_of来完成。

从那里,您可以使用string::substr或构造函数std::string使用迭代器来获取所需的子部分。

只需确保原始字符串至少包含一个\/,或者您正确处理了该情况。

下面是一个实现所需功能并返回一个包含路径的两个部分的pair的函数。如果指定的路径不包含任何\/字符,则整个路径将作为该对的第二成员返回,而第一成员为空。如果路径以/\结尾,则第二成员为空。

using std::pair;
using std::string;

pair<string, string> extract_filename(const std::string& path)
{
  const string::size_type p = path.find_last_of("/\\");

  // No separator: a string like "filename" is assumed.
  if (p == string::npos)
    return pair<string, string>("", path);

  // Ends with a separator: a string like "my/path/" is assumed.
  if (p == path.size())
    return pair<string, string(path.substr(0, p), "");

  // A string like "my/path/filename" is assumed.
  return pair<string, string>(path.substr(0, p), path.substr(p + 1));
}

当路径不符合预期格式时,你也可以修改此函数以抛出错误而不是优雅地退出。


1

请查看boost string split

示例:

string str1("hello abc-*-ABC-*-aBc goodbye");
typedef vector< iterator_range<string::iterator> > find_vector_type;
find_vector_type FindVec; // #1: Search for separators
ifind_all( FindVec, str1, "abc" ); // FindVec == { [abc],[ABC],[aBc] }
typedef vector< string > split_vector_type;
split_vector_type SplitVec; // #2: Search for tokens
split( SplitVec, str1, is_any_of("-*"), token_compress_on ); 
// SplitVec == { "hello abc","ABC","aBc goodbye" }

1
几点建议:首先,您使用的strtok是未定义行为;在g++的情况下,甚至可能导致一些非常奇怪的行为。您不能在字符串背后修改std::string的内容并期望逃脱惩罚。(需要使用const_cast应该已经提示了您。)
其次,如果您要操作文件名,我强烈推荐使用boost::filesystem。它知道所有关于路径分隔符等的事情,并且路径的最后一个组件通常是特殊的(因为它可能是文件名,而不是目录)。
第三,如果这只是一次性的,或者由于其他原因您不能或不想使用Boost:
std::string::const_iterator pivot
    = std::find( s.rbegin(), s.rend(), '/' ).base();

将为您提供一个迭代器,指向最后一个'/'之后的第一个字符,或者如果没有,则指向字符串中的第一个字符。之后,使用string的两个迭代器构造函数很容易获取这两个组件:

std::string basename( pivot, s.end() );
std::string dirname( s.begin(), pivot == s.begin() ? pivot : pivot - 1 );

如果您以后需要支持Windows,只需将find替换为:

static std::string const dirSeparators( "\\/" );
std::string::const_iterator pivot
    = std::find_first_of( s.rbegin(), s.rend(),
                          dirSeparators.begin(), dirSeparators.end() );

@sunset 不行,因为没有正确使用 strtok 的方法。这个函数只存在于 C++ 是因为它存在于 C 中,而且它在 C 中只是为了向后兼容的原因才存在。如果想要使用它与 std::string 一起,就需要将字符串复制到 char[] 中。 - James Kanze

0

你不能在 std::string 上使用 strtok

strtok 会修改字符串。这会破坏 c_str() 的约定。

使用 const_cast<> 是一个错误的信号。


0

只需使用字符串方法:

std::string  s="home/dir/folder/name"
std::string::size_type n = s.find_last_of("/");
std::string  s1 = s.substr(0,n);

if (n != std::string::npos) // increment past the '/' if we found it
{  ++n;
}
std::string  s2 = s.substr(n);

两个建议:

  • 永远不要使用strtok
  • 如果你正在处理文件系统路径,请查看boost::filesystem
  • 如果你想通用地处理标记化,请使用流操作符
  • 或者使用boost::tokenizer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接