如何检查C++ std::string是否以某个字符串开头,并将子字符串转换为整数?

410

我该如何在C++中实现以下Python伪代码?

if argv[1].startswith('--foo='):
    foo_value = int(argv[1][len('--foo='):])
(例如,如果argv [1]--foo=98,那么foo_value98。) 更新: 我不太愿意使用Boost,因为我只想对一个简单的命令行工具进行很小的更改(我不想为了一项小更改而学习如何链接和使用Boost)。

这也很有趣。 - manlio
23
C++20新增了starts_with函数,用于判断一个字符串是否以另一个字符串开头。 - Louis Go
7
谢谢。不再需要使用rfind()真是太好了。 - Melroy van den Berg
24个回答

805

使用带有搜索位置参数posrfind重载函数,并将其设置为0:

std::string s = "tititoto";
if (s.rfind("titi", 0) == 0) { // pos=0 limits the search to the prefix
  // s starts with prefix
}

谁还需要别的?纯STL就够了!

许多人错误地理解为“在整个字符串中向后查找前缀”。这会得到错误的结果(例如,string("tititito").rfind("titi", 0)返回2,因此与== 0比较时将返回false),并且效率低下(需要查找整个字符串而不是只查找开头)。但它实际上并没有这样做,因为它将pos参数传递为0,这将限制搜索仅匹配该位置或更早。例如:

std::string test = "0123123";
size_t match1 = test.rfind("123");    // returns 4 (rightmost match)
size_t match2 = test.rfind("123", 2); // returns 1 (skipped over later match)
size_t match3 = test.rfind("123", 0); // returns std::string::npos (i.e. not found)

3
@sweisgerber.dev,我对你的第一个观点感到困惑。从find返回的值只有在titi在字符串开头时才为零。如果它在其他地方找到,您将获得非零返回值,如果未找到,则会获得非零的npos。假设我是正确的,我更喜欢这个答案,因为我不需要引入任何非标准的东西(是的,我知道Boost无处不在,但我更喜欢使用核心C ++库来处理这样简单的事情)。 - paxdiablo
3
大多数编译器是否进行了此项优化,我们有任何证据吗?我并没有看到其他地方提到根据它所检查的返回值优化"find"或"rfind"是常见做法。请问需要翻译哪些内容? - Superziyi
10
“rfind将从字符串的末尾开始搜索…” 不,这仅适用于不带“pos”参数的“rfind()”函数重载。如果使用带有“pos”参数的函数重载,则它不会搜索整个字符串,而是只搜索该位置及其之前的部分。(就像使用具有“pos”参数的普通“find()”函数一样,只会在该位置或之后查找。)所以,如果您传递“pos==0”,就像在这个答案中所示,那么它将只考虑在该位置上匹配的内容。这已经在答案和评论中解释过了。 - Arthur Tacca
2
“that position or earlier” 是这里的重要短语。 - Vishal Sahu
2
要查找一个字符串是否以另一个字符串结尾,您可以执行s.find("toto", s.length() - 4) != std::string::npos操作。将数字4替换为您要查找的后缀长度。 - BlueStaggo
显示剩余2条评论

206

你可以像这样做:

std::string prefix("--foo=");
if (!arg.compare(0, prefix.size(), prefix))
    foo_value = std::stoi(arg.substr(prefix.size()));

寻找像 Boost.ProgramOptions 这样的库来完成此任务也是一个好主意。


11
这个问题的主要在于 atoi("123xyz") 返回的是 123,然而 Python 的 int("123xyz") 会抛出异常。 - Tom
我们可以采用的解决方法是使用 sscanf() 函数并比较结果和原始数据,以决定是继续执行还是抛出异常。 - Roopesh Majeti
1
或者只需将 atoi 替换为 strtolstrtoll,这样我们就可以检测输入值中的错误条件。 - Tom
1
这是比“rfind”更好的解决方案,它不依赖于优化来工作。 - Calmarius
2
@Calmarius,“rfind”解决方案不依赖于任何优化。根据定义,“rfind”在给定“pos = 0”时只查看单个索引,因此它始终是一种有效的检查方式。哪种语法更令人愉悦是个人偏好的问题。 - Yuval
显示剩余3条评论

173

为了完整性,我提一下用C语言的方法:

如果变量str是你的原始字符串,substr是你想要检查的子字符串:

strncmp(str, substr, strlen(substr))

如果strsubstr开头,将返回0。函数strncmpstrlen在C标准库头文件<string.h>中定义。

(本文最初由Yaseen Rauf 发布,我添加了一些标记)

对于不区分大小写的比较,使用strnicmp而不是strncmp

这是用C语言实现的方法,对于C++中的字符串,可以使用相同的函数:

strncmp(str.c_str(), substr.c_str(), substr.size())

19
确实,似乎每个人都只是说“使用boost”,而我对STL或操作系统库版本表示感激。 - Force Gaia
2
是的。但是,它假设字符串中没有空字符。如果不是这种情况,应该使用memcmp() - Avishai Y
2
为什么有人会使用除了这个简单美妙的解决方案之外的其他东西呢? - Adham Zahran
2
@AvishaiY “它假设字符串中没有空字符。” 这与假设一个“int”不包含值“-0.5”一样——C字符串恰好包含单个空字符作为结束标记。如果在任何其他位置包含\0,则根据定义它不是C字符串。 - ABaumstumpf

97

如果您已经在使用Boost,那么您可以使用 boost字符串算法 + boost类型转换:

#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>

try {    
    if (boost::starts_with(argv[1], "--foo="))
        foo_value = boost::lexical_cast<int>(argv[1]+6);
} catch (boost::bad_lexical_cast) {
    // bad parameter
}

这种方法与其他回答提供的方法一样,适用于非常简单的任务,但从长远来看,通常最好使用命令行解析库。Boost 有一个 (Boost.Program_options),如果您已经在使用 Boost ,那么可能会很合适。

否则,搜索“c++命令行解析器”将产生许多选项。


134
为了检查字符串前缀而引入大量依赖项就像用加农炮打鸟一样。 - Tobi
176
当有人问如何在C++中进行简单的字符串操作时,“使用Boost”总是错误的答案。 - Glenn Maynard
100
建议使用Boost,扣1分。 - uglycoyote
48
如果你已经在项目中使用了 boost,那么在这里使用 boost 是正确的。 - Alex Che
29
答案前缀为“If you're using Boost...”,很明显这是正确的答案,“如果你在使用Boost的话”的意思。如果没有使用Boost,请参考@Thomas的建议。 - NuSkooler
显示剩余6条评论

91

我使用的代码:

std::string prefix = "-param=";
std::string argument = argv[1];
if(argument.substr(0, prefix.size()) == prefix) {
    std::string argumentValue = argument.substr(prefix.size());
}

3
最简洁的方法只需使用std::string,除去在最终的substr末尾的可选和误导性的argument.size()。 - Ben Bryant
@ben-bryant:谢谢你提醒我。我不知道它是可选的。 - Huseyin Yagli
20
使用 substr 会导致不必要的复制。Thomas' answer 中使用的 str.compare(start, count, substr) 方法更高效。razvanco13's answer 提供另一种方法,通过使用 std::equal 避免复制。 - Felix Dombek
4
@HüseyinYağlı说Thomas使用的是只适用于Windows的atoi,嗯?实际上,atoi一直以来都是C标准库函数。事实上,atoi不好的原因不是它是Windows专用的,而是因为它(1)是C而不是C++,(2)即使在C中已经被废弃了(你应该使用strtol或其他相关函数),因为atoi没有错误处理。但是,再次强调,这仅限于C语言。 - Parthian Shot

54

目前还没有人使用STL algorithm/mismatch 函数。如果此函数返回true,则prefix是“toCheck”的前缀:

std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()

完整的示例程序:

#include <algorithm>
#include <string>
#include <iostream>

int main(int argc, char** argv) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
                  << "Will print true if 'prefix' is a prefix of string" << std::endl;
        return -1;
    }
    std::string prefix(argv[1]);
    std::string toCheck(argv[2]);
    if (prefix.length() > toCheck.length()) {
        std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
                  << "'prefix' is longer than 'string'" <<  std::endl;
        return 2;
    }
    if (std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()) {
        std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck << '"' << std::endl;
        return 0;
    } else {
        std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"' << toCheck << '"' << std::endl;
        return 1;
    }
}

编辑:

正如 @James T. Huggett 建议的那样,std::equal更适合问题:A是否为B的前缀?而且代码稍微更短:

std::equal(prefix.begin(), prefix.end(), toCheck.begin())

完整的示例程序:

#include <algorithm>
#include <string>
#include <iostream>

int main(int argc, char **argv) {
  if (argc != 3) {
    std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
              << "Will print true if 'prefix' is a prefix of string"
              << std::endl;
    return -1;
  }
  std::string prefix(argv[1]);
  std::string toCheck(argv[2]);
  if (prefix.length() > toCheck.length()) {
    std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
              << "'prefix' is longer than 'string'" << std::endl;
    return 2;
  }
  if (std::equal(prefix.begin(), prefix.end(), toCheck.begin())) {
    std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck
              << '"' << std::endl;
    return 0;
  } else {
    std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"'
              << toCheck << '"' << std::endl;
    return 1;
  }
}

2
为什么不使用std::equal? - Brice M. Dempsey
听起来不错,而且代码会更短。我想,现在我得编辑答案 :p - matiu
4
使用 std::equal 来对字符串进行比较有一个缺点,它不能检测字符串的结束,因此您需要手动检查前缀是否短于整个字符串。(在上面的单行代码中未正确执行此操作,但在示例程序中已正确执行。) - Felix Dombek
那么,与rfind相比没有任何好处吗? - Андрей Вахрушев
方法 endsWith 将会是 std::equal(suffix.rbegin(), suffix.rend(), toCheck.rbegin() - krab

40

使用 C++17,你可以使用 std::basic_string_view ,而在 C++20 中则支持 std::basic_string::starts_withstd::basic_string_view::starts_with

std::string_view 相对于 std::string 的优点 - 在内存管理方面 - 是它只持有一个指向字符串(char 型对象的连续序列)的指针,并知道其大小。以下是一个示例,不需要移动/复制源字符串即可获取整数值:

#include <exception>
#include <iostream>
#include <string>
#include <string_view>

int main()
{
    constexpr auto argument = "--foo=42"; // Emulating command argument.
    constexpr auto prefix = "--foo=";
    auto inputValue = 0;

    constexpr auto argumentView = std::string_view(argument);
    if (argumentView.starts_with(prefix))
    {
        constexpr auto prefixSize = std::string_view(prefix).size();
        try
        {
            // The underlying data of argumentView is nul-terminated, therefore we can use data().
            inputValue = std::stoi(argumentView.substr(prefixSize).data());
        }
        catch (std::exception & e)
        {
            std::cerr << e.what();
        }
    }
    std::cout << inputValue; // 42
}

1
@RolandIllig 不,std::atoi 完全没问题。它会在输入错误时抛出异常(这段代码已经处理了)。你有其他想法吗? - Roi Danton
你是在谈论来自<cstdlib>atoi吗?文档说:“它从不抛出异常”。 - Roland Illig
@RolandIllig 我指的是你的第一条评论。看起来,你错误地谈论了 atoi 而不是 std::atoi。前者使用不安全,而后者则没问题。我在这里的代码中使用的是后者。 - Roi Danton
请引用适当的参考资料,证明 std::atoi 确实会抛出异常。在你提供证据之前,我不会相信你,因为让 ::atoistd::atoi 以完全不同的方式运作将会非常令人困惑。 - Roland Illig
6
谢谢您的坚持,您是正确的,使用 std::atoi 而不是 std::stoi 是一个疏忽。我已经修复了这个问题。 - Roi Danton
这个答案是最现代的方法。+1 使用 string_view。 感谢您提供如此精心策划和强大的代码示例! - brita_

26

鉴于两个字符串——argv[1]"--foo"——都是C字符串,@FelixDombek的回答无疑是最佳解决方案。

然而,看到其他答案,我认为值得注意的是,如果您的文本已经作为std::string可用,那么存在一种简单、零复制、最大效率的解决方案,迄今为止还没有被提及:

const char * foo = "--foo";
if (text.rfind(foo, 0) == 0)
    foo_value = text.substr(strlen(foo));

如果foo已经是一个字符串:

std::string foo("--foo");
if (text.rfind(foo, 0) == 0)
    foo_value = text.substr(foo.length());

8
"rfind(x, 0) == 0" 应该在标准中被定义为 "starts_with"。 - porges
1
不行,因为rfind()(而非startswith())效率非常低 - 它会一直搜索到字符串末尾。 - ankostis
4
@ankostis的方法rfind(x)从字符串结尾向开头搜索,直到找到x为止。但是,rfind(x,0)会从开头(位置=0)开始搜索,直到开头;因此它只搜索需要搜索的位置,不会从/到结尾进行搜索。 - Anonymous Coward

20

从C++20开始,您可以使用starts_with方法。

std::string s = "abcd";
if (s.starts_with("abc")) {
    ...
}

15
text.substr(0, start.length()) == start

3
@GregorDoroschenko 这确实回答了“检查字符串是否以另一个字符串开头”的部分。 - etarion
2
高效而优雅,使用std::string。我从中学到了最多。 - Michael B
1
仅返回翻译后的文本。如果是一行代码适用于“if (one-liner)”,则额外加分。 - Adam.at.Epsilon
@Roland Illig,你为什么认为那种情况的行为是未定义的?根据https://en.cppreference.com/w/cpp/string/basic_string/substr,该表达式将返回false,因为substr返回一个与text长度相同的字符串。 - Macsinus

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