Важно понять с самого начала. Ошибки — это не признак того, что «программирование
не для вас». Ошибки — это рабочий материал. Опытный программист отличается от новичка
не тем, что не ошибается, а тем, что быстро узнаёт знакомые ошибки и знает,
где их искать. Именно этому и посвящена страница: соберите в голове каталог типичных промахов,
и в следующий раз вы их узнаете почти мгновенно.
Восемь классических ошибок
Кликните по карточке, чтобы развернуть полный разбор.
1
Целочисленное деление
›
Плохой код
int a = 7; int b = 2; double result = a / b; cout << result; // ожидаем 3.5, получаем 3
Что произошло. Операция
/ работает по-разному для целых и
вещественных чисел. Если оба операнда целые, деление целочисленное — дробная часть
отбрасывается. 7 / 2 даёт 3, и только после этого результат
«приводится» к double — уже как 3.0. Тип результата определяется
до присваивания.
Правильно
int a = 7; int b = 2; double result = (double)a / b; // 3.5 // или так: double result2 = a / 2.0; // 3.5 — делитель вещественный
Правило. Хотите дробный результат — сделайте хотя бы один операнд вещественным
явно: приведением типа
(double) или записав делитель как 2.0.
Особенно коварно это в формулах: (a + b + c) / 3 для среднего арифметического
даст целое число.
2
Забытая инициализация накопителя
›
Плохой код
int sum; // мусор в памяти
for (int i = 1; i <= 5; i++)
sum += i;
cout << sum; // каждый запуск разное!
Что произошло. Переменная
sum объявлена, но не инициализирована —
в ней лежит «мусор» от предыдущих программ. Прибавляя к нему числа, мы каждый раз получаем
разный результат. Ошибка опасна тем, что программа не падает — просто считает
неправильно, иногда незаметно.
Правильно
int sum = 0; // накопитель — обнуляем
for (int i = 1; i <= 5; i++)
sum += i;
cout << sum; // 15
Правило. Перед использованием переменной-накопителя всегда
задумайтесь, с какого значения начинается накопление. Для суммы это
0,
для произведения — 1, для поиска максимума — первый элемент массива
или очень маленькое число.
3
= вместо ==
›
= вместо ==Плохой код
int x = 5;
if (x = 3) { // присваивание, а не сравнение!
cout << "x равно 3";
}
cout << "x = " << x; // x стало 3, а не 5
Что произошло. Один знак
= — присваивание (положи в переменную
значение). Два знака == — сравнение (равно ли одно другому). В условии
if (x = 3) программа присваивает x значение 3, и это присваивание
возвращает 3 — оно ненулевое, значит, условие истинно. Программа молча делает не то,
что вы хотели.
Правильно
int x = 5;
if (x == 3) { // сравнение
cout << "x равно 3";
}
cout << "x = " << x; // x = 5, как и было
Компилятор предупредит. Включите флаги
-Wall -Wextra в g++ —
GCC выдаст предупреждение «suggest parentheses around assignment used as truth value».
Это сигнал: вы почти наверняка опечатались.
4
Лишняя точка с запятой после if или for
›
if или forПлохой код
int sum = 0;
for (int i = 1; i <= 5; i++); // ← вот тут ;
{
sum += i; // эта строка выполнится один раз
}
cout << sum; // ошибка компиляции или мусор
Что произошло. Точка с запятой завершает оператор. Записав
for (...);, вы говорите: «тело цикла пустое». Цикл честно отработает 5 раз,
ничего не делая. А фигурные скобки со sum += i; — это уже отдельный блок,
который выполнится один раз, да ещё и после выхода из цикла, где переменная i
недоступна.
Правильно
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
}
cout << sum; // 15
Особенно коварно для
if:
if (x > 0); y = 1; — присваивание y = 1 выполнится
всегда, независимо от условия. Найти такую ошибку взглядом очень трудно.
5
Выход за границы массива
›
Плохой код
int a[5] = {10, 20, 30, 40, 50};
// пытаемся обойти все 5 элементов
for (int i = 1; i <= 5; i++) {
cout << a[i] << " "; // при i=5 — выход за границу!
}
Что произошло. В C++ индексы массива начинаются с 0.
Массив из 5 элементов имеет индексы 0, 1, 2, 3, 4. Обращение к
a[5]
читает шестой элемент — который находится за пределами выделенной памяти.
Что там лежит — неизвестно: мусор, чужие данные, и читать их нельзя.
Правильно
int a[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
cout << a[i] << " "; // i = 0, 1, 2, 3, 4
}
Почему не падает сразу. C++ не проверяет границы массивов во время
выполнения. Программа может работать «нормально», но на самом деле читать или портить
чужие данные. Это источник самых неприятных, трудно находимых багов.
6
Отсутствие фигурных скобок в if
›
ifПлохой код
if (x > 0)
cout << "положительное";
cout << "число"; // выполняется всегда!
Что произошло. Без фигурных скобок
if управляет
только одной следующей строкой. Первый cout действительно внутри
условия. Второй — уже снаружи, он выполняется независимо от того, положительное
ли x. Отступ в коде обманчив — он ничего не значит для компилятора.
Правильно
if (x > 0) {
cout << "положительное";
cout << "число";
}
Правило хорошего тона. Ставьте фигурные скобки всегда, даже
если внутри одна строка. Так вы никогда не попадёте в эту ловушку, и код будет легче
расширять.
7
Сравнение C-строк через ==
›
==Плохой код
char name[20];
cin >> name;
if (name == "admin") { // всегда ложь!
cout << "Доступ разрешён";
}
Что произошло. Массив символов
char[] — это
адрес первого символа в памяти, а не строка в человеческом смысле.
Оператор == сравнивает адреса, а не содержимое. Адрес массива
name и адрес литерала "admin" — всегда разные, поэтому
условие всегда ложно.
Правильно
char name[20];
cin >> name;
if (strcmp(name, "admin") == 0) { // функция из <cstring>
cout << "Доступ разрешён";
}
// Альтернатива — std::string
string name2;
cin >> name2;
if (name2 == "admin") { // работает как ожидается
cout << "Доступ разрешён";
}
Запомните. Для
char[] используйте функции
strcmp, strcpy, strcat. Либо переходите
на std::string — там операторы ==, +, =
работают интуитивно.
8
cin >> для строки с пробелами
›
cin >> для строки с пробеламиПлохой код
char text[80]; cin >> text; // Если пользователь ввёл "Hello, World!" // в text попадёт только "Hello,"
Что произошло. Оператор
cin >> читает до первого
пробела (или табуляции, или перевода строки). Всё, что идёт после пробела, останется
во входном потоке и попадёт в следующий cin — часто с неожиданным результатом.
Правильно
char text[80]; cin.getline(text, 80); // прочитает всю строку целиком // или для std::string: string text2; getline(cin, text2); // или с явным ограничением символов: cin.get(text, 80);
Осторожно после
cin >>. Если до этого вы уже читали
числа через cin >>, в буфере может остаться символ перевода строки.
Тогда getline немедленно прочитает пустую строку. Обычное решение —
перед getline вызвать cin.ignore().
Тренажёр: найдите ошибку
Пять фрагментов кода. В каждом ровно одна ошибка. Выберите правильный вариант ответа.
0 / 5
правильных ответов
Мини-чеклист на каждый запуск
Прежде чем жаловаться «не работает», проверьте по порядку:
Синтаксис
- Все точки с запятой на месте — и их нет там, где не нужно?
- Скобки
{ }и( )закрыты парно? - Названия переменных написаны одинаково (регистр!)?
Логика
- Переменные инициализированы перед использованием?
- Границы циклов: с чего начинается счётчик и чем заканчивается?
- Деление: где-то не нужен ли
(double)? - Сравнение строк: точно
strcmpилиstd::string?
Данные
- Массив не выходит за свои границы?
- Ввод считывает то, что вы ожидаете (проверьте с явным выводом)?
- Первый и последний элементы обрабатываются корректно?
Инструменты
- Включены предупреждения компилятора (
-Wall -Wextra)? - Пробовали запустить в отладчике с точкой останова?
- Пробовали печатать промежуточные значения через
cout?
Совет. Если программа ведёт себя странно, самый быстрый способ понять
причину — вывести промежуточные значения на экран. Одна-две строки вида
cout << "i=" << i << " sum=" << sum << endl;
в подозрительном месте показывают, что происходит на самом деле.