- •Формальные свойства алгоритмов
- •Основные определения языка, структура программы на языке Си.
- •1. Алфавит
- •2. Литералы
- •3. Комментарии
- •Основные математические операции
- •Увеличение значения переменной на 1
- •Представление о префиксной (до) и постфиксной (после) операциях увеличения
- •Перегрузка операции присваивания копированием
- •Притянутые за уши примеры использования
- •Перегрузка операции запятая
- •1.4. Операторы
- •1.4.1. Оператор выражение
- •1.4.2. Пустой оператор
- •1.4.3. Составной оператор
- •1.4.4. Оператор if
- •1.4.5. Оператор switch
- •1.4.6. Оператор break
- •1.4.7. Оператор for
- •1.4.8. Оператор while
- •1.4.9. Оператор do while
- •1.4.10. Оператор continue
- •1.4.11. Оператор return
- •1.4.12. Оператор goto
- •Условный оператор
- •13. Массивы
- •14. Указатели
- •16. Операторы ввода-вывода в языке си (c)
- •Препроцессор Си
- •Директивы
- •Функции Включение
- •Условная компиляция
- •Функции возвращающие значение.
- •Функции с параметрами.
- •Статические переменные
- •Передача аргументов по ссылке
- •23. Урок 12. Локальные переменные и область видимости
- •Объявление локальных переменных
- •Глобальные переменные
- •24. Пространство имен
- •25. Стандартная библиотека языка Си
- •Структура
- •[Править]Библиотечные заголовочные файлы ansi Си
- •[Править]Стандартная библиотека Си в других языках
- •[Править]Общая поддержка библиотек
- •[Править]Встроенные функции компилятора
Перегрузка операции запятая
Мы подробно изучили использование операции запятая, и пока обнаружили лишь одно полезное применение. К счастью, язык программирования C++ позволяет перегрузить операцию запятая для того, чтобы она служила каким угодно целям. Синтаксис перегрузки очевиден:
возвр_тип operator,(тип1 арг1, тип2 арг2) { ... }
Следует знать о том, что точка следования, определённая на месте операции запятая, исчезает при перегрузке этой операции. Этот эффект вызван тем, что запятая в случае перегрузки превращается в обычный вызов функции, а порядок вычисления аргументов при вызове функции не определён.
Операция может быть шаблонной, а аргументы могут быть переданы по ссылкам. В общем, простора для творчества много (этот простор ограничен только тем, что хотя бы один из аргументов переопределяемой операции должен быть пользовательским классом).
Но и тут запятая показывает свой скверный характер. Неосторожно переопределив эту операцию для каких-либо широко используемых в программе типов данных, мы можем нарушить программу в совершенно неожиданных местах (в том числе могут начать неверно работать стандартные контейнеры).
Рассмотрим довольно частое применение перегруженной операции запятая — компактную запись инициализации контейнеров. Например, вместо такого кода:
vector<int> c;c.push_back(10); c.push_back(20); c.push_back(30); c.push_back(40);
появляется возможность писать такой код:
vector<int> c;c << 10, 20, 30, 40;
Добиться описанной функциональности можно двумя способами:
Сделать так, чтобы обе операции (запятая и <<) добавляли элемент (правый аргумент) в контейнер (левый аргумент), и возвращали ссылку на контейнер. По причине, описанной выше, это плохой способ, так как операция запятая начнёт срабатывать в неожиданных местах программы. Да и смысла не будет в запятой, ибо можно будет писать так: c << 10 << 20 << 30 << 40;
Сделать так, чтобы операция << возвращала объект специального класса, и определить запятую именно для этого класса.
Реализуем второй вариант. Прежде всего, создадим шаблонный класс CommaInserter, который содержит ссылку на контейнер. При конструировании объект этого класса будет запоминать ссылку на контейнер и добавлять в него первый элемент. Кроме того, CommaInserter будет иметь собственную операцию запятая, которая будет добавлять очередной элемент:
template<class C> class CommaInserter{private: C &container;public: template<class V> inline CommaInserter(C &container, V const &value): container(container) { container.insert(container.end(), value); } template<class V> inline CommaInserter<C> operator,(V const &value) { container.insert(container.end(), value); return *this; }};
Теперь доопределим операции << и < для того, чтобы они создавали экземпляр CommaInserter. Вторая операция отличается от первой тем, что вначале очищает контейнер:
template<class C, class V> inline CommaInserter<C> operator<<(C &container, V const &value){ //Начинает добавление элементов в контейнер, создавая CommaInserter return CommaInserter<C>(container, value);}template<class C, class V> inline CommaInserter<C> operator<(C &container, V const &value){ //Очищает контейнер и начинает добавление элементов container.clear(); return CommaInserter<C>(container, value);}
Представленные операции << и < для компилятора имеют минимальный приоритет (приоритет не в смысле порядка выполнения операций, а в смысле того, какая именно реализация операции будет выбрана компилятором), так как эти операции шаблонные, и все аргументы у них шаблонные. Поэтому не стоит переживать, что операции сработают в «лишних» местах: если уж программист пишет << применительно к какому-либо объекту, то программист явно хочет выполнить с объектом какое-либо действие (а не просто разделить два действия, как в случае с запятой).
Поиграемся с нашими новыми операциями:
//Вектор из стандартной библиотеки (#include <vector>)vector<int> v; //Пустой векторv << 1, 2, 3; //Добавляем элементы: [1, 2, 3]v << 2, 3, 4; //Добавляем ещё элементы: [1, 2, 3, 2, 3, 4]v < 3, 4, 5; //Очищаем вектор и добавляем элементы: [3, 4, 5]//Множество из стандартной библиотеки (#include <set>)set<int> s;s << 3, 2, 1, 3; //Добавляем элементы в множество. Множество всегда // отсортировано, и не содержит повторений, // поэтому s = {1, 2, 3}s << 2, 5; //Ещё пара элементов: s = {1, 2, 3, 5}s < 1, 2; //Очищаем и добавляем: s = {1, 2}
Хочется верить, что представленные операции будут работать со всеми контейнерами из стандартной библиотеки и с большинством пользователь