
- •Часть 1
- •1. Основы программирования на языке Си
- •1.1. Структура программы на языке Си.
- •1.2. Структура простейшей одномодульной программы
- •1.2.1. Комментарии в программе на языке Си
- •1.2.2. Начальные сведения о препроцессоре
- •1.2.3. Определение функции main().
- •1.2.4. Пример простейшей программы
- •1.3. Алфавит, синтаксис и семантика
- •1.4. Алфавит языка Си
- •1.5. Понятие о типе
- •1.6. Система типов языка Си
- •1.7. Понятие об объекте
- •1.8. Лексемы
- •1.9. Зарезервированные слова
- •1.10. Идентификаторы
- •1.11. Литералы
- •1.11.1. Целочисленные литералы
- •1.11.2. Литерал вещественного типа
- •1.11.3. Символьные литералы
- •1.11.4. Строковый литерал
- •1.12. Переменные
- •1.13. Символические константы в языке Си
- •1.14. Операторы, выражения и инструкции. Общие сведения.
- •1.14.1. Классификация операторов
- •1.14.2. Приоритет и ассоциативность операторов.
- •1.14.3. Побочные эффекты при вычислении выражений
- •1.14.4. Порядок вычисления выражений
- •1.15. Арифметические операторы и выражения
- •1.15.1. Унарные операторы
- •1.15.2. Бинарные операторы
- •1.15.3. Преобразования типа при выполнении бинарных операторов
- •1.15.3.1. Автоматическое преобразование типов
- •1.15.3.2. Явное преобразование типа
- •1.15.4. Математические функции
- •1.16. Оператор присваивания и инструкция присваивания
- •1.16.1. Простой оператор присваивания
- •1.16.2.Множественное присваивание
- •1.16.3. Составной оператор присваивания
- •1.16.4. Преобразование типа при присваивании
- •1.17. Начальные сведения об указателях. Выходные параметры функции
- •1.18. Принятие решений и логические величины. Операторы отношения и сравнения на равенство
- •1.18.1. Логические операторы
- •1.18.2. Поразрядные операторы
- •1.19. Условные выражения (оператор ?:)
- •1.20. Оператор запятая
- •1.21. Оператор sizeof
- •1.22.4. Инструкция return
- •1.23. Составная инструкция
- •1.24. Инструкция if else
- •1.24.1. Полная и сокращенная формы инструкции if
- •1.24.2. Вложенные инструкции if
- •1.25. Инструкция switch
- •1.25.1. Синтаксис инструкции switch
- •1.25.2. Использование инструкции switch
- •1.26. Функциональные компоненты цикла
- •1.27. Арифметические и итерационные циклы
- •1.27.1. Задачи, приводящие к арифметическим циклам
- •1.27.2. Задачи, приводящие к итерационным циклам
- •1.28. Циклические управляющие инструкции
- •1.29. Цикл, управляемый инструкцией for
- •2. Примеры решенных задач
- •2.1. Линейные вычислительные процессы
- •2.2. Организация разветвлений
- •2.2.1. Простейшие разветвления
- •2.2.2. Многовариантные разветвления
- •2.2.2.1. Использование переключателя switch
- •2.2.2.2. Использование вложенных инструкций if else if
- •2.2.2.3. Использование вложенных инструкций if ... If
- •2.2.2.4. Использование сокращённых форм инструкции if
- •2.2.3. Рекомендации по программированию
- •2.3. Организация циклов
- •2.3.1. Простые циклы
- •2.3.2. Циклы и разветвления
- •2.3.2.1. Разветвление до цикла
- •2.3.2.2. Разветвление внутри цикла
- •2.3.2.3. Разветвление после цикла
- •2.3.2.4. Циклы в ветвях разветвлений.
- •2.3.3. Рекомендации по программированию
- •Содержание
- •1. Основы программирования на языке Си
- •Литература
2.2.3. Рекомендации по программированию
Рекомендации, содержащиеся в данном разделе, можно разделить на две категории. К первой из них можно отнести рекомендации общего характера, а ко второй – рекомендации, связанные с организацией разветвлений.
1. Не останавливайтесь на первом полученном решении.
Примеры, приведённые выше, свидетельствуют о том, что на практике большинство задач допускает много вариантов решений. Поэтому после получения первого решения следует попытаться его улучшить. При этом в первую очередь желательно стремиться к улучшению структуры решения. Поясним это дополнительным примером.
Постановка задачи.
Вычислить значение величины “y”, заданной следующим образом
max(a,
b) при a<0,
у =
min(a, b) при a≥0.
Решение.
Первый вариант.
Форма, в которой представлена постановка задачи, “навязывает” определённый алгоритм её решения. В целом решение является разветвлением на два направления, что требует использования инструкции if...else. Каждая из ветвей этого разветвления требует организовать разветвление на два направления. Это необходимо один раз для вычисления max(a, b), а второй раз – min(a, b). Таким образом, необходимые действия можно реализовать с помощью вложенных инструкций if...else. Причём внешняя инструкция if...else содержит внутренние инструкции в обеих своих ветвях. Фрагмент программы, реализующей это разветвление, представлен ниже
if(a < 0)
if(a > b)
y = a;
else
y = b;
else
if(a < b)
y = a;
else
y = b;
К недостаткам полученного решения относится наличие вложенных инструкций if...else, ухудшающих читабельность программного кода.
Особенность полученного решения состоит в том, что оно имеет простую внешнюю структуру и сложную внутреннюю. Действительно, на уровне всей программы разветвление представлено единственной инструкцией, которая имеет сложную внутреннюю структуру.
Выполним, так называемый, рефакторинг, который состоит в модернизации существующего программного кода с целью улучшения его структуры.
Второй вариант.
Анализ программного кода, приведённого выше, показывает, что алгоритмы вычисления max(a, b) и min(a, b) могут быть вынесены из внешней инструкции if ... else в линейную часть программы. Математическая формализация предлагаемой операции имеет следующий вид
mn = min(a, b),
mx = max(a, b),
mx при a
< 0,
y =
mn при a ≥ 0.
Программный код, соответствующий такому подходу к решению рассматриваемой задачи, будет иметь следующий вид
if(a < b)
mn = a;
else
mn = b;
if(a >= b)
mx = a;
else
mx = b;
if(a < 0)
y = mx;
else
y = mn;
Сравнивая программный код нового решения с исходным программным кодом, можно сделать вывод о его лучшей читабельности.
Третий вариант.
Рассмотрим возможность продолжить рефакторинг. Основанием для этого является похожесть алгоритмов вычисления max(a,b) и min(a,b). Можно попытаться объединить эти алгоритмы. В результате получим программный код, приведённый ниже.
if(a < b) {
mn = a ;
mx = b ;
}
else
{
mn = b ;
mx = a ;
}
if ( a < 0 )
y = mx ;
else
y = mn ;
2. Критически относитесь к возможности с помощью одного единственного алгоритма решить несколько задач.
Выше был рассмотрен пример вполне допустимого объединения решения двух задач (max(a, b) и min(a, b)) в одном алгоритме. Однако такое объединение скорее можно считать исключением, чем правилом. Часто подобное объединение приводит к решениям, которые вряд ли могут считаться удачными. В качестве примера приведем решение задачи, содержащееся в одном из задачников.
Постановка задачи.
Даны три числа a, b, c. Присвоить переменной m1 наименьшее из них, переменной m2 – среднее, переменной m3 – наибольшее.
Решение.
Решение, содержащее на внешнем уровне только одну инструкцию if ... else, имеет следующий вид.
if(a < b)
if(a > c)
{
m1 = c;
m2 = a;
m3 = b;
}
else if(b < c)
{
m1 = a;
m2 = b;
m3 = c;
}
else
{
m1 = a;
m2 = c;
m3 = b;
}
else if(a < c)
{
m1 = b;
m2 = a;
m3 = c;
}
else if(b < c)
{
m1 = b;
m2 = c;
m3 = a;
}
else
{
m1 = c;
m2 = b;
m3 = a;
}
По нашему мнению полученное решение нельзя признать удачным. Программный код трудно читать.
При решении этой задачи предпочтительнее идти по другому пути. Из постановки задачи следует, что она содержит в себе три подзадачи:
-
вычисление max(a, b, c),
-
вычисление min(a, b, c),
-
вычисление среднего из трёх чисел.
Поэтому решение всей задачи целесообразно представлять как простую совокупность трёх отдельных решений. Затем, в случае необходимости выполнить рефакторинг полученного решения. Оставляем такое решение задачи в качестве самостоятельной работы.
Приводимые далее рекомендации непосредственно относятся к организации разветвлений.
3. При программировании разветвлений на два направления стандартным решением является инструкция if ... else.
4. При программировании многовариантных разветвлений предпочтительнее использовать переключатель switch и вложенные цепочки if … else… if.
5. Программный код обязательно должен быть структурирован. Разветвление следует выделять с помощью отступов.