Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Лабораторная работа № 12. Классы в C#. Операции класса
Цель: формирование целостного представления о сущности и назначении классов, основных операциях классов, овладение практическими навыками работы с классами в C#.
Необходимые теоретические сведения
В C# классы могут выполнять роль модулей или типов данных.
Класс как модуль представляет собой основную архитектурную Единицу программной системы. В C# любую программу, выстроенную в объектном типе, можно представить как совокупность классов, которые могут быть объединены в проекты, пространства имен, решения и т.п.
При этом одни и те же задачи могут решать программы, состоящие как из одного, так и из нескольких модулей.
Класс как тип данных задает реализацию некоторой абстракции данных, характерной для задачи, в интересах которой создается программа.
С этой точки зрения состав класса определяется той абстракцией данных, которую должен реализовать класс. Так, например, в классе Банковский_счет нельзя добавить поля класса Автомобиль.
В C# разработка программных систем предполагает реализацию принципа проектирования от данных, когда проектирование системы сводится к поиску абстракций данных, подходящих для конкретной задачи. Каждая из этих абстракций реализуется в виде класса.
Объявляются классы с использованием ключевого слова class:
<модификатор> class ИмяКласса: <ИмяРодителя>, <Интерфейсы>
{ // Члены класса }
Модификаторы доступа к классу
Модификатор |
Назначение |
Отсутствует или internal |
Доступ к классу может быть получен только из текущего класса |
public |
Общедоступный |
abstract / internal abstract |
Доступ к классу только из текущего проекта / + не допускает создания экземпляров |
public abstract |
Общедоступный, не допускает создания экземпляров |
sealed / internal sealed |
Не допускает наличия потомков-классов / + доступ только из текущего проекта |
Абстрактный класс (abstract) не допускают создания экземпляров, позволяет только наследовать от него другие классы, может иметь абстрактные типы.
Герметизированный класс (sealed) не допускает наследования от него никаких других классов.
<Имя_родителя> - ссылка на базовый класс. В C# базовый класс может быть только один. Производный класс не может быть более доступным, чем базовый:
Верно! |
Неверно! |
public class Баз_класс {// Члены класса} internal class Произв_класс: Баз_класс {// Члены класса} |
internal class Баз_класс {// Члены класса} public class Произв_класс: Баз_класс {// Члены класса} |
В определении класса могут указываться поддерживаемые интерфейсы (перечисляются только после определения базового класса, если таковой имеется!):
public class Класс_Б : Класс_А, Инт_1, Инт_ 2.
Объекты создаются явным или неявным образом, то есть либо программистом, либо системой. Программист создает экземпляр класса с помощью операции new, например:
Demo a = new Demo (); // Создается экземпляр класса Demo
Если достаточный для хранения объекта объем памяти выделить не удалось, то генерируется исключение OutOfMemoryException.
В теле класса могут быть объявлены:
Основу класса составляют его конструкторы, поля и методы.
Поле переменная экземпляра, представляющая собой данные конкретного объекта (по сути это простая переменная).
Содержательно поля задают представление той абстракции, которую реализует класс. Два объекта одного и того же класса будут иметь одинаковый набор полей, но разниться значениями этих полей.
Например, класс Персона может иметь поле Рост, значения которого для разных объектов будут Низкий, Средний, Высокий.
Объявляются поля как обычные переменные:
class Мой_класс
{
int Число;
string Строка;
static void Main()
{ // }
}
Доступ к полям определяется соответствующим модификатором: public, private (модификатор по умолчанию, т.е. все поля класса по умолчанию являются закрытыми), protected (защищенные поля доступны для методов производных классов), internal.
Независимо от значения атрибута, все поля класса доступны для всех методов данного класса.
Упражнение 1. Создание класса
class Circle
{ public int x=0;
public int y=0;
public int radius=3;
public const double pi = 3.14;
public static string name = "Окружность";
double p;
double s;
}
class Program
{ static void Main()
{ Circle cr = new Circle(); // создание экземпляра класса
Console.WriteLine("pi="+Circle.pi);// обращение к константе
Console.Write(Circle.name); // обращение к статическому полю
//обращение к обычным полям
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", cr.x, cr.y, cr.radius);
// Console.WriteLine(cr.p); - вызовет ошибку, т.к. поля p, c имеют тип private
Console.Write("Введите коэффициент= ");
int kof = int.Parse(Console.ReadLine());
cr.x -= kof; cr.y += kof; cr.radius *= kof;
Console.WriteLine(" Новая окружность с центром в точке ({0},{1}) и радиусом {2}", cr.x, cr.y, cr.radius);
//cr.s = 2 * Circle.pi * cr.radius; - вызовет ошибку, т.к. поля s, c имеют тип private
}}
Методы класса содержат описания операций, доступных над объектами класса. Атрибуты: public, private (по умолчанию), protected, internal.
Модификатор |
Назначение |
public (общедоступный) |
Метод доступен отовсюду |
private (приватный) |
Доступен только из класса, в котором описан |
protected (защищенный) |
Доступен из класса, в котором описан, а также из любых производных классов |
internal (внутренний) |
Доступен из всех классов внутри сборки, в которой он описан |
protected internal |
Действует как protected или internal |
Методы находятся в памяти в единственном экземпляре и используются всеми объектами одного класса совместно, поэтому необходимо обеспечить работу методов нестатических экземпляров с полями именно того объекта, для которого они были вызваны. Для этого в любой нестатический метод автоматически передается скрытый параметр this, в котором хранится ссылка на вызвавший функцию экземпляр.
В явном виде параметр this применяется для того, чтобы возвратить из метода ссылку на вызвавший объект, а также для идентификации поля в случае, если его имя совпадает с именем параметра метода
Упражнение 2. Работа с методами класса.
class Circle
{ public int x=0;
public int y=0;
public int radius=3;
public const double pi = 3.14;
public static string name = "Окружность";
public Circle T() // метод возвращает ссылку на экземпляр класса
{
return this;
}
public void Set(int x, int y, int r)
{
this.x = x;
this.y = y;
radius=r;
}
}
class Program
{ static void Main()
{
Circle cr = new Circle(); //создание экземпляра класса
Console.WriteLine("pi="+Circle.pi); // обращение к константе
Console.Write(Circle.name); // обращение к статическому полю
//обращение к обычным полям
Console.WriteLine(" с центром в точке ({0},{1}) и радиусом {2}", cr.x, cr.y, cr.radius);
cr.Set(1, 1, 10);
Console.WriteLine("Новая окружность с центром в точке ({0},{1}) и радиусом {2}",
cr.x, cr.y, cr.radius);
Circle b=cr.T();//получаем ссылку на объект cr, аналог b=c
Console.WriteLine("Новая ссылка на окружность с центром в точке ({0},{1}) и радиусом {2}", b.x, b.y, b.radius);
}
}
Методы-свойства (Propertis) поля, которые, с одной стороны, доступны для использования, с другой стороны, имеют ограничения в отношении действий, выполняемых над этим полем. При работе со свойствами объекта для реализации доступа могут быть выбраны следующие стратегии:
Открытость свойств (public) позволяет реализовать только 1-ую стратегию.
Синтаксис метода-свойства:
public возвращаемый_тип_значения Имя_метода-свойства
{ // Тело метода свойства
[get код_доступа]
[set код_доступа] }
Имя_метода-свойства обычно близко к имени поля.
Код_доступа представляет собой блоки операторов, которые выполняются при получении (get) или установке (set) свойства. Метод get возвращает текущее значение свойства, метод set помещает в свойство новое значение, хранящееся в служебной переменной со стандартным именем value.
Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, то свойство доступно только для чтения. Если отсутствует часть get, то свойство доступно только для записи.
Пример 1. В классе Person создать строковое поле fam и свойство Fam строкового типа:
class Person
{
string fam = ""; // Закрытое поле
public string Fam // Открытое свойство
{
get {return fam;} // Получение значения свойства
set {fam = value;} // Установка значения
}
}
Привлечение свойства Fam в примере 1 никак не влияет на характер доступа к полю fam: проще сделать открытым само поле.
Если, например, поле fam должно получать значение лишь один раз, метод set может быть таким:
set { i f(fam == "") fam = value;}
Пример 2. Добавить в класс Person закрытое вещественное поле salary (жалованье) и одноименное открытое свойство (доступно только для записи).
class Person
{
float salary = 0; // Закрытое поле
public float Salary // Открытое свойство только для записи
{
// Отсутствие метода get в свойстве Salary легко делает это поле доступным только для записи
set {salary = value;}}
}
}
Примечание. Закрытые поля доступны методам класса Person, поэтому поле salary может учитываться в них.
Упражнение 3. Имеется класс Персона, у которого есть следующие поля: фамилия (может быть задана 1 раз), статус (доступно только для чтения), зарплата (недоступна для чтения), возраст (доступно для чтения и записи), здоровье (закрыто для доступа, информация о здоровье может быть сообщена только специальными методами класса).
namespace ConsoleApplication1
{public class Персона
{ //Все поля закрыты
string фамилия = "", статус = "", здоровье = "";
int возраст = 0, зарплата = 0;
//Методы-свойства
//Чтение, запись при первом обращении (Read, Write-once)
public string Фамилия
{ set { if (фамилия == "") фамилия = value; }
get { return (фамилия); }
}
//стратегия - только чтение (Read-only)
public string Статус
{ get { return (статус); }}
//Стратегия - чтение и запись (Read, Write)
public int Возраст
{set
{ возраст = value;
if (возраст < 7) статус = "ребенок";
else if (возраст < 17) статус = "школьник";
else if (возраст < 22) статус = "студент";
else статус = "служащий"; }
get { return (возраст); }}
//Стратегия - только запись (Write-only)
public int Зарплата
{ set { зарплата = value; }}}
class Program
{ static void Main()
{Персона персона_1 = new Персона();
персона_1.Фамилия = "Петров";
персона_1.Возраст = 21;
персона_1.Зарплата = 1000;
Console.WriteLine("Фамилия = {0}, возраст = {1} лет, статус = {2}", персона_1.Фамилия, персона_1.Возраст, персона_1.Статус);
персона_1.Фамилия = "Иванов"; персона_1.Возраст += 1;
Console.WriteLine("Фамилия = {0}, возраст = {1} лет, статус = {2}", персона_1.Фамилия, персона_1.Возраст, персона_1.Статус);
Console.ReadKey();
}}}
Результат работы программы:
Операции классов
С# позволяет переопределить большинство операций так, чтобы при использовании их объектами конкретного класса выполнялись действия, отличные от стандартных. Это дает возможность применять объекты собственных типов данных в составе выражений, например:
newObject x, y, z;
…
z = x+y; // используется операция сложения, переопределенная для класса newObject
Определение собственных операций класса называют перегрузкой операций. Перегрузка операций обычно применяется для классов, для которых семантика операций делает программу более понятной. Если назначение операции интуитивно непонятно, перегружать такую операцию не рекомендуется.
Операции класса описываются с помощью методов специального вида, синтаксис которых выглядит следующим образом:
[ атрибуты] спецификаторы объявитель_операции
{ тело }
В качестве спецификаторов одновременно используются ключевые слова public и static. Кроме того, операцию можно объявить как внешнюю extern. Объявление операции может выглядеть по-разному, в зависимости от того, что мы перегружаем: унарную или бинарную операцию.
При описании операций необходимо соблюдать следующие правила:
Унарные операции
В классе можно переопределять следующие унарные операции: +, -, !, ~, ++, --, а также константы true и false. При этом, если была перегружена константа true, то должна быть перегружена и константа false, и наоборот.
Синтаксис объявителя унарной операции:
тип operator унарная_операция (параметр)
Пример 3. Заголовки унарных операций.
public static int operator + (DemoArray m)
public static DemoArray operator --(DemoArray m)
public static bool operator true (DemoArray m)
Параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. При этом операция должна возвращать для операций:
Упражнение 4. Разработать класс DemoArray, реализующий одномерный массив, в котором содержатся следующие функциональные элементы:
class DemoArray
{ int[] MyArray; // закрытый массив
public DemoArray(int size) // конструктор 1
{ MyArray = new int[size]; }
public DemoArray(params int[] arr) // конструктор 2
{ MyArray = new int[arr.Length];
for (int i=0; i<MyArray.Length; i++) MyArray[i]=arr[i]; }
public int LengthArray // свойство, возвращающее размерность
{ get { return MyArray.Length; }}
public int this[int i] // индексатор
{ get
{ if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
return MyArray[i]; }
set
{ if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
else MyArray[i] = value; }
}
// Перегрузка операции унарный минус
public static DemoArray operator -(DemoArray x)
{ DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = -x[i];
return temp; }
// Перегрузка операции инкремента
public static DemoArray operator ++(DemoArray x)
{ DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = x[i]+1;
return temp; }
// Перегрузка константы true
public static bool operator true(DemoArray a)
{ foreach (int i in a.MyArray)
{ if (i<0)
{ return false; }}
return true;
}
// Перегрузка константы false
public static bool operator false(DemoArray a)
{ foreach (int i in a.MyArray)
{ if (i>0)
{ return true; }}
return false;
}
// Метод выводит поле-массив на экран
public void Print(string name)
{ Console.WriteLine(name + ": ");
for (int i = 0; i < MyArray.Length; i++)
Console.Write(MyArray[i] + " ");
Console.WriteLine();
}
}
class Program
{ static void Main()
{ try
{ DemoArray Mas = new DemoArray(1, -4, 3, -5, 0); //вызов конструктора 2
Mas.Print("Исходный массив");
Console.WriteLine("\nУнарный минус");
DemoArray newMas=-Mas; //применение операции унарного минуса
Mas.Print("Mассив Mas"); // создается объект и знаки меняются
newMas.Print("Массив newMas"); // только у нового массива
Console.WriteLine("\nОперация префиксного инкремента");
DemoArray Mas1 = ++Mas;
Mas.Print("Mассив Mas");
Mas1.Print("Mассив Mas1=++Mas");
Console.WriteLine("\nОперация постфиксного инкремента");
DemoArray Mas2=Mas++;
Mas.Print("Mассив Mas");
Mas2.Print("Mассив Mas2=Mas++");
if (Mas)
Console.WriteLine("\nВ массиве все элементы положительные\n");
else Console.WriteLine("\nВ массиве есть не положительные элементы\n");
}
catch (Exception e)
{ Console.WriteLine(e.Message); }
}
Задание. Добавьте в класс DemoArray переопределение унарного плюса (все элементы массива преобразуются в положительные), унарного декремента (все элементы массива уменьшаются на единицу)
Бинарные операции
При разработке класса можно перегрузить следующие бинарные операции: +, -, *, /, %, &, |, ^, <<, >>, ==, !=, <, >, <=, >=.
Синтаксис объявителя бинарной операции:
тип operator бинарная_операция (параметр1, параметр2)
Пример 2. Заголовки бинарных операций.
public static DemoArray operator + (DemoArray a, DemoArray b)
public static bool operator == (DemoArray a, DemoArray b)
Правила переопределения бинарных операций:
Упражнение 5. Добавить в класс DemoArray (см. упражнение 1), реализующему одномерный массив, две версии переопределенной операции +:
class DemoArray
{
…
//вариант 1
public static DemoArray operator +(DemoArray x, int a)
{ DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i]=x[i]+a;
return temp; }
public static DemoArray operator +(DemoArray x, DemoArray y) //вариант 2
{ if (x.LengthArray == y.LengthArray)
{ DemoArray temp = new DemoArray(x.LengthArray);
for (int i = 0; i < x.LengthArray; ++i)
temp[i] = x[i] + y[i];
return temp; }
else throw new Exception("несоответствие размерностей"); }}
class Program
{ static void Main()
{ try
{ DemoArray a = new DemoArray(1, -4, 3, -5, 0);
a.Print("Массива a");
DemoArray b=a+10; b.Print("\nМассива b");
DemoArray c = a+b; c.Print("\nМассива c"); }
catch (Exception e)
{ Console.WriteLine(e.Message); }}}
Задание. Добавьте в класс DemoArray переопределение бинарного минуса (из всех элементов массива вычитается заданное число) и операции & (поэлементно сравнивает два массива, если соответствующие элементы попарно совпадают, то операция возвращает значение true, иначе false).
Операции преобразования типов
Операции преобразования типов обеспечивают возможность явного и неявного преобразования между пользовательскими типами данных. Синтаксис объявителя операции преобразования типов выглядит следующим образом:
explicit operator целевой_тип (параметр) //явное преобразование
implicit operator целевой_тип (параметр) //неявное преобразование
Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого выполняется преобразование.
Неявное преобразование выполняется автоматически:
Явное преобразование выполняется при использовании операции приведения типа.
При определении операции преобразования типа следует учитывать следующие особенности:
Упражнение 6. Добавить в класс DemoArray, реализующий одномерный массив (см. упражнение 1, 2), неявную версию переопределения типа DemoArray в тип одномерный массив и наоборот.
class DemoArray
{
…
public static implicit operator DemoArray (int []a) //неявное преобразование типа int [] в DemoArray
{ return new DemoArray(a); }
public static implicit operator int [](DemoArray a) //неявное преобразование типа DemoArray в int []
{ int []temp=new int[a.LengthArray];
for (int i = 0; i < a.LengthArray; ++i)
temp[i] = a[i];
return temp; }}
class Program
{ static void arrayPrint(string name, int[]a) //метод, который позволяет вывести на экран одномерный массив
{ Console.WriteLine(name + ": ");
for (int i = 0; i < a.Length; i++)
Console.Write(a[i] + " ");
Console.WriteLine(); }
static void Main()
{ try
{ DemoArray a = new DemoArray(1, -4, 3, -5, 0);
int []mas1=a; //неявное преобразование типа DemoArray в int []
int []mas2=(int []) a; //явное преобразование типа DemoArray в int []
DemoArray b1 = mas1; //неявное преобразование типа int [] в DemoArray
DemoArray b2 = (DemoArray)mas2; //явное преобразование типа int [] в DemoArray
//изменение значений
mas1[0]=0; mas2[0]=-1;
b1[0]=100; b2[0]=-100;
//вывод на экран
a.Print("Массива a");
arrayPrint("Maccив mas1", mas1);
arrayPrint("Maccив mas2", mas2);
b1.Print("Массива b1");
b2.Print("Массива b2"); }
catch (Exception e)
{ Console.WriteLine(e.Message); }}}
Задание. В методе Main используется операция приведения типа DemoArray к int[] (и наоборот) в явном виде, хотя явная версия операции преобразования типа DemoArray к int[] не была определена. Объясните, почему возможно использование явного приведения типа.
Задания для самостоятельного выполнения
PAGE 17