Как проверить, начинается ли строка C ++ std :: string с определенной строки, и преобразовать подстроку в int?

242

Как реализовать следующее (псевдокод Python) в C ++?

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

(Например, если argv[1]есть --foo=98, то foo_valueесть 98.)

Обновление: я не решаюсь взглянуть на Boost, так как я просто смотрю на внесение очень небольших изменений в простой маленький инструмент командной строки (я бы предпочел не изучать, как подключаться и использовать Boost для незначительного изменение).

Дэрил Спитцер
источник
Это тоже интересно.
Манлио

Ответы:

449

Используйте перегрузку 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.
Юлию Атудосией
@ sweisgerber.dev, я запутался в твоем первом утверждении. Возвращаемое значение from findбудет нулевым, только если оно titiнаходится в начале строки. Если он найден где-то еще, вы получите ненулевое возвращаемое значение и, если он не найден, вы получите nposтакже ненулевое значение. Предполагая, что я прав, я бы предпочел этот ответ, поскольку мне не нужно вводить какие-то нестандартные вещи (да, я знаю, что Boost везде, я бы просто предпочел базовые библиотеки C ++ для таких простых вещей, как этот).
paxdiablo
@paxdiablo: вы правы, он действительно проверяет, начинается ли он titi, но часть преобразования отсутствует.
sweisgerber.dev
2
Есть ли у нас доказательства того, что это оптимизировано в большинстве компиляторов? Я не нахожу в другом месте упоминания, что оптимизация "find" или "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")возврат 123, тогда как Python int("123xyz")выдает исключение.
Том
Обходной путь мы можем сделать с помощью функции sscanf () и сравнить результат и оригинал, чтобы решить, следует ли продолжить или выбросить исключение.
Рупеш Маджети
1
Или просто замените atoiна strtolили strtoll, что позволяет нам определять условия ошибки во входном значении.
Том
1
Это лучшее решение, чем решение, rfindкоторое зависит от оптимизации работы.
Кальмарий
143

Просто для полноты я упомяну способ C сделать это:

Если strваша исходная строка, substrподстрока, которую вы хотите проверить, то

strncmp(str, substr, strlen(substr))

вернется, 0если str начинается с substr. Функции strncmpи strlenнаходятся в заголовочном файле C<string.h>

(первоначально размещено здесь Ясином Рауфом , добавлена ​​разметка)

Для сравнения без учета регистра используйте 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 string + boost lexical cast:

#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 ++.
Гленн Мейнард
90
минус 1 за предложение Boost
uglycoyote
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, за исключением удаления необязательного и вводящего в заблуждение аргумента.size () в конце последнего substr.
Бен Брайант
@ Бен-Брайант: Спасибо за головы. Не знал, что это необязательно.
Хусейн Яглы
16
Использование substrприводит к ненужному копированию. str.compare(start, count, substr)Метод , используемый в ответ Томаса является более эффективным. Ответ Razvanco13 имеет другой метод, который позволяет избежать копирования с помощью std::equal.
Феликс Домбек
4
@ HüseyinYağlı А Thomas uses atoi which is only for windows? atoiбыла стандартной библиотечной функцией C с тех пор ... В самом деле, atoiэто bad- не потому , что Windows , specific- а потому , что (1) C, а не C ++, и (2) не рекомендуется даже в C (вы должны использовать strtolили один из других, связанных с ними функций. Так как atoiесть нет обработки ошибок. Но, опять же, это только в C, в любом случае).
Парфянский выстрел
50

Никто еще не использовал алгоритм STL / функцию несовпадения . Если это возвращает true, префикс является префиксом '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;
  }
}
matiu
источник
2
Почему бы не использовать std :: equal?
Брайс М. Демпси
Звучит неплохо. Это был бы более короткий код тоже. Spose, мне придется редактировать ответ сейчас: p
matiu
2
std::equalНедостатком использования for strings является то, что он не определяет конец строки, поэтому вам нужно вручную проверить, не короче ли префикс, чем вся строка. (Как правильно сделано в примере с прогой, но опущено в однострочнике выше.)
Феликс Домбек
Так что, никакой пользы от 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
1
Нет, потому что rfind()(вместо startswith()) очень неэффективно - поиск продолжается до конца строки.
Анкостис
4
@ankostis rfind (x) выполняет поиск с конца до начала, пока не найдет x, действительно. Но rfind (x, 0) начинает поиск с начала (позиция = 0) до начала; поэтому он ищет только там, где он нуждается в поиске; не ищет с / до конца.
Анонимный трус
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управлением памятью состоит в том, что оно содержит только указатель на «строку» (непрерывную последовательность объектов, похожих на символы) и знает ее размер. Пример без перемещения / копирования исходных строк только для получения целочисленного значения:

#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действительно выдает исключение, цитируя подходящую ссылку. Пока вы этого не сделаете, я вам не верю, так как было бы очень запутанно иметь и то, ::atoiи другое, std::atoiдействовать совершенно иначе.
Роланд Иллиг
4
@RolandIllig Спасибо за настойчивость! Вы правы, это был упущение, которое std::atoiиспользовалось вместо std::stoi. Я исправил это.
Рой Дантон
12
text.substr(0, start.length()) == start
Macsinus
источник
3
@GregorDoroschenko отвечает на вопрос «проверьте, начинается ли строка с другой».
etarion
1
Эффективно и элегантно, используя std :: string. Я узнал больше всего из этого.
Майкл Б
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;
}
razvanco13
источник
2
Это должно быть if (prefix.size()<=arg.size() && std::equal(...)).
Джаред Грабб
10

Я думаю, что этот риск 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]есть "--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 усиление с примером использования:

#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;
}
vallismortis
источник
2

Вы также можете использовать strstr:

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

но я думаю, что это хорошо только для коротких строк, потому что он должен перебирать всю строку, когда строка на самом деле не начинается с 'substr'.

SZX
источник
2

Хорошо, почему сложное использование библиотек и прочего? Строковые объекты C ++ перегружают оператор [], так что вы можете просто сравнивать символы .. Как и я, потому что я хочу перечислить все файлы в каталоге и игнорировать невидимые файлы и ... и. pseudofiles.

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

Это так просто..

Nils
источник
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;
}
Mois
источник
3
Было бы здорово, если вы избегаете вставки кода без объяснения кода. Спасибо.
Reborn
1
Неэффективный код продолжит поиск после начала строки.
Анкостис
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
}

Удачи!

danger89
источник
Почему бы не найти? rfind (str, 0) не будет без необходимости сканировать всю строку, чтобы сделать выбор, поскольку он не может продвигаться. Видеть других.
user2864740
0

Поскольку C ++ 11 std::regex_searchтакже может использоваться для обеспечения еще более сложного соответствия выражений. Следующий пример обрабатывает также плавающие числа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;
}

Вид магии паттерна регулярных выражений хорошо описан в следующем ответе .

РЕДАКТИРОВАТЬ: предыдущий ответ не выполнил преобразование в целое число.

alextoind
источник
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.

Проверьте, начинается ли строка с другой строки, а затем получите подстроку ('slice') первой строки и преобразуйте ее, используя лексическое приведение.

blwy10
источник