Лекция 13. Введение в ООП. Часть 4 Красс Александр СПбГУ ИТМО, 2008
2 Тема "Перегрузка операторов"
3 Перегрузка операторов При проектировании класса вы можете предоставить набор операторов, для которых будет определена семантика работы с этим классом. Это реализуется с помощью перегрузки операторов. Для этого необходимо реализовать функцию следующего вида: Тип - знак операции. Перегрузка операторов не более чем "синтаксический сахар"
4 Перегрузка операторов Операторная функция может быть или членом класса, или отдельной функцией. Если это отдельная функция, требующая доступа к не-public членам класса, она должна быть объявлена другом этого класса.
5 Перегрузка операторов Вы можете перегружать почти все операторы кроме : ::.*. ?: Вы не можете перегрузить операторы для стандартных типов. Вы не можете добавлять новые операторы (Например, % для вычисления экспоненты). Вы не можете превратить бинарный оператор в унарный.
6 Бинарные операторы Бинарный оператор может быть реализован или как функция-член с одним аргументом, или как глобальная функция с двумя аргументами Пример (оператор сложения): Имеем выражение a + b. Оно будет превращено в – a.operator+(b), если operator+ – функция член. – или в operator+(a, b), если operator+ – это глобальная функция.
7 Унарные операторы Унарный оператор может быть реализован или как функция-член без аргументов или как глобальная функция с одним аргументом Пример (оператор изменения знака): Имеем выражение -a. Оно будет превращено в – a.operator-(), если operator- – функция член, –или в operator-(a), если operator- – это глобальная функция.
8 Перегрузка операторов Пример перегрузки через функции-члены: class X { X* operator& (); // унарный & // (взятие адреса) X operator& (X); // бинарный & (и) X operator& (X, X); // ошибка: тернарный & X operator/ (X, X); // ошибка: тернарный / };
9 Перегрузка операторов Перегрузка через глобальные функции: X operator- (X); // унарный минус X operator- (X,X); // бинарный минус X operator- (); // ошибка: нет // операнда X operator- (X, X, X); // ошибка: //тернарный // оператор - X operator% (X); // ошибка: унарный // оператор %
10 String class String { public: String(); String(const char* s = NULL); String(const String& s); ~String (); String& operator= (const String& s); String operator+ (const String& s); char& operator[] (int element); int length () const; // length private: char* data; };
11 Операция конкатенации Операция конкатенации (через функцию-член): String String::operator+ (const String& s) { String temp; temp.data = new char[strlen(data) + strlen(s.data) + 1]; strcpy(temp.data, data); strcat(temp.data, s.data); return temp; }
12 Операция конкатенации Операция конкатенации должна поддерживать следующие формы записи: 1. String + String 2. String + "a C string" 3."a C string" + String 4."a C string" + "a C string" 5. String += String 6. String += "a C string"
13 Операция конкатенации void main () { String a ("Hello"); String b (" world"); String c, d; c = a + b; cout << c.ToChars() << endl;// На экране: d = a + " world"; // Hello world cout << d.ToChars() << endl;// Hello world }
14 Операция конкатенации Первые два варианта использования мы поддерживаем. В выражении d = a + " hello" строка " hello" неявно конвертируется в класс String с помощью конструктора String (const char* s). При выполнении этой строки компилятор создаст временную переменную и вызовет для нее этот конструктор. Следовательно, мы не обязаны реализовывать второй вариант оператора, со следующим прототипом: String operator+(const char *s); Хотя это и было бы эффективнее.
15 Операция конкатенации Операция конкатенации должна поддерживать следующие формы записи: 1. String + String 2. String + "a C string" 3."a C string" + String 4."a C string" + "a C string" 5. String += String 6. String += "a C string"
16 Операция конкатенации При попытке написать: String e = "Hello" + b; cout << e.ToChars() << endl; мы получим ошибку. Этот код как бы эквивалентен вызову "Hello".operator+(b);
17 Операция конкатенации Для поддержки третьего сценария надо использовать глобальную функцию String operator+ (const String& a, const String& b); Она должна быть объявлена как friend
18 Операция конкатенации class String {... friend String operator+ (const String& a, const String& b);... }; Функцию член в этом случае можно убрать
19 Операция конкатенации String operator+ (const String& a, const String& b) { String temp; temp.data = new char[strlen (a.data) + strlen (b.data) + 1]; strcpy(temp.data, a.data); strcat(temp.data, b.data); return temp; }
20 Операция конкатенации Операция конкатенации должна поддерживать следующие формы записи: 1. String + String 2. String + "a C string" 3."a C string" + String 4."a C string" + "a C string" 5. String += String 6. String += "a C string"
21 Операция конкатенации Теперь мы поддерживаем первые четыре сценария использования. Однако компилятор не может вывести оператор +=, даже если в классе определены операции + и =. Нам нужна своя реализация этого оператора: String& operator+=(const String &s); После реализации оператора +=, оператор + можно реализовать через +=
22 Операция конкатенации String operator+ (const String &a, const String &b) { String temp(a); // Конструктор // копирования temp += b; return temp; }
23 Оператор индексирования Пользователи класса String хотели бы получать доступ к отдельным символам строки с помощью следующего синтаксиса: String name ("kenny"); String buffer ("qenny");... cout << name[0] << endl; // k name[0] = 'K';... buffer[0] = name[0]; // "Kenny"
24 Оператор индексирования Для этого нам надо определить оператор индексирования: char& String::operator[] (int index) { return data[index]; } Обратите внимание на использование ссылки.
25 Оператор индексирования Также хорошо бы иметь и вот такую версию этого оператора: const char& String::operator[] (int index) const { return data[index]; } Она будет вызываться для константных объектов класса String.
26 Перегрузка операторов В каких случаях оператор надо делать членом класса, а в каких глобальной функцией? –Если нужно, что бы левый аргумент был другого типа, то оператор должен быть глобальной функцией. Возможно, другом (friend). –Операторы =, [], (), -> могут быть только членами класса. –Если есть возможность, оператор должен быть определён вне класса, но при этом не быть другом и находится в том же пространстве имён (namespace), что и сам класс.
27 String class String { public: String(const char* s = NULL); // Конструктор String(const String& s); // Конструктор копирования ~String (); // Деструктор String& operator= (const String& s); // Присваивание String operator+ (const String& s); // Конкатенация char& operator[] (int element); // Оператор индексации friend ostream& operator<< (ostream& os, String& s); // Вывод в поток int length (void) const; // Длина operator const char*() const; // Преобразование типа private: char* data; };
28 Оператор вывода в поток Оператор вывода в поток обязательно должен быть глобальной функцией. Реализация часто очень проста: std::ostream& operator<< (std::ostream& os, String& s) { return (os << s.data); } В конце оператор обязательно должен возвращать ссылку на ostream для поддержки последовательного вывода: String s1, s2; std::cout << s1 << " " << s2; //operator<<(std::cout, s1).operator<<(" ")...
29 Преобразование типов Было бы хорошо, если бы наш класс можно было использовать следующим образом: String s; if (!strcmp(s, "Hello")) {... }
30 Преобразование типов Для этого нам нужно реализовать свой оператор преобразования к типу char *: operator const char*() const { return data; }
31 StrNum Немного отвлечемся от нашего класса String, и рассмотрим другой класс: class StrNum { public: StrNum(int num = 0); StrNum(const char *s); int GetValue() const; private: char value[12]; };
32 StrNum Мы уже умеем перегружать оператор сложения. Теперь перегрузим унарный минус. int operator-(const StrNum& strNum) { return –atoi(strNum.value); }
33 Префиксные и постфиксные операторы Перегрузим оператор инкремента (++). Он обязательно должен быть членом класса.
34 Префиксные и постфиксные операторы // Префиксный // оператор // Например, ++i int operator++() { int v = atoi(value); ++v; itoa(v, value); return v; } // Постфиксный // оператор // Например, i++ int operator++(int) { int old = atoi(value); itoa(old + 1, value); return old; }
35 Префиксные и постфиксные операторы В постфиксном инкременте второй int введён только для того, чтобы можно было различить постфиксный и префиксный операторы инкремента. int operator++(int) Аналогичным образом перегружаются префиксный и постфиксный операторы декремента (--).
36 Спасибо за внимание Вопросы?