Методические указания по выполнению лабораторных и практических работ по мдк
Скачать 3.25 Mb.
|
Практическая работа № 1.17. Работа с типом данных структура Цель работы: изучение типа данных структура Теоретический материал Типы структур Структуры по своей внутренней организации похожи на классы, они содержат набор полей и методов. Как правило, их используют для объявления типов, которые определяются только значениями полей и не имеют индивидуальности. Например, объекты, описывающие транзакции, несмотря на то, что значения их полей могут совпадать не будут тождественными, то есть нам их нужно уметь различать несмотря на внешнее сходство. А точки на геометрической плоскости, которые задаются двумя координатами, такой индивидуальности не имеют, и если координаты двух точек совпадают, то это значит, что речь идет об одной и той же точке. Именно для таких типов хорошо подходят структуры. Для их объявления используется ключевое слово struct: struct Point { public Point(double x, double y) { X = x; Y = y; } public double X {get;} public double Y {get;} } Point p1 = new Point(1,2); Console.WriteLine($”({p1.X}, (p1.Y})”); Типы значений, допускающие null Про типы значений, допускающих null см. ниже “Nullable-типы (нулевые типы) и операция ??”. Типы значений кортежей Кортежи используются для группировки данных, которые могут иметь разные типы в единую именованную сущность. Они являются объектами типа System.ValueTuple. Объявим кортеж, состоящий из двух элементов типа double: (double, double) tp1 = (1.0, 2.0); // явное задание типов элементов кортежа var tp2 = (8.1, 4.3); // использование var для объявления кортежа Поля кортежа могут быть именованными: (double X, double Y) tp3 = (3.2, 5.34); var tp4 = (X: 1.2, Y: 3.4); var X = 5.6; var Y = 7.8; var tp5 = (X, Y); Более подробно про кортежи типов System.ValueTuple (тип-значение) и System.Tuple (ссылочный тип) будет рассказано в одном из следующих уроков. Ссылочные типы Переменные ссылочного типа располагаются в куче, за их уничтожение отвечает сборщик мусора, поэтому про них нельзя точно сказать, когда занимаемая ими память будет освобождена. 72 Переменная представляется в виде ссылки на соответствующее место в куче. Ссылочные типы являются наследниками от System.Object. Типы классов Классы являются наиболее фундаментальным элементов в системе типов C#. Тип System.Object, который является родительским для всех типов данных представляет собой класс. Из рассмотренных выше типов данных, класс больше всего похож на структуру, у них даже объявление похожи, только вместо ключевого слова struct нужно использовать class. class Persone { public Persone(string name, int age) { Name = name; Age = age; } public string Name {get;set;} public int Age {get;set;} } Persone persone1 = new Persone("John", 21); Console.WriteLine($"Persone: Name: {persone1.Name}, Age: {persone1.Age})"); Среди классов в C# можно выделить ряд классов, которые играю важную роль в языке, они перечислены в таблице ниже. Класс Описание System.Object Базовый класс для всех типов в C# System.ValueType Базовый класс для всех типов-значений System.Enum Базовый класс для всех перечислений System.Array Базовый класс для всех массивов System.Delegate Базовый класс для всех делегатов System.Exception Базовый класс для всех исключений System.String Класс, определяющий строкой тип данных Типы интерфейсов Интерфейс представляет собой набор методов, свойств, событий и индексаторов. До версии C# 8.0 интерфейс предполагал только декларацию (объявление) указанных выше элементов, начиная с 8.0, в рамках интерфейса можно располагать реализацию по умолчанию. Фактически интерфейс представляет собой контракт, а класс, который от него наследуюется, реализует этот контракт. Ход работы 1. Создадим интерфейс для описания человека, у которого есть два свойства имя: Name, и возраст: Age: interface IPersone { string Name {get;set;} int Age {get;set;} } 2. Изменим объявление класса Persone, так, чтобы он представлял реализацию интерфейса IPersone: class Persone: IPersone { //… } 3. Объявим переменную типа IPersone: IPersone persone2 = new Persone("Jim", 25); Console.WriteLine($"Persone: Name: {persone2.Name}, Age: {persone2.Age})"); Более подробно про интерфейсы будет рассказано в одном из следующих уроков. 73 Типы массивов Массив – это структура данных, которая позволяет хранить один или более элементов. Массивы в C# делятся на одномерные и многомерные, среди последних наибольшее распространение получили двумерные массивы. Все массивы являются наследниками класса System.Array. Создание и инициализация одномерного массива: int[] nArr1 = new int[5]; nArr1[0] = 0; nArr1[1] = 1; nArr1[2] = 2; nArr1[3] = 3; nArr1[4] = 4; Пример прямоугольного массива, в нем строки имеют одинаковую длину: int[,] nMx = new int[2,2]; // прямоугольный массив nMx[0,0]=0; nMx[0,1]=1; nMx[1,0]=2; nMx[1,1]=3; Пример зубчатого (jagged) массива, в нем строки могут иметь разную длину: int[][] jg = new int[2][]; // зубчатый массив jg[0] = new int[3]; jg[1] = new int[1]; Более подробно про массивы будет рассказано в одном из следующих уроков. Типы делегатов Делегаты являются аналогом указателей на функции из языков C / C++. Они используются в случаях, когда нужно передать некоторую функциональность как аргумент, перенаправлять вызовы и т.д. Nullable-типы (нулевые типы) и операция ?? Объявление и инициализация Nullable-переменных В работе с типами-значениями есть одна особенность, они не могут иметь значение null. При наличии любой из следующих строк кода, компиляция программы не будет выполнена: int nv = null; bool bv = null; На практике, особенно при работе с базами данных, может возникнуть ситуация, когда в записи из таблицы пропущены несколько столбцов (нет данных), в этом случае, соответствующей переменной нужно будет присвоить значение null, но она может иметь тип int или double, что приведет к ошибке. Можно объявить переменную с использованием символа ? после указания типа, тогда она станет nullable-переменной – переменной поддерживающей null-значение: int? nv1 = null; bool? bv1 = null; Использование символа ? является синтаксическим сахаром для конструкции Nullable Nullable Nullable Проверка на null. Работа с HasValue и Value Для того чтобы проверить, что переменная имеет значение null можно воспользоваться оператором is с шаблоном типа: bool? flagA = true; if(flagA is bool valueOfFlag) 74 { Console.WriteLine("flagA is not null, value: {valueOfFlag}"); } Также можно воспользоваться свойствами класса Nullable: Nullable Возвращает true если переменная имеет значение базового типа. То есть если она не null. Nullable Возвращает значение переменной если HasValue равно true, иначе выбрасывает исключение InvalidOperationException. bool? flagB = false; if(flagB.HasValue) { Console.WriteLine("flagB is not null, value: {flagB.Value}"); } Приведение Nullable-переменной к базовому типу При работе с Nullable-переменными их нельзя напрямую присваивать переменным базового типа. Следующий код не будет скомпилирован: double? nvd1 = 12.3; double nvd2 = nvd1; // error Для приведения Nullable-переменной к базовому типу можно воспользоваться явным приведением: double nvd3 = (double) nvd1; В этом случае следует помнить, что если значение Nullable-переменной равно null, то при выполнении данной операции будет выброшено исключение InvalidOperationException. Второй вариант – это использование оператора ??, при этом нужно дополнительно задаться значением, которое будет присвоено переменной базового типа если в исходной лежит значение null: double nvd4 = nvd1 ?? 0.0; Console.WriteLine(nvd4); bool? nvb1 = null; bool nvb2 = nvb1 ?? false; Console.WriteLine(nvb1); Console.WriteLine(nvb2); Второй вариант позволяет более лаконично обрабатывать ситуацию, когда вызов какого- то метода может возвращать null, а результат его работы нужно присвоить типу-значению, при этом заранее известно, какое значение нужно присвоить переменной в этой ситуации: static int? GetValue(bool flag) { if (flag == true) return 1000; else return null; } static void Main(string[] args) { int test1 = GetValue(true) ?? 123; Console.WriteLine(test1); int test2 = GetValue(false) ?? 123; Console.WriteLine(test2); } Ключевое слово dynamic 75 Вначале статьи мы говорили о том, что есть языки со статической и динамической типизацией, C# – язык со статической типизацией, т.е. типы переменных определяются на этапе компиляции. Но в рамках платформы .NET есть возможность работать с Python и Ruby в реализациях IronPython и IronRuby, но это языки с динамической типизацией, в них тип определяется во время выполнения программы. Для того чтобы можно было в C# проекте работать с тем, что было создано в рамках IronPython (или IronRuby) начиная с C# 4, в языке появилось ключевое слово dynamic и среда DLR (Dynamic Language Runtime), благодаря которой можно создавать динамические объекты, тип которых будет определен на этапе выполнения программы, а не в процессе компиляции. С помощью ключевого слова dynamic объявляются переменные, для которых нужно опустить проверку типов в процессе компиляции. Для этой переменной не производится присвоение типа из BCL (Base Class Library) – стандартной библиотеки классов .NET, фактически dynamic – это тип System.Object с дополнительным набором метаданных, они нужны для определения типа переменной в процессе выполнения (так называемое, позднее связывание). Ниже приведены несколько примеров, на которых можно разобраться с тем, как работать с dynamic: // Создадим переменную типа dynamic и проинициализируем ее double значением dynamic dval1 = 12.3; // Посмотрим на ее значение и тип Console.WriteLine($"Value: {dval1}"); Console.WriteLine($"Type: {typeof(dval1)}")); // Изменим значение переменной: dval1 += 17; Console.WriteLine($"Value: {dval1}"); Console.WriteLine($"Type: {typeof(dval1)}")); // Присвоим переменной значение другого типа: bool dval1 = true; // Посмотрим на ее значение и тип Console.WriteLine($"Value: {dval1}"); Console.WriteLine($"Type: {typeof(dval1)}")); Как вы можете видеть значение и тип переменной dval1 менялись в процессе выполнения программы. При этом нужно помнить, что если вы присвоили переменной dynamic, какое-то значение, которое определило ее тип, а пытаетесь с ней работать как с переменной другого типа, то будет вызвано исключение: dynamic dval2 = "hello"; // в переменной dval2 хранится строковое значение Console.WriteLine($"Value: {dval2}"); Console.WriteLine($"Type: {typeof(dval2)}"); dval2 = 123; // теперь значение типа int dval2 = dval2.ToUpper() // попытка вызвать на ней .ToUpper() приведет к ошибке Оператор default Оператор default создает значение по умолчанию для указанного типа, используется оно следующим образом: default(T), где T – это тип, для которого нужно создать соответствующее значение. 4. Объявим переменную типа int и присвоим ей значение по умолчанию с помощью new: int n3 = new int(); Console.WriteLine($"Default int value: {n3}"); Тоже самое можно сделать с помощью оператора default: int n4 = default(int); Console.WriteLine($"Value of int that inited by default(T): {n4}"); Если C# может самостоятельно вывести тип, то можно воспользоваться не оператором, а литерой default, без явного указания типа: 76 int n5 = default; Console.WriteLine($"Value of int that inited by default: {n5}"); Данный оператор полезен при разработке методов с обобщенным типом. Создадим метод, который выводит на консоль значение по умолчанию для типа переданного в нее аргумента: static void PrintDefaultValue { Console.WriteLine($"Type of val: {val.GetType()}, default value: {default(T)}, current value: {val}"); } Вызовем эту функцию: static void Main(string[] args) { PrintDefaultValue PrintDefaultValue } Практическая работа № 1.18. Коллекции Цель работы: Изучение инструмента коллекций Теоретический материал Коллекции являются одним из наиболее часто используемых инструментов в разработке программного обеспечения. В этом уроке мы познакомимся с пространством имен System.Collections.Generic, коллекциями List, Dictionary и типом Tuple. Коллекции Самым примитивным способом хранения объектов в C# является использование массивов. Одной из основных проблем, с которой столкнется разработчик следуя такому подходу, является то, что массивы не предоставляют инструментов для динамического изменения размера. В языке C# есть два пространства имен для работы со структурами данных: System.Collections; System.Collections.Generic. Первое из них – System.Collections предоставляет структуры данных для хранения объектов типа Object. У этого решения есть две основных проблемы – это производительность и безопасность типов. В настоящее время не рекомендуется использовать объекты классов из System.Collections. Для решения указанных выше проблем Microsoft были разработаны коллекции с обобщенными типами (их ещё называют дженерики), они расположены в пространстве имен System.Collections.Generic. Суть их заключается в том, что вы не просто создает объект класса List, но и указываете, объекты какого типа будут в нем храниться, делается это так: List T может быть int, string, double или какой-то ваш собственный класс. В рамках данного урока мы не будем подробно останавливаться на особенностях обобщенных типов, на текущий момент можете их воспринимать как псевдонимы, для реальных типов данных. Коллекции в языке C#. Пространство имен System.Collections.Generic Пространство System.Collections.Generic содержит большой набор коллекций, которые позволяют удобно и эффективно решать широкий круг задач. Ниже, в таблице, перечислены некоторые из обобщенных классов с указанием интерфейсов, которые они реализуют. Обобщенный класс Основные интерфейсы Описание List ICollection Список элементов с динамически изменяемым размером Dictionary IEnumerable Коллекция элементов связанных через уникальный ключ Queue ICollection, IEnumerable Очередь – список, работающий по алгоритму FIFO 77 Stack ICollection, IEnumerable Стэк – список, работающий по алгоритму LIFO SortedList ICollection IDictionary Ход работы Класс List Коллекциями с класса List Задание 1. Создание объекта класса List Можно создать пустой список и добавить в него элементы позже, с помощью метода Add(): List Либо воспользоваться синтаксисом, позволяющем указать набор объектов, который будет храниться в списке: List Работа с объектами List Ниже приведены таблицы, в которых перечислены некоторые полезные свойства и методы класса List Свойства класса List Свойство Описание Count Количество элементов в списке Capacity Емкость списка – количество элементов, которое может вместить список без изменения размера Console.WriteLine("Свойства"); Console.WriteLine($"- Count: nums.Count = {nums.Count}"); Console.WriteLine($"- Capacity: nums.Capacity = {nums.Capacity}"); Методы класса List Метод Описание Add(T) Добавляет элемент к списку BinarySearch(T) Выполняет поиск по списку Clear() Очистка списка Contains(T) Возвращает true, если список содержит указанный элемент IndexOf(T) Возвращает индекс переданного элемента ForEach(Action Insert(Int32, T) Вставляет элемент в указанную позицию Find(Predicate Remove(T) Удаляет указанный элемент из списка RemoveAt(Int32) Удаляет элемент из заданной позиции Sort() Сортирует список Reverse() Меняет порядок расположения элементов на противоположный Console.WriteLine($"nums: {ListToString(nums)}"); nums.Add(6); Console.WriteLine($"nums.Add(6): {ListToString(nums)}"); Console.WriteLine($"words.BinarySearch(\"two\"): {words.BinarySearch("two")}"); Console.WriteLine($"nums.Contains(10): {nums.Contains(10)}"); Console.WriteLine($"words.IndexOf(\"three\"): {words.IndexOf("three")}"); Console.WriteLine($"nums.ForEach(v => v * 10)"); nums.ForEach(v => Console.Write($"{v} => ")); nums.Insert(3, 7); 78 Console.WriteLine($"nums.Insert(3, 7): {ListToString(nums)}"); Console.WriteLine($"words.Find(v => v.Length == 3): {words.Find(v => v.Length == 3)}"); words.Remove("two"); Console.WriteLine($"words.Remove(\"two\"): {ListToString(words)}"); Код метода ListToString: static private string ListToString "{" + string.Join(", ", list.ToArray()) + "}"; Далее приведен пример работы со списком, в котором хранятся объекты пользовательского типа. Создадим класс Player, имеющий свойства: Name и Skill. class Player { public string Name { get; set; } public string Skill { get; set; } } Задание 2. Создадим список игроков и выполним с ним ряд действий: Console.WriteLine("Работа с пользовательским типом"); List players = new List { new Player { Name = "Psy", Skill = "Monster"}, new Player { Name = "Kubik", Skill = "Soldier"}, new Player { Name = "Triver", Skill = "Middle"}, new Player { Name = "Terminator", Skill = "Very High"} }; Console.WriteLine("Количество элементов в players:{0}", players.Count); //Добавим новый элемент списка players players.Insert(1, new Player { Name = "Butterfly", Skill = "flutter like a butterfly, pity like a bee"}); //Посмотрим на все элементы списка players.ForEach(p => Console.WriteLine($"{p.Name}, skill: {p.Skill}")); Класс Dictionary Класс Dictionary реализует структуру данных Отображение, которую иногда называют Словарь или Ассоциативный массив. Идея довольно проста: в обычном массиве доступ к данным мы получаем через целочисленный индекс, в словаре используется ключ, который может быть числом, строкой или любым другим типом данных, который реализует метод GetHashCode(). При добавлении нового объекта в такую коллекцию для него указывается уникальный ключ, который используется для последующего доступа к нему. |