305 38 656KB
Russian Pages [56]
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
ЮЖНО-УРАЛЬСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
004.4(07) C207
Е.М. Сартасов
ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ Учебное пособие
Челябинск 2014
Министерство образования и науки Российской Федерации Южно-Уральский государственный университет Кафедра информатики
004.4(07) C207
Е.М. Сартасов
ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ Учебное пособие
Челябинск Издательский центр ЮУрГУ 2014
УДК 004.434(075.8) C207 Одобрено учебно-методической комиссией факультета экономики и управления. Рецензенты: Завьялов О.Г., Овсяницкая Л.Ю.
Сартасов, Е.М. C207 Объектно-ориентированное программирование: учебное пособие / Е.М. Сартасов. – Челябинск: Издательский центр ЮУрГУ, 2014. – 54 с. В пособии рассмотрены вопросы разработки программ с использованием средств объектно-ориентированного программирования. В качестве языка программирования выбран С#. Изложение сопровождается примерами. Пособие ориентировано на студентов направления 230700.62 «Прикладная информатика», изучающих предмет «Объектноориентированное программирование». УДК 004.413(075.8)
© Издательский центр ЮУрГУ, 2014
Введение По мере развития вычислительной техники стал актуальным переход от процедурного программирования к объектно-ориентированному. Объектноориентированное программирование позволяет скрывать за понятием объектов часть малозначимых свойства и действий, а также наследовать свойства объектов от базовых к производным, что сокращает исходный код программ за счет уменьшения дублирования действий. Целью данного учебного пособия является: научить студентов направления 230700.62 «Прикладная информатика» разрабатывать программы с использование объектно-ориентированного подхода к программированию. Из поставленной цели вытекают следующие задачи: – изучить язык программирования си-шарп (в дальнейшем C#), который наиболее подходит для объектно-ориентированного программирования; – рассмотреть понятие класса, как основное понятие объектноориентированного программирования, а также понятия данных, свойств и методов класса; – научиться строить иерархию классов, наследовать свойства, методы и другие характеристики классов; – познакомиться с множественным наследованием классов. В первой главе пособия описаны элементы и конструкции языка C#, не связанные с объектно-ориентированный программированием, т.к. они понадобятся в последующих главах для реализации примеров построения программ. Во второй главе рассматривается понятие класса и связанных с ними понятий: свойства, методы, конструкторы, деструкторы. Также рассмотрено понятие инкапсуляции, как возможность группировать данные и скрывать внутри класса свойства и методы, малозначимые вне класса. В третьей главе описаны способы наследования классов и связанные с наследованием понятия: базовый и производный классы, полиморфизм – возможность единообразно обрабатывать данные различных типов и классов. В завершении главы рассмотрено множественное наследование и возможные его реализации в языке C#. В пособии рассмотрены практические примеры разработки программ с использование объектно-ориентированного подхода. Для изучения данного предмета требуются знания следующих предметов: «Информатика и программирование» и «Высокоуровневые методы информатики и программирования». Рассмотренные в пособии вопросы помогут изучать будущим бакалаврам последующие предметы: «Интернет-программирование» и «Управление информационными системами».
3
1. Конструкции языка С#, не связанные с ООП 1.1. Типы данных в языке C# В языке С# используются следующие основные типы данных: − sbyte – знаковое однобайтное целое число в диапазоне от -128 до 127; − byte – беззнаковое однобайтное целое число в диапазоне от 0 до 255; − short – знаковое двухбайтное целое число в диапазоне от -32768 до 32767; − ushort – беззнаковое двухбайтное целое число в диапазоне от 0 до 65535; − int – знаковое четырехбайтное целое число в диапазоне от -2147483648 до 2147483647; − uint – беззнаковое четырехбайтное целое число в диапазоне от 0 до 4294967295; − long – знаковое восьмибайтное целое число в диапазоне от -9223372036854775808 до 9223372036854775807; − ulong – знаковое восьмибайтное целое число в диапазоне от 0 до 18446744073709551615; − float – вещественное четырехбайтное число с точностью 6 разрядов; − double – вещественное восьмибайтное число с точностью 15 разрядов; − decimal – вещественное шестнадцатибайтное число с точностью 27 разрядов; − bool – логическое значение истина (true) или ложь (false); − char – двухбайтный символ в кодировке Unicode; − string – строка символов в кодировке Unicode. Подобно другим объектно-ориентированным языкам программирования, программист может дополнительно вводить свои типы данных, как это делать мы рассмотрим несколько позже.
1.2. Литералы Литералы – числовые, логические, символьные или строковые данные расположенные непосредственно в программе. Числовые литералы можно разделить на целочисленные и вещественные. Целочисленный литерал служит для записи целочисленных значений и является соответствующей последовательностью цифр (возможно, со знаком минус). Целочисленный литерал, начинающийся со знака 0, воспринимается как восьмеричное целое. В этом случае цифры 8 и 9 не должны встречаться среди составляющих литерал символов. Целочисленный литерал, начинающийся с 0х или 0Х, воспринимается как шестнадцатеричное целое. В этом случае целочисленный литерал может включать символы от А или а, до F или f, которые в шестнадцатеричной системе эквивалентны десятичным значениям от 10 до 15. Непосредственно за литералом могут располагаться в 4
произвольном сочетании один или два специальных суффикса: U или u (беззнаковый) и L или l (длинный). Вещественный литерал служит для отображения вещественных значений. Он фиксирует запись соответствующего значения в обычной десятичной или экспоненциальной форме. В экспоненциальной форме мантисса отделяется от порядка литерой Е или е. Непосредственно за литералом может располагаться один из двух специальных суффиксов: F или f и L или l. Значением символьного литерала является соответствующее значение ASCII кода символа. Символьный литерал представляет собой последовательность одной или нескольких литер, заключенных в одинарные кавычки. Символьный литерал служит для представления литер в одном из форматов представления. Например, литера Z может быть представлена литералом 'Z', а также литералами '\132' (код символа в восьмиричном представлении, начинается с обратного слеша, после которого 3 восьмиричных цифры) и '\х5А' (код символа в шестнадцатиричном представлении, начинается с обратного слеша и латинской буквы x, после который 2 шестнадцатиричных цифры). В качестве специальных символов можно использовать: '\t' – табуляция, '\n' – перевод на следующую строку, '\0' – нулевой символ, '\'' – апостроф, '\"' – кавычка. Строковые литералы являются последовательностью (возможно, пустой) символов в одном из рассмотренных выше видов представления, заключенных в двойные кавычки. Строковые литералы, расположенные последовательно, соединяются в один литерал. Строковый литерал заканчиваются нулевой литерой, которая используется как индикатор конца литерала. Логические литералы состоят из двух значений: true - истина и false - ложь.
1.3. Переменные Переменная – это расположение в памяти объекта определенного типа. Имена переменных могут состоять из латинских букв, цифр и знака подчеркивания, первым символом должна быть буква или знак подчеркивания. Все переменные должны быть описаны. Описание переменных имеем вид: тип список_имен_переменных. Например: int n, l, k; // Переменные целого типа с именами n, l, k; double x,y,z; // Переменные вещественного типа с именами x,y,z; bool f; // Логическая переменная с именем f; char c, s; // Символьные переменные с именами c, s.
1.4. Операции В языке C# допустимы арифметические, логические, битовые, текстовые и условные операции. К арифметическим операциям относятся: + – сложение, - – вычитание или изменение знака, * – умножение, / – деление, % – остаток от деления (только для целочисленных типов), ++ – увеличение на единицу, -- – уменьшение на единицу, += – увеличение на указанную величину, -= – уменьшение на 5
указанную величину, *= – увеличение в указанное число раз, /= – уменьшение в указанное число раз. К логическим операциям относятся: > – больше, < – меньше, == – равно, != – не равно, >= – больше или равно, – битовый сдвиг вправо на указанное количество бит, .~ – побитное инвертирование. К текстовым можно отнести только одну операцию + – сцепление строк. В языке C# существуют еще операции, которые мы рассмотрим ниже. В C# существует условная операция, которая имеет следующий вид: логическое выражение ? результат в случае истинного значения логического выражения : результат в случае ложного значения логического выражения. Если в одном выражении используется несколько операций, то они выполняются в порядке, определенном приоритетом операций. Приоритет операций следующий: 1. Унарные операции (примененные к одному операнду) - ! ~ ++ --. 2. Мультипликативные (умножение, деление, остаток от деления) * / %. 3. Аддитивные (сложение, вычитание) + -. 4. Битовый сдвиг >. 5. Сравнение на неравенство < > =. 6. Сравнение на равенство == !=. 7. Побитное И &. 8. Побитное ИЛИ |. 9. Логическое И &&. 10. Логическое ИЛИ ||. 11. Условная операция ?. 12. Бинарные операции увеличения или уменьшения += -= *= /=. Если встречаются последовательно несколько операций с равным приоритетом, то они выполняются слева направо. Если необходимо выполнять операции в ином порядке, то следует использовать круглые скобки.
1.5. Оператор присваивания Оператор присваивания имеет вид: переменная = выражение;. В выражении участвуют переменный и литералы, связанные знаками операций. Например: int a = 0; float b = 1; short d=10, e=15; a = 2* (d + e); bool l = d > e;
// Целочисленная переменная a получит значение 0. // Вещественная переменная b получит значение 1. // Целочисленный короткие переменные d получит // значение 10, e получит значение 15; // Целочисленная переменная a, описанная ранее, // получит значение 50. // Логическая переменная l получит значение false. 6
a += d;
// Целочисленная переменная a увеличит свое // значение на величину d и станет равной 60. string s="Проба пера";// Строковая переменная s получит значение // "Проба пера". string r = s + 1; // Строковая переменная r получит значение // "Проба пера1". Как мы видим в приведенных примерах, в выражениях могут участвовать переменные разных типов, при этом происходит автоматической (неявное) пребразование типов, однако не все типы могут быть неявно пребразованы в другие типы. Так целочисленные типы могут быть преобразованы в другие целочисленные большей разрядности, но не могут в типы меньшей разрядности, например: byte a=0; short b = a; int c = b; long d = c; int f = d; short g = c; byte j = b;
// Правильно, преобразование byte в short. // Правильно, преобразование short в int. // Правильно, преобразование int в long. // Неправильно, нельзя неявно преобразовать long в int. // Неправильно, нельзя неявно преобразовать int в short. // Неправильно, нельзя неявно преобразовать short в byte.
Существуют достаточно неожиданные ограничения на использование целочисленных данных, например следующий пример: byte x=1, y=2; byte z = x + y; Оказывается неверным, т.к. результат суммирования двух байтовых переменных имеет тип int, а данные этого типа не могут быть неявно преобразованы к типу byte. Аналогично с типом short, а с типами int и long такого эффекта не происходит. Любой целочисленный тип можно неявно преобразовывать в любой вещественный, обратно недопустимо, например: int c = 0; double f = c; int k = f;
// Правильно, преобразование double в int. // Неправильно, нельзя неявно преобразовать double в int.
Менее логичны неявные преобразования с вещественными данными, данные типа float могут быть неявно преобразованы в тип double, остальные неявные преобразования недопустимы, например: float p=0; double q = p; // Правильно, преобразование float в double. decimal r = q; // Неправильно, нельзя неявно преобразовать double в // decimal. double s = r; // Неправильно, нельзя неявно преобразовать decimal в // double. 7
float t = q; float u = t;
// Неправильно, нельзя неявно преобразовать double в // float. // Неправильно, нельзя неявно преобразовать decimal в // float.
Данные типа char могут быть неявно преобразованы в тип int и во все типы, в которые int может быть неявно преобразован: long, float, double, decimal. В результате такого преобразования числовые данные получат значения ASCII кода символа. Данные типа bool и string неявным образом невозможно преобразовать в другие, рассмотренные выше типы. Также невозможно преобразовать рассмотренные выше типы в bool и string. Кроме неявных преобразований существуют явные преобразования. Для явного преобразования необходимо перед переменной или выражением указать в круглых скобках тип, в который преобразовываем. Например: long d = 123; int f = (int)d; // Правильно, явное преобразование long в int. Явно можно преобразовывать любой числовой тип в любой другой числовой. Если осуществляется преобразование в тип, имеющий меньше разрядов, то для целочисленных типов теряются старшие байты и результат не имеет ничего общего с исходным с значением, для вещественных типов теряются младшие биты, результат в принципе верный, но с меньшей точностью. Например: int a = 1000; byte b = (byte)a; // Результат b=232, т.к. биты старших байт потерены. double x = 1234567890; float y = (float)x; // Результат y=1234568000, точность потеряна. В результате вышесказанного, сложение перменных типа byte следует выполнять следующим образом: byte x=1, y=2; byte z = (byte)(x + y); Явным образом можно преобразовать любой числовай тип в char. При таком преобразовании сначала данные преобазуются в тип byte, а затем получается символ с кодом получившегося числа. Можно явно преобразовывать тип char в любой числовой тип, при этом получисля код символа. Так же, как и неявным образом, данные типа bool и string явным образом невозможно преобразовать в другие, рассмотренные выше типы и обратно. Однако иногда такое преобразование необходимо. Для этого используется класс Convert. Классы мы рассмотрим ниже, однако преобразовывать надо уже сейчас и мы, не вникая в подробности, рассмотрим такое преобразование. Для преобразования к типу: − string используется конструкция Convert.ToString(данные); − bool – Convert.ToBoolean(данные); 8
− − − − − − −
byte – Convert.ToByte(данные); short – Convert.ToInt16(данные); int – Convert.ToInt32(данные); long – Convert.ToInt64(данные); double – Convert.ToDouble(данные); decimal – Convert.ToDecimal(данные); char – Convert.ToChar(данные);
Почему-то отсутствует такое преобразование в тип float. С помощью класса Convert можно преобразовать любой тип в любой. Однако если преобразовываем строку к числовому типу, то в строке должно быть число, например: string b = "123", c = "qwerty"; int d = Convert.ToInt32(b); // Правильно, d будет равно 123 int f = Convert.ToInt32(c); // Неправильно, невозможно преобразовать в // число Компиляция данного примера пройдет нормально, а во время выполнения возникнет ошибка. В заключении данного пункта рассмотрим пример сложения двух чисел. Запустите Microsoft Visual Studio, для этого нажмите ПУСК / Все программы / Microsoft Visual Studio 2010 / Microsoft Visual Studio 2010 (вместо 2010 может быть другой год). После запуска программы Microsoft Visual Studio, следует создать проект, для этого нажмите Файл / Создать / Проект. В левом списке следует выбрать язык программирования «Visual C#» (возможно для этого придется открыть элемент «Другие языки»), во втором списке «Приложение Windows Form», в поле ввода «Имя» следует ввести имя проекта, например «Sum», в поле «Расположение» следует с помощью кнопки «Обзор» выбрать папку, в которой будет расположен проект. После того, как все выберите и введете, нажмите кнопку «ОК». Проект будет создан, откроется пустое окно конструктора первой формы и окно обозревателя решений, в котором будет изображено дерево проекта, оно понадобится нам позже, когда будет несколько форм, а пока можно его закрыть. Откройте «Панель элементов», для этого нужно нажать Вид / Панель элементов. На панели элементов, щелкните по элементу Label (в переводе – метка, но более подходит – текст) и не отпуская левой кнопки мыши, перетащите ее на форму. На форме появится объект label1. Наведите на этот объект мышь и нажмите правую кнопку, в появившемся контекстном меню выберите пункт «Свойства», откроется окно свойств. В окне свойств установите свойство Text – «Первое слагаемое». Расположите на форме элементы, показанные на рис. 1.1.
9
Рис. 1.1. Заготовка для упражнения «Операции присваивания» Задайте элементам свойства, указанные в таблице 1. Таблица 1 Элемент Label1 Label2 Label3 textBox1 textBox2 textBox3 textBox3 button1 Форма
Свойство Text Text Text Text Text Text ReadOnly Text Text
Значение Первое слагаемое Второе слагаемое Сумма
True Сложить Сложение
Дважды щелкните по кнопке «Сложить», в открывшемся окне введите следующий текст: int a = Convert.ToInt32(textBox1.Text); // Взять содержимое поля ввода // textBox1,преобразовать его к целому типу и присвоить переменой a int b = Convert.ToInt32(textBox2.Text); // Взять содержимое поля ввода // textBox2,преобразовать его к целому типу и присвоить переменой b int c = a + b; // Сложить содержимое переменных а и b, результат // присвоить переменной с textBox3.Text = Convert.ToString(c); // Значение переменной с преобра- // зовать к строковому типу и записать в поле ввода textBox3 Сохраните получившуюся программу и запустите её. Если Вы все сделали правильно, то программа запустится и появится окно, показанное на рис. 1.2.
10
Рис 1.2. Окно программы «Сложение» Введите в качестве первого и второго слагаемых целые числа и нажмите кнопку «сложить», в поле «сумма» будет результат сложения. После проверки на корректных данных, проверим программу на некорректных данных, для этого введем в одном из полей ввода произвольный, нечисловой текст и нажмем кнопку «сложить». Программа выдаст окно ошибки, изображенное на рис. 1.3.
Рис. 1.3. Окно сообщения об ошибке «неверный формат данных» Нажмите кнопку «Прервать» и Вы увидите, в какой строке программы произошла ошибка. Для разработки программы эта информация полезна, а для работы с программой неудобна. Изменим программу, так чтобы при вводе некорректных данных выдавалось окно, предлагающее исправить неверные данные. int a,b,c; try { // Если в следующей строке будет ошибка, то будет выполняться // ветвь программы catch, если ошибки не будет, то ветвь catch // будет пропущена a = Convert.ToInt32(textBox1.Text); return; } catch { MessageBox.Show("Ошибка в первом слагаемом, исправьте ее"); 11
} try { b = Convert.ToInt32(textBox2.Text); } catch { MessageBox.Show("Ошибка во втором слагаемом, исправьте ее"); return; } c=a+b textBox3->Text = Convert::ToString(c); Запустите, получившуюся программу и убедитесь, что она корректно работает на некорректных данных. Самостоятельно измените программу для операций: вычитания, умножения, деления. Обратите внимание, что делить на ноль нельзя, поэтому при реализации деления предусмотрите дополнительную проверку, которая не допустит в качестве делимого нулевое значение.
1.6. Условные операторы Условные операторы необходимы для того, чтобы выполнять те или иные действия в зависимости от выполнения некоторого условия. К условным операторам относятся: оператор if которые разделяет действия на одну или две ветви и оператор switch который способен разделять действия на много ветвей.
1.6.1. Оператор if Оператор if выглядит следующим образом: if (условие) { // Действия, которые выполняются при истинном значении условия } else { // Действия, которые выполняются при ложном значении условия } Ветвь else может быть опущена, тогда при ложном значении условия не выполняется ни каких действий. Если в ветви одно действие, то фигурные скобки можно опустить. В качестве примера рассмотрим решение квадратного уравнения вида 2 ax +bx+c=0. Для тех, кто забыл, как решать данное уравнение, напомним алгоритм. 1. Вычислим дискриминант D=b2-4ac. 2. Если D>0, то имеется 2 решения: x1=(-b+√D)/(2a), x2=(-b-√D)/(2a). 3. Если D=0, то имеется 1 решения: x1=-b/(2a).
12
4. Если D 0) { x1 = (-b + Math.Sqrt(d)) / (2 * a); x2 = (-b - Math.Sqrt (d)) / (2 * a); textBox4.Text = Convert.ToString(x1); textBox5.Text = Convert.ToString(x2); } else if (d == 0) { x1 = (-b + Math.Sqrt (d)) / (2 * a); textBox4.Text = Convert.ToString(x1); textBox5.Text = ""; } else { textBox4.Text = "Решений нет"; textBox5.Text = ""; } Запустите программу. Для проверки всех трех ветвей программы используйте следующие комплекты данных: {1, 4, 1} – два решения, {1, 2, 1} – одно решение, {1, 1, 1} – нет решений. Самостоятельно измените программу, таким образом, чтобы она смогла работать при отрицательном дискриминанте. Для этого добавьте поля ввода, таким образом, чтобы получилось окно, изображенное на рис. 1.6.
14
Рис. 1.6. Окно программы решения квадратного уравнения с отрицательным дискриминантом
1.6.2. Оператор switch Оператор switch способен разделять выполнение программы на несколько ветвей. Он выглядит следующим образом: switch (выражение) { case константа1: Действия, которые выполняются при значении выражения равном константе1; break; case константа2: Действия, которые выполняются при значении выражения равном константе2; break; …………………………………………………………………………………. default: Действия, которые выполняются при значении выражения не равном ни одной ранее перечисленной константе; break; }
15
Ветка default может отсутствовать, тогда в случае неравенства выражения всем константам, не выполняется ни какого действия. Если нужно выполнять одинаковые действия для разных констант, то следует записать ветвь следующим образом: case константа M: case константа M+1: case константа M+2: Действия, которые выполняются при значении выражения равном всем, вышеперечисленным константам; break; В качестве примера рассмотрим программу приветствия. Для этого расположите на форме элементы, показанные на рис. 1.7.
Рис. 1.7. Заготовка для программы приветствия Дважды щелкните по кнопке «Вход в систему», в открывшемся окне добавьте следующий текст: string s = textBox1.Text; switch (s) { case "Ivanov": textBox2.Text = "Здравствуйте, Иван Иванович"; break; case "Petrov": textBox2.Text = "Здравствуйте, Петр Петрович"; break; default: textBox2.Text = "Извините, мы не знакомы"; break; } Запустите программу, если она работает, то измините ее так, что бы она приветствовала всех студентов Вашей группы.
16
1.7. Циклы Цикл – повторяющаяся последовательность действий при разных значениях переменных. В языке С# имеется четыре вида цикла. 1. Цикл с предусловием: while (условие) { // Последовательность действий } Сначала проверяется условие, если оно истинно, то «последовательность действий» пока условие не станет ложным. 2. Цикл с постусловием:
выполняется
do { // Последовательность действий while (условие) Сначала выполняется «последовательность действий», затем проверяется условие, если оно истинно, то процесс повторяется. 3. Цикл типа «прогрессия»: for (начальное действие; условие; приращение) { // Последовательность действий } Сначала выполняется «начальное действие», затем проверяется условие, если оно истинно, то выполняется «последовательность действий», затем выполняется «приращение», после чего проверяется условие, пока условие не станет ложным процесс повторяется. 4. Цикл прохода по массивам и коллекциям, т.к. массивы и коллекции мы еще не рассматривали, то и данный вид цикла мы рассмотрим позже, после рассмотрения массивов. Рассмотрим задачу формирования арифметической прогрессии: от 0 до 2 с шагом 0.1. Для этого разместим на форме список (listBox) и кнопку (button), показанные на рис. 1.8.
Рис 1.8. Заготовка для программы формирования прогрессии 17
Дважды щелкните по кнопке, в открывшемся окне добавьте следующий текст: for (double x=0; x= 0 && l < listBox1.Items.Count) { // Если есть текущий элемент Form2 f2 = new Form2(); // Создание второй формы f2.textBox1.Text = listBox1.Items[l].ToString(); // Занесение в поле // ввода второй формы значения текущего элемента списка f2.label2.Text = "Изменение"; // Изденение текста «Действие» f2.ShowDialog(); // Запуск второй формы if (f2.label2.Text == "OK") { // Проверка нажатия кнопки «ОК» listBox1.Items[l] = f2.textBox1.Text; // Занесение в текущий // текущий элемент списка содержимого поля ввода } }
22
Для кнопки «Удалить»: int l=listBox1.SelectedIndex; //Определим номер текущего элемента списка if (l >= 0 && l < listBox1.Items.Count) { // Если есть текущий элемент listBox1.Items.RemoveAt(l); // Удалить текущий элемент } Для кнопки «Вычислить»: double [ ] a; // Описание массива типа double a = new double [listBox1.Items.Count]; // Распределение памяти int n = listBox1.Items.Count; // Определим кол-во элементов в списке for (int i=0; i w || _y+_r > h) return false; // Окружность вышла за пределы, возвращаем ложь return true; // иначе возвращаем истина } } В начало класса Form1 добавьте строку: MyCircle c;
// Описание экземпляра объекта класса MyCircle
В конструктор класса Form1 добавьте строку: c = new MyCircle(100, 100, 100); // Создание экземпляра объекта класса // MyCircle Создайте функцию обработки события Paint формы, запишите в эту функцию следующий текст: Graphics g = e.Graphics; c.Draw(g);
// Получение доступа к объекту Graphics // формы // Рисование окружности
Создайте функцию обработки события KeyDown (нажатие клавиши клавиатуры), запишите в эту функцию следующий текст: Keys k = e.KeyCode; // Получение кода нажатой клавиши if (k == Keys.Left) c.Left(); // Если клавиши «стрелка влево», то вызов // метода Left. if (k == Keys.Right) c.Right(); // … «стрелка влево» … Right if (k == Keys.Up) c.Up(); // … «стрелка вверх» … Up if (k == Keys.Down) c.Down(); // … «стрелка вниз» … Down Invalidate(); // Вызов события Paint для перерисовки окна. 30
Запустите программу и убедитесь в ее работоспособности. Самостоятельно измените программу так, чтобы по кнопке F2 увеличивался радиус окружности, а по кнопке F3 он уменьшался, при этом окружность не выходила за пределы окна и радиус был положительный. 2.3. Свойства Свойство в языке C# представляет что-то среднее между данными и методами. При использовании свойства, мы обращаемся к нему, как к данным класса, но на самом деле обращение преобразовывается к вызову неявного метода. Такой метод называется аксессор (accessor). Существует два таких метода: get (для получения данных) и set (для записи). Объявление простого свойства имеет следующую структуру: [модификатор доступа] [тип] [имя_свойства] { get { // текст метода для чтения из поля } set { // текст метода для записи в поле } } В тексте метода get должен быть оператор return, который возвращает значение свойства. В тексте метода set нужно знать, какое значение присваивается свойству, для этого используется ключевое свойство value. Один из методов get или set может отсутствовать, тогда будет возможность доступа либо только для чтения, либо только для записи. В качестве примера рассмотрим свойства для чтения и записи координат центра окружности и радиуса класса MyCircle. public int X // Свойство для работы с x-координатой центра окружности { get // Метод для чтения из поля x { return x; } // get set // Метод для записи в поле x { if (value >= 0) { // value - значение, записываемое в поле х x = value;
31
} else { MessageBox.Show("Значение не может быть отрицательным"); } // else } // set } // public int X Самостоятельно, по аналогии разработайте свойства для работы с Yкоординатой и радиусом.
2.4. Создание нескольких экземпляров объектов С помощью разработанного выше класса можно создать несколько окружностей для этого следует несколько раз выполнить операцию new MyCircle(…). Рассмотрим пример в котором, по нажатию пункта меню, будем создавать новую окружность. Для этого создайте вторую форму для ввода координат центра окружности и радиуса. Заготовка формы показана на рис. 2.1.
Рис. 2.1. Заготовка для формы ввода координат центра и радиуса В классе формы Form2, создайте поле _Rc и свойства Rc, X, Y, R: private bool _Rc; // Признак того, что нажати «ОК» или «Отмена» public bool Rc { get { return _Rc; } } public int X { get { try { return Convert.ToInt32(textBox1.Text); } catch { MessageBox.Show("Ошибка при вводе Х-координаты"); return 10; // Значение по умолчанию, т.к. надо что-то возвращать } // try } // get set { if (value >= 0) { // value - значение, записываемое в поле х textBox1.Text = Convert.ToString(value);
32
} else { MessageBox.Show("Значение не может быть отрицательным"); textBox1.Text = "10"; } // else } // set } // public int X Свойства Y и R разработайте по аналогии самостоятельно. В конструкторе формы добавьте строчку _Rc = false; она нужна, для того, чтобы выход из окна по закрывательному крестику приравнялся к кнопке «Отмена». Для кнопки «ОК» напишите следующий текст: _Rc = true; Close(); Для кнопки «Отмена» : _Rc = false; Close(); Вернемся к главной форме. Т.к. окружностей будет несколько, их надо гдето хранить. Можно использовать простой массив, но удобнее пользоваться контейнером ArrayList. Для его использования необходимо в начале программы добавить using вида: using System.Collections. Контейнер следует описать в классе главной формы Form1, например так: ArrayList a, где а – имя контейнера. В конструкторе формы следует создать контейнер: a = new ArrayList(); по мере необходимости следует добавлять объекты в контейнер: a.Add(объект) и удалять a.RemoteAt(номер_объекта). Обращение к объекту осуществляется следующим образом: a[номер_объекта] as тип. Разместите на главной форме компонент MenuStrip, создайте там пункт «Добавить», дважды щелкните по этому пункту и запишете следующий текст: Form2 f2 = new Form2(); // Создаем вторую форму f2.ShowDialog(); // Открываем вторую форму if (f2.Rc) { // Если нажали «ОК» c = new MyCircle(f2.X, f2.Y, f2.R); // Создадим окружность a.Add(c); // Добавим окружность в массив Invalidate(); // Перерисуем окно } // if В конструкторе главной формы уберем строчку: c = new MyCircle(100, 100, 100); т.к. будем создавать окружности по пункту меню. Изменим функцию обработки события Paint главной формы: Graphics g = e.Graphics; 33
for (int i = 0; i < a.Count; i++) // Цикл по всем окружностям { (a[i] as MyCircle).Draw(g); // Рисуем текущую окружность } // for Запустите программу. Убедитесь, что окружности создаются, но перемещается по экрану только последняя. Этот недостаток мы исправим в следующем пункте.
2.5. Статические элементы классов Статический элемент класса является единственным для всех экземпляров класса. Он должен быть описан с ключевым словом static. Доступ к статическим элементам, кроме обычного способа, может осуществляться в виде имя_класса.имя_элемента. Могут быть статические методы (функции) класса. В тексте таких методов можно использовать только статические элементы класса. вызывать в виде Статические методы класса можно имя_класса.имя_метода(список_аргументов). В качестве примера в классе Form1 статическим сделаем элемент ArrayList a, написав перед ним слово static. Для определения количества окружностей создадим статическое публичное свойство Count: public static int Count { get { return a.Count; } // get } // public static int Count В классе MyCircle создадим статический элемент _CurCircle, означающий номер текущей окружности: private static int _CurCircle = 0; // текущая окружность. Для доступа к этому полю создадим статическое свойство: public static int CurCircle { get { return _CurCircle; } // get set { if (value >= 0 && value < Form1.Count) { _CurCircle = value; } else { MessageBox.Show("Недопустимое значение"); } // else 34
} // set } // public static int CurCircle С помощью этого свойства будем определять текущую окружность. При создании первой окружности _CurCircle будет равным нулю. При создании последующих окружностей можно менять номер текущей окружности. Изменять номер будем по нажатию клавиши Tab на клавиатуре или по щелчку мыши по окружности. Для обработки клавиши Tab добавим в функцию обработки события KeyDown следуюший текст: if (k == Keys.Tab) { if (MyCircle.CurCircle + 1 < Count) { MyCircle.CurCircle++; //Увеличиваем номер текущей окружности } else { // Вышли за пределы MyCircle.CurCircle = 0; // Обнулим номер } // if } // if Для изменения номера текущей окружности по щелчку мыши создадим обработчик события MouseDown. В тексте обработчика запишем: for (int i = 0; i < Count; i++) { MyCircle c = a[i] as MyCircle; if ((c.X - e.X)* (c.X - e.X)+ (c.Y - e.Y)* (c.Y - e.Y) < c.R*c.R) { MyCircle.CurCircle = i; break; } // if } // for Invalidate(); Изменим текст метода обработки события KeyDown, так для перемещения влево текст будет выглядеть следующим образом: if (k == Keys.Left) (a[MyCircle.CurCircle] as MyCircle).Left(); Для остальных направлений перемещения окружности выполните самостоятельно по аналогии. Запустите программу и убедитесь, что она работает. Однако мы не видим, какая из окружностей является текущей, этот недостаток мы исправим в следующем пункте.
2.6. Перегруженные методы Прегруженными называются методы имеющие одинаковое имя, но разное количество или тип аргументов. В качестве примера перегрузим метод Draw класса MyCircle. Оставив старый метод, напишем новый с дополнительным
35
аргументом bool IsCur – является ли окружность текущей. Если этот параметр истинный, то нарисуем фон окружности светло-желтым цветом. public void Draw(Graphics g, bool IsCur) // Перегрузка метода рисования // окружности { if (IsCur) { // Если окружность текущая Brush b=new SolidBrush(Color.Yellow);// Создаем кисть желтого цв. g.FillEllipse(b, x-r, y-r, 2*r, 2*r); // Закрашиваем фон } // if Pen p1 = new Pen(Color.Red, 3); //Создаем перо красного цвета, // толщина 3 g.DrawEllipse(p1, x-r, y-r, 2*r, 2*r); // Рисуем окружность } Изменим метод обработки события Paint так, он вызывал перегруженные методы Draw: Graphics g = e.Graphics; for (int i = 0; i < a.Count; i++) // Цикл по всем окружностям { if (i != MyCircle.CurCircle) { // Окружность не текущая (a[i] as MyCircle).Draw(g); // Рисуем старым методом } else { (a[i] as MyCircle).Draw(g, true); // Рисуем новым методом } // else } // for Запустите программу и убедитесь в ее работоспособности.
2.7. Передача параметров по ссылке Обычно при передаче параметров в метод класса, передается копия данного объекта, поэтому изменение объекта в методе не приводит к изменению объекта вне метода, но бывает ситуация когда необходимо передать не копию а сам объект. Таким образом внутри метода меняется не копия объекта а сам объект. Передача параметра по ссылке происходит через ключевое слово ref. В качестве примера рассмотрим два метода Inc и Inc2. Оба метода увеличивают целочисленное значение на единицу и возвращают получившееся значение. static private int Inc(int a) { return a+=1;
36
} static private int Inc2(ref int a) { return a+=1; } Однако второй метод, в отличие от первого, использует передачу параметров по ссылке, поэтому при вызове второго метода, в вызывающей части программы значение передаваемой переменной также увеличится на единицу. int x = 10; int y = Inc(x); // y будет равен 11, а x останется равным 10 int z = Inc2(ref x); // z будет равен 11, x станет равным 10
2.8. Контрольные вопросы 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22.
Что называется классом. Какие модификаторы доступа к элементам класса Вы знаете. Что означает модификатор доступа public. Что означает модификатор доступа private. Что означает модификатор доступа protected. Что означает модификатор доступа internal. Что представляет собой метод класса. Что такое инкапсуляция. Для чего используется конструктор класса. Для чего используется деструктор класса. Что такое свойство. Что такое свойство аксессор. Какие аксессоры Вы знаете. Что такое свойство аксессор get. Что такое свойство аксессор set. Как создать несколько экземпляров объектов класса. Как в контейнер ArrayList добавить элемент. Как из контейнера ArrayList удалить элемент. Как получить значение находящееся в контейнере ArrayList. Что такое статический элемент класса. Что такое перегруженные методы класса. Для чего используется передача параметров по ссылке.
37
3. Наследование классов. Полиморфизм 3.1. Базовый и производные классы Наследование классов – построение нового класса, включающего все основные элементы существующего класса, с добавление своих индивидуальных элементов. Существующий ранее класс, на базе которого строится новый класс, называется базовым классом. Класс, строящийся на основе базового класса, называется производным классом. Процесс построения производного класса на основе базового называется наследованием. Чтобы унаследовать один класс от другого, используется следующая конструкция: class : { индивидуальные элементы производного класса } В качестве примера наследования классов рассмотрим базовый класс фигура MyShape и два производных: окружность MyCircle и квадрат MyRect. Описание базового класса будет следующим: class MyShape { protected int x; // x-координата базовой точки protected int y; // y-координата базовой точки protected int r; // размер фигуры protected static int _CurShape = 0; // номер текущей фигуры protected int _Type; // тип фигуры: 1-окружность, 2-квадрат public int X // Свойство для работы с x-координатой { get // Метод для чтения из поля x { return x; } // get set // Метод для записи в поле x { if (value >= 0) { // value - значение, записываемое в поле х x = value; } else { MessageBox.Show("Значение должно быть > 0"); } // else } // set } // public int X
38
public int Y // Свойство для работы с x-координатой { get // Метод для чтения из поля x { return y; } // get set // Метод для записи в поле x { if (value >= 0) { // value - значение, записываемое в поле х y = value; } else { MessageBox.Show("Значение должно быть > 0"); } // else } // set } // public int Y public int R // Свойство для работы с x-координатой { get // Метод для чтения из поля x { return r; } // get set // Метод для записи в поле x { if (value >= 0) { // value - значение, записываемое в поле х r = value; } else { MessageBox.Show("Значение должно быть > 0"); } // else } // set } // public int R public static int CurShape { // Свойство для доступа к номеру тек. фиг. get { return _CurShape; } // get set { if (value >= 0 && value < Form1.Count) { _CurShape = value; } else { MessageBox.Show("Недопустимое значение"); } // else } // set } // public static int CurShape
39
public int Type { // Свойство для доступа к типу фигуры get { return _Type; } // get } // public int Type public MyShape(int _x, int _y, int _r) // Конструктор фигуры { x = _x; y = _y; r = _r; } // Конструктор public void Left() // Метод перемещения фигуры влево на 10 пикселей { // if (Valid(x-10, y, r)) x -= 10; // Если x можно уменьшить на 10, // то уменьшаем } // Left public void Right() // … вправо … { if (Valid(x+10, y, r)) x += 10; // Если x можно увеличить на 10, // то увеличим } // Right public void Up() // … влево … { if (Valid(x, y-10, r)) y -= 10; //Если y можно уменьшить на 10, то // уменьшим } // Up public void Down() // … вниз … { if (Valid(x, y+10, r)) y += 10; // Если y можно увеличить на 10, // то увеличим } // Down public bool Valid(int _x, int _y, int _r) // Временно, потом перепишем { FormCollection fc = Application.OpenForms; // Массив всех форм Form f1 = fc[0]; // Первая форма int w = f1.Width-15; // Ширина формы int h = f1.Height-40; // Высота формы if (_x-_r < 0 || _y-_r < 0 || _x+_r > w || _y+_r > h) return false; return true; } // Valid public void Draw(Graphics g, bool IsCur) // Метод рисования окружн. { if (_Type == 1) { 40
(this as MyCircle).Draw(g, IsCur); } // if if (_Type == 2) { (this as MyRect).Draw(g, IsCur); } // if } // Draw } // class MyShape В качестве базовой точки возьмем: для окружности – центр, для квадрата – левый верхний угол. В качестве размера: для окружности – радиус, для квадрата – размер стороны. Производные классы определим следующим образом: class MyCircle : MyShape { public MyCircle(int _x, int _y, int _r) : base (_x, _y, _r) // Конструктор { // окружности _Type = 1; } // Конструктор public void Draw(Graphics g, bool IsCur) // Метод рисования окружн. { if (IsCur) { // Если фигура текущая Brush b = new SolidBrush(Color.Yellow); // Создаем кисть // желтого цвета g.FillEllipse(b, x-r, y-r, 2*r, 2*r); // Закрашиваем фон } // if Pen p1 = new Pen(Color.Red, 3); //Создаем перо красного цвета, // толщина 3 g.DrawEllipse(p1, x-r, y-r, 2*r, 2*r); // Рисуем окружность } // Draw } // class MyCircle class MyRect : MyShape { public MyRect (int _x, int _y, int _r) : base (_x, _y, _r) // Конструктор { // квадрата _Type = 2; } // Конструктор public void Draw(Graphics g, bool IsCur) // Метод рисования квадрата { if (IsCur) { // Если фигура текущая Brush b = new SolidBrush(Color.Yellow); // Создаем кисть // желтого цвета g.FillRectangle(b, x, y, r, r); // Закрашиваем фон } // if
41
Pen p2 = new Pen(Color.Blue, 3); //Создаем перо синего // цвета, толщина 3 g.DrawRectangle(p2, x, y, r, r); // Рисуем квадрат } // Конструктор } // class MyRect В классе Form1, с помощью контекстной замены, замените текст Circle на текст Shape, в окне контекстной замены выключите флажок «Слово целиком», замену осуществляйте до класса MyShape. Измените меню, к пункту «Добавить» добавьте три подпункта: «Окружность», «Квадрат» и «Треугольник», у пункта «Добавить» очистите событие «Click». Для подпункта «Окружность» задайте действие: Form2 f2 = new Form2(); // Создаем вторую форму f2.ShowDialog(); // Открываем вторую форму if (f2.Rc) { // Если нажали «ОК» c = new MyCircle(f2.X, f2.Y, f2.R); // Создадим окружность a.Add(c); // Добавим окружность в массив Invalidate(); // Перерисуем окно } // if Для подпункта «Квадрат» задайте действие: Form2 f2 = new Form2(); // Создаем вторую форму f2.ShowDialog(); // Открываем вторую форму if (f2.Rc) { // Если нажали «ОК» c = new MyRect(f2.X, f2.Y, f2.R); // Создадим квадрат a.Add(c); // Добавим окружность в массив Invalidate(); // Перерисуем окно } // if Подпункт «Треугольник» создан для дальнейшего самостоятельного упражнения. В методе Paint главной формы уберите вызов функции Draw с одним параметром, получится следующий текст: Graphics g = e.Graphics; for (int i = 0; i < a.Count; i++) // Цикл по всем окружностям { if (i != MyShape.CurShape) { // Фигура не текущая (a[i] as MyShape).Draw(g, false); } else { (a[i] as MyShape).Draw(g, true); } // else
42
} // for Запустите программу и убедитесь, что она работает.
3.2. Виртуальные функции Обратим внимание на функции рисования (Draw). В базовом классе она обращается к функции соответствующего производного класса. Такая ситуация является типичной и для нее создан механизм виртуальных функций (или виртуальных методов). В базовом классе такая функция описывается со словом virtual, а в производных со словом override. Добавим слово virtual к функции Draw класса MyShape и уберем содержимое функции: virtual public void Draw(Graphics g, bool IsCur) // Метод рисования фигур { // Метод пустой, т.к. виртуальный и обращается к соответствующим // методам производных классов. } // Draw В производных классах (MyCircle и MyRect) добавьте слово override к функции Draw: override public void Draw(Graphics g, bool IsCur) // Метод рисования Запустите программу и убедитесь, что она работает. Обратите внимание, что проверка на выход за пределы формы работает не очень хорошо: окружность уходит под строку меню, квадрат до строки меню не доходит, квадрат не доходит до левой границы. Так происходит из-за того, что проверка универсальна и не зависит от типа фигуры, но это не так и сделаем алгоритм проверки, для разных типов фигур различным. Для этого тоже воспользуемся механизмом виртуальных функций. В базовом классе функцию Valid объявим виртуальной и уберем содержимое кроме return true, т.к. функция должна чтото возвращать: virtual public bool Valid(int _x, int _y, int _r) { return true; } В классе MyCircle переопределим эту функцию следующим образом: public override bool Valid(int _x, int _y, int _r) { if (_x-_r Form1.h-40) return false; return true; } // Valid
43
В классе MyRect переопределим эту функцию следующим образом: override public bool Valid(int _x, int _y, int _r) { if (_x < 0 || _y < 25 || _x+_r > Form1.w-10 || _y+_r > Form1.h-40) return false; return true; } // Valid Для того, чтобы Form1.w и Form1.h показывали ширину и высоту формы, добавим в описание класса формы следующие элементы: static int _w, _h; // Переменные для хранения ширины и высоты формы public static int w // Свойство для получения ширины формы { get { return _w; } } // w public static int h // Свойство для получения высоты формы { get { return _h; } } // h Для того, чтобы ширины и высота заносились в переменные _w и _h, зададим два события формы: Activated и Resize с одинаковым текстом: _w = Width; _h = Height; Событие Activated срабатывает при запуске программы, а событие Resize при изменении размера формы. Запустите программу и убедитесь, что она работает. Самостоятельно разработайте класс «Равносторонний треугольник», где x, y – координаты верней точки, r – длина стороны.
3.3. Абстрактные функции, свойства и классы Часто возникает ситуация при которой в базовом классе есть пустая виртуальная функция. В производных классах такая функция переопределена. В нашем примере это функции Draw и Valid. Функция Draw не возвращает значения, поэтому тело этой функии пустое, а функция Valid возвращает логическое значение, поэтому мы написали return true. Такая ситуация является типичной, поэтому должет быть механизм, позволяющий не делать пустые функции или функции с одним оператором return, т.к. они не нужны, потому что никогда не вызываются. Опустить эти функции в базовом классе тоже нельзя, т.к. в этом случае не будет работать механизм виртуальных функции. Для реализации такого механизма применяются абстрактные функции. Функция называется абстрактной, если ее реализация отсутствует. Такие функции встречаются исключительно в базовых классах и переопределяются в производных. 44
Синтаксис абстрактной функции в языке C# следующий: abstract тип_возвращаемого_значения имя_функции ( параметры ); В производных классах абстрактные функции переопределяются точно также, как и виртуальные. Класс, в котором есть хотя бы одна абстрактная функция, называется абстрактным и должен иметь описатель abstract. Невозможно создать объект абстрактного класса через операцию new, т.к. не все его методы определены. В нашем примере класс MyShape и использованием абстрактных функций будет иметь следующий вид: abstract class MyShape { protected int x; // x-координата базовой точки protected int y; // y-координата базовой точки protected int r; // размер фигуры protected static int _CurShape = 0; // номер текущей фигуры protected int _Type; // тип фигуры: 1-окружность, 2-квадрат public int X // Свойство для работы с x-координатой { Текст свойства Х, см выше } // public int X public int Y // Свойство для работы с x-координатой { Текст свойства Y, см выше } // public int Y public int R // Свойство для работы с x-координатой { Текст свойства R, см выше } // public int R public static int CurShape { // Свойство для доступа к номеру тек. фиг. Текст свойства CurShape, см выше } // public static int CurShape public int Type { // Свойство для доступа к типу фигуры Текст свойства Type, см выше } // public int Type public MyShape(int _x, int _y, int _r) // Конструктор фигуры { Текст конструктора, см выше } // Конструктор public void Left() // Метод перемещения фигуры влево на 10 пикселей { // Текст метода Left, см выше } // Left
45
public void Right() // … вправо … { Текст метода Right, см выше } // Right public void Up() // … влево … { Текст метода Up, см выше } // Up public void Down() // … вниз … { Текст метода Down, см выше } // Down abstract public bool Valid(int _x, int _y, int _r); abstract public void Draw(Graphics g, bool IsCur); } // class MyShape
3.4. Полиморфизм Полиморфизмом называется способность функции единообразно обрабатывать данные разных типов. Существуют два основных вида полиморфизма: параметрический полиморфизм и полиморфизм подтипов. Параметрический полиморфизм заключается в перегрузке функций и методов. В языке С++ он может быть не связан с классами, т.к. функции С++ могут не относиться к классам. В языке C# все функции относятся к классам и являются методами. Прегруженными называются методы имеющие одинаковое имя, но разное количество или тип аргументов. Вызов того или иного конкретного метода происходит по количеству или типу аргументов. Один из примеров перегруженных методов приведен в пункте 2.6. Однако, рассмотренная там перегрузка методов, является часть большого примера, поэтому ее рассмотрение не наглядно. В качестве более простого и наглядного примера рассмотрим метод Output, который выводит в textBox данные разных типов, например целые, вещественные и текстовые. Метод Output для целого типа будет иметь следующий вид: private void Output(TextBox tb, int s) { tb.Text = Convert.ToString(s); } Метод Output для вещественного типа: private void Output(TextBox tb, double s) { tb.Text = Convert.ToString(s); }
46
Метод Output для текстового типа: private void Output(TextBox tb, String s) { tb.Text = s; } Все три метода имеют одинаковое имя Output и два параметра, отличаются они типом второго параметра. Для вызова этих пегруженных методов создадим проект, состоящий из одной формы, в событие Load формы запишем 3 вызова метода Output: Output(textBox1, 3); Output(textBox2, 3.5); Output(textBox3, "3a"); Первый вызов в качестве второго параметра содержит целое число 3, поэтому будет вызван первый из перегруженных методов (программисты говорят: первая перегрузка). Второй вызов содержит в качестве второго параметра вещественное число 3.5, поэтому будет вызван второй из перегруженных методов. Третий вызов содержит в качестве второго параметра строку "3a", поэтому будет вызван третий из перегруженных методов. В результате использования данной технологии мы может одинаковыми методами выводить информацию разных типов, не задумываясь о конвертации данных. Полиморфизм подтипов связан с использованием базового и производных классов и виртуальных или абстрактных функции. При вызове виртуальных или абстрактных функции базового класса вызывается одна из переопределенных в производном классе функции. Примером такого полиморфизма являются функции Draw и Valid из примера о перемещении фигур.
3.5. Изолированные классы Иногда требуется сделать так, чтобы класс никогда не был базовым, такой класс называется изолированным. Что сделать класс изолированным, необходимо в определении класса использовать ключевое слово sealed. В качестве примера приведем изолированный класс «тип фигуры» с полями «название типа» и «количество углов»: sealed class TypeShape { private String _ShapeName; // Название фигуры private int _Number; // Кол-во углов public String ShapeName { get {return _ShapeName;} set {_ShapeName=value;}} public int Number { get {return _ Number;} set {_ Number =value;}}
47
public TypeShape(__ShapeName, __Number) { _ShapeName = __ ShapeName; _Number = __Number; } }
3.6. Множественное наследование В языке С++ производный класс может иметь несколько базовых. Такое наследование называется множественным. В языке C# такой возможности нет, однако множественное наследование можно реализовать с помощью интерфейсов. Интерфейс представляет собой набор свойств и методов без их реализации. Реализацию должен обеспечить класс, который реализовывает интерфейс. Интерфейс может содержать только сигнатуры (имя и типы параметров) своих элементов. Интерфейс не может содержать конструкторы, поля, константы, статические члены. Создавать объекты интерфейса невозможно. Интерфейс – это единица уровня класса, он объявляется за пределами класса, при помощи ключевого слова interface, например так: interface IA { int a { get; set; } // Сигнатура свойства a int fa(); // Сигнатура метода fa } Имена интерфейсам принято давать, начиная с префикса «I», чтобы сразу отличать класс от интерфейса. Чтобы указать, что класс реализовывает интерфейс, необходимо, так же, как и при наследовании, после имени класса и двоеточия указать имя интерфейса. Данный класс должен предоставить реализацию всех членов интерфейса, например так: class A : IA { private int _a; public A(int __a) { _a = __a; } public int a { get { return _a; } set { _a = value; } } public int fa() {return _a+1;}
48
// Реализация свойства a // Реализация метода fa
} // class A Класс может наследоваться более чем от одного интерфейса. Например так: interface IA { int a { get; set; } // Сигнатура свойства a int fa(); // Сигнатура метода fa } interface IB { int b { get; set; } // Сигнатура свойства b int fb(); // Сигнатура метода fb } class C : IA, IB { private int _c; // Индивидуальные данные класса С public int a { get; set; } // Реализация свойства a public int b { get; set; } // Реализация свойства b public int fa() { return a+1; } // Реализация метода fa public int fb() { return b-1; } // Реализация метода fb } В производном классе все методы базовых интерфейсов должны быть реализованы. Если от базовых интерфейсов наследуется несколько классов, то в каждом из производных классах все эти методы должны быть реализованы. В случае, когда реализация этих методов для разных приизводных классов различна, то так и должно быть, но иногда бывает одинаковая реализация и хочется вынести реализацию в базовый класс. Для этого существует так называемый метод расширения интерфейсов. Он реализуется классом InterfaceExtensions. Все базовые методы всех интерфейсов включаются в этот класс, в свой интерфейс и в производный класс они не включаются. Чтобы определить, к какому интерфейсу относится метод, первый параметр метода является объектом интерфейса (в других местах объект интерфейса создать невозможно, только здесь). В качестве примера рассмотрим предыдущий пример с добавленными методами ga и gb, реализованными с помощью метода расширения. interface IA { int a { get; set; } // Сигнатура свойства a int fa(); // Сигнатура метода fa }
49
interface IB { int b { get; set; } // Сигнатура свойства b int fb(); // Сигнатура метода fb } static class InterfaceExtensions { public static int ga(this IA xa) { return xa.a+2; } public static int gb(this IB xb) { return xb.b-2; } } class C : IA, IB { private int _c; // Индивидуальные данные класса С public int a { get; set; } // Реализация свойства a public int b { get; set; } // Реализация свойства b public int fa() { return a+1; } // Реализация метода fa public int fb() { return b-1; } // Реализация метода fb } Несмотря на то, что методы ga и gb не описаны ни в классе С ни в базовых интерфейсах, они доступны в объектах класса С. Например: C c = new C(); c.a = 10; int n = c.ga();
// Создаем объект класса С // Задаем свойство a равным 10 // Вызывает метод ga, n будет равным 12
В заключении рассмотрим пример, который делает тоже самое, но без интерфейсов и расширений. Вместо интерфейсов опишем классы А и В. Класс С будет не производным от А и В, а будет содержать объекты этих классов. class A { public int a { get; set; } public int fa() {return a+1;} public int ga() {return a+2;} }
50
class B { public int b { get; set; } public int fb(){return b-1;}; public int gb() {return b-2;} } class C { private A _a; private B _b; public C() // Конструктор, в нем создаем объекты классов А и В { _a = new A(); _b = new B(); } public A a {get {return _a;} set {_a = value;}} // Свойства для доступа public В в {get {return _b;} set {_b = value;}} // к объектам классов А и В public int fa() {return _a.fa();} // При необходимости можно public int ga() {return _a.ga();} // переопределить эти методы public int fb() {return _b.fb();} public int gb() {return _a.gb();} private int _c; // Индивидуальные данные класса С } Использование класса С несколько изменится: C c = new C(); c.а.a = 10; int n = c.а.ga();
// Создаем объект класса С // Задаем свойство a класса а равным 10 // Вызывает метод ga класса а, n будет равным 12
3.7. Контрольные вопросы 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
Какой класс называется базовым. Какой класс называется производным. Что такое наследование классов. Что такое виртуальная функция. Что такое переопределенная функция. Что такое абстракная функция. Что такое абстрактный класс. Что такое параметрический полиморфизм. Что такое полиморфизм подтипов. Что такое изолированный класс. Что такое множественное наследование. Что такое интерфейс.
51
Заключение Данное учебное пособие посвящено изучению основ разработки программных средств с использование объектно-ориентированного подхода. Пособие состоит из трех глав. В первой главе пособия описаны элементы и конструкции языка C#, не связанные с объектно-ориентированный программированием, т.к. они понадобятся в последующих главах для реализации примеров построения программ. Во второй главе рассматривается понятие класса и связанных с ними понятий: свойства, методы, конструкторы, деструкторы. Также рассмотрено понятие инкапсуляции, как возможность группировать данные и скрывать внутри класса свойства и методы, малозначимые вне класса. В третьей главе описаны способы наследования классов и связанные с наследованием понятия: базовый и производный классы, полиморфизм – возможность единообразно обрабатывать данные различных типов и классов. В завершении третьей главы рассмотрено множественное наследование и возможные его реализации в языке C# В пособии рассмотрены практические примеры разработки программных средств с использование объектно-ориентированного подхода на языке программирования C#. Пособие ориентировано на студентов направления 230700.62 «Прикладная информатика», изучающих предмет «Объектно-ориентированное программирование». Для изучения данного предмета требуются знания следующих предметов: «Информатика и программирование» и «Высокоуровневые методы информатики и программирования». Знания, полученные при изучении данного предмета, могут быть использованы для изучения других предметов: «Интернет-программирование» и «Управление информационными системами».
52
Библиографический список Основной 1. Хорев, П. Б. Объектно-ориентированное программирование : учебное пособие для вузов по направлению "Информатика и вычислительная. техника" / П. Б. Хореев – М. : Академия , 2011 – 448 с. 2. Новиков, П. В. Объектно-ориентированное программирование : учебное пособие к лаб. работам / П. В. Новиков ; Московский авиационный институт (государственный технический университет) – М. : Издательство МАИ , 2007 – 45 с. 3. Шилдт, Г. С# 4.0 : полное руководство / Герберт Шилдт ; пер. с англ. и ред. И. В. Бернштейна .– М.: Вильямс, 2011. 4. Шилдт, Г. C# Учебный Курс / Герберт Шилдт ; пер. с англ. и ред. И. В. Бернштейна . – М.: Вильямс, 2003. – 471 с. Дополнительный 5. Иванова Г. С., Объектно-ориентированное программирование : учеб. для вузов по направлению "Информатика и вычислительная техника" / Г. С. Иванова, Т. Н. Ничушкина, Е. К. Пугачев ; под ред. Г. С. Ивановой – М. : МГТУ им. Н. Э. Баумана , 2007 – 366 c. 6. Кьоу, Д. Объектно-ориентированное программирование : Учеб. курс / Д. Кьоу, М. Джеанини; Пер. с англ. В. Щербинина – СПб. и др. : Питер , 2005 – 237 с. 7. Бадд, Т. Объектно-ориентированное программирование в действии / Пер. с англ. А. Бердникова – СПб. : Питер , 1997 – 460 с. 8. Моррис, С. Объектно-ориентированное программирование / Пер. с англ. Ю. Г. Синдеева – Ростов на Дону : Феникс , 1997 – 350 с.
53
Оглавление Введение ....................................................................................................................... 3 1. Конструкции языка С#, не связанные с ООП 1.1. Типы данных в языке C# .................................................................................. 4 1.2. Литералы ............................................................................................................ 4 1.3. Переменные ....................................................................................................... 5 1.4. Операции ............................................................................................................ 5 1.5. Оператор присваивания.................................................................................... 6 1.6. Условные операторы ...................................................................................... 12 1.6.1. Оператор if ................................................................................................ 12 1.6.2. Оператор switch ........................................................................................ 15 1.7. Циклы ............................................................................................................... 17 1.8. Массивы ........................................................................................................... 20 1.9. Многомерные массивы ................................................................................... 24 1.10. Цикл прохода по массиву............................................................................. 25 1.11. Неопределенные значения переменных ..................................................... 25 1.12. Контрольные вопросы .................................................................................. 26 2. Классы 2.1. Определение класса. Инкапсуляция. ............................................................ 28 2.2. Конструктор, деструктор ............................................................................... 29 2.3. Свойства ........................................................................................................... 31 2.4. Создание нескольких экземпляров объектов ............................................... 32 2.5. Статические элементы классов ..................................................................... 34 2.6. Перегруженные методы ................................................................................. 35 2.7. Передача параметров по ссылке. ................................................................... 36 2.8. Контрольные вопросы .................................................................................... 37 3. Наследование классов. Полиморфизм 3.1. Базовый и производные классы..................................................................... 38 3.2. Виртуальные функции .................................................................................... 43 3.3. Абстрактные функции, свойства и классы................................................... 44 3.4. Полиморфизм. ................................................................................................. 46 3.5. Изолированные классы................................................................................... 47 3.6. Множественное наследование. ...................................................................... 48 3.7. Контрольные вопросы. ................................................................................... 51 Заключение................................................................................................................. 52 Библиографический список ...................................................................................... 53
54
Учебное издание
Сартасов Евгений Михайлович ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ Учебное пособие
Техн. редактор А.В. Миних Издательский центр Южно-Уральского государственного университета Подписано в печать 15.12. 2014. Формат 60×84 1/16. Печать цифровая. Усл. печ. л. 3,25. Тираж 30 экз. Заказ 594/457. Отпечатано в типографии Издательского центра ЮУрГУ. 454080, г. Челябинск, пр. им. В.И. Ленина, 76.