Як я можу перевірити, чи починається рядок C ++ std :: з певного рядка, і перетворити підрядку до 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 для неповнолітнього зміна).


Це теж цікаво.
manlio

Відповіді:


447

Використовуйте перевантаження, rfindяке має posпараметр:

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

Кому потрібно щось ще? Чистий STL!

Багато хто з них неправильно читають, що означає "пошук назад по цілому рядку в пошуках префікса". Це дало б неправильний результат (наприклад, string("tititito").rfind("titi")повертає 2, тож у порівнянні з == 0повернеться хибним), і це було б неефективно (переглядаючи всю нитку замість просто початку). Але це не робиться, тому що він передає 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)

32
ця відповідь повинна бути найбільш голосовою, а не прискореною: D навіщо використовувати іншу бібліотеку, коли у вас вже є STL.
Iuliu Atudosiei

@ sweisgerber.dev, я плутаюся у вашій першій суперечці. Повернене значення з findбуде лише нульовим, якщо titiзнаходиться на початку рядка. Якщо він знайдеться десь в іншому місці, ви отримаєте ненульове повернене значення, а якщо воно не знайдено, ви отримаєте nposі таке, що не є нульовим. Якщо припустити, що я правий, я віддаю перевагу цій відповіді, оскільки мені не потрібно вносити будь-які нестандартні речі (так, я знаю, що Boost є скрізь, я просто віддаю перевагу основним C ++ libs для простих речей, як це).
paxdiablo

@paxdiablo: ви праві, він дійсно перевіряє, чи починається він titi, але частина конверсії відсутня.
sweisgerber.dev

2
Чи є у нас докази того, що це оптимізовано у більшості компіляторів? Я не знаходжу в іншому місці згадування ні оптимізації "знайти", ні "відшукати" - це звичайна практика, заснована на поверненому значенні, на яке вона перевіряється.
Суперзій

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 () та порівняти результат та оригінал, щоб вирішити, продовжувати чи кидати виняток.
Roopesh Majeti

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
Дійсно, всі, здається, просто переходять на "use boost", і я за вдячність за stl або версію бібліотеки ОС
Force Gaia,

Так. Однак він припускає, що в рядку немає нульових символів. Якщо це не так - варто скористатисяmemcmp()
Avishai Y

чому б хтось використовував щось, крім цього простого красивого рішення?
Адам Захран

88

Якщо ви вже використовуєте 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
"Use Boost" - це завжди неправильна відповідь, коли хтось запитує, як зробити просту струнну операцію в C ++.
Гленн Мейнард

90
мінус 1 за пропозицію Boost
uglycoyote

37
Тут використовується правильний пристрій, якщо ви вже використовуєте імпульс у своєму проекті.
Алекс Че

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 () в кінці підсумкового субстр.
Бен Брайант

@ ben-bryant: Дякую за голову вгору. Не знав, що це необов’язково.
Hüseyin Yağlı

16
Використання substrпризводить до зайвого копіювання. str.compare(start, count, substr)Метод , який використовується в відповідь Томаса є більш ефективним. У відповіді razvanco13 є інший метод, який уникає копіювання за допомогою std::equal.
Фелікс Домбек

4
@ HüseyinYağlı Так Thomas uses atoi which is only for windows? atoiбула стандартною функцією бібліотеки С з… коли-небудь. Насправді, atoiце погано - не тому, що це специфічно для Windows, а тому, що (1) C, а не C ++ і (2) застаріло навіть у C (ви повинні використовувати strtolабо одну з інших, пов'язаних з ними функцій. Тому що atoiмає немає помилок в обробці. Але, знову ж таки, це лише у С).
Парфянський розстріл

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 :: рівне краще підходить для запитання: чи є префіксом 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 :: рівний?
Бріс М. Демпсі

Звучить добре для мене. Це був би і коротший код. Я псую, мені доведеться зараз відредагувати відповідь: p
matiu

2
Використання std::equalдля рядків має менший бік: він не виявляє кінець рядка, тому вам потрібно вручну перевірити, чи префікс коротший, ніж цілий рядок. (Як правильно зроблено в прикладі прог., Але опущено в одній лайнері вище.)
Фелікс Домбек

Отже, ніякої вигоди від rfind?
Андрій Вахрушев

26

З огляду на те, що обидва рядки - argv[1]і "--foo"- є струнами C, відповідь ФеліксДомбека - це найкраще рішення від руки.

Однак, побачивши інші відповіді, я подумав, що варто зауважити, що якщо ваш текст уже доступний у форматі 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()) дуже неефективна - вона продовжує шукати до кінця рядка.
ankostis

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>? У документації сказано, що "вона ніколи не викидає винятків".
Roland Illig

@RolandIllig Я маю на увазі ваш перший коментар. Здається, ви помилково говорите atoiзамість цього std::atoi. Перший небезпечний у використанні, тоді як останній - прекрасний. Я використовую останній у коді тут.
Рой Дантон

Докажіть, будь ласка, що std::atoiнасправді є винятком, посилаючись на відповідну довідку. Поки ти не зробиш, я не вірю тобі, оскільки було б дуже заплутано мати обоє ::atoiі std::atoiдіяти зовсім по-іншому.
Роланд

4
@RolandIllig Дякуємо за наполегливість! Ви маєте рацію, це був недогляд, який std::atoiвикористовувався замість std::stoi. Я це виправив.
Рой Дантон

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

3
@GregorDoroschenko він відповідає на "перевірити, чи починається рядок з іншої" частини.
etarion

1
Ефективно та елегантно за допомогою std :: string. Я з цього найбільше навчився.
Майкл Б

1
додаткові бали за те, що це if (one-liner)
однолінійний пристрій,

@Roland Illig Чому ти вважаєш, що поведінка в цьому випадку не визначена? Вираз повернеться хибним, оскільки 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]це так "--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 і ви не можете використовувати прискорення, ось розширення, сумісне з підсиленням, з прикладом використання:

#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+ більше не доступний
taStatic_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()

Приклад використання знахідки для пошуку одного знаку:

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

Приклад використання знахідки для пошуку повного рядка та починаючи з позиції 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 (str, 0) не зайвим буде сканувати цілу рядок, щоб зробити вибір, оскільки він не може просунутися. Дивіться інші.
user2864740

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;
}

Вид магії малюнка регулярних виразів детально деталізований у наступній відповіді .

EDIT: попередня відповідь не здійснила перетворення на ціле число.


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.

Перевірте, чи починається рядок з другого рядка, а потім дістаньте підрядку ('фрагмент') першого рядка та перетворіть її за допомогою лексичного складу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.