C++ std::string сомнительное использование

Я новичок в С++. Чтобы решить онлайн-головоломку, мне нужно было извлечь числа из строк и сохранить только первое и последнее число. Например 4nfhfbk56khfkvh => 456 => 46. Поэтому я сделал функцию, приведенную ниже.

int num_extr(const std::string &thisline){
 
     std::string stored_numbers = thisline;
     int i,k,result;
 
     k = 0;
 
     for(i=0; i<thisline.size(); i++){
 
         if (thisline[i] >= '0' && thisline[i] <= '9'){
             std::cout << "Number: " << thisline[i] << '\n';
             stored_numbers[k] = thisline[i];
             ++k;
         }
     }
     std::cout << "Stored_numbers: " << stored_numbers << '\n';
     std::cout << "Stored_numbers[0]: " << stored_numbers[0] << '\n';
     std::cout << "Stored_numbers[k-1]: " << stored_numbers[k-1] << '\n';

     if (k>0){
         result = ((int)stored_numbers[0] -48) * 10 + ((int)stored_numbers[k-1] -48);
     }
     else{
         result = ((int)stored_numbers[0] -48) * 10 + ((int)stored_numbers[0] -48);
     }
     return result;
 }

Как вы можете видеть, я назначаю Stored_numbers = thisline, чтобы сделать строку Stored_numbers той же длины, что и эта строка (это быстрее, чем использование функции для определения длины этой строки?). Затем я сохраняю найденные на нем числа, начиная с [0]. Этот код работает. Это пример вывода:

Initial string: trknlxnv43zxlrqjtwonect
Number: 4
Number: 3
Stored_numbers: 43knlxnv43zxlrqjtwonect
Stored_numbers[0]: 4
Stored_numbers[k-1]: 3
Extracted number: 43

Но когда я попытался не назначать Stored_numbers = thisline, код был бы таким:

int num_extr(const std::string &thisline){
 
     std::string stored_numbers;
     int i,k,result;
 
     k = 0;
 
     for(i=0; i<thisline.size(); i++){
 
         if (thisline[i] >= '0' && thisline[i] <= '9'){
             std::cout << "Number: " << thisline[i] << '\n';
             stored_numbers[k] = thisline[i];
             ++k;
         }
     }
     std::cout << "Stored_numbers: " << stored_numbers << '\n';
     std::cout << "Stored_numbers[0]: " << stored_numbers[0] << '\n';
     std::cout << "Stored_numbers[k-1]: " << stored_numbers[k-1] << '\n';

     if (k>0){
         result = ((int)stored_numbers[0] -48) * 10 + ((int)stored_numbers[k-1] -48);
     }
     else{
         result = ((int)stored_numbers[0] -48) * 10 + ((int)stored_numbers[0] -48);
     }
     return result;
 }

Тогда результат:

Initial string: trknlxnv43zxlrqjtwonect
Number: 4
Number: 3
Stored_numbers:
Stored_numbers[0]: 4
Stored_numbers[k-1]: 3
Extracted number: 43

Поэтому, когда я использую Store_numbers как массив (символов?) без присвоения, кажется, что это больше не одна строка, а становится набором (указателей?), которые нельзя представить как одну строку в cout... В правильном ли направлении находится эта идея? Может ли кто-нибудь объяснить это поведение более подробно?

Я проголосую за закрытие, пока вы не превратите это в минимально воспроизводимый пример.

Ted Lyngmo 20.08.2024 01:53

Но когда я попытался не назначать store_numbers = thisline и вы попытались получить доступ к символам пустой строки, это неопределенное поведение, программа недействительна.

3CxEZiVlQ 20.08.2024 02:01

Я удалил строки из фрагментов кода, поместил вывод программы внутрь фрагментов кода и в следующий раз постараюсь не использовать слишком много тегов. Спасибо за быстрый отзыв.

Αλέξανδρος Παππάς 20.08.2024 02:01

@3CxEZiVlQ Программа выдала правильный ответ на головоломку в обоих направлениях. Моя проблема в том, что когда я удалил задание, я ожидал получить ошибку. Я не понимаю, почему у меня нет ошибок.

Αλέξανδρος Παππάς 20.08.2024 02:11

Неопределенное поведение не означает, что вы получаете ошибку. Неопределенное поведение остается неопределенным, в худшем случае оно может вести себя визуально правильно. Если вам нужно определенное поведение, используйте .at(k) вместо [k].

3CxEZiVlQ 20.08.2024 02:21

@ΑλέξανδροςΠαππάς -- Вы делаете слишком много работы. Чтобы стереть все символы, кроме цифр, используйте std::remove_if вместе с функцией erase для std::string. Чтобы получить первый и последний символы, используйте front() и back() для создания новой строки. Чтобы превратить новую строку в целое число, используйте std::stoi. В основном 3 (или 4) функции.

PaulMcKenzie 20.08.2024 02:37

Итак, вы должны найти первую цифру в строке и последнюю цифру в строке? Ищите вперед от начала строки, пока не найдете цифру; это первый. Ищите в обратном порядке с конца строки, пока не найдете цифру; это последний.

Pete Becker 20.08.2024 03:01

@PaulMcKenzie Спасибо за предложение, на данный момент я очень скептически отношусь к использованию функций, но я проверю те, которые вы использовали. Проверьте ответ Селби ниже, хотя он больше C, чем C++, он великолепен.

Αλέξανδρος Παππάς 20.08.2024 03:02

В настоящее время я очень скептически отношусь к использованию функций. Когда вы используете функции алгоритма, 1) код гарантированно будет работать, если вы используете правильные функции и задаете правильные параметры этим функциям, и 2) код самодокументируемость, поскольку вы видите, какие функции вызываются. Если вы хотите знать, что делает каждая строка, это хорошо документировано, поэтому любой программист C++, имеющий опыт, точно знает, что делает этот код. С другой стороны, если бы я посмотрел на ваш код, сразу не было бы понятно, что он делает, если только вы мне не сказали и/или я не запустил его, чтобы посмотреть, что он делает.

PaulMcKenzie 20.08.2024 04:27

@ΑλέξανδροςΠαππάς Также есть один недостаток, точнее, недостаток во всем задании. Что, если полученное число превысит максимальное значение int?

PaulMcKenzie 20.08.2024 04:29

@PaulMcKenzie Вы правы, я не думал о максимальном значении int. Я понял вашу точку зрения насчет самодокументируемого кода с помощью функций, спасибо!

Αλέξανδρος Παππάς 20.08.2024 07:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
11
109
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Более простое решение, в котором используются только первые и последние цифры, найденные в строке.

Примечание. Вам не нужно жестко закодировать «48» в качестве ascii (порядкового) значения «0». Вы также можете просто ссылаться на это значение с помощью литерала '0'.

int num_extr(const std::string &thisline){
    char first = '\0';   // null char, not literal '0'
    char last = '\0';    // null char, not literal '0'
    char* ptr = &first;

    for (char c : thisline) {
        if ((c >= '0') && (c <= '9')) {  // you can also use std::isdigit
           *ptr = c;
           ptr = &last;
        }
    }

    if (!first) {
       return 0;
    }

    if (!last) {
       return first - '0';
    }

    return 10*(first-'0') + (last-'0');
}

Теперь, чтобы ответить на ваш вопрос. Давайте посмотрим на этот фрагмент кода из вашего второго примера кода:

std::string stored_numbers;
int i,k,result;

k = 0;
 
for(i=0; i<thisline.size(); i++){
    if (thisline[i] >= '0' && thisline[i] <= '9'){
       std::cout << "Number: " << thisline[i] << '\n';
       stored_numbers[k] = thisline[i];
        ++k;
    }
}

Проблема с вышеизложенным заключается в том, что stored_numbers не имеет заранее выделенной длины. Итак, когда код достигает этой строки:

stored_numbers[k] = thisline[i];

На самом деле это не сильно отличается от чего-то вроде этого:

int arr[3];   // valid indices are 0-2
arr[0] = 42;  // valid
arr[1] = 84;  // valid
arr[2] = 126; // valid
arr[3] = 168; // UNDEFINED BEHAVIOR

Или ближе к вашему примеру:

int arr[0];   // as odd as that looks, it's a valid array, but no index value is valid. Then length of the array is 0.
arr[0] = 42; // UNDEFINED BEHAVIOR

В тот момент, когда вы записываете индекс >= длины массива, вы находитесь на территории неопределенного поведения - с обычным эффектом, заключающимся в том, что вы вмешались в память, принадлежащую другой переменной. То же самое относится и к строке, поскольку она фактически является контейнером для массива. Традиционная реализация строкового оператора [] заключается в простом возврате указателя на базовый массив.

Теперь о причине, по которой ваша программа все еще «работает», и выведите в конце 43. Скорее всего, но не окончательно, строка имеет как элемент длины, так и некоторые предварительно выделенные буферы, которые вы фактически не переполнили (пока). Поэтому, когда cout << stored_numbers выполняется, длина строки по-прежнему равна нулю, поскольку не было допустимых операций добавления. Таким образом, он не передает никакого вывода. Но когда есть ссылка на stored_numbers[0], строка возвращает все, что есть в ее буфере, без проверки элемента длины. Это всего лишь предположение. «Неопределенное поведение» также означает, что ваш код может превратить вас в лягушку. Но это решать авторам компилятора и среды выполнения.

Я пока не могу голосовать, но спасибо за ответ. Сменить указатель с первого на второй — действительно крутая идея. Также спасибо за другие предложения!

Αλέξανδρος Παππάς 20.08.2024 02:38

@Barmar Я не принял ответ Селби как решение, потому что, хотя он и очень полезен, он не отвечает на мой главный вопрос о неопределенном поведении.

Αλέξανδρος Παππάς 20.08.2024 08:06

@ΑλέξανδροςΠαππάς — даже если вы не нашли ответа на свой вопрос, вы все равно можете проголосовать за него, если вы нашли его полезным в других отношениях.

selbie 20.08.2024 09:36

@ΑλέξανδροςΠαππάς - и я все равно ответил на ваш неопределенный вопрос о поведении.

selbie 20.08.2024 10:35

Чтобы проголосовать, мне нужно как минимум 15 репутации. Проголосовать против вашего ответа было первым, что я попробовал, поэтому в своем первом комментарии я сказал: «Я пока не могу голосовать». Я вернусь к голосованию, когда получу 15 повторений, ваш ответ очень полезен. Я тоже принял это как решение. Еще раз спасибо!

Αλέξανδρος Παππάς 20.08.2024 23:37

Нет смысла предварительно выделять память, особенно путем копирования исходной строки, и особенно, когда весьма вероятно, что вам понадобится гораздо меньше места, чем исходная строка. Это могло быть проще, вот так:

int num_extr(const std::string &thisline){
 
     std::string stored_numbers;
 
     for(int i=0; i<thisline.size(); i++){
 
         if (thisline[i] >= '0' && thisline[i] <= '9'){
             std::cout << "Number: " << thisline[i] << '\n';
             stored_numbers += thisline[i];
         }
     }
     std::cout << "Stored_numbers: " << stored_numbers << '\n';
     std::cout << "Stored_numbers[0]: " << *stored_numbers.begin() << '\n';
     std::cout << "Stored_numbers[k-1]: " << *stored_numbers.rbegin() << '\n';

     ...

Другие вопросы по теме