Содержание урока:
Теория:
Итак, давайте разберёмся, что именно представляет из себя процесс отладки программного кода. Чтобы наглядно продемонстрировать примеры процесса отладки, нам понадобятся придумать задачу, пошагово её решить, а затем также пошагово отладить, комментируя всё необходимое.
IПостановка задачи
Дан текстовый файл, содержащий 30 целых чисел. Пользователь вводит на клавиатуре три целых числа. Необходимо выбрать среди чисел из файла числа, имеющие остаток 1 при делении на 3 и находящиеся между первыми двумя числами, которые ввёл пользователь. Выбранные числа необходимо разделить на третье число пользователя, а затем взять квадратный корень от частного. Среди полученных частных, необходимо найти минимальное и вывести на экран. На примере данной задачи мы и разберём процесс отладки.
IIНаписание кода
Давайте начнём писать программный код на языке программирования С/С++.
Шаг 1. Создадим стандартный шаблон для программы:#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
int main(){ // создаём главную функцию
return 0; // возвращаем 0 - успех
}
Шаг 2. Теперь необходимо открыть текстовый файл для чтения, а также закрыть его в конце программы (5-6 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Шаг 3. Создадим три целочисленные переменные, пригласим пользователя на ввод и считаем три целых числа в три созданные переменные (7-8 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Шаг 4. Создадим скользящую переменную, в которую будут считываться числа из файла и запустим цикл for на 30 проходов. Внутри цикла for считаем очередное число из файла в скользящую переменную (9-12 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
int x = 0; // создаём скользящую переменную
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
fscanf(in, "%d", &x); // считываем скользящую переменную
}
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Шаг 5. Проверим очередное число на принадлежность к интервалу, заданному граничными числами пользователя (12-13 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
int x = 0; // создаём скользящую переменную
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
fscanf(in, "%d", &x); // считываем скользящую переменную
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
}
}
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Шаг 6. Проверим очередное число на остаток при делении на 3. Необходимо, чтобы остаток был равен единице (13-14 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
int x = 0; // создаём скользящую переменную
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
fscanf(in, "%d", &x); // считываем скользящую переменную
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
}
}
}
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Шаг 7. Разделим число, которое удовлетворяет необходимым требованиям, на делитель, введённый пользователем. А затем возьмём квадратный корень (15-16 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
int x = 0; // создаём скользящую переменную
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
fscanf(in, "%d", &x); // считываем скользящую переменную
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
x = sqrt(x); // берём квадратный корень от получившегося частного
}
}
}
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Шаг 8. Так как нам необходимо найти минимальное число, создаём переменную для хранения этого минимального числа. Затем после взятия квадратного корня, сравниваем получившееся число и минимальное. В случае, если получившееся окажется меньше минимального, кладём его в переменную, хранящую минимальное число (11, 18-20 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
fscanf(in, "%d", &x); // считываем скользящую переменную
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Шаг 9. Всё готово. Просто выведем в конце программы найденное минимальное число (24 строка):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
fscanf(in, "%d", &x); // считываем скользящую переменную
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Вот и всё! Вроде бы… Компилятор ведь никаких ошибок не выдаёт. Давайте запустим код на реальном примере, и начнём процесс отладки.
IIIОтладка
Теперь начнём процесс отладки.
Перед тем, как начать процесс отладки, давайте разберёмся в том, что нужно будет сделать пользователю. Допустим, у пользователя имеется исполняемый файл данной программы, т он намерен выполнить поставленную задачу для имеющихся у него данных. Также, предположим, что вместе с исполняемым файлом, у пользователя есть инструкция по использованию данной программы:
- Поместить в директорию с исполняемым файлом текстовый файл, содержащий 30 целых чисел, и назвать его «in.txt»
- Запустить исполняемый файл и получить результат
Ошибка открытия файла
Но предположим, что пользователь не так понял что-то или перепутал, а, может быть, просто забыл назвать файл именно «in.txt». Поэтому необходимо добавить в наш код проверку открытия текстового файла, а также подсказку по его местоположению и названию. Сделаем это (7-12 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
if (in == NULL){ // проверяем файл на корректность
printf("Open file error.\n"); // сообщаем пользователю об ошибке открытия файла
printf("\tThe file must be in the same directory with this programm\n"); // подсказываем, где должен находиться файл
printf("\tThe file must be named in.txt\n"); // подсказываем, как должен называться файл
return -2; // возвращаем -2 (если не установлен код ошибки открытия файла)
}
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
scanf("%d%d%d", &bord_1, &bord_2, &divisor); // считываем 3 числа с клавиатуры
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
fscanf(in, "%d", &x); // считываем скользящую переменную
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Теперь, если пользователь сделает что-то не так с названием или местоположением файла, в консоли выведется соответствующая ошибка и подсказки по её исправлению. Это часть хорошего тона программиста. Пример вывода в случае ошибки с открытием файла:
Open file error.
The file must be in the same directory with this programm
The file must be named in.txt
Process returned -2 (0xFFFFFFFE) execution time : 0.005 s
Press any key to continue.
Ошибка чтения чисел с клавиатуры или из файла
Пусть с файлом всё хорошо.
Теперь проблема может возникнуть при чтении чисел с клавиатуры. Предположим, пользователь вместо трёх целых чисел введёт что-то другое. В таком случае, программа, которая написана с соблюдением правил хорошего тона, должна сообщить пользователю об этом. Точно такая же ошибка может возникнуть и при чтении чисел из файла. Поэтому добавим соответствующие команды (15-18, 22-25 строки):#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
if (in == NULL){ // проверяем файл на корректность
printf("Open file error.\n"); // сообщаем пользователю об ошибке открытия файла
printf("\tThe file must be in the same directory with this programm\n"); // подсказываем, где должен находиться файл
printf("\tThe file must be named in.txt\n"); // подсказываем, как должен называться файл
return -2; // возвращаем -2 (если не установлен другой код ошибки открытия файла)
}
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
if (scanf("%d%d%d", &bord_1, &bord_2, &divisor) != 3){ // считываем 3 числа с клавиатуры
printf("Reading error.\n"); // сообщаем пользователю об ошибке чтения
return -1; // возвращаем -1 (если не установлен другой код ошибки чтения)
}
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
if (fscanf(in, "%d", &x) != 1){ // считываем скользящую переменную
printf("File reading error.\n"); // сообщаем пользователю об ошибке чтения из файла
return -3; // возвращаем -3 (если не установлен другой код ошибки чтения из файла)
}
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Теперь, если пользователь сделает что-то не так с названием или местоположением файла, в консоли выведется соответствующая ошибка и подсказки по её исправлению. Это часть хорошего тона программиста. Пример вывода в случае ошибки чтения:
Print a bords and a divisor:
12 20 e4
Reading error.
Process returned -1 (0xFFFFFFFF) execution time : 7.617 s
Press any key to continue.
Пример вывода в случае ошибки чтения из файла:
Print a bords and a divisor:
12 20 5
File reading error.
Process returned -3 (0xFFFFFFFD) execution time : 3.916 s
Press any key to continue.
Корректное определение границ
Предположим, что пользователь ввёл границы в другом порядке: сначала большее, а потом меньшее число. Тогда написанный нами программный код не сможет выбрать ни одного числа. Он будет пытаться проверить, что очередное число больше, чем большая граница и одновременно с этим меньше, чем меньшая. Чтобы исправить этот момент, необходимо добавить в программный код соответствующую проверку (19-24 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
if (in == NULL){ // проверяем файл на корректность
printf("Open file error.\n"); // сообщаем пользователю об ошибке открытия файла
printf("\tThe file must be in the same directory with this programm\n"); // подсказываем, где должен находиться файл
printf("\tThe file must be named in.txt\n"); // подсказываем, как должен называться файл
return -2; // возвращаем -2 (если не установлен другой код ошибки открытия файла)
}
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
if (scanf("%d%d%d", &bord_1, &bord_2, &divisor) != 3){ // считываем 3 числа с клавиатуры
printf("Reading error.\n"); // сообщаем пользователю об ошибке чтения
return -1; // возвращаем -1 (если не установлен другой код ошибки чтения)
}
int temp = 0; // создаём временную переменную
if (bord_1 > bord_2){ // проверяем границы
temp = bord_1; // меняем их местами, если меньшая оказалась большей
bord_1 = bord_2;
bord_2 = temp;
}
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
if (fscanf(in, "%d", &x) != 1){ // считываем скользящую переменную
printf("File reading error.\n"); // сообщаем пользователю об ошибке чтения из файла
return -3; // возвращаем -3 (если не установлен другой код ошибки чтения из файла)
}
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Деление на нуль
А кто гарантирует, что пользователь по ошибке или оплошности не укажет число нуль в качестве делителя? Вот именно, что никто. Поэтому необходимо добавить в программный код проверку того, что делитель не равен нулю (25-28 строки):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
if (in == NULL){ // проверяем файл на корректность
printf("Open file error.\n"); // сообщаем пользователю об ошибке открытия файла
printf("\tThe file must be in the same directory with this programm\n"); // подсказываем, где должен находиться файл
printf("\tThe file must be named in.txt\n"); // подсказываем, как должен называться файл
return -2; // возвращаем -2 (если не установлен другой код ошибки открытия файла)
}
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
if (scanf("%d%d%d", &bord_1, &bord_2, &divisor) != 3){ // считываем 3 числа с клавиатуры
printf("Reading error.\n"); // сообщаем пользователю об ошибке чтения
return -1; // возвращаем -1 (если не установлен другой код ошибки чтения)
}
int temp = 0; // создаём временную переменную
if (bord_1 > bord_2){ // проверяем границы
temp = bord_1; // меняем их местами, если меньшая оказалась большей
bord_1 = bord_2;
bord_2 = temp;
}
if (divisor == 0){ // проверяем, что делитель не нуль
printf("The divisor cannot be 0.\n"); // сообщаем об ошибке
return -4; // возвращаем -4 (если не установлен другой код ошибки деления на нуль)
}
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
if (fscanf(in, "%d", &x) != 1){ // считываем скользящую переменную
printf("File reading error.\n"); // сообщаем пользователю об ошибке чтения из файла
return -3; // возвращаем -3 (если не установлен другой код ошибки чтения из файла)
}
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Пример вывода в случае ошибки деления на нудь:
Print a bords and a divisor:
12 20 0
The divisor cannot be 0.
Process returned -4 (0xFFFFFFFC) execution time : 3.937 s
Press any key to continue.
Квадратный корень из отрицательного числа
Все мы знаем, что нельзя взять квадратный корень из отрицательного числа. В данной задаче никто не говорил, что все числа будут положительными, поэтому попытки ситуация взятия квадратного корня из отрицательного числа вполне возможна. Чтобы исправить данный момент, добавим соответствующую проверку (39 строка):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
if (in == NULL){ // проверяем файл на корректность
printf("Open file error.\n"); // сообщаем пользователю об ошибке открытия файла
printf("\tThe file must be in the same directory with this programm\n"); // подсказываем, где должен находиться файл
printf("\tThe file must be named in.txt\n"); // подсказываем, как должен называться файл
return -2; // возвращаем -2 (если не установлен другой код ошибки открытия файла)
}
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
if (scanf("%d%d%d", &bord_1, &bord_2, &divisor) != 3){ // считываем 3 числа с клавиатуры
printf("Reading error.\n"); // сообщаем пользователю об ошибке чтения
return -1; // возвращаем -1 (если не установлен другой код ошибки чтения)
}
int temp = 0; // создаём временную переменную
if (bord_1 > bord_2){ // проверяем границы
temp = bord_1; // меняем их местами, если меньшая оказалась большей
bord_1 = bord_2;
bord_2 = temp;
}
if (divisor == 0){ // проверяем, что делитель не нуль
printf("The divisor cannot be 0.\n"); // сообщаем об ошибке
return -4; // возвращаем -4 (если не установлен другой код ошибки деления на нуль)
}
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
if (fscanf(in, "%d", &x) != 1){ // считываем скользящую переменную
printf("File reading error.\n"); // сообщаем пользователю об ошибке чтения из файла
return -3; // возвращаем -3 (если не установлен другой код ошибки чтения из файла)
}
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1){ // проверяем, что число х имеет остаток 1 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
if (x > 0){ // проверяем, что число положительное
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Некорректное определение остатка
В языке программирования С/С++ остатки могут быть отрицательными. Чтобы в этом убедиться достаточно выполнить следующую команду:
printf("%d\n", (-10) % 3);
В консоли будет выписана -1:
-1
Process returned 0 (0x0) execution time : 0.006 s
Press any key to continue.
Так как в математике принято, что остатки при делении на k могут быть от 0 до k-1 включительно, мы в программе пропускаем отрицательные числа, имеющие остаток 1 с точки зрения общепринятых математических понятий.
Рассмотрим таблицу остатков с точки зрения математики и языка программирования С/С++.
Поэтому, например, при взятии остатка от числа -10 при делении на число 3, наибольшее число, кратное 3, но не превосходящее -10 — это -12. Далее вычитаем из числа -10 число -12 и получаем искомый остаток 2.
Выражение | Остаток в математике | Остаток в С/С++ |
-10 % 3 | 2 | -1 |
-8 % 3 | 1 | -2 |
-11 % 3 | 1 | -2 |
-13 % 5 | 2 | -3 |
-12 % 7 | 2 | -5 |
Чтобы исправить это добавим соответствующую проверку (37 строка):
#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
if (in == NULL){ // проверяем файл на корректность
printf("Open file error.\n"); // сообщаем пользователю об ошибке открытия файла
printf("\tThe file must be in the same directory with this programm\n"); // подсказываем, где должен находиться файл
printf("\tThe file must be named in.txt\n"); // подсказываем, как должен называться файл
return -2; // возвращаем -2 (если не установлен код ошибки открытия файла)
}
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
if (scanf("%d%d%d", &bord_1, &bord_2, &divisor) != 3){ // считываем 3 числа с клавиатуры
printf("Reading error.\n"); // сообщаем пользователю об ошибке чтения
return -1; // возвращаем -1 (если не установлен другой код ошибки чтения)
}
int temp = 0; // создаём временную переменную
if (bord_1 > bord_2){ // проверяем границы
temp = bord_1; // меняем их местами, если меньшая оказалась большей
bord_1 = bord_2;
bord_2 = temp;
}
if (divisor == 0){ // проверяем, что делитель не нуль
printf("The divisor cannot be 0.\n"); // сообщаем об ошибке
return -4; // возвращаем -4 (если не установлен другой код ошибки деления на нуль)
}
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
if (fscanf(in, "%d", &x) != 1){ // считываем скользящую переменную
printf("File reading error.\n"); // сообщаем пользователю об ошибке чтения из файла
return -3; // возвращаем -3 (если не установлен другой код ошибки чтения из файла)
}
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1 || x % 3 == -2){ // проверяем, что число х имеет остаток 1 при делении на 3 или остаток -2 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
if (x > 0){ // проверяем, что число положительное
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x){ // сравниваем получившееся число с минимальным
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
}
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
Некорректное начальное значение минимума
Если сейчас запустить задачу, то в качестве ответа будет нуль, независимо от начальных данных, потому что начальное значение переменной, отвечающей за минимальное число определено некорректно. Данная переменная изначально равна нулю, а все полученные числа будут больше либо равны нуля, потому что это значения квадратного корня. Более того, так как требуется, чтобы число имело остаток 1 при делении на 3, то оно не может быть нулём.
Исходя из вышеописанного, все исследуемые числа, среди которых необходимо найти минимальное строго больше нуля. А начальное значение переменной, отвечающей за минимальное число равно нулю. При этом в процессе работы программы, мы ищем те числа, которые будут меньше данной переменной, чтобы обновить её. Таких чисел попросту не найдётся. Но мы не имеем ни малейшего представления о том, какие числа могут быть в файле. Если положить в качестве минимума 1000, то в случае, если все числа в файле будут такими, что после их деления на пользовательский делитель и взятия квадратного корня они будут больше, чем 1000, мы снова некорректно решим задачу. Поэтому необходимо положить значение в переменную, отвечающую за минимальное число непосредственно из файла, причём непосредственно то, которое прошло все необходимые проверки. Для этого необходимо создать переменную-индикатор того, что первое значение для минимума найдено, и добавить соответствующую проверку (31, 42-44 строки):#include <stdio.h> // подключаем библиотеку stdio.h
#include <stdlib.h> // подключаем библиотеку stdlib.h
#include <math.h> // подключаем библиотеку math.h
int main(){ // создаём главную функцию
FILE* in = fopen("in.txt", "r"); // открываем файл для чтения
if (in == NULL){ // проверяем файл на корректность
printf("Open file error.\n"); // сообщаем пользователю об ошибке открытия файла
printf("\tThe file must be in the same directory with this programm\n"); // подсказываем, где должен находиться файл
printf("\tThe file must be named in.txt\n"); // подсказываем, как должен называться файл
return -2; // возвращаем -2 (если не установлен код ошибки открытия файла)
}
int bord_1 = 0, bord_2 = 0, divisor = 0; // cоздаём три целочисленные переменные
printf("Print a bords and a divisor:\n"); // приглашаем пользователя на ввод
if (scanf("%d%d%d", &bord_1, &bord_2, &divisor) != 3){ // считываем 3 числа с клавиатуры
printf("Reading error.\n"); // сообщаем пользователю об ошибке чтения
return -1; // возвращаем -1 (если не установлен другой код ошибки чтения)
}
int temp = 0; // создаём временную переменную
if (bord_1 > bord_2){ // проверяем границы
temp = bord_1; // меняем их местами, если меньшая оказалась большей
bord_1 = bord_2;
bord_2 = temp;
}
if (divisor == 0){ // проверяем, что делитель не нуль
printf("The divisor cannot be 0.\n"); // сообщаем об ошибке
return -4; // возвращаем -4 (если не установлен другой код ошибки деления на нуль)
}
int x = 0; // создаём скользящую переменную
int min_x = 0; // создаём переменную для хранения минимального числа
bool the_first_value_of_min_is_found = false; // создаём переменную-индикаторы
for (int i = 0; i < 30; i++){ // запускаем цикл for на 30 проходов
if (fscanf(in, "%d", &x) != 1){ // считываем скользящую переменную
printf("File reading error.\n"); // сообщаем пользователю об ошибке чтения из файла
return -3; // возвращаем -3 (если не установлен другой код ошибки чтения из файла)
}
if (bord_1 < x && x < bord_2){ // проверяем, что число х лежит в граничном интервале
if (x % 3 == 1 || x % 3 == -2){ // проверяем, что число х имеет остаток 1 при делении на 3 или остаток -2 при делении на 3
x /= divisor; // делим на делитель, введённый пользователем
if (x > 0){ // проверяем, что число положительное
x = sqrt(x); // берём квадратный корень от получившегося частного
if (x < min_x || !the_first_value_of_min_is_found){ // сравниваем получившееся число с минимальным, а также проверяем индикатор
min_x = x; // кладём получившееся число в переменную, хранящую минимальное число
the_first_value_of_min_is_found = true; // переключаем индикатор
}
}
}
}
}
printf("min: %d", min_x); // выписываем минимальное число
fclose(in); // закрываем файл
return 0; // возвращаем 0 - успех
}
IVЗаключение
И только после всех этих манипуляций отладки данная программа будет работать корректно.
Таким образом, хороший программный код должен учитывать всевозможные исходы событий и варианты поведения пользователей, чтобы минимизировать количество исключений и ошибок в работе программы.
Домашнее задание:
1Задача №1.
Пользователь вводит целое число. Необходимо выписать на экран его сумму цифр.