Студопедия КАТЕГОРИИ: АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Использование класса в качестве типа данных
Классы можно использовать в качестве типов данных как в других классах, так и в функциях. Рассмотрим пример использования одного класса в качестве типа данных в другом классе. namespace Nasl22 { class mas1 { // класс mas1 будет в дальнейшем использован в качестве типа данных protected int[] a; public mas1() { // конструктор int n; Console.Write("Elements "); n = Convert.ToInt32(Console.ReadLine()); a = new int[n]; } public void inpt() { // ввод массива for (int i = 0; i < a.Length; i++) { Console.Write("a[" + i + "]="); a[i] = Convert.ToInt32(Console.ReadLine()); } } int sum() { // нахождение суммы int s=0; for(int i=0;i<a.Length;i++) s+=a[i]; return s; } public int summa { // свойство get { return sum(); } } public int this[int k] { // индексатор get { return a[k];} set { a[k] = value; } } } class cl_a { public mas1 arr1; //объявим переменную типа класс mas1 int sm; public cl_a() { // конструктор класса cl_a, он создает и экземпляр mas1 arr1 = new mas1(); arr1.inpt(); } public int st1() { // обращение к свойству класса mas1 sm=arr1.summa; return sm; } } class Program { static void Main(string[] args) { cl_a my = new cl_a(); int n, m,r; n = my.arr1.summa; //обращение к свойству r = my.st1(); //обращение к собственной функции, // которая в свою очередь обращается к свойству класса mas1 m = my.arr1[2]; // работает индексатор Console.WriteLine("Сумма=" + n + " Сумма="+ r+" элемент [2] =" + m); Console.ReadLine(); } } }
Следующий пример иллюстрирует использование класса в качестве типа данных при работе с функциями: введем пары «имя – шифр» и выведем имя, соответствующее максимальному значению шифра (предположим, что оно единственное). Наличие обеих строк: инициализация массива и инициализация каждого элемента массива в цикле обязательно! namespace FunKlass { class dan { // этот класс будем использовать в качестве типа данных public string s1; public int k; } class Program { static dan[] fun2() { // функция определения количества элементов dan в массиве , // инициализация и ввод массива. int n,m; dan []w; Console.Write("Элементов ? "); n = Convert.ToInt32(Console.ReadLine()); w = new dan[n]; // инициализируем массив for (int i = 0; i < n; i++) { w[i] = new dan(); // инициализируем элемент массива Console.Write("Элемент " + i + " Имя "); w[i].s1 = Console.ReadLine(); Console.Write("Элемент " + i + " Номер "); w[i].k=Convert.ToInt32(Console.ReadLine()); } return w; } static dan fun1(dan[] x) { string t1; int max=0; dan d1=new dan(); for (int i = 0; i < x.Length; i++) { if (max < x[i].k) { max = x[i].k; d1 = x[i]; } } return d1; }
static void Main(string[] args) { dan []b; dan otv; b = fun2(); //ввод массива классов otv = fun1(b); // обработка массива классов Console.WriteLine("Number= " + otv.k + " Name= " + otv.s1); Console.ReadLine(); } } }
Можно использовать и следующие реализации класса dan и функции fun2. class dan { public string s1; public int k; public dan() { Console.Write("Element String "); s1 = Console.ReadLine(); Console.Write("Element number "); k = Convert.ToInt32(Console.ReadLine()); } } static dan[] fun2() { int n,m; dan []w; Console.Write("Elements ? "); n = Convert.ToInt32(Console.ReadLine()); w = new dan[n]; for (int i = 0; i < n; i++) { // каждый элемент вводится конструктором w[i] = new dan(); } return w; }
Работа со структурами
Класс является ссылочным типом: доступ к его объектам осуществляется с помощью ссылок. Доступ к объектам класса с помощью ссылок вызывает дополнительные накладные расходы при каждом доступе. При работе с маленькими объектами дополнительные расходы могут иметь существенное значение. С целью решения этой проблемы в C# введены структуры. Структура подобна классу, но она имеет тип значение, но не ссылка. Внешне объявление структуры похоже на объявление класса. Структуры могут иметь в своем составе данные, методы, индексаторы, свойства. Конструкторы тоже разрешены, но они обязательно должны иметь параметры; деструкторы – нет. Для создания экземпляра структуры можно вызвать конструктор через new, но можно и не вызывать. В таком случае экземпляр структуры создается, но записанные в конструкторе операции не будут выполнены. Структуры не могут участвовать в процессе наследования, ни в качестве предков, ни в качестве потомков. Исключение: в качестве предка структуры можно указать интерфейс (об интерфейсах поговорим позже).
namespace StructFun { struct dan1 { public string s1; // атрибут public обязателен public int k; } class Program { static dan1[] inpt() { // ввод массива структур dan1[] temp; int n; Console.Write("Elements ? "); n = Convert.ToInt32(Console.ReadLine()); temp = new dan1[n]; for (int i = 0; i < n; i++) { Console.Write("Elem " + i + " Num "); temp[i].k = Convert.ToInt32(Console.ReadLine()); Console.Write("Elem " + i + " Name "); temp[i].s1 = Console.ReadLine(); } return temp; } static double proc1(dan1 []x) { // обработка массива структур int s = 0; for (int i = 0; i < x.Length; i++) s += x[i].k; return (double)s / x.Length; } static void Main(string[] args) { dan1[] id; // массив исходных данных double aver; id = inpt(); // ввод исходных данных aver = proc1(id); // обработка массива структур Console.WriteLine("Average=" + aver); Console.ReadLine(); } } }
Наследование
В C# допускается простое наследование: каждый класс может иметь только одного предка. Используя наследование, можно создать базовый класс, который определяет характеристики, присущие множеству связанных объектов. Этот класс затем может быть унаследован другими классами с добавлением в каждый из них своих особенностей. Равнозначные термины: базовый класс – класс наследник; родительский класс – дочерний класс; класс предок – класс наследник. Создадим в качестве примера базовый класс для обработки массива, включающий определение массива, его ввод и вывод. К элементам базового класса с атрибутом доступа private нет доступа из классов – наследников, они, таким образом, не наследуются. Поэтому рекомендуют (если нет на этот счет особых соображений) дать элементам базового класса атрибут доступа protected. class arr { protected int[] k; //атрибут доступа protected //необходим для обеспечения доступа из классов - наследников public arr() { // конструктор 1 int n; Console.Write("Элементов ? "); n = Convert.ToInt32(Console.ReadLine()); k = new int[n]; for (int i = 0; i < n; i++) { Console.Write("K[" + i + "]="); k[i] = Convert.ToInt32(Console.ReadLine()); } } public arr(int p) { // конструктор 2 k = new int[p]; for (int i = 0; i < p; i++) { Console.Write("K[" + i + "]="); k[i] = Convert.ToInt32(Console.ReadLine()); } } public void output() { Console.WriteLine(); Console.WriteLine("Elements of Array"); for (int i = 0; i < k.Length; i++) Console.WriteLine("K[" + i + "]=" + k[i]); } }
На его базе можно построить классы обработки массивов. В нашем случае – нахождение суммы. Класс-наследник включает все данные своего предка (за исключением данных с атрибутом доступа private). Наследуются по общим правилам и индексаторы и свойства, а также методы перегрузки операторов.
class proc1 : arr // задаем базовый класс arr { int q; public proc1() { // конструктор класса наследника Console.Write("Граница "); q = Convert.ToInt32(Console.ReadLine()); } public int sum() { int s = 0; for (int i = 0; i < k.Length; i++) if (k[i] > q) s += k[i]; return s; } } Использование созданных классов class Program { static void Main(string[] args) { proc1 My = new proc1(); //1 int s1; My.output(); // обращение к методу предка s1=My.sum(); // обращение к собственному методу, // аналогично можно обращаться и к свойствам предка Console.WriteLine("Summa= " + s1); Console.ReadLine(); } }
При создании экземпляра класса, имеющего предка, (строка // 1) запускаются все конструкторы: в первую очередь конструктор базового класса и затем конструктор класса – наследника. В нашем случае это означает, что будет осуществлен ввод сначала массива и вслед за ним – границы. При наличии большего количества уровней наследования подряд будут запущены конструкторы всех уровней иерархии, начиная с базового. Если конструкторы не имеют формальных параметров, то при этом никаких проблем не возникает: каждый конструктор независимо от других выполняет свои операторы. Осталось решить вопрос: как обеспечить передачу параметра (ов) конструктору класса – предка, в нашем случае конструктору 2. Проще всего это выполнить с помощью списка инициализации в конструкторе класса – наследника. public proc1(int k1, int k2): base(k1) { q = k2; } Запись base(k1)означает, что конструктору базового класса в качестве фактического параметра будет передано значение к1. Пример главной функции в этом случае:
static void Main(string[] args) { int s1; proc1 Myaa = new proc1(6, 20); //обращение к // конструктору с параметрами Myaa.output(); s1=Myaa.sum(); Console.WriteLine("Summa= " + s1); Console.ReadLine(); } Лучше всего придерживаться следующего правила: при написании конструктора класса – наследника позаботиться о параметрах непосредственного предка. Таким образом, даже при большом количестве уровней иерархии будет обеспечена согласованная работа конструкторов.
Ссылки на объекты
C# является языком, требующим строгого соблюдения типа при присваивании. Автоматическое преобразование типов, применяемое при работе с обычными переменными, не распространяется на переменные ссылочного типа: ссылочная переменная одного класса не может ссылаться на объект другого класса. Исключение: ссылочной переменой базового класса можно присвоить ссылку на любой класс-наследник. Рассмотрим это на примере. namespace Virtual1 { class X { public int a; public X(int i) {a=i;} } class Y:X { public int b; public Y(int i, int j ): base(i) {b=j;} } class Class1 { static void Main(string[] args) { X x1= new X(10); X x2; Y y1=new Y(15,100); int k; x2=x1; //допустимо, переменные одного типа Console.WriteLine("First "+x1.a+" Second "+x2.a); x2=y1; //допустимо, Y наследник X Console.WriteLine("First "+x1.a+" Second "+x2.a);
// k=x2.b;ОШИБКА - класс X не имеет переменной b Console.ReadLine(); } } }
Возможность доступа к членам класса зависит от типа ссылочной переменной, а не от типа объекта, на который она ссылается. Поэтому закомментированный оператор будет ошибкой. Наследуемый класс «ничего не знает» о членах класса наследника!
Конструктор копирования
В общем случае в C# разрешено присвоение между объектами одного и то же класса. На практике это означает, что мы получим два указателя на один и тот же объект. Вспомните, точно так же было и при присвоении массивов. Для обеспечения создания нового объекта, которому в момент создания были переданы значения данных уже существующего объекта, но при этом под него выделялась собственная область памяти и в дальнейшем эти два объекта были бы полностью независимыми, необходим конструктор копирования. Единственным формальным параметром конструктора копирования всегда является переменная типа копируемый класс. При наличии конструктора копирования в классе всегда должен быть и обычный конструктор. Обратите внимание на состав формальных параметров конструктора копирования, и вы поймете, почему это так. Рассмотрим следующий пример. namespace Construct_Coop { class Shape { protected double a, h; public Shape(double x, double y) { // обычный конструктор a = x; h = y; } public Shape(Shape ob) { // конструктор копирования a = ob.a; h = ob.h; } public void NewDan(double x,double y) { a = x; h = y; } } class Tri : Shape { protected double area; public Tri(double x, double y) : base(x, y) { // конструктор наследника } public Tri(Tri ob) :base(ob) // 1 { // конструктор копирования наследника } public void Show_area() { area = a * h / 2; Console.WriteLine("S_Treug="+ area); } } class Square : Shape { protected double area; public Square(double x, double y) : base(x, y) { } public Square(Square ob) : base(ob) { } // 1 public void Show_area() { area = a * h; Console.WriteLine("S_Squar="+ area); } } class Program { static void Main(string[] args) { Tri my=new Tri(5,12); // работает конструктор my.Show_area(); Tri w = my; // работает конструктор w.NewDan(50, 120); // новые данные для w my.Show_area(); // будут выведены одинаковые значения w.Show_area(); Tri u = new Tri(w); //работает конструктор копирования u.NewDan(500, 1200); // новые данные для u w.Show_area();// будут выведены разные значения u.Show_area(); Console.ReadLine(); } } }
Обратите внимание на строки // 1: в них имеет место присвоение указателю на базовый класс (Shape) ссылки на класс-наследник (Tri, Square).
Виртуальные методы
Метод, при определении которого присутствует слово virtual, называется виртуальным. Каждый класс - наследник может иметь собственную версию виртуального метода, называется это переопределением и обозначается словом override. В C# выбор версии виртуального метода осуществляется в соответствии со значением указателя на момент вызова (а не типом указателя, как было в § 3.9.). Это делается во время выполнения программы. Указатель во время выполнения программы может указывать на объекты различных классов, поэтому по одному и тому же указателю могут вызываться разные версии виртуального метода. Переопределенные методы обеспечивают поддержку полиморфизма. Полиморфизм позволяет определять в базовом классе методы, которые будут общими для всех наследников, но каждый наследник, в случае необходимости, может иметь их собственные реализации. Естественно, что интерфейсы виртуального метода и всех его версий должны полностью совпадать. Таким образом, применение виртуальных методов позволяет фиксировать интерфейс метода и потом разработать под этот интерфейс новые реализации. Виртуальными могут быть и свойства и индексаторы. Рассмотрим это на примере. namespace Virtual1 { class Shape { protected int a,h; public Shape (int x,int y) { a=x; h=y; } publicvirtualvoid Show_area() { // вводится виртуальный метод Console.WriteLine("Площадь будет определен позже"); } } class Tri:Shape { int s; public Tri(int x, int y) :base(x, y) {} publicoverridevoid Show_area() { //первое переопределение виртуального метода s=a*h/2; Console.WriteLine("Площадь треугольника= "+s); } } class Square:Shape { int s; public Square(int x, int y):base(x, y) {} publicoverridevoid Show_area() { // второе переопределение виртуального метода s=a*h; Console.WriteLine("Площадь четырехугольника= "+s); } }
class Class1 { static void Main(string[] args) { Shape q=new Shape(10,30); q.Show_area(); // Tri z1=new Tri(5,12); z1.Show_area(); // Shape w; w=z1; // w будет указывать на объект класса Tri w.Show_area(); // Tri.Show_area() // Square w1=new Square(5,12); w1.Show_area(); // w=w1; // w будет указывать на объект класса Square w.Show_area(); //Square.Show_area() Console.ReadLine(); } } }
Как видно из примера, указатель w имеет тип Shape, но он может указывать на все наследники Shape. Выбор версии виртуального метода зависит от значения указателя на момент вызова, поэтому вызову w.Show_area(); соответствуют разные версии Show_area().
|
||
Последнее изменение этой страницы: 2018-04-12; просмотров: 343. stydopedya.ru не претендует на авторское право материалов, которые вылажены, но предоставляет бесплатный доступ к ним. В случае нарушения авторского права или персональных данных напишите сюда... |