
- •Часть 1
- •Общие сведения Сведения об эумк
- •Методические рекомендации по изучению дисциплины
- •Рабочая учебная программа
- •Учреждение образования
- •«Белорусский государственный университет
- •Информатики и радиоэлектроники»
- •Часть 2 __184__
- •Содержание дисциплины
- •1. Индивидуальные практические занятия, их характеристика
- •2. Контрольные работы, их характеристика
- •3. Курсовой проект, его характеристика
- •4. Литература
- •4.1. Основная
- •4.2. Дополнительная
- •5. Перечень компьютерных программ, наглядных и других пособий, методических указаний и материалов и технических средств обучения
- •Протокол согласования учЕбной программы по изучаемой учебной дисциплине с другими дисциплинами специальности
- •Теоретический раздел Введение
- •1. Основные типы данных
- •1.1. Общие сведения
- •1.2. Данные типа int
- •1.3. Данные типа char
- •1.4. Модификаторы доступа const и volatile
- •1.5. Данные вещественного типа (с плавающей точкой)
- •1.6. Элементарный ввод-вывод
- •1.7. Структура простой программы на языке Си
- •2. Операции и выражения
- •2.1. Выражение и его интерпретация
- •2.2. Основные операции
- •2.2.1. Арифметические операции
- •2.2.2. Побитовые логические операции
- •2.2.3. Операции сдвига
- •2.2.4. Операция присваивания
- •2.2.5. Операция sizeof
- •2.2.6. Преобразование типов в выражениях
- •2.2.7. Операция преобразования типов
- •2.2.8. Приоритеты в языке Си
- •3. Операторы управления вычислительным процессом
- •3.1. Оператор if
- •3.2. Операции отношения
- •3.3. Логические операции
- •3.4. Операция запятая
- •3.5. Операция условия ?:
- •3.6. Оператор безусловного перехода goto
- •3.7. Оператор switch
- •`` ` `3.8. Операторы цикла
- •3.8.1. Оператор for
- •3.8.2. Оператор while
- •3.8.3. Оператор do...While
- •3.9. Оператор break
- •3.10. Оператор continue
- •4. Массивы и указатели
- •4.1. Одномерные массивы и их инициализация
- •4.2. Многомерные массивы и их инициализация
- •4.3. Объявление указателей
- •4.4. Операции над указателями
- •1) Взятие адреса
- •2) Косвенная адресация или разыменование указателя
- •3) Увеличение или уменьшение значения указателя на целое число
- •4) Разность указателей
- •5) Сравнение указателей
- •6) Присваивание указателей друг другу
- •4.6. Связь между указателями и массивами
- •4.7. Динамическое распределение памяти
- •4.8. Массивы указателей
- •5. Функции
- •5.1. Общие сведения
- •5.2. Область видимости переменных
- •5.2.1. Локальные переменные
- •5.2.2. Глобальные переменные
- •5.3. Передача параметров в функцию
- •5.4. Рекурсивные функции
- •5.5. Использование функций в качестве параметров функций
- •5.6. Указатели на функции
- •5.7. Структура программы на Си
- •5.8. Передача параметров в функцию main()
- •6. Строки
- •7. Классы хранения и видимость переменных
- •7.1. Общие сведения
- •7.2. Автоматический класс хранения (auto)
- •7.3. Регистровый класс хранения (register)
- •7.4. Статический класс хранения (static)
- •7.5. Внешний класс хранения (extern)
- •7.6. Заключение
- •8. Структуры, объединения и перечисления
- •8.1. Общие сведения
- •8.2. Инициализация структурных переменных
- •8.3. Вложенные структуры
- •8.4. Указатели на структуры
- •8.5. Массивы структурных переменных
- •8.6. Передача функциям структурных переменных
- •8.7. Оператор typedef
- •8.8. Поля битов в структурах
- •8.9. Объединения
- •8.10. Перечисления
- •9. Динамические структуры данных
- •9.1. Общие сведения
- •9.2. Связные списки
- •9.2.1. Односвязные списки
- •9.2.2. Двусвязные списки
- •9.2.3. Циклические списки
- •9.3. Стеки
- •9.4. Очереди
- •9.5. Деревья
- •9.5.1. Понятие графа
- •9.5.2. Бинарные деревья
- •10. Файлы
- •10.1. Общие сведения
- •10.2. Открытие и закрытие файлов
- •10.3. Функции ввода-вывода для работы с текстовыми файлами
- •10.4. Произвольный доступ к файлу
- •10.5. Функции ввода-вывода для работы с бинарными файлами
- •11. Директивы препроцессора
- •11.1. Основные понятия
- •11.2. Директива #include
- •11.3. Директивы препроцессора #define и #undef
- •11.3.1. Символические константы
- •11.3.2. Макросы с параметрами
- •11.3.3. Директива #undef
- •11.4. Условная компиляция
- •11.5. Директивы # и ##
- •12. Модульное программирование
- •13. Введение в объектно-ориентированное программирование
- •13.1. Постановка задачи
- •13.2. Решение задачи средствами Си
- •13.5. Наследование
- •13.6. Перегрузка
- •13.7. Ссылочный тип
- •Литература
- •Приложение 1. Рекомендации по оформлению текстов программ
- •Тесты к теоретическому разделу Вопросы к разделу 1. Основные типы данных
- •Вопросы к разделу 2. Операции и выражения
- •Вопросы к разделу 3. Операторы управления вычислительным процессом
- •Вопросы к разделу 4. Массивы и указатели
- •Вопросы к разделу 5. Функции
- •Вопросы к разделу 6. Строки
- •Вопросы к разделу 7. Классы хранения и видимость переменных
- •Вопросы к разделу 8. Структуры, объединения и перечисления
- •Вопросы к разделу 9. Динамические структуры данных
- •Вопросы к разделу 10. Файлы
- •Вопросы к разделу 11. Директивы препроцессора
- •Вопросы к разделу 12. Модульное программирование
- •Вопросы к разделу 13. Введение в ооп
- •Правильные ответы на вопросы тестов к теоретическому разделу
- •Вопросы к теоретическому зачету
- •Варианты индивидуальных заданий
- •Контрольная работа №2
- •Варианты индивидуальных заданий
- •Индивидуальные практические работы Указания к выбору варианта индивидуальных практических работ
- •Индивидуальная практическая работа № 1. Массивы и строки
- •Варианты индивидуальных заданий
- •Индивидуальная практическая работа № 2. Динамические структуры данных
- •Варианты индивидуальных заданий
11.3.2. Макросы с параметрами
Формат директивы #define, определяющей макрос с параметрами:
#define идентификатор_макроса(параметры) замещающий_текст
Предупреждение: между идентификатором макроса и открывающейся скобкой не должно быть пробела.
Вызов макроса осуществляется выражением:
идентификатор_макроса(параметры)
Макрос, определяемый директивой препроцессора #define, это символическое имя некоторого набора операторов. Как и в случае символических констант, идентификатор макроса заменяется на замещающий текст до начала компиляции программы. Но сначала в замещающий текст подставляются значения параметров, а затем уже этот расширенный макрос подставляется в текст вместо идентификатора макроса и списка его параметров.
Например, следующий макрос с одним параметром определяет площадь круга, воспринимая передаваемый в него параметр как радиус:
#define CIRC(x) (3.14 * (x) *(x))
Везде в тексте файла, где появится идентификатор CIRC(A), значение параметра A будет использовано для замещения. Например, оператор с макросом в тексте программы
S = CIRC(4);
примет вид:
S = (3.14 * (4) * (4));
Поскольку это выражение состоит только из констант, его значение будет вычислено во время компиляции и полученный результат будет присвоен переменной S во время выполнения программы.
Если вызов имеет вид
S = CIRC(a + b);
то после расширения макроса тест будет иметь вид:
S = (3.14 * (a + b) * (a + b));
В данном случае параметр макроса является выражением, содержащим переменные a и b. Поэтому вычисления будут осуществляться не во время компиляции, а во время выполнения программы.
Следует обратить внимание на круглые скобки вокруг каждого включения параметра x в тексте рассмотренного макроса и вокруг всего выражения. При вызове типа CIRC(4) они кажутся излишними. Но во втором примере вызова при отсутствии скобок расширение привело бы к оператору:
S = 3.14 * a + b * a + b;
Тогда в соответствии со старшинством операций сначала выполнилось бы умножение 3.14 * a, затем b * a, а затем результаты этих умножений сложились бы друг с другом и с b. Конечно, результат вычислений был бы неверным.
Хороший стиль программирования: При объявлении макроса заключите в скобки параметры в замещающем тексте и сам замещающий текст. Это избавит от возможных неприятностей, связанных с неверной последовательностью вычислений при расширении макроса.
Пример_1: макрос, определяющий площадь эллипса через значения его полуосей может быть определен директивой
#define Ell(x, y) (3.14 * (x) * (y))
Вызов этого макроса может иметь вид:
S = Ell(R1, R2);
С точки зрения получаемых результатов вычислений макросы эквивалентны функциям. Например, вычисление площади круга можно было бы оформить функцией:
double circ(double x)
{
return 3.14 * x * x;
}
и вызвать ее оператором:
S = circ(a + b);
Пример_2: следующим образом можно создать элемент связанного списка, используя #define:
#define PTRSTRUCT(name) typedef struct name *name##Ptr
#define STRUCT(name) struct name \
{ \
int value; \
struct name *next; \
}; \
PTRSTRUCT(name)
и работать с ним:
STRUCT(MyStruct);
MyStruct My;
MyStructPtr MyPtr = &My;
My.value = 5;
printf(“\n%d”, My.value);
printf(“\n%d”, (*MyPtr).value);
При этом на экран будет выведено:
5
5
Пример_3: использование макросов вместо стандартных функций:
#define READ(val) scanf("%d", &val)
При использовании в тексте программы READ(y);, макрос расширится до scanf("%d",&y);.
Таким образом, возникает вопрос, что выгоднее использовать: макросы или функции?
Вызов функции сопряжен с дополнительными расходами и увеличивает время выполнения программы (если программа использует функцию, то в выполняемую программу помещается только одна копия функции. Каждый раз, при вызове функции, программа помещает параметры в стек и затем выполняет переход к коду функции. После завершения функции, программа удаляет параметры из стека и переходит обратно к оператору, который следует непосредственно за вызовом функции.). Это соображение работает в пользу использования макросов. С другой стороны, макрос расширяется во всех местах текста, где используется его вызов. Если таких мест в программе много, то это увеличивает размер текста и, соответственно размер выполняемого модуля. Так что функции позволяют сократить объем выполняемого файла, а макросы – увеличить скорость выполнения. Правда, макросы тоже могут быть связаны с дополнительными накладными расходами. В приведенном примере “с вычислением площади круга” значение параметра (a + b) вычисляется дважды, в то время, как в функции это вычисление осуществляется только один раз. Конечно, для таких простых вычислений это не существенно. Но если в качестве параметра передается сложное выражение, обращающееся в свою очередь к каким-нибудь сложным функциям, то эти дополнительные накладные расходы могут стать заметными и увеличить вычисления.
Недостатком макросов является отсутствие встроенного контроля согласования типов фактических и формальных параметров. Отсутствие соответствующих предупреждений компилятора может приводить к ошибкам программы, которые трудно отлавливать. Однако это является иногда и преимуществом. Например, если необходимо создать “универсальную функцию”, подобную шаблонным функциям в C++. Опишем пример работы подобной “универсальной функции”. Создаем макрос – суммирование двух элементов:
#define SUM(x, у) ((х) + (у))
Вызываем его в главной программе, передавая макросу в качестве фактических параметров константы int, float и char типов:
printf("3 + 5 = %d\n", SUM(3, 5));
printf("3.4 + 5.7 = %0.1f\n", SUM(3.4, 5.7));
printf("’a’ + ‘0’ = ‘%c’\n", SUM(‘a’, ‘0’));
После выполнения программы на экране будет отображено следующее:
3 + 5 = 8
3.4 + 5.7 = 9.1
'a'+’0’ = 'q’
Но самый существенный недостаток макросов – возможность появления побочных эффектов, если в качестве аргумента в макрос передается некоторое выражение. Например, если описанный выше макрос CIRC, вычисляющий площадь круга, вызвать следующим образом:
S = CIRC(a++);
предполагая рассчитать площадь и затем операцией постфиксного инкремента увеличить радиус на 1. Макрос будет расширен следующим образом:
S = (3.14 * (a++) * (a++));
Теперь выведем на экран полученную площадь S и переменную a (допустим до этого a == 2):
printf(“\n%f\n%d”, S, a);
получим:
12.56
4
При этом площадь вычислена верно, но постфиксный инкремент вычислялся два раза. В результате значение радиуса a увеличилось не на 1, а на 2.
Если мы попробуем вывести следующим образом (до этого a == 2):
printf(“\n%f\n%d”, CIRC(a++), a);
то получим:
18.84
2
В этом случае один инкремент произошел прямо при подсчёте площади круга.
Если же этот макрос вызвать следующим образом:
S = CIRC(++a);
предполагая увеличить радиус на 1 и вычислить площадь круга с таким увеличенным радиусом. Макрос будет расширен следующим образом:
S = (3.14159 * (++a) * (++a));
Теперь выведем на экран полученную площадь S и переменную a (допустим до этого a == 1):
printf(“\n%f\n%d”, S, a);
получим:
28.56
3
При этом макрос расширился до:
S = (3.14159 * (a + 2) * (a + 2));
Если мы попробуем вывести следующим образом (до этого a == 1):
printf(“\n%f\n%d”, CIRC(a++), a);
то получим:
18.84
1
При этом макрос расширился до:
S = (3.14159 * (a + 1) * (a + 2));
Всех этих побочных эффектов не будет, если вместо макроса использовать описанную выше функцию circ.
При выборе реализации вычислений функцией или макросом надо обеспечивать компромисс между скоростью вычислений и затратами памяти. Для небольших функций, возможно, наилучшим решением является применение встраиваемых функций. Для них проблемы оптимальной реализации решает компилятор.
Хороший стиль программирования: Избегайте применения сложных макросов с параметрами, так как они могут приводить к нежелательным побочным эффектам. Вместо макросов в этих случаях лучше использовать встраиваемые функции inline.
Преимущества макросов с параметрами перед функциями:
1. экономия времени выполнения.
Недостатки:
2. увеличение объема программного кода;
3. неправильная интерпретация изменяющихся параметров;
4. отсутствие контроля согласования типов.