Теория и задания по Си-Шарп (КФУ). Учебное пособие казань 2017 2 удк 681 06 ббк 32. 973 Печатается по постановлению Редакционноиздательского совета
![]()
|
ГЛАВА 2. ОБЗОР ЯЗЫКА С# В главе рассмотрим основную структуру программ на языке С#. Рассмотрим класс Console для выполнения простых операций ввода и вывода. Дадим ряд советов по обработке исключений и документации кода. Структура программы на С# При изучении любого языка, первая программа, которую обычно пишут – «Hello, World ». Рассмотрим, как она выглядит в C#. using System; class Hello { public static void Main() { Console.WriteLine(“Hello, World”); } } При выполнении приведенного кода на экране появится надпись «Hello, World». В C# приложение – это коллекция одного или нескольких классов, структур и других типов. Класс определяется как набор данных и методов, работающих с ними. При рассмотрении кода приложения «Hello, World», видим, что есть единственный класс названный Hello. После имени класса открываем фигурные скобки ({). Всё, что находится до соответствующей закрывающейся скобки (}), является частью класса. Можно распределить один класс приложения C# на несколько файлов. Также, с другой стороны, можно поместить несколько классов в один файл. Каждое приложение должно начинаться с какого-то оператора. В С# при запуске приложения начинает выполняться метод Main. Несмотря на то, что в приложении может быть много классов, точка входа всегда одна. Может быть несколько методов Main, но запускаться будет только один, он выбирается при компиляции. Синтаксис Main важен, если проект создан в Visual Studio, то он 12 сгенерируется автоматически. При завершении метода Main приложение заканчивает работу. Как часть MS.NET Framework С# содержит много различных классов, которые упорядочены в пространствах имен. Пространство имен (namespace) – это набор связанных классов. Пространство имен также может содержать вложенные пространства имен. Основное пространство имен – это System. К объектам пространств имен можно обращаться при помощи полного имени, используя префиксы. Например, пространство System, содержит класс Console, который выполняет несколько методов, включая WriteLine System.Console.WriteLine(“Hello, World”); Директива using позволяет обращаться к членам пространства имен напрямую без использования полного имени. using System; Console.WriteLine(“Hello, World”); Основные операции ввода/вывода В этом разделе рассмотрим класс Console и его методы ввода и вывода. Класс Console обеспечивает приложению С# доступ к стандартным потокам ввода, вывода и ошибок. Стандартный поток ввода – клавиатура. Стандартный поток вывода и ошибок – экран. Эти потоки могут быть перенаправлены из/в файлы. W rite и WriteLine методы вывода информации на консоль. Они перегружаемы, то есть могут выводить как строки, так и числа. Console.WriteLine(99); Console.WriteLine("Hello, World"); Console.WriteLine("The sum of {0} and {1} is {2}", 100, 130, 100+130); Можно использовать левое и правое выравнивание, задавать ширину вывода. Сonsole.WriteLine("\"Левое выравн. в поле ширины 10: {0, -10}\"", 99); Console.WriteLine("\ "Правое выравн. в поле ширины 10: {0,10}\"", 99); Будет выведено “Левое выравн. в поле ширины 10: 99 ” “Правое выравн. в поле ширины 10: 99” 13 Есть настройки для вывода числовых форматов: Console.WriteLine(" Валюта - {0:C} {1:C4}", 88.8, -888.8); Console.WriteLine(" Целое число - {0:D5}", 88); Console.WriteLine(" Экспонента - {0:E}", 888.8); Console.WriteLine(" Фиксированная точка - {0:F3}", 888.8888); Console.WriteLine(" Общий формат- {0:G}", 888.8888); Console.WriteLine(" Числовой формат - {0:N}", 8888888.8); Console.WriteLine ("Шестнадцатиричный - {0:X4}", 88); Соответственно отобразят: Валюта - $88.80 ($888.8000) Целое число - 00088 Экспонента - 8.888000E+002 Фиксированная точка - 888.889 Общий формат - 888.8888 Числовой формат - 8,888,888.80 Шестнадцатеричный формат – 0058 Рекомендации по оформлению кода Основная рекомендация – это комментирование кода. Комментирование программы позволяет разработчикам, не участвовавшим при написании, разобраться, как работает приложение. Нужно использовать полные и значимые имена для именования компонент. Хорошие комментарии объясняют не что написано в программе, а отвечают на вопрос, почему именно так. Если в вашей организации есть стандарты комментирования, придерживайтесь их. С# поддерживает несколько вариантов комментирования кода: однострочные(//), многострочные комментарии(/* */) и XML- документация(///). В XML-документации можно использовать различные предопределенные теги. Для генерации XML файла с комментариями используется дополнительный параметр компиляции. Для компиляции кода из командной строки: csc myprogram.cs /doc:mycomments.xml Надежная программа на С# обязана обрабатывать непредвиденные ситуации. Независимо от того, сколько различных ошибок предусмотрено, 14 всегда существует вероятность того, что что-то может пойти не так. Когда происходит ошибка при выполнении приложения, операционная система генерирует исключение. Конструкцией try-catch можно перехватывать эти исключения. Если при выполнении программы в блоке try произошло исключение, управление передаётся блоку catch. Если в программе не обрабатывается исключение, то оно вызовет окно операционной системы с предложением отладить программу при помощи Just-in-Time Debugging. Перед запуском приложения С# необходимо его откомпилировать. Компилятор преобразует исходный код в машинные коды. Компилятор С# можно вызывать как из командной строки, так и из Visual Studio. Пример вызова компилятора из командной строки: csc Hello.cs /debug+ /out:Greet.exe (Заметим, что csc – это файл, и путь к нему нужно прописывать полностью.) Если компилятор находит ошибки, он сообщает строку ошибки и номер символа. Если ошибок нет, и приложение откомпилировалось, то его можно запускать как при помощи Visual Studio, так и в командной строке по имени файла с расширением exe. Вопросы к разделу 1. Откуда начинается выполнение приложения С#? 2. Когда приложение заканчивает работу? 3. Сколько классов может содержать приложение С#? 4. Сколько методов Main может содержать приложение? 5. Как прочитать данные, введенные пользователем с клавиатуры? 6. В каком пространстве имен находится класс Console? 7. Что произойдёт при необработанном в приложении исключении? Лабораторная работа Задания на создание C# программ, компилирование и отладку, использование отладчика Visual Studio, обработку исключительных ситуаций. Время, необходимое на выполнение задания 60 мин. Упражнение 2.1 Написать программу, которая спрашивает имя пользователя, и затем приветствует пользователя по имени. (Создать консольное приложение.) 15 • Откомпилировать и запустить программу с помощью Visual Studio. • Откомпилировать и запустить программу с командной строки. • В среде Visual Studio использовать пошаговую отладку. Посмотреть, как изменяется текущее значение переменной. Упражнение 2.2 Написать программу, которой на вход подается два целых числа, на выходе – результат деления одного числа на другое. Предусмотреть обработку исключительной ситуации, возникающей при делении числа на ноль. Домашнее задание 2.1 Прочитать букву с экрана и вывести на печать следующую за ней букву в алфавитном порядке. Домашнее задание 2.2 Написать программу, которая решает квадратное уравнение. Входные данные – коэффициенты уравнения, выходные – найденные корни. 16 ГЛАВА 3. ИСПОЛЬЗОВАНИЕ СТРУКТУРНЫХ ПЕРЕМЕННЫХ Все приложения работают с теми или иными данными. Разработчику С# необходимо понимать как хранятся и обрабатываются данные в приложении. Обычно данные хранятся в переменных, которые необходимо объявить до их использования. При объявлении переменной под неё резервируется некоторое количество памяти в зависимости от типа переменной и объявляется имя. После объявления переменной можно присваивать ей значения. В этой главе рассмотрим как использовать структурные переменные в С#. Как именовать переменные в соответствии со стандартами, а также как присваивать значения и переводить существующие переменные из одного типа в другой. Общая система типов (Common Type System) Каждая переменная имеет тип данных, который определяет какие значения хранятся в ней. С# – язык безопасных типов, т.е. компилятор гарантирует, что значение хранящееся в переменной будет всегда соответствующего типа. CTS (Common Type System)– интегрированная часть общеязыковой среды выполнения. Эта модель определяет правила, которыми руководствуется среда выполнения при объявлении, использовании и управления типами. CTS обеспечивает структуру, необходимую для многоязыковой интеграции, безопасности типов и высокой эффективности выполнения кода. Рассмотрим два типа переменных: структурные и ссылочные. Структурные типы данных напрямую содержат данные. Каждая переменная содержит свою копию данных, т.е. при операции над одной переменной невозможно изменить другую. Структурные типы данных включают в себя встроенные и пользовательские типы. Разница между ними в С# минимальна, так как они используются одинаково. Все структурные типы напрямую содержат данные и не могут быть null. Ссылочные типы данных содержат ссылки на данные. Две переменные могут указывать на один и тот же объект, т.е. при изменении одной ссылочной переменной, можно изменить другую. 17 Все базовые типы данных содержатся в пространстве имен System. Все типы наследуются от System.Object. Все структурные типы наследуются от System.ValueType. Встроенные типы объявляются при помощи зарезервированных слов, кроме того, можно объявить при помощи типа структуры из пространства имен System: sbyte System.SByte byte System.Byte short System.Int16 ushort System.UInt16 int System.Int32 uint System.UInt32 long System.Int64 ulong System.UInt64 char System.Char float System.Single double System.Double bool System.Boolean decimal System.Decimal Правила именования переменных При именовании переменных необходимо соблюдать следующие правила (если не соблюдать, то получим ошибку при компиляции): • можно использовать только буквы, нижнее подчеркивание и цифры, • имя переменной начинается только с буквы или подчеркивания, • после первого символа можно использовать и цифры, • нельзя использовать зарезервированные слова. Рекомендации по именованию: • избегать имена только из заглавных букв, • избегать начинать имя с подчеркивания, • избегать аббревиатур, • именование PascalCasing – каждое слово начинается с большой буквы, используется для классов, методов, свойств, перечислений, интерфейсов, констант, пространств имен, • именование camelCasing – каждое слово, кроме первого, начинается с большой буквы, используется для переменных, полей и параметров. 18 Использование встроенных типов данных Для использования переменной необходимо выбрать ей имя, назначить тип и присвоить начальное значение. Переменные, которые объявлены в методах, называются локальными. В С# нельзя использовать неинициализированные переменные, в этом случае выдаётся ошибка при компиляции. Переменной можно присвоить значение соответствующего типа. Добавление значения переменной осуществляется очень просто: int itemCount; itemCount = 2; itemCount = itemCount + 40; Приведем сокращенные способы записи арифметический действий: var += expression; // var = var + expression var -= expression; // var = var - expression var *= expression; // var = var * expression var /= expression; // var = var / expression var %= expression; // var = var % expression Выражения (expression) состоят из операций и операндов. Существуют следующие общие операции: Операции присваивания – присваивают значение правого операнда левому: = *= /= %= += -= <<= >>= &= ^= |= . Операции сравнения – сравнивают два значения: == != . Логические – производит побитовые операции над операндами: << >> & ^ | . Условные – выполняют одно выражение из двух в зависимости от булевого условия: && || ?: . Инкремент и декремент – увеличивает/уменьшает значение на единицу: ++ -- . Арифметические – выполняют стандартные арифметические операции: + - * / % . Операции инкремент и декремент имеют два варианта префиксный (++var ) и постфиксный (var++). Соответственно, сначала выполняется увеличение значения переменной var, а затем выполняется выражение и 19 наоборот, сначала выражение, и только после него изменяется значение операнда. Пользовательские типы данных В приложениях есть возможность создавать свои типы данных: структуры и перечисления. Перечисления полезны, когда переменные могут принимать значения только из определенного набора. Каждому значению в перечислении соответствует свой номер. По умолчанию нумерация элементов начинается с нуля. enum Color { Red, Green, Blue } Цвет Red будет иметь значение 0, Green – 1, а Blue будет 2. Пример использования переменной перечислимого типа: Color colorPalette; // объявление переменной colorPalette = Color.Red ; // установка значения или colorPalette = (Color)0; //явное преобразование из int Структуры можно использовать для создания объектов, ведущих себя как встроенные типы. Они группируют данные различного типа. public struct Employee { public string firstName; public int age; } Для доступа к элементам структур используется следующая конструкция: Employee companyEmployee ; // объявление переменной companyEmployee.firstName = “Joe ”; // присваивание companyEmployee.age = 23; // значений Преобразование типов Различают два способа преобразования типов: явное и неявное. При неявном преобразовании возможно потеря точности. Для явного преобразования используется операция приведения типов. 20 Пример неявного преобразования: using System; class Test; { static void Main() { int intValue = 123; long longValue = intValue; Console.WriteLine(“(long) {0} = {1}”, intValue, longValue) } } Пример явного преобразования: using System; class Test; { static void Main() { long longValue = Int64.MaxValue; int intValue = (int) longValue; Console.WriteLine(“(int) {0} = {1}”, longValue, intValue) } } Вторая программа на экране напечатает (int) 9223372036854775807 = -1, т.к. произойдет переполнение переменной типа int. Есть возможность отслеживать такие ошибки, путем размещения данного кода внутри блока checked. Сhecked используется для явной проверки переполнения при выполнении арифметических операций и преобразований с данными целого типа. Пример: using System; class Test { static void Main() { long longValue = Int64.MaxValue; сhecked{ try{ 21 int intValue = (int) longValue; Console.WriteLine("(int) {0} = {1}", longValue, intValue); } catch (Exception e){ Console.WriteLin e("Ошибка приведения типов"); } } } } Более подробно о блоке try-catch написано в четвертой главе. Вопросы к разделу 1. Что такое общеязыковая система типов? 2. Может ли структурная переменная иметь значение null? 3. Можно ли не инициализировать переменные в С#? Почему? 4. Можно ли потерять данные в результате неявного преобразования? Лабораторная работа Задания на создание пользовательских типов данных, объявление и использование переменных. Время, необходимое на выполнение задания 35 мин. Упражнение 3.1 Создать перечислимый тип данных отображающий виды банковского счета (текущий и сберегательный). Создать переменную типа перечисления, присвоить ей значение и вывести это значение на печать. Упражнение 3.2 Создать структуру данных, которая хранит информацию о банковском счете – его номер, тип и баланс. Создать переменную такого типа, заполнить структуру значениями и напечатать результат. Домашнее задание 3.1 Создать перечислимый тип ВУЗ{КГУ, КАИ, КХТИ}. Создать структуру работник с двумя полями: имя, ВУЗ. Заполнить структуру данными и распечатать. 22 ГЛАВА 4. ОПЕРАТОРЫ И ИСКЛЮЧЕНИЯ В этой главе рассмотрим, как использовать операторы в С#, а также опишем, как обрабатывать исключения. В частности, покажем, как генерировать и перехватывать исключения, и как правильно описывать блок try- catch, чтобы ни одно исключение не вышло за рамки приложения. Операторы в С# Программа состоит из последовательности операторов. При выполнении они запускаются по очереди. При разработке приложения необходимо группировать операторы, в С# они группируются в блоки при помощи фигурных скобок. Каждый блок задает свою область видимости, в которой существуют локальные переменные { // блок операторов } Операторы, используемые в программах, можно разделить на: операторы присваивания, арифметические операторы, логические операторы, условные операторы, оператор инкремента и декремента, операторы цикла и операторы перехода. Операторы присваивания, арифметические, логические, инкремента и декремента были рассмотрены в предыдущей главе. Рассмотрим остальные операторы. Условные операторы Операторы if и switch – операторы выбора, они вычисляют значение условия и на основе этого выбора выполняют операторы. Неявное преобразование из int в bool содержит потенциальный путь к ошибкам, поэтому в C# такое действие запрещено. В условии обязательно должно быть булево выражение. int x; ... if (x ) ... // Должно быть x != 0 в C# if (x = 0) ... // Должно быть x == 0 в C# 23 Можно использовать каскадную последовательность if, для чего предусмотрена конструкцию else if. Оператор switch – механизм для обработки сложных условий. Он состоит из нескольких блоков case, каждый из которых определяется своей константой. Для группировки констант, пишем каждую из них в собственном case, а выполняющийся оператор – после всей группы. Операторы выполняются до оператора break. enum MonthName { January, February, ..., December } MonthName current; int monthDays; ... switch (current) { case MonthName.February : monthDays = 28; break; case MonthName.April : case MonthName.June : case MonthName.September : case MonthName.November : monthDays = 30; break; default : monthDays = 31; break; } Если константы повторяются, то получим ошибку при компиляции: switch (trumps) { case Suit.Clubs : case Suit.Clubs : // Ошибка: повтор метки default : default : // Ошибка: повтор метки } 24 Операторы цикла Операторы цикла выполняют операции до тех пор, пока условие верно. Перечислим операторы цикла: while, do - while, for и foreach. Цикл while – простейший оператор цикла. Заметим, что С# не поддерживает неявное преобразование в bool, поэтому обязательно надо использовать условие. Отличие цикла do – while, в том что условие проверяется после выполнения операции. Цикл for включает в себя первоначальную инициализацию счетчика, условие выполнения и обновление счетчика. Однако в нем может отсутствовать любая часть for – инициализация, условие и обновление. Например, for (;;) { Console.WriteLine("Help "); } Цикл foreach – цикл для выборки всех элементов коллекции. Необходимо выбрать имя и тип переменной, которая будет идти по коллекции. Это переменная в теле цикла будет доступна только для чтения. foreach(int number in numbers) Console.WriteLine(number); Операторы перехода Операторы goto, break и continue – операторы перехода. Они используются для передачи управления из одной части программы в другую. goto – простейший оператор, управление при его вызове передается в точку программы, отмеченную меткой. Метка определяется двоеточием после идентификатора. if(number % 2 == 0) goto Even; Console.WriteLine(“odd”); goto End; Even: Console.WiteLine(“even”); End:; 25 Операторы break и continue используются в циклах, первый из них предназначен для выхода из цикла, второй – для перехода к следующей итерации. Обработка исключений В этом разделе изучим, как перехватывать и обрабатывать исключительные ситуации. Традиционно обработка ошибок производится в теле программы, что затрудняет понимание логики самого приложения и запутывает код. Сообщения об ошибках представляют собой числа, которые не несут никакой смысловой нагрузки. Более того, один и тот же код ошибки может использоваться в разных функциях и означать совершенно разные ошибки. В C# предусмотрен специальный механизм для генерирования и обработки исключительных ситуаций. Все исключения – это объекты, унаследованные от класса Exception. Названия классов раскрывают вид исключения. Объекты могут содержать дополнительную информацию об ошибке, например, FileNotFoundException может хранить имя файла. Для обработки исключительной ситуации, необходимо потенциально опасный код заключить в блок try-catch-finally. В блоке try содержится код, который может сгенерировать исключительную ситуацию, например деление на ноль или отсутствие запрашиваемого файла на диске. Если возникла исключительная ситуация, выполнение кода в блоке try приостанавливается и начинается поиск блока catch, соответствующего по типу исключительной ситуации. Блок finally выполняется обязательно, вне зависимости от того, произошло исключение или нет. Он используется в двух случаях: для избегания повтора операторов и для освобождения ресурсов. Есть несколько вариантов блока catch: • общий catch { ... }, что равносильно catch (System.Exception) { ... } – обработка ошибки любого типа; • по виду исключения, например, catch (OutOfMemoryException caught) { ... }, где caught – объект класса исключения OutOfMemoryException. После блока try может присутствовать несколько блоков catch. В этом случае у каждого блока catch необходимо явно указывать, ошибку какого типа он обрабатывает. Нельзя чтобы в следующем блоке catch был указан тип 26 данных, наследник от класса указанном в предыдущем блоке catch. Нельзя чтобы было два блока catch, предназначенных для обработки ошибки одного и того же класса. Блок catch для ошибки типа System.Exception может быть только последним. Приведем несколько примеров. try { Console.WriteLine("Enter first number"); int i = int.Parse(Console.ReadLine()); Console.WriteLine("Enter second number"); int j = int.Parse(Console.ReadLine()); int k = i / j; } catch (OverflowException caught) { Console.WriteLine(caught); } catch(DivideByZeroException caught) { Console.WriteLine(caught); } finally { Console.WriteLine(“Блок try-catch завершен”); } Приведенный выше код не содержал никаких ошибок. try { } catch { ... } catch (OutOfMemoryException caught ) { ... } // ошибка – общий блок catch следует перед блоком с конкретной ошибкой. В следующем примере один и тот же класс используется два раза: catch (OutOfMemoryException caught) { ... } catch (OutOfMemoryException caught) { ... } // Error В следующем примере сначала идет блок catch, который обрабатывает ошибку класса Exception, в следующем блоке – ошибку класса OutOfMemoryException , который является наследником от класса Exception. 27 catch (Exception caught) { ... } catch (OutOfMemoryException caught) { ... } // Error Оператор throw генерирует исключение вручную. Можно генерировать только объекты класса исключения, то есть для этого надо создать свой собственный класс, наследник от класса Exception. if (minute<1|| minute>=60){ string fault=minute + “is not a valid minute”; throw new InvalidTimeException(fault); … } В приведенном примере предполагается, что класс InvalidTimeException описан где-то раньше, этот класс является наследником от класса Exception, его конструктор содержит один строковый параметр. Можно использовать оператор throw внутри блока catch: catch (IOException caught) { throw new FileNotFoundException(filename); } По умолчанию проверка переполнения стека арифметическими операциями в C# выключена. Для включения проверки переполнения можно создать блок checked, или при компиляции указать опцию: csc /checked+ example.cs , а для отключения: csc /checked- example.cs. Блок unchecked явно выключает проверку. В конце главы 3 был приведен пример для обработки исключительной ситуации, которая возникает при переполнении арифметическими операциями. Рекомендации по обработке исключений: • Включать строки описания string description = String.Format("{0}({1}): newline in string constant", filename, linenumber); throw new System.Exception(description); • Генерировать исключения более специфичного вида, например класс FileNotFoundException , лучше чем более общий класс IOException. • Не позволять исключениям выходить из метода Main, то есть всегда должна существовать такая конструкция static void Main( ) { 28 try { } catch (Exception caught) { } } Вопросы к разделу 1. Перечислите основные группы операторов, выделив их характерные особенности. 2. Что такое исключение? Какие классы для обработки исключительных ситуаций вы знаете? 3. Как организовать обработку исключительной ситуации? Лабораторная работа Задания на операторы if, switch, for, while, foreach, обработку исключений. Время, необходимое на выполнение задания 45 мин. Упражнение 4.1 Написать программу, которая читает с экрана число от 1 до 365 (номер дня в году), переводит этот число в месяц и день месяца. Например, число 40 соответствует 9 февраля (високосный год не учитывать). Упражнение 4.2 Добавить к задаче из предыдущего упражнения проверку числа введенного пользователем. Если число меньше 1 или больше 365, программа должна вырабатывать исключение, и выдавать на экран сообщение. Домашнее задание 4.1 Изменить программу из упражнений 4.1 и 4.2 так, чтобы она учитывала год (високосный или нет). Год вводится с экрана. (Год високосный, если он делится на четыре без остатка, но если он делится на 100 без остатка, это не високосный год. Однако, если он делится без остатка на 400, это високосный год.) |