C++ — Подскажите, почему не работает удаление пробелов c++


Содержание

Удалить пробелы в строке C++ не работает

Я уже прочитал эти два вопроса:

По какой-то причине я никогда не смогу правильно решить проблемы. В моей программе я собираю данные от пользователя и передаю его в std::string . Оттуда я хочу удалить все пробелы в нем. Например, если пользователь вводит «3 + 2», я бы хотел, чтобы он изменился на «3 + 2».

Что происходит, так это то, что до первой строки сохраняется. Вот моя программа:

Поэтому, когда я запускаю эту программу и набираю «3 + 2», выход «3».

Вот моя консоль:

Я не могу понять, как это решить. Я даже попытался использовать решение, которое использовало регулярное выражение для удаления всех символов \s , и это дало мне ту же проблему.

Чтобы прочитать полную строку (до завершения \n), вам нужно использовать, например, std::getline(std::cin, UserInput); , В противном случае вы в настоящее время читаете текст до первого символа пробела.

C++ — Подскажите, почему не работает удаление пробелов c++

БлогNot. Типичные ошибки начинающего разработчика на C++: проблемы и их решения

Типичные ошибки начинающего разработчика на C++: проблемы и их решения

сейчас в списке: 61 ошибка Проводя гораздо больше времени, чем мне хотелось бы, за разного рода учебными мероприятиями по программированию, я поневоле начал собирать коллекцию типовых ошибок, допускаемых начинающими разработчиками. Попробую приспособить для неё эту запись, надеюсь, список постепенно будет пополняться. до 50-60 самых типовых проблем, ну, уж никак не больше 100 :)

Конечно, я не ставлю цели рассказывать обо всех возможных синтаксических ошибках — для этого есть компилятор. Я обращу внимание скорее на типовые алгоритмические и логические неточности, допускаемые начинающими. Весь приведённый код проверен, в основном, для этого служили бесплатные сборки Microsoft Visual C++ версии 2010 или выше.

Страница длинная, поэтому воспользуйтесь оглавлением или нажмите комбинацию клавиш Ctrl+F для поиска нужного текста на странице.

Оглавление
Попытка модифицировать константный указатель

Ошибка особенно коварна тем, что всплывёт на этапе исполнения программы:

а при компиляции всё может выглядеть нормально.

Первый способ исправления — выделять память под s динамически:

Второй способ — заменить исходный указатель массивом.

Некоторые компиляторы, например, от Borland, выполняют и исходный код. Это их проблемы, а в стандарте языка такого нет.

К тому же виду ошибки можно отнести и такое:

Так нельзя, в s нет столько места, и после ввода строки возникнет ошибка. Правильно было бы сделать константный указатель на изменяемую строку, указав её размер сразу:

Теперь ввод в s будет работать, если не указывать вторым параметром метода getline размерность, большую количества выделенной памяти (255 байт в нашем случае).

Ещё правильнее выделить новую память, а затем её освободить:

Двойная перестановка элементов массива

Ошибка встречается при транспонировании матрицы, инверсии (переворчивании) символов строки и т.п. Суть дела в том, что вы применяете полные циклы там, где нужны только их «половинки» :)

Пример: перевернём символы строки правильно.

Типичная ошибка: при верхней границе цикла, равной L , строка останется неизменённой, так как каждая пара символов будет переставлена дважды.

Пример: транспонирование матрицы.

Типичная ошибка: если сделать в main полный двойной цикл

то ничего транспонировано не будет.

Пример: сравнить попарно все элементы вектора (программа только напечатает номера пар сравниваемых элементов)

Типовая ошибка: если сделать полный двойной цикл

то сравнения будут выполняться дважды, более того, каждый элемент будет сравниваться «сам с собой».

Пример. Меняем местами максимальные и минимальные элементы массива. Неверно:

После оператора, помеченного «1», бывший максимум уже заменён минимальным значением. Идущий следом второй оператор меняет всё обратно. Правильно, например, так:

Окно программы закрывается, я не успеваю увидеть результаты работы программы?

Если Вы не программировали остановки в программе, её и не будет!

Вставляйте оператор, ожидающий ввода с клавиатуры перед оператором return главной функции.

Варианты такого кода:

Неправильный кастинг (приведение типов)

Как известно, C++ допускает преобразования типов данных с потерей точности, в этом случае компилятор может просто «промолчать» или сгенерировать предупреждение:

Для таких случаев вполне подойдёт static_cast :

Все скобки — и треугольные, и круглые, здесь обязательны!

Если же компилятор выдаёт ошибку преобразования типа, например, при попытке присваивания структур разных типов

то можно рискнуть применить

— но лишь потому, что мы понимаем, что структуры type1 и type2 на самом деле полностью совместимы по типам данных, хотя и имеют разные теги имени структурного типа и разные наименования полей:

Обратите внимание, что присваивание через указатель var2 , как и положено, изменит значение поля исходной переменной var1 .

= вместо == (присваивание вместо сравнения)

Головная боль начинающих, особенно после Паскаля :)

Компилятор не найдёт в этом никаких проблем, максимум, сгенерирует предупреждение. Меж тем, переменной i будет присваиваться «двойка», а так как результат этой операции равен true , то всегда будет выполняться ветвь 1.

Решение очевидно — пишите правильно операцию сравнения, а именно, ==

& и | вместо && и || (побитовые операции вместо логических)

Первый оператор выполнит логическое умножение над отдельными битами значений n1 и n2 , так что для младшего байта получится


Второй оператор каждую переменную приведёт к типу bool , так что true && true = true (напомню, что true считаются все ненулевые значения).

Второй оператор мог бы быть более наглядно выполнен условием вида n1!=0 && n2!=0

Неверное использование char как int

Вот этот код ошибочно интерпретирует значение c как число (а не код символа), пытаясь прибавить к нему значение 10. В результате получится отнюдь не 11, а 59, то есть символ » ; «

Происходит это потому, что код символа «ноль» равен 48, а «единицы» 49. Правильным решением было бы сначала отнять от символа-цифры код нуля, равный 48. Неявно используется предположение, что цифровые символы закодированы идущими подряд числовыми значениями (это так во всех распространённых кодировках).

Неверное выделение/освобождение динамической памяти

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

1. Вся динамически выделяемая память должна освобождаться, в идеале «по принципу стека», то есть, занявший память последним объект освобождает её первым:

2. Если память выделялась через new , она освобождается через delete , если через new[] — через delete[] , то есть, последним оператором нужно освобождать память от массивов:

3. Не смешивайте в одной программе разные способы выделения и освобождения памяти. Если вы выделяете память с помощью функции malloc или calloc , то освобождайте её с помощью функции free , при выделении через операторы new/new[] , освобождайте с помощью delete/delete[] . В целом использование в проектах C++ «сишных» функций malloc , calloc и free не одобряется стандартом.

4. Бойтесь повторных delete , применённых к уже удалённым объектам — это одна из самых трудноуловимых ошибок. Для многих компиляторов действует неформальный «хак» — явно занулять указатель после delete , присваивая ему 0 или NULL , что зависит от компилятора:

Теперь повторные delete не опасны.

5. По возможности избегайте выделения памяти на одном уровне (например, в теле функции), а освобождения — на другом (например, в функции main ). Даже если речь о «куче» и операторах new / delete . Например:

При вызове функции f в неё передаётся копия указателя Ptr . При выделении памяти адрес помещается в эту копию, то есть, значение переменной Ptr не изменится. После возврата из функции адрес потеряется, соответственно, потеряется выделенная память и возникнет ошибка времени выполнения. Как вариант, можно делать так:

(передавать в функцию адрес указателя) или так:

(принимать в функции ссылку на указатель), но всё равно выглядит вычурно :)

Возврат из функции ссылки или указателя на локальную переменную

Да, если вам повезёт, эти 2 функции вернут результаты 1 и 2 соответственно:

На самом деле, так поступать нельзя. Локальные переменные, созданные в стеке, уничтожаются при выходе из их области видимости (в нашем случае — при выходе из функции). Таким образом, память получает статус свободной и туда могут быть записаны какие-либо новые данные. Если это не успело произойти, Вы свои 1 и 2 получите. но в сложном коде почти гарантированно однажды произойдёт сбой. Чтобы не попасть впросак, используйте естественное

Функция good2 имеет смысл в предположении, что оператор new выделяет память в куче (heap), а под локальные объекты память выделяется в стеке (stack). Также потенциально опасно, если память выделяется на одном уровне вложенности кода, а освобождается на другом.

Та же проблема имеется и с возвратом локальных строк:

Самый естественный способ исправить — в функцию передавать буфер, куда копируем строку. По возможности, при всех операциях с буфером нужно контролировать возможный выход за его границу, особенно когда работаем с заранее неизвестными размерами (типовое и не лучшее решение — использование strncpy вместо strcpy внутри функции f ).

Использование неинициализированных переменных

. то есть, вы делаете что-то с переменной, которая просто описана, но не получала никакого значения:

Результат может быть любым — от генерации ошибки времени исполнения до неверного расчёта, сделанного программой. Особенно часто забывают давать начальное значение количеству, сумме, максимуму или минимуму, демонстрируя тем самым, что типовые алгоритмы не учили :)

Пока мы не начали суммировать элементы массива, переменная-сумма sum отнюдь не обязана быть равной нулю!

Нулями автоматически инициализируются только:

  • статические переменные, описанные с указанием ключевого слова static ;
  • в некоторых компиляторах — глобальные переменные, описанные вне всех функций (полагаться не стоит);
  • динамические области памяти, выделяемые устаревшей сишной функцией calloc .

Два добрых совета, экономящих кучу времени:

1. Привыкнуть инициализировать объявленные переменные всегда. А указатели тоже инициализировать значением 0 или NULL (да ещё и сбрасывать обратно в эти значения после освобождения динамической памяти, связанной с указателем).

2. Объявлять переменные как можно ближе к месту их использования, благо, C++ это позволяет.

Пустой цикл из-за точки с запятой.

Часто ставят лишнюю точку с запятой, например, после открывающей части цикла:

Результат выполнения этого кода будет поистине страшен! Во-первых, здесь не написано «суммировать n элементов массива a «. Здесь написано » n раз выполнить пустой цикл, а затем прибавить к переменной sum значение i -го элемента массива a «. Во-вторых, после выхода из цикла по i , значение этой переменной может оказаться равным 3 , что приведёт ещё и к выходу за границы массива при выполнении a[i] .

Делают так и для функций. хотя вот это, к счастью, выловит любой современный компилятор:

Чтобы не совершать этой глупой ошибки, помните — точка с запятой завершает действие (цикл, объявление, оператор), а не начинает его.

Функция main не имеет типа int

Вроде бы и не ошибка, но. распространённейшее

— неверно. Согласно стандарту, функция main() должна возвращать целочисленное значение, правильно вот так:

Определение структурного типа или класса не заканчивается точкой с запятой

Вот эта маленькая программа сгенерирует, как минимум, 3 ошибки:

То же самое и при объявлении классов — не забывайте про символ ; в конце.

В нашем примере можно было и сразу же описать структурный тип и определить структуру:

Сравнение вещественных значений как целых

Проблема возникает при сравнении вещественных значений a , b в виде

Дело в том, что арифметические вычисления для чисел с плавающей запятой выполняются с некоторой погрешностью, обусловленной тем, что хранить все знаки дробной части числа в памяти бывает невозможно, например, 2/3=0.66666666666. — ряд шестёрок неизбежно будет где-то обрезан.

Правильный путь — сравнивать вещественные числа с некоторой точностью, которая зависит от поставленной задачи, например

Аналогичная проблема возникает в циклах с вещественной управляющей переменной, скажем, в этом цикле

переменная x рискует «потерять» своё последнее значение, равное 1 , всё из-за тех же погрешностей. Правильный путь — прибавлять к правой границе диапазона изменения x некое малое значение, заведомо меньшее шага: for (x=0; x char ) с помощью операций отношения , == , >= и т.д. — глубоко неправильное для C++ действие. Так мы будем сравнивать не содержимое, а указатели (адреса памяти, где начинаются строки). Правильный путь — стандартная функция сравнения строк strcmp (подключить заголовки string.h ).

Неправильная запись чисел

Да-да, такое тоже бывает. Например, посмотрите, что напечатает этот код:

Просто лидирующий ноль означает число в восьмеричной системе счисления. Добавлять ноль перед числом не нужно никогда. Перед строкой — пожалуйста:

Вторая «детская» проблема, способная, тем не менее, создать жуткий головняк — применение запятой вместо точки для отделения дробной части вещественного числа.

Правило — всегда использовать точку, а не запятую при записи чисел! C++ — не Microsoft Office, а средство его написания :)

В длинных математических выражениях ошибка очень трудноуловима. Максимум, что скажет компилятор — предупреждение о неиспользуемой переменной при включённом флаге -Wunused-value . Поэтому программирование математики должно быть особенно тщательным.


Принадлежит ли значение интервалу?

Проверка, которая выполняется очень часто. Допустим, есть переменная x , нужно проверить, попадает ли её значение в интервал [a,b] . Единственный правильный путь сделать это — записать

Самые распространённые неправильные способы записи собраны, некоторые из них даже компилируются, но это ничего не значит :)

Так как операция сравнения левоассоциативна (выполняется слева направо), это эквивалентно записи

дающей истинный результат при любом a .

Тоже истина для любого x .

Результатом операции «запятая» является последнее выражение, то есть, здесь проверяется только условие x

Неверный составной условный оператор

Проблема возникает, когда вы хотите запрограммировать более 2 вариантов расчёта. Обычно всё дело — в непонимании того, как работает условный оператор с несколькими ветвями расчёта.

Вот простейший пример, здесь мы определяем знак z целого числа a , по правилу:

Вот типичная ошибка такого расчёта:

Применение одного короткого и одного полного условных операторов является здесь грубой ошибкой — ведь после завершения короткого условного оператора для всех ненулевых значений a будет выполнено присваивание z=1 . Правильных вариантов этого расчета, по меньше мере, два:

— с помощью 3 коротких условных операторов, вариант не очень хорош тем, что проверяет лишние условия даже тогда, когда знак уже найден.

— с помощью составного условного оператора, этот вариант лучше.

Вторая типичная проблема — неправильный порядок ветвей в составном операторе, из-за которого некоторые условия срабатывают «досрочно» или не срабатывают вовсе. Например, в показанном ниже фрагменте, где нужно было вывести, является ли значение x чётным или нечётным и отдельно учесть значение x=12 , напечатав для него слово «Дюжина», эта самая «дюжина» не будет напечатана никогда:

Проблема в том, что значение 12 — тоже чётное, и сработавшая ветвь «Чётное» не даст сработать ветви «Дюжина». Да и проверка условий организована явно избыточно, правильно было так:

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

При использовании оператора последовательно проверяются условия 1, 2, . ,N , если некоторое условие истинно, то выполняется соответствующий оператор и управление передается на оператор, следующий за условным. Если все условия ложны, выполняется ветвь0 , если она задана, или не выполняется ни одной ветви, если ветвь0 отсутствует. Число ветвей N не ограничено. Существенно то, что если выполняется более одного условия из N , обработано всё равно будет только первое истинное условие.

Функция не возвращает значения всегда или в некоторых случаях

Если функция не имеет тип void , она должна возвращать значение указанного в заголовке типа при любых комбинациях своих входных параметров. Например, следующая функция

ошибочна: для нулевого или отрицательного значения x возвращаемое значение отсутствует.

Простейший способ избежать проблемы — всегда возвращать из функции некое «значение по умолчанию»:

Некоторые компиляторы, например, Visual Studio, могут предупредить об этой проблеме:

Но для этого надо установить соответствующий уровень предупреждений компилятора. В Visual Studio это делается через команду меню Проект, Свойства, Свойства конфигурации, С/С++, Общие, Уровень предупреждений, Уровень 4. По умолчанию принят уровень 3.

Не срабатывает останов в программе или «не работает» оператор ввода

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

Цукерберг рекомендует:  Html - Оцените верстку и... закидайте помидорами!

А вот в таком виде всё сработает верно:

В ряде компиляторов могут быть проблемы и с таким кодом:

На самом деле, всё очень просто. «Проскакивание» ожидания ввода (паузы) происходит из-за того, что в потоке после предшествующего ввода остаются символы. Чаще всего остаётся символ новой строки \n , который попадает в поток при нажатии Enter. Оператор cin >> его просто проигнорирует, а такие функции, как getchar() , cin.get() и т.п. считывают его как первый символ в потоке и код идёт дальше, а ожидания ввода символа не происходит. Резюме — перед применением этих функций поток нужно очищать — fflush (stdin) на С или cin.sync() на C++

Вот в этом коде, если не повезёт, могут быть проблемы:

А в этом — вряд ли:

Удобней всего просто синхронизировать поток перед операциями ввода:

Экзотика. Вместо cin.get() можно просто пропустить символы:

Можно также в цикле прочитать оставшиеся символы

Выход за границы массива

Индексы массивов в C++ начинаются с нуля. Для массива из n элементов допустимые индексы лежат в диапазоне [0..n-1] . Чтение или запись информации вне выделенной памяти приводит к неопределенному поведению программы.

Приведённая ниже ошибка, скорей всего, сравнительно безобидна, а вот запись элемента в несуществующий элемент массива способна привести к краху программы.

Со строками данная ошибка обусловлена, чаще всего, отсутствием в них нуль-терминатора (байта с кодом 0, пишется ‘\0’, не путать с цифрой 0 (‘0’), имеющей код 48!)

Переключатель switch без операторов break

Если проверяемая величина принимает заранее известный фиксированный набор значений, оператор-переключатель удобнее, чем составной условный. Но если забыть про то, что каждую ветвь case нужно завершать оператором break , позволяющим выйти из переключателя, результаты могут быть плачевны:

Здесь для i , равного 0 , будет выведено zero one other . При i , равном 1 , будет выведено one other . То есть, без break происходит «проваливание» к следующей метке.

Бывает, что отсутствие break — не баг, а фича. Например, вот эта функция умеет возвращать 1-ю, 2-ю или 3-ю степень «двойки», а в остальных случаях результат будет равен единице.

Просто укажите комментарием, если break вам не нужны и ветви переключателя должны «проваливаться».

Градусы вместо радианов у тригонометрических функций

Очень часто к неожиданным результатам расчётов приводят вот такие ляпы в коде: Получится значение x = 0.893997, а отнюдь не 1. Просто человек имел в виду 90 градусов, а компьютер принимает аргументы тригонометрических функций в радианах. Если входная величина задана в градусах, переведите её в радианы:

Ещё лучше написать маленькую функцию, делающую такой перевод.

Неверный вызов конструктора базового класса из конструктора производного

В показанном ниже коде производный класс B пытается воспользоваться конструктором своего родителя A .

Правильный способ — использовать списки инициализации в конструкторе потомка.

Сравнение знаковых и беззнаковых значений

Вот в этом коде -1 «неожиданно» окажется больше 1:

Знаковое x приводится к беззнаковому, отчего возрастает до значения UINT_MAX — 1 (само UINT_MAX равно max(unsigned int)-1 , в разных компиляторах может обозначаться разными именами).

Решение — не сравнивать знаковые и беззнаковые значения, заранее приводя их к одному типу данных.

Вызов виртуального метода из конструктора класса

Класс A хочет для инициализации свойства x воспользоваться виртуальным методом f :


У класса-потомка B виртуальный метод реализован, но всё равно ничего не выходит. Проблема в том, что при создании объекта B , сначала создается базовая часть (класс A ), а в конструкторе базового класса ничего о классе B ещё не известно, поскольку он еще не создан, в том числе, не заполнена таблица виртуальных функций.

Решение: не вызывать виртуальные методы из конструкторов.

Неправильное выделение тела цикла или ветви условного оператора

Если в теле цикла находится более одного оператора, его следует заключать в фигурные операторные скобки.

Если же повторяемый оператор всего один, допустима запись тела как с операторными скобками, так и без них:

К сожалению, начинающие разработчики часто путаются и не ставят операторных скобок даже если в цикле больше одного оператора. Получается вот что:

Оператор вывода cout не имеет отношения к циклу, в цикле находится только оператор a[i]= , а выравнивание отступами для компилятора С++ ничего не значит.

Делать его всё равно нужно, это облегчает чтение программы человеком

Для исправления ошибки достаточно заключить тело цикла в операторные скобки:

Аналогичная проблема характерна и для ветвей условных операторов. Вот в этом коде

несмотря на визуальное выравнивание, ветка else относится не к первому if , а ко второму, скобки же способны решить проблему и показать разветвление кода наглядно:

Без указания дополнительных операторных скобок else всегда относится к ближайшему сверху if, для которого ветка else ещё не указана

Самый простой способ не запутаться — всегда ставить операторные скобки после ключевых слов for , while , do , if , else if или else и закрывать открытые скобки сразу же, так, чтобы закрывающая скобка была непосредственно под открывающей

или же под первой буквой ключевого слова, к которому относится открывающая скобка:

В любом случае, код, помещённый внутри цикла или ветви алгоритма сдвигается вправо пробелами и выравнивается, так что одному уровню вложенности всегда соответствует одинаковое число пробелов. Я лично для скорости печати обхожусь одним, но часто испольуют несколько пробелов или табуляцию, тем более, что в современных редакторах кода этот параметр легко настраивается. В частности, в Visual C++ пройдите в меню Сервис, Параметры, Текстовый редактор, C/C++, Табуляция и настройте так, как вам удобно.

Функция не может узнать размер параметра-массива

Функция f неудачно пытается воспользоваться конструкцией sizeof для определения размера массива-параметра:

Решение 1, самое простое и верное, подходит и для статических, и для динамических массивов. Передать размерность массива как параметр функции.

Решение 2, только для статического массива. Размер статического массива можно узнать, если принять в функции ссылку на массив и использовать template для типа данных. Увы, это сработает не во всех компиляторах и обратная совместимость кода с Си нарушается.

Решение 3, тоже подойдёт для статического массива, но не сработает с динамическим, память под который выделялась оператором new . Размер массива можно задать при использовании шаблона, вот образец:

Но это уже совсем жутик по сравнению с универсальным и обладающим обратной совместимостью способом 1 :)

Неверный порядок свойств класса при использовании списка инициализации

Есть класс со свойствами x и y , проинициализируем их с использованием списка в конструкторе:

Проблема состоит в том, что в списке инициализации конструктора объекты инициализируются в том порядке, в котором они объявлены в классе или структуре, а не в том, который указан в списке инициализации. В нашем случае сначала будет проинициализирована переменная x , а только потом y , что приведёт к неопределённости значения x .

Выход — следить за порядком объявления переменных в классе.

Функция работает с локальной копией объекта вместо объекта

Представим, что мы написали пару функций для удобной инициализации динамического массива и его удаления из памяти:

Показанный код в корне неверен тем, что при вызове функции init_array в нее передается копия указателя a , описанного в главной функции. Этот указатель ещё не проинициализирован на момент вызова функции. При выделении памяти, адрес массива будет помещён в локальную копию a внутри функции, значение a в функции main от этого не изменится. Соответственно, после выхода из функции выделенная память будет потеряна.

Первый способ решения — передать в функцию адрес указателя, используя конструкцию ** :

Второй способ — принимать в функции ссылку на указатель, конструкцию *& :

Это удобнее, не так ли? :)

Наконец, вот такая версия программы тоже способна отработать успешно —

Несоблюдение правила Большой Тройки при разработке класса или структуры

Правило Большой Тройки состоит вот в чём: если класс или структура определяет любой из следующих трёх методов:

  1. Деструктор
  2. Конструктор копирования
  3. Оператор присваивания

то он должен определить и два остальных метода! По меньшей мере, если в классе идёт работа с памятью, её динамическое выделение!

В любом случае, эти три метода — особые члены класса и будут созданы компилятором автоматически. Но наверняка автоматически созданные версии не удовлетворят потребности класса, например, по копированию объектов или освобождению ранее выделенной памяти. Приведём классический пример класса-массива, в котором хранится его размерность, а память выделяется динамически.

В рассмотренном случае конструктор по умолчанию тоже не создается автоматически.

Чтение данных «в никуда»

В основном, этим грешат начинающие при использовании scanf , забывая, что ей нужно передавать адрес переменной:

Выведется бред или возникнет Runtime Error. Правильно так:

Использование указателя как массива

Гораздо чаще, чем хотелось бы, приходится видеть в коде такое:

Конечно, так нельзя — предварительно выделите память под a. Указатель — только переменная, предназначенная для хранения адреса памяти, места под элементы массива там нет! Правильно, например, так:

Ещё надёжнее проверять, удалось ли выделить память, особенно если размерности большие:

Перепутаны символ char и строка char *

Отдельный символ (величина типа char ) не есть строка символов, заданная указателем на тип char ( char * )

Скорее уж char похож на целое число int , представляя собой код символа.

При работе с однобайтовыми кодировками char занимает 1 байт памяти.

Строка char * занимает столько байт, сколько в ней символов, плюс ещё один, необходимый для хранения нуль-терминатора — байта с кодом ноль, записываемого как ‘\0’ . Не путать с цифрой ‘0’ , имеющей код 48 !

Вот практический пример. Во избежание синтаксической ошибки «находчивый» новичок явно преобразовал тип char к char * при вызове стандартного метода strcat . Всё, что он получил — Runtime-ошибку «Нарушение прав доступа».

Правильным путём было либо «присобачить» символ к строке «вручную», не забыв про нуль-терминатор:

либо создать массив из 2 символов и тогда уже использовать strcat :


Разумеется, всё это имеет смысл при условии, что в str есть «свободное место», зарезервированное статически или выделенное динамически.

Забыли, что в строке всегда должен быть нуль-терминатор

. и предусмотрен дополнительный символ (байт) для него. Вот этот код в Visual Studio вполне способен привести к краху программы.

Деструктор базового класса должен быть виртуальным!

Вот здесь я приводил эту великую мысль как пример типичной сиплюсплюснутой заумности :) Но что поделать, если это действительно так. Посмотрим простейшую иерархию из родителя A и потомка B , конструкторы и деструкторы которых печатают в консоль факт своего вызова:

Эта программа напечатает: A()B()

То есть, деструктор класса B не вызывался, соответственно, он мог не освободить память, если таковая выделялась конструктором!

Изменив деструктор класса A на

получим вывод A()B()

Таким образом, виртуальный деструктор нужен для правильного полиморфного удаления объектов.

Во избежание проблем всегда следует как минимум:

1. При наличии хотя бы одного виртуального метода объявлять виртуальным и деструктор.

2. Также его надо явно объявлять виртуальным, если класс предполагается в будущем сделать базовым.

Вызов функции, изменяющей некие величины, стоит в одном операторе с изменёнными величинами

Приведём пример простой программы, справа от операторов вывода написано, что печатается.

Как видим, обе функции успешно меняют то, что должны изменить: init — свои параметры, переданные по ссылке, а init2 — глобальную переменную global .

Но попытка вывести изменённые функциями значения в одном операторе с вызовом функций оба раза оказалась безуспешна.

Проблема в том, что до точки с запятой (конца текущего оператора) значения остаются в рамках одной инструкции и не меняются.

Также никогда нельзя рассчитывать на то, что вычисление значений аргументов в списке параметров функции выполняется слева направо.

Ссылка или указатель на «переехавший» объект

На словах эту распространённую проблему можно описать так:

1. В динамический объект с лимитом памяти, например, в вектор, добавляются объекты (элементы) по значению.

2. В какой-то момент запоминается ссылка или указатель на какое-либо из значений вектора.

3. В вектор добавляют ещё некоторое количество объектов.

4. В какой то момент резерв памяти иссякает, и вектор делает realloc , то есть, расширяет буфер, перенося туда свои объекты. В результате объекты меняют адрес.

5. Ранее выданные «наружу» ссылки или указатели становятся недействительными, но программа никак не отследит это.

6. Обращение по неверному указателю или ссылке приводит к ошибке времени исполнения и краху программы. Ну или просто к потере указателя, если читаем данные.

Ошибка очень трудноуловима, потому что «авария» может произойти очень далеко от места причины. Для вектора часто резервируют память ( vector::reserve ) и обычно её хватает. а однажды, при каких-то сочетаниях исходных данных, не хватит.

Совсем простой пример без векторов и классов:

Вывод — не полагаться на ссылку (указатель) на объект, являющийся элементом некой динамической структуры данных.

Неверный вызов конструктора по умолчанию

В показанном ниже коде программист хотел создать объект a класса A и написал круглые скобки для указания того, что нужен конструктор без параметров:

Но эта конструкция была проинтерпретирована как объявление прототипа функции без параметров с именем a , которая возвращает значение типа A .

Правильным было бы объявление

Неявный вызов конструктора по умолчанию базового класса вместо его конструктора копирования

При копировании объектов вместо конструктора копии, у базового класса запускается конструктор по умолчанию. Вот код примера:

Проблема состоит в том, что при наследовании конструктор копирования не будет порождать вызов конструктора копирования базового класса. Программист должен явно указать, какой базовый конструктор нужно запустить. А компилятор запускает только «конструкторы по умолчанию». Правильно было бы заменить строчку

Изменение управляющей переменной цикла в теле цикла

Глупая ошибка, но встречается очень часто, вот она в немного утрированном виде:

Всего лишь помним — внутри цикла for его управляющая переменная не должна меняться иначе, чем в заголовке цикла. Для циклов do .. while и while . do счётчик меняйте всегда последним оператором тела цикла, тогда не возникнет ошибок.

Повторное вычисление границ цикла

Проблема, часто замедляющая быстродействие программы. Цикл for создавался для выполнения заранее известного числа шагов. Не надо заставлять его вычислять это число шагов заново на каждом шаге:

У меня это выполнилось в среднем за 0.875 ms, на каждом шаге заново вычислялись верхняя граница цикла и шаг. Исправим отмеченную комментариями // часть на

и стабильно получим заметно меньшее время. В книгах и инете распространён миф, что компилятор самостоятельно умеет оптимизировать расчёты в условии цикла.

Это не совсем так. В общем случае, компилятор может оптимизировать только константы в условии цикла. Но у меня здесь не произошло и этого. Внешнюю переменную в границе цикла компилятор тем более не будет оптимизировать, как раз потому, что в теле цикла эта переменная может быть изменена.

При удачном стечении обстоятельств компилятор может оптимизировать и внешнюю переменную, если в теле цикла доступна вся информация о ней и есть гарантия, что оптимизация не изменит логики программы. но полагаться на это не стоит.

А вот если вы пользуетесь готовой и адекватной реализацией контейнера, она сама сделает подстановку нового размера. В показанном ниже коде перебор элементов вектора выполняется с помощью итератора i , верхняя граница изменения которого «каждый раз вычисляется заново» методом end :

Равноценна по времени исполнения будет замена выделенного комментариями // цикла на

При этом важно, что в теле такого цикла нельзя менять размерность вектора, удаляя или добавляя элементы, иначе заранее вычисленная константа перестанет соответствовать новой размерности.

Неверный итератор после изменения контейнера внутри цикла

Посмотрим на наполнение выделенного комментариями // цикла for .

Синтаксически вполне корректный, он приведёт к краху программы. Дело в том, что после удаления элемента, итератор i начинает ссылаться на несуществующий элемент и становится невалидным. Следующее i++ уже делается «к ничему» и приводит к краху.

Типовое неверное исправление могло бы быть таким:

Останется крах при размерности списка, кратной 3, то есть, предполагающей удаление последнего элемента. То есть, ошибка станет ещё трудноуловимей. А всё дело в том, что метод erase присваивает итератору ссылку на элемент, идущий следом за удаляемым. А за удаляемым последним ничего нет, и опять инкремент ведёт за разрешённую область памяти.

Правильно будет так:


Обратите внимание, что в заголовке цикла for теперь не делается «автоматического» шага.

Можно было сделать и

Почему? Различия в работе префискного и постфиксного инкрементов, которые нужно хорошо понимать.

Неверный #define с параметрами

Вот пример для этой распространённой проблемы:

Директива #define в этой программе неверна. И ответы будут разные, причём, верен второй, полученный без #define . Дело в том, что макрос — это совсем не функция, а только чисто синтаксическая подстановка, SQUARE(x+1) превратится в x+1 * x+1 или, с учётом старшинства операций, x + x +1 .

Всегда берите аргументы #define в круглые скобки!

На всякий случай, следует избегать и строчных комментариев // при определении #define , хотя современные компиляторы обычно справляются:

А вот всё тело макроса тоже лучше всегда брать в круглые скобки, иначе выйдет вот что:

А вот так всё правильно:

Типичной проблемой является также наличие пробела между именем макроса и открывающей скобкой при описании директивы #define . Это неправильно, открывающая скобка должна идти «впритык» к имени макроса.

Цукерберг рекомендует:  Связки ключей - Приложению требуется пароль от связки ключей Ubuntu 18.04

Вот так всё правильно:

Объявление и инициализация переменной в ветви case

Нельзя одновременно объявлять и инициализировать переменную в ветви case . Можно поместить ветвь оператора switch в операторные скобки < . >, но это чревато другими ошибками.

Объявляя переменную в одном блоке case , вы делаете её доступной для всех последующих case . Если засунуть в < . >, то область видимости ограничится фигурными скобками.

Компилятор не всегда «увидит» такую ошибку, более того, ряд компиляторов вообще не найдёт проблем в приведённом коде, но это не значит, что так нужно делать.

Несоблюдение отступов в тексте программы

Само по себе не является ошибкой, но создаёт для начинающих массу проблем, основная из которых на C++ — «потерянные» операторные скобки, порождающие кучу самых невероятных ошибок от компилятора.

Чтобы не попасть впросак и не терять времени, всегда соблюдайте несколько несложных правил.

1. Структурируйте и выравнивайте код, чтобы сделать его читаемым. Суть в том, что блоки закрываются скобкой > в том же столбце текстового редактора, в котором они открыты скобкой < . Существует, по меньшей мере, два осмысленных способа сделать это:

Мне лично нравится второй, так как экономит мне одно нажатие Enter и минимум одно нажатие пробела (если редактор текста не понимает автоматических отступов). Закрывающая скобка находится не под открывающей, но под первом символом открывающего блок оператора — это воспринимается как то же самое, стоит Вам написать 10-20 тысяч строк кода. Этот стиль неизменен практически во всех моих листингах.

Что касается количества пробелов в отступах, здесь вкусы тоже разнятся. Мне лично нравится одно нажатие пробела, как самое экономичное:

Мне не нравится также использование символа табуляции для отступа. В большинстве встроенных в IDE редакторов размер отступа табуляции невелик, скажем, равен 4, а в «простых» текстовых редакторах, вроде Блокнота, встроенного редактора Far или Notepad++, в которых тоже приходится открывать листинги, он классически равен 8. В результате программа «разъезжается» далеко вправо на сложных блоках, а вид текста зависит от размера отступа табуляции.

Поэтому в том же Visual Studio я делаю так: меню Сервис — Параметры — Все языки — Табуляция, Отступы = Блок (тогда курсор выравнивает следующую строку по предыдущей), Размер интервала табуляции и Размер отступа = 1, выбрана опция Вставлять пробелы.

2. Для соблюдения правила 1 любые блоки всегда закрывайте сразу. Такой блок, как

пишется сразу, а только потом ветвь алгоритма или тело цикла наполняются содержимым. Это поможет не запутаться в сложном коде и облегчит соблюдение следующего принципа.

3. Если делать всё правильно, некомпилируемый код никогда не остаётся «на завтра». Блочно-модульная структура программы позволяет всегда избежать этого. Подпрограмма может быть пустой «заглушкой», вы можете использовать ничего не делающие условия, пустые блоки, комментарии, но текущий код должен компилироваться, если завтра вы не хотите половину рабочего дня затратить на восстановление в памяти недоделанного сегодня.

4. В редакторе кода используется только моноширинный шрифт, как в старых консолях или современном Studio (загляните в меню Сервис — Параметры — Среда — Шрифты и цвета). Без этого все рассуждения об отступах теряют смысл.

«Неожиданный» результат деления целых чисел

Несмотря на её банальность, проблема встречается довольно часто.

Деление целых в C++ даёт целое, забывать об этом нельзя. Правильно так:

То есть, применяем один из способов:

  • хотя бы один из аргументов превратить в вещественный тип, добавив к числу символ точки;
  • явно привести хотя бы один из аргументов к вещественному типу оператором (float)a или (double)a .
Посимвольное чтение файла обрывается при достижении буквы ‘я’

Проблема не в букве ‘я’, а в байте со значением 255. Именно это значение, рассмотренное, как char ( 0xff ) и приведённое к типу int ( 0xffffffff ) совпадёт со значением EOF , которое равно -1 . При работе под Windows с файлами в кодировке Windows-1251 код номер 255 имеет русская буква ‘я’ маленькая. В других кодировках этому коду может соответствовать другая буква. Пример неверного кода:

Решение — изменить тип переменной c :

Использование strncpy без добавления нуль-терминатора

Стандартная функция strncpy часто используется для «безопасного» копирования строк — её третий аргумент задаёт «максимальное количество символов» (на самом деле, длину буфера).

Проблема в том, что функция «забывает» ставить нуль-терминатор в конец целевой строки:

Правильно было бы поставить перед printf «ручное» завершение строки:

Сама по себе реализация этой функции такова: если исходная строка короче целевого буфера, то strncpy будет заполнять всю хвостовую часть оставшегося буфера нулями. Если строка длиннее целевого буфера, strncpy забудет выполнить её нуль-терминацию. Увы, адекватной замены методу копирования ограниченного количества символов в стандарте нет. Есть strlcpy , но это только для unix-систем.

Обяснение проблемы такое: изначально strncpy предназначалась для перевода обычных нуль-терминированных строк в строки фиксированной ширины.

Строки фиксированной ширины использовались в файловой системе Unix.

В формате фиксированной ширины строка хранится в соответствии со следующими правилами:

  • Все строки хранятся в буфере заранее заданной фиксированной ширины N;
  • Если строка имеет длину меньше N, то все лишние позиции в строке заполняются нулевыми символами;
  • Если строка имеет длину ровно N, то она полностью заполняет буфер и не заканчивается нулевым символом.
Функции atoi/atof или sscanf для перевода строки в число

Указанные функции очень популярны при преобразованиях строки в число. Тем не менее, есть 2 серьёзных проблемы, связанных с ними:

  • они не формируют отчёта об ошибочных ситуациях;
  • они имеют неопределённое поведение при переполнении.

Покажем это на примере:

Стандарт рекомендует использовать для преобразования строк в числа функции группы strto. : strtol , strtoul , strtod . Они умеют сообщать в вызывающий код о неправильном формате входных данных и устойчивы к переполнению, при котором сообщают о нём через стандартную переменную errno :

Кроме того, любую некорректную запись числа atoi / atof считают нулём.

Применение delete к объекту из стека

Вот маленькая программа, на которой хорошо видна проблема:

Суть дела в том, что оператор new выделяет память в «куче» (heap) — см. объект c . Тогда применим и delete .


Объект d создаётся в стеке ( stack ), к нему delete неприменим. Хотя при создании объекта строкой кода

конструктор будет вызван.

Каждому способу выделения памяти для переменой соответствует свой способ освобождения, например

Следует учесть, что при выделении памяти через malloc конструктор не вызывается.

Отметим также, что если для объекта, расположенного в стеке, конструктор выделяет память под какие-либо поля — например, в классе есть поле

а конструктор выделяет для него память

то в классе всё равно пишется деструктор и в нём должен быть delete[] для name . Деструктор в этом случае вызовется автоматически в конце области видимости. Нужно проектировать программу так, чтобы подобный вызов был к месту.

Если же время жизни объекта нужно контролировать независимо от текущей области видимости и освобождать его директивно, явным вызовом деструктора, то такой объект следует размещать в «куче» и выделять память оператором new .

Наконец, в классе можно создать метод с условным именем erase , который закроет все внутренние объекты и освободит занимаемую ими память. Деструктор просто вызывает метод erase , но erase можно вызвать и без деструктора.

См. также в этой заметке про стек и кучу.

Пути к файлам без двойного бэкслеша

Очень частая проблема у начинающих, особенно, если они «пришли» с другого, не си-подобного языка:

В этой программе файл никогда не будет открыт и прочитан, даже если он существует и содержит данные. Дело в том, что обратный слеш внутри двойных кавчек для C и C++ — спецсимвол, например, \t означает табуляцию, а \n — перевод строки. Если внутри двойных кавчек нужен символ «обратный слэш», как известно, служащий в Windows для разделения имён папок при записи пути к файлам, он удваивается: \\.

Поэтому правильно будет так:

«Удвоение» последней строки файла

Возьмём небольшую программу, построчно читающую текстовый файл:

Сам файл C:\temp\data.txt состоит из пары строк и пустой строки в конце:

«Удивительным образом» наша программа «удвоит» string 2 , которая будет напечатана дважды. Проблема в том, что после чтения string 2 конец файла ещё не достигнут, а ввод-вывод через stdio буферизован. последний шаг цикла прочитает пустую строку, а в буфере всё ещё будет string 2 , которая и выведется повторно вместо пустой строки. Проверьте сами — если удалить из файла данных пустую строку в конце —

— никакого «удвоения» не будет. Встречается вот такое решение — — но оно грозит, наоборот, «потерять» последнюю строку файла, если после неё нет пустой. Хотя при форматном чтении данных, скажем, целых чисел из файла, такой подход бывает применим:

(лучше посмотреть здесь про чтение чисел scanf’ом из файла со «смешанным» форматом).

Что же до нашего исходного примера — всегда полезен дополнительный анализ прочитанных строк (например, исключение из вывода пустых) или хоть зануление буфера после того, как он использован:

«Лишние» пустые строки при построчном выводе данных

С программкой из этого примера связана ещё одна типовая проблема — прочитанные из файла строки при выводе почему-то содержат лишние пустые строки между строками данных. Файл данных: Вывод:

Всё объясняется просто — fgets читает строку файла вместе с символом перевода строки (точней, под Windows — с парой символов \r\n , интерпретируемых как один), а puts добавляет к выводимой строке ещё один перевод строки. Так что выводите другим методом или удаляйте из прочитанной fgets ‘ом строки последний символ:

Переопределённый оператор не возвращает экземпляр или ссылку на экземпляр класса

. хотя должен это делать. Попросту говоря, это нужно для того, чтобы работали вычисления по цепочке. Посмотрим внимательно на листинг:

Оператор += изменяет только текущий объект, но тоже может встретиться в цепочке вычислений, скажем, A d=*a+=*b; Вполне достаточно, если он вернёт *this для возможности выполнения дальнейших расчётов.

А вот бинарному сложению после выполнения *a + *b нужно вернуть именно новый объект класса, чтобы можно было, например, продолжить цепочку вычисления, прибавив той же функцией-оператором ещё c : *a + *b + c .

Формально не запрещено, сделать и такое бинарное сложение:

Но выражение над экземплярами класса *a + *b + с работать уже не будет, только *a + *b .

Если в коде примера вы замените тип возвращаемого значения у операторных функций с A & на A , тоже будет работать.

Из текстового файла по формату читается только первое число и всё зацикливается!

Вот эта программа в VS будет зацикливаться на чтении из файла

если файл data.txt в текущей папке вот такой:

(прочитается только первое число).

Решение — либо убрать русскую локаль, для которой разделитель целой и дробной части числа — не точка, а запятая:

(закомментировали оператор), либо в файле заменить . в числах на ,

Если читать средствами C++ (потоки), а Си-совместимыми — аналогично.

Сцепление Си-строк без выделения памяти

Сначала воспроизведем типичную ошибку начинающих:

Так как функция strcat не выделяет память, поведение такого кода непредсказуемо и с вероятностью 99% приведёт к ошибке времени исполнения программы! А вот такое сцепление строк сработает:

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

Из той же оперы — использование указателя как массива. Не забывайте также, что вместо динамических массивов и строк Си практичнее использовать контейнер vector и класс string.

Неверное использование тернарного оператора

Тернарный условный оператор (conditional expression) вида условие?оператор1:оператор2; очень удобен, но способен создать ряд проблем при неаккуратном его применении.

При работе с ним нужно учесть 2 момента.

Во-первых, типом тернарного оператора будет наиболее общий тип его двух последних операндов. Что значит наиболее общий? Например, для int и short общим типом будет int , то есть, наиболее общий тип — это такой тип, к которому могу быть приведены оба операнда.

Вполне возможны ситуации, когда общего типа нет и возникает ошибка, например:

Кроме того, если в тернарном операторе происходит преобразование типов к наиболее общему, то тернарный оператор является rvalue . Если же нет, то lvalue (что это значит?).

Во-вторых, нужно учитывать приоритеты операций, самый надёжный путь сделать это — брать тернарный оператор в дополнительне круглые скобки, если он записан внутри выражения или другого оператора.

Например, вот эта маленькая программа корректно использует тернарный оператор для вывода пробела или перевода строки в cout (массив печатается по 2 элемента в одной строке консоли):

Без дополнительных скобок в операторе вывода, то есть, при коде

вывод будет воспринят как

и окажется неправильным.

Присваивание объектов класса, содержащих динамические свойства, без написания конструктора копирования


Если в классе есть динамическое свойство, заданное указателем, при копировании объекта класса, под который выделялась динамическая память в «куче», скопируется только адрес памяти, содержащийся в указателе.

Если затем объект 2, бывший справа от знака «=», будет удалён, перестанет работать и ссылка на память из объекта 1:

Для корректной работы нужно опеределить в классе оператор присваивания и конструктор копирования (см. правило большой тройки).

Тогда можно переписать динамическое свойство s в новую память, которую мы выделим в куче (код простейший, не содержит проверок того, удалось ли выделить память):

«Опасная» проверка булевой переменной в условии

Безобидное на первый взгляд

может привести к трудноуловимой ошибке. Например, в ряде компиляторов константа true==-1 , а в C++ принято, что всё, что не 0 , равно true и только 0 — это false .

Соответственно, всегда безопасней

Неверное выделение памяти под динамическую матрицу

Отметим как отдельную ошибку. Правильный путь таков:

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

Освобождать память, в идеале, нужно в порядке, обратном выделению. Вот пример кода.

12.02.2015, 12:38; рейтинг: 81209

Удалить пробелы в строке C ++ не работает

Я прочитал эти два вопроса уже:

По какой — то причине, я не могу получить решения для правильной работы. В моей программе, я собираю ввод от пользователя и передать его на std::string . Оттуда, я хочу , чтобы удалить все пробелы в нем. Например, если пользователь вводит «3 + 2», то хотелось бы , чтобы изменить «3 + 2».

То, что происходит, все, что до того, как первая строка сохраняется. Вот моя программа:

Поэтому, когда я запускаю эту программу и введите «3 + 2», выход «3».

Вот моя консоль:

Я не могу понять, как решить эту проблему. Я даже пытался использовать решение, включавшее с использованием регулярных выражений , чтобы удалить все \s символы, и это дало мне тот же вопрос.

Чтобы прочитать полную строку (до завершения \ п), вам нужно использовать , например std::getline(std::cin, UserInput); . В противном случае, вы сейчас читаете текст до первого символа пробела.

Урок №7. Решения самых распространенных проблем

Обновл. 21 Фев 2020 |

В этом уроке мы рассмотрим наиболее частые проблемы, с которыми сталкиваются новички в C++.

Проблема №1

Как использовать русский язык в программах C++?

Ответ №1

Чтобы выводить кириллицу в C++ нужно подключить заголовочный файл Windows.h:

И прописать следующие две строчки в функции main():

Проблема №2

При выполнении программы появляется чёрное консольное окно, а затем сразу пропадает.

Ответ №2

Некоторые компиляторы (например, Bloodshed’s Dev C++) автоматически не задерживают консольное окно после того, как программа завершила своё выполнение. Если проблема в компиляторе, то следующие два шага решат эту проблему:

Во-первых, добавьте следующую строчку кода в верхнюю часть вашей программы:

Во-вторых, добавьте следующий код в конец функции main() (прямо перед оператором return):

Таким образом программа будет ожидать нажатия клавиши, чтобы закрыть консольное окно. Вы получите дополнительное время, чтобы хорошенько всё рассмотреть/изучить. После нажатия любой клавиши, консольное окно закроется.

Другие решения, такие как system(«pause»); , могут работать только на определённых операционных системах, поэтому вариант выше предпочтительнее.

Примечание: Visual Studio не задерживает консольное окно, если выполнение запущено с отладкой («Отладка» > «Начать отладку» или F5). Если вы хотите, чтобы была пауза — воспользуйтесь решением выше или запустите программу без отладки («Отладка» > «Запуск без отладки» или Ctrl+F5).

Проблема №3

При компиляции программы я получаю следующую ошибку:
«c:vcprojectstest.cpp(263) :fatal error C1010: unexpected end of file while looking for precompiled header directive».

Ответ №3

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

Обратите внимание, в программах с несколькими файлами, каждый файл должен начинаться именно с этой строчки кода. В качестве альтернативы, вы можете просто отключить использование предварительно скомпилированных заголовков.

Проблема №4

При использовании cin, cout или endl компилятор говорит, что cin, cout или endl являются «undeclared identifier» (необъявленными идентификаторами).

Ответ №4

Во-первых, убедитесь, что у вас присутствует следующая строчка кода в верхней части вашей программы:

Цукерберг рекомендует:  Конструктор запросов для MongoDB

Во-вторых, убедитесь, что cin, cout или endl имеют префикс “std::”. Например:

Проблема №5

При использовании endl для перехода на новую строку, появляется ошибка, что end1 является «undeclared identifier».

Ответ №5

Убедитесь, что вы не спутали букву l (нижний регистр L) в endl с цифрой 1. В endl все символы являются буквами. Также легко можно спутать заглавную букву О с цифрой 0 (нуль).

Проблема №6

Моя программа компилируется, но работает не так как нужно. Что мне делать?

Ответ №6


Проблема №7

Как включить нумерацию строк в Visual Studio?

Ответ №7

Перейдите в меню «Средства» > «Параметры»:

Затем откройте вкладку «Текстовый редактор» > «Все языки» > «Общие» и поставьте галочку возле «Номера строк», затем нажмите «ОК»:

Проблема №8

При компиляции программы я получаю следующую ошибку: «unresolved external symbol _main or _WinMain@16».

Ответ №8

Это означает, что ваш компилятор не может найти главную функцию main(). Все программы должны содержать эту функцию.

Здесь есть несколько пунктов, которые нужно проверить:

Есть ли в вашей программе функция main()?

Слово main написано правильно?

Подключен ли файл, который содержит функцию main(), к вашему проекту? (если нет, то переместите функцию main() в файл, который является частью вашего проекта, либо добавьте этот файл в ваш проект)

Подключен ли файл, содержащий функцию main(), к компиляции?

Проблема №9

При компиляции программы я получаю следующее предупреждение: “Cannot find or open the PDB file”.

Ответ №9

Это не ошибка, а предупреждение. На работоспособность вашей программы оно не повлияет. Тем не менее, в Visual Studio вы можете решить всё следующим образом: перейдите в меню «Отладка» > «Параметры» > «Отладка» > «Символы» и поставьте галочку возле «Серверы символов (Майкрософт)», затем нажмите «ОК».

Проблема №10

Я использую Code::Blocks или G++, но функционал C++11/C++14 не работает.

Ответ №10

В Code::Blocks перейдите в «Project» > «Build options» > «Compiler settings» > «Compiler flags» и поставьте галочку возле пункта «Have g++ follow C++14 ISO C++ language standard». Смотрите урок № 4 — там есть скриншоты, как это сделать.

При компиляции в g++, добавьте следующий код в командную строку:

Проблема №11

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

Ответ №11

Ваш антивирус может блокировать выполнение вашей программы. Попробуйте отключить его на время и запустите программу ещё раз.

У меня есть другая проблема, с которой я не могу разобраться. Как и где я могу получить ответ?

По мере прохождения уроков, у вас, несомненно, появятся вопросы или вы столкнетесь с проблемами. Что делать?

Во-первых, спросите у Google. Четко сформулируйте вашу проблему и просто погуглите. Если у вас есть сообщение об ошибке — скопируйте его и вставьте в поиск Google, используя кавычки. Скорее всего, кто-то уже задал такой же вопрос, как у вас, и получил на него ответ.

Если Google не помог, то спросите на специализированных сервисах вопросов/ответов/форумов. Вот самые популярные из них:

Но будьте внимательны и старайтесь максимально конкретизировать свою проблему, а также не забудьте указать, какую операционную систему и IDE вы используете, а также укажите, что вы пробовали сделать самостоятельно.

Несколько советов по оптимизации кода на C++

1) Вместо использования цикла for() быстрее использовать цикл while(). По той простой причине, что while() был придуман раньше, чем for(), то следовательно while() более низкоуровневый, а следовательно за абстрагирование мы расплачиваемся меньше, что ускоряет наш код.

2) если честно, то данной код можно ускорить ещё больше. поскольку оператор ++ является неоптимальным, лучше использовать его аналог =.

Так делали ещё наши деды на Паскале и Фортране, получая код, который просто летал. Я вам больше скажу, при программировании процессоров, работавших на советских космических станциях использовали именно этот оператор. И ведь где, а там нужно высокая эффективность кода.

3) Если вам нужно сделать вечный цикл, например как при поиски в ширину, лучше писать while(!false) вместо обычного while(true). поскольку процессор быстрее воспринимает команды с нулями, то и отрицание нуля он будет обрабатывать быстрее, нежели просто единицу. Это сильно ускоряет ваш проход по циклу.

4) Описание типа данных для каждой отдельной переменной отдельно также уменьшает затраты процессорного времени. Поскольку вы перекладываете часть работы процессора на себя. Как бы расписывая всё для каждой переменной в отдельности, тем самым «разжёвывая» код, процессору приходиться декодировать значительно меньше данных.

5) Ну и последний совет по оптимизации заключается в том, чтобы сократить потребление оперативной памяти при исполнении программы. ОБЯЗАТЕЛЬНО удаляйте все лишние пробелы, табуляции, переходы на новые строки и комментарии. Именно они зачастую являются причиной потребления большей части оперативки и кеша процессора. Вы сами можете увидеть, как код стал более компактным и более читабельным. Если ваш код будет читать другой программист, то на его экранном пространстве также он будет занимать меньше места, соответственно полезного кода будет помещаться больше.

Проблема с cin при вводе пробелов с использованием строкового класса

У меня есть следующий код:

main.cpp

Я заметил, что если я введу пробел в поле ввода для имени, это не даст мне возможности ввести имя, и он будет рассматривать запись после пробела как возраст. Я прошу прощения, если это ошибка новичка, что, вероятно, так и есть. Ранее я программировал Java и решил, что хочу перейти на C ++, потому что он лучше соответствует моим потребностям. Я также, вероятно, отформатирую свой код странно в соответствии с вашими стандартами, исправьте его, если хотите.

Я также заметил еще одну ошибку, с которой у меня никогда не было проблем в Java. Я не могу понять, как предотвратить его мгновенное закрытие после завершения обработки. Я слышал, что вы можете использовать «system. (» Пауза «); но мне также сказали не использовать его. Я действительно запутался в том, что использовать. Я слышал, чтобы использовать getchar () ;, но похоже, он ничего не делает.

Любая помощь будет принята с благодарностью, так как я начинающий, когда дело доходит до C ++.

2 ответа

Вот что происходит с входным буфером при запуске вашей программы:

Вы ждете ввода. Когда вы вводите «Ryan Cleary» и нажимаете ввод, буфер ввода содержит:

Теперь ваш cin читает ввод как обычно, останавливаясь на пробеле, оставляя ваш буфер следующим образом:

Обратите внимание на начальный пробел, так как он останавливается после чтения Ryan . Ваша первая переменная теперь содержит Ryan . Однако, если вам нужно полное имя, используйте std::getline . Он будет читать до новой строки, а не только пробел. Во всяком случае, продолжая:

Теперь вы получаете еще один вход. Там уже что-то есть. Он пропускает пробел, пока не может начать чтение, оставляя буфер просто:

Ваша вторая переменная получает текст Cleary . Обратите внимание, что перевод строки все еще в буфере, что подводит меня ко второй части. Заменить system («pause»); способом, который всегда работает, сложно. Лучше всего, как правило, жить с менее чем совершенным решением, или, как мне нравится, с тем, которое точно не гарантирует, что оно говорит:

Хорошо, так что cin.get() не работает. Как насчет этого:

Это прекрасно работает, но что, если вы скопируете и вставите его куда-нибудь, где символ новой строки не останется? Вам придется дважды нажать Enter!

Решением является очистка новой строки (и всего остального), а затем ожидание. Это цель cin.sync() . Однако, как видно из раздела примечаний, не гарантируется очистка буфера, как он говорит, поэтому, если ваш компилятор решит не делать этого, его нельзя будет использовать. Для меня, однако, это именно так, оставляя решение:


Главная плохая вещь в system(«pause»); заключается в том, что вы понятия не имеете, какую программу он будет запускать на чужом компьютере. Они могли бы изменить pause.exe или поставить тот, который был найден первым, и у вас нет возможности узнать. Это может потенциально разрушить их компьютер, потому что это может быть любая программа.

Вы должны попробовать cin.getline , таким образом, поток будет читаться до первого символа новой строки.

Хорошо, плохой совет, как указали некоторые люди. Вы можете использовать std :: getline , чтобы прочитать всю строку. Опять же, разделитель — это новая строка, если не сообщается. Чтобы прочитать из cin , вы можете передать его в качестве первого параметра (и строку в качестве второго).

(конечно, вы можете опустить часть std:: в строках, так как вы уже сообщили компилятору, что используете —- +: = 3 =: + —- пространство имен)

S.erase не работает при попытке удалить пробелы из строки

Я пытаюсь удалить пробелы из строки, чтобы проверить фразу палиндрома. Я искал другие методы, но мой профессор буквально скопировал и вставил в нашу инструкцию место для цикла удаления, но я не могу заставить его работать, и он говорит, что не хочет, чтобы мы обращались в Интернет за помощью. Я пытаюсь удалить пробелы из фразы типа «слишком жарко, чтобы кричать», чтобы подтвердить это. Я могу заставить мою программу работать с отдельными словами, такими как»bob», но не с фразами.

C++ — Подскажите, почему не работает удаление пробелов c++

1) На Раздел распространяются все Правила Форума.
2) Перед тем, как создать новый топик, убедитесь, что Вы читали Правила создания тем в Разделе.
3) Вопросы, не связанные с программированием (настройки MS Visual Studio, книги, библиотеки и т.д.),
обсуждаются в разделе C/C++: Прочее
4) Вопросы разработки .NET (Windows Form, C++/CLI и т.п.) приложений на Visual C++/C# обсуждаются в разделе .NET.
5) Нарушение Правил может повлечь наказание со стороны модераторов.

Два способа того, как удалить Microsoft Visual C++

Со временем на компьютере скапливается очень много приложений, которые не всегда нужны пользователю. Бывает и такое, что множество софта устанавливается автоматически. Если вы заметили, что объем свободного пространства на диске резко сократился, пора задуматься об удалении ненужных приложений. В этой статье будет рассказано, как удалить Microsoft Visual C++. Но прежде чем давать подробные инструкции по удалению, необходимо разобраться, можно ли вообще удалять этот компонент системы.

Можно ли удалить Microsoft Visual C++

Можно, но стоит ли это делать? Сейчас и ответим на этот вопрос. Если вкратце, то лучше этого не делать. Дело в том, что данное программное обеспечение нужно системе для запуска других программ. Каждая из них требует разную версию Visual C++, поэтому, удалив хоть одну с компьютера, вы можете со временем обнаружить, что какая-то программа попросту отказывается устанавливаться.

Рекомендуется удалять Visual C++ только в том случае, если вы точно знаете, что она не нужна для запуска других программ. Например, если вы не пользуетесь старыми приложениями, которые выходили в нулевых годах, значит, можете смело удалять старые версии Microsoft Visual C++.

Способы удаления

Мы выяснили, что Microsoft Visual C++ можно удалять, теперь давайте разберемся, как это сделать. Сразу стоит сказать, что существует два способа выполнения поставленной задачи. Первый подразумевает изменение параметров системы. Второй же задействует специальный системный компонент «Программы и компоненты». Разберем каждый метод в отдельности.

Способ № 1: через параметры системы (только в Windows 10)

Если вы владелец Windows 10, значит, вам лучше воспользоваться именно этим способом, позволяющим удалить Microsoft Visual C++. Заключается он в следующем:

  1. Откройте меню «Пуск».
  2. В нижнем левом углу нажмите по кнопке «Параметры», выполненной в виде шестеренки.
  3. В появившемся одноименном окне нажмите по кнопке «Приложения».
  4. Появится весь список установленных программ на компьютере. Пролистайте его вниз, пока не обнаружите Microsoft Visual C++.
  5. Нажмите по программе левой кнопкой мыши.
  6. В появившемся подменю нажмите по кнопке «Удалить».

Теперь вам необходимо дать согласие на выполнение этого действия и дождаться его завершения. Спустя несколько секунд программа будет полностью удалена с компьютера.

Способ № 2: через «Программы и компоненты» (универсальный метод)

Если на вашем компьютере установлена не Windows 10, значит, вышеописанной инструкцией вы не сможете воспользоваться. Давайте разберемся, как удалить Microsoft Visual C++ в других версиях «Виндовс».

  1. Нажмите сочетание клавиш Win + R.
  2. В появившемся окне «Выполнить» выпишите команду «appwiz.cpl» (без кавычек).
  3. Нажмите кнопку «ОК» для ее выполнения.
  4. Откроется окно «Программы и компоненты», в котором будет список всех установленных приложений на компьютере.
  5. Отыщите Microsoft Visual C++ и нажмите по программе правой кнопкой мыши.
  6. В появившемся меню выберите опцию «Удалить». Возможно, эта кнопка будет неактивна, тогда нажимайте «Изменить».

После этого, как и в предыдущем способе, начнется удаление программы. Дождитесь его завершения, после чего закрывайте окно «Программы и компоненты».

Заключение

Вот мы и разобрались, как удалить Microsoft Visual C++. Как можно заметить, две инструкции довольно схожи. Какой пользоваться — решать только вам.

Удалить лишние пробелы в C ++

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

В основном я хочу , чтобы преобразовать abc sssd g g sdg gg gf в abc sssd g g sdg gg gf .

В таких языках, как PHP или C #, было бы очень легко, но не в C ++, я вижу. Это мой код:

Это не работает. Я попробовал несколько методов. То, что я пытаюсь сделать, это перебирать строку по буквам и сбросить его в другой строке до тех пор, пока существует только одно место в ряду; если есть два пробела, не пишите второй символ новой строки.

Как я могу решить эту проблему?

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

Вот демо . Обратите внимание , что я изменил из строк с стилем на более безопасные и более мощные строки C ++.

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

Вот простое, не C ++ 11 решения, используя ту же remove_extra_whitespace() подпись , как и в вопросе:

Так как вы используете C ++, вы можете воспользоваться стандартной библиотекой функций , предназначенных для такого рода работы. Вы можете использовать std::string (вместо char[0x255] ) и std::istringstream , который заменит большую часть арифметики указателей.

Во-первых, сделать строковый поток:

Затем, считывать строки из него. Она будет автоматически удалять пробельные разделители:

Внутри цикла, построить свою выходную строку:

Недостаток этого способа является то, что она выделяет память динамически (в том числе несколько перераспределения, выполненного, когда выходная строка растет).

Есть много способов сделать это (например, с использованием регулярных выражений), но один из способов вы можете сделать это , используя std::copy_if с сохранением состояния функтора запоминания , был ли последний символ пробела:

для на месте модификации можно применить стирающие удалить метод:

Таким образом, вы сначала переместить все лишние пробелы в конце строки, а затем обрезать его.

Большое преимущество C ++ является то , что является универсальным достаточно , чтобы порт вашего кода равнинно-с-статических строк только с несколькими изменениями:

Достаточно интересный remove шаг здесь строковое представление независимой. Она будет работать с std::string без изменений вообще.

Я тонущий ощущение, что хороший ол»зсапЕ будет делать (на самом деле, это C школа эквивалент C ++ Анатолия решения):

Мы используем тот факт , что scanf имеет магические встроенные пространства пропуская возможности. Затем мы используем , возможно , менее известное %n «преобразование» спецификацию , которая дает нам количество символов , потребляемым scanf . Эта функция часто пригождается при чтении строк, как и здесь. Горькая капля , что делает это решение менее чем совершеннее strlen вызов на выходе (нет « сколько байт есть я на самом деле просто написал » спецификатор преобразования, к сожалению).

Последнее не менее важно использование зсапЕ легко здесь , потому что достаточно памяти гарантированно существовать output ; если бы это было не так, то код станет более сложным из — за буферизации и обработки переполнения.

Так как вы пишете с-стиль, вот способ сделать то , что вы хотите. Обратите внимание , что вы можете удалить ‘\r’ и ‘\n’ которые разрывы строк (но, конечно, это до вас , если вы считаете , эти пробелы или нет).

Эта функция должна быть столь же быстро или быстрее, чем любой другой альтернативы и нет выделения памяти не происходит даже тогда, когда он вызывается с STD :: строки (я перегружен его).

Если кратко рассмотрим на ++ стандартной библиотеки C, вы заметите, что многие C ++ функции, которые возвращают зЬй :: строку, или другие StD :: объекты являются в основном обертка для хорошо написана экстерном функции «C». Так что не бойтесь использовать функции C в приложениях C ++, если они хорошо написаны, и вы можете перегрузить их поддерживать STD :: строки и такие.

Например, в Visual Studio 2015, std::to_string написано именно так:

и _Integral_to_string обертка к функции С sprintf_s

Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих