Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шпоры информатика 2 сем.doc
Скачиваний:
18
Добавлен:
24.09.2019
Размер:
430.08 Кб
Скачать

Перегрузка операторов

В С# можно выполнить перегрузку операций для объектов класса, то есть с помощью знаков операций +, -, * и так далее можно определить похожие действия для абстрактных типов данных. Перегрузка операторов – это переопределение стандартных операторов языка для нестандартных типов данных (например, для классов). Формат перегрузки двуместной операции имеет вид

Тип_доступа тип_возвращаемого_значения operator @ (операнд1, операнд2) {тело_операции}, где @ – знак операции. Используется перегруженный знак так же, как для стандартных типов данных. Формат перегрузки одноместной операции имеет вид: Тип_доступа тип_возвращаемого_значения operator @ (операнд) {тело_операции}

1. При перегрузке операции, как член-функции класса, двуместная операция имеет два аргумента, одноместная – один;

2. Знак одноместной операции может быть перегружен только как одноместный, а двуместной – только как двуместный;

3. Наряду с обычным использованием перегруженного знака

obj1 @ obj2 для двуместной

и @ obj для одноместной

он может использоваться как член-функция класса operator @(obj1,obj2) и operator @(obj)

4. Полный список операторов, которые можно перегрузить:

Унарные операторы: +, -, !, ~, ++, --, true, false, Бинарные операторы: +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, <=.

Индексатор Индексатор – это перегрузка [].

При использовании индексатора возможны 2 случая: присвоение значения (set), если вызов оператора [] происходит в левой части арифметического выражения, и получение значения (get) , если вызов оператора [] происходит в правой части арифметического выражения или в качестве аргумента функции или процедуры. Каждый случай программируется (перегружается) отдельно.

Базовый и порожденные классы Пусть класс A – базовый, класс B – порожденный. Говорят, что класс B наследует все член-данные и член-функции класса A независимо от того, в какой части базового класса они находятся. Таким образом, класс B – это расширенный класс A.

Но это наследование имеет особенность такого рода: член-функции класса B не имеют прямого доступа к член-данным из части private базового класса.

Наследование понимается как возможность доступа член-функциями класса B к приватным данным базового класса A косвенно, то есть через член-функции класса A из части public.

Можно разрешить член-функциям порожденного класса использовать член-данные базового класса непосредственно. Таким данным присваивается тип доступа protected (зашищенный).

В этом случае член-функциям класса B (только им и наследникам класса B) разрешен прямой доступ к член-данным из части protected класса A.

Наследование порожденным классом свойств базового имеет следующие ограничения – наследованию не подлежат:

конструкторы;

операции new и ‘=’, перегруженные в базовом классе;

отношения дружественности.

Каждый класс должен позаботиться о создании собственных конструкторов. Он не может в этом вопросе полагаться на родителя, т.к. он добавляет собственные поля, о которых родитель ничего не может знать.

Если не задать конструкторов класса, то будет добавлен конструктор по умолчанию, инициализирующий все поля значениями по умолчанию, как это мы видели в предыдущем примере. Но это редкая ситуация. Чаще всего, класс создает собственные конструкторы и, как правило, не один, задавая разные варианты инициализации полей.

При создании конструкторов классов потомков есть одна важная особенность. Всякий конструктор создает объект класса - структуру, содержащую поля класса. Но потомок, прежде чем создать собственный объект, вызывает конструктор родителя, создавая родительский объект, который затем будет дополнен полями потомка. Ввиду транзитивности этого процесса, конструктор родителя вызывает конструктор своего родителя, и этот процесс продолжается, пока первым делом не будет создан объект прародителя.

Вызов конструктора родителя происходит не в теле конструктора, а в заголовке, пока еще не создан объект класса. Для вызова конструктора используется ключевое слово base, именующее родительский класс.

class Boss: Worker

{

protected int numOfWorkers;

public Boss(int age, string name, int numOfWorkers): base (int age, string name)

{

this. numOfWorkers = numOfWorkers;

}

}  

Для конструктора без аргументов вызов аналогичного конструктора родителя подразумевается по умолчанию.

Для конструкторов с аргументами вызов конструктора с аргументами родительского класса должен быть явным. Этот вызов синтаксически следует сразу за списком аргументов конструктора, будучи отделен от этого списка символом двоеточия.

Конструктору потомка передаются все аргументы, необходимые для инициализации полей, часть из которых передаются конструктору родителя для инициализации родительских полей.

Вызов конструктора - потомка приводит к цепочке вызовов конструкторов - предков, заканчивающейся вызовом конструктора прародителя. Затем в обратном порядке создаются объекты, начиная с объекта прародителя, и выполняются тела соответствующих конструкторов, инициализирующие поля и выполняющие другую работу этих конструкторов. Последним создается объект потомка и выполняется тело конструктора потомка.

Потомок может создать новый собственный метод с именем, отличным от имен наследуемых методов. В этом случае никаких особенностей нет.

В отличие от неизменяемых полей классов - предков, класс - потомок может изменять наследуемые им методы. Если потомок создает метод с именем, совпадающим с именем метода предков, то возможны три ситуации:

перегрузка метода. Она возникает, когда сигнатура создаваемого метода отличается от сигнатуры наследуемых методов предков. В этом случае в классе потомка будет несколько перегруженных методов с одним именем, и вызов нужного метода определяется обычными правилами перегрузки методов;

переопределение метода. Метод родителя в этом случае должен иметь модификатор virtual или abstract. Эта наиболее интересная ситуация подробно будет рассмотрена в следующих разделах нашей лекции. При переопределении сохраняется сигнатура и модификаторы доступа наследуемого метода;

скрытие метода. Если родительский метод не является виртуальным или абстрактным, то потомок может создать новый метод с тем же именем и той же сигнатурой, скрыв родительский метод в данном контексте. При вызове метода предпочтение будет отдаваться методу потомка, а имя наследуемого метода будет скрыто. Это не означает, что оно становится недоступным. Скрытый родительский метод всегда может быть вызван, если при вызове уточнить ключевым словом base имя метода.

Абстрактные классы Класс называется абстрактным, если он имеет хотя бы один абстрактный метод.

Метод называется абстрактным, если при определении метода задана его сигнатура, но не задана реализация метода.

Объявление абстрактных методов и абстрактных классов должно сопровождаться модификатором abstract.

Поскольку абстрактные классы не являются полностью определенными классами, то нельзя создавать объекты абстрактных классов.

Абстрактные классы могут иметь потомков, частично или полностью реализующих абстрактные методы родительского класса.

Абстрактный метод чаще всего рассматривается как виртуальный метод. Нельзя создавать экземпляры абстрактного класса - такой класс может использоваться только в качестве базового класса для других классов.

Для потомка такого класса есть две возможности - или он реализует все абстрактные методы базового класса (и в этом случае для такого класса-потомка мы сможем создавать его экземпляры), или он реализует не все абстрактные методы базового класса (в этом случае он является тоже абстрактным классом и единственная возможность его использования - это производить от него классы-потомки).

Переопределение абстрактного метода в производном классе происходит с помощью ключевого слова ovеrride.

abstract class Figure

{

//Площадь фигуры

public abstract double square();

public abstract double perimeter();

}

class Rectangle: Figure

{

double a, b; //Стороны

//Конструктор

public Rectangle(double a, double b)

{

this.a=a;

this.b=b;

}

public override double square()

{

return a*b;

}

public override double perimeter()

{

return (a+b)*2;

}

}

Иерархии порождения Порожденный класс, в свою очередь, может быть базовым для другого класса.

Рассмотрим простое наследование. Пусть в каждом классе есть функция с одинаковым именем F( ) в части public. Имеется определение D d; и вызов d.F( ); Из какого класса будет работать функция F( )?

Действует правило доминирования: одинаковое имя в порожденном классе подавляет имя в базовом.

То есть в нашем примере будет работать функция D::F( ).

Таким образом, дерево наследования при поиске функции просматривается снизу вверх и находится ближайшая к классу D.

Итак, классы программной системы находятся в определенных отношениях друг с другом.

Кроме отношения наследования выделяют отношение вложенности (встраивания). По-другому говорят отношение "клиенты и поставщики", или клиентские отношения.

Классы A и В находятся в отношении вложенности, если одним из полей класса В является объект класса А. Класс А называется поставщиком класса В, класс В называется клиентом класса А.

Заметим сразу, что помимо вложенности поля, могут существовать и другие способы взаимодействия двух классов, связывающие их отношением "клиент - поставщик".

Как проектировать класс Тщательно спроектировать поля класса, сделав их закрытыми для клиентов.

Продумать стратегии доступа к закрытым полям класса, реализовав их в виде методов свойств.

Тщательно спроектировать набор конструкторов класса.

Тщательно спроектировать интерфейс класса - набор открытых методов, доступных клиентам класса и позволяющих выполнять различные операции над объектами класса.

При необходимости добавить в класс перегрузку операций, что позволит клиентам класса строить выражения над объектами, используя привычные знаки операций и скобки.

Добавить в класс перегруженные операции отношения, позволяющие выполнять сравнение объектов на равенство и больше - меньше.

Переопределить методы, наследованные от родительского класса.

Описать исключительные ситуации, что позволит распознать и организовать обработку возникающих ситуаций.

Форматный вывод При обсуждении процедур ввода/вывода методы Console.Write и Console.WriteLine следует иметь ввиду, что независимо от типа выводимого значения, в конечном результате выводится СИМВОЛЬНОЕ ПРЕДСТАВЛЕНИЕ значения.

Выводимая символьная строка может содержать Escape-последовательности, которые при выводе информации в окно представления позволяют создавать различные "специальные эффекты". C - Отображение значения как валюты с использованием принятого по соглашению символа

D- Отображение значения как десятичное целое

X- Отображение значения в шестнадцатеричной нотации

Непосредственно за символом форматирования может быть расположена целочисленная ограничительная константа, которая, в зависимости от типа выводимого значения, может определять количество выводимых знаков после точки, либо общее количество выводимых символов.

При этом дробная часть действительных значений округляется или дополняется нулями справа. При выводе целочисленных значений ограничительная константа игнорируется, если количество преобразуемых символов превышает ее значение. В противном случае преобразуемое значение слева дополняется нулями.

При выводе на экран строк в C# можно использовать управляющие последовательности, позволяющие управлять процессом вывода текстовой информации на экран. Основной сферой применения этих последовательностей является вывод тех символов, которые невозможно вывести на экран при помощи обычной стандартной строки, заключенной в кавычки.

Кроме управляющих последовательностей в C# предусмотрен специальный префикс @, предназначенный для дословного вывода строки вне зависимости от наличия в ней управляющих символов. Можно сказать, что этот префикс отключает присутствующие в строке управляющие символы.

\n - Начало новой строки

\t - Горизонтальный символ табуляции

Оператор foreach Применяется для перебора элементов массива. Синтаксис:

foreach ( тип имя in имя_массива ) тело_цикла

Имя задает локальную по отношению к циклу переменную, которая будет по очереди принимать все значения из массива, например:

int[] massiv = { 24, 50, 18, 3, 16, -7, 9, -1 };

foreach ( int x in massiv ) Console.WriteLine( x );

Работа с файловой системой С#-программы выполняют операции ввода-вывода посредством потоков, которые построены на иерархии классов.

Поток (stream) - это абстракция, которая генерирует и принимает данные.

С помощью потока можно читать данные из различных источников (клавиатура, файл) и записывать в различные источники (принтер, экран, файл).

Несмотря на то, что потоки связываются с различными физическими устройствами, характер поведения всех потоков одинаков. Поэтому классы и методы ввода-вывода можно применить ко многим типам устройств.

Центральную часть потоковой С#-системы занимает класс Stream пространства имен System.IO. Класс Stream представляет байтовый поток и является базовым для всех остальных потоковых классов. Из класса Stream выведены такие байтовые классы потоков как:

FileStream - байтовый поток, разработанный для файлового ввода-вывода

BufferedStream - заключает в оболочку байтовый поток и добавляет буферизацию, которая во многих случаях увеличивает производительность программы;

MemoryStream - байтовый поток, который использует память для хранения данных.

Рассмотрим класс FileStream, классы StreamWriter и StreamReader, позволяющие преобразовывать байтовые потоки в символьные.

В классе определено несколько конструкторов. Чаще всего используется конструктор, который открывает поток для чтения и/или записи:

FileStream(string filename, FileMode mode)

где:

параметр filename определяет имя файла, с которым будет связан поток ввода-вывода данных; при этом filename определяет либо полный путь к файлу, либо имя файла, который находится в папке bin/debug вашего проекта.

параметр mode определяет режим открытия файла, который может принимать одно из возможных значений, определенных перечислением FileMode:

FileMode.Append - добавляет данные в конец файла;

FileMode.Create - создает новый файл, при этом если существует файл с таким же именем, то он будет предварительно удален;

FileMode.CreateNew создает новый файл, при этом файл с таким же именем не должен существовать;

FileMоde.Open – открывает существующий файл;

FileMode.ОpenOrCreate - если файл существует, то открывает его, в противном случае создает новый;

FileMode.Truncate – открывает и обнуляет существующий файл.

Если попытка открыть файл оказалась неуспешной, то генерируется одно из исключений: FileNotFoundException - файл невозможно открыть по причине его отсутствия,