Инициализация объектов класса с помощью таких функций как set_date()
- неэлегантное и чреватое ошибками решение. Поскольку явно не было
указано, что объект требует инициализации, программист может либо забыть
это сделать, либо сделать дважды, что может привести к столь же
катастрофическим последствиям. Лучше дать программисту возможность
описать функцию, явно предназначенную для инициализации объектов.
Поскольку такая функция конструирует значение данного типа, она
называется конструктором. Эту функцию легко распознать - она имеет
то же имя, что и ее класс:
class date {
// ...
date(int, int, int);
};
Если в классе есть конструктор, все объекты этого класса будут
проинициализированы. Если конструктору требуются параметры, их
надо указывать:
date today = date(23,6,1983);
date xmas(25,12,0); // краткая форма
date my_birthday; // неправильно, нужен инициализатор
Часто бывает удобно указать несколько способов инициализации
объекта. Для этого нужно описать несколько конструкторов:
class date {
int month, day, year;
public:
// ...
date(int, int, int); // день, месяц, год
date(int, int); // день, месяц и текущий год
date(int); // день и текущие год и месяц
date(); // стандартное значение: текущая дата
date(const char*); // дата в строковом представлении
};
Параметры конструкторов подчиняются тем же правилам о типах
параметров, что и все остальные функции ($$4.6.6). Пока конструкторы
достаточно различаются по типам своих параметров, транслятор
способен правильно выбрать конструктор:
date today(4);
date july4("July 4, 1983");
date guy("5 Nov");
date now; // инициализация стандартным значением
Размножение конструкторов в примере c date типично. При разработке
класса всегда есть соблазн добавить еще одну возможность, - а вдруг
она кому-нибудь пригодится. Чтобы определить действительно нужные
возможности, надо поразмышлять, но зато в результате, как правило,
получается более компактная и понятная программа. Сократить число
сходных функций можно с помощью стандартного значения параметра.
В примере с date для каждого параметра можно задать стандартное
значение, что означает: "взять значение из текущей даты".
class date {
int month, day, year;
public:
// ...
date(int d =0, int m =0, y=0);
// ...
};
date::date(int d, int m, int y)
{
day = d ? d : today.day;
month = m ? m : today.month;
year = y ? y : today.year;
// проверка правильности даты
// ...
}
Когда используется стандартное значение параметра, оно должно
отличаться от всех допустимых значений параметра. В случае месяца и
дня очевидно, что при значении нуль - это так, но неочевидно,
что нуль подходит для значения года. К счастью, в европейском
календаре нет нулевого года, т.к. сразу после 1 г. до р.х.
(year==-1) идет 1 г. р.х. (year==1). Однако для обычной программы
это, возможно, слишком тонкий момент.
Объект класса без конструктора может инициализироваться
присваиванием ему другого объекта этого же класса. Это незапрещено и
в том случае, когда конструкторы описаны:
date d = today; // инициализация присваиванием
На самом деле, имеется стандартный конструктор копирования,
определенный как поэлементное копирование объектов одного класса.
Если такой конструктор для класса X не нужен, можно переопределить
его как конструктор копирования X::X(const X&). Подробнее поговорим
об этом в $$7.6.
5.2.5 Удаление
Пользовательские типы чаще имеют, чем не имеют, конструкторы, которые
проводят надлежащую инициализацию. Для многих типов требуется и
обратная операция - деструктор, гарантирующая правильное удаление
объектов этого типа. Деструктор класса X обозначается ~X ("дополнение
конструктора"). В частности, для многих классов используется
свободная память (см. $$3.2.6), выделяемая конструктором и
освобождаемая деструктором. Вот, например, традиционное определение
типа стек, из которого для краткости полностью выброшена обработка
ошибок:
class char_stack {
int size;
char* top;
char* s;
public:
char_stack(int sz) { top=s=new char[size=sz]; }
~char_stack() { delete[] s; } // деструктор
void push(char c) { *top++ = c; }
void pop() { return *--top; }
};
Когда объект типа char_stack выходит из текущей области видимости,
вызывается деструктор:
void f()
{
char_stack s1(100);
char_stack s2(200);
s1.push('a');
s2.push(s1.pop());
char ch = s2.pop();
cout << ch << '\n';
}
Когда начинает выполняться f(), вызывается конструктор char_stack,
который размещает массив из 100 символов s1 и массив из 200
символов s2. При возврате из f() память, которая была занята обоими
массивами, будет освобождена.
5.2.6 Подстановка
Программирование с классами предполагает, что в программе появится
множество маленьких функций. По сути, всюду, где в программе с
традиционной организацией стояло бы обычное обращение к структуре
данных, используется функция. То, что было соглашением, стало
стандартом, проверяемым транслятором. В результате программа
может стать крайне неэффективной. Хотя вызов функции в C++
и не столь дорогостоящая операция по сравнению с другими
языками, все-таки цена ее много выше, чем у пары обращений к памяти,
составляющих тело тривиальной функции.
Преодолеть эту трудность помогают функции-подстановки (inline).
Если в описании класса функция-член определена, а не только описана,
то она считается подстановкой. Это значит, например, что при
трансляции функций, использующих char_stack из предыдущего примера,
не будет использоваться никаких операций вызова функций, кроме
реализации операций вывода! Другими словами, при разработке такого
класса не нужно принимать во внимание затраты на вызов функций.
Любое, даже самое маленькое действие, можно смело определять как
функцию без потери эффективности. Это замечание
снимает наиболее часто приводимый довод в пользу общих членов
данных.
Функцию-член можно описать со спецификацией inline и вне описания
класса:
class char_stack {
int size;
char* top;
char* s;
public:
char pop();
// ...
};
inline char char_stack::pop()
{
return *--top;
}
Отметим, что недопустимо описывать разные определения функции-члена,
являющейся подстановкой, в различных исходных файлах ($$R.7.1.2).
Это нарушило бы понятие о классе как о цельном типе.
Достарыңызбен бөлісу: |