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


242

如何在C ++中实现以下(Python伪代码)?

if argv[1].startswith('--foo='):
    foo_value = int(argv[1][len('--foo='):])

(例如,如果argv[1]is --foo=98,则foo_valueis 98。)

更新:我不愿讨论Boost,因为我只是想对一个简单的小型命令行工具进行很小的更改(我宁愿不必学习如何链接和使用Boost来进行较小的调整更改)。


也很有趣。
manlio 2015年

Answers:


447

使用rfind具有以下pos参数的重载:

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

谁还需要其他东西?纯STL!

许多人误将其理解为“向后搜索整个字符串以查找前缀”。这将产生错误的结果(例如,string("tititito").rfind("titi")返回2,因此与进行比较时== 0将返回false),并且效率低下(遍历整个字符串而不只是开始)。但是它不这样做,因为它传递pos参数as 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)

32
这个答案应该是最受好评的,而不是最好的答案。:D为什么在已经拥有STL时使用另一个库。
Iuliu Atudosiei

@ sweisgerber.dev,我对您的第一个争论感到困惑。from的返回值find只有在字符串titi开头才为零。如果在其他地方找到它,则将获得非零的返回值;如果未找到,则将获得非零的返回值npos。假设我是对的,我宁愿选择这个答案,因为我不必带任何非标准的东西(是的,我知道Boost无处不在,对于这样的简单东西,我只喜欢核心C ++库)。
paxdiablo

@paxdiablo:没错,它确实会检查它是否以开头titi,但是缺少转换部分。
sweisgerber.dev

2
我们是否有证据表明大多数编译器对此进行了优化?我在其他地方找不到提到“查找”或“ rfind”优化的常见做法,这是基于要检查的返回值的。
Superziyi

2
@alcoforado“ rfind将从字符串的后面开始...”不,那仅适用于rfind()不带pos参数的重载。如果您使用确实带有pos参数的重载,则它将不会搜索整个字符串,只会搜索该位置或更早的位置。(就像常规的find()带有pos参数的参数只在该位置或更高位置上查找。)因此pos == 0,如本答案所示,如果通过,则它将只考虑在该位置上的匹配。答案和评论中都已经说明了这一点。
亚瑟·塔卡

188

您可以这样做:

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

寻找诸如Boost.ProgramOptions之类的库为您完成此操作也是一个好主意。


7
最大的问题是atoi("123xyz")return 123,而Python的int("123xyz")抛出异常。
汤姆”,

我们可以采取的解决方法是对sscanf()进行比较,然后将结果与原始结果进行比较,以确定是继续处理还是引发异常。
Roopesh Majeti 09年

1
或者仅替换atoistrtolstrtoll,这使我们可以检测输入值中的错误情况。
汤姆(

1
这比rfind依赖于优化才能工作的解决方案更好。
卡尔马留斯19'Sep

143

仅出于完整性考虑,我将提到使用C的方式:

如果str是您的原始字符串,substr是您要检查的子字符串,则

strncmp(str, substr, strlen(substr))

0如果str 以开头,将返回substr。函数strncmpstrlen在C头文件中<string.h>

(最初由Yaseen Rauf 在此处发布,添加了标记)

对于不区分大小写的比较,请使用strnicmp代替strncmp

这是C的方法,对于C ++字符串,您可以使用如下相同的函数:

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

9
确实,每个人似乎都只是在“使用boost”,我很感谢stl或OS库版本
Force Gaia

是。但是,它假定字符串中没有空字符。如果不是这种情况,则应使用memcmp()
Avishai Y

除了这个简单漂亮的解决方案,为什么有人会使用其他东西呢?
亚当·扎赫兰

88

如果您已经在使用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 ++命令行解析器”将产生许多选项。


107
为字符串前缀检查引入巨大的依赖项就像用大炮射击小鸟。
托比

150
当有人问如何在C ++中执行简单的字符串操作时,“使用Boost”始终是错误的答案。
格伦·梅纳德

90
负1表示建议加强
免疫

37
如果您已经在项目中使用boost,那么在此处使用boost是正确的。
亚历克斯·谢

17
答案的前缀是“如果您正在使用Boost ...”。显然,这是正确的答案,“ ...如果使用的是Boost”。如果没有,请查看@Thomas的建议
NuSkooler

82

我使用自己的代码:

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

2
最简洁的方法仅取决于std :: string,除了在最终substr的末尾删除可选且具有误导性的parameter.size()。
本·布莱恩特

@ ben-bryant:感谢大家的注意。不知道这是可选的。
侯赛因Yağlı

16
使用substr会导致不必要的复制。Thomas的答案中str.compare(start, count, substr)使用的方法更有效。razvanco13的答案还有另一种避免使用进行复制的方法。std::equal
Felix Dombek 2013年

4
@HüseyinYağlı Thomas uses atoi which is only for windows嗯?atoi自……以来一直是C标准库函数。实际上,这atoi是不好的-不是因为它是Windows特定的-而是因为(1)C,而不是C ++,以及(2)即使在C语言中也已弃用(您应该使用strtol一个或其他相关功能。atoi)没有错误处理。不过,无论如何,这仅是C语言。
Parthian Shot 2015年

50

尚无人使用过STL 算法/不匹配功能。如果返回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

2
使用std::equal字符串的缺点是无法检测到字符串结尾,因此您需要手动检查前缀是否短于整个字符串。(如示例程序中正确完成,但在上面的单行代码中省略了。)
Felix Dombek

那么,没有比rfind有好处吗?
АндрейВахрушев

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());

6
rfind(x, 0) == 0应该在标准中真正定义为starts_with
porges '16

1
不,因为rfind()(代替startswith())效率很低-它一直搜索到字符串的末尾。
ankostis

4
@ankostis rfind(x)从头开始搜索直到找到x。但是rfind(x,0)从头开始搜索(位置= 0)直到头开始;因此它仅在需要搜索的地方搜索;不会从头到尾搜索。
匿名Co

18

在C ++ 17中,可以将std::basic_string_view&与C ++ 20 std::basic_string::starts_with或一起使用std::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完全可以。它在错误的输入上引发异常(在此代码中处理)。您还有其他想法吗?
罗伊·丹顿

你在谈论atoi<cstdlib>?该文档说 “它从不抛出异常”。
罗兰·伊利格

@RolandIllig我指的是您的第一个评论。看来,您是在错误地谈论atoi而不是std::atoi。前者不安全,而后者很好。我在这里的代码中使用后者。
罗伊·丹顿

std::atoi通过引用适当的引用向我证明确实引发了异常。除非您这样做,否则我不相信您,因为同时拥有这两者::atoistd::atoi以完全不同的方式行事会非常令人困惑。
罗兰·伊利格

4
@RolandIllig感谢您的坚持!没错,这是std::atoi代替使用的疏忽 std::stoi。我已经解决了。
罗伊·丹顿

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

3
@GregorDoroschenko它确实回答“检查字符串是否以另一个开头”部分。
etarion

1
使用std :: string高效而优雅。我从中学到了很多。
Michael B

1
适于与单衬里一起使用的加分点if (one-liner)
Adam.at.Epsilon,

@Roland Illig为什么您认为那种情况下的行为是不确定的?表达式将返回false,因为substr根据en.cppreference.com/w/cpp/string/basic_string/substr
Macsinus

11

使用STL可能看起来像:

std::string prefix = "--foo=";
std::string arg = argv[1];
if (prefix.size()<=arg.size() && std::equal(prefix.begin(), prefix.end(), arg.begin())) {
  std::istringstream iss(arg.substr(prefix.size()));
  iss >> foo_value;
}

2
那应该是if (prefix.size()<=arg.size() && std::equal(...))
贾里德·格拉布

10

冒着使用C构造的麻烦,我确实认为此sscanf示例比大多数Boost解决方案更为优雅。如果您在任何有Python解释器的地方运行,也不必担心链接!

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i) {
        int number = 0;
        int size = 0;
        sscanf(argv[i], "--foo=%d%n", &number, &size);
        if (size == strlen(argv[i])) {
            printf("number: %d\n", number);
        }
        else {
            printf("not-a-number\n");
        }
    }
    return 0;
}

这是一些示例输出,演示了该解决方案与等效的Python代码一样正确地处理了前导/后处理垃圾,并且比任何使用atoi(错误地忽略非数字后缀)的方法都正确。

$ ./scan --foo=2 --foo=2d --foo='2 ' ' --foo=2'
number: 2
not-a-number
not-a-number
not-a-number

7
如果argv[i]为is "--foo=9999999999999999999999999",则该行为是未定义的(尽管大多数或所有实现应具有合理的行为)。我假设9999999999999999999999999 > INT_MAX
基思·汤普森

10

我使用std::string::compare包裹在实用程序方法中,如下所示:

static bool startsWith(const string& s, const string& prefix) {
    return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}

5

为什么不使用gnu getopts?这是一个基本示例(不进行安全检查):

#include <getopt.h>
#include <stdio.h>

int main(int argc, char** argv)
{
  option long_options[] = {
    {"foo", required_argument, 0, 0},
    {0,0,0,0}
  };

  getopt_long(argc, argv, "f:", long_options, 0);

  printf("%s\n", optarg);
}

对于以下命令:

$ ./a.out --foo=33

你会得到

33

5

如果您需要C ++ 11兼容性并且不能使用boost,这是一个boost-compatible插件,其中包含用法示例:

#include <iostream>
#include <string>

static bool starts_with(const std::string str, const std::string prefix)
{
    return ((prefix.size() <= str.size()) && std::equal(prefix.begin(), prefix.end(), str.begin()));
}

int main(int argc, char* argv[])
{
    bool usage = false;
    unsigned int foos = 0; // default number of foos if no parameter was supplied

    if (argc > 1)
    {
        const std::string fParamPrefix = "-f="; // shorthand for foo
        const std::string fooParamPrefix = "--foo=";

        for (unsigned int i = 1; i < argc; ++i)
        {
            const std::string arg = argv[i];

            try
            {
                if ((arg == "-h") || (arg == "--help"))
                {
                    usage = true;
                } else if (starts_with(arg, fParamPrefix)) {
                    foos = std::stoul(arg.substr(fParamPrefix.size()));
                } else if (starts_with(arg, fooParamPrefix)) {
                    foos = std::stoul(arg.substr(fooParamPrefix.size()));
                }
            } catch (std::exception& e) {
                std::cerr << "Invalid parameter: " << argv[i] << std::endl << std::endl;
                usage = true;
            }
        }
    }

    if (usage)
    {
        std::cerr << "Usage: " << argv[0] << " [OPTION]..." << std::endl;
        std::cerr << "Example program for parameter parsing." << std::endl << std::endl;
        std::cerr << "  -f, --foo=N   use N foos (optional)" << std::endl;
        return 1;
    }

    std::cerr << "number of foos given: " << foos << std::endl;
}

2

您也可以使用strstr

if (strstr(str, substr) == substr) {
    // 'str' starts with 'substr'
}

但是我认为这仅对短字符串有用,因为当字符串实际上不是以'substr'开头时,它必须遍历整个字符串。


2

好的,为什么要复杂地使用库和东西?C ++字符串对象重载[]运算符,因此您可以比较字符。就像我刚才所做的那样,因为我想列出目录中的所有文件,而忽略不可见的文件以及..和。伪文件。

while ((ep = readdir(dp)))
{
    string s(ep->d_name);
    if (!(s[0] == '.')) // Omit invisible files and .. or .
        files.push_back(s);
}

就这么简单..



2
@robertwb Google+不再可用
__Static__assert

0
std::string text = "--foo=98";
std::string start = "--foo=";

if (text.find(start) == 0)
{
    int n = stoi(text.substr(start.length()));
    std::cout << n << std::endl;
}

3
如果您避免粘贴没有代码说明的代码,那就太好了。谢谢。
重生

1
低效的代码,将继续搜索字符串的开头。
ankostis

0

在C ++ 11或更高版本中,您可以使用find()find_first_of()

使用find查找单个字符的示例:

#include <string>
std::string name = "Aaah";
size_t found_index = name.find('a');
if (found_index != std::string::npos) {
    // Found string containing 'a'
}

使用find查找完整字符串并从位置5开始的示例:

std::string name = "Aaah";
size_t found_index = name.find('h', 3);
if (found_index != std::string::npos) {
    // Found string containing 'h'
}

使用find_first_of()和仅第一个字符的示例仅在开头搜索:

std::string name = ".hidden._di.r";
size_t found_index = name.find_first_of('.');
if (found_index == 0) {
    // Found '.' at first position in string
}

祝好运!


为什么不rfind?rfind(str,0)不会不必要地扫描整个字符串进行选择,因为它无法前进。见其他人。
user2864740 '19

0

由于C ++ 11 std::regex_search也可以用于提供更复杂的表达式匹配。以下示例还处理了浮点数thorugh std::stof和随后的强制转换为int

但是,如果前缀不匹配,则parseInt下面显示的方法可能会引发std::invalid_argument异常。这可以根据给定的应用轻松进行调整:

#include <iostream>
#include <regex>

int parseInt(const std::string &str, const std::string &prefix) {
  std::smatch match;
  std::regex_search(str, match, std::regex("^" + prefix + "([+-]?(?=\\.?\\d)\\d*(?:\\.\\d*)?(?:[Ee][+-]?\\d+)?)$"));
  return std::stof(match[1]);
}

int main() {
    std::cout << parseInt("foo=13.3", "foo=") << std::endl;
    std::cout << parseInt("foo=-.9", "foo=") << std::endl;
    std::cout << parseInt("foo=+13.3", "foo=") << std::endl;
    std::cout << parseInt("foo=-0.133", "foo=") << std::endl;
    std::cout << parseInt("foo=+00123456", "foo=") << std::endl;
    std::cout << parseInt("foo=-06.12e+3", "foo=") << std::endl;

//    throw std::invalid_argument
//    std::cout << parseInt("foo=1", "bar=") << std::endl;

    return 0;
}

下面的答案详细介绍了正则表达式模式的魔力。

编辑:先前的答案未执行转换为整数。


0

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

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

-3
if(boost::starts_with(string_to_search, string_to_look_for))
    intval = boost::lexical_cast<int>(string_to_search.substr(string_to_look_for.length()));

这是完全未经测试的。原理与Python相同。需要Boost.StringAlgo和Boost.LexicalCast。

检查字符串是否以另一个字符串开头,然后获取第一个字符串的子字符串(“切片”)并使用词法转换将其转换。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.