• Представление. Тип “знает”, как представить данные, необходимые в объекте.
• Операции. Тип знает, какие операции можно применить к объектам.
Эту концепцию, лежащую в основе многих идей, можно выразить так: “нечто” имеет данные для представления своего текущего значения, — которое иногда называют текущим состоянием, — и набор операций, которые к ним можно применить. Подумайте о компьютерном файле, веб-странице, CD-плеере, чашке кофе, телефоне, телефонном справочнике; все они характеризуются определенными данными и имеют более или менее фиксированный набор операций, которые можно выполнить. В каждом случае результат операции зависит от данных — текущего состояния объекта.
Итак, мы хотим выразить “идею” или “понятие” в коде в виде структуры данных и набора функций. Возникает вопрос: “Как именно?” Ответ на этот вопрос изложен в данной главе, содержащей технические детали этого процесса в языке С++.
В языке С++ есть два вида типов, определенных пользователем: классы и перечисления. Классы носят намного более общий характер и играют более важную роль в программировании, поэтому мы сосредоточим свое внимание в первую очередь на них. Класс непосредственно выражает некое понятие в программе.
Класс (class) — это тип, определенный пользователем. Он определяет, как представляются объекты этого класса, как они создаются, используются и уничтожаются (раздел 17.5). Если вы размышляете о чем-то как об отдельной сущности, то, вполне возможно, должны определить класс, представляющий эту “вещь” в вашей программе. Примерами являются вектор, матрица, поток ввода, строка, быстрое преобразование Фурье, клапанный регулятор, рука робота, драйвер устройства, рисунок на экране, диалоговое окно, график, окно, термометр и часы.
В языке С++ (как и в большинстве современных языков) класс является основной строительной конструкцией в крупных программах, которая также весьма полезна для разработки небольших программ, как мы могли убедиться на примере калькулятора (см. главы 6 и 7).
9.2. Классы и члены класса
Класс — это тип, определенный пользователем. Он состоит из встроенных типов, других типов, определенных пользователем, и функций. Компоненты, использованные при определении класса, называются его
членами (members). Класс может содержать несколько членов, а может и не иметь ни одного члена. Рассмотрим пример.
class X {
public:
int m; // данные - члены
int mf(int v) { int old = m; m=v; return old; } // функция - член
};
Члены класса могут иметь разные типы. Большинство из них являются либо данными-членами, определяющими представление объекта класса, либо функциями-членами, описывающими операции над такими объектами. Для доступа к членам класса используется синтаксическая конструкция вида объект.член. Например:
X var; // var — переменная типа X
var.m = 7; // присваиваем значение члену m объекта var
int x = var.mf(9); // вызываем функцию - член mf() объекта var
Тип члена определяет, какие операции с ним можно выполнять. Например, можно считывать и записывать член типа
int
, вызывать функцию-член и т.д.
9.3. Интерфейс и реализация
Как правило, класс имеет интерфейс и реализацию. Интерфейс — это часть объявления класса, к которой пользователь имеет прямой доступ. Реализация — это часть объявления класса, доступ к которой пользователь может получить только с помощью интерфейса. Открытый интерфейс идентифицируется меткой
public:
, а реализация — меткой
private:
. Итак, объявление класса можно представить следующим образом:
class X { // класс имеет имя X
public:
// открытые члены:
// – пользовательский интерфейс (доступный всем)
// функции
// типы
// данные (лучше всего поместить в раздел private)
private:
// закрытые члены:
// – детали реализации (используется только членами
// данного класса)
// функции
// типы
// данные
};
Члены класса по умолчанию являются закрытыми. Иначе говоря, фрагмент
class X {
int mf(int);
// ...
};
означает
class X {
private:
int mf(int);
// ...
};
поэтому
X x; // переменная x типа X
int y = x.mf(); // ошибка: переменная mf является закрытой
// (т.е. недоступной)
Пользователь не может непосредственно ссылаться на закрытый член класса. Вместо этого он должен обратиться к открытой функции-члену, имеющей доступ к закрытым данным. Например:
class X {
int m;
int mf(int);
public:
int f(int i) { m=i; return mf(i); }
};
X x;
int y = x.f(2);
Различие между закрытыми и открытыми данными отражает важное различие между интерфейсом (точка зрения пользователя класса) и деталями реализации (точка зрения разработчика класса). По мере изложения мы опишем эту концепцию более подробно и рассмотрим множество примеров. А пока просто укажем, что для обычных структур данных это различие не имеет значения. По этой причине для простоты будем рассматривать класс, не имеющий закрытых деталей реализации, т.е. структуру, в которой все члены по умолчанию являются открытыми. Рассмотрим пример.