Шаг 150.
Язык программирования C#. Начала
Наследование. Примеры использования
На этом шаге мы рассмотрим несколько примеров решения задач, в которых используется наследование.
Здесь мы рассмотрим несколько программ, в которых используются ранее рассмотренные свойства и ииндексаторы.
Задание 1.Напишите программу, в которой использована цепочка наследования из трех классов. В первом классе есть открытое символьное поле. Во втором классе
появляется открытое текстовое поле. В третьем классе появляется открытое целочисленное иоле. В каждом из классов должен быть конструктор, позволяющий создавать объект на основе
значений полей, переданных аргументами конструктору, а также конструктор создания копии.
Раскрыть/скрыть решение и комментарии.
Программа, решающая данную задачу, достаточно простая. За основу мы взяли программу из 145 шага и немного ее изменили. Вот ее текст.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr150_1
{
// Базовый класс:
class Alpha
{
// Открытое поле:
public char alpha;
// Виртуальный метод:
public virtual void show()
{
Console.WriteLine("Класс Alpha: {0}", alpha);
}
// Конструктор с одним аргументом:
public Alpha(char a)
{
alpha = a;
}
// Конструктор создания копии:
public Alpha(Alpha obj)
{
alpha = obj.alpha;
}
}
// Производный класс от класса Alpha:
class Bravo : Alpha
{
// Открытое поле:
public string bravo;
// Переопределение виртуального метода:
public override void show()
{
Console.WriteLine("Класс Bravo: {0} и {1}", alpha, bravo);
}
// Конструктор с двумя аргументами:
public Bravo(char a, String b)
: base(a)
{
bravo = b;
}
// Конструктор создания копии:
public Bravo(Bravo obj)
: base(obj)
{
bravo = obj.bravo;
}
}
// Производный класс от класса Bravo:
class Charlie : Bravo
{
// Открытое поле:
public int charlie;
// Переопределение виртуального метода:
public override void show()
{
Console.WriteLine("Класс Charlie: {0}, {1} и {2}", alpha, bravo, charlie);
}
// Конструктор с тремя аргументами:
public Charlie(char a, string b, int c)
: base(a, b)
{
charlie = c;
}
// Конструктор создания копии:
public Charlie(Charlie obj)
: base(obj)
{
charlie = obj.charlie;
}
}
// Класс с главным методом:
class Program
{
static void Main()
{
// Главный метод:
// Создание объекта класса Alpha:
Alpha objA = new Alpha('A');
Alpha objA_copy = objA;
// Проверка значения поля:
objA.show();
Console.WriteLine("Работает конструктор копирования:");
objA_copy.show();
Console.WriteLine();
// Создание объекта класса Bravo:
Bravo objB = new Bravo('A', "BCDE");
// Объектной переменной базового класса присваивается
// ссылка на объект производного класса:
objA = objB;
// Проверка значений полей:
objA.show();
objB.show();
Bravo objB_copy = objB;
Console.WriteLine("Работает конструктор копирования:");
objB_copy.show();
Console.WriteLine();
// Создание объекта класса Charlie:
Charlie objC = new Charlie('F', "GHIJ", 10);
// Объектной переменной базового класса присваивается
// ссылка на объект производного класса:
objA = objC;
objB = objC;
// Проверка значений полей:
objA.show();
objB.show();
objC.show();
Charlie objC_copy = objC;
Console.WriteLine("Работает конструктор копирования:");
objC_copy.show();
// Задержка:
Console.ReadLine();
}
}
}
Архив проекта можно взять
здесь.
Результат работы приложения приведен на рисунке 1.
Рис.1. Результат работы приложения
Единственное, что здесь может вызвать затруднение, так это создание конструктора копирования в производных классах. Нужно не забывать вызывать конструктор копирования
из базовх классов, что и сделано путем использования конструкции base в заголовке конструктора копирования. Остальные комментарии можно посмотреть на 145 шаге.
Задание 2.
Напишите программу, в которой есть базовый класс с открытым целочисленным полем. В классе описан виртуальный индексатор, позволяющий считывать цифры в десятичном
представлении числа (значение поля). На основе базового класса создается производный класс, в котором появляется еще одно открытое целочисленное поле. В производном классе
описывается версия индексатора с двумя индексами: первый индекс определяет поле, значение которого используется, а второй индекс определяет разряд, для которого считывается цифра.
Индексатор с одним индексом переопределяется так, что вычисления (считывание цифры в десятичном представлении числа) производятся на основе значения, равного сумме значений
полей индексируемого объекта.
Раскрыть/скрыть решение и комментарии.
Приведем текст программы, решающей указанную задачу:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr150_2
{
// Базовый класс:
class Alpha
{
// Целочисленное поле:
public int ch1;
// Виртуальный целочисленный индексатор
// с целочисленным индексом:
public virtual int this[int k]
{
// Аксессор для считывания значения:
get
{
// Сохраняем значение поля:
int t = ch1;
int kl = 0, ch = 0;
// Отсчитываем с конца требуемую цифру:
while (kl < k) {
kl++;
ch = t % 10;
t = t / 10;
}
// Возвращаем результат:
return ch;
}
}
}
// Производный класс:
class Bravo : Alpha {
// Целочисленное поле:
public int ch2;
// Определение индексатора с двумя
// целочисленными индексами:
public int this[int ch, int k]
{
// Аксессор для считывания значения:
get
{
// Определяем, из какого поля
// берем значение:
int t;
if (ch == 1) t = base.ch1;
else t = ch2;
int kl = 0, c = 0;
// Отсчитываем с конца требуемую цифру:
while (kl < k) {
kl++;
c = t % 10;
t = t / 10;
}
// Возвращаем результат:
return c;
}
}
// Переопределение индексатора с целочисленным индексом:
public override int this[int k]
{
// Аксессор для считывания значения:
get
{
// Складываем значения полей:
int t = base.ch1 + ch2;
int kl = 0, ch = 0;
// Отсчитываем с конца требуемую цифру:
while (kl < k) {
kl++;
ch = t % 10;
t = t / 10;
}
// Возвращаем результат:
return ch;
}
}
}
// Класс с главным методом:
class Program
{
// Главный метод:
static void Main()
{
// Создаем объект класса Alpha:
Alpha A = new Alpha();
// Определяем значение поля:
A.ch1 = 87615;
// Выводим значения поля и
// индексатора:
Console.WriteLine("А.ch1 = " + A.ch1);
Console.WriteLine("А[2] = " + A[2]);
Console.WriteLine();
// Создаем объект класса Bravo:
Bravo B = new Bravo();
// Определяем значения полей:
B.ch2 = 23456;
B.ch1 = 12345;
// Выводим значения полей и
// индексаторов:
Console.WriteLine("B.ch1 = " + B.ch1);
Console.WriteLine("B.ch2 = " + B.ch2);
Console.WriteLine("B[1, 4] = " + B[1, 4]);
Console.WriteLine("B[2, 3] = " + B[2, 3]);
Console.WriteLine();
int s = B.ch1 + B.ch2;
Console.WriteLine("B.ch1 + B.ch2 = " + s);
Console.WriteLine("B[3] = " + B[3]);
// Задержка:
Console.ReadLine();
}
}
}
Архив проекта можно взять
здесь.
Результат работы приложения изображен на рисунке 2.
Рис.2. Результат работы приложения
Основная сложность приложения - реализация get-акссесора в числовом индексаторе. Дадим краткие комментарии к его реализации.
В производном классе в индексаторе с двумя целочисленными индексами, первый параметр ch определяет, из какого класса будем брать значение целочисленного поля.
Для того, чтобы показать, что берется поле из базового класса, явно указана конструкция base. В остальном же алгоритм аналогичен использованному в базовом классе.
В производном классе при переодределении индексатора с одним целочисленным индексом сначала складываем значения полей из базового и производного классов, а потом
возвращаем значение требуемого разряда.
В главном методе создаем объекты указанных классов, придаем значения полям и демонстрируем использование созданных в классах методов.
Задание 3. Напишите программу, в которой есть базовый класс с защищенным целочисленным массивом, индексатором (с целочисленным индексом), позволяющим
считывать и присваивать значения элементам массива, а также свойство, возвращающее результатом размер массива. На основе базового класса создается производный класс, у которого
появляется защищенный символьный массив. Опишите в производном классе версию индексатора с символьным индексом, который возвращает значение элемента символьного массива и
позволяет присвоить значение элементу символьного массива. Для свойства из базового класса необходимо выполнить замещение так, чтобы результатом возвращался целочисленный
массив из двух элементов: первый элемент определяет размер целочисленного массива объекта, а второй элемент определяет размер символьного массива объекта
Раскрыть/скрыть решение и комментарии.
Программа, решающая эту задачу, приведена ниже:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace pr150_3
{
// Базовый класс:
class Alpha
{
// Защищенный целочисленный массив:
protected int[] num;
// Конструктор с одним аргументом:
public Alpha(int[] t)
{
// Присваивание значения полю:
num = t;
}
// Целочисленное свойство:
public int number
{
// Аксессор для считывания значения:
get
{
// Возвращает размер массива:
return num.Length;
}
}
// Целочисленный индексатор с целочисленным индексом:
public int this[int k]
{
// Аксессор для считывания значения:
get
{
return num[k];
}
// Аксессор для присваивания значения:
set
{
num[k] = value;
}
}
// Переопределение метода ToString():
public override string ToString()
{
// Текстовая переменная:
string txt = "|";
// Формируется значение текстовой переменной:
for (int k = 0; k < num.Length; k++)
{
txt += num[k] + "|";
}
// Результат метода:
return txt;
}
}
// Производный класс:
class Bravo : Alpha
{
// Защищенный символьный массив:
protected char[] symb;
// Конструктор с двумя аргументами:
public Bravo(int[] t, string txt): base(t){
// Присваивание значения полю:
// Создание массива на основе текстового аргумента:
symb = txt.ToCharArray();
}
// Символьный индексатор с символьным индексом:
public char this[char s] {
// Аксессор для считывания значения:
get {
return symb[s - 'a'];
}
// Аксессор для присваивания значения:
set {
symb[s - 'a'] = (char)value;
}
}
// Замещение свойства:
new public int[] number
{
// Аксессор для считывания значения:
get
{
int
// Аксессор для считывания значения:
get
{
int[] rez = {num.Length, symb.Length};
// Возвращает размер массива:
return rez;
}
}
}
// Класс с главным методом:
class Program
{
// Главный метод:
static void Main()
{
// Создание и инициализация массива:
int[] numsA = { 1, 3, 5, 7, 6, 5, 4 };
// Создание объекта:
Alpha objA = new Alpha(numsA);
// Проверка содержимого массива объекта:
Console.WriteLine("Объект Alpha: ");
Console.WriteLine(objA);
Console.WriteLine("objA[2] = {0}", objA[2]);
objA[2] = 200;
Console.WriteLine("Измененное значение objA[2] = {0}", objA[2]);
Console.WriteLine();
int[] numsB = { 10, 30, 50 };
// Создание объекта:
Bravo objB = new Bravo(numsB,"KLMNOP");
// Проверка содержимого объекта:
Console.WriteLine("Объект Bravo: ");
Console.WriteLine("objB[2] = {0}", objB[2]);
objB[2] = 400;
Console.WriteLine("Измененное значение objB[2] = {0}", objB[2]);
// Индексирование объекта:
objB['a'] = 'X';
objB['d'] = 'Y';
Console.WriteLine("objB[\'a\'] = " + objB['a']);
Console.WriteLine("objB[\'d\'] = " + objB['d']);
Console.WriteLine();
int[] masB = objB.number;
Console.WriteLine("objB.number = {0}, {1}", masB[0], masB[1]);
Console.WriteLine();
// Проверка содержимого массива объекта:
Console.WriteLine(objB);
// Задержка:
Console.ReadLine();
}
}
}
Архив проекта можно взять
здесь.
Результат работы приложения изображен на рисунке 3.
Рис.3. Результат работы приложения
При реализации этой программы иы кое-что взяли из примера приложения, приведенного на 148 шаге.
Попробуйте разобраться с ней самостоятельно.
На этом первая часть нашего изложения закончена. Мы познакомились с основными, наиболее важными темами. Это основа, ядро языка С#. На данном этапе вы уже вполне
подготовлены для написания сложных программ. Тем не менее точку в изучении языка С# мы не ставим. Впереди еще много полезного и интересного. Планы у нас большие, и
их воплощению посвящена следующий раздел.
Во второй части обсуждаются несколько фундаментальных концепций, которые в полной мере раскрывают красоту и мощь языка С#. Там мы познакомимся с интерфейсами и
абстрактными классами. Узнаем, что такое делегаты, ссылки на методы, лямбда-выражения и анонимные методы, познакомимся с событиями. Познакомимся с перечислениями и выясним,
чем структуры отличаются от классов. Мы научимся с помощью указателей выполнять операции с памятью. Узнаем, как в языке C# реализуется перехват и обработка ошибок.
Для нас откроются секреты создания многопоточных приложений. Мы научимся использовать обобщенные типы и сможем создавать приложения с графическим интерфейсом. Еще будут
операции с файлами, знакомство с коллекциями и многое другое. Так что работа предстоит большая и интересная.
На следующем шаге мы перечислим темы, изучением которых планируем заняться в дальнейшем.
Предыдущий шаг
Содержание
Следующий шаг