1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 47
Текст из файла (страница 47)
Но в целях структурированности ичитабельности программного кода создаваемый перегруженный оператор должен повозможности отражать исходное назначение того или иного оператора. Например, оператор“+”, перегруженный для класса ThreeD, концептуально подобен оператору “+”,определенному для целочисленных типов. Ведь вряд ли есть логика в определении длякласса, например, оператора “+”, который по своему действию больше напоминаетоператор деления (/). Таким образом, основная идея создания перегруженного оператора —наделить его новыми (нужными для вас) возможностями, которые тем не менее связаны сего первоначальным назначением.250Часть I.
Язык C#На перегрузку операторов налагается ряд ограничений. Нельзя изменять приоритетоператора. Нельзя изменять количество операндов, принимаемых оператором, хотяоператорный метод мог бы игнорировать любой операнд. Некоторые операторы вообщенельзя перегружать. Например, нельзя перегружать какие бы то ни было операторыприсваивания (включая составные, например “+=”). Ниже перечислены остальныеоператоры, перегрузка которых запрещена.&&||[]()newissizeoftypeof?->.=Несмотря на то что нельзя перегружать оператор приведения типов (()) в явномвиде, можно создать операторы преобразования, которые, как было показано выше,успешно выполняют это.Может показаться серьезным ограничением запрещение перегрузки таких составныхоператоров присваивания, как “+=”.
Если вы определили оператор, который используется всоставном операторе присваивания, будет вызван соответствующий перегруженныйоператорный метод. Таким образом, использование “+=” в программе автоматическивызывает вашу версию метода operator+(). Например, возьмем снова класс ThreeD.При использовании фрагмента кодаThreeD а = new ThreeD(1, 2, 3);ThreeD b = new ThreeD(10, 10, 10);b += a; // Суммируем а и b.автоматически вызывается метод operator+() класса ThreeD, в результате чего объектb будет содержать координаты 11,12,13.И еще.
Несмотря на то что нельзя перегружать оператор доступа к элементам массивапо индексу ([]), используя операторный метод (operator()), можно создаватьиндексаторы, которые описаны в следующей главе.Еще один пример перегрузки операторовНа протяжении всей этой главы для демонстрации перегрузки операторов мыиспользовали класс ThreeD. Но прежде чем завершить эту главу, хотелось бы рассмотретьеще один пример. Несмотря на то что основные принципы перегрузки операторов независят от используемого класса, следующий пример поможет продемонстрировать мощьперегрузки операторов, особенно в случаях, связанных с расширяемостью типов.В этом примере программы разрабатывается четырехразрядный целочисленный типданных, для которого определяется ряд операций.
Возможно, вам известно, что на зарекомпьютерной эры четырехразрядный тип был широко распространен, поскольку онпредставлял половину байта. Этот тип также позволял хранить одну шестнадцатеричнуюцифру. Так как четыре бита составляют половину байта, эту полубайтовую величину частоназывали nybble. В период всеобщего распространения компьютеров с передней панелью,ввод данных в которые осуществлялся порциями объемом в 1 nybble, программистыпривыкли оперировать величинами такого размера.
Несмотря на то что полубайт нынчеутратил былую популярность, этот четырехразрядный тип представляет интерес в качестведополнения к другим целочисленным типам данных. По традиции nybble-значениерассматривается как значение без знака.В следующем примере используется класс Nybble, в котором реализуетсяполубайтовый тип данных. Он основан на встроенном типе int, но ограничиваетдопустимые для хранения значения диапазоном 0—15. В этом классе определяютсяследующие операторы:■ сложение Nybble-объекта с Nybble-объектом;Глава 9.
Перегрузка операторов251■ сложение int-значения с Nybble-объектом;■ сложение Nybble-объекта с int-значением;■ больше (>) и меньше (<);■ оператор инкремента;■ преобразование int-значения в Nybble-объект;■ преобразование Nybble-объекта в int-значение.Этих операций вполне достаточно, чтобы показать, насколько полно тип классаможет быть интегрирован с системой типов C#.
Однако для полноты реализации типаNybble, необходимо определить остальные операции. Это предлагается сделать читателюв качестве упражнения.Ниже приведен код класса Nybble, а также класса NybbleDemo, который позволяетпродемонстрировать его использование.// Создание 4-битового типа с именем Nybble.using System;// 4-битовый тип.class Nybble {int val; // Основа типа - встроенный тип int.public Nybble() {val = 0;}public Nybble(int i) {val = i;val = val & 0xF; // Выделяем младшие 4 бита.}// Перегружаем бинарный оператор "+" для// сложения Nybble + Nybble.public static Nybble operator +(Nybble op1, Nybble op2){Nybble result = new Nybble();result.val = op1.val + op2.val;result.val = result.val & 0xF;// Оставляем младшие// 4 бита.return result;}// Перегружаем бинарный оператор "+" для// сложения Nybble + int.public static Nybble operator +(Nybble op1, int op2){Nybble result = new Nybble();result.val = op1.val + op2;result.val = result.val & 0xF;// Оставляем младшие// 4 бита.return result;}252Часть I.
Язык C#// Перегружаем бинарный оператор "+" для// сложения int + Nybble.public static Nybble operator +(int op1, Nybble op2){Nybble result = new Nybble();result.val = op1 + op2.val;result.val = result.val & 0xF;// Оставляем младшие// 4 бита.return result;}// Перегружаем оператор "++".public static Nybble operator ++(Nybble op){op.val++;op.val = op.val & 0xF;// Оставляем младшие// 4 бита.return op;}// Перегружаем оператор ">".public static bool operator >(Nybble op1, Nybble op2){if(op1.val > op2.val)return true;elsereturn false;}// Перегружаем оператор "<".public static bool operator <(Nybble op1, Nybble op2){if(op1.val < op2.val)return true;elsereturn false;}// Преобразование Nybble-объекта в значение типа int.public static implicit operator int(Nybble op){return op.val;}// Преобразование int-значения в Nybble-объект.public static implicit operator Nybble(int op) {return new Nybble(op);}}class NybbleDemo {public static void Main() {Nybble a = new Nybble(1);Nybble b = new Nybble(10);Nybble c = new Nybble();int t;Console.WriteLine("a: " + (int) a);Глава 9.
Перегрузка операторов253Console.WriteLine("b: " + (int) b);// Используем Nybble-объект в if-инструкции.if(a < b) Console.WriteLine("а меньше b\n");// Суммируем два Nybble-объекта.c = a + b;Console.WriteLine("с после сложения c = a + b: " + (int) c);// Суммируем int-значение с Nybble-объектом.a += 5;Console.WriteLine("а после сложения а += 5: " + (int) a);Console.WriteLine();// Используем Nybble-объект в int-выражении.t = a * 2 + 3;Console.WriteLine("Результат выражения а * 2 + 3: " + t);Console.WriteLine();// Иллюстрируем присваивание Nybble-объекту// int-значения и переполнение.a = 19;Console.WriteLine("Результат присваивания а = 19: " + (int) a);Console.WriteLine();// Используем Nybble-объект для управления циклом.Console.WriteLine("Управляем for-циклом с помощью Nybble-объекта.");}}for(a = 0; a < 10; a++)Console.Write((int) a + " ");Console.WriteLine();При выполнении эта программа генерирует следующие результаты:a: 1b: 10а меньше bс после сложения c = а + b: 11а после сложения а += 5: 6Результат выражения а * 2 + 3: 15Результат присваивания а = 19: 3Управляем for-циклом с помощью Nybble-объекта.
0123456789Несмотря на то что большинство реализованных здесь операций не нуждается вдополнительных комментариях, имеет смысл остановиться вот на чем. Операторыпреобразования играют немаловажную роль в интеграции типа Nybble в систему типов254Часть I. Язык C#C#. Поскольку в этом классе определены преобразования как из Nybble-объекта в intзначение, так из int-значения в Nybble-объект, то Nybble-объекты можно свободносмешивать в арифметических выражениях.
Рассмотрим, например, такое выражение из этойпрограммы:t = а * 2 + 3;Здесь переменная t имеет тип int, а переменная а представляет собой объект классаNybble. Тем не менее эти два типа совместимы в одном выражении благодаря операторунеявного преобразования Nybble-объекта в int-значение. В данном случае, посколькуостальная часть выражения имеет тип int, объект а преобразуется в int-значениепосредством вызова соответствующего метода преобразования.Преобразование int-значения в Nybble-объект позволяет присваивать Nybbleобъекту int-значение. Например, инструкцияа = 19;работает следующим образом.
Сначала выполняется оператор преобразования intзначения в Nybble-объект. При этом создается новый Nybble-объект, который содержитмладшие четыре (двоичных) разряда числа 19 (1910 = 100112). Это приводит кпереполнению, поскольку значение 19 выходит за пределы диапазона, допустимого в классеNybble. В результате получаем число 3 (00112 = 310), которое и присваивается объекту а.Без определения операторов преобразования такие выражения были бы недопустимы.Преобразование Nybble-объекта в int-значение используется также в цикле for.Без определения этого преобразования было бы невозможно написать цикл for такимпростым способом.Глава 9.
Перегрузка операторов255Полныйсправочник поГлава 10Индексаторы и свойстваВэтой главе рассматриваются два специальных типа членов класса, которые тесносвязаны друг с другом: индексаторы и свойства. Каждый из этих типов расширяетвозможности класса, усиливая его интеграцию в систему типов языка C# и гибкость.Индексаторы обеспечивают механизм, посредством которого к объектам можно получатьдоступ по индексу подобно тому, как это реализовано в массивах.
Свойства предлагаютэффективный способ управления доступом к данным экземпляра класса. Эти типы связаныдруг с другом, поскольку оба опираются на еще одно средство C#: аксессор, или средстводоступа к данным.ИндексаторыКак вы знаете, индексация массивов реализуется с использованием оператора “[]”. Всвоих классах вы можете перегрузить его, но не прибегая к “услугам” метода operator(),а посредством создания индексатора (indexer). Индексатор позволяет обеспечитьиндексированный доступ к объекту. Главное назначение индексаторов — поддержатьсоздание специализированных массивов, на которые налагается одно или несколькоограничений. При этом индексаторы можно использовать в синтаксисе, подобномреализованному в массивах.
Индексаторы могут характеризоваться одной или несколькимиразмерностями, но мы начнем с одномерных индексаторов.Создание одномерных индексаторовОдномерный индексатор имеет следующий формат.тип_элемента this[int индекс] {// Аксессор считывания данных.get {// Возврат значения, заданного элементом индекс.}// Аксессор установки данных.set {// Установка значения, заданного// элементом индекс.}}Здесь тип_элемента — базовый тип индексатора.
Таким образом, тип_элемента— это тип каждого элемента, к которому предоставляется доступ посредством индексатора.Он соответствует базовому типу массива. Параметр индекс получает индексопрашиваемого (или устанавливаемого) элемента. Строго говоря, этот параметр необязательно должен иметь тип int, но поскольку индексаторы обычно используются дляобеспечения индексации массивов, целочисленный тип — наиболее подходящий.В теле индексатора определяются два аксессора (средства доступа) с именами get иset. Аксессор подобен методу за исключением того, что в нем отсутствует объявлениетипа возвращаемого значения и параметров.