Методические указания по выполнению лабораторных и практических работ по мдк
Скачать 3.25 Mb.
|
Ход работы Задание. Пусть определены 3 класса, один из которых является базовым для двух других: Type 54 Tperson=class {базовый класс} fname:string; constructor Create(name:string); function info:string; virtual; end; Tstud=class(Tperson) {класс– потомок} fgr:integer; {поле для номера группы} constructor Create(name:string;gr:integer); function info:string; override; end; Tprof=class(Tperson) {класс– потомок} fdep:string; ; {поле для названия кафедры} constructor Create(name:string; dep:string); function info:string; override; end; В каждом из этих классов определен метод info. В базовом классе при помощи директивы virtual метод info объявлен виртуальным. Это дает возможность классу–потомку произвести замену виртуального метода своим собственным. В каждом классе–потомке определен свой метод info, который замещает соответствующий метод родительского класса и отмечается директивой override. Определим метод info для каждого класса индивидуально: function Tperson.info:string; begin result:=’’; end; function Tstud.info:string; begin result:=fname+' gruppa '+inttostr(fgr); end; function Tprof.info:string; 55 begin result:=fname+' department '+fdep; end; Далее в программе список всех людей можно представить массивом объектов класса Tperson. Отметим, что объект – указатель. Список людей имеет вид: list: array[1..szl] of Tperson; { szl –размер списка} Объявить подобным образом список можно потому, что OP позволяет присвоить указателю на родительский класс значение указателя на класс– потомок. Поэтому элементами массива list могут быть как объекты класса Tstud, так и объекты класса Tprof. Вывод списка можно осуществить применением метода info к элементам массива, например: St:= ’’; for i:=1 to szl do if list[i]<>nil then St:=St+list[i].info+#13; ShowMessage('Spisok:'+#13+St); { вывод в окно сообщения} Во время работы программы каждый элемент массива может содержать как объект типа Tstud, так и объект типа Tprof. Концепция полиморфизма обеспечивает применение к объекту именно того метода info, который соответствует типу объекта. Напишем программу, которая использует объявления классов Tperson, Tstud, Tprof, формирует список студентов и преподавателей и выводит полученный список в окно сообщения. Будем использовать визуальное программирование. Окно формы будет иметь вид: GroupBox1—это компонент , объединяющий группу компонентов, связанных по смыслу. В данном случае он включает 2 зависимых переключателя – RadioButton1 и RadioButton2 Текст модуля кода программы: unit Polimorfizm; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; Type TForm1 = class(TForm) Label1: TLabel; Label2: TLabel; Edit1: TEdit; Edit2: TEdit; GroupBox1: TGroupBox; RadioButton1: TRadioButton; RadioButton2: TRadioButton; 56 Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; type Tperson=class fname:string; constructor Create(name:string); function info:string; virtual; end; Tstud=class(Tperson) {класс– потомок} fgr:integer; {поле для номера группы} constructor Create(name:string;gr:integer); function info:string; override; end; Tprof=class(Tperson) {класс– потомок} fdep:string; ; {поле для названия кафедры} constructor Create(name:string; dep:string); function info:string; override; end; Сonst szl=10; Var Form1: TForm1; list:array[1..szl] of Tperson; n:integer; implementation {исполняемая часть} {$R *.DFM} constructor Tperson.Сreate(name:string); {описание конструктора} begin fname:=name; end; constructor Tstud.Create(name:string;gr:integer); begin inherited create(name); fgr:=gr; end; constructor Tprof.Create(name:string;dep:string); begin inherited create(name); fdep:=dep; end; function Tperson.info:string; begin result:=fname; end; function Tstud.info:string; begin result:=fname+' gruppa '+inttostr(fgr); end; function Tprof.info:string; begin result:=fname+' department '+fdep; 57 end; procedure TForm1.Button1Click(Sender: TObject); //процедура обработки нажатия на кнопку «Добавить» begin if n //процедура обработки нажатия на кнопку «Список» Var i:integer; St:string; Begin for i:=1 to szl do if list[i]<>nil then St:=St+list[i].info+#13; // list[i].info вызовет тот метод info, которому соответствует элемент ShowMessage('Spisok:'+#13+St); end; end. Процедура TForm1.Button1Click, которая запускается нажатием кнопки «Добавить» создает объект list[n] класса либо Tstud, либо Tprof. Класс создаваемого объекта определяется состоянием переключателя RadioButton. Установка переключателя в положение Студент определяет класс Tstud, а в положение – Преподаватель определяет класс Tprof. Процедура TForm1.Button2Click, которая запускается нажатием кнопки Список (Button2) применяет метод info к каждому элементу массива list[i] как к объекту списка и формирует строку, представляющую весь итоговый список. Виртуальность метода info обеспечивает применение к объекту именно того метода info, который соответствует типу объекта. Отметим, что в данной программе результаты выводятся в окно сообщения процедурой ShowMessage. Например: Showmessage('Spisok:'+#13+st); Showmessage('spisok zapolnen'); Практическая работа № 1.13. Определение операций в классе Цель работы: изучить способы определения операций в классе Теоретический материал Операции класса C# позволяет переопределить действие большинства операций так, чтобы при использовании с объектами конкретного класса они выполняли заданные функции. Это дает возможность применять экземпляры собственных типов данных в составе выражений таким же образом, как стандартных, например: MyObject а, Ь, с; с = а + Ь; // используется операция сложения для класса MyObject 58 Определение собственных операций класса часто называют перегрузкой опера- ций. Перегрузка обычно применяется для классов, описывающих математические или физические понятия, то есть таких классов, для которых семантика операций делает программу более понятной. Если назначение операции интуитивно не понятно с первого взгляда, перегружать такую операцию не рекомендуется. Операции класса описываются с помощью методов специального вида(функций- операций). Перегрузка операций похожа на перегрузку обычных методов. Синтаксис операции: [ атрибуты ] спецификаторы объявитель_операции тело Атрибуты рассматриваются позже, в качествеспецификаторов одновременно используются ключевые словаpublicиstatic. Кроме того, операцию можно объявить как внешнюю (extern). Объявитель операции содержит ключевое словоoperator, по которому и опознается описание операции в классе.Тело операции определяет действия, которые выполняются при использовании операции в выражении. Тело представляет собой блок, аналогичный телу других методов. Новые обозначения для собственных операций вводить нельзя. Для операций класса сохраняются количество аргументов, приоритеты операций и правила ассоциации (справа налево или слева направо), используемые в стандартных типах данных. При описании операций необходимо соблюдать следующие правила: операция должна быть описана как открытый статический метод класса (спецификаторы public static); параметры в операцию должны передаваться по значению (то есть не должны предваряться ключевыми словами refилиout); сигнатуры всех операций класса должны различаться; типы, используемые в операции, должны иметь не меньшие права доступа, чем сама операция (то есть должны быть доступны при использовании операции). В С# существуют три вида операций класса: унарные, бинарные и операции пре- образования типа. Унарные операции Можно определять в классе следующие унарные операции: + - ! - ++ -- 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. Операции не должны изменять значение передаваемого им операнда. Операция, возвращающая величину типа класса, для которого она определяется, должна создать новый объект этого класса, выполнить с ним необходимые действия и передать его в качестве результата. Префиксный и постфиксный инкременты не различаются (для них может существовать только одна реализация, которая вызывается в обоих случаях). Операции trueиfalseобычно определяются для логических типов SQL, обладающих неопределенным состоянием, и не входят в число тем, рассматриваемых здесь. В качестве примера усовершенствуем приведенный в листинге 1.17классSafeArrayдля удобной и безопасной работы с массивом. В класс внесены следующие изменения: добавлен конструктор, позволяющий инициализировать массив обычным массивом или серией целочисленных значений произвольного размера; добавлена операция инкремента; добавлен вспомогательный метод Print вывода массива; 59 изменена стратегия обработки ошибок выхода за границы массива; снято требование, чтобы элементы массива принимали значения в заданном диапазоне. Ход работы Задание1. Определить операции инкремента для класса SafeArray using System; namespace ConsoleApplication1 { class SafeArray { public SafeArray( int size) // конструктор { a = new int[size]; length = size; } public SafeArray( params int[ ] arr) // новый конструктор { length = arr.length; a = new int[length]; for( int i=0; i { SafeArray temp = new SafeArray( x.length ); for( int i = 0; i < x.length; ++i) temp[ i ] = ++x.a[ i ]; return temp; } public int this[ int i ] // индексатор { get { if ( i >= 0 && i < length ) return a[i]; else throw new indexOutOfRangeException(); // исключение } set { if ( i >= 0 && i < length ) a[i] = value; else throw new IndexOutOfRangeException(); // исключение } } public void Print( string name ) // вывод на экран { Console.WriteLine( name + ":" ); for ( int i = 0; i < length; ++i ) Console.Write( "\t" + a[i] ); Console.WriteLine(); } int[] a; // закрытый массив int length; // закрытая размерность . } class Class1 { static void Main() { try { 60 SafeArray a1 = new SafeArray( 5, 2, -1, 1, -2 ); a1.Print( "Массив 1" ); a1++; a1.Print( "Инкремент массива 1" ); } catch ( Exception e ) // обработка исключения { Console.WriteLine( e.Message ); } } } } Бинарные операции Можно определять в классе следующие бинарные операции: + - * / % & | ^ << >> == != > < >= <= Синтаксис объявителя бинарной операции: тип operator бинарная_операция (параметр1, параметр2) Примеры заголовков бинарных операций: public static MyObject operator + ( MyObject m1, MyObject m2 ) public static bool operator == ( MyObject m1, MyObject m2 ) Хотя бы один параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. Операция может возвращать величину любого типа. Операции ==и!=,>и<,>=и<= определяются только парами и обычно возвращают логическое значение. Чаще всего в классе определяют операции сравнения на равенство и неравенство для того, чтобы обеспечить сравнение объектов, а не их ссылок, как определено по умолчанию для ссылочных типов. Перегрузка операций отношения требует знания интерфейсов, поэтому она рассматривается позжe. Пример определения операции сложения для класса SafeArray, описанного в предыдущем разделе, приведен в листинге 1.20. В зависимости от операндов операция либо выполняет поэлементное сложение двух массивов, либо прибавляет значение операнда к каждому элементу массива. Задание 2. Определение операции сложения для класса SafeArray using System; namespace ConsoleApplication1 { class SafeArray { public SafeArray( int size ) { a = new int [ size ]; length = size; } public SafeArray( params int [ ] arr ) { length = arr.Length: a = new int [ length ]; for ( int i = 0; i < length; ++i ) a[ i ] = arr [ i ]; } public static SafeArray operator + ( SafeArray x, SafeArray у ) // + { int len = x.length < y.length ? x.length : y.length; SafeArray temp = new SafeArray ( len ); for ( int i = 0; i < len; ++i ) temp[i] = x[i] + y[i]; return temp; } public static SafeArray operator + ( SafeArray x, int у ) // + 61 { SafeArray temp = new SafeArray ( x.length ); for ( int i = 0; i < x.length; ++i ) temp[i] = x[i] + y; return temp; } public static SafeArray operator + ( int x, SafeArray у ) { // + SafeArray temp = new SafeArray(y.length); for ( int i = 0; i < y.length; ++i ) temp[i] = x + y[i]; return temp; } public static SafeArray operator ++ ( SafeArray x ) // ++ { SafeArray temp = new SafeArray(x.length); for ( int i = 0; i < x.length; ++i ) temp[i] = ++x.a[i]; return temp; } public int this[int i] { // [ ] get { if ( i >= 0 && i < length ) return a[i]; else throw new IndexOutOfRangeException(); } set { if ( i >= 0 && i < length ) a[i] = value; else throw new IndexOutOfRangeException(); } } public void Print( string name ) { Console.WriteLine( name + ":" ); for ( int i = 0; i < length; ++i ) Console.Write( "\t" + a [ I ] ); Console.WriteLine(); } int[] a; int length; } class Class1 { static void Main() { try { SafeArray a1 = new SafeArray( 5, 2, -1, 1, -2 ); a1.Print( "Массив 1" ); SafeArray a2 - new SafeArrayC 1. 0, 3 ); a2.Print( "Массив 2" ); a1++; SafeArray a3 = a1 + a2; a3.Print( "Сумма массивов 1 и 2" ); a1 = a1 + 100; // 1 a1.Print( "Массив 1 + 100" ); a1 = 100 + a1; // 2 a1.PrintC "100 + массив 1" ); 62 a2 += ++a2 +1; // 3 оторвать руки! a2.Print( "++a2, a2 + a2 + 1" ); } catch ( Exception e ) { Console.WriteLine( e.Message ); } } } } Результат работы программы: Массив 1: 5 2 -1 1 -2 Массив 2: 1 0 3 Сумма массивов 1 и 2: 7 3 3 Массив 1+100: 106 103 100 102 99 100 + массив 1: 206 203 200 202 199 ++a2, a2 +a2 + 1: 5 3 9 Обратите внимание: чтобы обеспечить возможность сложения с константой, операция сложения перегружена два раза для случаев, когда константа является первым и вторым операндом (операторы 2 и 1). Сложную операцию присваивания += (оператор 3) определять не требуется, да это и невозможно. При ее выполнении автоматически вызываются сначала операция сложения, а потом присваивания. В целом же оператор 3 демонстрирует недопустимую манеру программирования, поскольку результат его выполнения неочевиден. ПРИМЕЧАНИЕ В перегруженных методах для объектов применяется индексатор. Для повышения эффективности можно обратиться к закрытому полю-массиву и непосредственно, например: temp.a[i] = х + y.a[i]. Операции преобразования типа Операции преобразования типаобеспечивают возможность явного и неявного преобразования между пользовательскими типами данных. Синтаксис объявителя операции преобразования типа: implicit operator тип ( параметр ) // неявное преобразование explicit operator тип ( параметр ) // явное преобразование Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого определяется операция. Таким образом, операции выполняют преобразование либо типа класса к другому типу, либо наоборот. Преобразуемые типы не должны быть связаны отношениями наследования. Примеры операций преобразования типа для класса Monster, описанного в разделе 6: public static implicit operator int( Monster m ) { return m.health; } public static explicit operator Monster( int h ) { return new Monster( h, 100, "Fromlnt" ); } Ниже приведены примеры использования этих преобразований в программе. Не надо искать в них смысл, они просто иллюстрируют синтаксис: Monster Masha = new Monster( 200, 200, "Masha" ); 63 Int i = Masha: // неявное преобразование Masha = (Monster) 500; // явное преобразование Неявное преобразованиевыполняется автоматически: при присваивании объекта переменной целевого типа, как в примере; при использовании объекта в выражении, содержащем переменные целевого типа; при передаче объекта в метод на место параметра целевого типа; при явном приведении типа. +Явное преобразование выполняется при использовании операции приведения типа. Все операции класса должны иметь разные сигнатуры. В отличие от других видов методов, для операций преобразования тип возвращаемого значения включается в сигнатуру, иначе нельзя было бы определять варианты преобразования данного типа в несколько других. Ключевые слова implicit иexplicitв сигнатуру не включаются, следовательно, для одного и того же преобразования нельзя определить одновременно явную и неявную версии. Неявное преобразованиеследует определять так, чтобы при его выполнении не возникала потеря точности и не генерировались исключения. Если эти ситуации возможны, преобразование следует описать как явное. |