Программирование на языке С#: разработка консольных приложений

Технология объектно-ориентированного программирования

Разбить на страницы
Показывать лекцию целиком

В основе языка С# лежит технология объектно-ориентированного программирования (ООП). Все программы на языке С# в большей или меньшей степени являются объектно-ориентированными, поэтому, приступая к написанию даже самой простой программы, нужно познакомиться с основными понятиями в рамках ООП.

ООП основано на таких понятиях как "класс", "объект", "интерфейс", "инкапсуляция", "наследование", "полиморфизм", "событие".

абстракция реального объекта. Объект обладает атрибутами, поведением и индивидуальностью. Атрибуты определяют основные черты объекта, поведение — действия над объектом, индивидуальность — отличие одного объекта от другого с такими же атрибутами по их конкретным значениям. Например: два кота, у обоих есть шерсть, но у одного шерсть черного цвета, у другого — рыжая.

Класс – это множество объектов с одинаковыми атрибутами и поведением, представляемое в языке программирования в виде абстрактного типа данных, который включает в себя члены класса. Рассмотрим некоторые из них:

  • поля – непосредственно данные определенного типа для описания атрибутов;
  • методы - функции, предназначенные для обработки внутренних данных объекта данного класса;
  • свойства – это специальные поля данных, с помощью которых, можно управлять поведением объектов данного класса.
  • Класс служит образцом для создания объектов или, другими словами, объект является экземпляром класса.

    Важным свойством объекта является его обособленность. Детали реализации объекта, то есть внутренние структуры данных и алгоритмы их обработки, скрыты от пользователя и недоступны для непреднамеренного изменения. Объект используется через его интерфейс - совокупность правил доступа. Скрытие деталей реализации называется инкапсуляцией.

    В ООП данные и методы одного класса могут передаваться другим классам с помощью механизма наследования. Порожденный класс (потомок), наследующий характеристики другого класса, обладает теми же возможностями, что и класс (предок), от которого он порожден. При этом класс-предок остается без изменения, а классу-потомку можно добавлять новые элементы (поля, методы, свойства) или изменять унаследованные методы. Благодаря этому класс-потомок обладает большими возможностями, чем предок. Так, например, все классы (а их очень много и с некоторыми из них мы познакомимся чуть позже) порождены от корневого класса System.Object.

    Классы-потомки некоторого класса являются разновидностями этого класса-предка. (Другими словами, класс-предок является обобщением своих потомков). Это означает, что к объектам классов-потомков можно обращаться с помощью одного и того же имени (но при этом могут выполняться различные действия) — что составляет суть полиморфизма.Чаще всего понятие полиморфизма связывают с механизмом виртуальных методов, который мы рассмотрим позднее.

    Программу, построенную на принципах ООП, можно представить как совокупность взаимодействующих объектов. Объект А воздействует на объект Б, и для Б возникает событие, на которое Б отреагирует либо ответным воздействием на А, либо воздействием на объект В. Если А — внешний для системы объект, то Б — интерфейсный объект (отвечающий за взаимодействие системы с внешним миром).

    Операционная система Windows — объектно-ориентированная система, в которой определены классы для производства объектов, обеспечивающих, в частности, интерфейс с пользователем. Программа, написанная под Windows, обращается к ней, командуя какой интерфейсный объект на каком именно месте создать — так строится внешний вид (интерфейс) программы. Эти интерфейсные объекты кажутся принадлежащими программе, но на самом деле они — часть Windows: именно она отвечает за их базовый внешний вид и поведение. Поэтому, когда пользователь воздействует на интерфейсный объект программы (нажимает кнопку, выбирает пункт меню и т.п.), для этого объекта происходит событие и Windows переводит это событие в сообщение для программы. При написании программы предусматривается однозначная реакция на это сообщение в виде метода, а в методе вызываются методы других объектов. Т.е. воздействие на интерфейсный объект приводит к появлению в недрах Windows сообщения, которое, приходя в программу, запускает цепочку взаимодействий внутренних для нее объектов. Таким образом, ООП программа фактически встраивается в ОС Windows.

    Вспомним нашу первую программу:

    class Program	//класс
    { 
     static void Main () //метод класса
      { 
       Console.WriteLine("Hello!!!");
      }
    }

    Программа содержит класс Program с единственным статическим (static) методом Main, что позволяет обращаться к данному методу класса без создания его экземпляра.

    Состав языка

    Алфавит – совокупность допустимых в языке символов. Алфавит языка С# включает:

  • прописные и строчные латинские буквы и буквы национальных алфавитов (включая кириллицу);
  • арабские цифры от 0 до 9, шестнадцатеричные цифры от A до F ;
  • специальные знаки: " { } , | ; [ ] ( ) + - / % * . \ ' : ? < = > ! ~ ^ @ _
  • пробельные символы: пробел, символ табуляции, символ перехода на новую строку.
  • Из символов алфавита формируются лексемы языка: идентификаторы, ключевые (зарезервированные) слова, знаки операций, константы, разделители (скобки, точка, запятая, пробельные символы).

    Границы лексем определяются другими лексемами, такими, как разделители или знаки операций. В свою очередь лексемы входят в состав выражений (выражение задает правило вычисления некоторого значения) и операторов (оператор задает законченное описание некоторого действия).

    Идентификатор – это имя программного элемента: константы, переменной, метки, типа, класса, объекта, метода и т.д. Идентификатор может включать латинские буквы и буквы национальных алфавитов, цифры и символ подчеркивания. Прописные и строчные буквы различаются, например, myname, myName и MyName — три различных имени. Первым символом идентификатора может быть буква или знак подчеркивания, но не цифра.

    Пробелы внутри имен не допускаются. Язык С# не налагает никаких ограничений на длину имен, однако для удобства чтения и записи кода не стоит делать их слишком длинными.

    Для улучшения читабельности кода программным элементам следует давать осмысленные имена, составленные в соответствии с определенными правилами. Существует несколько видов нотаций – соглашений о правилах создания имен.

    В нотации Pascal каждое слово, входящее в идентификатор, начинается с заглавной буквы. Например: Age, LastName, TimeOfDeath.

    Венгерская нотация отличается от предыдущей наличием префикса, соответствующего типу величины. Например: fAge, sName, iTime.

    В нотации Camel с заглавной буквы начинается каждое слово идентификатора, кроме первого. Например: age, lastName, timeOfDeath.

    Наиболее часто используются нотации Pascal или Camel. Мы будем придерживаться нотации Pascal. Однако в простых программах будут использоваться однобуквенные переменные.

    Ключевые слова – это зарезервированные идентификаторы, которые имеют специальное значение для компилятора, например, include, main, int и т.д. Ключевые слова можно использовать только по прямому назначению. С ключевыми словами и их назначением можно ознакомиться в справочной системе С#.

    Замечание. Другие лексемы (знаки операций и константы), а также правила формирования выражений и различные виды операторов будут рассмотрены чуть позже.

    Типы данных

    С# является языком со строгой типизацией. В нем необходимо объявлять тип всех создаваемых программных элементов (например, переменных, объектов, окон, кнопок и т. д.), что позволяет компилятору предотвращать возникновение ошибок, следя за тем, чтобы объектам присваивались значения только разрешенного типа. Тип программного элемента сообщает компилятору о его размере (например, тип int показывает, что объект занимает 4 байта) и возможностях (например, кнопка может быть нарисована, нажата и т. д.).

    В С# типы делятся на две группы: базовые типы, предлагаемые языком, и типы, определяемые пользователем. Кроме того, типы С# разбиваются на две другие категории: размерные типы (типы по значению) и ссылочные типы.Почти все базовые типы являются размерными типами. Исключение составляют типы Object и String. Все пользовательские типы, кроме структур, являются ссылочными. Дополнительно к упомянутым типам, язык С# поддерживает типы указателей,однако они используются только с неуправляемым кодом.

    Принципиальное различие между размерными и ссылочными типами состоит в способе хранения их значений в памяти. В первом случае фактическое значение хранится в стеке (или как часть большого объекта ссылочного типа). Адрес переменной ссылочного типа тоже хранится в стеке, но сам объект хранится в куче.

    Замечание . Стек- это структура, используемая для хранения элементов по принципу первым пришел - последним ушел. В данном случае под стеком понимается область памяти, обслуживаемая процессором, в которой хранятся значения локальных переменных. Куча – область памяти, которая используется для хранения данных, работа с которыми реализуется через указатели и ссылки. Память для размещения таких данных динамически выделяется или освобождается в куче неявно (средствами CLR) или явно (программистом).

    Сборщик мусора уничтожает программные элементы в стеке через некоторое время после того, как закончит существование раздел стека, в котором они объявлены. В типичном случае раздел стека определяется методом. То есть, если в пределах метода объявлена локальная переменная, соответствующий программный элемент будет помечен для сборки мусора по окончании метода. Объект в куче подвергается сборке мусора через некоторое время после того, как уничтожена последняя ссылка на него.

    Язык С# предлагает обычный набор базовых типов, каждому из них соответствует тип, поддерживаемый общеязыковой спецификацией .NET (CLS). Соответствие базовых типов языка С# и типов платформы .NET гарантирует, что объекты, созданные в С#, могут быть использованы на равных основаниях с объектами, созданными в любом другом языке, удовлетворяющем требованиям .NET CLS (например, в языке VB.NET).

    Тип Размер в байтах Тип .NET Описание
    Базовый тип
    object Object Может хранить все что угодно, т.к. является всеобщим предком
    Логический тип
    bool 1 Boolean true или false
    Целые типы
    sbyte 1 SByte Целое со знаком (от -128 до 127)
    byte 1 Byte Целое без знака (от 0 до 255)
    short 2 Int16 Целое со знаком (от -32768 до 32767)
    ushort 2 UInt16 Целое без знака (от 0 до 65535)
    int 4 Int32 Целое со знаком (от -2147483648 до 2147483647)
    uint 4 UInt Целое число без знака ( от 0 до 4 294 967 295)
    long 8 Int64 Целое со знаком (от -9223372036854775808 до 9223372036854775807)
    ulong 8 UInt64 Целое без знака (от 0 до 0fffffffffffffff)
    Вещественные типы
    float 4 Single Число с плавающей точкой двойной точности. Содержит значения приблизительно от -1.5*10-45 до +3.4*1038 c 7 значащими цифрами
    double 8 Double Число с плавающей точкой двойной точности. Содержит значения приблизительно от -5. 0*10-324 до -1.7*10308 c 15-16 значащими цифрами
    Символьный тип
    char 2 Сhar Символы Unicode
    Строковый тип
    string String Строка из Unicode-символов
    Финансовый тип
    decimal 12 Decimal Число до 28 знаков с фиксированным положением десятичной точки. Обычно используется в финансовых расчетах. Требует суффикса <<m>> или <<М>>

    Переменные и константы

    Переменная представляет собой типизированную область памяти. Программист создает переменную, объявляя ее тип и указывая имя. При объявлении переменной ее можно инициализировать (присвоить ей начальное значение), а затем в любой момент ей можно присвоить новое значение, которое заменит собой предыдущее.

    static void Main()
     {
       int i=10;	//объявление и инициализация целочисленной переменной i
       Console.WriteLine(i);		//просмотр значения переменной
       i=100;		//изменение значение переменной
       Console.WriteLine(i);
     }

    В языке С# требуется, чтобы переменные были явно проинициализированы до их использования. Проверим этот факт на примере.

    static void Main()
     {
       int i;
       Console.WriteLine(i);
     }

    При попытке скомпилировать этот пример в списке ошибок будет выведено следующее сообщение: Use of unassigned local variable 'i' (используется неинициализированная локальная переменная i ).

    Однако инициализировать каждую переменную необязательно, но необходимо присвоить ей значение до того, как она будет использована.

    - это переменная, значение которой нельзя изменить. Константы бывают трех видов: литералы, символические константы и перечисления.

    В операторе присваивания: x=32;

    число 32 является литеральной константой. Его значение всегда равно 32 и его нельзя изменить.

    Символические константы именуют постоянные значения. Определение символической константы происходит следующим образом:

    const <тип>  <идентификатор> = <значение>;

    Рассмотрим пример:

    static void Main()
     {
       const int i=10;	//объявление целочисленной константы i
       Console.WriteLine(i);		//просмотр значения константы
       i=100;		//ошибка
       Console.WriteLine(i);
     }
    Задание.Измените программу так, чтобы при объявлении константы не происходила инициализация. Как на это отреагирует компилятор и почему?

    Перечисления (enumerations) являются альтернативой константам. Перечисление - это особый размерный тип, состоящий из набора именованных констант (называемых списком перечисления).

    Синтаксис определения перечисления следующий:

    [атрибуты] [модификаторы] enum <имя> [ : базовый тип]
    {список-перечисления констант(через запятую)};
    . Атрибуты и модификаторы, являются необязательными элементами этой конструкции. Более подробно мы рассмотрим их позже.

    Базовый тип - это тип самого перечисления. Если не указать базовый тип, то по умолчанию будет использован тип int. В качестве базового типа можно выбрать любой целый тип, кроме char. Пример использования перечисления:

    class Program
     {
       enum gradus:int
       {
        min=0,
        krit=72,
        max=100,
       }
    
       static void Main()
       {
        Console.WriteLine("минимальная температура=" + (int) gradus.min);
        Console.WriteLine("критическая температура=" + (int)gradus.krit);
        Console.WriteLine("максимальная температура=" + (int)gradus.max);
       }
    }
    Замечания
  • Запись ( int ) gradus.min используется для явного преобразования перечисления к целому типу. Если убрать ( int ), то на экран будет выводиться название констант.
  • Символ + в записи "минимальная температура=" + (int) gradus.min при обращении к методу WriteLine означает, что строка "минимальная температура=" будет "склеена" со строковым предствлением значения (int) gradus.min. В результате получится новая строка, которая и будет выведена на экран.
  • Организация ввода-вывода данных. Форматирование

    Программа при вводе данных и выводе результатов взаимодействует с внешними устройствами. Совокупность стандартных устройств ввода (клавиатура) и вывода (экран) называется консолью. В языке С# нет операторов ввода и вывода. Вместо них для обмена данными с внешними устройствами используются специальные объекты. В частности, для работы с консолью используется стандартный класс Console, определенный в пространстве имен System.

    Вывод данных

    В приведенных выше примерах мы уже рассматривали метод WriteLine, реализованный в классе Console, который позволяет организовывать вывод данных на экран. Однако существует несколько способов применения данного метода:

  • Console.WriteLine(x); //на экран выводится значение идентификатора х
  • Console.WriteLine("x=" + x +"y=" + y); /* на экран выводится строка, образованная последовательным слиянием строки "x=", значения x, строки "у=" и значения у */
  • Console.WriteLine("x={0} y={1}", x, y); /* на экран выводится строка, формат которой задан первым аргументом метода, при этом вместо параметра {0} выводится значение x, а вместо {1} – значение y*/
  • Замечание.

    Рассмотрим следующий фрагмент программы:

    int i=3, j=4; Console.WriteLine("{0} {1}", i, j);

    При обращении к методу WriteLine через запятую перечисляются три аргумента: "{0} {1}", i, j. Первый аргумент определяет формат выходной строки. Следующие аргументы нумеруются с нуля, так переменная i имеет номер 0, j – номер 1. Значение переменной i будет помещено в выходную строку на место {0}, а значение переменной j – на место {1}. В результате на экран будет выведена строка: 3 4. Если мы обратимся к методу WriteLine следующим образом Console.WriteLine("{0} {1} {2)", j, i, j), то на экран будет выведена строка: 4 3 4.

    Последний вариант использования метода WriteLine является наиболее универсальным, потому что он позволяет не только выводить данные на экран, но и управлять форматом их вывода. Рассмотрим несколько примеров:

  • Использование управляющих последовательностей:

    Управляющей последовательностью называют определенный символ, предваряемый обратной косой чертой. Данная совокупность символов интерпретируется как одиночный символ и используется для представления кодов символов, не имеющих графического обозначения (например, символа перевода курсора на новую строку) или символов, имеющих специальное обозначение в символьных и строковых константах (например, апостроф). Рассмотрим управляющие символы:

    Вид Наименование
    \a Звуковой сигнал
    \b Возврат на шаг назад
    \f Перевод страницы
    \n Перевод строки
    \r Возврат каретки
    \t Горизонтальная табуляция
    \v Вертикальная табуляция
    \\ Обратная косая черта
    \' Апостроф
    \" Кавычки

    Пример:

    static void Main()
    {
    Console.WriteLine("Ура!\nСегодня \"Информатика\"!!!");    
    }

    (рис 2.1) 02_01

  • Управление размером поля вывода:

    Первым аргументом WriteLine указывается строка вида {n, m} – где n определяет номер идентификатора из списка аргументов метода WriteLine, а m – количество позиций (размер поля вывода), отводимых под значение данного идентификатора. При этом значение идентификатора выравнивается по правому краю. Если выделенных позиций для размещения значения идентификатора окажется недостаточно, то автоматически добавится необходимое количество позиций. Пример:

    static void Main()
       {
        double x= Math.E;
        Console.WriteLine("E={0,20}", x);
        Console.WriteLine("E={0,10}", x);
        }
    (рис 2.2) 02_02
  • Управление размещением вещественных данных:

    Первым аргументом WriteLine указывается строка вида {n: ##.###} – где n определяет номер идентификатора из списка аргументов метода WriteLine, а ##.### определяет формат вывода вещественного числа. В данном случае под целую часть числа отводится две позиции, под дробную – три. Если выделенных позиций для размещения целой части значения идентификатора окажется недостаточно, то автоматически добавиться необходимое количество позиций. Пример:

    static void Main()
       {
        double x= Math.E;
        Console.WriteLine("E={0:##.###}", x);
        Console.WriteLine("E={0:.####}", x);
        }
    (рис 2.3) 02_03
    Задание. Измените программу так, чтобы число e выводилось на экран с точностью до 6 знаков после запятой.
  • Управление форматом числовых данных:

    Первым аргументом WriteLine указывается строка вида {n: <спецификатор>m} – где n определяет номер идентификатора из списка аргументов метода WriteLine, <спецификатор> - определяет формат данных, а m – количество позиций для дробной части значения идентификатора. В качестве спецификаторов могут использоваться следующие значения:

    Параметр Формат Значение
    C или c Денежный. По умолчанию ставит знак р. Изменить его можно с помощь объекта NumberFormatInfo Задается количество десятичных разрядов.
    D или d Целочисленный (используется только с целыми числами) Задается минимальное количество цифр. При необходимости результат дополняется начальными нулями
    E или e Экспоненциальное представление чисел Задается количество символов после запятой. По умолчанию используется 6
    F или f Представление чисел с фиксированной точкой Задается количество символов после запятой
    G или g Общий формат (или экспоненциальный, или с фиксированной точкой) Задается количество символов после запятой. По умолчанию выводится целая часть
    N или n Стандартное форматирование с использованием запятых и пробелов в качестве разделителей между разрядами Задается количество символов после запятой. По умолчанию – 2, если число целое, то ставятся нули
    X или x Шестнадцатеричный формат
    P или p Процентный

    Пример:

    static void Main()
     {
       Console.WriteLine("C Format:{0,14:C} \t{0:C2}", 12345.678);
       Console.WriteLine("D Format:{0,14:D} \t{0:D6}", 123);
       Console.WriteLine("E Format:{0,14:E} \t{0:E8}", 12345.6789);
       Console.WriteLine("G Format:{0,14:G} \t{0:G10}", 12345.6789);
       Console.WriteLine("N Format:{0,14:N} \t{0:N4}", 12345.6789);
       Console.WriteLine("X Format:{0,14:X} ", 1234);
       Console.WriteLine("P Format:{0,14:P} ", 0.9);    
     }

    (рис 2.4) 02_04

  • Ввод данных

    Для ввода данных обычно используется метод ReadLine, реализованный в классе Console. Особенностью данного метода является то, что в качестве результата он возвращает строку ( string ). Пример:

    static void Main()
     {
       string s = Console.ReadLine();
       Console.WriteLine(s);
     }

    Для того чтобы получить числовое значение необходимо воспользоваться преобразованием данных. Пример:

    static void Main()
     {
       string s = Console.ReadLine();
       int x = int.Parse(s); //преобразование строки в число
       Console.WriteLine(x);
     }

    Или сокращенный вариант:

    static void Main()
     {
       //преобразование введенной строки в число
       int x = int.Parse(Console.ReadLine()); 
       Console.WriteLine(x);
     }

    Для преобразования строкового представления целого числа в тип int мы используем метод int.Parse(), который реализован для всех числовых типов данных. Таким образом, если нам потребуется преобразовать строковое представление в вещественное, мы можем воспользоваться методом float.Parse() или double.Parse(). В случае, если соответствующее преобразование выполнить невозможно, то выполнение программы прерывается и генерируется исключение System.FormatExeption (входная строка имела неверный формат).

    Задание. Измените предыдущий фрагмент программы так, чтобы с клавиатуры вводилось вещественное число, а на экран это число выводилось с точностью до 3 знаков после запятой.

    Практикум

    Написать программу, которая, реализует диалог с пользователем:

  • запрашивает с клавиатуры два целых числа, и выводит на экран сумму данных чисел:

    (рис 2.1) 02_01

    Пример:

    using System;
    
    namespace Hello
    {
      class Program
      {
       static void Main()
       {
        Console.Write("a= ");
        int a = int.Parse(Console.ReadLine());
        Console.Write("b= ");
        int b = int.Parse(Console.ReadLine());
        Console.WriteLine("{0}+{1}={2}", a, b, a + b);
       }
      }
    }
  • запрашивает с клавиатуры два целых числа, и выводит на экран сумму данных чисел в прямом и обратном порядке:

    (рис 2.2) 02_02

  • запрашивает с клавиатуры три целых числа, и выводит на экран сумму данных чисел:

    (рис 2.3) 02_03

  • запрашивает с клавиатуры два вещественных числа, и выводит на экран произведение данных чисел (вещественные числа выводятся с точностью до 1 знака после запятой):

    (рис 2.4) 02_04

  • запрашивает с клавиатуры два вещественных числа, и выводит на экран результат деления первого числа на второе (вещественные числа выводятся с точностью до 3 знаков после запятой):

    (рис 2.5) 02_05

  • запрашивает с клавиатуры три вещественных числа, и выводит на экран следующее сообщение (вещественные числа выводятся с точностью до 2 знаков после запятой):

    (рис 2.6) 02_06

  • Самостоятельная работа

    Используя Интернет и дополнительную литературу найти ответы на следующие вопросы:

  • Чем отличается метод Console.WriteLine() от метода Console.Write()?
  • Чем отличается метод Console.ReadLine() от метода Console.Read()?
  • Какой тип имеет литеральная константа 3.2?
  • Как явным образом уточнить тип литеральной константы?
  • Что обозначается константой NaN? И в каких случаях компилятором используется данная константа?
  • Страницы:

    В основе языка С# лежит технология объектно-ориентированного программирования (ООП). Все программы на языке С# в большей или меньшей степени являются объектно-ориентированными, поэтому, приступая к написанию даже самой простой программы, нужно познакомиться с основными понятиями в рамках ООП.

    ООП основано на таких понятиях как "класс", "объект", "интерфейс", "инкапсуляция", "наследование", "полиморфизм", "событие".

    абстракция реального объекта. Объект обладает атрибутами, поведением и индивидуальностью. Атрибуты определяют основные черты объекта, поведение — действия над объектом, индивидуальность — отличие одного объекта от другого с такими же атрибутами по их конкретным значениям. Например: два кота, у обоих есть шерсть, но у одного шерсть черного цвета, у другого — рыжая.

    Класс – это множество объектов с одинаковыми атрибутами и поведением, представляемое в языке программирования в виде абстрактного типа данных, который включает в себя члены класса. Рассмотрим некоторые из них:

  • поля – непосредственно данные определенного типа для описания атрибутов;
  • методы - функции, предназначенные для обработки внутренних данных объекта данного класса;
  • свойства – это специальные поля данных, с помощью которых, можно управлять поведением объектов данного класса.
  • Класс служит образцом для создания объектов или, другими словами, объект является экземпляром класса.

    Важным свойством объекта является его обособленность. Детали реализации объекта, то есть внутренние структуры данных и алгоритмы их обработки, скрыты от пользователя и недоступны для непреднамеренного изменения. Объект используется через его интерфейс - совокупность правил доступа. Скрытие деталей реализации называется инкапсуляцией.

    В ООП данные и методы одного класса могут передаваться другим классам с помощью механизма наследования. Порожденный класс (потомок), наследующий характеристики другого класса, обладает теми же возможностями, что и класс (предок), от которого он порожден. При этом класс-предок остается без изменения, а классу-потомку можно добавлять новые элементы (поля, методы, свойства) или изменять унаследованные методы. Благодаря этому класс-потомок обладает большими возможностями, чем предок. Так, например, все классы (а их очень много и с некоторыми из них мы познакомимся чуть позже) порождены от корневого класса System.Object.

    Классы-потомки некоторого класса являются разновидностями этого класса-предка. (Другими словами, класс-предок является обобщением своих потомков). Это означает, что к объектам классов-потомков можно обращаться с помощью одного и того же имени (но при этом могут выполняться различные действия) — что составляет суть полиморфизма.Чаще всего понятие полиморфизма связывают с механизмом виртуальных методов, который мы рассмотрим позднее.

    Программу, построенную на принципах ООП, можно представить как совокупность взаимодействующих объектов. Объект А воздействует на объект Б, и для Б возникает событие, на которое Б отреагирует либо ответным воздействием на А, либо воздействием на объект В. Если А — внешний для системы объект, то Б — интерфейсный объект (отвечающий за взаимодействие системы с внешним миром).

    Операционная система Windows — объектно-ориентированная система, в которой определены классы для производства объектов, обеспечивающих, в частности, интерфейс с пользователем. Программа, написанная под Windows, обращается к ней, командуя какой интерфейсный объект на каком именно месте создать — так строится внешний вид (интерфейс) программы. Эти интерфейсные объекты кажутся принадлежащими программе, но на самом деле они — часть Windows: именно она отвечает за их базовый внешний вид и поведение. Поэтому, когда пользователь воздействует на интерфейсный объект программы (нажимает кнопку, выбирает пункт меню и т.п.), для этого объекта происходит событие и Windows переводит это событие в сообщение для программы. При написании программы предусматривается однозначная реакция на это сообщение в виде метода, а в методе вызываются методы других объектов. Т.е. воздействие на интерфейсный объект приводит к появлению в недрах Windows сообщения, которое, приходя в программу, запускает цепочку взаимодействий внутренних для нее объектов. Таким образом, ООП программа фактически встраивается в ОС Windows.

    Вспомним нашу первую программу:

    class Program	//класс
    { 
     static void Main () //метод класса
      { 
       Console.WriteLine("Hello!!!");
      }
    }

    Программа содержит класс Program с единственным статическим (static) методом Main, что позволяет обращаться к данному методу класса без создания его экземпляра.

    Состав языка

    Алфавит – совокупность допустимых в языке символов. Алфавит языка С# включает:

  • прописные и строчные латинские буквы и буквы национальных алфавитов (включая кириллицу);
  • арабские цифры от 0 до 9, шестнадцатеричные цифры от A до F ;
  • специальные знаки: " { } , | ; [ ] ( ) + - / % * . \ ' : ? < = > ! ~ ^ @ _
  • пробельные символы: пробел, символ табуляции, символ перехода на новую строку.
  • Из символов алфавита формируются лексемы языка: идентификаторы, ключевые (зарезервированные) слова, знаки операций, константы, разделители (скобки, точка, запятая, пробельные символы).

    Границы лексем определяются другими лексемами, такими, как разделители или знаки операций. В свою очередь лексемы входят в состав выражений (выражение задает правило вычисления некоторого значения) и операторов (оператор задает законченное описание некоторого действия).

    Идентификатор – это имя программного элемента: константы, переменной, метки, типа, класса, объекта, метода и т.д. Идентификатор может включать латинские буквы и буквы национальных алфавитов, цифры и символ подчеркивания. Прописные и строчные буквы различаются, например, myname, myName и MyName — три различных имени. Первым символом идентификатора может быть буква или знак подчеркивания, но не цифра.

    Пробелы внутри имен не допускаются. Язык С# не налагает никаких ограничений на длину имен, однако для удобства чтения и записи кода не стоит делать их слишком длинными.

    Для улучшения читабельности кода программным элементам следует давать осмысленные имена, составленные в соответствии с определенными правилами. Существует несколько видов нотаций – соглашений о правилах создания имен.

    В нотации Pascal каждое слово, входящее в идентификатор, начинается с заглавной буквы. Например: Age, LastName, TimeOfDeath.

    Венгерская нотация отличается от предыдущей наличием префикса, соответствующего типу величины. Например: fAge, sName, iTime.

    В нотации Camel с заглавной буквы начинается каждое слово идентификатора, кроме первого. Например: age, lastName, timeOfDeath.

    Наиболее часто используются нотации Pascal или Camel. Мы будем придерживаться нотации Pascal. Однако в простых программах будут использоваться однобуквенные переменные.

    Ключевые слова – это зарезервированные идентификаторы, которые имеют специальное значение для компилятора, например, include, main, int и т.д. Ключевые слова можно использовать только по прямому назначению. С ключевыми словами и их назначением можно ознакомиться в справочной системе С#.

    Замечание. Другие лексемы (знаки операций и константы), а также правила формирования выражений и различные виды операторов будут рассмотрены чуть позже.

    Типы данных

    С# является языком со строгой типизацией. В нем необходимо объявлять тип всех создаваемых программных элементов (например, переменных, объектов, окон, кнопок и т. д.), что позволяет компилятору предотвращать возникновение ошибок, следя за тем, чтобы объектам присваивались значения только разрешенного типа. Тип программного элемента сообщает компилятору о его размере (например, тип int показывает, что объект занимает 4 байта) и возможностях (например, кнопка может быть нарисована, нажата и т. д.).

    В С# типы делятся на две группы: базовые типы, предлагаемые языком, и типы, определяемые пользователем. Кроме того, типы С# разбиваются на две другие категории: размерные типы (типы по значению) и ссылочные типы.Почти все базовые типы являются размерными типами. Исключение составляют типы Object и String. Все пользовательские типы, кроме структур, являются ссылочными. Дополнительно к упомянутым типам, язык С# поддерживает типы указателей,однако они используются только с неуправляемым кодом.

    Принципиальное различие между размерными и ссылочными типами состоит в способе хранения их значений в памяти. В первом случае фактическое значение хранится в стеке (или как часть большого объекта ссылочного типа). Адрес переменной ссылочного типа тоже хранится в стеке, но сам объект хранится в куче.

    Замечание . Стек- это структура, используемая для хранения элементов по принципу первым пришел - последним ушел. В данном случае под стеком понимается область памяти, обслуживаемая процессором, в которой хранятся значения локальных переменных. Куча – область памяти, которая используется для хранения данных, работа с которыми реализуется через указатели и ссылки. Память для размещения таких данных динамически выделяется или освобождается в куче неявно (средствами CLR) или явно (программистом).

    Сборщик мусора уничтожает программные элементы в стеке через некоторое время после того, как закончит существование раздел стека, в котором они объявлены. В типичном случае раздел стека определяется методом. То есть, если в пределах метода объявлена локальная переменная, соответствующий программный элемент будет помечен для сборки мусора по окончании метода. Объект в куче подвергается сборке мусора через некоторое время после того, как уничтожена последняя ссылка на него.

    Язык С# предлагает обычный набор базовых типов, каждому из них соответствует тип, поддерживаемый общеязыковой спецификацией .NET (CLS). Соответствие базовых типов языка С# и типов платформы .NET гарантирует, что объекты, созданные в С#, могут быть использованы на равных основаниях с объектами, созданными в любом другом языке, удовлетворяющем требованиям .NET CLS (например, в языке VB.NET).

    Тип Размер в байтах Тип .NET Описание
    Базовый тип
    object Object Может хранить все что угодно, т.к. является всеобщим предком
    Логический тип
    bool 1 Boolean true или false
    Целые типы
    sbyte 1 SByte Целое со знаком (от -128 до 127)
    byte 1 Byte Целое без знака (от 0 до 255)
    short 2 Int16 Целое со знаком (от -32768 до 32767)
    ushort 2 UInt16 Целое без знака (от 0 до 65535)
    int 4 Int32 Целое со знаком (от -2147483648 до 2147483647)
    uint 4 UInt Целое число без знака ( от 0 до 4 294 967 295)
    long 8 Int64 Целое со знаком (от -9223372036854775808 до 9223372036854775807)
    ulong 8 UInt64 Целое без знака (от 0 до 0fffffffffffffff)
    Вещественные типы
    float 4 Single Число с плавающей точкой двойной точности. Содержит значения приблизительно от -1.5*10-45 до +3.4*1038 c 7 значащими цифрами
    double 8 Double Число с плавающей точкой двойной точности. Содержит значения приблизительно от -5. 0*10-324 до -1.7*10308 c 15-16 значащими цифрами
    Символьный тип
    char 2 Сhar Символы Unicode
    Строковый тип
    string String Строка из Unicode-символов
    Финансовый тип
    decimal 12 Decimal Число до 28 знаков с фиксированным положением десятичной точки. Обычно используется в финансовых расчетах. Требует суффикса <<m>> или <<М>>

    Переменные и константы

    Переменная представляет собой типизированную область памяти. Программист создает переменную, объявляя ее тип и указывая имя. При объявлении переменной ее можно инициализировать (присвоить ей начальное значение), а затем в любой момент ей можно присвоить новое значение, которое заменит собой предыдущее.

    static void Main()
     {
       int i=10;	//объявление и инициализация целочисленной переменной i
       Console.WriteLine(i);		//просмотр значения переменной
       i=100;		//изменение значение переменной
       Console.WriteLine(i);
     }

    В языке С# требуется, чтобы переменные были явно проинициализированы до их использования. Проверим этот факт на примере.

    static void Main()
     {
       int i;
       Console.WriteLine(i);
     }

    При попытке скомпилировать этот пример в списке ошибок будет выведено следующее сообщение: Use of unassigned local variable 'i' (используется неинициализированная локальная переменная i ).

    Однако инициализировать каждую переменную необязательно, но необходимо присвоить ей значение до того, как она будет использована.

    - это переменная, значение которой нельзя изменить. Константы бывают трех видов: литералы, символические константы и перечисления.

    В операторе присваивания: x=32;

    число 32 является литеральной константой. Его значение всегда равно 32 и его нельзя изменить.

    Символические константы именуют постоянные значения. Определение символической константы происходит следующим образом:

    const <тип>  <идентификатор> = <значение>;

    Рассмотрим пример:

    static void Main()
     {
       const int i=10;	//объявление целочисленной константы i
       Console.WriteLine(i);		//просмотр значения константы
       i=100;		//ошибка
       Console.WriteLine(i);
     }
    Задание.Измените программу так, чтобы при объявлении константы не происходила инициализация. Как на это отреагирует компилятор и почему?

    Перечисления (enumerations) являются альтернативой константам. Перечисление - это особый размерный тип, состоящий из набора именованных констант (называемых списком перечисления).

    Синтаксис определения перечисления следующий:

    [атрибуты] [модификаторы] enum <имя> [ : базовый тип]
    {список-перечисления констант(через запятую)};
    . Атрибуты и модификаторы, являются необязательными элементами этой конструкции. Более подробно мы рассмотрим их позже.

    Базовый тип - это тип самого перечисления. Если не указать базовый тип, то по умолчанию будет использован тип int. В качестве базового типа можно выбрать любой целый тип, кроме char. Пример использования перечисления:

    class Program
     {
       enum gradus:int
       {
        min=0,
        krit=72,
        max=100,
       }
    
       static void Main()
       {
        Console.WriteLine("минимальная температура=" + (int) gradus.min);
        Console.WriteLine("критическая температура=" + (int)gradus.krit);
        Console.WriteLine("максимальная температура=" + (int)gradus.max);
       }
    }
    Замечания
  • Запись ( int ) gradus.min используется для явного преобразования перечисления к целому типу. Если убрать ( int ), то на экран будет выводиться название констант.
  • Символ + в записи "минимальная температура=" + (int) gradus.min при обращении к методу WriteLine означает, что строка "минимальная температура=" будет "склеена" со строковым предствлением значения (int) gradus.min. В результате получится новая строка, которая и будет выведена на экран.
  • Организация ввода-вывода данных. Форматирование

    Программа при вводе данных и выводе результатов взаимодействует с внешними устройствами. Совокупность стандартных устройств ввода (клавиатура) и вывода (экран) называется консолью. В языке С# нет операторов ввода и вывода. Вместо них для обмена данными с внешними устройствами используются специальные объекты. В частности, для работы с консолью используется стандартный класс Console, определенный в пространстве имен System.

    Вывод данных

    В приведенных выше примерах мы уже рассматривали метод WriteLine, реализованный в классе Console, который позволяет организовывать вывод данных на экран. Однако существует несколько способов применения данного метода:

  • Console.WriteLine(x); //на экран выводится значение идентификатора х
  • Console.WriteLine("x=" + x +"y=" + y); /* на экран выводится строка, образованная последовательным слиянием строки "x=", значения x, строки "у=" и значения у */
  • Console.WriteLine("x={0} y={1}", x, y); /* на экран выводится строка, формат которой задан первым аргументом метода, при этом вместо параметра {0} выводится значение x, а вместо {1} – значение y*/
  • Замечание.

    Рассмотрим следующий фрагмент программы:

    int i=3, j=4; Console.WriteLine("{0} {1}", i, j);

    При обращении к методу WriteLine через запятую перечисляются три аргумента: "{0} {1}", i, j. Первый аргумент определяет формат выходной строки. Следующие аргументы нумеруются с нуля, так переменная i имеет номер 0, j – номер 1. Значение переменной i будет помещено в выходную строку на место {0}, а значение переменной j – на место {1}. В результате на экран будет выведена строка: 3 4. Если мы обратимся к методу WriteLine следующим образом Console.WriteLine("{0} {1} {2)", j, i, j), то на экран будет выведена строка: 4 3 4.

    Последний вариант использования метода WriteLine является наиболее универсальным, потому что он позволяет не только выводить данные на экран, но и управлять форматом их вывода. Рассмотрим несколько примеров:

  • Использование управляющих последовательностей:

    Управляющей последовательностью называют определенный символ, предваряемый обратной косой чертой. Данная совокупность символов интерпретируется как одиночный символ и используется для представления кодов символов, не имеющих графического обозначения (например, символа перевода курсора на новую строку) или символов, имеющих специальное обозначение в символьных и строковых константах (например, апостроф). Рассмотрим управляющие символы:

    Вид Наименование
    \a Звуковой сигнал
    \b Возврат на шаг назад
    \f Перевод страницы
    \n Перевод строки
    \r Возврат каретки
    \t Горизонтальная табуляция
    \v Вертикальная табуляция
    \\ Обратная косая черта
    \' Апостроф
    \" Кавычки

    Пример:

    static void Main()
    {
    Console.WriteLine("Ура!\nСегодня \"Информатика\"!!!");    
    }

    (рис 2.1) 02_01

  • Управление размером поля вывода:

    Первым аргументом WriteLine указывается строка вида {n, m} – где n определяет номер идентификатора из списка аргументов метода WriteLine, а m – количество позиций (размер поля вывода), отводимых под значение данного идентификатора. При этом значение идентификатора выравнивается по правому краю. Если выделенных позиций для размещения значения идентификатора окажется недостаточно, то автоматически добавится необходимое количество позиций. Пример:

    static void Main()
       {
        double x= Math.E;
        Console.WriteLine("E={0,20}", x);
        Console.WriteLine("E={0,10}", x);
        }
    (рис 2.2) 02_02
  • Управление размещением вещественных данных:

    Первым аргументом WriteLine указывается строка вида {n: ##.###} – где n определяет номер идентификатора из списка аргументов метода WriteLine, а ##.### определяет формат вывода вещественного числа. В данном случае под целую часть числа отводится две позиции, под дробную – три. Если выделенных позиций для размещения целой части значения идентификатора окажется недостаточно, то автоматически добавиться необходимое количество позиций. Пример:

    static void Main()
       {
        double x= Math.E;
        Console.WriteLine("E={0:##.###}", x);
        Console.WriteLine("E={0:.####}", x);
        }
    (рис 2.3) 02_03
    Задание. Измените программу так, чтобы число e выводилось на экран с точностью до 6 знаков после запятой.
  • Управление форматом числовых данных:

    Первым аргументом WriteLine указывается строка вида {n: <спецификатор>m} – где n определяет номер идентификатора из списка аргументов метода WriteLine, <спецификатор> - определяет формат данных, а m – количество позиций для дробной части значения идентификатора. В качестве спецификаторов могут использоваться следующие значения:

    Параметр Формат Значение
    C или c Денежный. По умолчанию ставит знак р. Изменить его можно с помощь объекта NumberFormatInfo Задается количество десятичных разрядов.
    D или d Целочисленный (используется только с целыми числами) Задается минимальное количество цифр. При необходимости результат дополняется начальными нулями
    E или e Экспоненциальное представление чисел Задается количество символов после запятой. По умолчанию используется 6
    F или f Представление чисел с фиксированной точкой Задается количество символов после запятой
    G или g Общий формат (или экспоненциальный, или с фиксированной точкой) Задается количество символов после запятой. По умолчанию выводится целая часть
    N или n Стандартное форматирование с использованием запятых и пробелов в качестве разделителей между разрядами Задается количество символов после запятой. По умолчанию – 2, если число целое, то ставятся нули
    X или x Шестнадцатеричный формат
    P или p Процентный

    Пример:

    static void Main()
     {
       Console.WriteLine("C Format:{0,14:C} \t{0:C2}", 12345.678);
       Console.WriteLine("D Format:{0,14:D} \t{0:D6}", 123);
       Console.WriteLine("E Format:{0,14:E} \t{0:E8}", 12345.6789);
       Console.WriteLine("G Format:{0,14:G} \t{0:G10}", 12345.6789);
       Console.WriteLine("N Format:{0,14:N} \t{0:N4}", 12345.6789);
       Console.WriteLine("X Format:{0,14:X} ", 1234);
       Console.WriteLine("P Format:{0,14:P} ", 0.9);    
     }

    (рис 2.4) 02_04

  • Ввод данных

    Для ввода данных обычно используется метод ReadLine, реализованный в классе Console. Особенностью данного метода является то, что в качестве результата он возвращает строку ( string ). Пример:

    static void Main()
     {
       string s = Console.ReadLine();
       Console.WriteLine(s);
     }

    Для того чтобы получить числовое значение необходимо воспользоваться преобразованием данных. Пример:

    static void Main()
     {
       string s = Console.ReadLine();
       int x = int.Parse(s); //преобразование строки в число
       Console.WriteLine(x);
     }

    Или сокращенный вариант:

    static void Main()
     {
       //преобразование введенной строки в число
       int x = int.Parse(Console.ReadLine()); 
       Console.WriteLine(x);
     }

    Для преобразования строкового представления целого числа в тип int мы используем метод int.Parse(), который реализован для всех числовых типов данных. Таким образом, если нам потребуется преобразовать строковое представление в вещественное, мы можем воспользоваться методом float.Parse() или double.Parse(). В случае, если соответствующее преобразование выполнить невозможно, то выполнение программы прерывается и генерируется исключение System.FormatExeption (входная строка имела неверный формат).

    Задание. Измените предыдущий фрагмент программы так, чтобы с клавиатуры вводилось вещественное число, а на экран это число выводилось с точностью до 3 знаков после запятой.

    Практикум

    Написать программу, которая, реализует диалог с пользователем:

  • запрашивает с клавиатуры два целых числа, и выводит на экран сумму данных чисел:

    (рис 2.1) 02_01

    Пример:

    using System;
    
    namespace Hello
    {
      class Program
      {
       static void Main()
       {
        Console.Write("a= ");
        int a = int.Parse(Console.ReadLine());
        Console.Write("b= ");
        int b = int.Parse(Console.ReadLine());
        Console.WriteLine("{0}+{1}={2}", a, b, a + b);
       }
      }
    }
  • запрашивает с клавиатуры два целых числа, и выводит на экран сумму данных чисел в прямом и обратном порядке:

    (рис 2.2) 02_02

  • запрашивает с клавиатуры три целых числа, и выводит на экран сумму данных чисел:

    (рис 2.3) 02_03

  • запрашивает с клавиатуры два вещественных числа, и выводит на экран произведение данных чисел (вещественные числа выводятся с точностью до 1 знака после запятой):

    (рис 2.4) 02_04

  • запрашивает с клавиатуры два вещественных числа, и выводит на экран результат деления первого числа на второе (вещественные числа выводятся с точностью до 3 знаков после запятой):

    (рис 2.5) 02_05

  • запрашивает с клавиатуры три вещественных числа, и выводит на экран следующее сообщение (вещественные числа выводятся с точностью до 2 знаков после запятой):

    (рис 2.6) 02_06

  • Самостоятельная работа

    Используя Интернет и дополнительную литературу найти ответы на следующие вопросы:

  • Чем отличается метод Console.WriteLine() от метода Console.Write()?
  • Чем отличается метод Console.ReadLine() от метода Console.Read()?
  • Какой тип имеет литеральная константа 3.2?
  • Как явным образом уточнить тип литеральной константы?
  • Что обозначается константой NaN? И в каких случаях компилятором используется данная константа?
  • Вернуться к учебному плану