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

Автор работы: Пользователь скрыл имя, 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 Кб (Скачать документ)

При описании операций необходимо соблюдать следующие правила:

                    операция должна быть описана как открытый статический метод класса (спецификаторы publicstatic);

                    параметры в операцию должны передаваться по значению (то есть не должны предваряться ключевыми словами ref или out);

                    сигнатуры всех операций класса должны различаться;

                    типы, используемые в операции, должны иметь не меньшие права доступа, чем сама операция (то есть должны быть доступны при использовании операции).

В C# существуют три вида операций класса: унарные, бинарные и операции преобразования типа.

Унарные операции

Можно определять в классе следующие унарные операции:

+     -     !     ~     ++     --     true     false

 

Синтаксис объявителя унарной операции:

тип operator унарная_операция ( параметр )

 

Примеры заголовков унарных операций:

public static int operator +( MyObject m )

public static MyObject operator --( MyObject m )

public static bool operator true( MyObject m )

 

Параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. Операция должна возвращать:

                    для операций +, -, ! и ~ величину любого типа;

                    для операций ++ и -- величину типа класса, для которого она определяется;

                    для операций true и false величину типа bool.

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

Бинарные операции

Можно определять в классе следующие бинарные операции:

+   -   *   /   %   &   |   ^   <<   >>   ==   !=   >   <   >=   <=

 

Синтаксис объявителя бинарной операции:

тип operator бинарная_операция (параметр1, параметр2)

 

Примеры заголовков бинарных операций:

public static MyObject operator +  ( MyObject m1, MyObject m2 )

public static bool     operator == ( MyObject m1, MyObject m2 )

 

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

Операции == и !=, > и <, >= и <= определяются только парами и обычно возвращают логическое значение. Чаще всего в классе определяют операции сравнения на равенство и неравенство для того, чтобы обеспечить сравнение объектов, а не их ссылок, как определено по умолчанию для ссылочных типов.

Сложные операции присваивания (например, +=) определять не требуется, да это и невозможно. При выполнении такой операции              автоматически вызываются сначала операция сложения, а потом присваивания.

Операции преобразования типа

Операции преобразования типа обеспечивают возможность явного и неявного преобразования между пользовательскими типами данных. Синтаксис объявителя операции преобразования типа:

implicit operator тип ( параметр )       // неявное преобразование

explicit operator тип ( параметр )       // явное преобразование

 

Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого определяется операция. Таким образом, операции выполняют преобразование либо типа класса к другому типу, либо наоборот. Преобразуемые типы не должны быть связаны отношениями наследования. Примеры операций преобразования типа для класса Monster, описанного ранее:

public static implicit operator int( Monster m )

    return m.health; 

public static explicit operator Monster( int h )

    return new Monster( h, 100, "FromInt" ); 

}

 

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

Monster Masha = new Monster( 200, 200, "Masha" );

int i = Masha;                      // неявное преобразование

Masha = (Monster) 500;              // явное преобразование

 

Неявное преобразование выполняется автоматически:

                    при присваивании объекта переменной целевого типа, как в примере;

                    при использовании объекта в выражении, содержащем переменные целевого типа;

                    при передаче объекта в метод на место параметра целевого типа;

                    при явном приведении типа.

Явное преобразование выполняется при использовании операции приведения типа.

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

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

Деструкторы

В C# существует специальный вид метода, называемый деструктором. Он вызывается сборщиком мусора непосредственно перед удалением объекта из памяти. В деструкторе описываются действия, гарантирующие корректность последующего удаления объекта, например, проверяется, все ли ресурсы, используемые объектом, освобождены (файлы закрыты, удаленное соединение разорвано и т. п.)

Синтаксис деструктора:

[ атрибуты ] [ extern ] ~имя_класса()

тело

 

Как видно из определения, деструктор не имеет параметров, не возвращает значения и не требует указания спецификаторов доступа. Его имя совпадает с именем класса и предваряется тильдой (~), символизирующей обратные по отношению к конструктору действия. Тело деструктора представляет собой блок или просто точку с запятой, если деструктор определен как внешний (extern).

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

Вложенные типы

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

Например, введем в наш класс Monster вспомогательный класс Gun. Объекты этого класса без «хозяина» бесполезны, поэтому его можно определить как внутренний:

using System;

namespace ConsoleApplication1

{   class Monster

    {

        class Gun

        {

           ...

        }

        ...

    }

}

 

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

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

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

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

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

Итак, наследование применяется для следующих взаимосвязанных целей:

                    исключения из программы повторяющихся фрагментов кода;

                    упрощения модификации программы;

                    упрощения создания новых программ на основе существующих.

Описание класса-потомка

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

[ атрибуты ] [ спецификаторы ] class имя_класса [ : предки ]

    тело класса

 

Рассмотрим наследование классов на примере. Ранее был описан класс Monster, моделирующий персонаж компьютерной игры. Допустим, нам требуется ввести в игру еще один тип персонажей, который должен обладать свойствами объекта Monster, а кроме того, уметь думать. Будет логично сделать новый объект потомком объекта Monster (листинг 15.1).

Листинг 15.1. Класс Daemon, потомок класса Monster

using System;

namespace ConsoleApplication1

{

    class Monster

    {

        ...

    }

   

    class Daemon : Monster

    {

        public Daemon()

        {

            brain = 1;

        }

 

        public Daemon( string name, int brain ) : base( name )          // 1

        {

            this.brain = brain;

        }

 

        public Daemon( int health, int ammo, string name, int brain )

            : base( health, ammo, name )                                // 2

        {

            this.brain = brain;

        }

 

        new public void Passport()                                      // 3

        {

            Console.WriteLine(

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

                Name, Health, Ammo, brain );

        }

 

        public void Think()                                             // 4

        {

            Console.Write( Name + " is" );

            for ( int i = 0; i < brain; ++i ) Console.Write( " thinking" );

            Console.WriteLine( "..." );

        }

 

        int brain;        // закрытое поле

    }

 

    class Class1

    {   static void Main()

        {

            Daemon Dima = new Daemon( "Dima", 3 );                      // 5

            Dima.Passport();                                            // 6

            Dima.Think();                                               // 7

            Dima.Health -= 10;                                          // 8

            Dima.Passport();

        }

    }

}

 

В классе Daemon введены закрытое поле brain и метод Think, определены собственные конструкторы, а также переопределен метод Passport. Все поля и свойства класса Monstr наследуются в классе Daemon.

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

Daemon Dima      health = 100 ammo = 100 brain = 3

Dima is thinking thinking thinking...

Daemon Dima      health = 90 ammo = 100 brain = 3

 

Как видите, экземпляр класса Daemon с одинаковой легкостью использует как собственные (операторы 5–7), так и унаследованные (оператор 8) элементы класса. Рассмотрим общие правила наследования.

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

                    Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор базового класса без параметров.

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

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

Поля, методы и свойства класса наследуются, поэтому при желании заменить элемент базового класса новым элементом следует явным образом указать компилятору свое намерение с помощью ключевого слова new. В листинге 15.1 таким образом переопределен метод вывода информации об объекте Passport. Метод Passport класса Daemon замещает соответствующий метод базового класса, однако возможность доступа к методу базового класса из метода производного класса сохраняется. Для этого перед вызовом метода указывается все то же волшебное слово base, например:

base.Passport();

 

Элементы базового класса, определенные как private, в производном классе недоступны. Поэтому в методе Passport для доступа к полям name, health и ammo пришлось использовать соответствующие свойства базового класса. Другое решение заключается в том, чтобы определить эти поля со спецификатором protected, в этом случае они будут доступны методам всех классов, производных от Monster. Оба решения имеют свои достоинства и недостатки.

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

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