Справочник от Автор24
Поделись лекцией за скидку на Автор24

Классы: подробности

  • 👀 171 просмотр
  • 📌 160 загрузок
Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Классы: подробности» docx
Лекция 7. Классы: подробности Перегрузка методов Часто бывает удобно, чтобы методы, реализующие один и тот же алгоритм для различных типов данных, имели одно и то же имя. Использование нескольких методов с одним и тем же именем, но различными типами параметров называется перегрузкой методов. Компилятор определяет, какой именно метод требуется вызвать, по типу фактических параметров. Этот процесс называется разрешением (resolution) перегрузки. Тип возвращаемого методом значения в разрешении не участвует. Механизм разрешения основан на достаточно сложном наборе правил, смысл которых сводится к тому, чтобы использовать метод с наиболее подходящими аргументами и выдать сообщение, если такой не найдется. Допустим, имеется четыре варианта метода, определяющего наибольшее значение: // Возвращает наибольшее из двух целых: int max( int a, int b ) // Возвращает наибольшее из трех целых: int max( int a, int b, int c ) // Возвращает наибольшее из первого параметра и длины второго: int max ( int a, string b ) // Возвращает наибольшее из второго параметра и длины первого: int max ( string b, int a ) ... Console.WriteLine( max( 1, 2 ) ); Console.WriteLine( max( 1, 2, 3 ) ); Console.WriteLine( max( 1, "2" ) ); Console.WriteLine( max( "1", 2 ) ); При вызове метода max компилятор выбирает вариант метода, соответствующий типу передаваемых в метод аргументов (в приведенном примере будут последовательно вызваны все четыре варианта метода). Если точного соответствия не найдено, выполняются неявные преобразования типов в соответствии с общими правилами. Если преобразование невозможно, выдается сообщение об ошибке. Если соответствие на одном и том же этапе может быть получено более чем одним способом, выбирается вариант, содержащий меньшее количество и длину преобразований. Если существует несколько вариантов, из которых невозможно выбрать лучший, выдается сообщение об ошибке. Перегруженные методы имеют одно имя, но должны различаться параметрами, точнее их типами и способами передачи ( out или ref ). Например, методы, заголовки которых приведены ниже, имеют различные сигнатуры и считаются перегруженными: int max( int a, int b ) int max( int a, ref int b ) Перегрузка широко используется в классах библиотеки .NET. Например, в стандартном классе Console метод WriteLine перегружен 19 раз для вывода величин разных типов. Пример класса с перегруженными методами приведен в конце этой лекции (листинг 7.3). Рекурсивные методы Рекурсивным называется метод, который вызывает сам себя. Такая рекурсия называется прямой. Существует еще косвенная рекурсия, когда два или более метода вызывают друг друга. Если метод вызывает себя, в стеке создается копия значений его параметров, как и при вызове обычного метода, после чего управление передается первому исполняемому оператору метода. При повторном вызове этот процесс повторяется. Ясно, что для завершения вычислений каждый рекурсивный метод должен содержать хотя бы одну нерекурсивную ветвь алгоритма, заканчивающуюся оператором возврата. При завершении метода соответствующая часть стека освобождается, и управление передается вызывающему методу, выполнение которого продолжается с точки, следующей за рекурсивным вызовом. Классическим примером рекурсивной функции является функция вычисления факториала (это не означает, что факториал следует вычислять именно так). Для того чтобы получить значение факториала числа n, требуется умножить на n факториал числа (n – 1 ). Известно также, что 0!= 1 и 1 != 1. long fact( long n ) { if ( n == 0 || n == 1 ) return 1; // нерекурсивная ветвь return ( n * fact( n – 1 ) ); // рекурсивная ветвь } То же самое можно записать короче: long fact( long n ) { return ( n > 1 ) ? n * fact( n – 1 ) : 1; } Рекурсивные методы чаще всего применяют для компактной реализации рекурсивных алгоритмов, а также для работы со структурами данных, описанными рекурсивно, например, с двоичными деревьями. К достоинствам рекурсии можно отнести компактность записи, к недостаткам — расход времени и памяти на повторные вызовы метода и передачу ему копий параметров, а главное, опасность переполнения стека. Методы с переменным количеством аргументов Иногда бывает удобно создать метод, в который можно передавать разное количество аргументов. Язык C# предоставляет такую возможность с помощью ключевого слова params. Параметр, помеченный этим ключевым словом, размещается в списке параметров последним и обозначает массив заданного типа неопределенной длины, например: public int Calculate( int a, out int c, params int[] d ) … В этот метод можно передать три и более параметров. Внутри метода к параметрам, начиная с третьего, обращаются как к обычным элементам массива. Количество элементов массива получают с помощью его свойства Length. В качестве примера рассмотрим метод вычисления среднего значения элементов массива (листинг 7.1). using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Example_1 { class Program { public static double Average( params int[] a ) { if ( a.Length == 0 ) throw new Exception( "Недостаточно аргументов в вызове метода" ); double av = 0; foreach ( int elem in a ) av += elem; return av / a.Length; } static void Main(string[] args) { try { int[] a = { 10, 20, 30 }; Console.WriteLine( Average( a ) ); // 1 int[] b = { -11, -4, 12, 14, 32, -1, 28 }; Console.WriteLine( Average( b ) ); // 2 short z = 1, e = 12; byte v = 100; Console.WriteLine( Average( z, e, v ) ); // 3 Console.WriteLine( Average() ); // 4 } catch( Exception e ) { Console.WriteLine( e.Message ); return; } } } } Листинг 7.1. Метод с переменным числом параметров Результат работы программы: Параметр-массив может быть только один и должен располагаться последним в списке. Соответствующие ему аргументы должны иметь типы, для которых возможно неявное преобразование к типу массива. Метод Main Метод, которому передается управление после запуска программы, должен иметь имя Мain и быть статическим. Он может принимать параметры из внешнего окружения и возвращать значение в вызвавшую среду. Предусматривается два варианта метода — с параметрами и без параметров: // без параметров: static тип Main() { ... } static void Main() { ... } // с параметрами: static тип Main( string[] args ) { /* ... */ } static void Main( string[] args ) { /* ... */ } Параметры, разделяемые пробелами, задаются при запуске программы из командной строки после имени исполняемого файла программы. Они передаются в массив args. Если метод возвращает значение, оно должно быть целого типа, если не возвращает, он должен описываться как void. Ненулевое значение обычно означает аварийное завершение. Возвращаемое значение анализируется в командном файле, из которого запускается программа. Обычно это делается для того, чтобы можно было принять решение, выполнять ли командный файл дальше. В листинге 7.2 приведен пример метода Main, который выводит свои аргументы и ожидает нажатия любой клавиши. using System; namespace Example_2 { class Program { static void Main(string[] args) { foreach (string arg in args) Console.WriteLine(arg); Console.ReadKey(); } } } Листинг 7.2. Параметры метода Main Индексаторы Индексатор представляет собой разновидность свойства. Если у класса есть скрытое поле, представляющее собой массив, то с помощью индексатора можно обратиться к элементу этого массива, используя имя объекта и номер элемента массива в квадратных скобках. Иными словами, индексатор — это такой "умный" индекс для объектов. Синтаксис индексатора аналогичен синтаксису свойства: атрибуты спецификаторы тип this [ список_параметров ] { get код_доступа set код_доступа } Индексаторы чаще всего объявляются со спецификатором public, поскольку они входят в интерфейс объекта. Атрибуты и спецификаторы могут отсутствовать. Код доступа представляет собой блоки операторов, которые выполняются при получении ( get ) или установке значения ( set ) элемента массива. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, индексатор доступен только для чтения (read-only), если отсутствует часть get, индексатор доступен только для записи (write-only). Список параметров содержит одно или несколько описаний индексов, по которым выполняется доступ к элементу. Чаще всего используется один индекс целого типа. Индексаторы в основном применяются для создания специализированных массивов, на работу с которыми накладываются какие-либо ограничения. Примеры работы с индексаторами приведены в учебнике. Язык C# допускает использование многомерных индексаторов. Они описываются аналогично обычным и применяются в основном для контроля за занесением данных в многомерные массивы и выборке данных из многомерных массивов, оформленных в виде классов. Операции класса Класс — это полноценный тип данных, поэтому программист может определить для него собственные операции. Вы уже сталкивались с перегрузкой (т.е. переопределением) операций: например, знак " + " для арифметических типов означает сложение, а для строк — склеивание. Операции перегружают в основном для классов, с помощью которых задают какие-либо математические понятия, т.е. тогда, когда знаки операции имеют общепринятую семантику. Можно задать операцию сложения для класса Frac, предназначенного для работы с простыми дробями. Примеры перегрузки приведены именно для этого класса, полное описание которого дано в конце лекции. Унарные операции Можно определять в классе следующие унарные операции: + - ! ~ ++ -- true false Синтаксис объявителя унарной операции: тип operator унарная_операция ( параметр ) Примеры заголовков унарных операций: public static int operator ++( MyObject m ) public static MyObject operator --( MyObject m ) public static bool operator true( MyObject m ) Параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. Операция должна возвращать: • для операций +, -, ! и ~ величину любого типа; • для операций ++ и -- величину типа класса, для которого она определяется; • для операций true и false величину типа bool. Операции не должны изменять значение передаваемого им операнда. Операция, возвращающая величину типа класса, для которого она определяется, должна создать новый объект этого класса, выполнить с ним необходимые действия и передать его в качестве результата. Примечание Префиксный и постфиксный инкремент не различаются (для них может существовать только одна реализация, которая вызывается в обоих случаях). Пример перегрузки операции инкремента (примем, что увеличение монстра на единицу должно увеличивать его здоровье): class Frac { public static Frac operator ++(Frac a) { a.num += 1; a.Reduce(); return a; } … } … Frac x = new Frac(1,2); ++x; x++; Бинарные операции Можно определять в классе следующие бинарные операции: + - * / % & | ^ << >> == != > < >= <= Синтаксис объявителя бинарной операции: тип operator бинарная_операция (параметр1, параметр2) Примеры заголовков бинарных операций: public static MyObject operator + ( MyObject m1, MyObject m2 ) public static bool operator == ( MyObject m1, MyObject m2 ) Хотя бы один параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. Операция может возвращать величину любого типа. Операции == и !=, > и <, >= и <= определяются только парами и обычно возвращают логическое значение. Чаще всего в классе определяют операции сравнения на равенство и неравенство для того, чтобы обеспечить сравнение объектов, а не их ссылок, как определено по умолчанию для ссылочных типов. Сложные операции присваивания (например, += ) определять не требуется, да это и невозможно. При выполнении такой операции автоматически вызываются сначала операция сложения, а потом присваивания. Пример описания бинарных операций для класса Frac (сложение с константой): public static Frac operator +(int a, Frac b) { int n = a * b.denom + b.num; int d = b.denom; return new Frac(n, d); } public static Frac operator +(Frac a, int b) { int n = a.num + b * a.denom; int d = a.denom; return new Frac(n, d); } Сложение двух дробей: public static Frac operator +(Frac a, Frac b) { int n = a.num * b.denom + b.num * a.denom; int d = a.denom * b.denom; return new Frac(n, d); } … Frac a = new Frac(1, 5); Frac d = 1 + a; d = a + 2; d = a + d; … Операции преобразования типа Операции преобразования типа обеспечивают возможность явного и неявного преобразования между пользовательскими типами данных. Синтаксис объявителя операции преобразования типа: implicit operator тип ( параметр ) // неявное преобразование explicit operator тип ( параметр ) // явное преобразование Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого определяется операция. Таким образом, операции выполняют преобразование либо типа класса к другому типу, либо наоборот. Преобразуемые типы не должны быть связаны отношениями наследования. Примеры операций преобразования типа для класса Frac, описанного ранее: public static implicit operator double(Frac m) { return (double)m.num / m.denom; } public static explicit operator Frac(double n) { double v1 = 100000000 * n; double v2 = 10000000 * n; return new Frac((int)(v1 - v2), 90000000); } Ниже приведены примеры использования этих преобразований в программе. Не надо искать в них смысл, они просто иллюстрируют синтаксис: Frac z = new Frac( 12, 5 ); double r = z; // неявное преобразование Frac c = ( Frac ) 0.75; // явное преобразование Неявное преобразование выполняется автоматически: • при присваивании объекта переменной целевого типа, как в примере; • при использовании объекта в выражении, содержащем переменные целевого типа; • при передаче объекта в метод на место параметра целевого типа; • при явном приведении типа. Явное преобразование выполняется при использовании операции приведения типа. Все операции класса должны иметь разные сигнатуры. В отличие от других видов методов, для операций преобразования тип возвращаемого значения включается в сигнатуру, иначе нельзя было бы определять варианты преобразования данного типа в несколько других. Ключевые слова implicit и explicit в сигнатуру не включаются, следовательно, для одного и того же преобразования нельзя определить одновременно явную и неявную версию. Неявное преобразование следует определять так, чтобы при его выполнении не возникала потеря точности и не генерировались исключения. Если эти ситуации возможны, преобразование следует описать как явное. Пример класса с перегруженными методами и операциями class Program { class Frac { int num; int denom; public override string ToString() { string s = string.Format("{0}/{1}", num, denom); return s; } public Frac(int n, int d = 1) { this.num = n; this.denom = d; Reduce(); } public Frac(string s = "0/1") { int n = s.IndexOf('/'); if (n == -1) { this.num = int.Parse(s); this.denom = 1; } else { this.num = int.Parse(s.Substring(0, n)); this.denom = int.Parse(s.Substring(n + 1)); } Reduce(); } public static Frac operator ++(Frac a) { a.num += 1; a.Reduce(); return a; } public static Frac operator +(Frac a, Frac b) { int n = a.num * b.denom + b.num * a.denom; int d = a.denom * b.denom; return new Frac(n, d); } public static Frac operator +(int a, Frac b) { int n = a * b.denom + b.num; int d = b.denom; return new Frac(n, d); } public static Frac operator +(Frac a, int b) { int n = a.num + b * a.denom; int d = a.denom; return new Frac(n, d); } public static implicit operator double(Frac m) { return (double)m.num / m.denom; } public static explicit operator Frac(double n) { double v1 = 100000000 * n; double v2 = 10000000 * n; return new Frac((int)(v1 - v2), 90000000); } static int nod(int a, int b) { int t; a = Math.Abs(a); b = Math.Abs(b); if (a > b) { t = a; a = b; b = t; } while (a != 0 & a != b) { if (a <= b) { b = b % a; } else { t = a; a = b; b = t; } } return b; } void Reduce() { int t = nod(this.num, this.denom); this.num /= t; this.denom /= t; } } static void Main(string[] args) { Frac a = new Frac(1, 5); Console.WriteLine("a = " + a); Frac d = 1 + a; Console.WriteLine("d = " + d); d = a + 2; Console.WriteLine("d = " + d); d = a + d; Console.WriteLine("d = " + d); Frac z = new Frac(12, 5); double r = z; Frac c = (Frac)0.75; Console.WriteLine("r = " + r); Console.WriteLine("c = " + c); /*Frac b = new Frac("3/5"); Console.WriteLine(b.ToString()); Frac c = a + b; Console.WriteLine(c.ToString()); Frac x = new Frac(1,2); for (int i = 0; i < 5; i++) { Console.WriteLine(x); ++x; x++; }*/ } } Рассмотрим решение следующей задачи. В текстовом файле хранится база отдела кадров предприятия. На предприятии 100 сотрудников. Каждая строка файла содержит запись об одном сотруднике. Формат записи: фамилия и инициалы (30 поз., фамилия должна начинаться с первой позиции), год рождения (5 поз.), оклад (10 поз.). Написать программу, которая по заданной фамилии выводит на экран сведения о сотруднике, подсчитывая средний оклад всех запрошенных сотрудников. Для решения этой задачи логично создать класс "сотрудник" и организовать из экземпляров этого класса массив. При описании класса полезно задаться следующим вопросом: какие обязанности должны быть возложены на этот класс? Очевидно, что первая обязанность — хранение сведений о сотруднике. Чтобы воспользоваться этими сведениями, клиент (то есть код, использующий класс) должен иметь возможность получить эти сведения, изменить их и вывести на экран. Кроме этого, для поиска сотрудника желательно иметь возможность сравнивать его имя с заданным. Поля класса сделаем в соответствии с принципом инкапсуляции закрытыми, а доступ к ним организуем с помощью свойств. Это позволит контролировать процесс занесения данных в поля. Для идентификации попытки ввода неверных данных воспользуемся механизмом исключений. Чтобы сделать класс более универсальным, введем в него возможность увеличивать и уменьшать оклад сотрудника с помощью перегруженных операций класса. На этом примере мы формализуем общий порядок создания программы, которого полезно придерживаться при решении даже простейших задач I. Исходные данные, результаты и промежуточные величины. Исходные данные. База сотрудников находится в текстовом файле. Прежде всего надо решить, хранить ли в оперативной памяти одновременно всю информацию из файла или можно обойтись буфером на одну строку. Если бы сведения о сотруднике запрашивались однократно, можно было бы остановиться на втором варианте, но поскольку поиск по базе будет выполняться более одного раза, всю информацию желательно хранить в оперативной памяти, поскольку многократное чтение из файла крайне нерационально. Максимальное количество строк файла по условию задачи ограничено, поэтому можно выделить для их хранения массив из 100 элементов. Каждый элемент массива будет содержать сведения об одном сотруднике, организованные в виде класса. Примечание Строго говоря, для решения этой конкретной задачи запись о сотруднике может быть просто строкой, из которой при необходимости выделяется подстрока с окладом, преобразуемая затем в число, но мы для общности и удобства дальнейшей модификации программы и демонстрации синтаксиса будем использовать класс. В программу по условию требуется также вводить фамилии искомых сотрудников. Очередную вводимую фамилию будем также хранить в стандартной строке типа string. Результаты. В результате работы программы требуется вывести на экран требуемые элементы исходного массива. Поскольку эти результаты представляют собой выборку из исходных данных, дополнительная память для них не отводится. Кроме того, необходимо подсчитать средний оклад для найденных сотрудников. Для этого необходима переменная вещественного типа. Промежуточные величины. Для поиска среднего оклада необходимо подсчитать количество сотрудников, для которых выводились сведения. Заведем для этого переменную целого типа. II. Алгоритм решения задачи очевиден: 1. Ввести из файла в массив сведения о сотрудниках. 2. Организовать цикл вывода сведений о сотруднике: ◦ ввести с клавиатуры фамилию; ◦ выполнить поиск сотрудника в массиве; ◦ увеличить суммарный оклад и счетчик количества сотрудников; ◦ вывести сведения о сотруднике или сообщение об их отсутствии; 3. Вывести средний оклад. Необходимо решить, каким образом будет производиться выход из цикла вывода сведений о сотрудниках. Условимся, что для выхода из циклавместо фамилии следует просто нажать клавишу Enter. Текст программы приведен в листинге 7.3. using System; using System.IO; namespace ConsoleApplication1 { class Man { // 1 const int l_name = 30; string name; int birth_year; double pay; public Man() // конструктор по умолчанию { name = "Anonymous"; birth_year = 0; pay = 0; } public Man(string s) // 2 { name = s.Substring(0, l_name); birth_year = Convert.ToInt32(s.Substring(l_name, 4)); pay = Convert.ToDouble(s.Substring(l_name + 4)); if (birth_year < 0) throw new FormatException(); if (pay < 0) throw new FormatException(); } public override string ToString() // 3 { return string.Format( "Name: {0,30} birth: {1} pay: {2:F2}", name, birth_year, pay); } public int Compare(string name) // сравнение фамилии { return (string.Compare(this.name, 0, name + " ", 0, name.Length + 1, StringComparison.OrdinalIgnoreCase)); } // -------------------- свойства класса -------------------------- public string Name { get { return name; } set { name = value; } } public int Birth_year { get { return birth_year; } set { if (value > 0) birth_year = value; else throw new FormatException(); } } public double Pay { get { return pay; } set { if (value > 0) pay = value; else throw new FormatException(); } } // ------------------ операции класса ------------------------------ public static double operator +(Man man1, double a) { man1.pay += a; return man1.pay; } public static double operator +(double a, Man man1) { man1.pay += a; return man1.pay; } public static double operator -(Man man1, double a) { man1.pay -= a; if ( man1.pay < 0 ) throw new FormatException(); return man1.pay; } }; class Class1 { static void Main() { Man[] dbase = new Man[100]; int n = 0; try { StreamReader f = new StreamReader("dbase.txt"); // 4 string s; int i = 0; while ((s = f.ReadLine()) != null) // 5 { dbase[i] = new Man(s); Console.WriteLine(dbase[i]); ++i; } n = i - 1; f.Close(); } catch (FileNotFoundException e) { Console.WriteLine(e.Message); Console.WriteLine(" Проверьте правильность имени файла!"); return; } catch (IndexOutOfRangeException) { Console.WriteLine("Слишком большой файл!"); return; } catch (FormatException) { Console.WriteLine("Недопустимая дата рождения или оклад"); return; } catch (Exception e) { Console.WriteLine("Ошибка: " + e.Message); return; } int n_man = 0; double mean_pay = 0; Console.WriteLine("Введите фамилию сотрудника"); string name; while ((name = Console.ReadLine()) != "") // 6 { bool not_found = true; for ( int k = 0; k <= n; ++k ) { Man man = dbase[k]; if (man.Compare(name) == 0) { Console.WriteLine(man); ++n_man; mean_pay += man.Pay; not_found = false; } } if (not_found) Console.WriteLine("Такого сотрудника нет"); Console.WriteLine( "Введите фамилию сотрудника или Enter для окончания"); } if (n_man > 0) Console.WriteLine("Средний оклад: {0:F2}", mean_pay / n_man); } } } Листинг 7.3. Поиск в массиве классов В операторе 1 описан класс Man для хранения информации об одном сотруднике. Кроме полей данных, в ней задан конструктор, выполняющий заполнение полей (оператор 2), переопределен метод преобразования в строку (оператор 3), что позволяет выводить экземпляр объекта на экран с помощью метода WriteLine класса Console, а также описан метод Compare, в котором фамилия сотрудника сравнивается с заданной через параметр. Сравнение фамилии выполняется с помощью одного из вариантов метода Compare класса string, который позволяет сравнивать подстроки без учета регистра. После заданной фамилии добавлен пробел, поскольку если пробела нет, то искомая фамилия является частью другой, и эта строка нам не подходит. Свойства и операции класса позволяют выполнять ввод и корректировку оклада и года рождения. Входной файл следует создать до первого запуска программы в соответствии с форматом, заданным в условии задачи. Можно использовать любой текстовый редактор, поддерживающий кодировку Unicode (например, Блокнот). Файл для целей тестирования должен состоять из нескольких строк, причем необходимо предусмотреть случай, когда одна фамилия является частью другой (например, Иванов и Ивановский). Не забудьте проверить, выдается ли диагностическое сообщение, если файл не найден. В операторе 4 описан экземпляр стандартного класса символьного потока. Цикл 5 выполняет построчное считывание из файла в строку s и заполнение очередного элемента массива dbase. В операторе 6 организуется цикл просмотра массива. Просматриваются только заполненные при вводе элементы. Деструкторы В C# существует специальный вид метода, называемый деструктором. Он вызывается сборщиком мусора непосредственно перед удалением объекта из памяти. В деструкторе описываются действия, гарантирующие корректность последующего удаления объекта, например, проверяется, все ли ресурсы, используемые объектом, освобождены (файлы закрыты, удаленное соединение разорвано и т. п.) Синтаксис деструктора: [ атрибуты ] [ extern ] ~имя_класса() тело Как видно из определения, деструктор не имеет параметров, не возвращает значения и не требует указания спецификаторов доступа. Его имя совпадает с именем класса и предваряется тильдой (  ), символизирующей обратные по отношению к конструктору действия. Тело деструктора представляет собой блок или просто точку с запятой, если деструктор определен как внешний ( extern ). Сборщик мусора удаляет объекты, на которые нет ссылок. Он работает в соответствии со своей внутренней стратегией в неизвестные для программиста моменты времени. Поскольку деструктор вызывается сборщиком мусора, невозможно гарантировать, что деструктор будет обязательно вызван в процессе работы программы. Следовательно, его лучше использовать только для гарантии освобождения ресурсов, а "штатное" освобождение выполнять в другом месте программы. Вложенные типы В классе можно определять типы данных, внутренние по отношению к классу. Так определяются вспомогательные типы, которые используются только содержащим их классом. Механизм вложенных типов позволяет скрыть ненужные детали и более полно реализовать принцип инкапсуляции. Непосредственный доступ извне к такому классу невозможен (имеется в виду доступ по имени без уточнения). Для вложенных типов можно использовать те же спецификаторы, что и для полей класса. Например, введем в наш класс Frac вспомогательный класс DenMistake. Это класс объектов, сигнализирующих об исключительной ситуации, возникающей в операциях на объектах класса Frac. Эта исключительная ситуация возникает, когда числитеь дроби становится равен нулю. Объекты этого класса без "хозяина" бесполезны, поэтому его можно определить как внутренний: class Frac { public class DenMistake : Exception { public DenMistake(string s) : base(s) { } } int num; int denom; public Frac(int n = 0, int d = 1) { if (d == 0) throw new DenMistake("Знаменатель равен нулю!"); this.num = n; this.denom = d; Reduce(); } … } Помимо классов, вложенными могут быть и другие типы данных: интерфейсы, структуры и перечисления. Мы рассмотрим их позже. Вопросы и задания для самостоятельной работы студента 1. Что такое индексатор, для каких классов он применяется? 2. Может ли статический конструктор инициализировать поля экземпляра? 3. Зачем нужна перегрузка методов и операций? 4. Опишите способы перегрузки операций. 5. Для каких классов имеет смысл использовать перегруженные операции? 6. Какой тип имеет параметр унарной операции класса? 7. Можно ли перегрузить операции простого и сложного присваивания? 8. Как передать программе параметры из внешнего окружения? Лабораторная работа 8. Классы и операции Теоретический материал: глава 7. Каждый разрабатываемый класс должен, как правило, содержать следующие элементы: скрытые поля, конструкторы с параметрами и без параметров, методы; свойства, индексаторы; перегруженные операции. Функциональные элементы класса должны обеспечивать непротиворечивый, полный, минимальный и удобный интерфейс класса. При возникновении ошибок должны выбрасываться исключения. Задание Описать класс для работы с одномерным массивом целых чисел (вектором). Обеспечить следующие возможности: • задание произвольных целых границ индексов при создании объекта; • обращение к отдельному элементу массива с контролем выхода за пределы массива; • выполнение операций поэлементного сложения и вычитания массивов с одинаковыми границами индексов; • выполнение операций умножения и деления всех элементов массива на скаляр; • вывода на экран элемента массива по заданному индексу и всего массива. Написать программу, демонстрирующую все разработанные элементы класса.
«Классы: подробности» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Найди решение своей задачи среди 1 000 000 ответов
Найти
Найди решение своей задачи среди 1 000 000 ответов
Крупнейшая русскоязычная библиотека студенческих решенных задач

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot