Программирование

Автор работы: Пользователь скрыл имя, 28 Марта 2012 в 11:49, курс лекций

Описание

Лекции по дисциплине "Программирование"

Содержание

Лекция 1. Состав языка Типы данных Переменные и операции

Лекция 2. Линейные программы

Лекция 3. Простейшие операторы. Операторы ветвления

Лекция 4. Операторы цикла и передачи управления

Лекция 5. Обработка исключительных ситуаций

Лекция 6. Классы: основные понятия Описание класса

Лекция 7. Параметры методов

Лекция 8. Конструкторы и свойства

Лекция 9. . Массивы

Лекция 10. Символы и строки

Лекция 11 Дополнительные возможности методов. Индексаторы

Лекция 12. Операции класса. Деструкторы

Лекция 13. Наследование классов

Лекция 14. Интерфейсы

Лекция 15. Стандартные интерфейсы .NET

Лекция 16. Структуры и перечисления

Лекция 17. Делегаты

Лекция 18. События

Работа состоит из  1 файл

Лекции C#.doc

— 847.00 Кб (Скачать документ)

Попробуем описать массив объектов базового класса и занести туда объекты производного класса. В листинге 15.2 в массиве типа Monster хранятся два объекта типа Monster и один — типа Daemon.

Листинг 15.2. Массив объектов разных типов

using System;

namespace ConsoleApplication1

{

    class Monster

    {

        ...

    }

   

    class Daemon : Monster

    {

        ... //

    }

 

    class Class1

    {   static void Main()

        {

            const int n = 3;

            Monster[] stado = new Monster[n];

 

            stado[0] = new Monster( "Monia" );

            stado[1] = new Monster( "Monk" );

            stado[2] = new Daemon ( "Dimon", 3 );

 

            foreach ( Monster elem in stado ) elem.Passport();          // 1

 

            for ( int i = 0; i < n; ++i ) stado[i].Ammo = 0;            // 2

            Console.WriteLine();

 

            foreach ( Monster elem in stado ) elem.Passport();          // 3

        }

    }

}

 

Результат работы программы:

Monster Monia    health = 100 ammo = 100

Monster Monk     health = 100 ammo = 100

Monster Dimon    health = 100 ammo = 100

 

Monster Monia    health = 100 ammo = 0

Monster Monk     health = 100 ammo = 0

Monster Dimon    health = 100 ammo = 0

 

Результат радует нас только частично: объект типа Daemon действительно можно поместить в массив, состоящий из элементов типа Monster, но для него вызываются только методы и свойства, унаследованные от предка. Это устраивает нас в операторе 2, а в операторах 1 и 3 хотелось бы, чтобы вызывался метод Passport, переопределенный в потомке.

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

Это и понятно: ведь компилятор должен еще до выполнения программы решить, какой метод вызывать, и вставить в код фрагмент, передающий управление на этот метод (этот процесс называется ранним связыванием). При этом компилятор может руководствоваться только типом переменной, для которой вызывается метод или свойство (например, stado[i].Ammo). То, что в этой переменной в разные моменты времени могут находиться ссылки на объекты разных типов, компилятор учесть не может.

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

Виртуальные методы

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

Следовательно, надо каким-то образом дать знать компилятору, что эти методы будут обрабатываться по-другому. Для этого в C# существует ключевое слово virtual. Оно записывается в заголовке метода базового класса, например:

virtual public void Passport() ...

 

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

Для его реализации необходимо, чтобы адреса виртуальных методов хранились там, где ими можно будет в любой момент воспользоваться, поэтому компилятор формирует для этих методов таблицу виртуальных методов (Virtual Method Table, VMT). В нее записываются адреса виртуальных методов (в том числе унаследованных) в порядке описания в классе. Для каждого класса создается одна таблица.

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

Если в производном классе требуется переопределить виртуальный метод, используется ключевое слово override, например:

override public void Passport() ...

 

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

Добавим в листинг 8.2 два волшебных слова — virtual и override — в описания методов Passport соответственно базового и производного классов (листинг 15.3).

Листинг 15.3. Виртуальные методы

using System;

namespace ConsoleApplication1

{

    class Monster

    {

        ...

        virtual public void Passport()

        {

            Console.WriteLine( "Monster {0} \t health = {1} ammo = {2}",

                              name, health, ammo );

        }

        ...

    }

       class Daemon : Monster

    {

        ...

        override public void Passport()

        {

            Console.WriteLine(

                "Daemon {0} \t health = {1} ammo = {2} brain = {3}",

                Name, Health, Ammo, brain );

        }

        ...

    }

     class Class1

    {   static void Main()

        {

            const int n = 3;

            Monster[] stado = new Monster[n];

 

            stado[0] = new Monster( "Monia" );

            stado[1] = new Monster( "Monk" );

            stado[2] = new Daemon ( "Dimon", 3 );

             foreach ( Monster elem in stado ) elem.Passport();

             for ( int i = 0; i < n; ++i ) stado[i].Ammo = 0;

            Console.WriteLine();

 

            foreach ( Monster elem in stado ) elem.Passport();

        }

    }

}

 

Результат работы программы:

Monster Monia    health = 100 ammo = 100

Monster Monk     health = 100 ammo = 100

Daemon Dimon     health = 100 ammo = 100 brain = 3

 

Monster Monia    health = 100 ammo = 0

Monster Monk     health = 100 ammo = 0

Daemon Dimon     health = 100 ammo = 0 brain = 3

 

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

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

С помощью виртуальных методов реализуется один из основных принципов объектно-ориентированного программирования — полиморфизм. Это слово в переводе с греческого означает «много форм», что в данном случае означает «один вызов — много методов».

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

Абстрактные классы

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

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

Абстрактный класс задает интерфейс для всей иерархии, при этом методам класса может не соответствовать никаких конкретных действий. В этом случае методы имеют пустое тело и объявляются со спецификатором abstract.

Если в классе есть хотя бы один абстрактный метод, весь класс также должен быть описан как абстрактный, например:

abstract class Spirit

{

    public abstract void Passport();

}

class Monster : Spirit

{

    ...

    override public void Passport()

    {

        Console.WriteLine( "Monster {0} \t health = {1} ammo = {2}",

                          name, health, ammo );

    }

    ...

}

 

class Daemon : Monster

{

    ...

    override public void Passport()

    {

        Console.WriteLine(

            "Daemon {0} \t health = {1} ammo = {2} brain = {3}",

            Name, Health, Ammo, brain );

    }

    ... // полный текст этих классов приведен в главе 12

}

 

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

Бесплодные классы

В C# есть ключевое слово sealed, позволяющее описать класс, от которого, в противоположность абстрактному, наследовать запрещается:

    sealed class Spirit

    {

        ...

    }

//  class Monster : Spirit { ... }           ошибка!

 

Большинство встроенных типов данных описано как sealed. Если необходимо использовать функциональность бесплодного класса, применяется не наследование, а вложение, или включение: в классе описывается поле соответствующего типа.

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

Класс object

Корневой класс System.Object всей иерархии объектов .NET, называемый в C# object, обеспечивает всех наследников несколькими важными методами. Производные классы могут использовать эти методы непосредственно или переопределять их.

Класс object часто используется и непосредственно при описании типа параметров методов для придания им общности, а также для хранения ссылок на объекты различного типа — таким образом реализуется полиморфизм.

Открытые методы класса System.Object перечислены ниже.

                    Метод Equals с одним параметром возвращает значение true, если параметр и вызывающий объект ссылаются на одну и ту же область памяти. Синтаксис:

    public virtual bool Equals(object obj);

 

                    Метод Equals с двумя параметрами возвращает значение true, если оба параметра ссылаются на одну и ту же область памяти. Синтаксис:

    public static bool Equals(object ob1, object ob2);

 

                    Метод GetHashCode формирует хэш-код объекта и возвращает число, однозначно идентифицирующее объект. Это число используется в различных структурах и алгоритмах библиотеки. Если переопределяется метод Equals, необходимо перегрузить и метод GetHashCode. Подробнее о хэш-кодах рассказывается в разделе «Абстрактные структуры данных» (см. с. <$Rhash>). Синтаксис:

    public virtual int GetHashCode();

 

                    Метод GetType возвращает текущий полиморфный тип объекта, то есть не тип ссылки, а тип объекта, на который она в данный момент указывает. Возвращаемое значение имеет тип Type. Это абстрактный базовый класс иерархии, использующийся для получения информации о типах во время выполнения. Синтаксис:

    public Type GetType();

 

                    Метод ReferenceEquals возвращает значение true, если оба параметра ссылаются на одну и ту же область памяти. Синтаксис:

    public static bool(object ob1, object ob2);

 

                    Метод ToString по умолчанию возвращает для ссылочных типов полное имя класса в виде строки, а для значимых — значение величины, преобразованное в строку. Этот метод переопределяют для того, чтобы можно было выводить информацию о состоянии объекта. Синтаксис:

    public virtual string ToString()

 

В производных объектах эти методы часто переопределяют. Например, можно переопределить метод Equals для того, чтобы задать собственные критерии сравнения объектов.

Пример применения и переопределения методов класса object для класса Monster приведен в листинге 15.4.

Листинг 15.4. Перегрузка методов класса object

using System;

namespace ConsoleApplication1

{

    class Monster

    {

        public Monster( int health, int ammo, string name )

        {

            this.health = health;

            this.ammo   = ammo;

            this.name   = name;

        }

 

        public override bool Equals( object obj )

        {

            if ( obj == null || GetType() != obj.GetType() ) return false;

 

            Monster temp = (Monster) obj;

            return  health == temp.health &&

                    ammo   == temp.ammo   &&

                    name   == temp.name;

        }

 

        public override int GetHashCode()

        {

            return name.GetHashCode();

        }

 

        public override string ToString()

        {

            return string.Format( "Monster {0} \t health = {1} ammo = {2}",

                    name, health, ammo );

        }

 

        string name;

        int health, ammo;

    }

   

    class Class1

    {   static void Main()

        {

            Monster X = new Monster( 80, 80, "Вася" );

            Monster Y = new Monster( 80, 80, "Вася" );

            Monster Z = X;

       

            if ( X == Y ) Console.WriteLine(" X == Y ");

            else          Console.WriteLine(" X != Y ");

 

            if ( X == Z ) Console.WriteLine(" X == Z ");

            else          Console.WriteLine(" X != Z ");

 

            if ( X.Equals(Y) ) Console.WriteLine( " X Equals Y " );

            else               Console.WriteLine( " X not Equals Y " );

 

            Console.WriteLine(X.GetType());

        }

    }

}

 

Результат работы программы:

X != Y

X == Z

X Equals Y

ConsoleApplication1.Monster

 

Анализируя результат работы программы, можно увидеть, что в операции сравнения на равенство сравниваваются ссылки, а в перегруженном методе Equals — значения. Для концептуального единства можно переопределить и операции отношения.

 

Лекция 14. Интерфейсы

Синтаксис интерфейса

Интерфейс является «крайним случаем» абстрактного класса. В нем задается набор абстрактных методов, свойств и индексаторов, которые должны быть реализованы в производных классах. Иными словами, интерфейс определяет поведение, которое поддерживается реализующими этот интерфейс классами. Основная идея использования интерфейса состоит в том, чтобы к объектам таких классов можно было обращаться одинаковым образом.

Каждый класс может определять элементы интерфейса по-своему. Так достигается полиморфизм: объекты разных классов по-разному реагируют на вызовы одного и того же метода.

Информация о работе Программирование