Синтаксический анализатор

1. Введение
Задача синтаксического анализатора – проверить правильность записи выражения и разбить его на лексемы. Лексемой называется неделимая часть выражения, состоящая, в общем случае, из нескольких символов. Для арифметических и логических выражений лексемами будут:
  • имена переменных;
  • имена функций;
  • константы;
  • операции – арифметические (+, -, *, /), логические (!, &&, ||) и сравнения (==, !=, >, >=, <, <=);
  • открывающая скобка;
  • закрывающая скобка.
Обычно лексемы разделяются пробелами, хотя возможны ситуации, когда появление новой лексемы обозначает конец предыдущей. Например, знак арифметической операции заканчивает имя стоящей перед ним переменной.
При реализации синтаксического анализатора надо все символы, которые могут встретиться в обрабатываемой строке, разбить на группы таким образом, чтобы все символы группы вызывали одинаковую реакцию синтаксического анализатора. Например, при разборе арифметических и логических выражений все цифры 0-9 будут вызывать одинаковую реакцию.
Кроме того, необходимо выделить состояния синтаксического анализатора. Состояние определяет, какие символы в данный момент могут быть на входе синтаксического анализатора, и какова будет реакция на этот символ. Например, если на вход синтаксического анализатора пришла цифра, то он переходит в состояние «Константа», и до завершения константы (т.е. до появления пробела, знака операции, закрывающей скобки или конца строки) на вход синтаксического анализатора могут приходить только цифры и точка, если константа вещественная. Причём, естественно, точка может встречаться только один раз, что приводит к делению состояния «Константа» на два состояния: «Константа до точки», в котором точка может появиться, и «Константа после точки», в котором появление точки будет считаться ошибкой.
Одно состояние является начальным – с него начинается работа синтаксического анализатора, и одно или несколько состояний должны быть конечными.
Далее строиться таблица, которая определяет реакцию синтаксического анализатора на входные символы в зависимости от состояния. Реакция обычно заключается в смене состояния синтаксического анализатора и ещё каких-то действиях, например, запись очередного символа во временную переменную, увеличение или уменьшение уровня вложенности при появлении скобок и т.д.
Обычно в программе синтаксический анализатор реализуется как бесконечный цикл (for ( ; ; ) { ... }) с выходами в случае ошибки или при достижении конца обрабатываемой строки (обратите внимание, что достижение конца строки ещё не означает правильность выражения). Внутри цикла пишется условный блок, например, по группам символов, и в каждой части этого условного блока – свой условный блок (или оператор-переключатель) по состояниям синтаксического анализатора.
Состояния синтаксического анализатора в программе для простоты обозначаются номерами, однако, чтобы не путаться в этих номерах, можно задать им осмысленные имена с помощью конструкции #define.
Если выражение записано верно, то в результате работы синтаксического анализатора должен появиться список лексем (можно реализовать его как массив, а не настоящий список). Поскольку элементы массива должны иметь одинаковый тип, надо выбрать такую структуру, с помощью которой можно представить все возможные лексемы. Это можно сделать, если, например, каждый элемент массива представляет собой структуру, состоящую из двух полей: тип лексемы и её номер в списке лексем этого типа.
Часть лексем в арифметических и логических выражениях состоят из одного символа, но имена переменных и функций и константы в общем случае состоят из нескольких символов. Поэтому входные символы надо записывать во временную переменную, и когда данная лексема заканчивается, проверять, что она из себя представляет.
Не забудьте, что одна и та же переменная может входить в выражение несколько раз, поэтому каждый раз, когда имя переменной сформировалось, надо проверять, была ли она уже в списке переменных.
Не забудьте, что при достижении конца обрабатываемого выражения надо проверить, что количество открывающих и закрывающих скобок было одинаковым (вложенность = 0).

2. Анализ арифметических выражений

Состояния
  1. Операнд (исходное) – должен появиться операнд (константа, переменная, функция, выражение в скобках).
  2. Оператор – должен появиться оператор.
  3. Имя – имя переменной или функции началось, но ещё не закончилось.
  4. Константа до точки – константа началась, но ещё не закончилась, точки не было.
  5. Константа после точки – константа началась, но ещё не закончилась, точка была.
  6. Параметры функции – это состояние возникает, когда после имени, идентифицированного как имя функции, появился пробел. Единственный возможный символ в этом состоянии – открывающая скобка.
Ошибки
  1. Пропущен операнд
  2. Неверно расставлены скобки
  3. Неверный символ «...»
  4. Пропущен оператор
  5. Не заданы параметры функции «...»
  6. Неизвестная функция «...»
  7. Неверное имя «...»
  8. Неверная константа «...»

3. Анализ логических выражений

Состояния
  1. Операнд (исходное) – должен появиться операнд (константа, переменная, функция, выражение в скобках).
  2. Оператор – должен появиться оператор.
  3. Переменная – имя переменной началось, но ещё не закончилось.
  4. Константа до точки – константа началась, но ещё не закончилась.
  5. = – был знак равенства, следующий символ тоже должен быть знаком равенства.
  6. ! – был восклицательный знак, следующий символ должен быть знаком равенства.
  7. > – был знак «больше», следующий символ должен быть знаком равенства, или может начаться следующий операнд.
  8. < – был знак «меньше», следующий символ должен быть знаком равенства, или может начаться следующий операнд.
  9. & – был амперсант, следующий символ тоже должен быть амперсантом.
  10. | – была вертикальная черта, следующий символ тоже должен быть вертикальной чертой.
Ошибки
  1. Пропущен операнд
  2. Неверно расставлены скобки
  3. Неверный символ «...»
  4. Пропущен оператор
  5. Неверная константа «...»
  6. Неверный оператор «...»
Для логических выражений надо учитывать, что после операции сравнения должна идти логическая операция. Поэтому вводится признак «следующая операция», который обозначает, какая операция должна быть следующей – любая или только логическая.

4. Таблица для синтаксического анализатора арифметических выражений

 |  | 0..9 | a..z, A..Z, _ | Пробел | +, -, *, / | ( | ) | . | \0 | Остальные
1. | Операнд (исходное) | Состояние 4 | Состояние 3 |  | Ошибка 1 | Вложенность++ | Ошибка 2 | Состояние 5 | Ошибка 1 | Ошибка 3
2. | Оператор | Ошибка 4 | Ошибка 4 |  | Состояние 1 | Ошибка 4 | Вложенность-- | Ошибка 3 | ОК | Ошибка 3
3. | Имя |  |  | Если имя – функция, то состояние 6, иначе состояние 2 | Если имя – функция, то ошибка 5, иначе переход в состояние 2 и повторная обработка этого же символа, что приводит к переходу в состояние 1 | Если имя – функция, то состояние 1, иначе ошибка 6. Вложенность++ | Если имя – функция, то ошибка 2, иначе состояние 2. Вложенность-- | Ошибка 7 | Если имя – функция, то ошибка 5, иначе ОК | Ошибка 3
4. | Константа до точки |  | Ошибка 8 | Состояние 2 | Переход в состояние 2 и повторная обработка этого же символа, что приводит к переходу в состояние 1 | Ошибка 4 | Состояние 2. Вложенность-- | Состояние 5 | ОК | Ошибка 3
5. | Константа после точки |  | Ошибка 8 | Состояние 2 | Переход в состояние 2 и повторная обработка этого же символа, что приводит к переходу в состояние 1 | Ошибка 4 | Состояние 2. Вложенность-- | Ошибка 8 | ОК | Ошибка 3
6. | Параметры функции | Ошибка 5 | Ошибка 5 |  | Ошибка 5 | Состояние 1. Вложенность++ | Ошибка 2 | Ошибка 5 | Ошибка 5 | Ошибка 3

5. Таблица для синтаксического анализатора логических выражений

 |  | 0..9 | a..z, A..Z, _ | Пробел | = | ! | > | < | & | | | ( | ) | \0 | Остальные
1. | Операнд (исходное) | Состояние 4 | Состояние 3 |  | Ошибка 1 | Операция «НЕ». Состояние 1 | Ошибка 1 | Ошибка 1 | Ошибка 1 | Ошибка 1 | Если следующая операция – любая, то вложенность++, иначе ошибка 2 | Ошибка 2 | Ошибка 1 | Ошибка 3
2. | Оператор | Ошибка 4 | Ошибка 4 |  | Если следующая операция – любая, то состояние 5, иначе ошибка 6 | Если следующая операция – любая, то состояние 6, иначе ошибка 6 | Если следующая операция – любая, то состояние 7, иначе ошибка 6 | Если следующая операция – любая, то состояние 8, иначе ошибка 6 | Если следующая операция – любая, то состояние 9, иначе ошибка 6 | Если следующая операция – любая, то состояние 10, иначе ошибка 6 | Ошибка 4 | Вложенность--. Следующая операция – логическая | ОК | Ошибка 3
3. | Переменная |  |  | Состояние 2 | Если следующая операция – любая, то состояние 5, иначе ошибка 6 | Если следующая операция – любая, то состояние 6, иначе ошибка 6 | Если следующая операция – любая, то состояние 7, иначе ошибка 6 | Если следующая операция – любая, то состояние 8, иначе ошибка 6 | Если следующая операция – любая, то состояние 9, иначе ошибка 6 | Если следующая операция – любая, то состояние 10, иначе ошибка 6 | Ошибка 4 | Состояние 2. Вложенность--. Следующая операция – логическая | ОК | Ошибка 3
4. | Константа |  | Ошибка 5 | Состояние 2 | Если следующая операция – любая, то состояние 5, иначе ошибка 6 | Если следующая операция – любая, то состояние 6, иначе ошибка 6 | Если следующая операция – любая, то состояние 7, иначе ошибка 6 | Если следующая операция – любая, то состояние 8, иначе ошибка 6 | Если следующая операция – любая, то состояние 9, иначе ошибка 6 | Если следующая операция – любая, то состояние 10, иначе ошибка 6 | Ошибка 4 | Состояние 2. Вложенность--. Следующая операция – логическая | ОК | Ошибка 3
5. | = | Ошибка 4 | Ошибка 4 | Ошибка 4 | Операция «==». Следующая операция – логическая | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 4 | Ошибка 4 | Ошибка 4 | Ошибка 3
6. | ! | Ошибка 4 | Ошибка 4 | Ошибка 4 | Операция «!=». Следующая операция – логическая. Состояние 1 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 4 | Ошибка 4 | Ошибка 4 | Ошибка 3
7. | > | Операция «>». Следующая операция – логическая. Состояние 4 | Операция «>». Следующая операция – логическая. Состояние 3 | Операция «>». Следующая операция – логическая. Состояние 1 | Операция «>=». Следующая операция – логическая. Состояние 1 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 4 | Ошибка 4 | Ошибка 4 | Ошибка 3
8. | < | Операция «<». Следующая операция – логическая. Состояние 4 | Операция «<». Следующая операция – логическая. Состояние 3 | Операция «<». Следующая операция – логическая. Состояние 1 | Операция «<=». Следующая операция – логическая. Состояние 1 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 4 | Ошибка 4 | Ошибка 4 | Ошибка 3
9. | & | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Операция «&&». Следующая операция – любая. Состояние 1 | Ошибка 6 | Ошибка 4 | Ошибка 4 | Ошибка 4 | Ошибка 3
10. | | | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Ошибка 6 | Операция «||». Следующая операция – любая. Состояние 1 | Ошибка 4 | Ошибка 4 | Ошибка 4 | Ошибка 3

Файл в формате Word

Содержание


 Данный конспект лекций предназначен для всех студентов, желающих освоить программирование на С и С++.
Автор: ст. преподаватель кафедры Прикладной математики МЭИ(ТУ) Чибизова Наталья Владимировна.
Количество посещений: 6901


 Задания
  • Часть 1. Вычисление значения выражения
  • Часть 2. Графика в Microsoft Visual C++

Пояснения
  • Общие принципы программирования в Microsoft Visual C++
  • Создание первой программы
  • Создание второй программы
  • Разработка синтаксического анализатора
  • Преобразование в постфиксную запись и вычисление выражения

Примеры программ
  • Пробная программа
  • Вычисление значения выражения
  • Графика в Microsoft Visual C++


Типовой расчёт. Часть 1. «Вычисление значения выражения»

Типовой расчёт. Часть 1.
«Вычисление значения выражения»

Выражение задаётся в виде строки. Оно может содержать знаки операций, скобки, имена переменных и константы соответствующего типа. В первую очередь необходимо произвести синтаксический анализ введённой строки. Если строка представляет собой правильное выражение, необходимо преобразовать выражение в постфиксную запись и вывести строку, представляющую собой полученную постфиксную запись. Затем вводятся значения переменных, вычисляется значение выражение и выводится полученный результат.
При реализации первой части типового расчёта необходимо создать мультидокументное приложение. Вид каждого документа должен основываться на диалоге. Для ввода значений переменных необходимо использовать другой диалог. Для изменения текста выводимого в элементах Static Text и Group Box можно использовать функцию SetWindowText.

Пример программы
Создание программы
  1. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в двоичной системе счисления.
  2. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в восьмеричной системе счисления.
  3. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в десятичной системе счисления.
  4. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в шестнадцатеричной системе счисления.
  5. Вычислить значение выражения, содержащего арифметические операции и переменные и константы вещественного типа в обычной нотации.
  6. Вычислить значение выражения, содержащего арифметические операции и переменные и константы вещественного типа в научной нотации.
  7. Вычислить значение выражения, содержащего логические операции и операции сравнения и переменные и константы логического типа.
  8. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в двоичной системе счисления.
  9. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в восьмеричной системе счисления.
  10. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в десятичной системе счисления.
  11. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в шестнадцатеричной системе счисления.
  12. Вычислить значение выражения, содержащего арифметические операции и переменные и константы вещественного типа в обычной нотации.
  13. Вычислить значение выражения, содержащего арифметические операции и переменные и константы вещественного типа в научной нотации.
  14. Вычислить значение выражения, содержащего логические операции и операции сравнения и переменные и константы логического типа.
  15. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в двоичной системе счисления.
  16. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в восьмеричной системе счисления.
  17. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в десятичной системе счисления.
  18. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в шестнадцатеричной системе счисления.
  19. Вычислить значение выражения, содержащего арифметические операции и переменные и константы вещественного типа в обычной нотации.
  20. Вычислить значение выражения, содержащего арифметические операции и переменные и константы вещественного типа в научной нотации.
  21. Вычислить значение выражения, содержащего логические операции и операции сравнения и переменные и константы логического типа.
  22. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в двоичной системе счисления.
  23. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в восьмеричной системе счисления.
  24. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в десятичной системе счисления.
  25. Вычислить значение выражения, содержащего арифметические операции и переменные и константы целого типа в шестнадцатеричной системе счисления.


Типовой расчёт. Часть 2. «Рисование и использование мыши»

Типовой расчёт. Часть 2.
«Рисование и использование мыши»

Вторая часть типового расчёта посвящена выводу изображений в окно документа, а также обработке событий, получаемых от мыши и от клавиатуры.
Задание состоит в разработке класса для работы с некоторой геометрической фигурой. Над фигурой необходимо выполнять следующие действия:
  • создание;
  • рисование;
  • изменение цвета;
  • перемещение;
  • изменение размера;
  • удаление.
При реализации второй части типового расчёта необходимо создать однодокументное приложение. Вид документа должен основываться на классе CView. Необходимо предусмотреть возможность создания фигуры, изменения её цвета, перемещения и изменения размера с помощью мыши и с помощью клавиатуры, удаления выбранной фигуры и всех фигур.

Пример программы
Создание программы
  1. Разработать класс для фигуры «круг».
  2. Разработать класс для фигуры «эллипс».
  3. Разработать класс для фигуры «треугольник».
  4. Разработать класс для фигуры «ромб».
  5. Разработать класс для фигуры «прямоугольник».
  6. Разработать класс для фигуры «квадрат».
  7. Разработать класс для фигуры «круг».
  8. Разработать класс для фигуры «эллипс».
  9. Разработать класс для фигуры «треугольник».
  10. Разработать класс для фигуры «ромб».
  11. Разработать класс для фигуры «прямоугольник».
  12. Разработать класс для фигуры «квадрат».
  13. Разработать класс для фигуры «круг».
  14. Разработать класс для фигуры «эллипс».
  15. Разработать класс для фигуры «треугольник».
  16. Разработать класс для фигуры «ромб».
  17. Разработать класс для фигуры «прямоугольник».
  18. Разработать класс для фигуры «квадрат».
  19. Разработать класс для фигуры «круг».
  20. Разработать класс для фигуры «эллипс».
  21. Разработать класс для фигуры «треугольник».
  22. Разработать класс для фигуры «ромб».
  23. Разработать класс для фигуры «прямоугольник».
  24. Разработать класс для фигуры «квадрат».
  25. Разработать класс для фигуры «круг».


Введите строки для поиска, объединяя их логическими операциями «И» (&)
и «ИЛИ» (|), без скобок, каждая строка должна быть заключена в кавычки:

В примерах Учитывать регистр

Назад


1. | Три массива
2. | Обработка матриц
3. | Работа с двоичными файлами
4. | Обработка строк
5. | Работа со структурами
6. | Динамическое распределение памяти
7. | Работа со списком
8. | Поразрядные операции
9. | Создание простого класса
10. | Перегрузка операций
11. | Дружественные функции
12. | Производные классы
13. | Абстрактные классы
14. | Шаблоны


Задача с тремя массивами

В каждом варианте необходимо обработать три массива разной размерности. Необходимо написать функцию, которая выполняет требуемые действия, применить её ко всем трем массивам и сравнить полученные результаты.
Пример программы см. в лекции 3.

  1. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше среднее арифметическое элементов, меньших заданного числа. Если в двух или трех массивах значения среднего арифметического совпадают, вывести соответствующее сообщение.
  2. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше среднее арифметическое элементов, больших заданного числа. Если в двух или трех массивах значения среднего арифметического совпадают, вывести соответствующее сообщение.
  3. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше количество элементов, меньших заданного числа. Если в двух или трех массивах количества искомых элементов совпадают, вывести соответствующее сообщение.
  4. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше количество элементов, больших заданного числа. Если в двух или трех массивах количества искомых элементов совпадают, вывести соответствующее сообщение.
  5. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше минимум элементов, больших заданного числа. Если в двух или трех массивах минимумы совпадают, вывести соответствующее сообщение.
  6. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше максимум элементов, меньших заданного числа. Если в двух или трех массивах максимумы совпадают, вывести соответствующее сообщение.
  7. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше сумма элементов, попадающих в заданный диапазон. Если в двух или трех массивах суммы совпадают, вывести соответствующее сообщение.
  8. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше сумма элементов, не попадающих в заданный диапазон. Если в двух или трех массивах суммы совпадают, вывести соответствующее сообщение.
  9. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше произведение элементов, не попадающих в заданный диапазон. Если в двух или трех массивах произведения совпадают, вывести соответствующее сообщение.
  10. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше произведение элементов, попадающих в заданный диапазон. Если в двух или трех массивах произведения совпадают, вывести соответствующее сообщение.
  11. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше среднее арифметическое элементов, меньших заданного числа. Если в двух или трех массивах значения среднего арифметического совпадают, вывести соответствующее сообщение.
  12. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше среднее арифметическое элементов, больших заданного числа. Если в двух или трех массивах значения среднего арифметического совпадают, вывести соответствующее сообщение.
  13. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше количество элементов, меньших заданного числа. Если в двух или трех массивах количества искомых элементов совпадают, вывести соответствующее сообщение.
  14. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше количество элементов, больших заданного числа. Если в двух или трех массивах количества искомых элементов совпадают, вывести соответствующее сообщение.
  15. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше минимум элементов, больших заданного числа. Если в двух или трех массивах минимумы совпадают, вывести соответствующее сообщение.
  16. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше максимум элементов, меньших заданного числа. Если в двух или трех массивах максимумы совпадают, вывести соответствующее сообщение.
  17. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше сумма элементов, попадающих в заданный диапазон. Если в двух или трех массивах суммы совпадают, вывести соответствующее сообщение.
  18. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше сумма элементов, не попадающих в заданный диапазон. Если в двух или трех массивах суммы совпадают, вывести соответствующее сообщение.
  19. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше произведение элементов, не попадающих в заданный диапазон. Если в двух или трех массивах произведения совпадают, вывести соответствующее сообщение.
  20. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше произведение элементов, попадающих в заданный диапазон. Если в двух или трех массивах произведения совпадают, вывести соответствующее сообщение.
  21. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше среднее арифметическое элементов, меньших заданного числа. Если в двух или трех массивах значения среднего арифметического совпадают, вывести соответствующее сообщение.
  22. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше среднее арифметическое элементов, больших заданного числа. Если в двух или трех массивах значения среднего арифметического совпадают, вывести соответствующее сообщение.
  23. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше количество элементов, меньших заданного числа. Если в двух или трех массивах количества искомых элементов совпадают, вывести соответствующее сообщение.
  24. Даны три массива, содержащих разное количество элементов. Определить в каком массиве меньше количество элементов, больших заданного числа. Если в двух или трех массивах количества искомых элементов совпадают, вывести соответствующее сообщение.
  25. Даны три массива, содержащих разное количество элементов. Определить в каком массиве больше минимум элементов, больших заданного числа. Если в двух или трех массивах минимумы совпадают, вывести соответствующее сообщение.


Задача на обработку матриц

При выполнении этого задания необходимо написать две функции. Одна из этих функций должна получать и обрабатывать матрицу целиком. Другая функция должна обрабатывать одномерный массив. В качестве этого одномерного массива передаётся одна строка матрицы.
Пример программы см. в лекции 3.

  1. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше максимальный элемент, найти максимальный элемент в каждой строке.
  2. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше максимальный элемент, проверить наличие положительных элементов в каждой строке.
  3. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше максимальный элемент, найти количество положительных элементов в каждой строке.
  4. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше максимальный элемент, найти сумму положительных элементов в каждой строке.
  5. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше максимальный элемент, найти среднее арифметическое ненулевых элементов в каждой строке.
  6. Даны две матрицы произвольной размерности. Для той из матриц, в которой нет нулевых элементов, найти минимальный элемент в каждой строке.
  7. Даны две матрицы произвольной размерности. Для той из матриц, в которой нет нулевых элементов, проверить наличие отрицательных элементов в каждой строке.
  8. Даны две матрицы произвольной размерности. Для той из матриц, в которой нет нулевых элементов, найти количество отрицательных элементов в каждой строке.
  9. Даны две матрицы произвольной размерности. Для той из матриц, в которой нет нулевых элементов, найти сумму ненулевых элементов в каждой строке.
  10. Даны две матрицы произвольной размерности. Для той из матриц, в которой нет нулевых элементов, найти среднее арифметическое положительных элементов в каждой строке.
  11. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше количество нулевых элементов, найти минимальный элемент в каждой строке.
  12. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше количество нулевых элементов, проверить наличие отрицательных элементов в каждой строке.
  13. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше количество нулевых элементов, найти количество отрицательных элементов в каждой строке.
  14. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше количество нулевых элементов, найти сумму ненулевых элементов в каждой строке.
  15. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше количество нулевых элементов, найти среднее арифметическое положительных элементов в каждой строке.
  16. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше среднее арифметическое положительных элементов, найти минимальный элемент в каждой строке.
  17. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше среднее арифметическое положительных элементов, проверить наличие отрицательных элементов в каждой строке.
  18. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше среднее арифметическое положительных элементов, найти количество отрицательных элементов в каждой строке.
  19. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше среднее арифметическое положительных элементов, найти сумму ненулевых элементов в каждой строке.
  20. Даны две матрицы произвольной размерности. Для той из матриц, в которой меньше среднее арифметическое положительных элементов, найти среднее арифметическое положительных элементов в каждой строке.
  21. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше сумма ненулевых элементов, найти максимальный элемент в каждой строке.
  22. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше сумма ненулевых элементов, проверить наличие положительных элементов в каждой строке.
  23. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше сумма ненулевых элементов, найти количество положительных элементов в каждой строке.
  24. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше сумма ненулевых элементов, найти сумму положительных элементов в каждой строке.
  25. Даны две матрицы произвольной размерности. Для той из матриц, в которой больше сумма ненулевых элементов, найти среднее арифметическое ненулевых элементов в каждой строке.


Работа с двоичными файлами

В двоичном файле поменять местами две записи с заданными номерами. Обязательно проверить, что записи с такими номерами существуют в файле. При выполнении этого задания необходимо преобразовать текстовый файл в двоичный, а также вывести на экран содержимое двоичного файла до обработки и после обработки. Не забывайте закрывать файлы.
Пример программы см. в лекции 4.
  1. Каждая запись представляет собой 2 символа.
  2. Каждая запись представляет собой 2 числа типа short int.
  3. Каждая запись представляет собой 2 числа типа long int.
  4. Каждая запись представляет собой 2 числа типа float.
  5. Каждая запись представляет собой 2 числа типа double.
  6. Каждая запись представляет собой 3 символа.
  7. Каждая запись представляет собой 3 числа типа short int.
  8. Каждая запись представляет собой 3 числа типа long int.
  9. Каждая запись представляет собой 3 числа типа float.
  10. Каждая запись представляет собой 3 числа типа double.
  11. Каждая запись представляет собой 4 символа.
  12. Каждая запись представляет собой 4 числа типа short int.
  13. Каждая запись представляет собой 4 числа типа long int.
  14. Каждая запись представляет собой 4 числа типа float.
  15. Каждая запись представляет собой 4 числа типа double.
  16. Каждая запись представляет собой 5 символов.
  17. Каждая запись представляет собой 5 чисел типа short int.
  18. Каждая запись представляет собой 5 чисел типа long int.
  19. Каждая запись представляет собой 5 чисел типа float.
  20. Каждая запись представляет собой 5 чисел типа double.
  21. Каждая запись представляет собой 6 символов.
  22. Каждая запись представляет собой 6 чисел типа short int.
  23. Каждая запись представляет собой 6 чисел типа long int.
  24. Каждая запись представляет собой 6 чисел типа float.
  25. Каждая запись представляет собой 6 чисел типа double.


Работа со строками

Для решения задачи вам нужно ввести строки с помощью функций gets или fgets, разбить строку на слова и выбрать нужные (функция scanf с форматом %s вводит строку до пробела, но в данной задаче вам не нужно так делать!). Стандартные функции работы со строками НЕ использовать! Обратите внимание, что во всех случаях слова могут разделяться любым (!) количеством пробелов и знаков препинания – «. , ; : ! ? ( )». Желательно всю обработку выполнить за один проход строки, хотя это возможно не во всех вариантах.
Примеры программ см. в лекции 5.
  1. Сформировать строку из нечетных слов исходной строки.
  2. Сформировать строку из четных слов исходной строки.
  3. Сформировать строку из слов исходной строки, начинающихся на заданную букву.
  4. Сформировать строку из слов исходной строки, заканчивающихся на заданную букву.
  5. Сформировать строку из слов исходной строки, содержащих заданную букву.
  6. Сформировать строку из слов исходной строки, не содержащих заданную букву.
  7. Сформировать строку удалением всех вхождений заданной подстроки в исходную строку.
  8. Сформировать строку заменой в исходной строке заданной подстроки на другую заданную подстроку (возможно разной длинны).
  9. Сформировать строку, добавляя к каждой заданной подстроке другую заданную подстроку.
  10. Сформировать строку из тех же слов исходной строки в обратном порядке.
  11. Реверсировать буквы в каждом слове.
  12. Сформировать строку из слов исходной строки заданной длины.
  13. Сформировать строку из слов исходной строки, длина которых не равна заданной величине.
  14. Сформировать строку из слов исходной строки в алфавитном порядке.
  15. Сформировать строку из слов исходной строки в порядке увеличения количества символов в слове.
  16. Сформировать строку из слов исходной строки в порядке уменьшения количества символов в слове.
  17. Сформировать строку из первых половин каждого слова исходной строки (если слово имеет нечетную длину, то взять (n + 1) / 2 букв).
  18. Сформировать строку, «склеив» первое слово с последним, второе с предпоследним и т.д.
  19. Сформировать строку, «склеив» первое слово со слово с номером n / 2, второе – со словом с номером n / 2 + 1 и т.д. (n – количество слов в строке).
  20. Сформировать строку из слов исходной строки, содержащих повторяющиеся буквы.
  21. Сформировать строку из слов исходной строки, в которых нет повторяющихся букв.
  22. Сформировать строку, удалив из каждого слова исходной строки повторяющиеся в нем буквы.
  23. Сформировать строку, добавляя к каждой заданной подстроке другую заданную подстроку.
  24. Сформировать строку из слов исходной строки заданной длины.
  25. Сформировать строку из слов исходной строки, содержащих повторяющиеся буквы.


Работа со структурами

Ввести массив, состоящий из заданных структур. Вывести структуры, удовлетворяющие заданному условию. Ввод/вывод производить из файла/в файл (пример см. в лекции 4).
Пример программы см. в лекции 6.
  1. Информация о студенте содержит ФИО, год рождения, группу, телефон. Вывести информацию о студентах заданной группы.
  2. Информация о студенте содержит ФИО, год рождения, группу, телефон. Вывести информацию о студентах заданного года рождения.
  3. Информация о студенте содержит ФИО, год рождения, группу, телефон. Вывести информацию о студентах, возраст которых меньше заданного.
  4. Информация о студенте содержит ФИО, оценки по математике, физике и информатике. Вывести информацию о студентах, имеющих только 5.
  5. Информация о студенте содержит ФИО, оценки по математике, физике и информатике. Вывести информацию о студентах, не имеющих 5.
  6. Информация о студенте содержит ФИО, оценки по математике, физике и информатике. Вывести информацию о студентах, имеющих только 4 и 5.
  7. Информация о студенте содержит ФИО, оценки по математике, физике и информатике. Вывести информацию о студентах, имеющих только положительные оценки.
  8. Информация о студенте содержит ФИО, оценки по математике, физике и информатике. Вывести информацию о студентах, имеющих хотя бы одну 2.
  9. Информация о сотруднике содержит ФИО, должность, отдел, телефон. Вывести информацию о сотрудниках, работающих на заданной должности.
  10. Информация о сотруднике содержит ФИО, должность, отдел, телефон. Вывести информацию о сотрудниках, работающих в заданном отделе.
  11. Информация о группе содержит номер группы, факультет, количество студентов, фамилию старосты. Вывести информацию о группах заданного факультета.
  12. Информация о группе содержит номер группы, факультет, количество студентов, фамилию старосты. Вывести информацию о группах, количество студентов в которых больше заданного.
  13. Информация о факультете содержит название, количество студентов, количество групп, фамилию декана. Вывести информацию о факультетах, количество студентов в которых меньше заданного числа.
  14. Информация о факультете содержит название, количество студентов, количество групп, фамилию декана. Вывести информацию о факультетах, количество групп в которых больше заданного числа.
  15. Информация о дисциплине содержит название, количество часов лекций, количество часов практических занятий, фамилию преподавателя. Вывести информацию о дисциплинах, количество часов лекций которых больше заданного.
  16. Информация о дисциплине содержит название, количество часов лекций, количество часов практических занятий, фамилию преподавателя. Вывести информацию о дисциплинах, количество часов практических занятий которых меньше заданного.
  17. Информация о дисциплине содержит название, количество часов лекций, количество часов практических занятий, фамилию преподавателя. Вывести информацию о дисциплинах, которые читаются заданным преподавателем.
  18. Информация о файле содержит название, директорию размещения, размер, дату создания. Вывести информацию о файлах, находящихся в заданной директории.
  19. Информация о файле содержит название, директорию размещения, размер, дату создания. Вывести информацию о файлах заданного размера.
  20. Информация о файле содержит название, директорию размещения, размер, дату создания. Вывести информацию о файлах, дата создания которых меньше заданной.
  21. Информация о стране содержит название, площадь, население, столицу. Вывести информацию о странах, площадь которых больше заданной.
  22. Информация о стране содержит название, площадь, население, столицу. Вывести информацию о странах, население которых меньше заданного.
  23. Информация о городе содержит название, страну, площадь, население. Вывести информацию о городах заданной страны.
  24. Информация о городе содержит название, страну, площадь, население. Вывести информацию о городах, площадь которых меньше заданной.
  25. Информация о городе содержит название, страну, площадь, население. Вывести информацию о городах, население которых больше заданного.


Работа со списком

Переделать задачу со структурами следующим образом: необходимо построить список из структур заданного типа и из полученного списка удалить элементы, не удовлетворяющие заданному условию. Ввод/вывод производить из файла/в файл (пример см. в лекции 4).
Пример программы см. в лекции 7.


Задача на поразрядные операции

Написать функцию, указанную в задании, используя только операции над битами. Написать программу для проверки правильности работы функции. Для того, чтобы было удобно проверять правильность работы с битами, исходное и полученное число можно вывести, используя формат %08x - целое число в шестнадцатеричной системе счисления, 8 знаков, впереди добавляются нули.
Примеры программ см. в лекции 8.
  1. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  2. Написать функцию, которая в числе x устанавливает 0 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  3. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся слева от позиции p, остальные биты остаются без изменения.
  4. Написать функцию, которая в числе x устанавливает 0 в n бит, находящиеся слева от позиции p, остальные биты остаются без изменения.
  5. Написать функцию, которая возвращает число x после выполнения в нем циклического сдвига вправо на n бит.
  6. Написать функцию, которая возвращает величину x после выполнения в ней циклического сдвига влево на n бит.
  7. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  8. Написать функцию, которая в числе x устанавливает 0 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  9. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся слева от позиции p, остальные биты остаются без изменения.
  10. Написать функцию, которая в числе x устанавливает 0 в n бит, находящиеся слева от позиции p, остальные биты остаются без изменения.
  11. Написать функцию, которая возвращает число x после выполнения в нем циклического сдвига вправо на n бит.
  12. Написать функцию, которая возвращает величину x после выполнения в ней циклического сдвига влево на n бит.
  13. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  14. Написать функцию, которая в числе x устанавливает 0 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  15. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся слева от позиции p, остальные биты остаются без изменения.
  16. Написать функцию, которая в числе x устанавливает 0 в n бит, находящиеся слева от позиции p, остальные биты остаются без изменения.
  17. Написать функцию, которая возвращает число x после выполнения в нем циклического сдвига вправо на n бит.
  18. Написать функцию, которая возвращает величину x после выполнения в ней циклического сдвига влево на n бит.
  19. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  20. Написать функцию, которая в числе x устанавливает 0 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  21. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  22. Написать функцию, которая в числе x устанавливает 0 в n бит, находящихся справа от позиции p, остальные биты остаются без изменения.
  23. Написать функцию, которая в числе x устанавливает 1 в n бит, находящихся слева от позиции p, остальные биты остаются без изменения.
  24. Написать функцию, которая в числе x устанавливает 0 в n бит, находящиеся слева от позиции p, остальные биты остаются без изменения.
  25. Написать функцию, которая возвращает число x после выполнения в нем циклического сдвига вправо на n бит.


Создание простого класса

В данной работе вам необходимо разработать класс для представления некоторого объекта. В первую очередь необходимо разработать интерфейс класса, затем множество переменных для представления данных о заданном объекте. Для множества обязательно проверять, чтобы каждый элемент входил в множество только один раз! Кроме указанных в задании функций вы можете определить любые другие функции, которые вы сочтёте нужными. Необходимо также написать программу, демонстрирующую возможности разработанного класса.
Пример программы см. в лекции 11.

  1. Разработать класс для представления объекта множество символов *. Определить конструктор с одним параметром-строкой, который задаёт элементы множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа char в множество и из множества в переменную типа char.
  2. Разработать класс для представления объекта множество чисел *. Определить конструктор с одним параметром-числом, который задаёт элемент множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в множество и из множества в переменную типа int.
  3. Разработать класс для представления объекта строка. Определить конструктор с одним параметром целого типа – длина строки, который можно использовать как конструктор умолчания. Определить конструктор, который копирует в новую строку n первых символов другой строки и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из вещественного числа в строку, представляющую это число, и из строки в вещественное число.
  4. Разработать класс для представления объекта матрица, состоящая из элементов типа int. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  5. Разработать класс для представления объекта матрица, состоящая из элементов типа double. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа double в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  6. Разработать класс для представления объекта множество символов *. Определить конструктор с одним параметром-строкой, который задаёт элементы множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа char в множество и из множества в переменную типа char.
  7. Разработать класс для представления объекта множество чисел *. Определить конструктор с одним параметром-числом, который задаёт элемент множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в множество и из множества в переменную типа int.
  8. Разработать класс для представления объекта строка. Определить конструктор с одним параметром целого типа – длина строки, который можно использовать как конструктор умолчания. Определить конструктор, который копирует в новую строку n первых символов другой строки и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из вещественного числа в строку, представляющую это число, и из строки в вещественное число.
  9. Разработать класс для представления объекта матрица, состоящая из элементов типа int. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  10. Разработать класс для представления объекта матрица, состоящая из элементов типа double. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа double в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  11. Разработать класс для представления объекта множество символов *. Определить конструктор с одним параметром-строкой, который задаёт элементы множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа char в множество и из множества в переменную типа char.
  12. Разработать класс для представления объекта множество чисел *. Определить конструктор с одним параметром-числом, который задаёт элемент множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в множество и из множества в переменную типа int.
  13. Разработать класс для представления объекта строка. Определить конструктор с одним параметром целого типа – длина строки, который можно использовать как конструктор умолчания. Определить конструктор, который копирует в новую строку n первых символов другой строки и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из вещественного числа в строку, представляющую это число, и из строки в вещественное число.
  14. Разработать класс для представления объекта матрица, состоящая из элементов типа int. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  15. Разработать класс для представления объекта матрица, состоящая из элементов типа double. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа double в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  16. Разработать класс для представления объекта множество символов *. Определить конструктор с одним параметром-строкой, который задаёт элементы множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа char в множество и из множества в переменную типа char.
  17. Разработать класс для представления объекта множество чисел *. Определить конструктор с одним параметром-числом, который задаёт элемент множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в множество и из множества в переменную типа int.
  18. Разработать класс для представления объекта строка. Определить конструктор с одним параметром целого типа – длина строки, который можно использовать как конструктор умолчания. Определить конструктор, который копирует в новую строку n первых символов другой строки и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из вещественного числа в строку, представляющую это число, и из строки в вещественное число.
  19. Разработать класс для представления объекта матрица, состоящая из элементов типа int. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  20. Разработать класс для представления объекта матрица, состоящая из элементов типа double. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа double в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  21. Разработать класс для представления объекта множество символов *. Определить конструктор с одним параметром-строкой, который задаёт элементы множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа char в множество и из множества в переменную типа char.
  22. Разработать класс для представления объекта множество чисел *. Определить конструктор с одним параметром-числом, который задаёт элемент множества и который можно использовать как конструктор умолчания. Определить конструктор, которые копирует в новое множество элементы другого множества, не превышающие заданное значение, который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в множество и из множества в переменную типа int.
  23. Разработать класс для представления объекта строка. Определить конструктор с одним параметром целого типа – длина строки, который можно использовать как конструктор умолчания. Определить конструктор, который копирует в новую строку n первых символов другой строки и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из вещественного числа в строку, представляющую это число, и из строки в вещественное число.
  24. Разработать класс для представления объекта матрица, состоящая из элементов типа int. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа int в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
  25. Разработать класс для представления объекта матрица, состоящая из элементов типа double. Определить конструктор с двумя параметрами целого типа – размерность матрицы, который можно использовать как конструктор умолчания. Определить конструктор, который создаёт новую матрицу таким образом, что все её элементы больше элементов другой матрицы на заданное число, и который можно использовать как конструктор копирования. Определить деструктор. Определить преобразования из переменной типа double в матрицу – заполнение матрицы и из матрицы в переменную типа double – среднее арифметическое элементов матрицы.
Содержание

* Попробуйте представить множество как набор бит, где бит с номером n равен 1, если символ с кодом n присутствует в множестве, и 0 – в противном случае. При таком представлении множества теоретико- множественные операции реализуются за счёт побитовых операций элементарным образом, и кроме того, не нужно делать проверку на повторное вхождение элемента в множество.


Перегрузка операций

В класс, созданный при выполнении задания «Создание простого класса» добавить указанные в задании функции-члены класса. Обратите внимание, что операции «+», «-» и «*» должны формировать новый объект, а операция «+=» – изменять объект, к которому она применяется.
Пример программы см. в лекции 12.

  1. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  2. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  3. Оператор присваивания; оператор сравнения; операция «[ ]» – доступ к одному символу строки; операция «+» – конкатенация строк; операция «-» – удаление из строки всех вхождений заданной подстроки; операция «+=» – добавление символа в конец строки.
  4. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  5. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  6. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  7. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  8. Оператор присваивания; оператор сравнения; операция «[ ]» – доступ к одному символу строки; операция «+» – конкатенация строк; операция «-» – удаление из строки всех вхождений заданной подстроки; операция «+=» – добавление символа в конец строки.
  9. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  10. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  11. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  12. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  13. Оператор присваивания; оператор сравнения; операция «[ ]» – доступ к одному символу строки; операция «+» – конкатенация строк; операция «-» – удаление из строки всех вхождений заданной подстроки; операция «+=» – добавление символа в конец строки.
  14. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  15. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  16. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  17. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  18. Оператор присваивания; оператор сравнения; операция «[ ]» – доступ к одному символу строки; операция «+» – конкатенация строк; операция «-» – удаление из строки всех вхождений заданной подстроки; операция «+=» – добавление символа в конец строки.
  19. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  20. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  21. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  22. Оператор присваивания; оператор сравнения; операция «+» – объединение множеств; операция «*» – пересечение множеств; операция «-» – вычитание множеств; операция «+=» – добавление нового элемента в множество.
  23. Оператор присваивания; оператор сравнения; операция «[ ]» – доступ к одному символу строки; операция «+» – конкатенация строк; операция «-» – удаление из строки всех вхождений заданной подстроки; операция «+=» – добавление символа в конец строки.
  24. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.
  25. Оператор присваивания; оператор сравнения; операция «[ ][ ]» – доступ к элементу матрицы; операция «+» – сложение матриц; операция «-» – вычитание матриц; операция «+=» – прибавление заданного значения ко всем элементам матрицы.


Дружественные функции

Операции «+», «-», «*» и «+=», созданные при выполнении задания «Перегрузка операций», реализовать как дружественные функции.
Пример программы см. в лекции 13.


Производные классы

Разработать структуру из базовых и производных классов. Написать программу, демонстрирующую работу реализованных классов.
Пример программы см. в лекции 14.

  1. Разработать структуру классов «пользователь - опытный пользователь - администратор». Информация о каждом пользователе содержит имя и пароль. Простые пользователи имеют право читать файлы. Опытные пользователи имеют право читать, создавать и удалять файлы. Администраторы имеют право читать, создавать и удалять файлы, а также создавать и удалять пользователей любого уровня.
  2. Разработать структуру классов для работы с «записной книжкой». Простое сообщение содержит дату, когда оно должно быть выдано, и текст сообщения. Простое сообщение выдаётся один раз и после этого удаляется. Кроме простых сообщений могут быть многократные сообщения двух типов: сообщения первого типа автоматически переносятся на следующий день, для сообщений второго типа дата следующей выдачи сообщения запрашивается у пользователя.


Абстрактные классы

Пример программы см. в лекции 15.


Шаблоны

Разработать шаблон для представления некоторого множества классов. Параметрами шаблона должны быть тип элементов класса и, возможно, количество элементов класса.
Пример программы см. в лекции 16.

  1. Разработать шаблон для классов множество.
  2. Разработать шаблон для классов матрица.
  3. Разработать шаблон для классов список.
  4. Разработать шаблон для классов множество.
  5. Разработать шаблон для классов матрица.
  6. Разработать шаблон для классов список.
  7. Разработать шаблон для классов множество.
  8. Разработать шаблон для классов матрица.
  9. Разработать шаблон для классов список.
  10. Разработать шаблон для классов множество.
  11. Разработать шаблон для классов матрица.
  12. Разработать шаблон для классов список.
  13. Разработать шаблон для классов множество.
  14. Разработать шаблон для классов матрица.
  15. Разработать шаблон для классов список.
  16. Разработать шаблон для классов множество.
  17. Разработать шаблон для классов матрица.
  18. Разработать шаблон для классов список.
  19. Разработать шаблон для классов множество.
  20. Разработать шаблон для классов матрица.
  21. Разработать шаблон для классов список.
  22. Разработать шаблон для классов множество.
  23. Разработать шаблон для классов матрица.
  24. Разработать шаблон для классов список.
  25. Разработать шаблон для классов множество.


Критерии качества программы

  • Корректность – очевидно, что программа должна работать правильно, иначе нет смысла ее писать (пример из олимпиады).
  • Надежность – программа не должна «зависать» или «зацикливаться» при любых исходных данных.
  • Эффективность – программа должна использовать, по возможности, минимальное количество ресурсов, как по памяти (хотя в настоящее время это стало менее актуально), так и по времени (это проблема остается актуальной, особенно при обработке больших массивов данных или при решении задач, в которых время обработки экспоненциально зависит от размерности исходных данных).
  • Эргономичность – удобство для пользователя. Не забывайте, что первым пользователем Вашей программы будете Вы сами.
  • Читабельность – удобство для программиста. К сожалению, по прошествии времени, программа забывается, и давно написанную Вами программу невозможно прочитать как книгу, Вы должны заново принимать все когда-то принятые решения. Поэтому нет смысла создавать себе дополнительные трудности в виде плохо структурированного и плохо читаемого текста. Для решения этой проблемы существует ряд принципов написания текста программы.
    1. Не стоит писать, как слишком длинные строки – они уходят за пределы экрана по ширине, так и слишком короткие строки – они увеличивают длину не только всей программы, но и каждого отдельного блока, который Вы в этом случае не можете охватить взглядом и оценить, что он делает.
    2. Принято вложенные блоки писать со смещением вправо.
    3. Используйте везде, где можно, аккуратные столбцы.
    4. Не жалейте пробелов – «а + b» читается лучше, чем «a+b».
    5. Не прячьте операторные скобки (begin … end в Паскале, {…} в С) в конец строки – в этом случае их трудно найти и определить начало и конец блока.
  • Переносимость – программа должна работать не только на Вашем компьютере, но и на других!


Список вопросов

  1. Типы данных языка С.
  2. Операции языка С. Приоритет операций.
  3. Операторы языка С.
  4. Структура программы на языке С.
  5. Препроцессор. Директивы препроцессора.
  6. Массивы в языке С.
  7. Работа с файлами. Текстовый и двоичный режим.
  8. Указатели. Адресная арифметика.
  9. Строки в языке С.
  10. Перечислимый тип. Структуры. Объединения.
  11. Динамическое распределение памяти.
  12. Побитовые операции.
  13. Время жизни и область действия.
  14. Спецификаторы класса памяти.
  15. Понятие класса.
  16. Функции-члены класса. Указатель this.
  17. Конструкторы. Деструкторы.
  18. Преобразования объектов класса.
  19. Статические члены класса.
  20. Доступ к членам класса.
  21. Друзья класса.
  22. Совместное использование.
  23. Перегрузка операций.
  24. Производные классы.
  25. Виртуальные функции. Абстрактные классы.


Контрольные вопросы

Вопрос № 1

Какие типы данных существуют в языке С?

char
unsigned char
integer
long int
real
double
string


Общие принципы написания программ под Windows с использованием MFC AppWizard

Общие принципы написания программ под Windows
с использованием MFC AppWizard

Windows – это система окон. Окнами являются практически все видимые объекты (иногда и невидимые тоже): окно приложения, окно документа внутри приложения, диалог, элементы диалога и т.д. Окна приложений являются окнами верхнего уровня. Окна, которые находятся внутри другого окна или вызываются из другого окна, – например, окна документов внутри приложения, окно диалога, вызванного некоторым приложением, элементы диалога внутри диалога – являются подчинёнными, и для каждого из них определено родительское окно – т.е. окно, внутри которого они находятся или из которого были вызваны. Подчинённое окно может иметь свои подчинённые окна.
Поскольку все эти объекты принадлежат к одному классу, есть общие принципы работы с ними. Например, каждое окно имеет размеры и координаты, определяющие его местоположение. Причём, эти координаты могут измеряться относительно левого верхнего угла дисплея, а могут (для подчинённых окон) относительно левого верхнего угла родительского окна. Иногда приходится преобразовывать координаты из одного способа представления в другой, для этого существуют функции ScreenToClient и ClientToScreen.
Ряд функций также являются общими для всех окон. Например, функции EnableWindow, с помощью которой делают доступными или недоступными элементы диалога, или SetWindowText, с помощью которой можно поменять заголовок окна, надпись на кнопке, текст элементов Radio Button и Check Box и т.д. Но поскольку окна всё-таки разные, существуют функции, специфичные для каждого типа окон. Например, для элемента Edit Box существуют функции работы с текстом и с буфером обмена, а для кнопки или элемента Check Box таких функций не существует.
Для взаимодействия между окнами и системой и между окнами существуют сообщения. Каждое событие, такое как нажатие клавиши или кнопки мыши, движение мыши и т.д., вызывает посылку сообщения (а иногда и нескольких) соответствующему окну. Программа под Windows состоит именно из обработки таких сообщений.
Для каждого типа окна существует свой класс – набор данных и функций. Когда вы создаёте свою программу, все существующие в ней классы для различных окон являются классами, производными из какого-либо стандартного класса – т.е. ряд данных и функций уже определены для этого класса, и, кроме того, вы можете добавить данные или функции или переопределить некоторые функции. Например, когда вы создаёте диалог, вы добавляете в стандартный класс CDialog данные для представления именно тех элементов диалога, которые должны быть в вашем диалоге, и переопределяете некоторые стандартные функции, например, OnInitDialog, которая задаёт начальное состояние элементов диалога.
Основными классами, необходимыми для создания мультидокументного приложения – т.е. приложения, в котором могут быть открыты сразу несколько документов – являются CDocument, CView (и различные производные от него классы), CMDIFrameWnd, CMDIChildWnd, а также класс CDialog, используемый для создания диалогов, которые являются распространённым элементом приложений.
Класс CDocument используется для хранения данных документа и выполнения таких действий с документом, как создание нового документа, открытие существующего документа, сохранение документа, чтение из файла и запись в файл. За отображение же данных документа на экране отвечает класс CView. В общем случае на экране могут быть представлены не все данные документа. Кроме того, в принципе, возможно создавать разные отображения одного документа. В классе CView должны существовать переменные и функции, которые позволяют отображать данные документа на экране. Таким образом, данные, хранящиеся в файле, проходят длинный путь до отображения на экране: сначала надо прочитать их в переменные документа, потом связать данные документа с данными класса CView, и, наконец, отобразить данные класса CView непосредственно на экране. Для выполнения последнего действия обычно используются функции OnDraw или UpdateData, если вид документа основан на диалоге.
Для связи между классами CDocument и CView существуют функции GetDocument и GetNextView, позволяющие получить соответственно документ, с которым связано некоторое отображение, и отображение некоторого документа.
Классы CMDIFrameWnd и CMDIChildWnd используются для создания соответственно окна приложения и окон документов и для работы с такими частями приложения, как меню, панель инструментов, строка состояния.
При использовании MFC AppWizard часть программы, которая одинакова для всех приложений, генерируется автоматически. Таким образом, вам не нужно думать, как отобразить окно вашей программы, передвинуть его, создать окно документа, вызвать диалоги открытия и сохранения и т.д. Вы должны писать только действия, специфичные для вашей конкретной программы.
При автоматической генерации программы MFC AppWizard создаёт для вашей программы классы, производные из описанных выше, задавая им имена C<имя вашей программы>Doc, C<имя вашей программы>View, CMainFrame и CChildFrame.

Файл в формате Word


 Лекции
Задания
Литература
Предметный указатель
Типовой расчёт
Контрольные вопросы
Поиск

В примерах

Расширенный
поиск


 По языку С я использовала книгу Бочков С.О., Субботин Д.М. Язык программирования Си для персонального компьютера. М:. Радио и связь, 1990. Хоть эта книга и не новая, но она написана очень хорошо, в ней есть все необходимые сведения по С, изложенные очень чётко и аккуратно. Также можно использовать ???.
По языку С++ я испльзовала книгу М. Эллис, Б. Страуструп. Справочное руководство по языку программирования С++ с комментариями. М.: Мир, 1992. Можно использовать любые другие книги Страуструпа (он является создателем языка С++), а также ???.
Обязательно прочитайте книгу Ален И. Голуб. С и С++. Правила программирования. М.: Восточная Книжная Компания, 1996. Эта книга предназначеня для тех, кто знает языки С и С++, она содержит лишь правила, которые автор вывел на основании своего опыта программирования. Однако она написана очень интересно, и даже если Вы не во всём согласитесь с автором, она будет Вам полезна.


Алфавит и основные понятия языка С

Лекция 1. Алфавит и основные понятия языка С

Введение

Также как и Паскаль, язык С является языком высокого уровня, однако его существенно отличают от Паскаля ряд низкоуровневых черт, а главное – другая логика, другая идеология, другое «отношение к программисту». Как сказано в одной хорошей книге, на нем «можно сделать всё, что угодно, даже если вы того и не хотите». По сравнению с Паскалем язык С предоставляет программисту гораздо больше свободы, но зато требует от него самоконтроля – делай, что хочешь, но ты, и только ты, отвечаешь за то, что получится!
Среди современных языков программирования язык C является одним из наиболее распространенных. Язык C универсален, однако наиболее эффективно его применение в задачах системного программирования – разработке трансляторов, операционных систем, инструментальных средств. Язык C хорошо зарекомендовал себя эффективностью, лаконичностью записи алгоритмов, логической стройностью программ. Во многих случаях программы, написанные на языке C, сравнимы по скорости с программами, написанными на Ассемблере, при этом они более наглядны и просты в сопровождении.
Язык C имеет ряд существенных особенностей, которые выделяют его среди других языков программирования. В значительной степени на формирование идеологии языка повлияла цель, которую ставили перед собой его создатели – обеспечение системного программиста удобным инструментальным языком, который мог бы заменить Ассемблер. В результате появился язык программирования высокого уровня, обеспечивающий необычайно легкий доступ к аппаратным средствам компьютера. С одной стороны, как и другие современные языки высокого уровня, язык C поддерживает полный набор конструкций структурного программирования, модульность, блочную структуру программы. С другой стороны, язык C имеет ряд низкоуровневых черт.

Перечислим некоторые особенности языка C.
  • В языке C реализован ряд операций низкого уровня. Некоторые из таких операций напрямую соответствуют машинным командам, например, поразрядные операции или операции ++ и --.
  • Базовые типы данных языка C отражают те же объекты, с которыми приходится иметь дело в программе на Ассемблере – байты, машинные слова и т.д. Несмотря на наличие в языке C развитых средств построения составных объектов (массивов и структур), в нем практически отсутствуют средства для работы с ними как с единым целым.
  • Язык C поддерживает механизм указателей на переменные и функции. Указатель – это переменная, предназначенная для хранения машинного адреса некоторой переменной или функции. Поддерживается арифметика указателей, что позволяет осуществлять непосредственный доступ и работу с адресами памяти практически так же легко, как на Ассемблере. Использование указателей позволяет создавать высокоэффективные программы, однако требует от программиста особой осторожности.
  • Как никакой другой язык программирования высокого уровня, язык C «доверяет» программисту. Даже в таком существенном вопросе, как преобразование типов данных, налагаются лишь незначительные ограничения. Однако это также требует от программиста осторожности и самоконтроля.
  • Несмотря на эффективность и мощность конструкция языка C, он относительно мал по объему. В нем отсутствуют встроенные операторы ввода/вывода, динамического распределения памяти, управления процессами и т.п., однако в системное окружение языка C входит библиотека стандартных функций, в которой реализованы подобные действия.

1. Алфавит

Множество символов языка C включает:
  • прописные буквы латинского алфавита;
  • строчные буквы латинского алфавита;
  • арабские цифры;
  • разделители: , . ; : ? ' " ! | / \ ~ _ ^ ( ) { } [ ] < > # % & - = + *
Остальные символы могут быть использованы только в символьных строках, символьных константах и комментариях. Язык C различает большие и маленькие буквы, таким образом, name и Name – разные идентификаторы.

2. Константы

Константы в языке C могут быть целые, вещественные, символьные и строковые.
  • Целые:
    • десятичные: 10, 132, -32179;
    • восьмеричные (предваряются символом «0»): 010, , 0204, -076663;
    • шестнадцатеричные (предваряются символами «0х»): 0хА, 0x84, 0x7db3.
  • Вещественные: 15.75, 1.575e1, .75, -.125
  • Символьные: 'a', '.', 'e', '2'.
  • Строковые: "строка".

3. Комментарии

Комментарий – это последовательность символов, которая игнорируется компилятором языка C. Комментарий имеет следующий вид: /*<символы>*/. Комментарии могут занимать несколько строк, но не могут быть вложенными. Кроме того, часть строки, следующая за символами «//», также рассматривается как комментарий.

4. Типы данных языка C

Имя | Размер | Представляемые значения | Диапазон
(signed) char | 1 байт | символы целые числа | от –128 до 127
(signed) short int | 2 байта | целые числа | от -32768 до 32767
(signed) int | зависит от реализации (в последних компиляторах обычно 4 байта) | целые числа |
(signed) long int | 4 байта | целые числа | от -2147483648 до 2147483647
unsigned char | 1 байт | символы целые числа | от 0 до 255
unsigned short int | 2 байта | целые числа | 0 до 65535
unsigned int | зависит от реализации (в последних компиляторах обычно 4 байта) | целые числа |
unsigned long int | 4 байта | целые числа | от 0 до 4294967295
float | 4 байта | вещественные числа | от 1.175494351e–38 до 3.402823466e+38
double | 8 байт | вещественные числа | от 2.2250738585072014e–308 до 1.7976931348623158e+308
long double | зависит от реализации | вещественные числа |

В языке C также существуют перечислимый тип – enum, который является подмножеством целого типа, и пустой тип – void, который имеет специальное назначение. Он используется для объявления функций, которые не возвращают никакого значения, а также для объявления указателей на значение типа void. Такие указатели могут быть преобразованы к указателям на любой другой тип.
В языке С можно объявлять структуры и так называемые объединения.
В языке C нет специальных типов для массивов, строк (которые представляются массивом символов), и для логических значений. Логические значения представляются данными целого типа, при этом значение 0 соответствует логическому значению ложь, а все остальные целые значения соответствуют логическому значению истина. Этим свойством языка C можно пользоваться, например, следующим образом: значение некоторой переменной, равное 0, говорит об отсутствии некоторого признака у объекта, а остальные значения говорят о его наличии, и при этом несут какую-либо дополнительную информацию.

В языке С контроль типов не так силён, как в Паскале. Вы можете выполнять любые разумные присвоения между переменными разных типов. Кроме того, при выполнении большинства операций производятся преобразования по умолчанию для приведения операндов к одному и тому же типу:
  • все операнды типа float преобразуются к типу double ;
  • если один операнд имеет тип double , то второй операнд преобразуется к типу double ;
  • если один операнд имеет тип unsigned long , то второй операнд преобразуются к типу unsigned long ;
  • если один операнд имеет тип long int , то второй операнд преобразуются к типу long int ;
  • если один операнд имеет тип unsigned int , то второй операнд преобразуются к типу unsigned int ;
  • все операнды типов char и short преобразуются к типу int ;
  • все операнды типов unsigned char и unsigned short преобразуются к типу unsigned int ;
  • иначе оба операнда имеют тип int .
В языке С нет операций преобразованием между символом и кодом символа, т.к. в оперативной памяти символ и так храниться в виде его кода. Поэтому можно к переменной, хранящей символ, прибавить 1 и получить следующий символ.
Контроль типов языка С можно (но не нужно!) обойти с помощью операции приведения типа.

5. Операции языка C

Приоритет и ассоциативность операций в языке C:

Знак операции | Наименование | Ассоциативность
( ) [ ] . -> | Первичные | Слева направо
- ~ ! * & ++ -- sizeof приведение типа | Унарные | Справа налево
* / % | Мультипликативные | Слева направо
+ - | Аддитивные | Слева направо
<< >> | Сдвиг | Слева направо
< > <= >= | Отношение | Слева направо
== != | Отношение | Слева направо
& | Поразрядное И | Слева направо
^ | Поразрядное исключающее ИЛИ | Слева направо
| | Поразрядное ИЛИ | Слева направо
&& | Логическое И | Слева направо
|| | Логическое ИЛИ | Слева направо
? : | Условная операция | Справа налево
= *= /= %= += -= <<= >>= &= ^= |= | Простое и составное присваивания | Справа налево
, | Операция последовательного вычисления | Слева направо
  • ( ) – выражение в скобках (используется для изменения порядка вычисления) или вызов функции.
  • [ ] – индексное выражение, используется для работы с массивами.
  • . и -> – выбор элемента, используются при работе со структурами и объединениями.
  • - – унарный минус.
  • ~ – обратный код (см. лекцию 8).
  • ! – логическое отрицание.
  • * – косвенная адресация (см. лекцию 5).
  • & – адресация (см. лекцию 5).
  • Операции ++ и -- инкрементируют (увеличивают на 1) и декрементируют (уменьшают на 1) свой операнд. Операнд должен иметь целый, вещественный тип или быть указателем. Операции инкремента и декремента могут записываться как перед своим операндом (префиксная форма записи), так и после него (постфиксная форма записи). При префиксной форме записи операнд сначала инкрементируется или декрементируется, а затем его новое значение участвует в дальнейшем вычислении выражения, содержащего данную операцию. При постфиксной форме записи операнд инкрементируется или декрементируется лишь после того, как его старое значение участвует в вычислении выражения. Таким образом, результатом операций инкремента и декремента является либо новое, либо старое значение операнда. Например, если переменная i = 0, то выражение a[++i] = 1 меняет элемент a[1], а выражение a[i++] = 1 меняет элемент a[0]. В обоих случая переменная i получает новое значение, равное 1.
  • sizeof – вычисление размера в байтах переменной или типа.
  • Операция приведения типа записывается следующим образом: (<новый тип>)<выражение>. Например, (long int)n приводит переменную n к типу long int. При преобразовании типов надо помнить, что при преобразовании между знаковыми/беззнаковыми значениями и при преобразовании от типа с большей размерностью к типу с меньшей размерностью могут возникнуть ошибки.
  • % – остаток от деления.
  • В языке C имеется одна тернарная операция – условная операция. Она имеет следующий синтаксис: <операнд 1> ? <операнд 2> : <операнд 3>. Если <операнд 1> имеет ненулевое значение, то вычисляется <операнд 2> и результатом условной операции является его значение. Если же <операнд 1> равен нулю, то вычисляется <операнд 3> и результатом является его значение. В любом случае вычисляется только один из операндов, <операнд 2> или <операнд 3>, но не оба.
  • Простое присваивание. Операция простого присваивания обозначается знаком «=». Значение правого операнда присваивается левому операнду. Операция вырабатывает результат, который может быть далее использован в выражении. Результатом операции является присвоенное значение. Например, выражение a = b = c = 0 присваивает всем переменным значение 0, а в результате вычисления выражения a = (b = 3) + (c = 5) переменная c будет иметь значение 5, переменная b будет иметь значение 3, и переменная a будет иметь значение 8.
  • Составное присваивание. Операция составного присваивания состоит из простой операции присваивания, скомбинированной с какой-либо другой бинарной операцией. При составном присваивании вначале выполняется действие, специфицированное бинарной операцией, а затем результат присваивается левому операнду. Оператор n += 5 эквивалентен оператору n = n + 5, но при этом первый оператор легче для понимания и выполняется быстрее.
  • Операция последовательного вычисления «,» обычно используется для вычисления нескольких выражений в ситуациях, где по синтаксису допускается только одно выражение. Однако, запятая, разделяющая параметры функции, не является операцией последовательного вычисления.
Операция последовательного вычисления, логические операции «И» и «ИЛИ» и условная операция гарантируют определенный порядок вычисления своих операндов. Операция последовательного вычисления обеспечивает вычисление своих операндов по очереди, слева направо. Условная операция вычисляет сначала свой первый операнд, а затем, в зависимости от его значения, либо второй, либо третий операнд. Логические операции также обеспечивают вычисление своих операндов слева направо. Однако логические операции вычисляют минимальное число операндов, необходимое для определения результата выражения. Таким образом, второй операнд выражения может вообще не вычисляться.


Операции и операторы языка С. Структура программы

Лекция 2. Операции и операторы языка С.
Структура программы

1. Операторы языка C

Операторы управляют процессом выполнения программы. Набор операторов языка C содержит все управляющие конструкции структурного программирования. В составе некоторых операторов используются выражения, играющие роль условий. Так как в языке C отсутствует логический тип, в качестве условных выражений используют обычные выражения языка. Значение выражения считается истинным, если оно не равно нулю, и ложным, если оно равно нулю. Из этого следует, что условные выражения не обязательно должны содержать операции отношения. Составной оператор ограничивается фигурными скобками. Все другие операторы заканчиваются точкой с запятой.
  • Пустой оператор – ;
    Пустой оператор – это оператор, состоящий только из точки с запятой. Он может появиться в любом месте программы, где по синтаксису требуется оператор. Выполнение пустого оператора не меняет состояния программы.
  • Составной оператор – {...}
    Действие составного оператора состоит в последовательном выполнении содержащихся в нем операторов, за исключение тех случаев, когда какой-либо оператор явно передает управление в другое место программы.
  • Условный оператор
    if (<выражение>) <оператор 1> [else <оператор 2>]
  • Оператор цикла с предусловием
    while (<выражение>) <оператор>
  • Оператор цикла с постусловием
    do <оператор> while <выражение>;
    В языке C этот оператор отличается от классической реализации цикла с постусловием тем, что при истинности <выражения> происходит продолжение работы цикла, а не выход из цикла.
  • Оператор пошагового цикла
    for ([<начальное выражение>];
    [<условное выражение>];
    [<выражение приращения>])
    <оператор>
    Тело оператора for выполняется до тех пор, пока <условное выражение> не станет ложным. <Начальное выражение> и <выражение приращения> обычно используются для инициализации и модификации параметров цикла и других значений. <Начальное выражение> вычисляется один раз до первой проверки <условного выражения>, а <выражение приращения> вычисляется после каждого выполнения <оператора>. Любое из трех выражений заголовка цикла, и даже все три могут быть опущены (не забывайте только оставлять точки с запятой). Если опущено <условное выражение>, то оно считается истинным, и цикл становится бесконечным.
    Оператор пошагового цикла в языке C является гибкой и удобной конструкцией, поэтому оператор цикла с предусловием while используется в языке C крайне редко, т.к. в большинстве случаев удобнее пользоваться оператором for.
  • Оператор-переключатель
    switch (<выражение>)
    { case <константное выражение 1>: <оператор 1>
    case <константное выражение 2>: <оператор 2>
    …
    case <константное выражение N>: <оператор N>
    [default <оператор>]
    }
    Оператор-переключатель предназначен для выбора одного из нескольких альтернативных путей выполнения программы. Вычисление оператора-переключателя начинается с вычисления <выражения>, после чего управление передается <оператору>, помеченному <константным выражением>, равным вычисленному значению <выражения>. Выход из оператора-переключателя осуществляется оператором break. Если значение <выражения> не равно ни одному <константному выражению>, то управление передается <оператору>, помеченному ключевым словом default, если он есть.
  • Оператор разрыва break
    Оператор разрыва прерывает выполнение операторов while, do, for и switch. Он может содержаться только в теле этих операторов. Управление передается оператору программы, следующему за прерванным. Если оператор разрыва записан внутри вложенных операторов while, do, for, switch, то он завершает только непосредственно охватывающий его оператор.
  • Оператор продолжения continue
    Оператор продолжения передает управление на следующую итерацию в операторах цикла while, do, for. Он может содержаться только в теле этих операторов. В операторах do и while следующая итерация начинается с вычисления условного выражения. В операторе for следующая итерация начинается с вычисления выражения приращения, а затем происходит вычисление условного выражения.
  • Оператор возврата
    return [<выражение>];
    Оператора возврата заканчивает выполнение функции, в которой он содержится, и возвращает управление в вызывающую функцию. Управление передается в точку вызывающей функции, непосредственно следующую за оператором вызова. Значение <выражения>, если она задано, вычисляется, приводится к типу, объявленному для функции, содержащей оператор возврата, и возвращается в вызывающую функцию. Если <выражение> опущено, то возвращаемое функцией значение не определено.
    С формальной точки зрения операторы break, continue и return не являются операторами структурного программирования. Однако их использование в ограниченных количествах оправдано, когда они упрощают понимание программы и позволяют избегать больших вложенных структур. Например, мы проверяем входные данные на аномалии. Если не использовать эти операторы, то всю обработку придется вложить в условный блок, что ухудшает читабельность программы. Вместо этого можно написать небольшой условный блок, который организует выход из функции при неверных исходных данных.
Ввод/вывод не является частью языка C, а осуществляется функциями, входящими в состав стандартной библиотеки. Для подробной информации см. лекцию 4.

2. Структура программы

Программа на языке C состоит из директив препроцессора, указаний компилятору, объявлений переменных и/или функций и определений функций. Все функции в программе существуют на глобальном уровне и не могут быть вложены друг в друга.
Среди функций выделяется одна главная функция, которая должна иметь имя main. С нее начинается выполнение программы, обычно она управляет выполнением программы, организуя вызовы других функций. Для того чтобы программа могла быть скомпилирована и выполнена, она должна содержать, по крайней мере, определение функции main.

2.1. Определение функции
Определение функции имеет следующий синтаксис:
<тип> <имя> ([<список формальных параметров>])
{
[<объявления>]
[<операторы>]
}

Примеры определения функции

double Cube(double x)
{
return x * x * x;
}
// Функция одного вещественного
// аргумента, которая возвращает
// вещественное значение, равное
// кубу аргумента
void main()
{
printf("Hello!\n");
}
// Главная функция программы,
// которая печатает строку
// "Hello!" на экране

2.2. Объявление переменной
Объявление переменной задает имя и атрибуты переменной. Атрибутами переменной могут быть тип, количество элементов (для массивов), спецификация класса памяти, а также инициализаторы. Инициализатор – это константа соответствующего типа, задающая значение, которое присваивается переменной при создании.
Объявление переменной имеет следующий синтаксис:
[<спецификация класса памяти>] <тип> <имя> [= <инициализатор>]
[,<имя> [= <инициализатор>] ...];

Примеры объявления переменных

int x; // Объявление переменной целого типа
// без инициализатора.
double y = exp(1); // Переменная вещественного типа
// инициализируется числом e.
// exp(x) – функция, вычисляющая ex.
int a, b = 0; // Объявление двух переменных целого
// типа. Переменная b инициализируется
// значением 0.

2.3. Препроцессор
Препроцессор – это программа, которая обрабатывает текст Вашей программы до компилятора. Таким образом, на вход компилятора попадает текст, который может отличаться от того, который видите Вы. Работа препроцессора управляется директивами. С помощью препроцессора можно выполнять следущие операции:
  • включение в программу текстов из указанных файлов;
  • замена идентификаторов последовательностями символов;
  • макроподстановка, т.е. замена обозначения параметризованным текстом, формируемым препроцессором с учетом конкретных аргументов;
  • исключение из программы отдельных частей текста (условная компиляция).
2.3.1. Включение файлов
Включение файлов производиться с помощью директивы #include, которая имеет следующий синтаксис:
#include <путь>
#include "путь"
Угловые скобки здесь являются элементом синтаксиса.
Директива #include включает содержимое файла, путь к которому задан, в компилируемый файл вместо строки с директивой. Если путь заключен в угловые скобки, то поиск файла осуществляется в стандартных директориях. Если путь заключен в кавычки и задан полностью, то поиск файла осуществляется в заданной директории, а если путь полностью не задан – в текущей директории. С помощью это директивы Вы можете включать в текст программы как стандартные, так и свои файлы.
Во включаемый файл можно поместить, например, общие для нескольких исходных файлов определения именованных констант и макроопределения. Включаемые файлы используются также для хранения объявлений внешних переменных и абстрактных типов данных, разделяемых несколькими исходными файлами.
Кроме того, как было указано выше, в языке C ряд функций, такие как функции ввода/вывода, динамического распределения памяти и т.д., не являются элементом языка, а входят в стандартные библиотеки. Для того чтобы пользоваться функциями стандартных библиотек, необходимо в текст программы включать так называемые заголовочные файлы (в описании каждой функции указывается, какой заголовочный файл необходим для неё). Это также делается с помощью директивы препроцессора #include.
Директива #include может быть вложенной. Это значит, что она может встретиться в файле, включенном другой директивой #include. Допустимый уровень вложенности директив #include зависит от реализации компилятора.

2.3.2. Макроподстановки
Макроподстановки реализуются директивой #define, которая имеет следующий синтаксис:
#define <идентификатор> <текст>
#define <идентификатор>(<список параметров>) <текст>
Директива #define заменяет все вхождения <идентификатора> в исходном файле на <текст>, следующий в директиве за <идентификатором>. Этот процесс называется макроподстановкой. <Идентификатор> заменяется лишь в том случае, если он представляет собой отдельную лексему. Например, если <идентификатор> является частью строки или более длинного идентификатора, он не заменяется.
<Текст> представляет собой набор лексем, таких как ключевые слова, константы, идентификаторы или выражение. Один или более пробельных символов должны отделять <текст> от <идентификатора> (или от заключённых в скобки параметров). Если <текст> не умещается на строке, то он может быть продолжен на следующей строке, для этого следует набрать в конце строки символ «обратный слэш» и сразу за ним нажать клавишу «ВВОД».
<Текст> может быть опущен. В этом случае все экземпляры <идентификатора> будут удалены из исходного текста программы. Тем не менее, сам <идентификатор> рассматривается как определённый.
<Список параметров>, если он задан, содержит один или более идентификаторов, разделённых запятыми, и должен быть заключён в круглые скобки. Идентификаторы в списке должны отличаться друг от друга. Их область действия ограничена макроопределением, в котором они заданы. Имена формальных параметров в <тексте> отмечают позиции, в которые должны быть подставлены фактические аргументы макровызова.
В макровызове следом за <идентификатором> записывается в круглых скобах список фактических аргументов, соответствующих формальным параметрам из <списка параметров>. Списки фактически и формальных параметров должны содержать одно и то же количество элементов. Не следует путать подстановку аргументов в макроопределение с передачей аргументов функции. Подстановка в препроцессоре носит чисто текстовый характер. Никаких вычислений или преобразований типа при этом не производится.
После того как выполнена макроподстановка, полученная строка вновь просматривается для поиска других имен макроопределений. При повторном просмотре не принимается к рассмотрению имя ранее произведенной макроподстановки. Поэтому директива #define x x не приведет к зацикливанию препроцессора.

Примеры

#define N 100
#define MULT(a, b) ((a) * (b))
#define MAX(x, y) ((x) > (y)) ? (x) : (y)

Вызов MULT(x + y, z) будет заменен на ((x + y) * (z)). При отсутствии внутренних скобок получилось бы (x + y * z), что неверно.
Макровызов MAX(i, a[i++]) заменится на ((i) > (a[i++])) ? (i) : (a[i++])). Результат вычисления непредсказуем.

В директиве #define две лексемы могут быть «склеены» вместе. Для этого их нужно объединить знаками ## (слева и справа допустимы пробельные символы). Препроцессор объединяет такие лексемы в одну. Например, макроопределение #define VAR(i, j) i ## j при макровызове VAR(x, 6) образует идентификатор x6.
Символ #, помещаемый перед аргументом макроопределения, указывает на необходимость преобразования его в символьную строку. При макровызове конструкция #<формальный параметр> заменяется на "<фактический параметр>".

Замены в тексте можно отменить директивой #undef, которая имеет следующий синтаксис:
#undef <идентификатор>
Директива #undef отменяет действие текущего определения #define для <идентификатора>. Чтобы отменить макроопределение, достаточно задать его <идентификатор>. Задание списка параметров не требуется. Не является ошибкой применение директивы #undef к идентификатору, который ранее не был определён или действие которого уже отменено.

2.3.3. Условная компиляция
Условная компиляция обеспечивается в языке C++ набором команд, которые, по существу, управляют не компиляцией, а препроцессорной обработкой. Эти директивы позволяют исключить из процесса компиляции какие-либо части исходного файла посредством проверки условий.
#if <константное выражение>
[<текст>]
[#elif <константное выражение>
[<текст>]]
...
[#else
[<текст>]]
#endif
 Каждой директиве #if в том же исходном файле должна соответствовать завершающая её директива #endif. Между директивами #if и #endif допускается произвольное количество директив #elif и не более одной директивы #else. Если директива #else присутствует, то между ней и директивой #endif на данном уровне вложенности не должно быть других директив #elif.
Препроцессор выбирает участок текста для обработки на основе вычисления <константного выражения>, следующего за каждой директивой #if и #elif. Выбирается <текст>, следующий за <константным выражением> со значением «истина». Если ни одно ограниченное константное выражение не истинно, то препроцессор выбирает <текст>, следующий за директивой #else. Если же директива #else отсутствует, то никакой текст не выбирается.
<Константное выражение> может содержать препроцессорную операцию defined(<идентификатор>). Эта операция возвращает истинное значение, если заданный <идентификатор> в данный момент определён, в противном случае выражение ложно.

#if (sizeof(void *) == 2)
#define SDATA
#else
#define LDATA
#endif

#if defined(CREDIT)
credit();
#elif defined(DEBIT)
debit();
#else
printerror();
#endif

3. Пример

Напишем программу для задачи поиска корня уравнения f(x) = 0 на отрезке [a; b] с заданной точностью методом деления отрезка пополам.

Первый вариант - обычный способ

#include <stdio.h> // Включаем заголовочные файлы,
#include <math.h> // содержащие прототипы функций ввода/вывода
// и математических функций (для fabs)
void main()
{ double a, b, e, x, c, fa, fc; // Объявления переменных
int n;

printf("Введите границы отрезка и точность: "); // Приглашение для пользователя
scanf("%lf%lf%lf", &a, &b, &e); // Ввод исходных данных
for (n = 0; fabs(a - b) > e; n++) // В заголовок цикла for включаем
// инициализацию переменной n,
// её увеличение на 1, т.к. оно безусловно
// выполняется на каждом шаге цикла,
// и проверку условия цикла
{ c = (a + b) / 2; // Т.к. в теле цикла должно быть более
fa = f(a); // одного оператора, а по синтаксису
fc = f(c); // возможен только один, операторы,
if (fa * fc < 0) // составляющие тело цикла, объединяются
b = c; // в один с помощью операторных скобок {...}
else
a = c;
}
x = (a + b) / 2;
printf("Корень уравнения = %lf\nЧисло итераций = %d\n", x, n);
}

Второй вариант - как на Паскале

#include <stdio.h>
#include <math.h>

void main()
{ double a, b, e, x, c, fa, fc;
int n;

printf("Введите границы отрезка и точность: ");
scanf("%lf%lf%lf", &a, &b, &e);
n = 0;
while (fabs(a - b) > e)
{ c = (a + b) / 2;
fa = f(a);
fc = f(c);
if (fa * fc < 0)
b = c;
else
a = c;
n++;
}
x = (a + b) / 2;
printf("Корень уравнения = %lf\nЧисло итераций = %d\n", x, n);
}
Третий вариант - весь алгоритм помещен в заголовок цикла for

#include <stdio.h>
#include <math.h>

void main()
{ double a, b, e, x, c, fa, fc;
int n;

printf("Введите границы отрезка и точность: ");
scanf("%lf%lf%lf", &a, &b, &e);
for (n = 0;
fabs(a - b) > e;
// Для объединения нескольких операторов в выражении приращения цикла for
// используется операция последовательного вычисления
 c = (a + b) / 2, fa = f(a), fc = f(c), fa * fc < 0 ? b = c : a = c, n++) ;
x = (a + b) / 2;
printf("Корень уравнения = %lf\nЧисло итераций = %d\n", x, n);
}


Массивы

Лекция 3. Массивы

Массив – это совокупность данных, которая обладает следующими свойствами:
  • все элементы массива имеют один и тот же тип;
  • массив имеет одно имя для всех элементов;
  • доступ к конкретному элементу массива осуществляется по индексу (индексам).

1. Объявление массива
Объявление массива имеет следующий синтаксис:
<спецификация типа> <имя> [<константное выражение>];
<спецификация типа> <имя> [ ];
 Здесь квадратные скобки являются элементом синтаксиса, а не признаком необязательности конструкции.
Объявление массива может иметь одну из двух синтаксических форм, указанных выше. Квадратные скобки, следующие за <именем>, – признак того, что переменная является массивом. <Константное выражение>, заключенное в квадратные скобки определяет число элементов в массиве. Индексация элементов массива в языке C начинается с нуля. Таким образом, последний элемент массива имеет индекс на единицу меньше, чем число элементов массива.
Во второй синтаксической форме константное выражение в квадратных скобках опущено. Эта форма может быть использована, если в объявлении массива присутствует инициализатор, либо массив объявляется как формальный параметр функции, либо данное объявление является ссылкой на объявление массива где-то в другом месте программы. Однако для многомерного массива может быть опущена только первая размерность.
Многомерный массив, или массив массивов, объявляется путем задания последовательности константных выражений в квадратных скобках, следующей за <именем>: <спецификация типа> <имя> [<константное выражение>][<константное выражение>] ... ;
Каждое <константное выражение> определяет количество элементов в данном измерении массива, поэтому объявление двумерного массива содержит два константных выражение, трехмерного – три и т.д.
Массив может состоять из элементов любого типа, кроме типа void и функций, т.е. элементы массива могут иметь базовый, перечислимый, структурный тип, быть объединением, указателем или массивом.

Примеры объявлений массивов

int x[10]; // Одномерный массив из 10 целых чисел.
// Индексы меняются от 0 до 9.
double y[2][10]; // Двумерный массив вещественных чисел
// из 2 строк и 10 столбцов.

2. Инициализация массивов
Как и простые переменные, массивы могут быть инициализированы при объявлении. Инициализатор для объектов составных типов (каким является массив) состоит из списка инициализаторов, разделенных запятыми и заключенных в фигурные скобки. Каждый инициализатор в списке представляет собой либо константу соответствующего типа, либо, в свою очередь, список инициализаторов. Эта конструкция используется для инициализации многомерных массивов.
Наличие списка инициализаторов в объявлении массива позволяет не указывать число элементов по его первой размерности. В этом случае количество элементов в списке инициализаторов и определяет число элементов по первой размерности массива. Тем самым определяется размер памяти, необходимой для хранения массива. Число элементов по остальным размерностям массива, кроме первой, указывать обязательно.
Если в списке инициализаторов меньше элементов, чем в массиве, то оставшиеся элементы неявно инициализируются нулевыми значениями. Если же число инициализаторов больше, чем требуется, то выдается сообщение об ошибке.

Примеры инициализации массивов

int a[3] = {0, 1, 2}; // Число инициализаторов равно числу элементов
double b[5] = {0.1, 0.2, 0.3}; // Число инициализаторов меньше числа элементов
int c[ ] = {1, 2, 4, 8, 16}; // Число элементов массива определяется
// по числу инициализаторов
int d[2][3] = {{0, 1, 2}, // Инициализация двумерного массива. Массив
{3, 4, 5}}; // состоит из двух строк, в каждой из которых
// по 3 элемента. Элементы первой строки
// получают значения 0, 1 и 2, а второй –
// значения 3, 4 и 5.

3. Работа с массивами

3.1. Доступ к элементу массива
 Для доступа к конкретному элементу массива используются так называемые индексные выражения:
<имя массива>[<целочисленное выражение>]
 Здесь квадратные скобки являются требованием синтаксисам языка, а не признаком необязательности конструкции.
Индекс массива может быть не только константой, но и выражением, которое имеет целочисленный тип, например, a[i + 1] (здесь a должно быть именем ранее объявленного массива, а i – переменной целого типа).
Объявление массива и индексное выражение, используемое для доступа к элементу массива, имеют схожий синтаксис. Различаются они по месту в программе. Это особенно важно, когда мы определяем индекс последнего элемента массива. Как было сказано ранее, индексы элементов массива в языке C начинаются с 0, и номер последнего элемента на 1 меньше количества элементов массива. Поэтому если Вы объявили массив x из 10 элементов, Вы не можете написать индексное выражение x[10], т.к. в этом случае Вы пытаетесь обратиться к элементу с индексом 10, которого нет в Вашем массиве. Компилятор не выдаст сообщения об ошибке, но результаты работы такой программы будут непредсказуемы.
Имя массива является адресом его начала! Оно имеет тип указатель на <тип элементов массива>. Конструкция a[i] эквивалентна *(a + i) (см. лекцию 5).
Для многомерного массива надо указать соответствующее количество индексов в квадратных скобках.

3.2. Обработка массивов
 Для обработки элементов массива обычно используется оператор пошагового цикла for.

for (i = 0; // Присваиваем счетчику цикла значение индекса
// первого элемента
i < n; // Условие продолжения цикла – пока значение
// счетчика меньше количества элементов массива
i++) // Увеличиваем счетчик цикла на 1 для перехода
// к следующему элементу массива
<тело цикла> // В теле цикла происходит обработка одного
// элемента массива

Для обработки многомерного массива используется соответствующее количество циклов.

Поскольку в языке С нет типа «массив», при использовании массива в качестве параметра функции можно только передать адрес его начала:

int f(..., int x[], ...) { ... } // Можно использовать любой из двух вариантов
int f(..., int *x, ...) { ... }

Программист должен сам передавать в функцию количество элементов массива и следить, чтобы не было выхода за границу массива.

3.3. Ввод/вывод массивов
 В языке C нет возможности вводить и выводить весь массив одним оператором ввода/вывода. Можно вводить и выводить только один элемент массива. Следовательно, для того чтобы ввести весь массив, надо использовать цикл.

int a[10], n; // Объявляем массив и переменную для количества
// элементов массива
printf("Введите количество элементов массива (от 0 до 10): ");
scanf("%d", &n); // Ввод количества элементов массива
if (n < 0 || n > 10) // Если входные данные неверны, то печатаем
{ printf("Количество элементов массива должно быть от 0 до 10!\n");
return; // соответствующее сообщение и выходим из программы
}
for (i = 0; i < n; i++) // Ввод массива по одному элементу
scanf("%d", &a[i]); // Можно использовать scanf("%d", a + i)

Если в ходе решения задачи Вы изменили значения элементов массива, то его необходимо вывести. Вывод также осуществляется в цикле.

for (i = 0; i < n; i++)
printf("a[%d] = %3d\n", i + 1, a[i]);

В результате на экране мы увидим примерно следующий текст:

a[1] = 4
a[2] = 15
a[3] = -2
...
3.4. Пример 1. Обработка одномерного массива
 Даны три массива разной размерности. Определить в каком массиве больше сумма элементов.

#include <stdio.h>
#define NMAX 100

double Sum(double x[], int n);

void main()
{ double a[NMAX], b[NMAX], c[NMAX];
double sa, sb, sc, max;
int na, nb, nc, i;

printf("Введите количество элементов массива A (до %d элементов): ", NMAX);
scanf("%d", &na);
if (na < 0 || na > NMAX)
{ printf("Количество элементов массива должно быть от 0 до %d!\n", NMAX);
return;
}
printf("Введите массив A\n");
for (i = 0; i < na; i++)
scanf("%lf", &a[i]);

... // Аналогично вводим массивы В и С

sa = Sum(a, na);
sb = Sum(b, nb);
sc = Sum(c, nc);

max = sa;
if (sb > max) max = sb;
if (sc > max) max = sc;

if (sa == max)
printf("Массив А имеет максимальную сумму элементов: %9.3lf\n", max);
if (sb == max)
printf("Массив B имеет максимальную сумму элементов: %9.3lf\n", max);
if (sc == max)
printf("Массив C имеет максимальную сумму элементов: %9.3lf\n", max);
}

double Sum(double x[], int n)
{ int i;
double s;

for (s = 0, i = 0; i < n; i++)
s += x[i];

return s;
}

 3.5. Пример 2. Обработка двумерного массива
 Для каждой строки матрицы проверить наличие нулевых элементов.

Первый способ
#include <stdio.h>
#define NMAX 100

// Функция Zeros формирует одномерный массив, i-ый элемент которого равен 1,
// если в i-ой строке есть нулевые элементы, и 0 в противном случае
void Zeros(double x[][NMAX], int m, int n, int z[]);

void main()
{ double a[NMAX][NMAX];
int m, n, z[NMAX], i, j;

printf("Введите размерность матрицы: "); scanf("%d%d", &m, &n);
if (m < 0 || m > NMAX || n < 0 || n > NMAX)
{ printf("Размерность матрицы должна быть от 1 до %d!\n", NMAX);
return;
}
printf("Введите матрицу\n");
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
scanf("%lf", &a[i][j]);

Zeros(a, m, n, z);

for (i = 0; i < m; i++)
if (z[i])
printf("В %3d строке есть нулевые элементы\n", i + 1);
}

void Zeros(double x[][NMAX], int m, int n, int z[])
{ int i, j;

for (i = 0; i < m; i++)
for(z[i] = 0, j = 0; j < n; j++)
if (x[i][j] == 0) // if (fabs(x[i][j]) < 0.0001)
{ z[i] = 1; break; }
}

 Второй способ
#include <stdio.h>
#define NMAX 100

// Функция Zeros обрабатывает одну строку матрицы и возвращает 1,
// если в строке есть нулевые элементы, и 0 в противном случае
int Zeros(double x[], int n);

void main()
{ double a[NMAX][NMAX];
int m, n, z[NMAX], i, j;

... // Ввод матрицы (как в первом варианте)

for (i = 0; i < m; i++)
if (Zeros(a[i], n)) // Конструкция a[i] (а является двумерным массивом)
// позволяет передать в функцию
// одну строку двумерного массива
printf("В %3d строке есть нулевые элементы\n", i + 1);
}

int Zeros(double x[], int n)
{ int j;

for (j = 0; j < n; j++)
if (x[j] == 0) // if (fabs(x[i][j]) < 0.0001)
return 1;
return 0;
}


Ввод/вывод

Лекция 4. Ввод/вывод

1. Использование параметров программы
void main(int argc, char *argv[]) // Имена не являются требованием языка
Первый параметр содержит количество элементов в массиве – втором параметре, который является массивом указателей на строки. Каждая строка хранит один переданный программе параметр, при этом первый параметр (с индексом 0) содержит имя исполняемого файла и существует всегда. Порядок объявления параметров существенен. Пример использования параметров программы см. в примере 1 в конце лекции

2. Ввод/вывод

В языке С определены три стандартных потока ввода/вывода:
  1. stdin – стандартное устройство ввода (клавиатура);
  2. stdout – стандартное устройство вывода (экран);
  3. stderr – стандартное устройство вывода сообщения об ошибках (также экран).
Простейший ввод/вывод:
  • getch() // Ввод одного символа без отображения его на экране
  • getche() // Ввод одного символа с отображением его на экране
  • putchar() // Вывод одного символа на экран
Все функции требуют включения заголовочного файла conio.h.

2.1. Ввод/вывод с экрана
  • int scanf (char *format, ...);
  • int printf(char *format, ...);
Вывод в языке C осуществляется функцией printf, которая имеет следующий синтаксис:
printf(<строка описания форматов> [, <список вывода>]);

Строка описания форматов состоит из обычных символов, специальных управляющих последовательностей символов и спецификаций формата вывода.
Обычные символы и управляющие последовательности просто копируются в стандартный выходной поток в порядке их появления.
Спецификации формата вывода начинаются с символа % и заканчиваются символом, определяющим тип выводимого значения, кроме того, спецификации формата могут содержать символы и цифры для управления видом выводимого значения (подробно см. ниже). <Список вывода> состоит из переменных и/или констант, значения которых должны быть выведены. Количество спецификаций формата должно быть равно количеству выводимых значений, которые указываются в <списке вывода>. Если это условие не будет соблюдаться, выполнение функции может привести к непредсказуемым результатам.

Ввод в языке C осуществляется функцией scanf, которая имеет следующий синтаксис:
scanf(<строка описания форматов> [, <список ввода>]);

<Строка описания форматов> состоит из набора спецификаций формата, таких же, как для функции printf. <Список ввода> состоит из адресов переменных, куда будут заноситься вводимые значения. Адрес переменной вычисляется с помощью унарной операции &. Количество спецификаций формата должно быть равно количеству вводимых значений, которые указываются в <списке ввода>.

К управляющим последовательностям относятся следующие последовательности символов:

Последовательность | Дейcтвие
\a | Звуковой сигнал
\b | Удаление предыдущего символа
\n | Новая строка
\r | Возврат каретки
\t | Табуляция
\' | Апостроф
\" | Кавычки
\\ | Обратный слеш
\ooo | ASCII символ в восьмеричной нотации
\x ooo | ASCII символ в шестнадцатеричной нотации

Спецификация формата, которая состоит из обязательных и необязательных полей, имеет следующий вид:
%[флаги] [ширина] [.точность] [{h | l | L | I64}] тип

Флаги | Значение | По умолчанию
– | Выравнивание по левому краю. | Выравнивание по правому краю.
+ | Добавление знака + или – перед числами. | Знак добавляется только перед отрицательными числами.
0 | Добавление нулей перед выводимым значением. Если одновременно используются флаги – и 0, 0 игнорируется. | Добавление пробелов.
пробел | Добавление пробела перед положительным числом. Если одновременно используются флаги пробел и +, пробел игнорируется. | Пробел не добавляется.
# | Добавление символов 0, 0х или 0Х перед ненулевым значением, если флаг # используется с форматами о, х или Х соответственно. | Символы 0, 0х и 0Х не добавляются.
 | При использовании с форматами e, E и f флага # выводимое число будет содержать десятичную точку в любом случае. | Десятичная точка добавляется, только если за ней следуют цифры.
 | При использовании с форматами g и G флага # выводимое число будет содержать десятичную точку и хвостовые нули в любом случае. | Десятичная точка добавляется, только если за ней следуют цифры. Хвостовые нули не выводятся.
 | Игнорируется при использовании с форматами c, d, i, u и s. |

Поле ширина содержит минимальное количество выводимых символов – неотрицательное целое число. Если выводимое значение содержит меньше символов, то оно расширяется пробелами (если не задан флаг 0). Если поле ширина содержит звездочку (*), то в качестве значения поля берётся целое число из списка аргументов, предшествующее выводимому значению.

Поле точность также представляет собой неотрицательное целое число. Действие зависит от типа выводимого значения.

Тип | Действие | По умолчанию
c, C | Точность не имеет эффекта. | Выводится символ.
d, i, u, o, x, X | Точность задаёт минимальное количество символов, которые будут напечатаны. Если число содержит меньше символов, оно расширяется нулями. | Точность равна 1.
e, E, f | Точность задаёт количество символов после десятичной точки. Число округляется. | Точность равна 6. Если точность равна 0 или опущена, десятичная точка не выводится.
g, G | Точность задаёт максимальное количество значащих цифр. | Печатается 6 значащих цифр.
s, S | Точность задаёт максимальное количество выводимых символов. | Выводятся все символы строки.

Если поле точность содержит звездочку (*), то в качестве значения поля берётся целое число из списка аргументов, предшествующее выводимому значению.

Дополнительные префиксы h, l, L и I64 задают «размер» аргумента – long или short, однобайтовый символ или расширенный символ, в зависимости от спецификации типа, которую они модифицируют.

Обязательное поле тип задаёт тип выводимого значения.

Символ | Тип | Формат вывода
c | int или wint_t | При использовании с функцией printf определяет однобайтовый символ, при использовании с функцией wprintf определяет расширенный символ.
C | int или wint_t | При использовании с функцией printf определяет расширенный символ, при использовании с функцией wprintf определяет однобайтовый символ.
d | int | Знаковое десятичное целое.
i | int | Знаковое десятичное целое.
o | int | Беззнаковое восьмеричное целое.
u | int | Беззнаковое десятичное целое.
x | int | Беззнаковое шестнадцатеричное целое с использованием «abcdef».
X | int | Беззнаковое шестнадцатеричное целое с использованием «ABCDEF».
e | double | Знаковое число в форме [ – ]d.dddd e [знак]ddd, где d есть одна десятичная цифра, dddd – одна или более десятичных цифр, ddd – три десятичные цифры and знак есть + или –.
E | double | Идентичен формату e, за исключением того, что символ E, а не e вводит экспоненту.
f | double | Знаковое число в форме [ – ]dddd.dddd, где dddd есть одна или более десятичных цифр. Количество цифр перед десятичной точкой зависит от величины числа, а количество цифр после десятичной точки – от требуемой точности.
g | double | Знаковое число в формате f или e, в зависимости от того, какой формат более компактен для заданного значения и точности.
G | double | Идентичен формату g, за исключением того, что символ E, а не e вводит экспоненту.
n | pointer to integer | Количество символов успешно записанных к данному моменту в выходной поток. Это значение сохраняется в целочисленной переменной, чей адрес задан как аргумент.
p | pointer to void | Печатает адрес, заданный аргументом.
s | string | При использовании с функцией printf задаёт строку однобайтовых символов, при использовании с функцией wprintf задаёт строку расширенных символов. Символы печатаются до достижения признака конца строки.
S | string | При использовании с функцией printf задаёт строку расширенных символов, при использовании с функцией wprintf задаёт строку однобайтовых символов. Символы печатаются до достижения признака конца строки.

Функция scanf возвращает количество успешно введенных и преобразованных значений. Функция printf возвращает количество символов, записанных в выходной поток.

2.2. Работа с файлами
  • Объявление файловой переменной – FILE *file;
  • Открытие файла – FILE *fopen(char *name, char *mode);
  • Проверка достижения конца файла – int feof(FILE *file);
  • Закрытие файла – int fclose(FILE *file);
FILE – специальная структура, объявленная в файле stdio.h, которая используется при работе с файлами. Для работы с файлом нужно объявить переменную FILE *<имя>.

Параметр mode задаёт требуемый тип доступа к файлу.

Mode | Действие
"r" | Открытие для чтения. Если файл не существует или не может быть найден, функция fopen возвращает признак ошибки.
"w" | Открытие для записи. Если файл существует, его содержимое уничтожается. Если файл не существует, он создаётся.
"a" | Открытие для добавления. Если файл не существует, он создаётся.
"r+" | Открытие для чтения и записи. Файл должен существовать.
"w+" | Открытие пустого файла для чтения и записи. Если файл существует, его содержимое уничтожается.
"a+" | Открытие для чтения и добавления. Если файл не существует, он создаётся.

Кроме того, к параметру mode могут быть добавлены символы t и b для задания текстового и двоичного режимов соответственно. По умолчанию используется текстовый режим.
В случае ошибки функция fopen возвращает NULL.

2.3. Текстовый режим
 При вводе/выводе в текстовом режиме происходит преобразование между внешним представлением значения и внутренним (машинным) представлением этого значения.
  • ввод одного символа – int getc (FILE *file);
  • вывод одного символа – int putc (int c, FILE *file);
  • ввод – int fscanf (FILE *file, char *format, ...);
  • вывод – int fprintf (FILE *file, char *format, ...);
  • ввод из строки – int sscanf (char *str, char *format, ...);
  • вывод в строку – int sprintf (char *str, char *format, ...);
  • ввод строки – char* fgets (char *line, int maxline, FILE *file);
  • вывод строки – int fputs (char *line, FILE *file);
2.4. Двоичный режим
 В двоичном режиме никаких преобразований не производится, внутреннее представление значения записывается в файл. Для открытия файла в двоичном режиме необходимо в параметр mode функции fopen добавить символ b.
  • ввод из файла – unsigned fread (void *buf, int bytes, int num, FILE *file);
  • вывод в файл – unsigned fwrite (void *buf, int bytes, int num, FILE *file);
Функция fread читает из файла file в переменную buf num элементов, каждый размером bytes байт. Функция fwrite записывает в файл file из переменной buf num элементов, каждый размером bytes байт.
Функции возвращают количество прочитанных/записанных элементов.

В двоичном режиме возможен прямой доступ к файлу:
int fseek (FILE *file, long nbytes, int origin)
Данная функция смещает указатель в файле file на nbytes байт с позиции, определяемой параметром origin. При этом параметр origin может принимать следующие значения:
  • SEEK_SET 0 – начало файла;
  • SEEK_CUR 1 – текущая позиция указателя;
  • SEEK_END 2 – конец файла.
Функция long ftell(FILE *file) возвращает текущую позицию указателя в файле file.

// Определение размера файла
fseek(file, 0, SEEK_END);
n = ftell(file);

3. Примеры

Пример 1. Ввод из текстового файла
// Функция ввода одномерного массива.
// Если ввод был осуществлен без ошибок, возвращается 1.
// В противном случае - 0.
// x - вводимый массив,
// n - указатель на переменную, содержащую количество элементов массива,
// fname - имя файла для ввода.

int Input_of_Array(double x[], int *n, char *fname)
{ int i;
FILE *file;

if ((file = fopen(fname, "r")) == NULL)
{ printf("Невозможно открыть файл '%s'\n", fname);
return 0;
}
if (fscanf(file, "%d", n) < 1)
{ printf ("Ошибка чтения из файла '%s'\n", fname);
fclose(file);
return 0;
}
if (*n < 0 || *n > NMAX)
{ printf("Кол-во эл-тов масс. должно быть от 1 до %d! (файл '%s')\n", NMAX, fname);
fclose(file);
return 0;
}
for (i = 0; i < *n; i++)
if (fscanf(file, "%lf", &x[i]) < 1)
{ printf ("Ошибка чтения из файла '%s'\n", fname);
fclose(file);
return 0;
}
fclose(file);
return 1;
}

// Основная программа для задачи с тремя массивами (см. лекцию 3)

#include <stdio.h>
#define MAXN 100

int Input_of_Array(double x[], int *n, char *fname);

void main(int argc, char *argv[])
{ double a[MAXN], b[MAXN], c[MAXN];
double sa, sb, sc, max;
int na, nb, nc, i;

if (argc < 4)
{ printf("Недостаточно параметров!\n");
return;
}
if (!Input_of_Array(a, &na, argv[1]))
return;
if (!Input_of_Array(b, &nb, argv[2]))
return;
if (!Input_of_Array(c, &nc, argv[3]))
return;

... // Всё остальное также

}
Пример 2. Ввод/вывод из двоичного файла
// Вывод массива в двоичный файл
// Если вывод был осуществлен без ошибок, возвращается 1.
// В противном случае - 0.
// x - выводимый массив,
// n - переменная, содержащая количество элементов массива,
// fname - имя файла для вывода.

int Bin_Arr_Output(double x[], int n, char *fname)
{ FILE *file;

if ((file = fopen(fname, "wb")) == NULL)
{ printf("Невозможно открыть файл '%s'\n", fname);
return 0;
}
if (fwrite(x, n * sizeof(double), 1, file) < 1)
{ printf ("Ошибка записи в файл '%s'\n", fname);
fclose(file);
return 0;
}
fclose(file);
return 1;
}

// Ввод массива из двоичного файла
// Если ввод был осуществлен без ошибок, возвращается 1.
// В противном случае - 0.
// x - вводимый массив,
// n - указатель на переменную, содержащую количество элементов массива,
// fname - имя файла для ввода.

int Bin_Arr_Input(double x[], int *n, char *fname)
{ FILE *file;

if ((file = fopen(fname, "rb")) == NULL)
{ printf("Невозможно открыть файл '%s'\n", fname);
return 0;
}
for (*n = 0; ; (*n)++)
if (fread(x + (*n), sizeof(double), 1, file) < 1)
if (feof(file))
break;
else
{ printf ("Ошибка чтения из файла '%s'\n", fname);
fclose(file);
return 0;
}
fclose(file);
return 1;
}

// Другой способ

int Bin_Arr_Input(double x[], int *n, char *fname)
{ FILE *file;

if ((file = fopen(fname, "rb")) == NULL)
{ printf("Невозможно открыть файл '%s'\n", fname);
return 0;
}
fseek(file, 0, SEEK_END);
*n = ftell(file) / sizeof(double);
fseek(file, 0, SEEK_SET);
if (fread(x, sizeof(double), *n, file) < *n)
{ printf ("Ошибка чтения из файла '%s'\n", fname);
fclose(file);
return 0;
}
fclose(file);
return 1;
}


Указатели. Адресная арифметика. Работа со строками

Лекция 5. Указатели. Адресная арифметика.
Работа со строками

1. Указатели

Каждая переменная, которую вы объявляете в программе, имеет адрес – номер ячейки памяти, в которой она расположена. Адрес является неотъемлемой характеристикой переменной. Можно объявить другую переменную, которая будет хранить этот адрес и которая называется указателем. Указатели применяются при передаче в функцию параметров, которые мы хотим изменить, при работе с массивами, при работе с динамической памятью и в ряде других случаев.
Объявление указателя имеет следующий синтаксис:
<спецификация типа> *<идентификатор> [ = <инициализатор>];

Указатель может указывать на значения базового, перечислимого типа, структуры, объединения, функции, указателя.

int* p, s; // Плохой стиль объявления, s – не указатель!
int *p, s; // Видно, что s – не указатель
int *p, *s; // Два указателя
int **p; // Указатель на указатель на значение типа int

int f(int x) { ... };
int (*p)(int) = f; // p – указатель на функцию, получающую один параметр
// типа int и возвращающую значение типа int,
// инициализируется адресом функции f
int *f(int); // f – функция одного целочисленного параметра,
// возвращающая указатель на значение типа int
Специальное применение имеют указатели на тип void. Указатель на тип void может указывать на значения любого типа. Однако для выполнения операций над указателем на void либо над указуемым объектом, необходимо явно привести тип указателя к типу, отличному от указателя на void.

Существуют две операции, которые имеют отношение к работе с указателями. Этими операциями являются:
  • операция взятия адреса (адресация) &;
  • операция взятия значения по адресу (косвенная адресация) *.

int a, *p;
p = &a; // p присваивается адрес переменной a
 *p = 0; // Значение по адресу, находящемуся в переменной p
// (т.е. значение переменной а) становится равным 0
2. Адресная арифметика

Над указателями можно производить следующие действия:
  • складывать указатель и целое число;
  • вычитать из указателя целое число;
  • вычитать из указателя указатель.
При этом прибавляемое/вычитаемое или полученное целое число обозначает не количество байт, а количество элементов того типа, на который указывает указатель, т.е. это число умножается или делится на размер типа.
Указатели можно использовать для обработки массивов.

int a[100], n, *end, *p;

end = a + n; // n - количество элементов массива a
// Имя массива является адресом его начала (см. лекцию 3).
// Таким образом, end - адрес элемента, находя-
// щегося после последнего элемента массива
 for (p = a; p < end; p++)
printf("%4d", *p);

Из того, что можно вычитать из указателя целое число, следует, что возможны отрицательные индексы массива.

int a[N];
int *endA = a + N - 1, i;

for (i = 0; i < N; i++)
printf("%4d", endA[-i]);

3. Строки

Строка – это последовательность (массив) символов (типа char), которая заканчивается специальным символом – признаком конца строки. Это символ записывается как '\0' (не путайте с символом переноса строки '\n'). При вводе строки символ конца строки добавляется автоматически. Все функции работы со строками – и стандартные, и создаваемые программистом – должны ориентироваться на этот символ. Если требуется сформировать новую строку, то обязательно надо добавлять признак конца строки. Если этого не сделать, то при дальнейшей работе возникнут ошибки.

'a' // Символьная константа - один символ
"a" // Строковая константа - массив из двух символов
// 'a' и '\0', заменяется на адрес.

char str[51]; // Объявление строки
char *str; // Нельзя, т.к. не выделяется память под элементы строки
char *str = "abcd"; // Можно, но строка может содержать не более 4 символов!
char *str1 = "abc", *str2 = "abc"; // Не известно, будет ли выполняться str1 == str2?
Поскольку мы не знаем, сколько в строке содержится символов, но знаем, что в конце стоит символ конца строки, цикл для обработки строки пишется следующим образом: for (i = 0; str[i] != '\0'; i++) {...}.
 Можно опустить сравнение с нулем, для С это будет эквивалентно: for (i = 0; str[i]; i++) { ... }.
 Можно использовать указатели для обработки строк: for (p = str; *p; p++) { ... }.
 Заголовки стандартных функций работы со строками хранятся в файле string.h. Основными из этих функций являются:
  • int strlen(const char *str) - определение длины строки;
  • int strcmp(const char *str1, const char *str2) - сравнение строк;
  • char *strcpy(char *str1, const char *str2) - копирование str2 в str1;
  • char *strcat(char *str1, const char *str2) - конкатенация строк;
  • char *strchr(const char *str, char c) - поиск символа в строке;
  • char *strstr(const char *str1, const char *str2) - поиск подстроки str2 в str1.
Ввод/вывод строки:
  • функция scanf с форматом %s - ввод строки до пробела или другого разделителя;
  • char *gets (char *buffer) - ввод строки (которая может включать пробелы);
  • char *fgets(char *string, int n, FILE *stream) - ввод строки из файла, n задаёт макси-
    мальное количество символов для ввода;
  • функция printf с форматом %s - вывод строки с форматированием;
  • int puts(const char *string) - вывод строки;
  • int fputs(const char *string, FILE *stream) - вывод строки в файл.

4. Примеры

Пример 1. Написать функцию, которая меняет все вхождения буквы «я» на «а», «а» – на «б», «б» – на «в» и т.д. Остальные символы должны оставаться без изменения.

void Change(char *str)
{ char *p;

for (p = str; *p; p++)
if (*p == 'я')
*p = 'а';
else if ('а'<= *p && *p <= 'ю')
(*p)++;
}

 Пример 2. Написать функцию, формирующую строку, состоящую из символов исходной строки, не входящих в заданное множество.

void NotEntered(char *dest, const char *source, const char *symbols)
{ int i, j;

for (i = 0, j = 0; source[i]; i++)
if (!strchr(symbols, source[i]))
dest[j++] = source[i];
dest[j] = '\0'; // Обязательно добавляем признак конца строки в формируемую строку
}


Перечислимый тип. Структуры. Объединения

Лекция 6. Перечислимый тип. Структуры. Объединения

1. Перечислимый тип

Перечислимый тип задаёт тип, который является подмножеством целого типа.
Объявление переменной перечислимого типа задаёт имя переменной и определяет список именованных констант, называемый списком перечисления:
enum [<тег>] {<список перечисления>} <описатель> [, <описатель> ...];
enum <тег> <описатель> [, <описатель> ...];
 <Тег> предназначен для различения нескольких перечислимых типов, объявленных в одной программе.
<Список перечисления> содержит одну или более конструкций вида:
<идентификатор> [= <константное выражение>]
 Конструкции в списке разделяются запятыми. Каждый <идентификатор> именует элемент списка перечисления. По умолчанию, если не задано <константное выражение>, первому элементу присваивается значение 0, следующему элементу – значение 1 и т.д.
Запись = <константное выражение> изменяет умалчиваемую последовательность значений. Элемент, идентификатор которого предшествует записи = <константное выражение>, принимает значение, задаваемое этим константным выражением. Константное выражение должно иметь тип int и может быть как положительным, так и отрицательным. Следующий элемент списка получает значение, равное <константное выражение> + 1, если только его значение не задаётся явно другим константным выражением.
В списке перечисления могут содержаться элементы, которым сопоставлены одинаковые значения, однако каждый идентификатор в списке должен быть уникальным. Кроме того, идентификатор элемента списка перечисления должен быть отличным от идентификаторов элементов всех остальных списков перечислений, а также от других идентификаторов.

enum Weekdays {SA, SU, MO, TU, WE, TH, FR};
enum Weekdays {SA, SU = 0, MO, TU, WE, TH, FR}; // SA и SU имеют одинаковое значение

void main()
{ enum Weekdays d1 = SA, d2 = SU, d3 = WE, d4;

d4 = 2; // Ошибка!
d4 = d1 + d2; // Ошибка!
d4 = (enum Weekdays)(d1 + d2); // Можно, но результат может
d4 = (enum Weekdays)(d1 - d2); // не попасть в область определения
d4 = (enum Weekdays)(TH * FR); // перечисления
d4 = (enum Weekdays)(WE / TU);
}

2. Структуры

Структура позволяет объединить в одном объекте совокупность значений, которые могут иметь различные типы. Однако в языке С реализован очень ограниченный набор операций над структурами как единым целым: передача функции в качестве аргумента, возврат в качестве значения функции, получение адреса. Можно присваивать одну структуру другой, если они имеют одинаковый тип.
Объявление структуры задает имя структурного типа и/или последовательность объявлений переменных, называемых элементами структуры. Эти элементы могут иметь различные типы.
struct [<тег>] {<список объявлений элементов>} <описатель> [, <описатель> ...];
struct <тег> <описатель> [, <описатель> ...];
 <Тег> предназначен для различения нескольких структур, объявленных в одной программе.
Список объявлений элементов представляет собой последовательность из одного или более объявлений переменных. Каждая переменная, объявленная в этом списке называется элементом структуры. Особенность синтаксиса объявлений элементов структуры состоит в том, что они не могут содержать спецификаций класса памяти и инициализаторов. Элементы структуры могут иметь базовый тип, либо быть массивом, указателем, объединением или структурой.

struct
{ char str[50];
int a, b; // Объявляем структуру, не задавая тег
} s; // и сразу же объявляем переменную

struct S
{ char str[50];
int a, b;
}; // Объявляем структуру с тегом S
struct S s; // Объявляем переменную
Элемент структуры не может быть структурой того же типа, в которой он содержится. Однако он может быть указателем на тип структуры, в которую он входит. Размер указателя стандартный (2 или 4 байта), поэтому компилятор знает, сколько памяти потребуется под указатель. Для работы с указателем надо знать размер типа, на который он указывает, но к моменту работы с указателем структура будет полностью объявлена, и, следовательно, размер её будет известен.
Идентификаторы элементов структуры должны различаться между собой. Идентификаторы элементов разных структур могут совпадать.
Для инициализации структуры, как и других составных типов, надо записать список инициализаторов через запятую в фигурных скобках.

struct S s = {"Str", 0, 1}; // Используем тег S, объявленный в предыдущем примере
Выбор элемента структуры осуществляется с помощью одной из следующих конструкций:
<переменная> . <идентификатор элемента структуры>
<указатель> -> <идентификатор элемента структуры>
 Выражение выбора элемента позволяет получить доступ к элементу структуры. Выражение имеет значение и тип выбранного элемента.

struct S s, *p = &s; // Объявляем переменную s и указатель p,
// в который заносим адрес переменной s
s.a = 10;
p->b = 20;

 2.1. Пример
 Вводим массив структур и осуществляем поиск по любой совокупности параметров.

#include <stdio.h>
#include <string.h>
#include <conio.h>

struct S // Объявляем структуру, состоящую
{ char str[21]; // из строки и целого числа
int a;
};

struct S s[10]; // Объявляем массив структур

void main(int argc, char *argv[])
{ int n, a, i, check = 0; // Переменная а содержит число, которое
// будет сравниваться с полем структуры а.
// Переменная check указывает, нужно ли
// использовать этот параметр для поиска.
char str[21] = ""; // Переменная str содержит строку, которая
// будет сравниваться с полем структуры str. // Если переменная str содержит пустую строку, // этот параметр не используется для поиска.
FILE *in, *out;
char ans;

if (argc < 3)
{ printf("Too few arguments.\n"); return; }
if ((in = fopen(argv[1], "r")) == NULL)
{ printf("It is impossible to open file '%s'.\n", argv[1]);
return;
}
if ((out = fopen(argv[2], "w")) == NULL)
{ printf("It is impossible to open file '%s'.\n", argv[2]);
fclose(in); return;
}
for (n = 0; !feof(in); n++)
fscanf(in, "%s%d", s[n].str, &s[n].a);
fclose(in);
printf("Use Str for search? "); ans = getche();
if (ans == 'y' || ans == 'Y')
{ printf("\nInput string for search: "); scanf("%s",str); }
printf("Use A for search? "); ans = getche();
if (ans == 'y' || ans == 'Y')
{ check = 1; printf("\nInput A: "); scanf("%d", &a); }
for (i = 0; i < n; i++)
// Следующее условие проверяет содержимое структуры
// на равенство параметрам поиска, учитывая
// необходимость сравнения с этим параметром
if ((!*str || strcmp(str, s[i].str) == 0) &&
(!check || a == s[i].a))
fprintf(out, "%-30s %3d\n", s[i].str, s[i].a);
fclose(out);
}

3. Объединения

Объединение позволяет в разные моменты времени хранить в одном объекте значения разных типов. В процессе объявления объединения с ним ассоциируется набор типов значений, которые могут храниться в данном объединении. В каждый момент времени объединение может хранить значение только одного типа из набора. Контроль над тем, какого типа значение хранится в данный момент в объединении, возлагается на программиста.
union [<тег>] {<список объявлений элементов>} <описатель> [, <описатель> ...];
union <тег> <описатель> [, <описатель> ...];
 <Тег> предназначен для различения нескольких объединений, объявленных в одной программе.
Память, которая выделяется переменной типа объединение, определяется размером наиболее длинного элемента объединения. Все элементы объединения размещаются в одной и той же области памяти с одного и того же адреса. Значение текущего элемента объединения теряется, когда другому элементу объединения присваивается значение.

#include <stdio.h>

void main()
{ union
{ float f;
long int i;
} u;

printf("Input float number: "); scanf("%f", &u.f);
printf("Internal: %08x\n\n", u.i);
}


Динамическое распределение памяти. Списки

Лекция 7. Динамическое распределение памяти. Списки

1. Динамическое распределение памяти

Статически распределяемые переменные существуют либо всё время существования программы, либо в течение времени существования функции, в которой они объявлены (для локальных переменных). Иногда возникает необходимость управлять распределением памяти динамически во время работы программы, например, если мы не знаем точно, сколько памяти нам понадобиться, или для более гибкого управления памятью, чтобы не занимать память, которая реально не нужна.
Распределение памяти осуществляется функцией
void *malloc(size_t size);

 Прототип функции находится в файлах stdlib.h и malloc.h.
Данная функция выделяет память, объём которой в байтах указывается как параметр функции. Функция возвращает адрес выделенной памяти или NULL, если память не может быть выделена. NULL – это специальная константа, которая представляет собой недействительный указатель.
Функция возвращает указатель на пустой тип (void *), который может быть преобразован к любому типу и должен быть преобразован к нужному типу. Обязательно надо проверять результат, возвращаемый функцией!
Существуют также ещё две функции, которые позволяют сделать выделение памяти более гибким:
void *calloc(size_t num, size_t size);
void *realloc(void *memblock, size_t size);

 Первая выделяет память под массив из num элементов, каждый из которых имеет размер size. Элементы массива инициализируются нулями.
Вторая функция изменяет размер блока, выделенного ранее функциями malloc, calloc или realloc. Параметр memblock содержит адрес блока для изменения, а параметр size – необходимый размер блока. Положение блока в оперативной памяти может измениться, при этом содержимое будет скопировано.
После того, как отпала необходимость в выделенной памяти void free(void *memblock);

 2. Списки

Однонаправленный список – это структура, каждый элемент которой содержит адрес следующего элемента. В последнем элементе в данный адрес записывается значение специальной константы NULL для обозначения конца списка. Адрес первого элемента нужно хранить в специальной переменной.
Поскольку каждый элемент должен содержать информационную часть и адрес следующего элемента (которые в общем случае имеют разный тип) для хранения элементов списка используются структуры.
Существует два основных способа построения списков: с добавлением элементов в начало списка и с добавлением элементов в конец списка. Первый способ проще, но элементы в списке располагаются в обратном порядке. Если же нужно, чтобы элементы списка располагались в прямом порядке, надо использовать второй способ.

struct S
{ char str[21];
int a;
struct S *next;
};

struct S *begin, *cur, *prev;

// Добавление элементов в начало списка
cur = NULL;
while (!feof(in))
{ begin = (struct S *)malloc(sizeof(struct S));
if (begin == NULL)
{ printf("Insufficient memory\n");
fclose(in); fclose(out); return;
}
fscanf(in, "%s%d", begin->str, &(begin->a));
begin->next = cur;
cur = begin;
}

// Добавление элементов в конец списка
begin = (struct S *)malloc(sizeof(struct S));
if (begin == NULL)
{ printf("Insufficient memory\n");
fclose(in); fclose(out); return;
}
fscanf(in, "%s%d", begin->str, &(begin->a));
begin->next = NULL;
prev = begin;
while (!feof(in))
{ cur = (struct S *)malloc(sizeof(struct S));
if (cur == NULL)
{ printf("Insufficient memory\n");
fclose(in); fclose(out); return;
}
fscanf(in, "%s%d", cur->str, &(cur->a));
cur->next = NULL;
prev->next = cur;
prev = cur;
}

3. Примеры
Пример 1. Динамическое распределение массива.

 #include <stdio.h>
#include <stdlib.h>

void main(int argc, char *argv[])
{ double *a, *p, *end;
double s;
int n, i;
FILE *file;

if (argc < 2)
{ printf("Недостаточно параметров!\n");
return;
}
if ((file = fopen(argv[1], "r")) == NULL)
{ printf("Невозможно открыть файл '%s'\n", argv[1]);
return;
}
if (fscanf(file, "%d", &n) < 1)
{ printf ("Ошибка чтения из файла '%s'\n", argv[1]);
fclose(file);
return;
}

// Используем один из двух вариантов выделения памяти
if ((a = (double *)malloc(n * sizeof(double))) == NULL)
{ printf("Недостаточно памяти!\n");
fclose(file);
return;
}
if ((a = (double *)calloc(n, sizeof(double))) == NULL)
{ printf("Недостаточно памяти!\n");
fclose(file);
return;
}

for (i = 0; i < n; i++)
if (fscanf(file, "%lf", &a[i]) < 1)
{ printf ("Ошибка чтения из файла '%s'\n", argv[1]);
fclose(file);
return;
}
fclose(file);

// Обычный способ обработки массива
for (s = 0, i = 0; i < n; i++)
s += a[i];
printf("Сумма равна %lf\n", s);

// Использование указателя
// для доступа к элементам массива
for (s = 1, p = a, end = p + n; p < end; p++)
s *= *p;
printf("\Произведение равно %lf\n", s);

free(a); // Освобождение выделенной памяти
}

Пример 2. Работа со списком (см. пример в лекции 6).

#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <string.h>

struct S
{ char str[21];
int a;
struct S *next; // Можно объявить указатель
// на еще не объявленную структуру
};

void main(int argc, char *argv[])
{ struct S *begin, *cur, *prev;
int a, check = 0;
char str[21] = "";
FILE *in, *out;
char ans;

if (argc < 3)
{ printf("Too few arguments.\n"); return; }
if ((in = fopen(argv[1], "r")) == NULL)
{ printf("It is impossible to open file '%s'.\n", argv[1]);
return;
}
if ((out = fopen(argv[2], "w")) == NULL)
{ printf("It is impossible to open file '%s'.\n", argv[2]);
fclose(in); return;
}

// Строим список, добавляя элементы в конец
begin = (struct S *)malloc(sizeof(struct S)); // Первый элемент вводим отдельно
if (begin == NULL)
{ printf("Insufficient memory\n");
fclose(in); fclose(out); return;
}
fscanf(in, "%s%d", begin->str, &(begin->a));
begin->next = NULL;
prev = begin;
while (!feof(in))
{ cur = (struct S *)malloc(sizeof(struct S));
if (cur == NULL)
{ printf("Insufficient memory\n");
fclose(in); fclose(out); return;
}
fscanf(in, "%s%d", cur->str, &(cur->a));
cur->next = NULL;
prev->next = cur;
prev = cur;
}
fclose(in);

printf("Use Str for search? "); ans = getche();
if (ans == 'y' || ans == 'Y')
{ printf("\nInput string for search: "); scanf("%s",str); }
printf("\nUse A for search? "); ans = getche();
if (ans == 'y' || ans == 'Y')
{ check = 1; printf("\nInput A: "); scanf("%d", &a); }

// Вывод элементов, удовлетворяющих условию
for (cur = begin; cur; cur = cur->next) // Перебор всех элементов списка
if ((!*str || strcmp(str, cur->str) == 0) &&
(!check || a == cur->a))
fprintf(out, "%-30s %3d\n", cur->str, cur->a);

// Удаление элементов, не удовлетворяющих условию
for (prev = NULL, cur = begin; cur; ) // Адрес предыдущего элемента
if ((*str && strcmp(str, cur->str) != 0) || // понадобится при удалении
(check && a != cur->a))
if (prev == NULL) // Если нужно удалить первый элемент
{ begin = cur->next;
free(cur);
cur = begin;
}
else // Удаление не первого элемента
{ prev->next = cur->next;
free(cur);
cur = prev->next;
}
else // Если удалять не надо, просто меняем указатели
{ prev = cur; cur = cur->next; }

for (cur = begin; cur; cur = cur->next)
fprintf(out, "%-30s %3d\n", cur->str, cur->a);
fclose(out);

// Освобождение памяти, выделенной для списка
for (cur = begin; cur; )
{ prev = cur;
cur = cur->next;
free(prev);
}
begin = NULL;
}


Поразрядные операции

Лекция 8. Поразрядные операции

Введение

Информация в компьютере представляется в двоичной системе (наличие и отсутствие напряжения). Минимальной единицей информации является бит – ноль или единица, ложь или истина, «нет» или «да». Каждый байт состоит из 8 бит. Если число знаковое (signed), то самый левый его бит обозначает знак числа – 0 для положительных чисел и 1 для отрицательных чисел, остальные биты формируют модуль числа (это относится только к целым числам, вещественные числа всегда со знаком). Если число беззнаковое (unsigned), то все биты участвуют в формировании значения, но число может быть только положительным.
Положительные целые числа в компьютере представляются в нормальном коде – это обычное представление числа в двоичной системе, а отрицательные – в дополнительном коде. Для получения дополнительного кода берется двоичное представление равного по модулю целого числа, затем все цифры двоичного представления инвертируются (0 переходит в 1, 1 – в 0), при этом получается так называемый обратный код, к которому прибавляется 1 для получения дополнительного кода. Например, нормальный код числа 207 при использовании 2 байт – 0000000011001111, а дополнительный код числа -207 – 1111111100110001 (количество цифр в числе существенно!). Если сложить два эти числа, получается 0 (с переносом 1 за старший разряд числа). При сложении различных по модулю положительного и отрицательного чисел получается число в нормальном коде, если результат больше 0, и число в дополнительном коде, если результат меньше 0.
Существуют операции, которые работают с битами – можно взять отрицание, применить операции «И» или «ИЛИ». Однако нельзя применить эти операции к одному биту, а можно лишь применить одну и ту же операцию ко всем битам переменной.

1. Операции «отрицание» ~, «и» &, «или» |, «исключающее или» ^

Система счисления | А | В | ~ А | А & В | А | В | А ^ В
Десятичная | 15 | 85 | 240 (-16) | 5 | 95 | 90
Двоичная | 00001111 | 01010101 | 11110000 | 00000101 | 01011111 | 01011010
Шестнадцатеричная | 0f | 55 | f0 | 05 | 5f | 5a

Пример.

int a, b, c;

c = a & b;
c = a ^ b;
a |= 0x0f0f0f0f;

2. Операции сдвига

Операции сдвига вправо и влево сдвигают биты в переменной на заданное количество позиций. Существует три разновидности сдвига:
  • логический – освобождающиеся биты заполняются нулями как при сдвиге вправо, так и при сдвиге влево;
  • арифметический – при сдвиге влево освобождающиеся символы заполняются нулями, при сдвиге вправо освобождающиеся биты заполняются значением знакового (самого левого) бита;
  • циклический – выдвигаемые биты перемещаются на место освобождающихся.
В языке Ассемблер существуют все три разновидности сдвига, в языке С, однако, существует только одна операция сдвига влево и одна операция сдвига вправо. При сдвиге влево разницы между арифметическим и логическим сдвигом нет. При сдвиге вправо, если переменная объявлена как беззнаковая (unsigned), выполняется логический сдвиг, если же переменная объявлена как знаковая (по умолчанию) выполняется арифметический сдвиг.

Система счисления | Число | unsigned char |  | signed char
 |  | << | >> | << | >>
Десятичная | 189 (-67) | 122 | 94 | 122 | -34
Двоичная | 10111101 | 01111010 | 01011110 | 01111010 | 11011110
Шестнадцатеричная | bd | 7a | 5e | 7a | de

Пример.

int a;
unsigned int b;

a = a << 2; // Сдвиг влево, эквивалентен умножению на 4
a >>= 2; // Арифметический сдвиг вправо, деление на 4
b >>= 3; // Логический сдвиг вправо, деление на 8

3. Примеры

Функция, считающая количество единичных бит в числе.
int Bitcount (int x)
{ int c;
unsigned mask = 0x80000000; // Вспомогательная переменная, с помощью которой
// выделяется один бит из числа (за счет того, что
// все биты, кроме самого левого, после применения
// операции & будут равны 0, самый левый бит останется
// без изменений). Переменная инициализируется
// шестнадцатеричной константой 0x80000000, в которой
// все биты, кроме самого левого, равны 0.
for (c = 0; x != 0; x <<= 1)
if (x & mask) c++;
return c;
}
Функция, устанавливающая заданный бит в 0.
int ClearBit (int x, int pos)
{ unsigned mask = 1;

if (0 <= pos && pos < 32)
return x & (~(mask << pos));
return -1; // В случае ошибки возвращаем число, в котором все биты установлены в 1
}
Функция, устанавливающая заданный бит в 1.
int SetBit (int x, int pos)
{ unsigned mask = 1;

if (0 <= pos && pos < 32)
return x | (mask << pos);
return 0; // В случае ошибки возвращаем число, в котором все биты установлены в 0
}


Время жизни и область действия. Спецификации класса памяти. Функции с переменным числом аргументов

Лекция 9. Время жизни и область действия.
Спецификации класса памяти.
Функции с переменным числом аргументов

1. Время жизни и область действия. Спецификации класса памяти

Введение
Время жизни переменной может быть глобальным и локальным. Переменная с глобальным временем жизни характеризуется тем, что в течение всего времени выполнения программы с ней ассоциирована ячейка памяти и значение. Переменной с локальным временем жизни выделяется новая ячейка памяти при каждом входе в блок, в котором она определена или объявлена. Время жизни функции всегда глобально.
Область действия объекта (переменной или функции) определяет, в каких участках программы допустимо использование имени этого объекта.
Объявления и определения, записанные внутри какого-либо блока, называются внутренними (локальными). Объявления и определения, записанные за пределами всех блоков, называются внешними (глобальными).

1.1. Переменные
Объявление переменной задает имя и атрибуты переменной. Определение переменной, помимо этого, приводит к выделению для нее памяти. Кроме того, определение переменной задает ее начальное значение (явно или неявно). Таким образом, не каждое объявление переменной является определением переменной. К объявлениям, которые не являются определением относятся объявления формальных параметров функций, а также объявления со спецификацией класса памяти extern, которые являются ссылкой на переменную, определённую в другом месте программы (см. ниже).

Объявления переменных в языке С имеют следующий синтаксис:
[<спецификация класса памяти>] <тип> <описатель> [= <инициализатор>]
[, <описатель> [= <инициализатор>]];
 В языке С имеется четыре спецификации класса памяти:
  • auto
  • register
  • static
  • extern
Спецификации класса памяти auto и register могут быть использованы только на внутреннем уровне.

Переменная, объявленная на внешнем уровне, имеет глобальное время жизни. При отсутствии инициализатора такая переменная инициализируется нулевым значением. Область действия переменной, определенной на внешнем уровне, распространяется от точки, где она определена, до конца исходного файла. Переменная недоступна выше своего определения в том же самом исходном файле. На другие исходные файлы программы область действия переменной может быть распространена только в том случае, если ее определение не содержит спецификации класса памяти static.
Если в объявлении переменной задана спецификация класса памяти static, то в других исходных файлах могут быть определены другие переменных с тем же именем и любым классом памяти. Эти переменных никак не буду связаны между собой.
Спецификация класса памяти extern используется для объявления переменной, определенной где-то в другом месте программы. Такие объявления используются в случае, когда нужно распространить на данный исходный файл область действия переменной, определенной в другом исходном файле на внешнем уровне. Область действия переменной распространяется от места объявления до конца исходного файла. В объявлениях, которые используют спецификацию класса памяти extern, инициализация не допускается, так как они ссылаются на переменные, значения которых определены в другом месте.
Переменная, объявленная на внутреннем уровне, доступна только в том, блоке в котором она объявлена, независимо от класса памяти. По умолчанию она имеет класс памяти auto. Переменные этого класса размещаются в стеке. Переменные класса памяти auto автоматически не инициализируются, поэтому в случае отсутствия инициализации в объявлении значение переменной класса памяти auto считается неопределенным.
Спецификация класса памяти register требует, чтобы переменной была выделена память в регистре, если это возможно. Т.к. работа с регистрами происходит быстрее, спецификация класса памяти register обычно используется для переменных, к которым предполагается обращаться очень часто.
Для каждого рекурсивного входа в блок порождается новый набор переменных класса памяти auto и register. При этом каждый раз производится инициализация переменных, в объявлении которых заданы инициализаторы.
Если переменная, объявленная на внутреннем уровне, имеет спецификацию памяти static, то область действия остается прежней, а время жизни становится глобальным. В отличие от переменных класса памяти auto, переменные, объявленные со спецификацией класса памяти static, сохраняют свое значение при выходе из блока. Переменные класса памяти static могут быть инициализированы константным выражением. Если явной инициализации нет, то переменная класса памяти static автоматически инициализируется нулевым значением. Инициализация выполняется один раз во время компиляции и не повторяется при каждом входе в бока. Все рекурсивные вызовы данного бока будут разделять единственный экземпляр переменной класса памяти static.
Переменная, объявленная со спецификацией класса памяти extern, является ссылкой на переменную с тем же самым именем, определенную на внешнем уровне в любом исходном файле программы. Цель внутреннего объявления extern состоит в том, чтобы сделать определение переменной доступным именно внутри данного блока.

Файл 1

int a, b;

static int c;

extern int d = 10;

int f(int x)
{ int y;

static int z;
...
}

// Глобальные переменные

// На переменную с нельзя
// будет сослаться
// из другого файла

// Нельзя использовать
// инициализатор с extern

// Переменная класса auto.
// Локальные время жизни и
// область действия.

// Локальная переменная
// класса static. Имеет
// глобальное время жизни.
Файл 2

extern int a;

int d = 10;

auto int e;
register int e;

int g(int x)
{ extern int b;

register int w;
...
}

// Ссылка на переменную а
// из файла 1

// Нельзя использовать
// auto и register
// на внешнем уровне

// Ссылка на переменную b
// из файла 1, но она будет
// доступна только
// в функции g

// Переменная класса
// register

1.2. Функции
Объявление функции (прототип) задает ее имя, тип возвращаемого значения и атрибуты её формальных параметров.
Объявления функций в языке С имеют следующий синтаксис:
[<спецификация класса памяти>] <тип> <имя> (<список формальных параметров>);
Определение функции специфицирует тело функции, которое представляет собой составной оператор, содержащий объявления и операторы. Определение функции также задает ее имя, тип возвращаемого значения и атрибуты ее формальных параметров.
Определение функции имеет следующий синтаксис:
[<спецификация класса памяти>] <тип> <имя> (<список формальных параметров>) { <тело функции> }
Функции имеют глобальное время жизни.
Определение функции может быть задано только на внешнем уровне. Область действия функции распространяется от определения до конца файла. Чтобы использовать вызов функции выше ее определения, нужно написать ее объявление (прототип). Прототип некоторой функции может быть расположен на внешнем уровне (тогда вызов этой функции будет возможен из любой функции исходного файла) или же на внутреннем уровне (тогда вызов данной функции будет возможен только из блока, в котором находится прототип).
Для использования функции, расположенной в другом исходном файле, также необходимо написать ее прототип.
Если функция определена со спецификацией класса памяти static, ее использование в других исходных файлах программы невозможно.

2. Функции с переменным числом аргументов

В языке С существует возможность использовать функции с переменным числом аргументов. Для объявления такой функции надо указать многоточие (,…) в конце списка аргументов функции. Для вызова такой функции не требуется никаких специальных действий, просто задается столько аргументов, сколько нужно.
Функцию только с необъявленными аргументами, в принципе, определить можно, но выбрать параметры будет затруднительно, т.к. макроопределения для работы с необъявленными аргументами используют имя последнего объявленного формального параметра.
Внутри функции программист сам отвечает за выбор из стека дополнительных аргументов. Для работы с ними используются макроопределения va_arg, va_start и va_end, определённые в файле stdarg.h.

Пример. Функция с переменным числом параметров, аналогичная функции printf.

#include <stdio.h>
#include <stdarg.h>

void print(char *format, ...);

void main()
{ int a = 45, b = 87;
double f = 2.75;

print("dfd", a, f, b);
}

void print(char * format, ...)
{ va_list list; // Переменная для работы со списком аргументов
int n, i;
double f;

va_start(list, format); // Инициализация указателя на список аргументов
for (i = 0; format[i]; i++)
switch(format[i])
{ case 'd':
n = va_arg(list, int); // Выбираем очередной параметр
printf("%d\n", n);
break;
case 'f':
f = va_arg(list, double); // Выбираем очередной параметр
printf("%lf\n", f);
break;
}
va_end(list); // Сброс указателя на список аргументов в NULL
}


Классы

Лекция 10. Классы

Введение

Язык С++ является расширением языка С. Основные отличия состоят в наличии средств объектно-ориентированного программирования (классы, наследование), а также в возможности совместного использования (overloading) операций и функций. Совместное использование позволяет описать несколько функций с одним именем, если их вызовы можно различать по списку параметров.
Все операции и операторы языка С можно использовать в языке С++. А также, кроме объектно-ориентированного программирования и совместного использования, в С++ есть ряд новых возможностей.

  • В языке С++ добавлена одна операция: операция разрешения области действия ::. При повторном объявлении имени во вложенном блоке или классе предыдущие объявления оказываются скрытыми. Однако скрытое имя члена класса можно использовать, если квалифицировать его именем класса при помощи операции разрешения области действия. Скрытое глобальное имя можно использовать, если квалифицировать его унарной операцией разрешения области действия.

    int x = 5;

    int f(int x) // Возвращает параметр х, если он не 0, в противном
    { return x ? x : ::x; } // случае возвращает глобальную переменную х

    class X
    { public:
    static int f() { return 456; }
    };

    int ff()
    {
    return X::f(); // Вызов функции f класса Х, а не глобальной
    } // функции f

  •  В языке С++ введены новые операторы для распределения и освобождения
динамической памяти: new и delete.

int *p;
p =  new int;

// Выделение памяти для одно переменной типа int
delete p;

// Освобождение выделенной памяти
p =  new int [10];

// Выделение памяти для массива из 10 элементов типа int
delete [] p;

// Освобождение выделенной памяти
  • В языке С++ введены так называемые ссылки. Ссылка – это объект, который синтаксически выглядит как переменная, а по семантике является адресом. Объявление ссылки, кроме случаев, когда ссылка является параметром функции, возвращаемым функцией значением или членом класса, должно содержать инициализатор. Далее все операции производятся не над самой ссылкой, а над тем объектом, на который она указывает.

    int a = 10;
    int &r = a;
    r++; // Значение переменной а становится 11

    void f(double &a) { a += 3.14; }
    double d = 0;
    f(d); // d = 3.14

    int v[20];
    int& f(int i) { return v[i]; }
    f(3) = 7; // Элементу массива v[3] присваивается 7
    На мой взгляд, ссылки не стоит использовать слишком часто, т.к. они затрудняют понимание программы из-за несовпадения синтаксиса и семантики ссылки. Однако они могут быть полезны для того, чтобы не передавать по значению (и не создавать копию) параметр функции, который имеет большой размер. В том случае, если мы не собираемся менять этот параметр внутри функции, можно объявить ссылку с модификатором const. В этом случае мы будем гарантированы, что параметр не измениться, вместо большого объекта будет передавать его адрес, а для пользователя всё будет выглядеть как передача параметра по значению.

    class X { ... // Описание большого класса };
    int f (const X& x) { ... }

  •  В языке С++ можно задавать так называемые параметры функции по умолчанию. Если в
объявлении формального параметра задано выражение, то оно воспринимается как умолчание этого
параметра. Все последующие параметры также должны иметь умолчания. Умолчания параметров
подставляются в вызов функции при отсутствии в нём последних по списку параметров.

int g(int m = 1,  int n)
;              // Ошибка

int f(int m = 1,  int n = 2);
f(5, 6);

// Вызов функции с двумя параметрами
f(5);
// Эквивалентно f(5, 2);
f();
// Эквивалентно f(1, 2);
1. Понятие класса

Класс – это определённый пользователем тип. Объявление класса задаёт представление объектов этого класса и набор операций, которые можно применять к таким объектам. Класс обеспечивает абстракцию данных, он скрывает детали представления объекта и предоставляет доступ к содержащимся в нём данным только посредством функций и операций, описанных как часть этого класса.
Объявление класса выглядит следующим образом:
class <имя класса> {<список членов класса>};

Объявление класса является объявлением некоторого типа. Для дальнейшей работы необходимо объявлять соответствующие переменные или объекты класса.

class X { ... }; // Объявление типа
X x; // Объявляем переменную х – объект класса (типа) Х

 Объекты класса можно присваивать, передавать в качестве параметров функции и возвращать как её результат. Другие естественные операции, вроде проверки на равенство, также могут быть определены пользователем.

2. Члены класса

В списке членов класса можно объявлять переменные, функции, классы, перечисления, а также дружественные функции и классы. Член класса не может объявляться в списке членов класса дважды. Это относиться и к функциям (хотя могут быть функции с одним именем, но разным набором формальных параметров). Кроме того, нельзя объявить в классе переменную и функцию с одним именем. Список членов класса определяет полный набор членов этого класса. Нельзя добавлять к классу члены ещё в каком-то месте.

class X
{ int i;
int i; // Ошибка: повторное объявление
};
int X::k; // Ошибка: попытка объявить член класса
// вне объявления класса
class Y
{ int f();
int f(); // Ошибка: повторное объявление функции
int f(int x); // Ошибок нет
};
class Z
{ int f();
int f; // Ошибка: есть функция с таким же именем
};

Член класса не может иметь инициализатора. Член класса не может быть объявлен со спецификациями класса памяти auto, extern и register. Инициализация объектов класса осуществляется с помощью конструкторов. Объект класса не может содержать объект того же класса, но может содержать указатель или ссылку на объект того же класса.
Для доступа к членам класса (после объявления некоторой переменной этого класса или указателя на объект данного класса) используется следующий синтаксис:
<переменная> . <имя члена класса>
<указатель> -> <имя члена класса>

3. Функции-члены класса

Функция, объявленная в классе без спецификатора friend, называется функцией-членом класса. Её вызов имеет соответствующий синтаксис.
Описание функции-члена класса относиться к области действия класса. Это означает, что функция-член класса может непосредственно использовать имена членов своего класса.

class X // Объявление класса Х
{ int n;
void f();
};

void X::f() // Определение функции f из класса Х
{ n++; }

class X a, b; // Объявление переменных класса Х

a.f(); // Вызов функции f применяется к переменной а.
// Таким образом, изменяется член n переменной a.
// Переменная b остаётся без изменений.

3.1. Указатель this
 В нестатической функции-члене класса ключевое слово this обозначает указатель на объект, для которого вызвана данная функция, т.е. внутри функции-члена класса член того же класса с именем х можно обозначать как x, и как this -> x. Указатель на объект, для которого вызвана функция является неявным параметром этой функции.

class X
{ int a;
void f(int a) { this -> a = a; } // Члену класса a присваивается значение параметра a
};

Указатель this в функции-члене класса Х имеет тип X* const. Это означает, что изменить этот указатель нельзя.

3.2. Встраиваемые функции-члены класса
 Функция-член класса может быть описана внутри объявления класса. В этом случае она считается встраиваемой (inline) функцией. Спецификатор inline подсказывает компилятору, что открытая подстановка тела функции предпочтительнее обычной реализации вызова функции.
Открытая подстановка не влияет на результаты вызова функции, чем отличается от макроподстановки. Встраиваемая функция имеет обычный синтаксис описания функции и подчиняется всем правилам, касающимся области действия и контроля типов. Открытая подстановка является просто иной реализацией вызова функции. Вместо генерации кода, передающего управление и параметры единственному экземпляру тела функции, копия тела функции, соответственно модифицированная, помещается на место вызова. Это экономит время для передачи управления.
Для всех, кроме простейших, функций время выполнения функций доминирует над издержками времени на обслуживание вызова. Из этого следует, что для всех, кроме простейших, функций экономия за счёт открытой подстановки минимальна. Идеальным кандидатом для открытой подстановки является функция, делающая нечто простое, вроде увеличения или возврата значения. Существование таких функций вызывается необходимостью обработки скрытых данных.

Пример. В обоих случаях функция GetSize является встраиваемой.

class Vector
{ private:
int size;
...
public:
int GetSize() { return size; }
...
}

class Vector
{ private:
int size;
...
public:
int GetSize();
...
}
inline int Vector::GetSize() { return size; }

4. Доступ к членам класса

Управление доступом применяется единообразно к функциям-членам класса и данным- членам класса.
Член класса может быть:
  • приватным (private) – это значит, что его имя может употребляться лишь внутри функций-членов класса и друзей класса, в котором этот член класса объявлен;
  • защищённым (protected) – это значит, что его имя может употребляться лишь внутри функций-членов класса, друзей этого класса и производных от него классов;
  • публичным (public) – это значит, что его имя может употребляться внутри любой функции (а также и вне функций в инициализаторах).
Механизмы управления доступом в С++ обеспечивают защиту от случайного, а не от преднамеренного доступа. Однако это относится к проблемам дисциплины программирования, а не к проблемам языка.
Члены класса без спецификатора доступа по умолчанию являются приватными. Члены структур и объединений по умолчанию являются публичными.


Специальные функции-члены класса

Лекция 11. Специальные функции-члены класса

Некоторые функции-члены класса являются специальными в том смысле, что они влияют на создание, копирование и уничтожение объектов класса или задают способ приведения значений к значениям других типов. Часто такие специальные функции вызываются неявно.

1. Конструкторы

Функция-член класса с тем же именем, что и у класса, называется конструктором. Она используется для построения объектов этого класса. Конструктор не должен возвращать никакого значения, даже void.

1.1. Конструктор умолчания
 Конструктор умолчания класса Х – это конструктор класса Х, вызываемый без параметров. Конструктор умолчания обычно имеет вид Х::Х(), однако и конструктор, который может вызываться без параметров, потому что имеет параметры с умолчанием, например, Х::Х(int = 0), также считается конструктором умолчания. При отсутствии других объявленных конструкторов, конструктор умолчания генерируется компилятором.

class X
{ ...
public:
X();
...
};
X x ; // Вызов конструктора умолчания

class Y
{ ...
public:
Y(int n = 0);
...
}
Y y1(5); // Вызов конструктора с целым параметром
Y y2; // Конструктор вызывается как конструктор умолчания

1.2. Конструктор копирования
 Конструктор копирования для класса Х – это конструктор, который может быть вызван для копирования объекта класса Х, т.е. такой конструктор, который может быть вызван с одним параметром – ссылкой на объект класса Х. Например, X::X(const X&) и X::X(X&, int = 0) являются конструкторами копирования.
При отсутствии объявленных конструкторов копирования компилятор генерирует публичный конструктор копирования. Генерируемый конструктор копирования выполняет побитовое копирование объекта. Этот метод годится лишь при отсутствии в объекте указателей, которые хранят адреса динамически распределенной памяти. Сгенерированный конструктор скопирует адрес, а не содержимое памяти, таким образом, два разных объекта будут ссылаться на один и тот участок памяти и меняться синхронно, что не является ожидаемым поведением. В таком случае программист обязательно должен сам написать конструктор копирования, который будет, в частности, копировать содержимое динамически распределённой памяти.
Пример конструктора копирования см. в примере в конце лекции.

2. Деструкторы

Функция-член класса Х с именем ~Х называется деструктором. Она используется для разрушения значения класса Х непосредственно перед разрушением содержащего их объекта. Деструктор не имеет параметров и возвращаемого типа, нельзя задавать даже void.
Деструкторы автоматически вызываются, когда
  • автоматический или временный объект уходит из области действия;
  • завершается программа (для статически сконструированных объектов);
  • используется операция delete для объектов размещенных операцией new.
Деструктор может также вызываться явным образом.

class X
{ int n;
public:
X();
~X();
};
X xx;
xx.~X(); // Явный вызов деструктора

3. Преобразования
Преобразования (изменения типа) объектов класса выполняются конструкторами и преобразующими функциями.
Такие преобразования, называемые пользовательскими, часто неявно применяются в дополнение к стандартным преобразованиям. Например, функция, ожидающая параметр типа Х, может вызываться не только с параметром типа Х, но и с параметром типа Т, если существует преобразование из Т в Х. Кроме того, пользовательские преобразования применяются для приведения инициализаторов, параметров функций, возвращаемых функциями значений, операндов в выражениях, управляющих выражений, операторах цикла и выбора и для явного приведения типов.
Пользовательские преобразования применяются только там, где они однозначны.

3.1. Преобразование посредством конструктора
 Конструктор с одним параметром задаёт преобразование типа своего параметра к типу своего класса.
Конструктор с одним параметром не обязательно вызывается явно.

class X
{ int x;
public:
X(int n);
...
};
X::X(int n) { x = n; }

X a = 1; // Эквивалентно X a = X(1)

3.2. Преобразующие функции
 Функция-член класса Х, имя которой имеет вид operator <имя типа>, определяет преобразование из Х в тип, заданный именем типа. Такие функции называются преобразующими функциями или функциями приведения.
Для такой функции не могут быть заданы ни параметры, ни возвращаемый тип.

class X
{ int x;
public:
X(int n);
operator int();
...
};
X::X(int n) { x = n; }

X::operator int() { return x; }

int a;
X b;
a = (int)b; // Явный вызов преобразующей функции
a = b; // Неявный вызов преобразующей функции

4. Пример. Разработка класса для работы со стеком
Первый вариант
Разработку класса необходимо начинать с интерфейса, т.е. с открытых (публичных) функций, которые будут использоваться остальной частью программы для взаимодействия с разрабатываемым классом. Для стека определены две операции: взятие элемента из стека и добавление элемента в стек. Также определим конструктор (умолчания) и деструктор (обычно их объявляют для всех классов), а также вспомогательные функции, проверяющие пуст ли стек и была ли ошибка при работе со стеком. Таким образом, интерфейс класса следующий:

class Stack
{ public:
Stack();
~Stack();
int Push(int n);
int Pop();
int Is_Empty();
int Is_Error();
void LastError(char *error_text);
};

 Теперь можно разрабатывать структуру данных для класса. Для хранения элементов стека будем использовать массив. Также нам понадобятся переменная, указывающая на текущий элемент стека, и переменная, хранящая признак ошибки.
Кроме этого, необходимо, естественно, определить функции, входящие в класс. После этого можно использовать разработанный класс.

#include <stdio.h>
#include <string.h>

class Stack
{ private:
enum { SIZE = 100 }; // Область действия константы, опре-
enum { NO_ERROR, STACK_EMPTY, STACK_FULL }; // делённой с помощью define, - файл.
// Чтобы локализовать область действия константы
// в классе, используют перечислимый тип enum
int stack[SIZE]; // Массив для хранения элементов стека
int *cur; // Указатель на текущий элемент стека
int error; // Признак ошибки
public:
Stack(); // Конструктор
~Stack(); // Деструктор
int Push(int n); // Добавление элемента в стек
int Pop(); // Выбор элемента из стека
int Is_Empty(); // Проверка, пуст ли стек
int Is_Error(); // Проверка, была ли ошибка
void LastError(char *error_text); // Функция, возвращающая строку описания ошибки
};

Stack::Stack()
{ cur = stack; error = NO_ERROR; }

Stack::~Stack()
{ }

int Stack::Push(int n)
{ if (cur - stack < SIZE)
{ *cur++ = n; error = NO_ERROR; return 1; }
else
{ error = STACK_FULL; return 0; }
}

int Stack::Pop()
{ if (cur != stack)
{ error = NO_ERROR; return *--cur; }
else
{ error = STACK_EMPTY; return 0; }
}

inline int Stack::Is_Empty()
{ return cur == stack; }

inline int Stack::Is_Error()
{ return error != NO_ERROR; }

void Stack::LastError(char *error_text)
{ if (error == NO_ERROR)
strcpy(error_text, "There is no error");
else if (error == STACK_EMPTY)
strcpy(error_text, "Stack is empty");
else
strcpy(error_text, "Stack is full");
}

void main()
{ Stack s;
char str[50];

s.Push(1);
s.Push(2);
s.Push(3);
printf("%d\n", s.Pop());
printf("%d\n", s.Pop());
printf("%d\n", s.Pop());
printf("%d\n", s.Pop());
printf("%d\n", n);
s.LastError(str);
printf("%s\n", str);
s.Push(4);
s.Push(5);
s.Push(6);
s.LastError(str);
printf("%s\n", str);
}

Файл с программой для печати

Второй вариант
После некоторого размышления разработчик пришел к выводу, что использование статического массива для хранения элементов стека ограничивает разработанный класс, и решил использовать динамически распределяемый массив, размер которого можно будет при необходимости увеличить. Таким образом, вместо массива мы объявляем указатель и ещё одну переменную, которая будет хранить размер динамически распределённого массива.
В результате этих изменений разработчику пришлось также изменить конструктор, в который добавлены операторы для динамического распределения памяти, деструктор, в который добавлены операторы освобождения памяти, и функцию добавления элемента в стек, которая при необходимости перераспределяет выделенную память. Также был добавлен конструктор копирования. Остальные функции остались без изменений.

#include <stdio.h>
#include <string.h>
#include <malloc.h>

class Stack
{ private:
enum { SIZE = 100 };
enum { NO_ERROR, STACK_EMPTY, NOT_ENOUGH_MEMORY };
int size;
int *stack;
int *cur;
int error;
public:
Stack();
Stack(const Stack& s); // Конструктор копирования
~Stack();
int Push(int n);
int Pop();
int Is_Empty();
int Is_Error();
void LastError(char *error_text);
};

Stack::Stack()
{ size = SIZE;
stack = NULL;
if (stack = (int *)malloc(size * sizeof(int)))
{ cur = stack;
error = NO_ERROR;
}
else
{ error = NOT_ENOUGH_MEMORY;
size = 0;
}
}

Stack::Stack(const Stack& s)
{ size = s.size;
stack = NULL;
error = NO_ERROR;
if (size)
if ((stack = (int *)malloc(size * sizeof(int))) == NULL)
{ error = NOT_ENOUGH_MEMORY;
size = 0;
}
else
for (int i = 0; i < size; i++)
*(stack + i) = *(s.stack + i);
cur = stack;
}

Stack::~Stack()
{ if (stack) free(stack); }

int Stack::Push(int n)
{ if (!stack) return 0;
if (cur - stack < size)
{ *cur++ = n; error = NO_ERROR; return 1; }
else
if (stack = (int *)realloc(stack, (size + SIZE) * sizeof(int)))
{ cur = stack + size;
size += SIZE;
*cur++ = n;
error = NO_ERROR;
return 1;
}
else
{ error = NOT_ENOUGH_MEMORY; size = 0; return 0; }
}

int Stack::Pop()
{ if (cur != stack)
{ error = NO_ERROR; return *--cur; }
else
{ error = STACK_EMPTY; return 0; }
}

inline int Stack::Is_Empty()
{ return cur == stack; }

inline int Stack::Is_Error()
{ return error != NO_ERROR; }

void Stack::LastError(char *error_text)
{ if (error == NO_ERROR)
strcpy(error_text, "There is no error");
else if (error == STACK_EMPTY)
strcpy(error_text, "Stack is empty");
else
strcpy(error_text, "There is not enough memory");
}

void main()
{ Stack s;
char str[50];

s.Push(1);
s.Push(2);
s.Push(3);
printf("%d\n", s.Pop());
printf("%d\n", s.Pop());
printf("%d\n", s.Pop());
printf("%d\n", s.Pop());
s.LastError(str);
printf("%s\n", str);
s.Push(4);
s.Push(5);
s.Push(6);
s.LastError(str);
printf("%s\n", str);
}

Файл с программой для печати

Обратите внимание, – и это самое главное, – что основная программа осталась без изменений. Поскольку разработчик поступил правильно и начал с разработки интерфейса, изменения в самом классе не затронули остальную программу. Конечно, для стека всё просто, т.к. набор необходимых функций мал и хорошо известен. Но разработку класса всегда надо начинать с разработки интерфейса. Чем лучше будет продуман интерфейс используемых в программе классов, чем меньше будет проблем при разработке программы.


Совместное использование. Перегрузка операций

Лекция 12. Совместное использование.
Перегрузка операций

1. Совместное использование

Если в некоторой области действия имеется несколько различных объявлений функций с одним именем, это имя называется совместно используемым. Когда употребляется такое имя, нужная функция выбирается путём сравнения типов фактических параметров с типами формальных параметров.

double abs(double x);
int abs(int x);

abs(1); // Вызов int abs(int x)
abs(1.0); // Вызов double abs(double x)

 Поскольку для всякого типа Т типы Т и Т& допускают совпадающие множества инициализирующих значений, функции с типами параметров, различающимися только в этом отношении, не могут иметь одно имя.
Функции, которые различаются только возвращаемым типом, не могут иметь одно имя.
Различные версии совместно используемой функции-члена класса могут предоставлять различные права доступа.

class Buffer
{ private:
char *p;
int size;
protected:
Buffer(int s, char *np) { size = s; p = np; }
public:
Buffer(int s) { p = new char[size = s]; }
...
}

 При вызове функции с данным именем необходимо выбрать среди всех функций с этим именем из данной области действия, таких, что имеется набор преобразований параметров, делающий вызов возможным, функцию, для которой фактические параметры подходят наилучшим образом. Оптимально отожествляемая функция лежит в пересечении множеств функций, наилучшим образом подходящих по каждому параметру. Если это пересечение не содержит ровно одну функцию, вызов считается недопустимым.

2. Перегрузка операций

Большинство операций языка С могут использоваться совместно. Для этого в объявлении класса необходимо объявить функцию, имеющую следующее имя:
operator <операция> (<операнды>)
 Не могут использоваться совместно операции . .* :: ?:
И унарная, и бинарная формы операций + - * & могут использоваться совместно.
Для отличия префиксного и постфиксного вариантов операции функция, реализующая постфиксный вариант, объявляется с дополнительным параметром типа int.
Недопустимо и невозможно менять старшинство, ассоциативность и число операндов у операции.
Тождества, верные для операций над основными типами (например, ++а ~ а += 1), не обязаны выполняться в отношении операций над «классовыми» типами.
Совместно используемая операция не может иметь параметров с умолчаниями.
Функция-оператор должна либо быть функцией-членом, либо иметь хотя бы один параметр некоторого класса или ссылки на класс. Префиксная унарная операция может быть объявлена как нестатическая функция-член класса без параметров либо как функция, не являющаяся членом класса, с одним параметром. Бинарная операция может быть объявлена либо как нестатическая функция- член класса с одним параметром, либо как обычная функция (не член класса) с двумя параметрами.

class Complex
{ private:
double r, m;
public:
Complex(double nr = 0, double nm = 0);
Complex operator ++(); // Префиксная операция ++
Complex operator ++(int); // Постфиксная операция ++
Complex operator + (const Complex& c);
};

Complex::Complex(double nr, double nm)
{ r = nr; m = nm; }

Complex Complex::operator ++() // Объект класса надо возвращать, чтобы было
{ ++r; return *this; } // возможно вкладывать вызов операций ++ (как
// префиксной, так и постфиксной) в другие операторы
Complex Complex::operator ++(int) // Префиксная операция ++ возвращает новое значение
{ Complex x = *this; // операнда, в то время, как постфиксная операция ++
r++; // возвращает старое значение операнда
return x;
}

Complex Complex::operator + (const Complex& c)
{ Complex x;

x.r = r + c.r;
x.m = m + c.m;
return x;
}

void main()
{ Complex a(0, 0), b(2, 2), c;

++a; // Эквивалентно a.operator++()
a++; // Эквивалентно a.operator++(0)
c = a + b; // Эквивалентно a.operator+(b)
c = a + ++b;
}

3. Пример

Разрабатываемый класс Vector представляет собой вектор с переменным числом элементов. Над векторами определены операции присваивание, сложение, вычитание, скалярное произведение и сравнение. Для удобства доступа к элементам вектора также определена операция «индексное выражение».

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

class Vector
{ private:
int size; // Размер вектора
double *v; // Адрес массива для вектора
public:
Vector(int n = 0); // Конструктор умолчания
Vector(Vector& vector); // Конструктор копирования
~Vector(); // Деструктор
int GetSize(); // Получение размера вектора
int SetSize(int n); // Изменение размера вектора
Vector operator = (const Vector& vector); // Операция присваивания
double& operator [] (int n); // Индексное выражение
Vector operator - (); // Унарный минус
int operator == (const Vector& vector2); // Сравнение
Vector operator + (const Vector& vector2); // Сложение
Vector operator += (const Vector& vector2); // Составное присваивание
Vector operator - (const Vector& vector2); // Вычитание
Vector operator -= (const Vector& vector2); // Составное присваивание
Vector operator + (double value); // Сложение с числом
Vector operator += (double value); // Составное присваивание
Vector operator - (double value); // Вычитание числа
Vector operator -= (double value); // Составное присваивание
double operator * (const Vector& vector2); // Скалярное произведение
};

Vector::Vector(int n)
{ if (n < 0) n = 0;
size = n;
v = NULL;
if (size)
if ((v = (double *)malloc(size * sizeof(double))) == NULL)
size = 0;
}

Vector::Vector(Vector& vector)
{ size = vector.size;
v = NULL;
if (size)
if ((v = (double *)malloc(size * sizeof(double))) == NULL)
size = 0;
else
for (int i = 0; i < size; i++)
*(v + i) = vector[i];
}

Vector::~Vector()
{ if (v) free(v); }

inline int Vector::GetSize()
{ return size; }

int Vector::SetSize(int n)
{ if (n < 0) n = 0;
size = n;
if (size)
if ((v = (double *)realloc(v, size * sizeof(double))) == NULL)
size = 0;
return size; // Т.к. в языке С нет логического типа,
// равенство 0 и будет признаком ошибки
 }

Vector Vector::operator = (const Vector& vector) // Оператор присваивания выполняет
{ if (this == &vector) return *this; // те же действия, что и конструктор
size = vector.size; // копирования, и кроме того, возвращает
v = NULL; // присваиваемое значение,
if (size) // т.к. операция присваивания
if ((v = (double *)malloc(size * sizeof(double))) == NULL)
size = 0; // должна возвращать значение, которое
else // может быть использовано при вложении
for (int i = 0; i < size; i++) // оператора присваивания в другие
*(v + i) = vector[i]; // операторы
return *this;
}

double& Vector::operator [] (int n) // Результатом операции «индексное выражение»
{ if (n < 0) n = 0; // объявлена ссылка для того, чтобы можно
if (n >= size) n = size - 1; // было не только получать значение элемента
return *(this->v + n); // вектора, но и изменять его
}

Vector Vector::operator - ()
{ int i;

for (i = 0; i < size; i++)
*(this->v + i) = -*(this->v + i);
return *this;
}

int Vector::operator == (const Vector& vector2)
{ int i;

if (size != vector2.size) return 0;
for (i = 0; i < size; i++)
if (*(this->v + i) != *(vector2.v + i))
return 0;
return 1;
}

Vector Vector::operator + (const Vector& vector2)
{ Vector res(size);
int i;

if (size != vector2.size) return res;
for (i = 0; i < size; i++)
*(res.v + i) = *(this->v + i) + *(vector2.v + i);
return res;
}

Vector Vector::operator += (const Vector& vector2)
{ int i;

if (size != vector2.size) return *this;
for (i = 0; i < size; i++)
*(this->v + i) += *(vector2.v + i);
return *this;
}

Vector Vector::operator - (const Vector& vector2)
{ Vector res(size);
int i;

if (size != vector2.size) return res;
for (i = 0; i < size; i++)
*(res.v + i) = *(this->v + i) - *(vector2.v + i);
return res;
}

Vector Vector::operator -= (const Vector& vector2)
{ int i;

if (size != vector2.size) return *this;
for (i = 0; i < size; i++)
*(this->v + i) -= *(vector2.v + i);
return *this;
}

Vector Vector::operator + (double value)
{ Vector res(size);
int i;

for (i = 0; i < size; i++)
*(res.v + i) = *(this->v + i) + value;
return res;
}

Vector Vector::operator += (double value)
{ int i;

for (i = 0; i < size; i++)
*(this->v + i) += value;
return *this;
}

Vector Vector::operator - (double value)
{ Vector res(size);
int i;

for (i = 0; i < size; i++)
*(res.v + i) = *(this->v + i) - value;
return res;
}

Vector Vector::operator -= (double value)
{ int i;

for (i = 0; i < size; i++)
*(this->v + i) -= value;
return *this;
}

double Vector::operator * (const Vector& vector2)
{ double res = 0;
int i;

if (size != vector2.size) return res;
for (i = 0; i < size; i++)
res += *(this->v + i) * *(vector2.v + i);
return res;
}

void main()
{ Vector v1(3), v2, v3(2), v4;
int i;
double r;

v2.SetSize(3);
for (i = 0; i < v1.GetSize(); i++) // Задаём элементы векторов v1 и v2
{ v1[i] = i + 1;
v2[i] = i + 5;
}
v3 = v1 + v2;
v4 = v1 - v2 + v3;
v4 = -v4;
v3 += v1;
r = v1 * v2;
v4 = v1 * v2 + v3; // Т.к. нельзя изменить приоритет операций, сначала
// должна выполняться операция *. Но поскольку в классе
// не определена операция сложения числа с объектом класса,
// выражение вычислить невозможно
}

Файл с программой для печати


Статические члены класса. Друзья класса

Лекция 13. Статические члены класса.
Друзья класса

1. Статические члены класса

При объявлении класса данные-члены и функции-члены класса можно объявлять статическими – static. Существует лишь один экземпляр статических данных-членов класса, разделяемый всеми объектами этого класса в программе. Статический член является не частью объектов класса, а отдельным объектом. Статический член класса будет существовать, даже если не создано ни одного объекта класса.
Объявление статических данных-членов внутри объявления класса не является его определением. Определение должно быть дано где-нибудь ещё.
Статический член mem класса cl можно обозначать как cl::mem, т.е. независимо от объекта. На него можно также ссылаться при помощи операций доступа к членам класса (. и ->). При обращении к статическому члену класса через операцию доступа к членам класса выражение слева от этой операции не вычисляется.
Функция-член класса также может быть статической. Такая функция не получает указатель this и может ссылаться на нестатические члены класса только посредством операций доступа к членам класса (. и ->).
Статическая функция-член класса не может быть виртуальной. Не могут существовать одновременно статическая и нестатическая функция- член класса с одинаковым именем и одинаковым набором параметров. Статические члены глобального класса имеют внешнее связывание, т.е. могут быть доступны из других файлов программы. Локальные (т.е. объявленные внутри некоторой функции) классы не могут иметь статических членов.
Статические члены класса подчиняются обычным правилам доступа к членам класса.
Пример использования статических членов класса см. в примере в конце лекции.

2. Друзья класса

Друг класса – это функция, которая не является членом этого класса, но которой разрешается использовать его приватные и защищённые члены. Имя друга класса не лежит в области действия этого класса, и друг класса не вызывается при помощи операции доступа к члену класса.
Друг класса, как и его члены, является частью интерфейса класса. Дружба, как и другие права доступа, предоставляются классом, а не захватываются.
Спецификаторы доступа не затрагивают объявлений дружественных функций. «Дружба» не обладает ни наследуемостью, ни транзитивностью.

class X
{ private:
int n;
friend void friend_function(X* p, int i); // Спецификатор доступа private не работает
public:
void member_function(int i);
};

void friend_function(X* p, int i) // Функция не является членом класса,
{ p->n = i; } // поэтому X:: не пишем
// Поскольку дружественная функция не является членом
// класса, она не получает указатель this и ей
// необходимо явно передавать объект, с которым она
// будет работать

void X::member_function(int i)
{ n = i; } // Имеется в виду this->n = i

void f()
{ X obj;

friend_function(&obj, 10); // Функции вызываются по-разному,
obj.member_function(10); // но приводят к одному и тому же результату
}

 Когда дружественной объявляется совместно используемая функция, другом становится лишь функция с заданными типами параметров. Это сделано, чтобы избежать «захвата» прав доступа путём объявления ещё одной совместно используемой функции с новым набором параметров.

Функция-член класса Х может быть дружественной классу Y.

class Y
{ friend void X::f();
...
};

 Сразу все функции-члены класса Х могут быть объявлены дружественными классу Y:

class Y
{ friend class X;
...
};

Механизм дружественности важен по двум причинам. Во-первых, функция может быть другом двух классов. Это увеличивает эффективность работы такой функции и упрощает интерфейсы классов.
Во-вторых, дружественная функция допускает применение пользовательских преобразований к своему первому параметру, в то время как функции-члены класса – нет*. Это позволяет программисту выразить требование к первому параметру быть l-значением**, описав соответствующую функцию как член класса, и напротив, выразить отсутствие такого требования, описав такую функцию как дружественную.

class Complex
{ private:
double r, m;
public:
Complex();
Complex(double nr, double nm);
Complex(double n) { r = n; m = 0; };
//operator double () { return r; };
Complex operator =(const Complex& c);
friend Complex operator ++(Complex& c);
friend Complex operator ++(Complex& c, int);
friend Complex operator + (const Complex& c1, const Complex& c2);
Complex operator +=(const Complex& c); // Функцию operator += лучше реализовывать
}; // как функцию-член класса

Complex::Complex() // Два отдельных конструктора объявлены вместо одного с пара-
{ r = 0; m = 0; } // метрами по умолчанию, т.к. при наличии преобразующего
// конструктора с одним параметром возникает неоднозначность
Complex::Complex(double nr, double nm)
{ r = nr; m = nm; } // при использовании одного параметра при вызове конструктора

Complex Complex::operator =(const Complex& c)
{ if (&c == this) return *this;
r = c.r;
m = c.m;
return *this;
}

Complex operator ++(Complex& c)
{ ++c.r; return c; }

Complex operator ++(Complex& c, int)
{ Complex x = c;

c.r++;
return x;
}

Complex operator +(const Complex& c1, const Complex& c2)
{ Complex r;

r.r = c1.r + c2.r;
r.m = c1.m + c2.m;
return r;
}

Complex Complex::operator +=(const Complex& c)
{ r += c.r;
m += c.m;
return *this;
}

void main()
{ Complex a(0, 0), b(2, 2), c;
double d = 2;

c = ++a;
c = a++;
c = a + ++b;
c = a + d; // Неявные вызовы преобразующего конструктора. При наличии функции
c = 3 + b; // преобразования operator double() возникала бы неоднозначность
c += 4;
2 += c; // Ошибка, даже при наличии преобразования из числа типа
} // double к классу Complex, т.к. 2 не является l-значением

3. Пример

Приведённый в примере класс является частью программы, в которой на экране существуют несколько движущихся кружочков. При попадании мышкой в кружочек он уничтожается. Программа завершается после уничтожения всех кружочков.
В классе Circle существуют три статических члена – переменные, хранящие общее количество кружочков и размеры окна программы. Один экземпляр каждой из этих переменных разделяется всем объектами Circle. Также в классе Circle объявлены функции SetSize и GetCount, которые работают со статическими членами класса. Поскольку эти функции должны вызываться безотносительно некоторого объекта класса, и даже когда не существует ни одного объекта класса Circle, они должны быть объявлены как дружественные функции или как статические функции-члены класса. Если функции объявлены как статические функции-члены класса, при вызове они должны быть квалифицированы именем класса с помощью операции разрешения области действия.

3.1. Файл «Circle.h»
class Circle
{ private:
static int count; // Общее количество кружочков
static long int maxX, maxY; // Размеры экрана
int x, y, sx, sy; // Центр кружочка и его скорость
int radius; // Радиус
int colour; // Цвет
public:
Circle();
~Circle();

// Выбираем один из двух вариантов
friend void SetSize(int x, int y); // Дружественные функции
friend int GetCount();

static void SetSize(int x, int y); // Статические функции-члены класса
static int GetCount();

void Paint(HDC hdc);
void Move();
int Is_Inside(int px, int py);
int GetColour();
void ChangeSpeed();
};

#define WM_END (WM_USER + 1)

3.2. Файл «Circle.cpp»
#include "StdAfx.h"
#include "Circle.h"

extern HWND hWnd;

int Circle::count = 0; // Определение и инициализация
long int Circle::maxX = 800; // статических членов класса Circle
long int Circle::maxY = 600; // (в дополнение к объявлению внутри класса)

Circle::Circle()
{ ++count; // При создании кружочка увеличиваем
// общее количество на 1
x = rand() % maxX;
y = rand() % maxY;
radius = rand() % 7 + 6;
colour = rand() % 16;
sx = rand() % 15 - 7;
sy = rand() % 15 - 7;
}

Circle::~Circle()
{ if (--count == 0) // При уничтожении – уменьшаем на 1
PostMessage(hWnd, WM_END, 0, 0);
}

void Circle::Paint(HDC hdc) // Рисуем
{ Ellipse(hdc, x - radius, y + radius, x + radius, y - radius); }

void Circle::Move() // Перемещаем
{ x += sx;
if (x < 0) x = 0;
if (x > maxX) x = maxX;
y += sy;
if (y < 0) y = 0;
if (y > maxY) y = maxY;
}

int Circle::Is_Inside(int px, int py) // Попали?
{ return (px - x) * (px - x) + (py - y) * (py - y) <= radius * radius; }

int Circle::GetColour()
{ return colour; }

void Circle::ChangeSpeed()
{ sx = rand() % 15 - 7; sy = rand() % 15 - 7; }

// Функции SetSize и GetCount должны быть объявлены как дружественные функции
// или статические функции-члены класса, т.к. они должны вызываться безотносительно
// некоторого объекта класса, и даже когда нет ни одного объекта класса
void SetSize(int x, int y) // Дружественные функции
{ Circle::maxX = x; Circle::maxY = y; }

int GetCount()
{ return Circle::count; }

void Circle::SetSize(int x, int y) // Статические функции-члены класса
{ Circle::maxX = x; Circle::maxY = y; }

int Circle::GetCount()
{ return Circle::count; }

Файл с программой для печати
Вы можете также скачать полную версию этой программы

Содержание

* Имеется в виду, что первым параметром функции-члена класса является передаваемый неявно указатель this.
** Говоря упрощённо, под l-значением понимается выражение, которое может стоять слева от оператора присваивания. Например, переменная или индексное выражение для доступа к элементу массива, являются l-значениями. А константа или выражение, содержащее знаки операций, не являются l-значениями.


Наследование

Лекция 14. Наследование

1. Базовые и производные классы

Класс может быть порождён из другого класса, который называется базовым классом производного класса. Класс может быть порождён из одного или нескольких классов. Порождённые классы наследуют свойства базовых классов, включая данные-члены и функции-члены. Кроме того, в производном классе могут быть объявлены дополнительные данные-члены и функции-члены.
Объявление производного класса имеет следующий синтаксис:
class <имя производного класса> : <спецификатор доступа> <имя базового класса>
[, <спецификатор доступа> <имя базового класса> ]
{ ... };
 Если члены базового класса не переопределены в производном классе, они обозначаются и трактуются так же, как и члены производного класса. Говорят, что члены базового класса наследуются производным классом. Операция разрешения области действия :: может употребляться для явной ссылки на член базового класса. Это обеспечивает доступ к имени, которое переопределено в производном классе.
Производный класс может сам, в свою очередь, служить базовым классом.
Указатель на производный класс может быть неявно преобразован к указателю на однозначно доступный базовый класс. Ссылка на производный класс может быть неявно преобразована к ссылке на однозначно доступный базовый класс.

class Base
{ public:
int a, b;
};

class Derived : public Base
{ public:
int b, c;
};

void f
{ Derived d;

d.a = 1; // Инициализация a, унаследованного из класса Base
d.Base::b = 2; // Инициализация b из класса Base
d.b = 3; // Инициализация b, объявленного в классе Derived
d.c = 4;
// Преобразуем указатель на класс Derived
Base *bp = &d; // в указатель на класс Base
}
Класс называется непосредственным базовым классом, если он упоминается при объявлении производного класса, и косвенным базовым классом, если он является базовым классом для одного из базовых классов объявляемого класса.

class A { public: void f(); };
class B : public A { };
class C : public B // Класс B является непосредственным базовым классом
{ public: // для класса C, а класс A – косвенным базовым классом
void f(); void ff(); // для класса C
};
В записи <имя класса>::<имя> имя класса может быть именем косвенного базового класса, это имя класса определяет класс, в котором начинается поиск имени.

void C::ff()
{ f(); // Вызов функции f() из класса C
A::f(); // Вызов функции f() из класса A
B::f(); // Снова вызов функции f() из класса A,
} // т.к. в классе В функция f() не определена

2. Спецификаторы доступа для базовых классов

 Спецификатор доступа может быть:
  • public – в этом случае публичные члены базового класса становятся публичными членами производного класса, а защищённые члены базового класса становятся защищёнными членами производного класса;
  • private – в этом случае публичные и защищённые члены базового класса становятся приватными членами производного класса.
Приватные члены класса недоступны даже в производном классе, если только он не объявлен как дружественный.
По умолчанию подразумевается спецификатор доступа private.

class Base
{ private:
int a;
protected:
int b;
public:
int c;
friend class Derived;
};

class Derived : public Base
{ ... // a доступен, т.к. класс Derived объявлен дружественным.
// В противном случае, он был бы недоступен.
// b – защищённый член класса Derived
// c – публичный член класса Derived
};

class Derived : private Base
{ ... // a доступен, т.к. класс Derived объявлен дружественным.
// В противном случае, он был бы недоступен.
// b и c – приватные члены класса Derived
};

3. Объявления доступа

 Доступ к члену базового класса в производном классе можно скорректировать, упомянув его квалифицированное имя* в производном классе. Такое упоминание называется объявлением доступа.

class Base
{ public:
int n;
...
};
class Derived : private Base
{ public:
Base::n; // По умолчанию n был бы приватным членом класса Derived
...
};

 Объявление доступа для имени совместно используемой функции устанавливает доступ всем функция с этим именем в базовом классе.

class Base
{ public:
void f();
void f(int n);
};
class Derived : private Base
{ public:
Base::f; // Обе функции Base::f() и Base::f(int) будут публичными
};

 Доступ к члену базового класса не может быть скорректирован в производном классе, если в нём в то же время определяется член с тем же именем.

class Base
{ public:
void f();
};
class Derived : private Base
{ public:
void f(int n);
Base::f; // Ошибка!
};

4. Доступ к защищённым членам класса

Дружественная функция или функция-член производного класса может иметь доступ к защищённому статическому члену базового класса. Дружественная функция или функция-член производного класса может иметь доступ к защищённому нестатическому члену одного из его базовых классов только по указателю или ссылке на производный класс или через объект производного класса.

class Base
{ protected:
int n;
};

class Derived1 : public Base { ... };

class Derived2 : public Base
{ void member_function(Base *pb, Derived1 *p1);
friend void friend_function(Base *pb, Derived1 *p1, Derived2 *p2);
};

void Derived2::member_function(Base *pb, Derived1 *p1)
{ pb->n = 0; // Запрещено!
p1->n = 0; // Запрещено!
n = 0; // Доступ через объект класса Derived2
}

void friend_function(Base *pb, Derived1 *p1, Derived2 *p2)
{ pb->n = 0; // Запрещено!
p1->n = 0; // Запрещено!
p2->n = 0; // Доступ по указателю на объект класса Derived2
}

5. Множественное наследование

 Класс может быть порождён из любого числа базовых классов. Наличие более чем одного непосредственного базового класса называется множественным наследованием.

class A { ... };
class B { ... };
class C { ... };
class D : public A, public B, private C { ... };
Класс не может быть задан в качестве непосредственного базового класса более одного раза, но он может быть более одного раза косвенным базовым классом.

class A { ... };
class B : public A, public A { ... }; // Ошибка!

class A { ... };
class B : public A { ... };
class C : public A { ... };
class D : public B, public C { ... }; // Всё правильно
Здесь объект класса D будет иметь два подобъекта класса A.
Производный класс и его базовые классы могут быть представлены в виде направленного ациклического графа.

Класс не может появляться дважды в списке базовых классов просто потому, что каждая ссылка на него или его члены была бы неоднозначной. Эта проблема не возникает, если класс появляется дважды в качестве косвенного базового класса. Объект класса D имеет два подобъекта класса A – B::A и C::A.
Поскольку в объекте D имеется два объекта A, приведение (явно или неявное) между указателем на A и указателем на D неоднозначно и поэтому запрещено.

D *pd = new D;
A *pa = pd; // Неоднозначность!
pa = (A *)pd; // Всё равно неоднозначность!

pa = (A *)(B *)pd; // Приведение к указателю на A в объекте B
pa = (A *)(С *)pd; // Приведение к указателю на A в объекте С
pd = (D *)(B *)pa; // Приведение указателя на базовый класс к указателю
pd = (D *)(С *)pa; // на производный класс может привести к ошибкам

 К описателю базового класса можно добавлять ключевое слово virtual.

class A { ... };
class B : virtual public A { ... };
class C : virtual public A { ... };
class D : public B, public C { ... };

 В этом случае класс D содержит только один экземпляр класса А. Графически это выглядит так:

Класс может содержать как виртуальный, так и не виртуальный базовый класс данного типа.

class A { ... };
class B : virtual public A { ... };
class C : virtual public A { ... };
class D : public A { ... };
class E : public B, public C, public D { ... };

 Здесь класс E включает два экземпляра класса A: один из класса D, а другой виртуальный класс A, разделяемый классами B и C.

6. Примеры

 6.1. Вектор с ограниченными значениями

 На базе класса Vector (см. пример в лекции 12) реализуем производный класс, в котором задаются границы для значений, записываемых в вектор.

class Vector
{ protected:
int size;
double *v;
public:
Vector(int n = 0);
Vector(Vector& vector);
~Vector();
int GetSize();
int SetSize(int n);
Vector operator = (const Vector& vector);
double& operator [] (int n);
Vector operator - ();
int operator == (const Vector& vector2);
int operator != (const Vector& vector2);
Vector operator + (const Vector& vector2);
Vector operator += (const Vector& vector2);
Vector operator - (const Vector& vector2);
Vector operator -= (const Vector& vector2);
Vector operator + (double value);
Vector operator += (double value);
Vector operator - (double value);
Vector operator -= (double value);
double operator * (const Vector& vector2);
};

// В производный класс добавляем дополнительные данные (границы), функции GetBounds
// и SetBounds для получения и установки границ, функцию Bounds для изменения значений
// вектора, так чтобы они попадали в заданные границы. Также были переопределены
// конструкторы умолчания и копирования, деструктор, оператор присваивания. Остальные
// функции наследуются из базового класса. Вторые версии конструктора копирования и
// оператора присваивания необходимы, т.к. после использования, например, оператора +
// результат принадлежит к классу Vector.
class VectorWithBounds : public Vector
{ protected:
double min, max;
public:
VectorWithBounds(int n = 0, double min = 0, double max = 0);
VectorWithBounds(VectorWithBounds& vector);
VectorWithBounds(Vector& vector);
~VectorWithBounds();
void GetBounds(double *min, double *max);
void SetBounds(double min, double max);
VectorWithBounds operator = (VectorWithBounds& vector);
VectorWithBounds operator = (Vector& vector);
int Bounds();
};

// Конструкция : Vector(n) передаёт параметр в конструктор базового класса
VectorWithBounds::VectorWithBounds(int n, double min, double max) : Vector(n)
{ this->min = min; this->max = max; }

VectorWithBounds::VectorWithBounds(VectorWithBounds& vector)
{ size = vector.size;
min = vector.min;
max = vector.max;
v = NULL;
if (size)
if ((v = (double *)malloc(size * sizeof(double))) == NULL)
size = 0;
else
for (int i = 0; i < size; i++)
*(v + i) = vector[i];
}

VectorWithBounds::VectorWithBounds(Vector& vector)
{ size = vector.GetSize();
v = NULL;
if (size)
if ((v = (double *)malloc(size * sizeof(double))) == NULL)
size = 0;
else
for (int i = 0; i < size; i++)
if (vector[i] < min)
*(v + i) = min;
else if (vector[i] > max)
*(v + i) = max;
else
*(v + i) = vector[i];
}

VectorWithBounds::~VectorWithBounds()
{ }

VectorWithBounds VectorWithBounds::operator = (VectorWithBounds& vector)
{ if (this == &vector) return *this;
size = vector.size;
min = vector.min;
max = vector.max;
v = NULL;
if (size)
if ((v = (double *)malloc(size * sizeof(double))) == NULL)
size = 0;
else
for (int i = 0; i < size; i++)
*(v + i) = vector[i];
return *this;
}

VectorWithBounds VectorWithBounds::operator = (Vector& vector)
{ if (this == &vector) return *this;
size = vector.GetSize();
v = NULL;
if (size)
if ((v = (double *)malloc(size * sizeof(double))) == NULL)
size = 0;
else
for (int i = 0; i < size; i++)
if (vector[i] < min)
*(v + i) = min;
else if (vector[i] > max)
*(v + i) = max;
else
*(v + i) = vector[i];
return *this;
}

void VectorWithBounds::GetBounds(double *min, double *max)
{ if (min) *min = this->min; if (max) *max = this->max; }

void VectorWithBounds::SetBounds(double min, double max)
{ this->min = min; this->max = max; }

int VectorWithBounds::Bounds()
{ int n = 0;

for (int i = 0; i < size; i++)
if (*(v + i) < min) { *(v + i) = min; n++; }
else if (*(v + i) > max) { *(v + i) = max; n++; }
return n;
}

void main()
{ VectorWithBounds v1(3, 0, 10), v2, v3(2), v4;
int i = 0;
double r;

v2.SetSize(3);
v2.SetBounds(0, 10);
v3.SetBounds(0, 10);
v4.SetBounds(0, 10);
for (i = 0; i < v1.GetSize(); i++)
{ v1[i] = i + 1; v2[i] = i + 5; }
v3 = v1 + v2; // Используются функции operator +, operator -,
 v4 = v1 - v2; // operator -= из класса Vector. Затем используется
 v3 -= v2; // функция operator = (Vector& vector), которая
 v3.Bounds(); // присваивает результат операций +, -, принад-
 } // лежащий классу Vector в класс VectorWithBounds

Файл с программой для печати

6.2. Использование виртуального базового класса.

 Разработаем классы для задания информации о сотрудниках института. Класс Employee будет содержать общую информацию о сотруднике: фамилия, имя, отчество, табельный номер, адрес, телефон. Производные из него классы Teacher и Engineer содержат необходимую информацию о преподавателе и инженере соответственно. Также необходим класс Teacher_Engineer для хранения информации о сотрудниках, которые совмещают обе должности. Поскольку такой сотрудник является сотрудником только один раз, класс Employee должен быть виртуальным базовым классом, чтобы не было дублирования информации.

class Employee
{ protected:
char f[100], i[100], o[100], address[256], telephone[20];
int number;
. . .
};
class Teacher : virtual public Employee
{ protected:
class TimeTable timetable; // Расписание
. . .
};
class Engineer : virtual public Employee
{ protected:
class Instruction instruction; // Должностная инструкция
. . .
};
class Teacher_Engineer : public Teacher, public Engineer
{ . . . };
Файл с программой для печати

Содержание

* Квалифицированное имя – это конструкция типа <квалифицированное имя класса> :: <имя>, где квалифицированное имя класса также может содержать операцию разрешения области действия и имя базового класса, которое, в свою очередь, может опять содержать операцию разрешения области действия и имя своего базового класса и т.д.


Виртуальные функции и абстрактные классы

Лекция 15. Виртуальные функции и абстрактные классы

1. Виртуальные функции

Функция-член класса может содержать спецификатор virtual. Такая функция называется виртуальной. Спецификатор virtual может быть использован только в объявления нестатических функций-членов класса.
Если некоторый класс содержит виртуальную функцию, а производный от него класс содержит функцию с тем же именем и типами формальных параметров, то обращение к этой функции для объекта производного класса вызывает функцию, определённую именно в производном классе. Функция, определённая в производном классе, вызывается даже при доступе через указатель или ссылку на базовый класс. В таком случае говорят, что функция производного класса подменяет функцию базового класса. Если типы функций различны, то функции считаются разными, и механизм виртуальности не включается. Ошибкой является различие между функциями только в типе возвращаемого значения.

class Base
{ public:
virtual void f1();
virtual void f2();
virtual void f3();
void f();
};

class Derived : public Base
{ public:
void f1();
void f2(int); // Скрывает Base::f2()
char f3(); // Ошибка – различие только в типе
// возвращаемого значения!
void f();
};

Derived *dp = new Derived;
Base *bp = dp; // Преобразование по умолчанию
bp->f1(); // Вызов Derived::f1
bp->f2(); // Вызов Base::f2
bp->f(); // Вызов Base::f
bp->Base::f1(); // Вызов Base::f1
dp->f2(0); // Вызов Derived::f2
dp->f2(); // Ошибка, т.к. не задан параметр
dp->Base::f2(); // Вызов Base::f2
dp->f3(); // Вызов Base::f3 (из класса Derived функция f3
// была удалена, как ошибочная)
Интерпретация вызова виртуальной функции зависит от типа объекта, для которого она вызывается, в то время как интерпретация вызова невиртуальной функции-члена класса зависит от типа указателя или ссылки, указывающей на этот объект.
Этот механизм делает производные классы и виртуальные функции ключевыми понятиями при разработке программ на С++. Базовый класс определяет интерфейс, для которого производные классы обеспечивают набор реализаций. Указатель на объект класса может передаваться в контекст, где известен интерфейс, определённый одним из его базовых классов, но этот производный класс неизвестен. Механизм виртуальных функций гарантирует, что этот объект всё равно будет обрабатываться функциями, определёнными для него, а не для базового класса.
Спецификатор virtual предполагает принадлежность функции классу, поэтому виртуальная функция не может быть ни глобальной функцией, ни статическим членом класса, поскольку вызов виртуальной функции нуждается в конкретном объекте для выяснения того, какую именно функцию следует вызывать.
Подменяющая функция в производном классе также считается виртуальной, даже при отсутствии спецификатора virtual.
Виртуальная функция может быть объявлена дружественной в другом классе.

2. Абстрактные классы

 Механизм абстрактных классов служит для представления общих понятий, которые фактически используются лишь для порождения более конкретных понятий. Абстрактный класс можно также употреблять как определение интерфейса, в котором производные классы обеспечивают разнообразие реализаций.
Абстрактный класс – это класс, который может использоваться лишь в качестве базового класса для некоторого другого класса. Класс является абстрактным, если он содержит хотя бы одну чистую виртуальную функцию.
Виртуальная функция называется чистой, если в объявлении функции внутри объявления класса задан чистый спецификатор = 0.

class Shape
{ public:
virtual void draw() = 0; // Чистая виртуальная функция
...
};
Абстрактный класс нельзя употреблять в качестве типа объектов, типа параметров функций, типа возвращаемого функцией значения или как тип при явном приведении типа. Можно, однако, объявлять указатели и ссылки на абстрактный класс.

Shape s; // Ошибка: объект абстрактного класса
Shape *s; // Всё правильно
Shape f(); // Ошибка
void f(Shape s); // Ошибка
Shape& f(Shape &s); // Всё правильно
Чистые виртуальные функции наследуются и остаются чистыми виртуальными функциями.

3. Пример

 // MyShapes – абстрактный класс для представления обобщенной фигуры. Все его функции
// являются чистыми виртуальными функциями
class MyShapes
{ protected:
COLORREF color;
int left, top, right, bottom;
public:
enum {LEFT, UP, RIGHT, DOWN};
virtual void Draw(HDC hdc) = 0;
virtual void Move(int where, int x, int y) = 0;
virtual void Move(int where, const MyShapes *shape) = 0;
virtual double Space() = 0;
virtual double Perimeter() = 0;
virtual int IsInside(int x, int y) = 0;
friend class Circle; // Классы объявлены дружественными, т.к. функция
friend class Triangle; // Move(int where, const MyShapes *shape) получает
// указатель на объект класса MyShapes,
// и без объявления классов дружественными
// невозможно получить доступ к защищённым членам
// класса MyShapes left, top, right, bottom
};

// Circle – класс для представления круга
class Circle : public MyShapes
{ private:
int cx, cy, radius;
public:
Circle(int x = 0, int y = 0, int r = 0, COLORREF c = RGB(0, 0, 0));
~Circle();
void Draw(HDC hdc);
void Move(int where, int x, int y);
void Move(int where, const MyShapes *shape);
double Space();
double Perimeter();
int IsInside(int x, int y);
};

Circle::Circle(int x, int y, int r, COLORREF c)
{ cx = x; cy = y; radius = r;
color = c;
left = cx - radius; top = cy - radius;
right = cx + radius; bottom = cy + radius;
}

void Circle::Draw(HDC hdc)
{ HPEN pen;
HBRUSH brush;

pen = CreatePen(PS_SOLID, 1, color);
brush = CreateSolidBrush(color);
SelectObject(hdc, pen); SelectObject(hdc, brush);
Ellipse(hdc, cx - radius, cy - radius, cx + radius, cy + radius);
DeleteObject(pen); DeleteObject(brush);
}

int Circle::IsInside(int x, int y)
{ return (x - cx) * (x - cx) + (y - cy) * (y - cy) <= radius * radius; }

double Circle::Space()
{ return 3.1415926536 * radius * radius; }

// Triangle – класс для представления треугольника
class Triangle : public MyShapes
{ protected:
int x1, y1, x2, y2, x3, y3;
public:
Triangle(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0, int x3 = 0, int y3 = 0,
COLORREF c = RGB(0, 0, 0));
~Triangle();
void Draw(HDC hdc);
void Move(int where, int x, int y);
void Move(int where, const MyShapes *shape);
double Space();
double Perimeter();
int IsInside(int x, int y);
};

Triangle::Triangle(int x1, int y1, int x2, int y2, int x3, int y3, COLORREF c)
{ this->x1 = x1;
this->y1 = y1;
this->x2 = x2;
this->y2 = y2;
this->x3 = x3;
this->y3 = y3;
color = c;
left = Min(x1, x2, x3);
top = Min(y1, y2, y3);
right = Max(x1, x2, x3);
bottom = Max(y1, y2, y3);
}

void Triangle::Draw(HDC hdc)
{ HPEN pen;
HBRUSH brush;
POINT points[] = {{x1, y1}, {x2, y2}, {x3, y3}};

pen = CreatePen(PS_SOLID, 1, color);
brush = CreateSolidBrush(color);
SelectObject(hdc, pen); SelectObject(hdc, brush);
Polygon(hdc, points, 3);
DeleteObject(pen); DeleteObject(brush);
}

int Triangle::IsInside(int x, int y)
{ Triangle t1(x1, y1, x2, y2, x, y),
t2(x1, y1, x3, y3, x, y),
t3(x2, y2, x3, y3, x, y);

if (fabs(Space() - t1.Space() - t2.Space() - t3.Space()) < 0.001)
return 1;
else
return 0;
}

double Triangle::Space()
{ return fabs((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)) / 2; }

// Объявляем массив указателей на фигуры
MyShapes *shapes[20];
...
// В массив указателей на фигуры добавляем указатель на новый круг и рисуем его
case ID_CIRCLE:
if (DialogBox(hInst, (LPCTSTR)IDD_NEWCIRCLE, hWnd, (DLGPROC)NewCircle) == IDOK)
{ shapes[count] = new Circle(cx, cy, radius, RGB(r, g, b));
shapes[count++]->Draw(memdc);
InvalidateRect(hWnd, NULL, 1);
}
break;
// В массив указателей на фигуры добавляем указатель на новый треугольник и рисуем его
case ID_TRIANGLE:
if (DialogBox(hInst, (LPCTSTR)IDD_NEWTRIANGLE, hWnd, (DLGPROC)NewTriangle) == IDOK)
{ shapes[count] = new Triangle(x1, y1, x2, y2, x3, y3, RGB(r, g, b));
shapes[count++]->Draw(memdc);
InvalidateRect(hWnd, NULL, 1);
}
break;
// Выстраиваем все существующие фигуры сверху вниз
case ID_UPDOWN:
for (i = 1; i < count; i++)
{ shapes[i]->Move(MyShapes::DOWN, shapes[i - 1]);
shapes[i]->Draw(memdc);
}
InvalidateRect(hWnd, NULL, 1);
break;
...

Файл с программой для печати
Вы можете также скачать полную версию этой программы


Шаблоны. Обработка исключительных ситуаций

Лекция 16. Шаблоны.
Обработка исключительных ситуаций

1. Шаблоны

Шаблон класса определяет данные и операции потенциально неограниченного множества родственных классов. Шаблон функции определяет потенциально неограниченное множество родственных (совместно используемых) функций.
Объявление шаблона имеет следующий синтаксис:
template <список параметров шаблона> объявление
 Здесь угловые скобки являются элементом синтаксиса.
Объявление в объявлении шаблона должно описывать функцию или класс. Объявление шаблона может быть только глобальным.
Список параметров шаблона не может быть пуст. Шаблон с пустым списком параметров может быть просто описан как обычный класс либо функции, и потому не имеет смысла.
Механизм шаблонов предоставляет способ описания обобщённых типов, таких как список, вектор, ассоциативный массив, где конкретный тип элементов задаётся параметром.

1.1. Шаблоны функций

Шаблон функции задаёт способ построения отдельных функций. Синтаксис шаблона функции:
template <список параметров шаблона функции> тип_функции имя_функции (список формальных параметров) { ... }
 Все параметры из списка параметров шаблона функции должны быть так называемыми т [image] повыми параметрами, т.е. параметрами, задающими типы формальных параметров функции и/или возвращаемого функцией значения.

template <typename TYPE> TYPE abs(TYPE x)
{ return x >= 0 ? x : -x; }

void main()
{ int i = 567;
double d = -123.45;

printf("%7d\n", abs<int>(i));
printf("%7.2lf\n", abs<double>(d));
printf("%7d\n", abs(i)); // Параметр шаблона определяется
printf("%7.2lf\n", abs(d)); // по типу фактического параметра функции
}
Можно определить отдельную функцию для определённого типа параметра шаблона, задав явное описание функции для этого типа.
char abs(char x)
{ return x; }
Или с использованием нового варианта синтаксиса:
template <> char abs<char>(char x)
{ return x; }
При отождествлении некоторого вызова функции с шаблоном функции генерируется отдельная шаблонная функция с параметрами, точно отождествляемыми с типами фактических параметров вызова.

template <typename TYPE> TYPE max(TYPE a, TYPE b)
{ return a > b ? a : b; }

void main()
{ int i = 567;
float f = 7.5;
double d = -123.45;

printf("%7d\n", max(i, 0)); // Всё правильно
printf("%7.2lf\n", max(d, 0)); // Ошибка: 0 – константа целого типа
printf("%7.2lf\n", max(d, 0.0)); // Всё правильно
printf("%7.2lf\n", max(d, f)); // Ошибка: f и d имеют разные типы
printf("%7.2lf\n", max(d, (double)f)); // Всё правильно
}
Шаблонная функция может совместно использоваться с другими (обычными) функциями и с другими шаблонными функциями.

template <typename TYPE> TYPE max(TYPE a, TYPE b)
{ return a > b ? a : b; }

template <typename TYPE> TYPE max(TYPE a, TYPE b, TYPE c)
{ TYPE d;

d = a;
if (b > d) d = b;
if (c > d) d = c;
return d;
}

1.2. Шаблоны класса

 Шаблон класса задаёт способ построения отдельных классов. Синтаксис шаблона класса:
template <список параметров шаблона класса> class имя_шаблона_класса { ... };
 Здесь угловые скобки являются элементом синтаксиса.
Имя шаблона класса должно быть уникально в программе. Список параметров шаблона класса может содержать т [image] повые параметры, а также обычные параметры.
После объявления шаблона можно объявить некоторый класс по этому шаблону:
имя_шаблона_класса <список фактических параметров шаблона>
 Это объявление задаёт так называемое имя класса по шаблону. Каждый фактический параметр шаблона может быть именем типа (т [image] повый параметр) или выражением, а именно: константным выражением или адресом глобального объекта или функции или статического члена класса.

template <typename TYPE> class Vector
{ private:
int size;
TYPE *v;
public:
Vector(int n = 0);
~Vector();
...
};

template <typename TYPE, int NMAX> class Vector
{ private:
int size;
TYPE v[NMAX];
public:
Vector(int n = 0);
~Vector();
...
};

template <class CLASS, void (*err_fun)()> class List
{ ... };
Класс может быть описан как «подменяющий» описание шаблонного класса.
class Vector<char> { ... }; // Отдельная реализация вектора из элементов типа char
1.2.1. Шаблоны функций-членов класса
 Функция-член шаблонного класса неявно оказывается шаблонной функцией, параметрами шаблона которой являются параметры шаблона класса.

template <typename TYPE, int NMAX> class Vector
{ private:
int size;
TYPE v[NMAX];
public:
Vector(int n = 0);
~Vector();
int GetSize();
};

 Данное объявление класса объявляет три шаблона функций. Соответственно их описание должно выглядеть следующим образом:

template <typename TYPE, int NMAX> Vector<TYPE, NMAX>::Vector(int n)
{
if (0 <= n && n <= NMAX)
size = n;
else
size = 0;
}

template <typename TYPE, int NMAX> Vector<TYPE, NMAX>::~Vector()
{ }

template <typename TYPE, int NMAX> inline int Vector::GetSize()
{ return size; }

1.2.2. Друзья
 Функция-друг шаблона не становится неявно шаблонной функцией. Однако если параметры функции или возвращаемое значение принадлежат шаблонному классу, такая функция становится шаблонной.

template <class T> class X
{ public:
friend void f1();
friend X<T>* f2();
friend int f3(X<T> *p);
friend void f4(X *p); // Ошибка, т.к. не существует класса X
};

void f1() { ... }

template <class T> X<T>* f2 () { ... }

template <class T> int f3(X<T> *p) { ... }

1.3. Пример

 В данном примере разрабатывается шаблон для класса Vector. Параметрами шаблона являются тип элементов вектора и максимально возможное количество элементов вектора, которое, однако, может отличаться от реально используемого количества элементов.
Данный шаблон затем используется для генерации одномерных массивов из 10 вещественных элементов и матриц. Каждая матрица состоит из 5 элементов, каждый из которых генерируется по тому же шаблону и состоит из 5 вещественных элементов.

1.3.1. Файл «Vector.h»
template <typename TYPE, int NMAX> class Vector
{ private:
int size;
TYPE v[NMAX];
public:
Vector(int n = 0);
~Vector();
int GetSize();
int SetSize(int n);
Vector operator = (Vector& vector);
TYPE& operator [] (int n);
int operator == (const Vector& vector2);
Vector operator + (const Vector& vector2);
Vector operator += (const Vector& vector2);
Vector operator - (const Vector& vector2);
Vector operator -= (const Vector& vector2);
double operator * (const Vector& vector2); // Тип double для этого оператора
// выбран как наиболее общий числовой тип
};

template <typename TYPE, int NMAX> Vector<TYPE, NMAX>::Vector(int n)
{
if (0 <= n && n <= NMAX)
size = n;
else
size = 0;
}

template <typename TYPE, int NMAX> Vector<TYPE, NMAX>::~Vector()
{ }

template <typename TYPE, int NMAX> inline int Vector<TYPE, NMAX>::GetSize()
{ return size; }

template <typename TYPE, int NMAX> int Vector<TYPE, NMAX>::SetSize(int n)
{
if (0 <= n && n <= NMAX)
size = n;
else
size = 0;
return size;
}

template <typename TYPE, int NMAX>
Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator = (Vector& vector)
{
if (this == &vector) return *this;
size = vector.size;
for (int i = 0; i < size; i++)
*(v + i) = vector[i];
return *this;
}

template <typename TYPE, int NMAX>
inline TYPE& Vector<TYPE, NMAX>::operator [] (int n)
{
if (n < 0) n = 0;
if (n >= size) n = size - 1;
return *(this->v + n);
}

template <typename TYPE, int NMAX>
int Vector<TYPE, NMAX>::operator == (const Vector& vector2)
{ int i;

if (size != vector2.size) return 0;
for (i = 0; i < size; i++)
if (*(this->v + i) != *(vector2.v + i))
return 0;
return 1;
}

template <typename TYPE, int NMAX>
Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator + (const Vector& vector2)
{ Vector res(size);
int i;

if (size != vector2.size) return res;
for (i = 0; i < size; i++)
res[i] = *(this->v + i) + *(vector2.v + i);
return res;
}

template <typename TYPE, int NMAX>
Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator += (const Vector& vector2)
{ int i;

if (size != vector2.size) return *this;
for (i = 0; i < size; i++)
*(this->v + i) += *(vector2.v + i);
return *this;
}

template <typename TYPE, int NMAX>
Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator - (const Vector& vector2)
{ Vector res(size);
int i;

if (size != vector2.size) return res;
for (i = 0; i < size; i++)
res[i] = *(this->v + i) - *(vector2.v + i);
return res;
}

template <typename TYPE, int NMAX>
Vector<TYPE, NMAX> Vector<TYPE, NMAX>::operator -= (const Vector& vector2)
{ int i;

if (size != vector2.size) return *this;
for (i = 0; i < size; i++)
*(this->v + i) -= *(vector2.v + i);
return *this;
}

template <typename TYPE, int NMAX>
double Vector<TYPE, NMAX>::operator * (const Vector& vector2)
{ double res = 0;
int i;

if (size != vector2.size) return res;
for (i = 0; i < size; i++)
res += *(this->v + i) * *(vector2.v + i);
return res;
}

 1.3.2. Файл «Vector.cpp»
#include <stdio.h>
#include "Vector.h"

void PrintVector(Vector<double, 10> *v);
void PrintMatrix(Vector<Vector<double, 5>, 5> *v);

void main()
{ Vector<double, 10> v1(3), v2, v3;
int i, j;
Vector<Vector<double, 5>, 5> m1(3), m2, m3;
double r;

// Вектор
v2.SetSize(3);
for (i = 0; i < v1.GetSize(); i++)
{ v1[i] = i; v2[i] = i + 5; }
PrintVector(&v1);
PrintVector(&v2);
v3 = v1 + v2;
PrintVector(&v3);
v3 = v1 - v2;
PrintVector(&v3);
r = v1 * v2;
printf("%9.3lf\n\n", r);

// Матрица
m2.SetSize(3);
for (i = 0; i < m1.GetSize(); i++)
m1[i].SetSize(2);
for (i = 0; i < m2.GetSize(); i++)
m2[i].SetSize(2);
for (i = 0; i < m1.GetSize(); i++)
for (j = 0; j < m1[i].GetSize(); j++)
{ m1[i][j] = (i + 1) * (j + 1); m2[i][j] = (i + 1) * j + 5; }
PrintMatrix(&m1);
PrintMatrix(&m2);
m3 = m1 + m2;
PrintMatrix(&m3);
m3 = m1 - m2;
PrintMatrix(&m3);
r = m1 * m2;
printf("%9.3lf\n", r);
}

void PrintVector(Vector<double, 10> *v)
{ int i;

for (i = 0; i < v->GetSize(); i++)
printf("%9.3lf", (*v)[i]);
printf("\n");
}

void PrintMatrix(Vector<Vector<double, 5>, 5> *v)
{ int i, j;

for (i = 0; i < v->GetSize(); i++)
{ for (j = 0; j < (*v)[i].GetSize(); j++)
printf("%9.3lf", (*v)[i][j]);
printf("\n");
}
printf("\n");
}

Файл с программой для печати

2. Обработка исключительных ситуаций

 Обработка исключительных ситуаций обеспечивает способ передачи управления и информации в неопределённую точку, где было выражено желание обрабатывать ситуации данного типа. Ситуации любого типа могут возбуждаться (throw) и перехватываться (catch), и для функции может быть перечислено множество ситуаций, которые могут быть в ней возбуждены.
Реакция будет вызвана только в случае исполнения выражения возбуждения внутри блока с контролем или в функциях, вызванных из этого блока.
Синтаксис блока с контролем:
try { ... } <список реакций>
 Список реакций представляет собой следующую конструкцию:
catch (<объявление ситуации>) { ... } [ catch (<объявление ситуации>) { ... } ... ]
 Выражение возбуждения имеет следующий синтаксис:
throw <выражение>;
 При возбуждении ситуации (т.е. выполнении оператора throw) управление передаётся на реакцию. Тип операнда оператора throw определяет, какие реакции могут перехватывать данную ситуацию.
В объявлении ситуации можно задать многоточие, которое даёт отождествление с любой ситуацией. Реакция с многоточием, если она есть, должна быть последней в списке реакций некоторого блока с контролем.
Если среди реакций блока с контролем подходящей не найдено, поиск подходящей реакции продолжается в блоке с контролем, охватывающем данный блок с контролем.
Если в программе не нашлось подходящей реакции, вызывается функция terminate(). Функция terminate() вызывает функцию, заданную при последнем обращении к функции set_terminate(). По умолчанию функция, вызываемая из функции terminate(), есть abort(). Функция, вызываемая функцией terminate(), должна завершать выполнение программы.

2.1. Примеры
Проверка открытия файла

void main()
{ FILE *f;

try
{ f = fopen("in.txt", "r");
if (!f) throw "in.txt";
}
catch (char *str)
{ printf("Impossible to open file '%s'!\n", str); return; }
. . .
}

 Проверка адреса

void main()
{ int *p = NULL;
try
{ *p = 0; }
catch (...)
{ printf("Error!\n"); return; }
}
Более сложная обработка

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

class FileException
{ private:
char fName[_MAX_FNAME];
public:
FileException(char *str) { strcpy(fName, str); };
~FileException() { };
void Reason();
};

void FileException::Reason()
{ unsigned int error = GetLastError();
char erMessage [256] = "";
char str[512];

if (error)
{ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
erMessage, 256, NULL);
erMessage[strlen(erMessage) - 3] = '\0';
sprintf(str, "Ошибка %d '%s' произошла во время обработки файла '%s'!",
error, erMessage, fName);
strcat(str, "Прервать выполнение программы?\n");
if (::MessageBox(NULL, str, "Ошибка", MB_YESNO | MB_ICONWARNING) == IDYES)
exit(0);
}
}

void main()
{ FILE *f;

try
{ f = fopen("in.txt", "r");
throw FileException("in.txt");
}
catch (FileException ex)
{ ex.Reason(); }
}

Файл с программой для печати


 Критерии качества программы
 Лекция 1. Алфавит и основные понятия языка С
Лекция 2. Операции и операторы языка С. Структура программы
Лекция 3. Одномерные и двумерные массивы и их обработка
Лекция 4. Использование параметров программы. Ввод/вывод. Прямой доступ к файлу
Лекция 5. Указатели. Адресная арифметика. Строки
Лекция 6. Перечислимый тип. Структуры. Объединения
Лекция 7. Динамическое распределение памяти (malloc, calloc, realloc). Списки
Лекция 8. Поразрядные операции
Лекция 9. Время жизни и область действия. Функции с переменным числом аргументов
Лекция 10. Понятие класса
Лекция 11. Специальные члены класса (конструкторы, деструкторы, преобразования)
Лекция 12. Перегрузка операций
Лекция 13. Статические члены класса. Друзья класса
Лекция 14. Наследование
Лекция 15. Виртуальные функции. Абстрактные классы
Лекция 16. Шаблоны. Обработка исключительных ситуаций
Список вопросов
Экзаменационные задачи

Примеры программ

Пример из лекции 11. Разработка класса для работы со стеком (первый вариант)
Пример из лекции 11. Разработка класса для работы со стеком (второй вариант)
Пример из лекции 12. Перегрузка операций
Пример из лекции 13. Статические члены класса
Пример из лекции 14. Вектор с ограниченными значениями
Пример из лекции 14. Использование виртуального базового класса
Пример из лекции 15. Абстрактные классы
Пример из лекции 16. Использование шаблонов
Пример из лекции 16. Обработка исключительных ситуаций


-
-
--
-=
->
->
->
!
!=
#
##
#define
#elif
#else
#endif
#if
#include
#undef
%
%=
& (адресация)
& (адресация)
& (поразрядное И)
& (поразрядное И)
&&
&=
( )
* (косвенная адресация)
* (косвенная адресация)
* (умножение)
*=
,
.
.
.
/
/=
::
;
? :
[ ]
[ ]
^
^
^=
{ }
|
|
||
|=
~
~
+
++
+=
<
<<
<<
<<=
<=
=
==
>
>=
>>
>>
>>=

A

auto

B

break

C

case
catch
char
class
continue

D

default
defined
do
double

E

else
enum
extern

F

float
for
friend

I

if
int

L

long

P

private
private
protected
public
public

R

register
return

S

signed
signed
short
static
static
struct
switch

T

this
template
throw
try
typename

U

union
unsigned
unsigned

V

void
virtual
virtual

W

while
while

А

Адрес
- арифметика
Алфавит

В

Ввод/вывод
- из файла
-- двоичный режим
-- текстовый режим
- с экрана

Д

Деструктор
Доступ
- к защищённым членам класса
- объявления
- спецификаторы
-- для базовых классов
-- для членов класса

И

Исключительные ситуации
 К

Классы
- абстрактные
- базовые
-- виртуальные
-- косвенные
-- непосредственные
-- спецификаторы доступа
- друзья
- объект
- объявление
- объявление доступа
- производные
- функции-члены
-- встраиваемые
-- дружественные
-- специальные
-- статические
-- указатель this
- члены
-- доступ
-- доступ
-- защищённые
-- приватные
-- публичные
-- статические
Комментарии
Константы
- вещественные
- целые
-- восьмеричные
-- десятичные
-- шестнадцатеричные
- символьные
- строковые
Конструктор
- копирования
- преобразования посредством
- умолчания

Л

Логические значения

М

Массив
- ввод/вывод
- доступ к элементу
- индексация
- инициализация
- многомерный
- обработка
- объявление

Н

Наследование
- множественное

О

Объединение
Объявление
- переменной
- переменной
- функции
Операторы
- возврата
- переключатель
- пошагового цикла
- продолжения
- пустой
- разрыва
- составной
- условный
- цикла с постусловием
- цикла с предусловием
Операции
- sizeof
- аддитивные
- логическое И
- логическое ИЛИ
- мультипликативные
- мультипликативные
- отношение
- первичные
- первичные
- поразрядное И
- поразрядное И
- поразрядное ИЛИ
- поразрядное ИЛИ
- поразрядное исключающее ИЛИ
- поразрядное исключающее ИЛИ
- последовательного вычисления
- последовательного вычисления
- приведение типа
- присваивания
-- простое
-- простое
-- составное
-- составное
- разрешения области действия
- сдвиг
- сдвиг
- унарные
- унарные
- условная операция
- условная операция
Определение
- переменной
- функции
- функции

П

Параметры программы
Перегрузка операций
Переменная
- время жизни
- инициализация
- область действия
- объявление
- объявление
- определение
Перечислимый тип
Преобразования объектов класса
- посредством конструктора
- преобразующие функции
Преобразования по умолчанию
Препроцессор
Порядок вычисления операндов

Р

Распределение памяти
- динамическое
-- операторы new и delete

С

Совместное использование
Спецификаторы доступа
- для базовых классов
- для членов класса
Спецификация класса памяти
Список
Строка
Ссылка
Структура
- выбор элемента
- инициализация
- объявление

Т

Типы данных
- char
- double
- float
- int
- long double
- long int
- short int
- unsigned char
- unsigned int
- unsigned long int
- unsigned short int
- void
- перечислимый

У

Указатель
- арифметика

Ф

Функция
- main
- виртуальная
-- чистая
- время жизни
- дружественная
- область действия
- объявление
- определение
- определение
- параметры по умолчанию
- прототип
- преобразующая
- с переменным числом аргументов
- совместно используемая

Ш

Шаблоны
- класса
-- друзей класса
-- функций-членов класса
- функций


Постфиксная запись

Постфиксная запись представляет собой такую запись арифметического выражения, в которой сначала записываются операнды, а затем – знак операции. Например, для выражения a + b * c постфиксная запись будет a b c * +. Здесь операндами операции * будут b и c (два ближайших операнда), а операндами операции + будут а и составной операнд b c *. Эта запись удобна тем, что она не требует скобок. Например, для выражения (a + b) * c постфиксная запись будет a b + c *. В этой записи не требуется ставить скобки для того, чтобы изменить порядок вычисления, зависящий от приоритета операций, как в исходном выражении.
Алгоритм перевода в постфиксную запись обрабатывает исходный массив лексем и строит новый массив из тех же лексем, расположенных в другом порядке. Кроме того, необходим еще стек – аналогичный массив, используемый для временного хранения операций.
Алгоритм перевода выражения в постфиксную запись следующий.
  1. Константы и переменные кладутся в формируемую запись в порядке их появления в исходном массиве.
  2. При появлении операции в исходном массиве:
    1. если в стеке нет операций, операции кладётся в стек;
    2. если новая операции имеет больший приоритет* , чем верхняя операции в стеке, то новая операции кладётся в стек;
    3. если новая операция имеет меньший или равный приоритет, чем верхняя операции в стеке, то все операции, находящиеся в стеке, перекладываются в формируемую запись, а новая операции кладётся в стек.
  3. Открывающая скобка кладётся в стек.
  4. Закрывающая скобка выталкивает из стека в формируемую запись все операции до ближайшей открывающей скобки, открывающая скобка удаляется из стека.
  5. После того, как мы добрались до конца исходного выражения, операции, оставшиеся в стеке, перекладываются в формируемое выражение.
Примеры

  1. a + b * c
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
a | a |  | 1
+ | a | + | 2a
b | a b | + | 1
* | a b | + * | 2b
c | a b c | + * | 1
 | a b c * + |  | 5

Обратите внимание, что после перекладывания операции из стека в результат при завершении работы алгоритма, порядок операций сменился на противоположный.

  2. a + b - c * d
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
a | a |  | 1
+ | a | + | 2a
b | a b | + | 1
- | a b + | - | 2c
c | a b + c | - | 1
* | a b + c | - * | 2b
d | a b + c d | - * | 1
 | a b + c d * - |  | 5

  3. a + b * c - d
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
a | a |  | 1
+ | a | + | 2a
b | a b | + | 1
* | a b | + * | 2b
c | a b c | + * | 1
- | a b c * + | - | 2c
d | a b c * + d | - | 1
 | a b c * + d - |  | 5

  4. (a + b) * c
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
( |  | ( | 3
a | a | ( | 1
+ | a | ( + | 2a
b | a b | ( + | 1
) | a b + |  | 4
* | a b + | * | 2a
c | a b + c | * | 1
 | a b + c * |  | 5

  5. (a + b * c) / 2
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
( |  | ( | 3
a | a | ( | 1
+ | a | ( + | 2a
b | a b | ( + | 1
* | a b | ( + * | 2b
c | a b c | ( + * | 1
) | a b c * + |  | 4
/ | a b c * + | / | 2a
2 | a b c * + 2 | / | 1
 | a b c * + 2 / |  | 5

  6. (a * ( b + c) + d) / 2
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
( |  | ( | 3
a | a | ( | 1
* | a | ( * | 2a
( | a | ( * ( | 3
b | a b | ( * ( | 1
+ | a b | ( * ( + | 2a
c | a b c | ( * ( + | 1
) | a b c + | ( * | 4
+ | a b c + * | ( + | 2c
d | a b c + * d | ( + | 1
) | a b c + * d + |  | 4
/ | a b c + * d + | / | 2a
2 | a b c + * d + 2 | / | 1
 | a b c + * d + 2 / |  | 5

  7. a && b == c
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
a | a |  | 1
&& | a | && | 2a
b | a b | && | 1
== | a b | && == | 2b
c | a b c | && == | 1
 | a b c == && |  | 5

  8. a == b && c || a != d
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
a | a |  | 1
== | a | == | 2a
b | a b | == | 1
&& | a b == | && | 2c
c | a b == c | && | 1
|| | a b == c && | || | 2c
a | a b == c && a | || | 1
!= | a b == c && a | || != | 2b
d | a b == c && a d | || != | 1
 | a b == c && a d != || |  | 5

  9. (a || b) && !c
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
( |  | ( | 3
a | a | ( | 1
|| | a | ( || | 2a
b | a b | ( || | 1
) | a b || |  | 4
&& | a b || | && | 2a
! | a b || | && ! | 2b
c | a b || c | && ! | 1
 | a b || c ! && |  | 5

Расширить этот алгоритм так, чтобы он обрабатывал выражения, содержащие вызовы функций несложно. Для удобства перевода выражения в постфиксную запись в синтаксическом анализаторе необходимо в список лексем сначала класть открывающую скобку, а затем имя функции. В алгоритме перевода в постфиксную запись появляется новый пункт:
  6. Имя функции кладётся в стек и выталкивается оттуда только закрывающей скобкой.

  10. x + (sin x) * 2
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
x | x |  | 1
+ | x | + | 2a
( | x | + ( | 3
sin | x | + ( sin | 6
x | x x | + ( sin | 1
) | x x sin | + | 4
* | x x sin | + * | 2b
2 | x x sin 2 | + * | 1
 | x x sin 2 * + |  | 5

  11. a + (sin (b + c))
Обрабатываемая лексема | Результат | Стек | Пункт алгоритма
a | a |  | 1
+ | a | + | 2a
( | a | + ( | 3
sin | a | + ( sin | 6
( | a | + ( sin ( | 3
b | a b | + ( sin ( | 1
+ | a b | + ( sin ( + | 2a
c | a b c | + ( sin ( + | 1
) | a b c + | + ( sin | 4
) | a b c + sin | + | 4
 | a b c + sin + |  | 5

Вычислить значение выражения, записанного в постфиксной записи, очень просто (не забудьте только сначала ввести значения переменных, использованных в выражении). Просматриваем постфиксную запись. Значения переменных и констант кладутся в стек. Когда встречается операция, из стека берутся два верхних значения, вычисляется результат применения операции к этим значениям, и результат помещается в стек. Если встречается функция, то берётся одно значение из стека, а результат помещается в стек на его место.

Вычислим значение выражения a b c + * d + 2 / при а = 1, b = 2, c = 3, d = 4

Обрабатываемая лексема | Стек
a | 1
b | 1 2
c | 1 2 3
+ | 1 5
* | 5
d | 5 4
+ | 9
2 | 9 2
/ | 4.5

Вычислим значение выражения x x sin 2 * + при x = 3.14

Обрабатываемая лексема | Стек
x | 3.14
x | 3.14 3.14
sin | 3.14 0
2 | 3.14 0 2
* | 3.14 0
+ | 3.14

Вычислим значение выражения a b || c ! && при a = 0, b = 2, c = 0

Обрабатываемая лексема | Стек
a | 0
b | 0 2
|| | 1
c | 1 0
! | 1 1
&& | 1

Файл в формате Word

Содержание

* Операции * и / имеют равный приоритет, который больше приоритета операций + и -. Операции + и - также имеют равный приоритет. Операции сравнения имеют больший приоритет, чем логические операции. Операция «НЕ» имеет больший приоритет, чем операция «И» , а «И» имеет больший приоритет, чем операция «ИЛИ».


Использование Visual C++. Часть 1

1. Создание проекта с помощью MFC AppWizard

  1. Выберите File – New, на вкладке Projects выберите MFC AppWizard (exe), наберите имя проекта FirstProgram и нажмите ОК.
  2. На шаге 1 выберите Multiple Documents. Шаги 2 и 3 можно пропустить, ничего не меняя.
  3. Параметры, задаваемые на шаге 4, позволяют менять вид будущего приложения. Можно пока убрать Printing and print preview. Если вы нажмете кнопку Advanced…, вы также сможете задать ряд других свойств приложения, а именно: заголовок приложения, расширение для открываемых файлов, размер окна приложения, наличие системного меню, кнопок изменения размера окна и т.д.
  4. Шаг 5 можно пропустить, ничего не меняя. На шаге 6 в качестве Base Class задайте CFormView. В этом случае вид документа будет основан на диалоге.

2. Создание формы для документа

  5. В появившемся окне нарисуйте следующий диалог (пункт меню Layout позволяет выравнивать элементы диалога, делать их одинаковыми по размеру, центрировать и т.д.):

Используемые элементы: Static Text, Edit Box, Spin, Combo Box. Идентификаторы элементов диалога: IDC_STATIC, IDC_EDIT_DAY, IDC_EDIT_MONTH, IDC_EDIT_YEAR, IDC_SPIN_DAY, IDC_SPIN_MONTH, IDC_SPIN_YEAR, IDC_COMBO_EVENTS, IDC_EDIT_COMMENTS. Для элементов Spin нужно установить параметры Auto buddy, Set buddy integer (эти параметры позволяют автоматически связать элемент Spin и соседним с ним Edit Box) и Alignment = Right (вкладка Styles). Для элемента Combo Box нужно добавить данные: Встреча, Звонок, День рождения, Другое (для разделения элементов используются клавиши Ctrl-Enter). Для элемента IDC_EDIT_COMMENTS установите свойство Disabled.
  6. Необходимо сменить язык диалога на русский. Для этого в окне ResourseView найдите диалог с идентификатором IDD_FIRSTPROGRAM_FORM, нажатием правой кнопки мыши вызовите контекстное меню, выберите Properties и измените язык.
  7. Теперь можно откомпилировать и запустить программу (для этого можно использовать кнопку с красным восклицательным знаком). У вас готова оболочка и внешнее представление приложения, однако, она ещё не делает ничего существенного.

3. Работа с элементами диалога и данными, отображаемыми в диалоге

Для работы с элементами диалога и отображаемыми в них данными элементам диалога надо сопоставлять переменные, которые могут быть двух категорий: Value и Control. Переменные катего- рии Value используются для работы с данными, а переменные категории Control – для работы с самими элементами. Иногда одному элементу могут быть сопоставлены переменные категории Value разных типов, например, для Edit Box такая переменная может быть строковой или целочисленной.
  8. Выберите View – ClassWizard. Не создавайте новый класс! За отображение данных документа отвечает класс CFirstProgramView (который уже существует), поэтому все переменные для работы с элементами диалога должны объявляться в этом классе.
  9. Перейдите на вкладку Member Variables и создайте следующие переменные:
Идентификатор элемента | Тип переменной | Переменная
IDC_COMBO_EVENTS | int | m_Event
IDC_EDIT_COMMENTS | CString | m_Comments
IDC_EDIT_COMMENTS | CEdit | m_EditComments
IDC_EDIT_DAY | int | m_Day
IDC_EDIT_MONTH | int | m_Month
IDC_EDIT_YEAR | int | m_Year
IDC_SPIN_DAY | CSpinButtonCtrl | m_SpinDay
IDC_SPIN_MONTH | CSpinButtonCtrl | m_SpinMonth
IDC_SPIN_YEAR | CSpinButtonCtrl | m_SpinYear

Теперь нам надо инициализировать отображаемые данные. Как было указано ранее, за отображение вашего документа отвечает класс CFirstProgramView, в котором, в частности, существует функция OnInitialUpdate, которой и надо воспользоваться в данном случае.
  10. Найдите эту функцию через окно ClassView. Добавьте описания необходимых переменных и после существующих в ней операторов необходимые действия (жирным шрифтом выделено то, что добавляется):
void CFirstProgramView::OnInitialUpdate()
{ CTime time = CTime::GetCurrentTime();
int year = time.GetYear();

CFormView::OnInitialUpdate();
ResizeParentToFit();

m_SpinDay.SetRange(1, 31); // Установка верхней и нижней границы
m_SpinMonth.SetRange(1, 12); // изменения данных для элемента Spin
m_SpinYear.SetRange(year, year + 5);
m_Day = time.GetDay();
m_Month = time.GetMonth();
m_Year = year;
UpdateData(0); // Модификация диалога
}
Теперь сделаем так, чтобы при выборе одного из вариантов с помощью элемента IDC_COMBO_EVENTS, элемент IDC_EDIT_COMMENTS становился бы доступен, и можно было бы ввести туда текст. Для этого надо написать функцию, которая будет вызываться, когда произойдет событие «изменение выбранной строки» для элемента IDC_COMBO_EVENTS.
  11. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CFirstProgramView, в поле Object IDs выберите IDC_COMBO_EVENTS, в поле Messages – CBN_SELCHANGE. Нажмите кнопки Add Function для добавления функции и Edit Code для перехода к новой функции. Добавьте в неё следующие операторы:
UpdateData(1); // Не забывайте модифицировать данные!
if (m_Event != -1) // Номер строки, выбранной в Combo Box - начинается // с 0, -1 означает, что ничего не выбрано
m_EditComments.EnableWindow(1); // Делаем элемент доступным
else
m_EditComments.EnableWindow(0);

4. Открытие и сохранение

За открытие и сохранение, а также за создание нового документа, отвечает класс CFirstProgramDoc. В этот класс необходимо добавить переменные, в которых будут храниться данные. В нашем случае они продублируют переменные, которые мы создали в классе CFirstProgramView, однако в более сложных случаях связь между данными и их отображением может быть не такая однозначная.
В классе CFirstProgramDoc существуют функции OnNewDocument, OnOpenDocument, OnSaveDocument, которые вызываются при соответствующих событиях, однако, обратите внимание, что в эти функции нужно добавлять только вспомогательные действия, производимые при вводе и выводе, например, установку начальных значений при создании нового документа, проверку правильности данных при выводе, проверку правильности ввода/вывода. Сам ввод/вывод осуществляется в функции Serialize того же класса CFirstProgramDoc! При этом вызов диалогов для выбора файла, а также открытие и закрытие файлов берёт на себя та часть приложения, которая генерируется автоматически, и вам не нужно заботиться об этом.
  12. Добавьте в класс CFirstProgramDoc переменные day, month, year, event и comments соответствующих типов. Для этого нужно нажатием правой кнопкой мыши вызвать контекстное меню для этого класса и выбрать Add Member Variable… Переменные должны быть public.
  13. Функция OnNewDocument должна инициализировать эти переменные при создании нового документа:
BOOL CFirstProgramDoc::OnNewDocument()
{ CTime time = CTime::GetCurrentTime();

if (!CDocument::OnNewDocument())
return FALSE;

// TODO: add reinitialization code here
// (SDI documents will reuse this document)

day = time.GetDay();
month = time.GetMonth();
year = time.GetYear();
event = -1;
comments = "";
return TRUE;
}
  14. Однако нам теперь надо изменить функцию OnInitialUpdate так, чтобы она ссылалась на эти переменные:
void CFirstProgramView::OnInitialUpdate()
{ int year = GetDocument()->year; // Функция GetDocument позволяет сослаться
// на документ, к которому присоединён
// данный объект класса CFirstProgramView
CFormView::OnInitialUpdate();
ResizeParentToFit();

m_SpinDay.SetRange(1, 31);
m_SpinMonth.SetRange(1, 12);
m_SpinYear.SetRange(year, year + 5);
m_Day = GetDocument()->day;
m_Month = GetDocument()->month;
m_Year = year;
m_Event = GetDocument()->event;
m_Comments = GetDocument()->comments;
UpdateData(0); // Модификация диалога
if (m_Event != -1)
m_EditComments.EnableWindow(1);
}
  15. Напишем ввод и вывод. Как было сказано ранее, это делается в функции Serialize:
void CFirstProgramDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring()) // Вывод
{ ar.Write(&day, sizeof(int));
ar.Write(&month, sizeof(int));
ar.Write(&year, sizeof(int));
ar.Write(&event, sizeof(int));
ar.Write(comments, 256);
}
else // Ввод
{ ok = 1; // В класс CFirstProgramDoc надо также добавить
// переменную ok типа int, которая будет
// использоваться для хранения признака ошибки
if (ar.Read(&day, sizeof(int)) == 0) ok = 0;
if (ar.Read(&month, sizeof(int)) == 0) ok = 0;
if (ar.Read(&year, sizeof(int)) == 0) ok = 0;
if (ar.Read(&event, sizeof(int)) == 0) ok = 0;
ar.Read(comments.GetBuffer(255), 256);
}
}

 Однако если теперь попробовать сохранить файл, а потом открыть его, мы не увидим тех же данных. Это связано с тем, что после изменения данных пользователем надо модифицировать соответствующие переменные класса CFirstProgramDoc. Мы сделаем это в функции OnSaveDocument.
  16. Сначала надо добавить эту функцию. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CFirstProgramDoc, в поле Object IDs – тоже CFirstProgramDoc, в поле Messages – OnSaveDocument. Добавьте функцию и отредактируйте её:
BOOL CFirstProgramDoc::OnSaveDocument(LPCTSTR lpszPathName)
{ POSITION pos;
CFirstProgramView *view;

pos = GetFirstViewPosition(); // Эти операторы используются для
view = (CFirstProgramView *)GetNextView(pos); // доступа к объекту класса
// CFirstProgramView данного документа
view->UpdateData(1);
day = view->m_Day;
month = view->m_Month;
year = view->m_Year;
event = view->m_Event;
comments = view->m_Comments;
if (event == -1)
{ MessageBox(theApp.m_pMainWnd->m_hWnd, "Выберите событие",
"Внимание", MB_OK | MB_ICONWARNING);
return FALSE;
}
if (comments == "")
if (MessageBox(theApp.m_pMainWnd->m_hWnd,
"Вы уверены, что не хотите добавить комментарии?",
"Внимание", MB_YESNO | MB_ICONQUESTION) == IDNO)
return FALSE;

return CDocument::OnSaveDocument(lpszPathName);
}
  17. Однако прежде чем компилировать программу надо в начало файла добавить следующие строки:
#include "FirstProgramView.h" // Необходимо включить данный заголовочный файл для того,
// чтобы сослаться на класс CFirstProgramView
extern CFirstProgramApp theApp; // Объявление переменной theApp, которая нужна для того, // чтобы передать функции MessageBox первый
// параметр – указатель на окно приложения
  18. Аналогично добавьте функцию OnOpenDocument и добавьте в неё код, который выводит сообщение в случае ошибок при вводе:
BOOL CFirstProgramDoc::OnOpenDocument(LPCTSTR lpszPathName)
{ CString str;

if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;

// TODO: Add your specialized creation code here
if (!ok)
{ str = "Произошли ошибки при чтении файла '";
str += lpszPathName; str += "'";
MessageBox(theApp.m_pMainWnd->m_hWnd, str, "Ошибка", MB_OK | MB_ICONSTOP);
return FALSE;
}

return TRUE;
}
5. Работа с меню

Если вы откроете окно ресурсов, то увидите, что ресурсы вашей программы содержат два меню: IDR_MAINFRAME и IDR_FIRSTPTYPE. Первое меню отображается только тогда, когда нет ни одного открытого документа. В остальных случаях отображается второе меню.
  19. Необходимо сменить язык меню на русский.
  20. Добавьте в меню IDR_FIRSTPTYPE пункт «Параметры». По умолчанию он отмечен как Pop-up (т.е. меню, содержащее подменю) и не имеет идентификатора. Если вы хотите, то можете перетащить этот пункт в любое место меню.
  21. Добавьте в этот пункт подпункт «Дополнительные параметры». Задайте ему идентификатор ID_PARAM. В окне Prompt задается строка подсказки, которая может состоять из двух частей, разделенных символом перевода строки \n. Первая часть содержит текст, который отображается в строке состояния (строка внизу вашего приложения), а вторая часть (обычно не очень длинная) отображается в виде всплывающей подсказки.
Если вы теперь запустите вашу программу, то увидите, что пункт меню «Дополнительные параметры» отображается, но он не доступен. Это происходит потому, что мы не написали функцию, которая будет выполняться при выборе данного пункта меню.
    22. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CChildFrame, в поле Object IDs – ID_PARAM, в поле Messages – COMMAND. Добавьте соответствующую функцию. Даже если она будет пустая, пункт меню «Дополнительные параметры» станет доступным.

6. Создание диалога

  23. Откройте окно ресурсов. Для добавления диалога выберите Insert Dialog из меню элемента Dialog. Нарисуйте следующий диалог:

Используемые элементы: Group Box, Radio Button, Check Box. Идентификаторы элементов диалога: IDC_STATIC, IDC_RADIO_THE_DAY, IDC_RADIO_DAY_BEFORE, IDC_RADIO_ 2DAYS_BEFORE, IDC_RADIO_WEEK_BEFORE, IDC_CHECK_SHOW, IDC_CHECK_DEL. Для кнопок используются идентификаторы, задаваемые по умолчанию. Для первой радио-кнопки необходимо установить параметр Group. Для того чтобы редактировать параметры всего диалога надо вызвать контекстное меню нажатием правой кнопки мыши на диалоге, но не на каком-либо элементе. Для диалога будем использовать идентификатор IDD_DIALOG_PARAM. Также необходимо сменить язык диалога на русский.
  24. Войдите в ClassWizard. Создайте новый класс для вашего диалога: CDialogParam.
  25. Перейдите на вкладку Member Variables и создайте следующие переменные:
Идентификатор элемента | Тип переменной | Переменная
IDC_CHECK_DEL | BOOL | m_Del
IDC_CHECK_SHOW | BOOL | m_Show
IDC_RADIO_THE_DAY | int | m_Before

Для каждой группы радио-кнопок создаётся только одна переменная, которая имеет значение -1, если ни одна радио-кнопка не выбрана, значение 0 – если выбрана первая радио-кнопка, значение 1 – если выбрана вторая радио-кнопка и т.д.
  26. Создайте в классе CFirstProgramDoc соответствующие переменные before, show и del. Добавьте в функцию OnNewDocument операторы для инициализации этих переменных (например, значениями 0, 1 и 1), а в функцию Serialize – операторы для ввода/вывода этих переменных.
Теперь можно попробовать вызвать новый диалог. Для вызова диалога необходимо, в первую очередь, объявить переменную соответствующего класса. Затем инициализируются внутренние переменные класса. Для вызова диалога используется функция DoModal. После этого, если пользователь нажал ОК, необходимо переменным before, show и del задать новые значения.
  27. В функцию, которая вызывается для пункта меню «Дополнительные параметры» добавьте действия для работы с диалогом:
void CChildFrame::OnParam()
{ CDialogParam dp; // Объявление переменной для диалога
CFirstProgramDoc *doc;
CFirstProgramView *view;

// TODO: Add your command handler code here
view = (CFirstProgramView *)GetActiveView(); // Получаем указатель на документ
doc = view->GetDocument();
dp.m_Before = doc->before; // Инициализируем переменные диалога
dp.m_Show = doc->show;
dp.m_Del = doc->del;
if (dp.DoModal() == IDOK) // Вызов диалога и проверка ответа
{ doc->before = dp.m_Before; // Меняем переменные документа
doc->show = dp.m_Show;
doc->del = dp.m_Del;
}
}
  28. Для корректной работы программы необходимо в текущий файл добавить следующие директивы препроцессора:
#include "FirstProgramView.h"
#include "FirstProgramDoc.h"
#include "DialogParam.h"

 Однако в данный момент программа работать ещё не будет, т.к. мы не установили связь между переменными m_Before, m_Show и m_Del и их отображением в диалоге. Для этого нужно добавить функции, которые будут производить начальную инициализацию диалога и сохранение переменных при нажатии пользователем кнопки ОК.
  29. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CDialogParam, в поле Object IDs – тоже CDialogParam, в поле Messages – WM_INITDIALOG. Добавьте функцию для инициализации диалога. В нее необходимо добавить один оператор: UpdateData(0);
  30. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CDialogParam, в поле Object IDs – IDOK, в поле Messages – BN_CLICKED. Добавьте функцию, вызываемую при нажатии ОК. В нее необходимо добавить один оператор: UpdateData(1);
Можно сделать так, чтобы элемент IDC_CHECK_SHOW («Показывать каждый день») был доступен, только когда выбран показ сообщения раньше назначенного дня.
  31. Откройте ClassWizard, перейдите на вкладку Member Variables и для элемента IDC_CHECK_SHOW создайте вторую переменную с именем m_CheckShow категории Control типа CButton. Эта переменная будет использоваться для управления состоянием элемента.
  32. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CDialogParam, в поле Object IDs – IDC_RADIO_THE_DAY, в поле Messages – BN_CLICKED. Добавьте функцию, вызываемую при нажатии первой из радио-кнопок. В нее необходимо добавить один оператор m_CheckShow.EnableWindow(0);, который сделает соответствующий элемент недоступным.
  33. Аналогично создайте функции, вызываемые при нажатии остальных радио-кнопок. В них надо добавить тот же оператор, только параметр функции должен быть 1 для того, чтобы сделать элемент доступным.

7. Работа с панелью инструментов

  34. Для добавления панель в строку инструментов откройте через окно ресурсов панель инструментов, имеющую идентификатор IDR_MAINFRAME. На пустой кнопке нарисуйте подходящую иконку.
  35. Дважды щелкните на кнопке и в параметрах в качестве идентификатора выберите идентификатор того пункта меню, с которым вы хотите связать кнопку, – ID_PARAM.

8. Запрос на сохранение

Запрос на сохранение закрываемого файла выдаётся приложением автоматически в том случае, если известно, что документ был изменен.
  36. Добавьте в функцию CFirstProgramView::OnSelchangeComboEvents() оператор
    GetDocument()->SetModifiedFlag(1); и проверьте результат.
Для того, что бы запрос выдавался при любом изменении данных, Вам придется добавлять соответствующие функции, которые вызываются при изменении данных в том или ином элементе, и добавлять в функции этот оператор. Элемент Edit Box при изменении посылает сообщение EN_CHANGE, следовательно, необходимо написать функции, которые вызывались бы при получении такого сообщения от каждого элемента Edit Box.
  37. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CFirstProgramView, в поле Object IDs – IDC_EDIT_COMMENTS, в поле Messages – EN_CHANGE. Добавьте соответствующую функцию и отредактируйте её, добавив оператор
    GetDocument()->SetModifiedFlag(1);.
  38. Аналогично создайте и отредактируйте функции для других элементов Edit Box.
  39. Добавьте этот оператор в функцию CChildFrame::OnParam(), чтобы установить, что документ был изменён, если пользователь изменил дополнительные параметры (если, конечно, он нажал кнопку ОК!).

9. Создание всплывающего меню

Всплывающее меню, которое вызывается нажатием правой кнопки мыши на нужном элементе, – очень удобная для пользователя возможность, поэтому в хорошей программе обязательно должны быть такие меню. Создать его не сложно – объявляется соответствующая переменная, добавляются нужные пункты меню и пишутся действия для каждого пункта.
  40. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CFirstProgramView, в поле Object Ds – тоже CFirstProgramView, в поле Messages – WM_RBUTTONUP. Добавьте соответствующую функцию.
  41. В эту функцию добавьте действия по созданию меню и работе с ним:
void CFirstProgramView::OnRButtonUp(UINT nFlags, CPoint point)
{ CMenu menu; // Переменная для меню

// TODO: Add your message handler code here and/or call default
ClientToScreen(&point); // Преобразуем координаты для отображения меню
// в нужном месте
menu.CreatePopupMenu(); // Создание меню и добавление пунктов меню
menu.AppendMenu(MF_STRING, ID_PARAM, "Дополнительные параметры");
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING, ID_CLEAR, "Очистить");
menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, // Функция отображения меню
point.x, point.y, this, NULL);
menu.DestroyMenu(); // Уничтожение меню

CFormView::OnRButtonUp(nFlags, point);
}
  42. В файл Resource.h необходимо добавить строку #define ID_CLEAR 32772 для определения идентификатора ID_CLEAR (предыдущие идентификаторы создавались автоматически).
Теперь меню отображается при нажатии правой кнопки мыши, но ещё ничего не делает. Обработка команд, получаемых от меню, происходит в функции OnCommand.
  43. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CFirstProgramView, в поле Object IDs – тоже CFirstProgramView, в поле Messages – OnCommand. Добавьте соответствующую функцию и отредактируйте её код:
BOOL CFirstProgramView::OnCommand(WPARAM wParam, LPARAM lParam)
{ CDialogParam dp;
CFirstProgramDoc *doc;
CTime time = CTime::GetCurrentTime();

// TODO: Add your specialized code here and/or call the base class
switch (LOWORD(wParam)) // Проверка, какой пункт меню был выбран
{ case ID_PARAM:
doc = GetDocument();
dp.m_Before = doc->before;
dp.m_Show = doc->show;
dp.m_Del = doc->del;
if (dp.DoModal() == IDOK)
{ doc->before = dp.m_Before;
doc->show = dp.m_Show;
doc->del = dp.m_Del;
doc->SetModifiedFlag(1);
}
break;
case ID_CLEAR:
m_Day = time.GetDay();
m_Month = time.GetMonth();
m_Year = time.GetYear();
m_Event = -1;
m_Comments = "";
UpdateData(0);
m_EditComments.EnableWindow(0);
GetDocument()->SetModifiedFlag(1);
break;
}

return CFormView::OnCommand(wParam, lParam);
}

10. Включение и отключение пунктов меню

Если закрыть все окна, кнопка панели инструментов «Дополнительные параметры» всё равно остаётся доступной, и при нажатии на неё возникают ошибки. Чтобы включать и отключать пункты меню при определённых условиях (а кнопки панели инструментов, по сути, являются более удобной формой для вызова пунктов меню и работают синхронно с соответствующими пунктами меню), необходимо обрабатывать сообщение UPDATE_COMMAND_UI.
  44. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CChildFrame, в поле Object IDs – ID_PARAM, в поле Messages – UPDATE_COMMAND_UI. Добавьте соответствующую функцию и отредактируйте её код:
void CChildFrame::OnUpdateParam(CCmdUI* pCmdUI)
{ CWnd *wnd;

// TODO: Add your command update UI handler code here
wnd = GetFocus();
if (wnd) // Если есть хоть какое-то окно
pCmdUI->Enable(1); // включаем пункт меню (и кнопку)
else // Если нет окон
pCmdUI->Enable(0); // отключаем пункт меню (и кнопку)
}

11. Операции «Отменить», «Вырезать», «Копировать», «Вставить»

Данные пункты меню автоматически вставляются в генерируемое по умолчанию меню. Однако для того чтобы они работали, надо написать соответствующие функции. Функции Undo(), Cut(), Copy() и Paste() являются членами класса CEdit, который отвечает за работы элемента Edit Box, поэтому написание функций для соответствующих пунктов меню не представляет особого труда.
  45. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CMainFrame, в поле Object IDs – ID_EDIT_UNDO, в поле Messages – COMMAND. Добавьте соответствующую функцию и отредактируйте её код:
void CMainFrame::OnEditUndo()
{ CWnd *wnd;
char className[6];

// TODO: Add your command handler code here
wnd = GetFocus(); // Получаем указатель на текущее окно
if (wnd && wnd->m_hWnd &&
::GetClassName(wnd->m_hWnd, className, 6) && // Проверяем, что это
stricmp(className, "Edit") == 0) // элемент Edit Box
((CEdit *)wnd)->Undo(); // Отменяем последнее действие
}
  46. Аналогично напишите функции для остальных действий.
Теперь надо написать функции, которые отключают и соответствующие пункты меню.
  47. Для каждого пункта меню «Отменить», «Вырезать», «Копировать», «Вставить» добавьте функцию для обработки сообщения UPDATE_COMMAND_UI (см. пункт 44).
  48. Отредактируйте функцию для пункта меню «Отменить»:
void CMainFrame::OnUpdateEditUndo(CCmdUI* pCmdUI)
{ CWnd *wnd;
char className[6];

// TODO: Add your command update UI handler code here
wnd = GetFocus();
if (!wnd || !wnd->m_hWnd)
{ pCmdUI->Enable(0);
return;
}
if (::GetClassName(wnd->m_hWnd, className, 6) &&
stricmp(className, "Edit") == 0 &&
((CEdit *)wnd)->CanUndo()) // Функция CanUndo проверяет, можно ли
pCmdUI->Enable(1); // отменить последнюю операцию
else
pCmdUI->Enable(0);
}
  49. Отредактируйте функцию для пунктов меню «Вырезать» и «Копировать» (они одинаковы):
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{ CWnd *wnd;
char className[6];
int start, end;

// TODO: Add your command update UI handler code here
wnd = GetFocus();
if (!wnd || !wnd->m_hWnd)
{ pCmdUI->Enable(0);
return;
}
if (::GetClassName(wnd->m_hWnd, className, 6) &&
stricmp(className, "Edit") == 0)
{ ((CEdit *)wnd)->GetSel(start, end); // Проверяем, что есть
pCmdUI->Enable(start != end); // выделенный текст
}
else
pCmdUI->Enable(0);
}
  50. Отредактируйте функцию для пункта меню «Вставить»:
void CMainFrame::OnUpdateEditPaste(CCmdUI* pCmdUI)
{ CWnd *wnd;
char className[6];

// TODO: Add your command update UI handler code here
wnd = GetFocus();
if (!wnd || !wnd->m_hWnd)
{ pCmdUI->Enable(0);
return;
}
if (::GetClassName(wnd->m_hWnd, className, 6) &&
stricmp(className, "Edit") == 0 &&
!(wnd->GetStyle() & ES_READONLY))
// Функция IsClipboardFormatAvailable проверяет,
// что в буфере находится текст,
// который можно вставить в элемент Edit Box
pCmdUI->Enable(::IsClipboardFormatAvailable(CF_TEXT));
else
pCmdUI->Enable(0);
}

Файл в формате Word


Использование Visual C++. Часть 2

  1. С помощью MFC AppWizard создайте однодокументное приложение, для отображения документа выберите класс CView (см. пункты 1-4 части 1).
  2. Создайте меню и панель инструментов с опциями и кнопками для создания соответствующей фигуры и удаления всех фигур (см. пункты 19-21 и 34-35 части 1).
  3. Создайте функции, которые будут выполняться при выборе этих пунктов меню (см. пункт 22 части 1). Для создания новой фигуры, прежде всего, необходимо вызвать диалог, в котором пользователь может задать параметры фигуры.
  4. Создайте диалог для задания местоположения и цвета фигуры (см. пункты 23-33 части 1).
Цвет фигуры нужно задавать числом, которое затем будет использоваться в функциях рисования. Чтобы не задумываться, какое число представляет какой цвет, можно использовать стандартный диалог для выбора цвета.
  5. Создайте на диалоге кнопку выбора цвета. Отредактируйте функцию, вызываемую по нажатию этой кнопки (чтобы создать эту функцию, можно дважды щелкнуть по ней мышью):
void CNewCircle::OnButtonColor()
{ CColorDialog cd; // Переменная для стандартного диалога выбора цвета

// TODO: Add your control notification handler code here
if (cd.DoModal() == IDCANCEL) return;
UpdateData(1);
m_Colour = cd.GetColor(); // Функция GetColor возвращает числовое представление
// выбранного цвета
UpdateData(0);
}

Можно сделать так, чтобы на кнопке вместо текста было некоторый рисунок.
  6. В свойствах кнопки на вкладке Styles установите Bitmap.
  7. В ресурсы добавьте объект Bitmap и нарисуйте на нём подходящее изображение.
  8. В класс для диалога добавьте переменные bitmap типа CBitmap и hBitmap типа HBITMAP.
  9. Отредактируйте функцию OnInitDialog:
BOOL CNewCircle::OnInitDialog()
{
CDialog::OnInitDialog();

// TODO: Add extra initialization here
bitmap.LoadBitmap(IDB_COLOURS); // IDB_COLOURS – идентификатор объекта Bitmap
hBitmap = HBITMAP(bitmap);
m_CButtonColour.SetBitmap(hBitmap);
UpdateData(0);

return TRUE; // return TRUE unless you set the focus toa control
// EXCEPTION: OCX Property Pages should return FALSE
}
  10. Далее вам необходимо создать класс для представления одной фигуры и список объектов этого класса.
Для того чтобы нарисовать фигуры на экране, необходимо использовать функцию класса CSecondProgramView OnDraw. Эта функция имеет один параметр – указатель на объект класса CDC. Класс CDC используется для работы с так называемым контекстом устройства. Для экрана контекст устройства представляет собой, говоря упрощённо, копию части экрана. Все функции рисования получают этот указатель и выводят изображение именно в контекст устройства, и затем при необходимости изображение копируется на экран. Функция OnDraw вызывается автоматически при восстановлении приложения, перемещении и т.д. Если вы хотите изменить изображение на экране, например, при появлении новой фигуры, изменении цвета фигуры, перемещении или удалении фигуры, вам также необходимо вызывать эту функцию. Однако она вызывается не напрямую, а с помощью функции InvalidateRect. Функция InvalidateRect имеет один параметр – указатель на объект класса CRect. Класс CRect используется для представления прямоугольника и содержит четыре целых числа, представляющие собой координаты верхнего левого и правого нижнего угла прямоугольника. Если параметр функции InvalidateRect равен NULL, перерисовывается всё окно. Если параметр этой функции не равен NULL, перерисовывается только часть окна, заданная параметром функции. Обычно не стоит вызывать функцию InvalidateRect с параметром, равным NULL, т.к. в этом случае экран будет подмигивать, особенно при многократной перерисовке, когда вы, например, перемещаете фигуру.
Для рисования различных фигур используются такие функции, как Ellipse, Polygon, Rectangle и т.д. Однако прежде чем что-то рисовать, необходимо выбрать «карандаш» и «кисть». «Карандаш» используется для рисования контура фигуры, а «кисть» – для заполнения фигуры.

CPen pen; // Переменная «карандаш»
CBrush brush; // Переменная «кисть»

brush.CreateSolidBrush(colour); // Создаём сплошную кисть. Здесь colour –
// переменная, содержащая цвет рисуемой фигуры
pen.CreatePen(PS_SOLID, 1, colour); // Создаём сплошной карандаш толщиной 1
pDC->SelectObject(&pen); // Выбираем карандаш и кисть для рисования
pDC->SelectObject(&brush); // pDC – указатель на объект класса CDC
// Рисуем круг, используя выбранные инструменты
pDC->Ellipse(x - radius, y + radius, x + radius, y - radius);
  11. Добавьте в функцию OnDraw операторы для рисования всех фигур из списка. Если вы создали класс для представления фигуры, то функция рисования одной фигуры должна принадлежать этому классу. Ей необходимо передать параметр функции OnDraw. В функции OnDraw тогда необходимо вызвать функцию рисования для каждого объекта класса.
  12. После вызова диалога создания фигуры добавьте вызов функции InvalidateRect.
  13. Добавляйте вызов функции InvalidateRectкаждый раз, когда необходимо изменить изображение на экране.
Далее необходимо запрограммировать перемещение и изменение размера фигуры с помощью мыши.
  14. Откройте ClassWizard. Перейдите на вкладку Message Maps. В поле Class Name выберите CSecondProgramView, в поле Object IDs – тоже CSecondProgramView, в поле Messages – WM_MOUSEMOVE. Добавьте функцию для обработки данного сообщения.
Эта функция получает два параметра – переменную nFlags типа UINT, которая содержит флаги, определяющие, какие клавиши на клавиатуре и на мыши были нажаты во время перемещения мыши, и переменную point типа CPoint, которая содержит координаты точки, куда была передвинута мышь. Поскольку передаются координаты только одной точки, необходимо каждый раз после обработки сообщения сохранять координаты точки, чтобы следующий раз использовать её, как точку, откуда была передвинута мышь, чтобы определить расстояние. Кроме того, при нажатии клавиши мыши также необходимо запоминать точку, чтобы затем использовать её при обработке сообщения WM_MOUSEMOVE. Таким образом, функция для обработки сообщения WM_MOUSEMOVE выглядит следующим образом:

void CSecondProgramView::OnMouseMove(UINT nFlags, CPoint point)
{ CRect rect;

// TODO: Add your message handler code here and/or call default
// Выражение nFlags & MK_LBUTTON проверяет, что во время передвижения мыши
// была нажата левая клавиша мыши. Это надо именно с помощью поразрядной
// операции &! circle – это указатель на класс Circle для представления фигуры.
// Если он не равен NULL, это означает, что левая клавиша мыши была нажата,
// когда курсор находился внутри некоторой фигуры, и определяет фигуру,
// с которой надо работать. Этот указатель устанавливается в функциях,
// которые обрабатывают сообщения WM_LBUTTONDOWN – нажатие левой клавиши мыши
// и WM_RBUTTONDOWN – нажатие правой кнопки мыши
if (nFlags & MK_LBUTTON && circle)
{ circle->Move(::point, point, &rect); // Перемещаем фигуру. ::point – глобальная
// переменная, которая содержит предыдущую
// точку. Функция также устанавливает пере-
// менную rect, которая определяет координаты
// части окна, которую надо обновить
InvalidateRect(&rect);
::point = point; // Запоминаем текущую точку
}
// Аналогично для случая, когда нажата правая кнопка мыши
else if (nFlags & MK_RBUTTON && circle)
{ circle->Resize(::point, point, &rect);
InvalidateRect(&rect);
resize = 1; // Запоминаем, что было изменение размера фигуры,
// чтобы в этом случае на появлялось всплывающее
// меню при отпускании кнопки мыши
::point = point;
}

CView::OnMouseMove(nFlags, point);
}
  15. Добавьте в класс CSecondProgramView функцию для обработки сообщения WM_MOUSEMOVE и отредактируйте её так, чтобы с её помощью перемещать вашу фигуру и изменять её размер.
  16. Добавьте в класс CSecondProgramView функции для обработки сообщений WM_LBUTTONDOWN и WM_RBUTTONDOWN – эти сообщения посылаются приложению, когда были нажаты (но не отпущены!) левая или правая кнопки мыши соответственно. Эти функции должны проверять, попадает ли точка, в которой находился курсор, когда была нажата клавиша мыши, в какую-либо фигуру, устанавливать эту фигуру как рабочую и запоминать точку, в которой была нажата клавиша мыши.
Чтобы при нажатии правой кнопки мыши появлялось всплывающее меню, необходимо обрабатывать сообщение WM_RBUTTONUP.
  17. Добавьте в класс CSecondProgramView соответствующую функцию и отредактируйте её код:
void CSecondProgramView::OnRButtonUp(UINT nFlags, CPoint point)
{ CMenu menu;

// TODO: Add your message handler code here and/or call default
if (resize) // Если было изменение размера фигуры (см. функцию
{ resize = 0; return; } // OnMouseMove), то создавать меню не надо
. . . // Проверяем, что попали в какую-либо фигуру
if (circle) // Если попали
{ . . . // Создание и обработка меню (см. пункты 40-43 части 1)
// для фигуры, которое должно содержать пункты
// для изменения цвета фигуры, перемещения, изменения
// размера и удаления фигуры
}
else // Если не попали
{ . . . // Создание и обработка общего меню, которое должно
// содержать пункты «Создание новой фигуры»
// и «Удаление всех фигур»
}

CView::OnRButtonUp(nFlags, point);
}
  18. Добавьте в класс CSecondProgramView функцию OnCommand и отредактируйте её код:
BOOL CSecondProgramView::OnCommand(WPARAM wParam, LPARAM lParam)
{ CColorDialog cd;

// TODO: Add your specialized code here and/or call the base class
switch (LOWORD(wParam))
{ case ID_CHANGE_COLOUR: // Если надо изменить цвет
if (cd.DoModal() == IDOK) // Используем стандартный диалог выбора цвета
{ circle->SetColour(cd.GetColor());
InvalidateRect(NULL);
}
break;
case ID_MOVE: // Это ещё не перемещение или изменение размера
move = 1; // фигуры, а только переход в соответствующий
break; // режим. Реально перемещение и изменение размера
case ID_RESIZE: // будут производиться при нажатии клавиш на
resize = 1; // клавиатуре в функции, которая обрабатывает
break; // соответствующее сообщение
case ID_DELETE: // Если надо удалить фигуру
if (MessageBox("Are you sure?", "Deleting a circle",
MB_YESNO | MB_ICONQUESTION) == IDYES)
{ . . . // Удаление
InvalidateRect(NULL);
}
break;
case ID_CREATE: // Если надо создать новую фигуру
. . . // Создание новой фигуры
break;
case ID_DELETE_ALL: // Если надо удалить все фигуры
if (MessageBox("Are you sure that you want to delete all circles?",
"Deleting circles", MB_YESNO | MB_ICONQUESTION) == IDYES)
{ . . . // Удаление всех фигур
InvalidateRect(NULL);
}
break;
}

return CView::OnCommand(wParam, lParam);
}

Теперь осталось только написать функцию, которая будет перемещать фигуру или изменять её размер при нажатии клавиш на клавиатуре.
  19. Прежде всего, определите константы для кодов клавиш:
#define KEYLEFT 37
#define KEYUP 38
#define KEYRIGHT 39
#define KEYDOWN 40
#define ENTER 13
Существует два сообщения, передаваемых приложению при нажатии клавиш – WM_KEYUP и WM_KEYDOWN. Функции, обрабатывающие эти сообщения, получают три параметра – переменную nChar типа UINT, которая содержит код клавиши, переменную nRepCnt типа UINT, которая определяет количество повторений, если пользователь нажимает и не отпускает клавишу, и переменную nFlags типа UINT, которая содержит флаги. Однако для функции, которая обрабатывает сообщение WM_KEYUP, переменная nRepCnt всегда равна 1. Поэтому лучше использовать функцию, обрабатывающую сообщение WM_KEYDOWN, чтобы при удерживании клавиши происходило перемещение фигуры или изменение её размера.
  20. Добавьте в класс CSecondProgramView соответствующую функцию и отредактируйте её код:
void CSecondProgramView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{ CRect rect;

// TODO: Add your message handler code here and/or call default
if (!circle) return;
if (move) // Если надо переместить (см. функцию OnCommand)
{ switch (nChar)
{ case KEYLEFT:
circle->Move(-nRepCnt, 0, &rect); // В классе Circle существуют
InvalidateRect(&rect); // две функции с именем Move,
break; // но с разным набором параметров
case KEYUP:
circle->Move( 0, -nRepCnt, &rect);
InvalidateRect(&rect);
break;
case KEYRIGHT:
circle->Move( nRepCnt, 0, &rect);
InvalidateRect(&rect);
break;
case KEYDOWN:
circle->Move( 0, nRepCnt, &rect);
InvalidateRect(&rect);
break;
case ENTER: // Выход из режима перемещения
move = 0;
break;
}
}
else if (resize) // Если надо изменить размер (см. функцию OnCommand)
{ switch (nChar)
{ case KEYLEFT: // При нажатии этих клавиш действия одинаковы
case KEYUP:
circle->Resize(-nRepCnt, &rect); // В классе Circle существуют
InvalidateRect(&rect); // также две функции с именем Resize,
break; // но с разным набором параметров
case KEYRIGHT: // При нажатии этих клавиш действия также одинаковы
case KEYDOWN:
circle->Resize( nRepCnt, &rect);
InvalidateRect(&rect);
break;
case ENTER: // Выход из режима изменения размера
resize = 0;
break;
}
}

CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
Файл в формате Word


«При помощи этих языков программирования можно сделать всё, что угодно, даже если вы того и не хотите».
 Ален И. Голуб «С и С++. Правила программирования»

C & C++