Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Класс может содержать любое количество спецификаторов доступа; нет никаких ограничений на то, как часто используется спецификатор. Каждый спецификатор определяет уровень доступа последующих членов. Заданный уровень доступа остается в силе до следующего спецификатора доступа или до конца тела класса.
Использование ключевых слов class и structБыло также внесено еще одно изменение: в начале определения класса использовано ключевое слово class, а не struct. Это изменение является чисто стилистическим; тип класса можно определить при помощи любого из этих ключевых слов. Единственное различие между ключевыми словами struct и class в заданном по умолчанию уровне доступа.
Члены класса могут быть определены перед первым спецификатором доступа. Уровень доступа к таким членам будет зависеть от того, как определяется класс. Если используется ключевое слово struct, то члены, определенные до первого спецификатора доступа, будут открытыми; если используется ключевое слово class, то они будут закрытыми.
Общепринятым стилем считается определение классов, все члены которого предположительно будут открытыми, с использованием ключевого слова struct. Если члены класса должны быть закрытыми, используется ключевое слово class.
Единственное различие между ключевыми словами class и struct в задаваемом по умолчанию уровне доступа.
Ключевая концепция. Преимущества инкапсуляцииИнкапсуляция предоставляет два важных преимущества.
• Пользовательский код не может по неосторожности повредить состояние инкапсулированного объекта.
• Реализация инкапсулированного класса может со временем измениться, это не потребует изменений в коде на пользовательском уровне.
Определив переменные-члены закрытыми, автор класса получает возможность вносить изменения в данные. Если реализация изменится, то вызванные этим последствия можно исследовать только в коде класса. Пользовательский код придется изменять только при изменении интерфейса. Если данные являются открытыми, то любой использовавший их код может быть нарушен. Пришлось бы найти и переписать любой код, который полагался на прежнюю реализацию, и только затем использовать программу.
Еще одно преимущество объявления переменных-членов закрытыми в том, что данные защищены от ошибок, которые могли бы внести пользователи. Если есть ошибка, повреждающая состояние объекта, места ее поиска ограничены только тем кодом, который является частью реализации. Это существенно облегчает поиск проблем и обслуживание программы.
Упражнения раздела 7.2Упражнение 7.16. Каковы ограничения (если они есть) на количество спецификаторов доступа в определении класса? Какие виды членов должны быть определены после спецификатора public? Какие после спецификатора private?
Упражнение 7.17. Каковы различия (если они есть) между ключевыми словами class и struct?
Упражнение 7.18. Что такое инкапсуляция? Чем она полезна?
Упражнение 7.19. Укажите, какие члены класса Person имеет смысл объявить как public, а какие как private. Объясните свой выбор.
7.2.1. Друзья
Теперь, когда переменные-члены класса Sales_data стали закрытыми, функции read(), print() и add() перестали компилироваться. Проблема в том, что хоть эти функции и являются частью интерфейса класса Sales_data, его членами они не являются.
Класс может позволить другому классу или функции получить доступ к своим не открытым членам, установив для них дружественные отношения (friend). Класс объявляет функцию дружественной, включив ее объявление с предваряющим ключевым словом friend:
class Sales_data {
// добавлены объявления дружественных функций, не являющихся
// членами класса Sales_data
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
// другие члены и спецификаторы доступа, как прежде
public:
Sales_data() = default;
Sales data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue (p*n) { }
Sales_data(const std::string &s): bookNo(s) { }
Sales_data(std::istream&);
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales data&);
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// объявления частей, не являющихся членами интерфейса
// класса Sales_data
Sales_data add(const Sales_data&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
Объявления друзей могут располагаться только в определении класса; использоваться они могут в классе повсюду. Друзья не являются членами класса и не подчиняются спецификаторам доступа раздела, в котором они объявлены. Более подробная информация о дружественных отношениях приведена в разделе 7.3.4.
Объявления друзей имеет смысл группировать в начале или в конце определения класса.
Хотя пользовательский код не должен изменяться при изменении определения класса, файлы исходного кода, использующие этот класс, следует перекомпилировать при каждом изменении класса.
Объявление дружественных отношенийОбъявление дружественных отношений устанавливает только право доступа. Это не объявление функции. Если необходимо, чтобы пользователи класса были в состоянии вызвать дружественную функцию, ее следует также объявить.
Чтобы сделать друзей класса видимыми его пользователям, их обычно объявляют вне класса в том же заголовке, что и сам класс. Таким образом, в заголовке Sales_data следует предоставить отдельные объявления (кроме объявлений дружественными в теле класса) для функций read(), print() и add().
Многие компиляторы не выполняют правило, согласно которому дружественные функции должны быть объявлены вне класса, прежде чем они будут применены.
Некоторые компиляторы позволяют вызвать дружественную функцию, когда для нее нет обычного объявления. Даже если ваш компилятор позволяет такие вызовы, имеет смысл предоставлять отдельные объявления для дружественных функций. Так не придется переделывать весь код, если вы перейдете на компилятор, который выполняет это правило.
Упражнения раздела 7.2.1Упражнение 7.20. Когда полезны дружественные отношения? Укажите преимущества и недостатки их использования.
Упражнение 7.21. Измените свой класс Sales_data так, чтобы скрыть его реализацию. Написанные вами программы, которые использовали операции класса Sales_data, должны продолжить работать. Перекомпилируйте эти программы с новым определением класса, чтобы проверить, остались ли они работоспособными.
Упражнение 7.22. Измените свой класс Person так, чтобы скрыть его реализацию.
7.3. Дополнительные средства класса
Хотя класс Sales_data довольно прост, он все же позволил исследовать немало средств поддержки классов. В этом разделе рассматриваются некоторые из дополнительных средств, связанных с классом, которые класс Sales_data не будет использовать. К этим средствам относятся типы-члены (type member), внутриклассовые инициализаторы для типов-членов класса, изменяемые переменные-члены, встраиваемые функции-члены, функции-члены, возвращающие *this, а также подробности определения и использования типов класса и дружественных классов.
7.3.1. Снова о членах класса
Для исследования некоторых из дополнительных средств определим пару взаимодействующих классов по имени Screen и Window_mgr.
Определение типов-членовКласс Screen представляет окно на экране. У каждого объекта класса Screen есть переменная-член типа string, хранящая содержимое окна и три переменные-члена типа string::size_type, представляющие позицию курсора, высоту и ширину окна.
Кроме переменных и функций-членов, класс может определять собственные локальные имена таких типов. Определенные классом имена типов подчиняются тем же правилам доступа, что и любой другой его член, и могут быть открытыми или закрытыми:
class Screen {
public:
typedef std::string::size_type pos;