И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 22
Текст из файла (страница 22)
Еслиже ошибки нет, то программист должен выразить свои намеренияявно:v = Vector(1);Неявные преобразования в С#. В языке C# область примененияпользовательских преобразований уже, чем в языке C++. Можноопределять свои преобразования только между двумя классами, нельзя определять преобразования в типы значений или из них.Преобразование из класса X в класс Y реализуется с помощьюспециального метода — функции преобразования:static operator Y(X х){ ...}Функция преобразования может быть только статическим методомлибо класса х, либо класса Y.Если такая функция преобразования есть, то она вызывается с использованием обычного синтаксиса преобразований: (Y) х.Компилятор вставляет неявное преобразование из X в Y только,если соответствующая функция преобразования снабжена модификатором i m p l i c i t :static implicit operator Y (X x) { ... }107Если же используется модификатор explicit, то функция преобразования может вызываться только явно.
По умолчанию принимается модификатор explicit, что снижает вероятность случайнойошибки.7.3. Инкапсуляция. Абстрактные типы данныхИнкапсуляция — это языковой механизм, позволяющий ограничить доступ к отдельным членам класса. Иногда инкапсуляцию называют упрятыванием, или защитой, информации. Инкапсуляцияпозволяет скрыть внутреннее представление объекта, обеспечиваяего целостность, за счет того, что пользователь не может «испортить»внутренние данные объекта.Например, недостатком класса Stack из подразд.
7.1 являетсято, что его данные не инкапсулированы. Поэтому можно, напримеризменить значение переменной top, присвоив ей нулевое значениеи «опустошив» тем самым весь стек. Также можно непосредственномодифицировать тело стека (массив body) и т.п.Рассмотрим, как реализована инкапсуляция в объектно-ориентированных языках программирования.Во-первых, в языках с классами возможно ограничение доступак отдельным членам класса. Единицей инкапсуляции (или единицейзащиты) является член класса. При этом правила защиты применяются единообразно ко всем членам, будь то данные, методы, специальные функции-члены, вложенные классы.Во-вторых, атомом защиты является весь класс целиком, т.
е.правила защиты применяются единообразно ко всем экземплярамкласса. Нельзя устанавливать отдельные правила доступа к экземпляру класса, отличные от правил доступа к другим экземплярамэтого класса.Инкапсуляция в языке C++. В этом языке все члены класса относятся к одной из следующих областей доступа:• public — открытая, доступная любым функциям;• protected — защищенная, доступная только собственным методам и методам производных классов;• private — закрытая, доступная только собственным методам.Члены класса, находящиеся в закрытой области (private), недоступны для использования со стороны внешних функций.
Напротив,члены класса, находящиеся в открытой секции (public), доступныдля использования из любых функций, в том числе и внешних. Приобъявлении класса каждый член класса помещается в одну из перечисленных выше областей доступа следующим образом:class имя_класса {private:108определение_закрытых_членов_классаpublic:определение_открытых_членов_классаprotected:определение защищенных_членов_класса};Порадок следования областей доступа и их количество в классепроизвольны.Ключевое слово, определяющее первую область доступа, можетотсутствовать.
Умолчание зависит от того, с какого ключевого слованачинается объявление класса.Объявление класса может начинаться с ключевого слова class(как ранее) или struct.Класс C++ отличается от структуры C++ только определением поумолчанию первой области доступа в их описании (а также определением по умолчанию способа наследования — см. подразд. 8.1):• для структур умолчанием является открытый доступ (public);• для классов умолчанием является закрытый доступ (private).Различия в умолчаниях связаны с обеспечением совместимостипрограмм на языке С и на языке C++, что позволяют рассматривать«старые» структуры в программах на С как классы C++ без функцийчленов и с открытыми данными (т.е.
«плохие», но все-таки классы).Модель управления доступом, основанная на трех уровнях доступа (только для класса, только для иерархии классов, для любыхклассов и функций), является достаточно простой. Однако эта модельиногда является слишком ограничительной, так как не позволяетразличать права доступа для внешних функций и классов. «Чужаки» (т. е. внешние классы и функции) либо все сразу имеют доступ(к открытым членам), либо все сразу не имеют доступа (к закрытыми защищенным членам). На практике выясняется, что иногда некоторые внешние классы или функции нельзя рассматривать как«чужаков».Для примера рассмотрим класс string, инкапсулирующий структуру динамической строки. Все ли операции с этим классом имеетсмысл реализовывать через его методы? Например, удобно определить операцию конкатенации (сцепления) двух строк и использоватьдля нее перегрузку стандартной операции сложения «+».Есть два варианта перегрузки операции «+».
Во-первых, можноперегрузить ее как функцию-член:class String {public:String operator + (const String & S);... / / другие члены, в том числе закрытые};109а во-вторых, можно перегрузить ее как внешнюю функцию:String operator +&S2) ;(const String &S1,const StringОбращение к операции в обоих случаях имеет видString ml, m2, m3;ml = m2 + m3; //Однако компилятор трактует вызов операции «+» по-разному:ml = m2.operator + (m3); // функция-членml = operator + (m2, m3); // внешняя функцияПри этом допустима только одна интерпретация, которую долженвыбрать программист.В первом варианте первый параметр сложения играет выделенную роль, а во втором варианте оба параметра равнозначны.
Второйвариант лучше отвечает общепринятой семантике сложения, а этоважно при перегрузке стандартных операций (если перегруженнаяоперация не отвечает общепринятой семантике, то это верный путьк ошибкам в программе).Кроме того, второй вариант более универсален, например он позволяет единообразно трактовать неявные преобразования.
Пояснимэто на примере класса S t r i n g .Предположим, что класс String имеет конструктор преобразования из вещественного типа, который переводит число в текстовоепредставление:String(double d); // слово explicit отсутствует!Такой конструктор аналогичен методу toString () в языке Java.Этот метод применим ко всем объектам данных (даже простых типов). Если контекст использования объекта требует строкового значения, то компилятор Java автоматически подставляет вызов методаtoString () для объекта. Таким образом, можно считать, что методtoString () реализует неявное преобразование объекта данныхязыка Java в строку (и это единственное неявное преобразование,допустимое в Java).
Итак, в Java можно записать:String s = "Line ";String ss = s + 6; // ss получает значение "Line 6";Однако теперь (при наличии конструктора преобразования) такой код верен и для класса String в языке C++ (и это достигнуточисто языковыми средствами). Компилятор подставляет цепочкуиз неявных преобразований, т.е. одно пользовательское, второе —стандартное:String ss = s + (String)(double)6;110Теперь рассмотрим, как перегружена операция сложения для класса String. Если она перегружена функцией-членом, то приведенныйранее пример работает корректно:String ss = s .operator+((String)(double)6);а вот перестановка операндов приводит к ошибке:String ss = 6 + s;Если же перегрузить операцию сложения внешней функцией, топреобразование будет работать корректно в обоих случаях:String ss = operator+(s, (String)(double)6);String ss = operatort((String)(double)6, s);Таким образом, можно привести общую рекомендацию: двуместные «симметричные» операции, в которых оба аргумента равноправны, лучше перегружать как внешние функции, а «асимметричные»операции с выделенным левым операндом (к такому типу относятся,например, комбинированные операции присваивания типа «+=») —функциями-членами.Однако если выбрать первый вариант, то мешает правило инкапсуляции: внешняя операция не может получить доступ к закрытымпеременным.
Это пример функции, которая, с одной стороны,должна быть внешней по отношению к классу, а с другой стороны,должна иметь доступ ко всем членам класса. Таким образом, модельязыка C++ нуждается в расширении: некоторые внешние функциидолжны иметь «привилегированный» доступ.Такое расширение реализовано в C++ посредством друзей класса.Друг класса X — это внешняя функция (глобальная либо члендругого класса), имеющая такие же права доступа к членам X, каки у членов этого класса. Понятие друга декларируется самим классом(друзей не навязывают), понятие друга не является транзитивным(друг моего друга необязательно мой друг), друзья класса не наследуются (если производный класс хочет иметь в друзьях друзей базовогокласса, то он должен явно их объявить).Объявление друга должно содержаться внутри объявления класса,объявляющего друга.
Объявление друга имеет три модификации:friend прототип_глобальной_функции;friend прототип_функции_члена_класса;friend имя_класса;Например:class X{friend void f(X&);friend void Y ::AccessX(X&);111friend class Z; // все функции-члены Z — друзья X};class String{ ...friend String operator +String &);(const String &, const};Инкапсуляция в языках C # и Java. В языках C# и Java имеются такие же уровни доступа (и такие же ключевые слова), как и вязыке C++: p u b l i c , p r o t e c t e d , p r i v a t e . Кроме того, модельуправления доступом дополнена другими уровнями, которые исключают необходимость в друзьях.Дело в том, что в C# и Java можно набор логически связанныхклассов объединить в некоторую сущность и использовать эту сущность как целое (например, распространять, импортировать из нееклассы и т.п.). Эта сущность играет роль библиотеки (классов).
Конечно, оттранслированные классы на C++ тоже можно объединятьв библиотеки, но понятие «библиотеки» на языковом уровне нет нив С, ни в C++. Приходится использовать соответствующее понятиеиз виртуального компьютера операционной системы.В языке C# роль библиотеки играет понятие сборки (assem bly),а в Java — понятие пакета (package).Исходные тексты программ как на С#, так и на Java хранятсяв текстовых файлах (расширение .cs и .java соответственно). Напомним, что программы состоят только из определений типов (классов,перечислений, интерфейсов). Принадлежность класса сборке (С#)определяется при трансляции в командной строке, вызывающейкомпилятор C# (интегрированная среда разработки сама генерируеткомандную строку из установок проекта). В Java есть специальнаяконструкция package, которая указывает, какому пакету принадлежит класс (классы) из файла программы.
Эта конструкция должнабыть первой в файле. Форма конструкции очень проста:package имя-пакета;Например:Package ru.soft-company.common.graphics;Поскольку классы из сборок (пакетов) должны быть логическисвязаны, то они имеют привилегированный доступ друг к другу посравнению с классами из внешних сборок (пакетов).В результате появляется еще один уровень доступа (промежуточный между закрытым и открытым). В языке C# этот уровеньдоступа называется внутренним (и обозначается ключевым словомinternal). В Java аналогичный уровень называется пакетным, он112является умолчательным и не имеет ключевого слова.