- •Министерство образования Республики Беларусь
- •1. Что такое программа на языке программирования
- •2. Общее знакомство с языком с
- •3. Структура простой программы на языке с
- •4. Что такое программа на языке с
- •5. Представление информации и типы данных в языке с
- •6. Константы
- •7. Переменные
- •8. Элементарный вВод и вывод информации
- •9. Выражения и операции
- •9.1. Арифметические операции
- •9.2. Операция изменения знака
- •9.3. Операции инкремента и декремента
- •9.4. Операция присваивания
- •9.6. Поразрядные логические операции
- •9.8. Логические операции и операции отношения
- •9.9. Условная операция «? :»
- •9.10. Операция последовательного вычисления
- •9.11. Операция определения требуемой памяти в байтах sizeof
- •9.12. Операция приведения типа (type)
- •10. Операторы управления вычислительным процессом
- •10.1. Операторы ветвления if и else
- •10.2. Оператор switch
- •10.3. Оператор цикла while
- •10.4. Оператор цикла do…while
- •10.5. Оператор цикла for
- •10.6. Бесконечные циклы
- •10.7. Другие управляющие средства языка с
- •10.8. Стандартные математические функции
- •11. Вычисление выражений и побочные эффекты
- •11.1. Преобразования типов при вычислении выражений
- •11.2. Побочные эффекты при вычислении выражений
- •12. Массивы
- •12.1. Описание массива
- •12.2. Инициализация массива
- •12.3. Ввод-вывод массива
- •12.4. Двумерные массивы (массивы массивов)
- •13. Указатели
- •14. Адресная арифметика
- •15. Массивы и указатели
- •15.1. Указатели и одномерные массивы
- •15.2. Указатели и двумерные массивы
- •16. Строки
- •17. Массивы строк
- •18. Функции
- •18.1. Определение функции в языке с
- •18.2. Возвращение значений из функции
- •18.3. Формальные и фактические параметры функции
- •18.4. Вызов функции
- •18.5. Объявление и определение функции: прототип функции
- •19. Передача параметров в функции
- •19.1. Способы передачи параметров в функции
- •19.2. Передача параметров в функции в языке с
- •19.3. Передача указателей в функции
- •20. Классы хранения и видимость переменных
- •20.1. Общие положения
- •20.2. Спецификаторы класса памяти
- •20.3. Область видимости функций
- •20.4. Глобальные переменные
- •20.5. Глобальные статические переменные
- •20.6. Локальные переменные
- •20.7. Статические локальные переменные
- •20.8. Регистровые переменные
- •20.9. Выводы
- •21. Организация памяти программы
- •22. Многофайловая компиляция (проекты)
- •23. Передача в функции массивОв
- •23.1. Передача одномерных массивов в функции
- •23.2. Передача двумерных массивов в функции
- •23.3. Передача в функции символьных строк
- •23.4. Возвращение указателей из функций
- •24. Функции с переменным количеством аргументов
- •24.1. Соглашения о вызовах: модификаторы функций
- •24.2. Объявление списка параметров переменной длины
- •25. Передача параметров в функцию main()
- •26. Указатели на функцию
- •27. Стандартные функцИи языка с
- •27.1. Функции для работы со строками
- •27.2. Функции для проверки символов и преобразования данных
- •27.3. Функция быстрой сортировки – gsort()
- •27.4. Функция двоичного поиска – bsearch()
- •28. Работа с файлами
- •28.1. Основные понятия
- •28.2. Основные функции для работы с файлами
- •28.3. Открытие и закрытие файлов
- •28.4. Ввод/вывод символов
- •28.5. Ввод/вывод строк
- •28.6. Форматированный ввод/вывод
- •28.7. Ввод/вывод блоков данных
- •28.8. Другие средства для работы с файлами
- •28.9. Ввод/вывод низкого уровня (префиксный доступ к файлам)
- •29. Типы, определяемые пользователем: Перечисления, структуры и объединения
- •29.1. Переименование типов – оператор typedef
- •29.2. Перечисления (enum)
- •29.3. Основные сведения о структурах
- •29.4. Структурные переменные в памяти компьютера
- •29.5. Доступ к полям структуры
- •29.6. Массивы структур
- •29.7. Структуры и функции
- •29.8. Объединения (union)
- •30. Динамическая память
- •30.1. Понятие динамического объекта
- •30.2 Создание и уничтожение динамических объектов
- •30.3 Динамическое размещение одномерных массивов и строк
- •30.4 Динамическое размещение двумерных массивов
- •30.5. Функции для работы с блоками памяти
- •31. Динамические структуры данных
- •31.1. Понятие структуры данных
- •31.2. Структуры, ссылающиеся на себя
- •31.3. Связанные списки
- •31.5. Очереди
- •Ниже приводятся примеры функций для очереди (структура элемента очереди совпадает со структурой элемента стека в примере выше):
- •32. Препроцессор языка с
- •32.1 Директива включения файлов
- •32.2. Директива определения макрокоманд (макросов)
- •32.3 Директива условной компиляции
- •32.4 Дополнительные директивы препроцессора
11.2. Побочные эффекты при вычислении выражений
1) Операции присваивания, инкремента и декремента в сложных выражениях могут вызывать побочные эффекты, так как они не только возвращают значение, но еще и изменяют операнд. Эти побочные эффекты могут вызвать трудности из-за того, что получение значения и обновление переменной могут проходить не тогда, когда ожидается. Кроме этого, порядок вычисления операндов некоторых операций зависит от реализации (от компилятора).
Другими словами, необходимо с осторожностью использовать выражения, при вычислении которых возможны побочные результаты, так как результаты вычисления таких выражений часто проявляются не сразу и, кроме того, зависят от используемого компилятора.
j = 3;
i = (k = j + 1) + (j = 5);
Значение переменной i будет равно 9 или 11 в зависимости от того, какое выражение в скобках будет вычислено первым. Таким образом, с использованием разных компиляторов можно получить различные результаты. Кроме побочных эффектов, использование вложенных операторов присваивания усложняет чтение и восприятие программного кода.
При применении операций инкремента или декремента к переменной эта переменная не должна появляться в выражении более одного раза, так как итог и в этом случае будет зависеть от компилятора. Не следует писать код, который полагается на порядок обработки или особенности компилятора:
m[i++] = m[i++] = 0;
Смысл выражения – записать 0 в следующие две позиции массива m. Однако в зависимости от того, когда будет обновляться i, позиция в m может быть пропущена, и в результате переменная i будет увеличена только на 1.
В BC, например, получим: i = 0, m = {1, 2, 3, 4, 5} => i = 2, m = {0, 2, 3, 4, 5}, а не ожидаемые: i = 2, m = {0, 0, 3, 4, 5}.
Надо разбить выражение на 2 выражения: m[i++] = 0; m[i++] = 0;
Даже если в выражении содержится только один инкремент, результат может быть неоднозначным:
m[i++] = i;
Если изначально i = 1, то элемент массива может принять значение 1 или 2.
2) Операнды логических выражений вычисляются слева направо. Если значения первого операнда достаточно, чтобы определить результат операции, то второй операнд не вычисляется. Это опять же зависит от компилятора. Например, в BC есть ускоренное вычисление логических выражений.
int a = 2, b = 3, c = 4, i = 0, j = 0;
if (a < i++ && b < j++)
c = 10;
Т.к. a=2 уже не меньше i=0, то i++ выполнится, а второе выражение даже не будет проверяться и вычисляться и после выполнения оператора: j=0, а не j=1.
3) Побочные эффекты могут возникать и при вызове функции, если он содержит прямое или косвенное присваивание (через указатель). Это связано с тем, что аргументы функции могут вычисляться в любом порядке. Например, побочный эффект имеет место в следующем вызове функции:
prog(a, a = k * 2); // в зависимости от того, какой аргумент вычисляется первым, в функцию могут быть переданы различные значения
Ввод-вывод – еще один потенциальный источник возникновения закулисных действий.
В примере осуществляется попытка прочитать два взаимосвязанных числа из стандартного ввода:
scanf (“%d %d”, &n, &m[n]);
Выражение неверно, поскольку одна его часть изменяет n, а другая использует ее. Значение m[n] будет правильным только в том случае, если новое значение n будет таким же, как и старое. Проблема здесь не в порядке вычисления аргументов, а в том, что все аргументы scanf() вычисляются до того, как эта функция вызывается на выполнение, так что &m[n] всегда будет вычисляться с использованием старого значения n.
Следующий пример показывает, что аргументы при вызове функции вычисляются не в том порядке, в каком ожидалось:
int a = 2;
printf("%d %d", a+=3, a+=5); // a = 10 на экране 10 и 7 (а не 5 и 10)
или
printf("%d %d", a+=3, a); // a = 5 на экране 5 и 2 (а не 5 и 5)
Чтобы избежать недоразумений при выполнении побочных эффектов, необходимо придерживаться следующих правил:
1. Не использовать операции присваивания переменной в выражении, если эта переменная используется в выражении более одного раза.
2. Не применять операции увеличения или уменьшения к переменной, которая входит в выражение более одного раза.
3. В логических выражениях не проводить никаких дополнительных вычислений.
4. Не использовать операции присваивания переменной в вызове функции, если эта переменная участвует в формировании других аргументов функции.
5. Не применять операции увеличения или уменьшения к переменной, присутствующей в более чем одном аргументе функции.
6. Использовать естественную форму выражений. Записывать выражения в том виде, в котором вы произносили бы их вслух.
7. Разбивать сложные выражения. Язык С имеет богатый и разнообразный синтаксис, поэтому многие увлекаются втискиванием всего подряд в одну конструкцию.
x += (xp = (2*k < (n-m) ? c[k+1] : d[k --]) );
Лучше написать так:
xp = 2*k < (n-m) ? c[k+1] : d[k --];
x += xp;
или даже так:
if (2*k < n-m)
xp = c[k+1];
else
xp = d[k--];
x += xp;
8. И как вывод: необходимо быть проще. Это убережет и от ошибок, и упростит восприятие вашего программного кода.
