class Counter
{
Counter (short int x);
// ...
};
Данный конструктор создает объект класса Counter, используя временный безымянный объект этого класса, способный принимать значения типа short. Чтобы сделать этот процесс более наглядным, предположим, что для значений типа short создается не безымянный объект, а объект класса Counter с именем wasShort.
Шаг 3: присвоение значения объекта wasShort объекту theCtr, что эквивалентно записи
"theCtr = wasShort";
На этом шаге временный объект wasShort, созданный при запуске конструктора, замещается на постоянный объект theCtr, принадлежащий классу Counter. Другими словами, значение временного объекта присваивается объекту theCtr.
Чтобы понять, как происходит этот процесс, следует четко уяснить принципы работы, справедливые для ВСЕХ перегруженных операторов, определенных с помощью ключевого слова operator. В случае с операторами с двумя операндами (такими как = или +) находящийся справа операнд объявляется как параметр функции оператора, заданной в конструкторе. Так, выражение
а = b
объявляется как
a.operator=(b);
Что произойдет, если изменить порядок присвоения, как в следующем примере:
1: Counter theCtr(5);
2: int theShort = theCtr;
3: cout << "theShort : " << theShort << endl;
Вновь компилятор покажет сообщение об ошибке. Хотя сейчас компилятор уже знает, как создать временный объект Counter для принятия значения типа int, но он не знает, как осуществить обратный процесс.
Операторы преобразования типов
Чтобы разрешить эту и подобные ей проблемы, в C++ есть специальные операторы преобразования типов, которые можно добавить в пользовательский класс. В результате появится возможность явного преобразования типа пользовательского класса к любому из базовых типов данных языка программирования. Реализация этой возможности показана в листинге 10.18. Только одно замечание: в операторах преобразований не задается тип возврата. Даже если их работа напоминает возврат функции, в действительности они возвращают преобразованное значение.
Листинг 10.18. Преобразования данных типа Counter в тип unsigned short()
1: #include <iostream.h>
2:
3: class Counter
4: {
5: public:
6: Counter();
7: Counter(int val);
8: ~Counter(){ }
9: int GetItsVal()const { return itsVal; }
10: void SetItsVal(int x) { itsVal = x; }
11: operator unsigned short();
12: private:
13: int itsVal;
14:
15: };
16:
17: Counter::Counter():
18: itsVal(0)
19: { }
20:
21: Counter::Counter(int val):
22: itsVal(val)
23: { }
24:
25: Counter::operator unsigned short ()
26: {
27: return ( int (itsVal) );
28: }
29:
30: int main()
31: {
32: Counter ctr(5);
33: int theShort = ctr;
34: cout << "theShort: " << theShort << endl;
35: return 0;
36: }
Результат:
theShort: 5
Анализ: В строке 11 объявляется оператор преобразования типа. Обратите внимание, что в нем не указан тип возврата. Функция оператора преобразования выполняется в строках 25—28. В строке 27 возвращается значение объекта itsVal, преобразованное в тип int.
Теперь компилятор знает, как присвоить объекту класса значение типа int и как возвратить из объекта класса текущее значение, чтобы присвоить его внешней переменной типа int.
Резюме
Сегодня вы научились перегружать функции-члены пользовательского класса. Вы также узнали, как передавать в функции значения, заданные по умолчанию, и в каких случаях вместо значений по умолчанию лучше использовать перегруженные функции.
Перегрузка конструкторов класса позволяет более гибко управлять классами и создавать новые классы, содержащие объекты других классов. Лучше всего инициализацию объектов класса осуществлять во время инициализации конструктора, вместо того чтобы делать это в теле конструктора.
Конструктор-копировщик и оператор присваивания по умолчанию предоставляются компилятором, если в классе эти объекты не были созданы пользователем. Но при использовании копировщика и оператора присваивания, заданных по умолчанию, осуществляется только поверхностное копирование данных. В тех классах, где в числе членов класса используются указатели на области динамической памяти, вместо поверхностного копирования лучше использовать глубинное, при котором копируемые данные размещаются по новым адресам.
Хотя в языке C++ можно произвольно перегружать все операторы, настоятельно рекомендуем не создавать таких операторов, функции которых противоречат их традиционному использованию. Кроме того, невозможно изменить ассоциативность оператора, а также создавать собственные операторы, не представленные в языке C++.
Указатель this ссылается на текущий объект и является невидимым параметром для всех функций-членов. Разыменованный указатель this часто возвращается перегруженными операторами.
Операторы преобразования типов позволяют настраивать классы для использования в выражениях, осуществляющих обмен данными разных типов. Данные операторы являются исключением из правила, состоящего в том, что все функции возвращают явные значения, как, например, конструктор и деструктор. В данных операторах тип возврата не устанавливается.
Вопросы и ответы
Зачем использовать значения, заданные по умолчанию, если можно перегрузить функцию?
Проще иметь дело с одной функцией, чем с двумя. Кроме того, зачастую проще понять работу функции, использующей значения, заданные по умолчанию, чем каждый раз внимательно изучать тело функции, чтобы понять ее назначение. Кроме того, обновление одной версии функции без обновления другой версии часто бывает причиной ошибок в работе программы.
Почему бы тогда постоянно не использовать только значения, заданные по умолчанию?
Перегрузка функций предоставляет ряд возможностей, которые нельзя реализовать, используя только значения, заданные по умолчанию. Например, изменять не только число параметров в списке, но и их типы.
Какие переменные-члены следует инициализировать одновременно с инициализацией конструктора, а какие оставлять для тела конструктора?
Используйте следующее простое правило: одновременно с конструктором следует инициализировать как можно больше переменных-членов. Только некоторые из них, такие как переменные для текущих вычислений и управления выводом на печать следует инициализировать в теле конструктора.
Может ли перегруженная функция содержать параметры, заданные по умолчанию?
Конечно. Нет никакой причины, по которой не следовало бы использовать это мощное средство. Одна или несколько версий перегруженных функций могут иметь собственные значения, заданные по умолчанию. При установке значений по умолчанию для перегруженных функций нужно следовать тем же общим правилам, что и при установке значений по умолчанию для обычных функций.
Почему одни функции-члены определяются в описании класса, а другие нет?
Если функция определяется в описании класса, то далее она используется в режиме inline. Впрочем, встраивание кода функции по месту вызова происходит только в
том случае, если функция достаточно простая. Также следует отметить, что задать встраивание кода функции-члена в код программы можно с помощью ключевого слова inline, даже если эта функция была описана отдельно от класса.
Коллоквиум
В этом разделе предлагаются вопросы для самоконтроля и укрепления полученных знаний и приводится несколько упражнений, которые помогут закрепить ваши практические навыки. Попытайтесь самостоятельно ответить на вопросы теста и выполнить задания, а потом сверьте полученные результаты с ответами в приложении Г. Не приступайте к изучению материала следующей главы, если для вас остались неясными хотя бы некоторые из предложенных ниже вопросов.
Контрольные вопросы
1. Если вы перегрузили функцию-член, как потом можно будет различить разные варианты функции?
2. Какая разница между определением и объявлением?
3. Когда вызывается конструктор-копировщик?
4. Когда вызывается деструктор?
5. Чем отличается конструктор-копировщик от оператора присваивания (=)?
6. Что представляет собой указатель this?
7. Как отличается перегрузка операторов предварительного и последующего действия?
8. Можно ли перегрузить operator+ для переменных типа short int?
9. Допускается ли в C++ перегрузка operator++ таким образом, чтобы он выполнял в классе операцию декремента?
10. Как устанавливается тип возврата в объявлениях функций операторов преобразования типов?
Упражнения
1. Представьте объявление класса SimpleCircle с единственной переменой-членом itsRadius. В классе должны использоваться конструктор и деструктор, заданные по умолчанию, а также метод установки радиуса.