Я новичок в С++. Чтобы решить онлайн-головоломку, мне нужно было извлечь числа из строк и сохранить только первое и последнее число. Например 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... В правильном ли направлении находится эта идея? Может ли кто-нибудь объяснить это поведение более подробно?
Но когда я попытался не назначать store_numbers = thisline и вы попытались получить доступ к символам пустой строки, это неопределенное поведение, программа недействительна.
Я удалил строки из фрагментов кода, поместил вывод программы внутрь фрагментов кода и в следующий раз постараюсь не использовать слишком много тегов. Спасибо за быстрый отзыв.
@3CxEZiVlQ Программа выдала правильный ответ на головоломку в обоих направлениях. Моя проблема в том, что когда я удалил задание, я ожидал получить ошибку. Я не понимаю, почему у меня нет ошибок.
Неопределенное поведение не означает, что вы получаете ошибку. Неопределенное поведение остается неопределенным, в худшем случае оно может вести себя визуально правильно. Если вам нужно определенное поведение, используйте .at(k)
вместо [k]
.
@ΑλέξανδροςΠαππάς -- Вы делаете слишком много работы. Чтобы стереть все символы, кроме цифр, используйте std::remove_if
вместе с функцией erase
для std::string
. Чтобы получить первый и последний символы, используйте front()
и back()
для создания новой строки. Чтобы превратить новую строку в целое число, используйте std::stoi
. В основном 3 (или 4) функции.
Итак, вы должны найти первую цифру в строке и последнюю цифру в строке? Ищите вперед от начала строки, пока не найдете цифру; это первый. Ищите в обратном порядке с конца строки, пока не найдете цифру; это последний.
@PaulMcKenzie Спасибо за предложение, на данный момент я очень скептически отношусь к использованию функций, но я проверю те, которые вы использовали. Проверьте ответ Селби ниже, хотя он больше C, чем C++, он великолепен.
В настоящее время я очень скептически отношусь к использованию функций. Когда вы используете функции алгоритма, 1) код гарантированно будет работать, если вы используете правильные функции и задаете правильные параметры этим функциям, и 2) код самодокументируемость, поскольку вы видите, какие функции вызываются. Если вы хотите знать, что делает каждая строка, это хорошо документировано, поэтому любой программист C++, имеющий опыт, точно знает, что делает этот код. С другой стороны, если бы я посмотрел на ваш код, сразу не было бы понятно, что он делает, если только вы мне не сказали и/или я не запустил его, чтобы посмотреть, что он делает.
@ΑλέξανδροςΠαππάς Также есть один недостаток, точнее, недостаток во всем задании. Что, если полученное число превысит максимальное значение int
?
@PaulMcKenzie Вы правы, я не думал о максимальном значении int. Я понял вашу точку зрения насчет самодокументируемого кода с помощью функций, спасибо!
Более простое решение, в котором используются только первые и последние цифры, найденные в строке.
Примечание. Вам не нужно жестко закодировать «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]
, строка возвращает все, что есть в ее буфере, без проверки элемента длины. Это всего лишь предположение. «Неопределенное поведение» также означает, что ваш код может превратить вас в лягушку. Но это решать авторам компилятора и среды выполнения.
Я пока не могу голосовать, но спасибо за ответ. Сменить указатель с первого на второй — действительно крутая идея. Также спасибо за другие предложения!
@Barmar Я не принял ответ Селби как решение, потому что, хотя он и очень полезен, он не отвечает на мой главный вопрос о неопределенном поведении.
@ΑλέξανδροςΠαππάς — даже если вы не нашли ответа на свой вопрос, вы все равно можете проголосовать за него, если вы нашли его полезным в других отношениях.
@ΑλέξανδροςΠαππάς - и я все равно ответил на ваш неопределенный вопрос о поведении.
Чтобы проголосовать, мне нужно как минимум 15 репутации. Проголосовать против вашего ответа было первым, что я попробовал, поэтому в своем первом комментарии я сказал: «Я пока не могу голосовать». Я вернусь к голосованию, когда получу 15 повторений, ваш ответ очень полезен. Я тоже принял это как решение. Еще раз спасибо!
Нет смысла предварительно выделять память, особенно путем копирования исходной строки, и особенно, когда весьма вероятно, что вам понадобится гораздо меньше места, чем исходная строка. Это могло быть проще, вот так:
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';
...
Я проголосую за закрытие, пока вы не превратите это в минимально воспроизводимый пример.