Основы программирования. Учебное пособие-1
.pdfОсмысленный выбор имен переменных поможет сделать программу самодокументированной, то есть позволит уменьшить количество необходимых комментариев.
Первая буква идентификатора, представляющего простую переменную, должна быть набрана в нижнем регистре. Кроме того, в ряде случаев идентификаторам, которые начинаются с заглавной буквы, и идентификаторам, в которых используются исключительно заглавные буквы, придается особый смысл.
Имена переменных, состоящие из нескольких слов, помогут сделать программу более легкой для восприятия. Однако следует избегать
слитного написания отдельных слов, как, например, totalcommissions. Лучше разделять слова при помощи символа подчеркивания total_commissions, или, если вы все же хотите писать слова слитно, начинайте каждое слово после первого с заглавной буквы, например,
totalCominissions.
Разделяйте объявления и исполняемые операторы одной пустой строкой,отмечая,такимобразом,гдекончаютсяобъявления, агденачинаются исполняемые операторы.
Оставлять пробелы с каждой стороны двухместной операции. Это выделит операции и придаст программе более ясный вид.
Смещать вправо операторы в теле if.
Помещать в программе пустую строку над и под каждым опера-
тором if для облегчения восприятия программы.
В каждой строке программы должно быть не более одного опера-
тора.
Длинные операторы можно записывать в несколько строк. Если оператор необходимо разбить на несколько строк, старайтесь делать это осмысленно (скажем, после запятой в разделенном запятыми списке). Если оператор разбит на две или большее число строк, смещайте вправо все строки, следующие за первой.
При написании выражений, содержащих много операций, пользуйтесь таблицей старшинства операций. Следите за тем, чтобы операции в выражении выполнялись в правильном порядке. Если вы не уверены в порядке выполнения операций в сложном выражении, используйте скобки для принудительного задания нужного порядка, так же как делаете это в алгебраических выражениях. Не следует также забывать, что некоторые операции в C, например операция присваивания ( = ), ас-
социативны справа налево, а не наоборот.
41
Переносимость программ
Рекомендуетсяиспользоватьидентификаторысколичествомсимволов, равным или меньшим 31. Это гарантирует переносимость и даст возможность избежать некоторых трудно обнаружимых ошибок в процессе программирования.
Структурная разработка программ
Распространенные ошибки
Пропуск одной или обеих фигурных скобок, ограничивающих составной оператор.
Помещение точки с запятой после условия в условной структуре
приводит к логической ошибке в структурах if с одним выбором и к синтаксической ошибке в структурах if с двойным выбором.
Отсутствие в теле структуры while действия, которое в конечном итоге приводит к тому, что условие в структуре while становится ложным. Обычно такая структура повторения никогда не будет завершена – возникает ошибка, называемая «бесконечным циклом».
Написание ключевого слова while с W в верхнем регистре в виде While (не забывайте, что C является языком программирования, различающим регистр). Все зарезервированные ключевые слова языка C, такие как while, if и else, содержат только символы нижнего регистра.
Если счетчик или итоговая сумма не инициализированы, результаты работы вашей программы будут, по всей вероятности, некорректными. Это является примером логической ошибки.
Выборвкачествеконтрольногозначениятакого,котороеявляется допустимым значением данных.
Попытка деления на ноль, являющаяся причиной фатальной ошибки.
Указание точности для спецификации преобразования в строке управления форматом функции scanf является ошибочным. Точность
используется только в спецификациях преобразования функции printf.Предположение, что числа с плавающей точкой будут представлены в памяти компьютера с абсолютной точностью, может привести к неправильным результатам. На большинстве компьютеров числа с пла-
вающей точкой представляются только приблизительно.
Попытка использовать операцию инкремента или декремента с выражением, отличным от простого имени переменной вроде ++(х + 1), является синтаксической ошибкой.
42
Хороший стиль
Единообразное применение разумных соглашений об отступах значительно повышает удобочитаемость программы. Используйте для отступа табуляцию фиксированного размера приблизительно в полсантиметра или три пробела.
Псевдокод часто используется для «продумывания» программы в процессе ее проектирования. Затем программа на псевдокоде преобразуется в программу на C.
Делайте отступы для обеих групп операторов структуры if/else.
При наличии нескольких уровней отступов отступы для каждого уровня должны содержать одно и то же количество дополнительных пробелов.
Некоторые программисты предпочитают вводить начальную и завершающую фигурные скобки в составных операторах до начала ввода операторов внутри фигурных скобок. Это помогает избежать пропусков одной или обеих фигурных скобок.
Инициализируйте счетчики и итоговые суммы.
При выполнении деления на выражение, значение которого может быть нулем, явно осуществляйте проверку этого случая и обрабатывайтеегосоответствующимобразом(например,выводитесообщение об ошибке) и не допускайте возникновения фатальной ошибки.
В цикле, управляемом контрольным значением, подсказки для запросавводаданныхдолжныявнонапоминатьпользователю,чемуравно это значение.
Не проверяйте на равенство значения с плавающей точкой.
Унарныеоперацииследуетпомещатьнепосредственнорядомсих операндами без разделительных пробелов.
Стандартом ANSI в общем случае не специфицируется порядок, в котором будут оцениваться операнды той или иной операции. Поэтому программист должен избегать команд с операциями инкремента или декремента, в которых некоторая переменная подвергается их воздействию более одного раза.
Повышение эффективности
Инициализация переменных при их объявлении уменьшает время выполнения программы.
Выражениесоперациейприсваивания(например,с += 3)компилируется быстрее, чем эквивалентное раскрытое выражение ( с = с + 3 ),
43
поскольку переменная с в первом выражении оценивается только один раз, в то время как во втором выражении она оценивается дважды.
Многие из приведенных здесь советов по повышению эффективности приводят к незначительным на первый взгляд улучшениям, поэтому у читателя может возникнуть искушение игнорировать их. Дело в том, что заставить программу выполняться значительно быстрее может только совокупный эффект от всех этих мер по повышению эффективности. Кроме того, значительное улучшение достигается при внесениивродебынезначительногоусовершенствованиявцикл,которыйможет повторяться большое количество раз.
Методические замечания
Составной оператор может быть помещен в любое место программы, где может стоять простой оператор.
Подобно тому, как составной оператор можно помещать всюду, где может быть помещен простой оператор, также возможно не помещать там никакого оператора вообще, то есть использовать пустой оператор. Пустой оператор представляется помещением точки с запятой ( ; ) на место, обычно занимаемое некоторым оператором.
Каждое уточнение, так же как и сам верхний уровень, представляет собой полную спецификацию алгоритма; меняется только уровень детализации.
Многиепрограммылогическимогутбытьразделенынатриэтапа: этап инициализации, на котором происходит инициализация переменных программы; этап обработки, на котором происходит ввод данных
исоответствующая настройка переменных программы; этап завершения работы программы, на котором происходит вычисление и вывод окончательных результатов.
Программист завершает процесс нисходящего пошагового уточнения в тот момент, когда алгоритм на псевдокоде специфицирован достаточно подробно для того, чтобы программист мог преобразовать псевдокод в программу на C. Реализация программы на C в этом случае обычно не вызывает затруднений.
Опыт показывает, что наиболее трудной частью решения задачи на компьютере является разработка алгоритма ее решения. Как только соответствующий алгоритм построен, процесс написания работающей программы на C обычно не вызывает затруднений.
Многие программисты пишут программы, никогда не пользуясь никакими инструментами разработки программ типа псевдокода.
44
Они полагают, что, поскольку их конечной целью является решение задачи на компьютере, написание псевдокода только задерживает производство конечного продукта.
Управление программой
Хороший стиль
Управляйте циклами со счетчиком с помощью целочисленных значений.
Делайте отступы для операторов в теле каждой управляющей структуры.
Помещайте пустую строку до и после каждой управляющей структуры большого размера для выделения ее в тексте программы.
Слишком большое число уровней вложенности может сделать программу трудной для понимания. Пытайтесь избегать использования более трех уровней вложенности.
Сочетание междустрочного интервала до и после управляющих структур и отступов в теле управляющих структур в пределах их заголовков придает программам двумерный вид, который значительно повышает их удобочитаемость.
Использование конечного значения в условии структур while или for и операции отношения <= помогает избежать ошибок сдвига счетчика. Например, для цикла, используемого для вывода значений от 1 до
10, условием продолжения цикла должно быть counter <= 10, а не counter < 11 или counter < 10.
Помещайте в разделы инициализации и приращения структуры for только выражения, включающие управляющие переменные. Манипуляции с другими переменными должны производиться либо перед циклом (если они выполняются только один раз как операторы инициализации), либо в теле цикла (если они выполняются по одному разу за повторение как операторы инкремента или декремента).
Хотя значение управляющей переменной может быть изменено в
теле цикла for, это может привести к труднообнаружимым ошибкам; лучше его не изменять.
Хотя операторы, предшествующие структуре for, и операторы, содержащиеся в ее теле, часто могут быть объединены в заголовке структуры for, избегайте этого, поскольку это делает программу более трудной для чтения.
45
Ограничивайте, если это возможно, размер заголовков управляющих структур одной строкой.
Не забывайте о блоке default в операторах switch. Случаи, явно
непроверяемые в структуре switch, игнорируются. Блок default помогает избежать этого, фокусируя внимание программиста на необходимости обрабатывать необычные условия. Существуют ситуации, когда никакой обработки по умолчанию (в блоке default) не требуется.
Хотя предложения case и предложение default в структуре switch могут появляться в произвольном порядке, хорошим стилем программирования считается размещение предложения default в конце структуры.
Втомслучае,когдавструктуреswitch предложениеdefault ука-
зано последним, оператора break для него не требуется. Однако некоторые программисты вводят этот break для ясности и единообразия с метками case.
Не забывайте о возможной необходимости обработки символов новой строки во входном потоке при считывании символов по одному.
Некоторые программисты всегда включают фигурные скобки в структуру do/while, даже если они не являются необходимыми. Это по-
могает отличить структуру do/while, содержащую один оператор, от структуры while.
Некоторым программистам кажется, что операторы break и continue нарушают нормы структурного программирования. Поскольку результаты действия этих операторов могут быть достигнуты структурными средствами, эти программисты не пользуются операто-
рами break и continue.
Когда в выражение равенства входят переменная и константа, как
вслучае х == 1, некоторые программисты предпочитают записывать в
этом выражении константу слева, а имя переменной справа от знака = в качестве защитной меры от логической ошибки, возникающей при случайной замене программистом операции == операцией =.
Повышение эффективности
В случаях, ориентированных на эффективность выполнения, когда нужно экономить память или необходимо быстродействие, может оказаться предпочтительным использование целых чисел меньших размеров.
Операторы break и continue при условии их надлежащего использования выполняются быстрее соответствующих структурных методов.
46
В выражениях, включающих операцию &&, располагайте условие, ложность которого наиболее вероятна, крайним слева. В выражениях, включающих операцию ||, располагайте условие крайним слева, истинность которого наиболее вероятна. Это может уменьшить время выполнения программы.
Переносимость программ
Комбинации клавиш для ввода EOF (конца файла) являются системно зависимыми.
Проверка значения на равенство символической константе EOF, а не числу 1 делает программы более переносимыми. Стандарт ANSI требует, чтобы EOF являлась отрицательным целым числом (но необязательно 1). Таким образом, на различных вычислительных системахEOF может иметь различные значения.
Поскольку тип int различается по размеру от системы к системе, пользуйтесьцелымитипаlong,есливырассчитываетеобрабатыватьцелые числа вне диапазона ±32767 и хотели бы иметь возможность выполнять программу на нескольких различных компьютерных системах.
Методические замечания
Существуетопределенноепротиворечиемеждуразработкойкачественного программного обеспечения и разработкой программного обеспечения, работающего наиболее эффективно. Часто одна из этих целей достигается за счет другой.
Функции
Распространенные ошибки
Приотсутствиив программезаголовочногофайламатематикиобращение программы к функциям математической библиотеки может приводить к странным результатам.
Если тип возвращаемого значения в определении функции опущен, то это вызовет синтаксическую ошибку в том случае, если прототип функции определяет, что возвращаемый тип не является целым
( int ).
Если функция должна возвращать некоторое значение, а в функции опущен оператор возврата, то это может привести к непредвиден-
ным ошибкам. Стандарт ANSI гласит, что в этом случае результат не определен.
47
Возврат значения из функции, для которой возвращаемый тип был объявлен как void, вызывает синтаксическую ошибку.
Запись float х, у вместо float х, float у при объявлении параметровфункции,относящихсякодномутипу.Такоеобъявлениепа-
раметров, как float х, у, фактически присвоило бы параметру у тип int, поскольку int принимается по умолчанию.
Символ ; после правой круглой скобки, закрывающей список параметров в определении функции, является синтаксической ошибкой.
Повторное определение параметра функции как локальной переменной внутри функции – синтаксическая ошибка.
Определение функции внутри другой функции – синтаксическая ошибка.
Пропуск точки с запятой в конце прототипа функции вызывает синтаксическую ошибку.
Преобразование более высокого типа данных в иерархии возведения к более низкому типу может изменить значение данных.
Пропуск прототипа функции вызывает синтаксическую ошибку,
если функция возвращает тип, отличный от int, а определение функции появляется в программе после вызова функции. В других случаях пропуск прототипа функции может вызвать ошибку во время выполнения программы или привести к непредвиденному результату.
Использование srand вместо rand для генерации случайных чисел.
Использование нескольких спецификаторов класса памяти для идентификатора. К данному идентификатору может применяться только один спецификатор класса памяти.
Случайное объявление во внутреннем блоке имени идентификатора, которое уже используется во внешнем блоке, в то время как программист на самом деле хотел, чтобы идентификатор внешнего блока был доступен для внутреннего блока.
Отсутствие возврата значения из рекурсивной функции, когда он необходим.
Пропуск основного случая или некорректная запись шага рекурсии приводит к тому, что рекурсия не сходится на основном случае. Это вызывает бесконечную рекурсию и приводит в конечном счете к переполнению памяти. Аналогией этому является проблема бесконечного цикла в итеративном (не рекурсивном) решении. Бесконечная рекурсия может быть также вызвана неправильным вводом значения.
Создание программы, которая зависит от порядка вычисления
значений операндов операций, не являющихся &&, ||, ? : и операцией-
48
запятой ( , ), может привести к ошибкам, поскольку компиляторы могут вычислять значения операндов не в том порядке, который ожидал программист.
Случайный вызов нерекурсивной функцией самой себя или непосредственно, или косвенно через другую функцию.
Хороший стиль
Освойтесь с широким набором функций стандартной библиотеки
ANSI C.
При использовании функций математической библиотеки включите в программу заголовочный файл математики с помощью дирек-
тивы препроцессора #include <math.h>.
Вставляйте пустую строку между определениями функций, чтобы отделить функции и улучшить тем самым читаемость программы.
Хотя опущенный возвращаемый тип по умолчанию рассматрива-
ется как int, всегда задавайте возвращаемый тип явным образом. Однако для функции main возвращаемый тип обычно опускается.
Включайте тип каждого параметра в список параметров функции даже в том случае, если данный параметр относится к типу int, принятому по умолчанию.
Хотя это и не является ошибкой, не используйте одни и те же имена для аргументов, переданных функции, и соответствующих параметров в определении функции. Это поможет избежать неоднозначности.
Выбор осмысленных имен функций и параметров делает программы более удобочитаемыми и помогает избежать чрезмерного использования комментариев.
Включите в программу прототипы функций для всех функций, чтобы воспользоваться преимуществами проверки типов C. Исполь-
зуйте директивы препроцессора #include, чтобы получить прототипы функцийстандартнойбиблиотекииз заголовочныхфайловсоответствующих библиотек. Также используйте #include, чтобы включить заголовочные файлы, содержащие ваши собственные прототипы функций или прототипы, используемые членами вашей рабочей группы.
Имена параметров иногда включаются в прототип функции с целью документирования. Компилятор игнорирует эти имена.
Переменные, использующиеся только в определенной функции, должны быть объявлены локальными переменными этой функции, а не внешними переменными.
49
Не объявляйте имен переменных, которые скрывают имена во внешних областях действия. Можно просто избегать любого дублирования имен идентификаторов в программе.
Переносимость программ
Использование функций стандартной библиотеки ANSI C делает программы более переносимыми.
Программы, которые зависят от порядка вычисления значений
операндов операций, не являющихся &&, ||, ?: и операцией-запятой ( , ), могут функционировать по-разному на системах с различными компиляторами.
Повышение эффективности
Автоматическое хранение способствует экономии памяти, поскольку автоматические переменные существуют только тогда, когда они необходимы. Они создаются при запуске функции, в которой они объявлены, и уничтожаются, когда происходит выход из функции.
Избегайте рекурсивных программ, подобных вычислению чисел Фибоначчи, которые приводят к экспоненциальному «взрыву» рекурсивных вызовов.
Избегайте использования рекурсии в ситуациях, требующих производительностипрограммы.Рекурсивныеобращениятребуютвремени
ирасходуют дополнительную память.
Программа, интенсивно использующая функции, по сравнению с монолитной (т. е. состоящей из одной части) программой без функций имеет потенциально большое число вызовов, а это увеличивает процессорное время программы. Но монолитные программы трудны для написания, тестирования, отладки, сопровождения и развития.
Методические замечания
Не изобретайте колесо. Если есть возможность, используйте функции стандартной библиотеки ANSI C вместо того, чтобы создавать новые функции. Это уменьшает время разработки программы.
Впрограммах,содержащихбольшоечислофункций,main должна бытьреализованакакгруппаобращенийкфункциям,выполняющимосновную часть работы.
Каждая функция должна ограничиваться выполнением одной, точно определенной задачи, а имя функции должно отражать смысл данной задачи. Это облегчает абстракцию и способствует многократному использованию программного кода.
50
