Васильев А.Н. Основы программирования на C#. Васильев А. Н. Программирование
Скачать 5.54 Mb.
|
Задания для самостоятельной работы Всем лежать Полчаса! из к/ф «Кин-дза-дза» 1. Напишите программу, в которой для заданного целочисленного значения вычисляется факториал, двойной факториал и число из последовательности Фибоначчи. Для вычисления каждого из значений запускается дочерний поток. Факториал n! для числа n вычисляется как произведение чисел от 1 до этого числа включительно, то есть n! = 1 × 2 × 3 × ... × n. Двойной факториал n!! вычисляется как произведение натуральных Многопоточное программирование 325 чисел через одно, то есть n!! = n × (n − 2) × (n − 4) × ... (последний множитель равен 2 или 1 в зависимости оттого, четное число n или нечетное. В последовательности Фибоначчи первые два числа равны 1, а каждое следующее число равно сумме двух предыдущих (получаются числа 1, 1, 2, 3, 5, 8, 13, 21 итак далее Напишите программу, в которой в главном потоке целочисленная переменная через определенные промежутки получает случайное значение. Два дочерних потока периодически (через определенные промежутки времени) проверяют значение переменной. Первый поток проверяет, является ли значение переменной нечетным, а второй поток проверяет, делится ли значение переменной на 3. Если проверка успешная, то соответствующий поток выводит в консольное окно сообщение. Напишите программу, в которой символьный массив заполняется с помощью двух дочерних потоков первый поток заполняет элементы счетными индексами латинскими буквами, а второй поток заполняет элементы с нечетными индексами кириллическими буквами. Напишите программу, в которой два потока заполняют целочисленный массив. Один поток заполняет массив сначала до конца числами, являющимися степенями двойки (2 0 , 2 1 , 2 2 итак далее. Другой поток заполняет массив с конца до начала числами, являющимися степенями тройки (3 0 , 3 1 , 3 2 итак далее. Напишите программу, содержащую класс с двумя полями одно является ссылкой на целочисленный массива второе поле является ссылкой на символьный массив. Создайте объект класса, а также запустите на выполнение два дочерних потока. Один дочерний поток должен заполнить символьный массив объекта, а второй дочерний поток должен заполнить целочисленный массив объекта. Способ заполнения предложите самостоятельно (например, случайные символы и числа. Напишите программу, в которой создается одномерный целочисленный массив и запускаются два дочерних потока. Один дочерний поток выполняет поиск элемента с наибольшим значением, а второй поток выполняет поиск элемента с наименьшим значением. Найденные значения отображаются в консольном окне. Напишите программу, в которой создается двумерный целочисленный массив. Массив заполняется построчно первая строка заполняется числами, являющимися степенями двойки (2 0 , 2 1 , 2 2 итак далее, вторая Глава строка заполняется степенями тройки (3 0 , 3 1 , 3 2 итак далее, итак далее. Для заполнения каждой строки запускается дочерний поток. Объектные переменные для потоков следует организовать в виде массива. Напишите программу, в которой создается двумерный символьный массив. Массив заполняется случайными символами. Заполнение выполняется по столбикам. Для заполнения каждого столбика запускается отдельный дочерний поток. Напишите программу, в которой с помощью дочернего потока вычисляется сумма ∑ n=1 ∞ 2 n (n + 1)/n! (точное значение для суммы равно 3 e 2 − 1 ≈ 21,167168). 10. Напишите программу, в которой с помощью дочернего потока вычисляется произведение ∏ n=2 ∞ (n 3 – 1)/(n 3 + 1). Сравните результат сточным значением 2/3. Глава ОБОБЩЕННЫЕ ТИПЫ А пацаки и чатлане — это национальность? из к/ф «Кин-дза-дза» В этой главе речь пойдет об обобщенных типах. В процессе работы с материалом главы нам предстоит узнать, как тип данных может выступать в качестве параметра научиться описывать и использовать обобщенные методы, в которых тип данных является параметром познакомиться с обобщенными классами и структурами, в которых тип данных определяется в качестве параметра научиться описывать и использовать обобщенные интерфейсы познакомиться с обобщенными делегатами узнать, как накладываются ограничения на параметры, определяющие тип данных. Будут рассмотрены и другие вопросы, прямо или косвенно касающиеся использования обобщенных типов. Передача типа данных в виде параметра А чем они друг от друга отличаются Ты что, дальтоник, Скрипач Зеленый цвет от оранжевого отличить не можешь? из к/ф «Кин-дза-дза» Идея использования обобщенных типов базируется на том, что при работе с методами, классами, структурами, интерфейсами или делегатами тип фактически используемых данных передается с помощью специального параметра Глава 7 328 { i НАЗ А МЕТКУ Те конструкции, которые описываются в данной главе, в англоязычной литературе описываются термином generics, что можно было бы перевести как обобщения (часто именно таки переводят. Иногда применяют термин шаблоны или универсальные шаблоны. Но эта терминология, скорее, из языка C++. Мы будем использовать термин обобщенные типы». Чтобы лучше понять, зачем все это нужно и как реализуется на практике, представим следующую ситуацию. Допустим, мы хотим описать статический метод, который получает два аргумента одного итого же типа, и при выполнении метода эти аргументы обмениваются значениями. Что примечательно в таком методе Примечательно в нем то, что алгоритм, который необходимо реализовать в методе, практически не зависит от типа аргументов, передаваемых методу. Самое важное и фактически единственное условие состоит в том, что оба аргумента должны относиться к одному и тому же типу. А какой именно это тип — вопрос второстепенный. Конечно, мы могли бы описать несколько версий метода для аргументов разного типа. Но, во-первых, это очень неудобно и нерационально (поскольку придется фактически переписывать несколько раз один и тот же код — с минимальными поправками на конкретный тип аргументов. Во-вторых, такой подход не всегда приемлем, поскольку типов данных (с учетом различных библиотечных и пользовательских классов) очень много и перебрать их все нет физической возможности. Так какой же выход из ситуации А выход простой и элегантный. Мы описываем алгоритм выполнения метода, но при этом не конкретизируем тип аргументов, а лишь указываем, что они относятся к одному типу. При вызове метода данный алгоритм выполняется с учетом того, к какому типу относятся аргументы, переданные методу. В этом состоит основная идея, связанная с использованием обобщенных методов. Примерно такой же подход используется при работе с обобщенными классами. Скажем, мы хотим описать классу которого имеется одно поле некоторого типа. Еще у класса есть конструктор с одним аргументом (того же типа, что и тип поля. Аргумент конструктора определяет значение поля. В классе можно описать метод (без аргументов, отображающий значение поля, а также метод с одним аргументом, позволяющий присвоить полю значение. В общем и целом — стандартная ситуация. Особенность ее лишь в том, что способ описания такого класса практически не зависит оттого, к какому типу относится поле класса. Обобщенные типы 329 Важно лишь, чтобы тип поля, тип аргумента конструктора и тип аргумента метода совпадали. Ну, еще тип должен быть таким, чтобы значение этого типа можно было отобразить в консольном окне (то есть чтобы такая операция имела смысл. Вот, собственно, и все. А теперь давайте представим, что нам необходимо несколько классов, отличающихся лишь типом поля. В рамках традиционного подхода для каждого типа данных пришлось бы описывать отдельный класс. Причем если в случае с методом можно было бы воспользоваться перегрузкой (и разные версии метода имели бы одно и тоже имя, то при описании нескольких классов каждому классу пришлось бы давать собственное имя. При этом содержимое каждого такого класса было бы во многом одинаково НАЗ А МЕТКУ Теоретически можно было бы описать один класс с полем типа класса. Но ив таком подходе есть свои серьезные недостатки, так что это тоже не является решением проблемы. Вместо создания нескольких однотипных классов мы можем описать один обобщенный класс. От обычного класса обобщенный отличается тем, что в нем не указан конкретный тип поля. Просто указывается, что поле есть и оно какого-то типа. И аргумент конструктора и метода — того же типа. А какой именно это тип, станет понятно, когда будет создаваться объект. Преимущество данного подхода как минимум в том, что нет необходимости многократно дублировать сходные блоки программного кода НАЗ А МЕТКУ Обычно интерес представляет объект класса (в данном случае имеется ввиду обычный, не обобщенный класс. Объект, как мы знаем, создается на основе класса. В этом смысле класс представляет собой некую абстракцию, которая служит каркасом для создания объекта. Обобщенный класс — это еще более высокий уровень абстракции. С определенной долей упрощения обобщенный класс можно рассматривать как каркас, на основе которого формируется обычный класс, и уже на основе этого обычного класса создается объект. Процесс формирования обычного класса на основе обобщенного происходит при создании объекта. Как это делается на практике, мы рассмотрим далее. Конечно, данная схема очень упрощенная и не совсем точная, но она позволяет понять место и роль обобщенных классов в программном коде Глава Похожим образом идея обобщенности распространяется на структуры, интерфейсы и делегаты. Мы последовательно рассмотрим способ реализации данной концепции и начнем с методов и классов. Обобщенные методы Нет, ты тоже пацак! Ты — пацак, ион пацак! А я — чатланин! И они — чатлане! из к/ф «Кин-дза-дза» Итак, знакомство с обобщенными типами мы начнем с обобщенных методов НАЗ А МЕТКУ Сначала рассмотрим статические методы, а затем уделим внимание обобщенным методам в обычных (не обобщенных) классах. Принципиальное отличие обобщенного метода от обычного состоит в том, что при описании обобщенного метода вместо ключевого слова, определяющего тип данных, используется обобщенный параметр типа некоторый идентификатор, обозначающий тип данных. Этот идентификатор используется в описании метода для обозначения типа данных. При вызове метода вместо обобщенного параметра типа подставляется фактический идентификатор типа, который обычно определяется по типу аргумента (или аргументов, переданного методу. При описании обобщенного метода идентификатор, используемый в качестве обобщенного параметра типа, указывается в угловых скобках после имени метода. Именно наличие угловых скобок (с идентификатором обобщенного параметра типа) после имени метода является признаком того, что метод — обобщенный. Шаблон описания статического обобщенного метода выглядит так ɬɢɩ_ɪɟɡɭɥɶɬɚɬɚ ɢɦɹ_ɦɟɬɨɞɚ<ɩɚɪɚɦɟɬɪ_ɬɢɩɚ>(ɚɪɝɭɦɟɧɬɵ){ // Ʉɨɞ Ситуацию можно описать и иначе в обобщенном методе после имени метода в угловых скобках можно указать идентификатор, который Обобщенные типы 331 в коде метода используется для обозначения типа данных. Причем под кодом метода подразумевается и описание аргументов, и идентификатор типа результата. Например, ниже приведено описание статического обобщенного метода с одним обобщенным параметром типа static void show Это очень простой метод. Он не возвращает результату него один аргумент (обобщенного типа, и все, что делает этот метод, — выводит в консоль значение своего аргумента. При вызове метода ему фактически можно передавать аргумент любого типа. Тип аргумента (и конкретное значение параметра T) определяется по команде, которой вызывается метод. Скажем, если для вызова метода использована команда show(123), то вместо параметра T будет подставлено значение Int32. Также при вызове обобщенного метода можно (а иногда это просто необходимо) явно указать, какое значение следует использовать в качестве обобщенного типа. Такое значение (идентификатор типа) указывается в команде вызова обобщенного метода в угловых скобках после имени метода (но перед круглыми скобками с аргументами метода. В этом смысле корректной является команда show q ПОДРОБНОСТИ int , который мы используем для обозначения типа целочисленных переменных, на самом деле является псевдонимом для типа среды .Net Framework, который называется Int32 (это структура из пространства имен System ). Поэтому при проверке типа для значений получаем название типа Int32 . Аналогичная ситуация имеет место и для других базовых типов. Определить тип аргумента, фактически переданного методу, можно с помощью метода GetType() . Метод GetType() вызывается из объекта, тип которого нужно определить. Результатом является объект класса Type . Объект класса Type содержит информацию о типе исходного объекта (того объекта, из которого вызывался метод GetType() ). У объекта класса Type есть свойство Name . Значение свойства Name — название типа (класс объекта, из которого вызывался метод Для определения значения обобщенного типа (то есть когда мы хотим выяснить, какое значение использовано для обобщенного Глава параметра) можно воспользоваться инструкцией typeof , после которой (в круглых скобках) указывается идентификатор типа. Результатом является объект класса Type . Название типа можно определить с помощью свойства Небольшой пример, в котором объявляется и используется статический обобщенный метод, представлен в листинге Листинг 7.1. Статические обобщенные методы System; // Ʉɥɚɫɫ ɫ ɰɟɥɨɱɢɫɥɟɧɧɵɦ ɩɨɥɟɦ: class MyClass{ // ɐɟɥɨɱɢɫɥɟɧɧɨɟ ɩɨɥɟ: public int code; // Ʉɨɧɫɬɪɭɤɬɨɪ: public MyClass(int n){ code=n; } // ɉɟɪɟɨɩɪɟɞɟɥɟɧɢɟ ɦɟɬɨɞɚ ToString(): public override string ToString(){ return Ǝɩɨɥɟ = Ǝ+code; } // Ɉɩɟɪɚɬɨɪɧɵɣ ɦɟɬɨɞ ɞɥɹ ɩɪɟɨɛɪɚɡɨɜɚɧɢɹ ɨɛɴɟɤɬɚ ɤ // ɰɟɥɨɱɢɫɥɟɧɧɨɦɭ ɬɢɩɭ: public static implicit operator int(MyClass obj){ return obj.code; } // Ɉɩɟɪɚɬɨɪɧɵɣ ɦɟɬɨɞ ɞɥɹ ɩɪɟɨɛɪɚɡɨɜɚɧɢɹ ɰɟɥɨɝɨ ɱɢɫɥɚ // ɜ ɨɛɴɟɤɬ: public static implicit operator MyClass(int n){ return new MyClass(n); } } Обобщенные типы Ʉɥɚɫɫ ɫ ɝɥɚɜɧɵɦ ɦɟɬɨɞɨɦ: class GenStatMethDemo{ // Ɉɛɨɛɳɟɧɧɵɣ ɦɟɬɨɞ (ɫ ɚɪɝɭɦɟɧɬɨɦ ɨɛɨɛɳɟɧɧɨɝɨ ɬɢɩɚ): static void show // Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Console.WriteLine( ƎɈɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: {0}Ǝ,typeof(T).Name); // Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: Console.WriteLine( ƎɁɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: {0}Ǝ,arg); Console.WriteLine( Ǝ------------------------------Ǝ); } // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ // ȼɵɡɨɜ ɨɛɨɛɳɟɧɧɵɯ ɦɟɬɨɞɨɜ: show(100); show show show show( ƍAƍ); show ƍBƍ); show ƍCƍ); show( ƎAlphaƎ); MyClass obj=new MyClass(500); // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ show(obj); show Результат выполнения программы представлен ниже: Результат выполнения программы (из листинга 7.1) Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Int32 Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: 100 Глава 7 334 ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Int32 Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: 200 ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Double Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: 300 ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: MyClass Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: ɩɨɥɟ = 400 ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Char Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: A ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Char Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: B ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Int32 Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: 67 ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: String Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: Alpha ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: MyClass Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: ɩɨɥɟ = 500 ------------------------------ Ɉɛɨɛɳɟɧɧɵɣ ɩɚɪɚɦɟɬɪ: Int32 Ɂɧɚɱɟɧɢɟ ɚɪɝɭɦɟɧɬɚ: В этой программе мы описываем самый обычный класс MyClass, объект которого мы планируем передавать статическому обобщенному методу. В классе описано целочисленное поле code, его значение определяется аргументом конструктора. Метод ToString() переопределен Обобщенные типы 335 для класса так, что результатом возвращается текстовая строка со значением поля code. Еще в классе определены два операторных метода. Один определяет способ неявного преобразования объекта в целое число (в этом случае результатом является значение поля code объекта, а другой метод определят способ преобразования целого числа в объект (результатом операции является ссылка на вновь созданный объекта значение поля code объекта определяется исходным целочисленным значением). В классе с главным методом описан обобщенный статический метод show() . У метода один обобщенный параметр (обозначен как T). При вызове метода в консольном окне отображаются значение обобщенного параметра и значение аргумента. Значение обобщенного параметра определяется инструкцией В главном методе программы метод show() вызывается с несколькими аргументами, в том числе и с аргументом, являющимся объектом класса MyClass (при отображении значения аргумента, когда аргументом является объект класса MyClass, вызывается метод ToString()). Например, при выполнении команды show(100) обобщенный тип определяется по типу переданного методу аргумента (целое число. В команде) значение для обобщенного типа указано явно. Здесь значение для обобщенного типа совпадает со значением аргумента метода. В команде show Команды show( ƍAƍ) и show ƍCƍ) для обобщенного типа указано значение int, хотя аргумент относится к типу char. Как ив предыдущих аналогичных случаях, в игру вступает автоматическое приведение типа (значение типа char преобразуется в значение типа При выполнении команды show( ƎAlphaƎ) в качестве обобщенного типа используется класс String. Наконец, в команде show(obj) аргументом методу передается объект obj класса MyClass, поэтому Глава в качестве обобщенного типа используется класс MyClass. А вот в команде) аргумент метода хотя и является объектом класса MyClass , но значение для обобщенного типа указано явно, поэтому будет задействовано автоматическое преобразование объекта класса MyClass к целочисленному значению. Благо, мы описали в классе MyClass соответствующий операторный метод. Обобщенный метод может содержать несколько обобщенных параметров. В таком случае идентификаторы для обобщенных параметров указываются через запятую в угловых скобках в описании метода. При вызове такого метода фактические значения для аргументов указываются в угловых скобках после имени метода или определяются автоматически на основе типов аргументов, переданных методу. В листинге 7.2 представлена программа, в которой есть обобщенные методы с несколькими обобщенными параметрами. |