Краткое содержание
Этот урок открывает восьмой и последний раздел курса, посвящённый большим программам. Он объясняет назначение раздела: организовывать, проверять на корректность и отлаживать программы, которые уже не помещаются ни в один файл, ни в голову; представляет три его урока и нить, которая их связывает и замыкает круг, открытый в первом уроке курса (специфицировать, строить, проверять); напоминает, что раздел считает известным, а именно весь курс; формулирует задачу, которую решает раздел, задачу программы из одного файла, которая работает на тех данных, на которых её тестировали, и умалчивает об ошибке на границе своей спецификации; предвосхищает различие между тестированием и доказательством; и показывает, куда ведёт раздел: к итоговому проекту и к курсу «Программирование на C++ II».
Цели обучения
- Описать последовательность содержания раздела и объяснить, что каждый урок даёт следующему.
- Вспомнить на примере различие между объявлением и определением функции, контракт, который её специфицирует, и этапы (компиляцию и компоновку), превращающие несколько файлов в программу.
- Предвосхитить на примере различие между тестированием программы на нескольких случаях и доказательством того, что она корректна для всех допустимых.
- Распознавать, что ожидается уметь делать по окончании раздела и курса, и отличать это от того, что будет изучаться в курсе «Программирование на C++ II».
Входная проверка
Прежде чем проходить раздел, полезно определить исходный уровень. Эта проверка не оценивается: она направляет занятия. Первые семь вопросов касаются того, что раздел считает известным (объявление и определение, предусловие, контракт функции из раздела 6, сообщения компоновщика, случаи двоичного поиска, таблица трассировки и чтение за пределами вектора); следующие три в порядке уроков касаются центральных идей раздела, и в них ответ «Пока не знаю» честен. Каждый ответ объясняет суть и называет урок, который её разбирает; те же умения снова измеряются в уроке итогов.
Перед началом раздела оцени свою отправную точку. Это короткая самопроверка: она не оценивается, а результат хранится только в твоём браузере.
Вопрос 1Из предыдущих разделовЧто делает строка
int gcd(int a, int b);, написанная передmain?Путает объявление и определение: без тела выполнять нечего, и ни одна функция не выполняется оттого, что она написана, а только когда её вызывают.Путает объявление с вызовом: вызов не содержит типов параметров.Не учитывает, что компилятору, чтобы проверить вызов, достаточно сигнатуры; отсутствие тела, если его никто не напишет, обнаруживает компоновщик, а не компилятор.Объявления достаточно, чтобы компилятор проверил каждый вызов; определение, с телом, может находиться ниже или в другом файле. Весь урок Несколько файлов и собственные библиотеки опирается на это разделение: заголовочный файл объявляет, файл реализации определяет.Повторить: Функции
Вопрос 2Из предыдущих разделовУ функции
toBase(n, base)предусловие «n >= 0и2 <= base <= 10». Кто должен его обеспечить?Путает предусловие с валидацией входных данных: валидация есть задача границы программы, а не каждой функции.Думает, что комментарии проверяются; компилятор их отбрасывает.Не видит, что контракт распределяет обязанности; без него никто не знает, кто ошибся, когда что-то ломается.Предусловие есть часть контракта, которая обязывает вызывающего; взамен функция гарантирует своё постусловие. Если предусловие нарушено, функция ничего не обещает. Урок Тестирование программ научит контролировать его с помощьюassert, пока программа разрабатывается.Повторить: Функции
Вопрос 3Из предыдущих разделовФункция
binarySearch(values, target)из урока 6.4 возвращает позициюtargetилиvalues.size(), если его нет. Каково её предусловие?С пустым вектором цикл не делает ни одной итерации, и функция возвращает 0, то естьvalues.size(): этот случай покрыт постусловием.Функция сообщает об отсутствии; требовать присутствия значило бы сделать её бесполезной.При повторах функция возвращает одну из позиций значения, что постусловие допускает.Каждая итерация отбрасывает половину интервала, сравнивая с центральным элементом, а это законно, только если вектор упорядочен. На неупорядоченных данных функция без всякого предупреждения отвечает что-то ложное. Предусловие записывается в контракте, где его видит вызывающий.Повторить: Поиск и сортировка
Вопрос 4Из предыдущих разделовПрограмма компилируется без ошибок, а при сборке исполняемого файла появляется
undefined reference to `tetrahedral(int)'. Какой этап не удался?Не различает сообщения компилятора, которые указывают файл и строку, и сообщения компоновщика, которые указывают объектные файлы и имена.Программа так и не появилась: без исполняемого файла нет выполнения.Отсутствующий#includeдаёт при компиляцииwas not declared in this scope, а не это сообщение.Компилятор принял каждый вызов, потому что видел объявление; компоновщик искал определение во всех объектных файлах и в библиотеках и не нашёл его. Урок Несколько файлов и собственные библиотеки объясняет обычные причины: файл, который не скомпилировали, или объявление, скопированное с другим типом.Повторить: Программы, компиляторы и C++
Вопрос 5Из предыдущих разделовКакой из этих наборов случаев лучше всего испытывает двоичный поиск?
Путает количество с охватом: никогда не испытывает ни отсутствие, ни границы, где обычно и прячутся ошибки.Случай, который уже прошёл, имеет меньше всего шансов обнаружить что-то новое.Их бесконечно много или, в пределах типа, астрономически много: тестирование требует выбора.Каждый случай задействует отдельную границу полуинтервала : интервал, пустой с самого начала, интервал, сократившийся до одного элемента, крайние элементы и три способа отсутствовать. Выбирать случаи по спецификации, по областям и границам, есть тема урока Тестирование программ.Повторить: Поиск и сортировка
Вопрос 6Из предыдущих разделовЧему равны
aиbпослеint a{0}; int b{1}; for (int i{0}; i < 4; ++i) { const int t{a + b}; a = b; b = t; }?Насчитывает лишнюю итерацию: приi < 4тело выполняется четыре раза, а не пять.Насчитывает на одну итерацию меньше: это симметричная ошибка на единицу.Забывает, чтоtхранит сумму, вычисленную до того, как изменилосьa: на последней итерацииaполучает 3 изb, аbполучает эту сумму, 5, а не новое значениеa.Таблица трассировки, итерация за итерацией, даёт : четыре итерации, сiот 0 до 3. Это та таблица, которую отладчик заполняет за того, кто программирует, и урок Отладка программ пользуется ею, чтобы судить о состоянии в каждой точке останова.Повторить: Циклы
Вопрос 7Из предыдущих разделовПочему
std::vector<int> v{1, 2}; std::println("{}", v[2]);останавливается с опциями курса, а при компиляции только с-O2, без санитайзеров, может напечатать произвольное число?Думает, что оптимизация исправляет ошибки; она лишь убирает контроль и предполагает, что программа не нарушает стандарт.Переворачивает эффект: программу останавливает контроль библиотеки без оптимизации.Это ошибка на единицу в индексах: индексы вектора из двух элементов равны 0 и 1.Индекса 2 в векторе из двух элементов нет. Что наблюдается, зависит от компилятора и от опций, и поэтому тесты запускают со всеми включёнными средствами контроля, как учит урок Тестирование программ.Повторить: Векторы
Вопрос 8Заголовочный файл с защитой от повторного включения определяет (а не только объявляет)
double scale(double x)безinline, и его включаютmain.cppиreport.cpp. Что происходит при сборке программы?Думает, что защита действует между файлами; она действует внутри каждой единицы трансляции.Путает компилируемый файл со всей программой: внутри каждого файла определение одно.Это честный ответ: урок 8.1 показывает это на настоящем сообщении компоновщика.Защита не даёт заголовочному файлу войти дважды в один и тот же файл, но каждый.cppкомпилируется отдельно и получает свою копию определения: компоновщик находит две. Это правило одного определения из урока Несколько файлов и собственные библиотеки.Повторить: Несколько файлов и собственные библиотеки
Вопрос 9Функция проходит пятьсот тщательно подобранных автоматических тестов. Что можно утверждать?
Путает обилие с доказательством; ошибка на границе, которой не коснулся ни один случай, переживёт любое число внутренних случаев.Случаи не являются случайной выборкой из возможных ошибок, и частота успехов не измеряет вероятность того, что программа верна.Это честный ответ: урок 8.2 начинается с этого вопроса.Тест может показать присутствие ошибки, но никогда не её отсутствие: чтобы утверждать корректность для всех случаев, нужно рассуждение, как инвариант из урока 4.3, а не список случаев. Хорошо подобранные тесты делают маловероятным, что распространённая ошибка пройдёт незамеченной, и поэтому так ценны. Урок Тестирование программ развивает это различие.Повторить: Тестирование программ
Вопрос 10Точка останова стоит на строке
total += k;внутри цикла, и программа останавливается там в третий раз. Что означает выделенная строка?Путает остановку с уже сделанным шагом.Путает место, где останавливается программа (его выбирает тот, кто отлаживает), с причиной сбоя.Это честный ответ: урок 8.3 разбирает точки останова и шаги.Отладчик останавливается перед отмеченной строкой; значениеtotal, которое он показывает, есть значение после двух предыдущих итераций. Если это перепутать, таблицы трассировки сдвигаются на одну строку. Урок Отладка программ развивает это.Повторить: Отладка программ
Загрузка содержимого…