Открытый интерфейс разрабатывают в первую очередь, поскольку именно он интересует большинство людей. В принципе пользователю не обязательно знать детали реализации. На самом же деле люди, как правило, любопытны и хотят знать, насколько разумна реализация класса и какие приемы использовал ее автор, чтобы научиться у него чему-нибудь. И все же, если реализацию класса создавали не мы, то большую часть времени будем работать с его открытым интерфейсом. Компилятору безразличен порядок следования членов класса; он обрабатывает объявления в любом порядке, в котором мы их укажем.
Определяя члены за пределами класса, мы должны указать, какому классу они принадлежат. Для этого используется обозначение
Date::Date(int yy, int mm, int dd)// конструктор
:y(yy), m(mm), d(dd) // примечание: инициализация члена
{
}
void Date::add_day(int n)
{
// ...
}
int month() // Ой: мы забыли про класс Date::
{
return m; // не функция-член, к переменной m доступа нет
}
Обозначение :y(yy)
, m(mm)
, d(dd)
указывает на то, как инициализируются члены. Оно называется списком инициализации. Мы могли бы написать эквивалентный фрагмент кода.
Date::Date(int yy, int mm, int dd) // конструктор
{
y = yy;
m = mm;
d = dd;
}
Однако сначала нам следовало бы инициализировать члены их значениями, заданными по умолчанию, и лишь потом присваивать им новые значения. Кроме того, в этом случае не исключена возможность того, что мы случайно используем член класса до его инициализации. Обозначение :y(yy)
, m(mm)
, d(dd)
точнее отражает наши намерения. Разница между этими фрагментами точно такая же, как между двумя примерами, приведенными ниже. Рассмотрим первый из них.
int x; // сначала определяем переменную x
// ...
x = 2; // потом присваиваем ей значение
Второй пример выглядит так:
int x = 2; // определяем и немедленно инициализируем двойкой
Для полноты картины укажем еще один способ инициализации с помощью синтаксической конструкции, напоминающей аргументы функции в скобках.
int x(2); // инициализируем двойкой
Date sunday(2009,8,29); // инициализируем объект Sunday
// триадой (2009,8,29)
Функцию-член класса можно также определить в определении класса.
// простой класс Date (детали реализации будут рассмотрены позднее)
class Date {
public:
Date(int yy, int mm, int dd)
:y(yy), m(mm), d(dd)
{
// ...
}
void add_day(int n)
{
// ...
}
int month() { return m; }
// ...
private:
int y, m, d; // год, месяц, день
};
Во-первых, отметим, что теперь объявление класса стало больше и запутаннее. В данном примере код конструктора и функции add_day()
могут содержать десятки строк. Это в несколько раз увеличивает размер объявления класса и затрудняет поиск интерфейса среди деталей реализации. Итак, мы не рекомендуем определять большие функции в объявлении класса. Тем не менее посмотрите на определение функции month()
. Оно проще и короче, чем определение Date::month()
, размещенное за пределами объявления класса. Определения коротких и простых функций можно размещать в объявлении класса.
Обратите внимание на то, что функция month()
может обращаться к переменной m, даже несмотря на то, что переменная m определена позже (ниже) функции month()
. Член класса может ссылаться на другой член класса независимо от того, в каком месте класса он определен. Правило, утверждающее, что имя переменной должно быть объявлено до ее использования, внутри класса ослабляется.
• Функция становится month()
.
• При изменении тела подставляемой функции-члена класса придется скомпилировать заново все модули, в которых он используется. Если тело функции определено за пределами объявления класса, то потребуется перекомпилировать только само определение класса. Отсутствие необходимости повторного компилирования при изменении тела функции может оказаться огромным преимуществом в больших программах.