- •И. А. Андрианов, д. В. Кочкин, с. Ю. Ржеуцкая
- •Учебное пособие
- •Оглавление
- •1. Основы языка 8
- •1.2.2 Простые типы данных 13
- •2. Работа с памятью 73
- •3. Основы объектно-ориентированного программирования 87
- •4.Обработка исключений 114
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов 130
- •6. Паттерны проектирования 159
- •7. Антипаттерны 211
- •9. Методы отладки и оптимизации кода 242
- •1. Основы языка
- •1.1.2 Понятие проекта
- •1.2 Простые типы данных
- •1.2.1 Понятие типа
- •1.2.2 Простые типы данных
- •1.2.3 Внутреннее представление простых типов
- •1.2.4 Ключевое слово typedef. Тип size_t
- •1.3 Константы и переменные
- •1.3.1 Литералы
- •1. Числовые константы:
- •2. Символьные константы:
- •1.3.2 Переменные
- •1.3.3 Описание переменных
- •1.4. Выражения. Преобразование типов
- •1.4.1 Операнды и операции
- •1.4.2 Приоритет операций
- •1.4.3 Преобразование типов
- •1.5 Ветвления и циклы
- •1.5.2 Циклы
- •1.6 Массивы, строки
- •1.6.1 Основные понятия
- •1.6.2 Встроенные массивы
- •1.6.3 Cтроки. Обработка строк с завершающим нулём
- •1.7 Указатели и ссылки. Связь указателей и массивов. Библиотека cstring
- •1.7.1 Понятия указателя и ссылки
- •1.7.2 Связь между массивами и указателями
- •1.7.3 Библиотека cstring
- •1.8 Использование типов vector и string
- •1.8.1 Шаблонный класс vector
- •1.8.2 Класс string
- •1.9 Структуры и объединения. Битовые поля
- •1.10.1 Понятие функции
- •1.10.2 Описание функции и прототип функции
- •1.11 Параметры функции. Способы передачи параметров
- •1.11.1 Параметры функции и глобальные переменные
- •1.11.2 Способы передачи параметров в функцию
- •1.11.3 Передача массивов в функцию
- •1.11.4 Параметры-константы
- •1.11.5 Значения параметров по умолчанию
- •1.12.1 Указатель на функцию
- •1.12.2 Функции с переменным числом параметров
- •1.12.3 Перегрузка функций
- •1.12.4 Встроенные (inline) функции
- •1.13 Рекурсивные функции
- •1.14 Пространства имён
- •1.15 Директивы препроцессора. Макросы
- •2. Работа с памятью
- •2.1 Управление выделением и освобождением памяти
- •2.1.1 Статическое и динамическое выделение памяти
- •2.1.2 Способы динамического выделения и освобождения памяти
- •2.2 Динамические структуры данных
- •2.2.1 Основные понятия
- •2.2.2 Примеры реализации динамических структур на основе указателей
- •3. Основы объектно-ориентированного программирования
- •3.1 Основные понятия ооп
- •3.2.1 Описание класса
- •3.2.2 Область видимости элементов класса. Инкапсуляция
- •3.2.3 Первые примеры
- •3.3. Конструкторы и деструкторы.
- •3.4 Указатель this
- •3.5 Перегрузка операций
- •3.6 Дружественные функции и классы
- •3.7 Статические элементы класса
- •3.8 Наследование и полиморфизм
- •3.8.1. Основные понятия
- •3.8.2 Одиночное наследование
- •3.8.3 Множественное наследование
- •3.8.4 Конструкторы и деструкторы классов-потомков
- •3.9. Полиморфизм при наследовании классов
- •3.9.1 Механизмы раннего и позднего связывания
- •3.9.2 Абстрактные классы
- •4.Обработка исключений
- •4.1 Основные понятия
- •4.2 Перехват исключений
- •4.3 Поиск обработчика исключений. Раскрутка стека.
- •4.4 Повторное возбуждение исключений
- •4.5 "Аппаратные" и "программные" исключения
- •4.6 Стандартные классы исключений
- •4.7 Спецификация исключений, возбуждаемых функцией
- •4.8 Исключения в конструкторах при наследовании
- •4.9. Исключения в деструкторах
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов
- •5.1 Шаблонные функции
- •5.2 Шаблонные классы
- •5.3 Специализация шаблонов
- •5.4 Шаблонные параметры шаблонов
- •5.5 Разработка шаблонных классов с настраиваемой функциональностью
- •5.6 Использование шаблонов для вычислений на этапе компиляции
- •5.7 Библиотека стандартных шаблонов (stl) – основные понятия
- •5.8 Последовательные контейнеры. Итераторы
- •5.9. Адаптеры контейнеров
- •5.10 Ассоциативные контейнеры
- •5.11 Алгоритмы
- •6. Паттерны проектирования
- •6.1 Порождающие шаблоны
- •6.2 Структурные шаблоны
- •6.3 Шаблоны поведения
- •6.4 Шаблон "фабричный метод" (Factory method)
- •6.5 Шаблон "одиночка" (Singleton)
- •6.6 Шаблон "итератор" (Iterator)
- •6.7 Шаблон "наблюдатель" (Observer)
- •6.8 Шаблон "пул объектов" (Object pool)
- •6.9 Шаблон "команда" (Command)
- •6. 10 Шаблон "посетитель" (Visitor)
- •6.11 Дополнительные задания
- •6.11.1 Шаблон Iterator
- •6.11.2 Шаблон Observer
- •6.11.3 Шаблоны Command и Observer
- •6.11.5 Шаблон Visitor
- •6.11.5 Разработка класса − контейнера
- •6.11.6 Оценка производительности кода
- •7. Антипаттерны
- •7.1 Программирование методом копирования и вставки (Copy-Paste Programming)
- •7.2 Спагетти-код (Spaghetti code)
- •7.3 Магические числа (Magic numbers)
- •7.4 Бездумное комментирование
- •7.5 Жесткое кодирование (Hard code)
- •7.6 Мягкое кодирование (Soft code)
- •7.7 Золотой молоток (Golden hammer)
- •7.8 Слепая вера (Blind faith)
- •7.9 Ненужная сложность (Accidental complexity)
- •7.10 Божественный объект (God Object)
- •7.11 Лодочный якорь (Boat anchor)
- •7.12 Поток лавы (Lava flow)
- •7.13 Изобретение велосипеда (Reinventing the wheel)
- •7.14 Программирование перебором (Programming by permutation)
- •8.1 Выведение типов
- •8.2 Списки инициализации
- •8.3 Улучшение процесса инициализации объектов
- •8.4 Цикл for по коллекции
- •8.5 Лямбда-функции
- •8.6 Константа нулевого указателя nullptr
- •8.7 "Умные" указатели
- •9. Методы отладки и оптимизации кода
- •9.1 Отладка кода
- •9.1.1 Основные этапы отладки
- •9.1.2 Инструменты и приёмы отладки
- •9.2 Оптимизация кода
- •9.2.1 Рекомендации по выполнению оптимизации
- •9.2.2 Методики оптимизации кода
- •Заключение
- •Библиографический список
1.12.1 Указатель на функцию
Указатель на функцию содержит адрес памяти, по которому располагается данная функция. Имя функции является адресом начала программного кода функции (как и в случае с массивом). Указатель на функцию объявляется следующим образом:
Тип (* имя_указателя)(список_параметров_функции)
Указатель на функцию можно передать в качестве параметра в другую функцию, в некоторых случаях это помогает существенно расширить её функциональность. Не рекомендуем сильно увлекаться этой возможностью – всё-таки понятность такого кода несколько снижается.
Пример: напишем универсального функцию поиска элемента в массиве, которой в качестве параметра передаётся функция сравнения двух элементов (точнее, указатель на функцию сравнения). При таком подходе можно написать различные функции сравнения, основанные не обязательно на точном равенстве, например, сравнение абсолютных величин и т.д. В примере, кроме сравнения на равенство, имеется ещё функция сравнения на равенство последних цифр.
// Пример 1.18 - универсальный вариант поиска в массиве
#include <iostream>
using namespace std;
// функция возвращает позицию первого найденного элемента
// или -1, если такой элемент не найден в массиве
int search(int a[],int n,int s, bool(*compare)(int,int)){
for (int i=0;i<n;i++)
if(compare(a[i],s))return i+1;
return -1;
}
bool cmp1(int a, int b){ // сравнение на точное равенство
if (a==b)return true; else return false;
}
bool cmp2(int a, int b){ // сравнение на равенство последней цифры
if (a%10==b%10)return true; else return false;
}
int main() { // демонстрация работы функций
int a[]={1,4,12,2,3,4,11};
int l = search(a,7,2,cmp1);
cout << l << endl;
l = search(a,7,2,cmp2);
cout << l << endl;
system("pause"); return 0;
}
1.12.2 Функции с переменным числом параметров
Язык C++ поддерживает возможность написания функций с переменным числом параметров. Для этого при объявлении функции в конце списка параметров ставится многоточие (…). В стандартной библиотеке наиболее типичными примерами таких функций являются printf и scanf.
Поскольку при вызове функции параметры передаются через стек, технически не представляет сложности заносить в стек любое число аргументов. Однако, возникает вопрос: как именно функция может узнать, сколько параметров ей передано, и каковы типы этих параметров. Типичный подход состоит в том, чтобы передавать функции в первом обязательном параметре информацию о количестве аргументов (а также об их типах, если они могут быть разными). Именно так работает стандартная функция printf. Посмотрим на пример вызова этой функции:
printf("%d %f %s", 5, 3.14, "abc");
Первый (обязательный) параметр функции представляет собой так называемую строку формата. В ней встречаются спецификаторы формата (начинающиеся со знака '%'), с помощью которых функция printf понимает, сколько дополнительных параметров передано в функцию и какие они имеют типы. Спецификаторы %d, %f и %s говорят о том, что в функцию переданы три дополнительных аргумента типов int, double и char* соответственно.
В качестве примера реализуем функцию sum, позволяющую вычислять сумму произвольного количества целочисленных аргументов. Функция имеет один обязательный параметр, в котором передаётся количество аргументов.
// Пример 1.19 - функция с переменным числом параметров
#include <iostream>
int sum(int n, ...) { // может верно работать не во всех компиляторах!
int res = 0;
for (int i = 0, *p = &n + 1; i < n; i++, p++)
res += *p;
return res;
}
int main() {
std::cout << sum(2, 3, 5) << std::endl; // 8
std::cout << sum(4, 1, 2, 3, 4) << std::endl; // 10
}
В функции sum мы берём указатель на первый аргумент функции в стеке, и в цикле перемещаемся по остальным её аргументам. К сожалению, данный код будет работать не везде. Дело в том, что в некоторых компиляторах с включенными настройками оптимизации нет гарантии, что все аргументы функции будут действительно передаваться через стек. Например, для повышения производительности первый аргумент может быть передан через регистр процессора.
Более правильным подходом к написанию функций с переменным числом аргументов является использование специальных макросов va_start, va_arg, va_end из заголовочного файла stdarg.h. Данные макросы делают примерно то же самое, что мы выше делали вручную, но при этом гарантируется корректное получение параметров из стека. Ниже приведён правильный вариант написания функции sum:
// Пример 1.20 - функция с переменным числом параметров
// с использованием стандартных макросов
#include <iostream>
#include <stdarg.h>
int sum(int n, ...) {
int res = 0;
va_list args;
//встаём на первый параметр
va_start(args, n);
for (int i = 0; i < n; i++) {
//получаем следующий параметр, указывая его тип
res += va_arg(args, int);
}
va_end(args); // обеспечиваем правильное восстановление стека
return res;
}
int main() {
std::cout << sum(2, 3, 5) << std::endl; // 8
std::cout << sum(4, 1, 2, 3, 4) << std::endl; // 10
}
