Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
Тип pos определен в части public класса Screen, поскольку пользователи должны использовать это имя. Пользователи класса Screen не обязаны знать, что он использует класс string для хранения своих данных. Определив тип pos как открытый член, эту подробность реализации класса Screen можно скрыть.
В объявлении типа pos есть два интересных момента. Во-первых, хоть здесь и был использован оператор typedef (см. раздел 2.5.1), с таким же успехом можно использовать псевдоним типа (см. раздел 2.5.1):
class Screen {
public:
// альтернативный способ объявления типа-члена с использованием
// псевдонима типа
using pos = std::string::size_type;
// другие члены как прежде
};
Во-вторых, по причинам, которые будут описаны в разделе 7.3.4, в отличие от обычных членов, типы-члены определяются прежде, чем используются. В результате типы-члены обычно располагают в начале класса.
Функции-члены класса ScreenЧтобы сделать наш класс полезней, добавим в него конструктор, позволяющий пользователям задавать размер и содержимое экрана, наряду с членами, позволяющими переместить курсор и получить символ в указанной позиции:
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default; // необходим, поскольку у класса Screen есть
// другой конструктор
// внутриклассовый инициализатор инициализирует курсор значением 0
Screen(pos ht, pos wd, char c) : height(ht), width(wd),
contents(ht * wd, c) { }
char get() const // получить символ в курсоре
{ return contents [cursor]; } // неявно встраиваемая
inline char get(pos ht, pos wd) const; // явно встраиваемая
Screen &move(pos r, pos с); // может быть сделана встраиваемой позже
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
Поскольку мы предоставляем конструктор, компилятор не будет автоматически создавать стандартный конструктор сам. Если у нашего класса должен быть стандартный конструктор, то придется создать его явно. В данном случае используется синтаксис = default, чтобы попросить компилятор самому создать определение стандартного конструктора (см. раздел 7.1.4).
Стоит также обратить внимание на то, что второй конструктор (получающий три аргумента) неявно использует внутриклассовый инициализатор для переменной-члена cursor (см. раздел 7.1.4). Если бы у класса не было внутриклассового инициализатора для переменной-члена cursor, то мы явно инициализировали бы ее наряду с другими переменными-членами.
Встраиваемые члены классаУ классов зачастую бывают небольшие функции, которые выгодно сделать встраиваемыми. Как уже упоминалось, определенные в классе функции-члены автоматически являются встраиваемыми (inline) (см. раздел 6.5.2). Таким образом, конструкторы класса Screen и версия функции get(), возвращающей обозначенный курсором символ, являются встраиваемыми по умолчанию.
Функцию-член можно объявить встраиваемой явно в ее объявлении в теле класса. В качестве альтернативы функцию можно указать встраиваемой в определении, расположенном вне тела класса:
inline // функцию можно указать встраиваемой в определении
Screen &Screen::move(pos r, pos с) {
pos row = r * width; // вычислить положение ряда
cursor = row + с; // переместить курсор к столбцу этого ряда
return *this; // возвратить этот объект как l-значение
}
char Screen::get(pos r, pos с) const // объявить встраиваемый в классе
{
pos row = r * width; // вычислить положение ряда
return contents[row + с]; // возвратить символ в данном столбце
}
Хоть и не обязательно делать это, вполне допустимо указать ключевое слово inline и в объявлении, и в определении. Однако указание ключевого слова inline в определении только вне класса может облегчить чтение класса.
По тем же причинам, по которым встраиваемые функции определяют в заголовках (см. раздел 6.5.2), встраиваемые функции-члены следует определить в том же заголовке, что и определение соответствующего класса.
Перегрузка функций-членовПодобно функциям, которые не являются членами класса, функции-члены могут быть перегружены (см. раздел 6.4), если они отличаются количеством и/или типами параметров. При вызове функции-члена используется тот же процесс подбора функции (см. раздел 6.4), что и у функций, не являющихся членом класса.
Например, в классе Screen определены две версии функции get(). Одна версия возвращает символ, обозначенный в настоящее время курсором; другая возвращает символ в указанной позиции, определенной ее рядом и столбцом. Чтобы определить применяемую версию, компилятор использует количество аргументов:
Screen myscreen;
char ch = myscreen.get(); // вызов Screen::get()
ch = myscreen.get(0,0); // вызов Screen::get(pos, pos)
Изменяемые переменные-членыИногда (но не очень часто) у класса есть переменная-член, которую следует сделать изменяемой даже в константной функции-члене. Для обозначения таких членов в их объявление включают ключевое слово mutable.
Изменяемая переменная-член (mutable data member) никогда не бывает константой, даже когда это член константного объекта. Соответственно константная функция-член может изменить изменяемую переменную-член. В качестве примера добавим в класс Screen изменяемую переменную-член access_ctr, используемую для отслеживания частоты вызова каждой функции-члена класса Screen:
class Screen {
public:
void some_member() const;
private:
mutable size_t access_ctr; // может измениться даже в константном
// объекте
// другие члены как прежде
};
void Screen::some_member() const {
++access_ctr; // сохранить количество вызовов любой функции-члена
// безотносительно других выполняемых ею действий
}
Несмотря на то что функция-член some_member() константная, она может изменить значение переменной-члена access_сtr. Этот член класса является изменяемым, поэтому любая функция-член, включая константные, может изменить это значение.
Инициализаторы переменных-членов классаКроме класса Screen, определим также класс диспетчера окон, который представляет коллекцию окон на данном экране. У этого класса будет вектор объектов класса Screen, каждый элемент которого представляет отдельное окно. По умолчанию класс Window_mgr должен изначально содержать один объект класса Screen, инициализированный значением по умолчанию. По новому стандарту наилучшим способом определения такого значения по умолчанию является внутриклассовый инициализатор (см. раздел 2.6.1):
class Window_mgr {
private:
// по умолчанию отслеживающий окна объект класса Window_mgr
// содержит одно пустое окно стандартного размера
std::vector<Screen> screens{Screen(24, 80, ' ')};
};
При инициализации переменных-членов типа класса их конструктору следует предоставить аргументы. В этом случае применяется список инициализации переменной-члена типа vector (см. раздел 3.3.1) с инициализатором для одного элемента. Этот инициализатор содержит значение типа Screen, передаваемое конструктору vector<Screen> для создания вектора с одним элементом. Это значение создается конструктором класса Screen, получающим параметры в виде двух размерностей и заполняющего символа, чтобы создать пустое окно заданного размера.
Как уже упоминалось, для внутриклассовой инициализации может использоваться форма инициализации = (как при инициализации переменных-членов класса Screen) или прямая форма инициализации с использованием фигурных скобок (как у вектора screens).
При предоставлении внутриклассового инициализатора это следует сделать после знака = или в фигурных скобках.
Упражнения раздела 7.3.1Упражнение 7.23. Напишите собственную версию класса Screen.