
- •Лекции по курсу "Технология программирования" (1-й семестр) Оглавление
- •Технология .Net Предыдущее состояние дел.
- •Главные компоненты платформы .Net (clr, cts и cls)
- •Общеязыковая среда выполнения (clr)
- •О бщая система типов и общеязыковые спецификации (cts и cls)
- •Библиотека базовых классов
- •Роль языка с#
- •Компоновочные блоки
- •Роль метаданных типов .Net
- •Роль манифеста компоновочного блока
- •Общая система типов.
- •Объектно-ориентированное программирование
- •Главные элементы объектно-ориентированного подхода
- •Дополнительные элементы ооп
- •Принципы объектно-ориентированного программирования.
- •Классы Инкапсуляция
- •Объект (экземпляр класса).
- •Ключевое слово this
- •Отношения между объектами.
- •Основные отличительные особенности класса
- •Спецификаторы доступа
- •Состав класса
- •Поля класса
- •Доступ к полям
- •Статические и экземплярные переменные
- •Методы (функции-члены класса)
- •Переменное число параметров метода
- •Статические методы
- •Конструкторы
- •Закрытые конструкторы или классы без экземпляров
- •Статические конструкторы.
- •Деструкторы
- •Абстрактные методы и классы.
- •Свойства
- •Индексаторы
- •Статические классы
- •Частичные классы
- •Рекомендации по программированию
- •Наследование Понятие наследования в программировании
- •Типы наследования
- •Наследование реализации
- •Определение наследующих классов
- •Уровень доступа protected и internal
- •Ссылка на объект базового класса
- •Протоклассы
- •Предотвращение наследования с помощью ключевого слова sealed.
- •Отношения между классами
- •Абстрактные классы.
- •Класс object
- •Функциональные замыкания
- •Разработка функциональных замыканий с помощью наследования
- •Разработка функциональных замыканий с помощью экземпляров класса
- •Заключение.
- •Полиморфизм
- •Полиморфизм наследующих классов.
- •Переопределение методов родительского класса. Раннее связывание.
- •Виртуальные методы и их переопределение.
- •Как вызывают виртуальные методы
- •Виртуальные функции и принцип полиморфизма
- •Перегрузка.
- •Перегруженные конструкторы
- •Рекомендации программисту.
Определение наследующих классов
Класс в С# может иметь произвольное количество потомков и только одного предка. При описании дочернего класса имя его предка записывается в заголовке класса сразу после двоеточия. Если имя предка не указано, то предком считается базовый класс всей иерархии System.Object:
[ атрибуты ] [ спецификаторы ] class имя класса [ : предки ] тело класса
Например,
//Обьявление базового класса
public class A
{ … };
// Объявление производного класса
public class B : A
{ … };
Класс, который наследуется, называется базовым или дочерним классам. А класс, который наследует базовый класс, называется производным. Производный класс, наследует все переменные, методы, свойства, операторы и индексаторы, определенные в базовом классе, кроме того в производный класс могут быть добавлены уникальные элементы или переопределены существующие. Это означает, что объект типа наследника является также в полной мере и объектом типа базового класса.
Конструкторы базовых классов не наследуются, поэтому производный класс должен иметь собственные конструкторы. Порядок вызова конструкторов определяется следующими правилами:
Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, то автоматически вызывается конструктор базового класса без параметров.
Для иерархии, состоящей из нескольких уровней, конструкторы базовых классов вызываются, начиная с самого верхнего уровня. После этого выполняются конструкторы тех элементов класса, которые являются его объектами, в порядке их объявления в классе, а затем исполняется конструктор класса. Таким образом, каждый конструктор инициализирует свою часть объекта.
Если конструктор базового класса требует указания параметров, он должен быть явным образом вызван в конструкторе производного класса в списке инициализации. Вызов выполняется с помощью ключевого слова base. При этом, вызывается та версия конструктора, список параметров которой соответствует списку аргументов, указанных после слова base.
Поля, методы и свойства класса наследуются, но, если потребуется заменить элемент базового класса новым элементом, то следует явным образом указать компилятору свое намерение с помощью ключевого слова new.
Несмотря на то, что private члены базового класса и наследуются, но при наследовании они не доступны напрямую из своих производных классов. Все остальные (не private) члены базового класса сохраняют свой уровень доступа после их наследования производными классами. Например, public члены базового класса становятся public членами производного класса, а protected члены - protected членами производного класса. В результате объекты всех классов, производных от базового класса, можно рассматривать как объекты этого базового класса.
Методы производных классов могут требовать доступа к не закрытым переменным и методам своего базового класса. Элементы базового класса, которые не должны быть доступными для методов производных классов через наследование, объявляются в базовом классе как private. Производные классы могут изменить состояние закрытых данных базового класса, но только с помощью не закрытых, унаследованных методов базового класса. Не унаследованные методы производного класса не могут получить прямого доступа к private членам их базового класса.
using System;
//Определение класса точки неявно наследует все из класса Object
public class Point
{
// Данные:
private int x, y; // Координаты точки
// Методы:
public Point() // конструктор по умолчанию (без аргументов)
{
// здесь происходит неявное обращение к конструктору Object
x=100; y=100; // если явно не указать значения x и y, то они будут равны 0
}
public Point(int a, int b) // конструктор
{
// здесь происходит неявное обращение к конструктору Object
x=a; y=b;
}
// Открытые функции доступа к закрытым данным
public void SetX(int a){ if(a>=0) x=a; }
public int GetX(){ return x; }
public void SetY(int b){ if(b>=0) y=a; }
public int GetY(){ return y; }
// Функции общего назначения
public void Move(int dx, int dy) { x+=dx; y+=dy; }
}
//Определение класса окружности неявно наследует все из класса Object
public class Circle
{
// Данные:
private int x, y; // координаты центра
private double radius; // радиус окружности
// Методы:
public Circle()// конструктор по умолчанию (без аргументов)
{
// здесь происходит неявное обращение к конструктору Object
x=100; y=100; // если явно не указать значения x и у, то они будут равны 0
}
public Circle(int a, int b, double r) // конструктор
{
// здесь происходит неявное обращение к конструктору Object
x=a; y=b; radius=r;
}
// Открытые функции доступа к закрытым данным
public void SetRad(diuble rad){ radius=rad; }
public int GetRad(){ return radius; }
public void SetX(int a){ x=a; }
public int GetX(){ return x; }
public void SetY(int a){ y=a; }
public int GetY(){ return y; }
// Функции общего назначения
public void Move(int dx, int dy) { x+=dx; y+=dy; }
public double Area(){ … }
}
После описания класса окружности можно легко заметить, что большая часть его содержимого похожа, если не одинакова, на содержимое класса точки. Например, описание переменных x и y, конструкторы и функции public void SetX, GetX, SetY, GetY и Move. Единственными добавлениями к классу окружности являются переменная радиуса (radius) и функция вычисления занимаемой площади Area. Возникает впечатление, что описание класса точки было просто скопировано и вставлено в класс окружности, после чего класс окружности был расширен включением радиуса и функции вычисления площади. Подобный принцип "копирования и вставки" не защищен от возможных ошибок, требует много времени. И, что еще хуже, результатом его может стать чрезмерное количество физических копий существующего в системе программного кода, обслуживание и поддержка которого превращается в "кошмар" для его разработчиков.
Наследование, представляет собой способ "поглощения" характеристик и поведения одного класса таким образом, чтобы они автоматически становились частью других классов. Покажем это на следующем примере, в котором создадим класс окружности, наследующего переменные x и у и функции SetX, GetX, SetY, GetY и Move из класса точки. По сути дела новый класс окружности становится точной копией класса точки, в которую добавлена переменная радиуса и функция вычисления площади - Area. При этом, производный класс наследует от базового класса всё, что он имеет. Другое дело, что воспользоваться в производном классе можно не всем наследством.
class Circle2: Point
{
// Данные:
private double radius; // радиус окружности
// Методы:
public Circle2()// конструктор по умолчанию (без аргументов)
{
// здесь происходит неявное обращение к конструктору Object
// Ошибка! Доступ к закрытым данным базового класса запрещен!
//x=100; y=100;
radius =10;
}
public Circle2(int a, int b, double value) // конструктор
{
// здесь имеет место неявное обращение к конструктору класса Point (x=y=0)
// Ошибка! Доступ к закрытым данным базового класса запрещен!
//x=a; y=b;
radius=value;
}
public double Area(){ … }
}
Теперь класс окружности выглядит намного компактнее. Однако, после компиляции этого программного кода компилятор выдает сообщение об ошибке, которые он обнаруживает в теле конструкторов класса Circle2 с аргументами. Объясняется это тем, что в теле конструктора с аргументами делается попытка прямого использования значений x и y, принадлежащих базовому классу. Дело в том, что производному классу не разрешен доступ к закрытым (private) элементам (x и у) базового класса Point. Эту ошибку легко исправить, если воспользоваться открытыми функциями доступа к закрытым переменным в базовом классе.
class Circle3: Point
{
// Данные:
private double radius; // радиус окружности
// Методы:
public Circle2()// конструктор по умолчанию (без аргументов)
{
// здесь происходит неявное обращение к конструктору Object
// если явно не указать значения x, у и radius, то они будут равны 0
SetX(100); SetY(100); // Ошибки уже нет!
radius=10;
}
public Circle2(int a, int b, double r) // конструктор
{
// здесь имеет место неявное обращение к конструктору класса Point (x=y=0)
SetX(a); SetY(b); // Ошибки уже нет!
radius=10;
}
public int GetRad(){ return radius; }
public void SetRad(double r){ if(r>0) radius=r; }
public double Area(){ … }
}