Представление данных в языке C#
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

А.Г. Мацкевич

Лекции

 

По курсу


ИНФОРМАЦИОННЫЕ ТЕХНОЛОГИИ            С ИЗЛОЖЕНИЕМ ОСНОВ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С#


Часть первая

Учебное пособие

 

Москва 2016


ФЕДЕРАЛЬНОЕ АГЕНТСТВО СВЯЗИ                                                  Ордена Трудового Красного Знамени федеральное государственное                                                         бюджетное образовательное учреждение высшего образования

Московский технический университет связи и информатики


А.Г. Мацкевич

 

                                                                                         «Рекомендовано УМО по образованию в области

                                                                                  Инфокоммуникационных технологий и систем связи

                                                                                  в качестве учебного пособия для студентов высших

                                                                                  учебных заведений, обучающихся по направлению

                                                                                  подготовки 11.03.02 – Инфокоммуникационные тех-

                                                                                  нологии и системы связи (уровень высшего образова-                                                                                                                                                               

                                                                                  ния – бакалавриат)».                       

                                                                                         Протокол № 85 от 01.09.2015 г.

Лекции

 

По курсу


ИНФОРМАЦИОННЫЕ ТЕХНОЛОГИИ            С ИЗЛОЖЕНИЕМ ОСНОВ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С#


Часть первая

Учебное пособие

 

Москва 2016

УДК 681.3.07

   Мацкевич А.Г.  Лекции  по  курсу: Информационные  технологии

с изложением основ программирования на языке С#: Учебное пособие /

МТУСИ. – М., 2016. – 82 с.

 

   Данная работа является первой частью учебного пособия, написанного по лекциям, прочитанным автором в течение двух лет, и состоит из семи лекций, что соответствует материалу, изложенному в первом семестре. Задача пособия – преподать основы программирования на С# студентам, не получившим компетенций программирования в курсе «Информатика» средней школы и не имеющим опыта в написании программ на каком-либо языке программирования.

 Ил. 34, табл. 3 список лит. 7 назв.

 

 

Рецензенты: Е.Б.Козеренко, к.ф.н., зав. лаб. (ФИЦ ИУ РАН)

                 М.Я.Клепцов, д.т.н., профессор (МГУПС)

 

                                                       © Московский технический университет                                           связи и информатики, 2016

                            

                                    Какие-либо знания из курса «Информатика»

                                      средней школы не предполагаются.


Лекция 1. Начала основ

 

Введение

Компания Microsoft (MS) разработала платформу .NET Framework, в которую входит библиотечный набор классов и общеязыковая среда выполнения (Common Language Runtime – CLR). Программный код для этой среды можно писать на различных языках программирования, однако самым популярным из них является язык C#, который был создан именно для этой платформы сотрудником MS Андерсом Хейлсбергом.

В этом учебном пособии в виде лекций кратко изложены основы языка высокого уровня C#, причем не предполагается, что читатели имеют хоть какой-то опыт программирования. Вы уже можете возразить, что в двух первых предложениях используются неизвестные понятия: платформа .NET, языки программирования, классы. Пока не обращайте на это внимания.

Итак, язык программирования C#. Это один из языков программирования платформы .NET, на котором можно написать программу для решения практически любой задачи. Программа в среде .NET называется приложением. Начнем с описания структуры приложения на C#. Это позволит посмотреть на приложение «сверху», то есть охватить всю «картину мира C#». Конечно, в дальнейшем освоение этого мира начнется с самых азов.

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

Кажется, что такая «матрешка» уже встречалась в какой-то русской сказке: помните, смерть Кощея Бессмертного лежит в сундуке, который висит на дубе. В сундуке сидит утка. В утке есть яйцо, а в нем игла. Если сломать эту иглу, то Кащей помрет. Получается игла в яйце, яйцо в утке, утка в сундуке, сундук на дубе. Так и в С#: последовательность операторов в методе Main(). Метод Main() в одном из классов. Класс в одном из пространств имен. Приложение состоит из нескольких пространств имен.

Можно провести еще одну параллель – организация файловой системы. Если посмотреть сверху, то можно сказать, что файловая система состоит из папок. Каждая папка состоит из других папок и файлов. В файлах хранится информация. Но это лирика.

Изучение C# начнем с того, что будем записывать необходимую нам последовательность операторов в единственный метод Main() класса. Что такое оператор? Можно считать, что это инструкция, по которой выполняются некоторые известные стандартные действия. Эти действия будут выполнены во время работы приложения после его запуска на выполнение. Класс, в который входит этот метод, а тем более пространство имен, пока трогать не будем.

Для эффективной работы по созданию программных приложений на платформе .NET разработана  интегрированная среда разработки приложений Visual  Studio .NET (VS). Встает вопрос – как работать с приложением в среде VS ?

 Для этого надо найти на рабочем столе компьютера иконку Microsoft Visual Studio и запустить среду. Здесь все примеры сделаны в среде Visual Studio 2010. Есть другие версии VS: 2008, 2012, 2014. На первоначальном уровне освоения это практически не имеет значения. Откроется главное окно, которое состоит из нескольких панелей (рис. 1.1).

 

 

 

Рис. 1.1. Фрагмент главного окна среды VS

 

Однако пришло время определиться с терминологией для того, чтобы в дальнейшем оперировать одинаковыми понятиями.

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

Проект является также единицей компиляции. Результатом компиляции проекта является сборка. Каждый проект содержит всю информацию, необходимую для построения сборки.

В зависимости от выбранного типа проект может быть выполняемым или невыполняемым. К выполняемым проектам относятся, например, проекты типа Console или типа Windows. В результате компиляции такого проекта создается PE-файл (Portable Executablefile) –  выполняемый переносимый файл с расширением . exe. Такой PE-файл может выполняться только на компьютерах, где установлен Framework.Net.

К невыполняемым проектам относятся, например, проекты типа        DLL – динамически связываемые библиотеки, т.е. проекты, предназначенные для включения (связывания) в другой проект. В результате компиляции такого проекта в сборку войдет файл с уточнением . dll. Такие проекты (сборки) непосредственно не могут быть выполнены на компьютере. Они присоединяются к выполняемым сборкам, откуда и вызываются методы классов, размещенных в невыполняемом проекте (DLL). 

Каждый проект, создаваемый в VS, помещается в оболочку, называемую Решением – Solution. Решение может содержать несколько проектов, как правило, связанных общей темой. Решения позволяют придать структуру множеству проектов, что особенно полезно, когда проектов много.

 Теперь в окне «Начальная страница» находим и смело выбираем пункт «Создать проект». Откроется окно «Создать проект», как показано                   на рис. 1.2.

 

 

Рис. 1.2а. Фрагмент окна «Создать проект» среды VS

 

Попасть в это окно можно и по-другому - используя пункты главного меню (по-русски)(File) -> (по-русски)(New) ->(по-русски)(Project).

В окне «Установленные шаблоны» (слева) выбираем язык «Visual C#»,  в среднем окне выбираем «Консольное приложение». Внизу есть еще один или два однострочных редактора, в которых надо (или можно) выбрать Имя проекта (Name) и расположение Папки проекта (Location). Обратите внимание на расположение папки проекта. С использованием кнопки Обзор(View) следует расположить эту папку в удобном месте файловой системы компьютера.

 

 

Рис. 1.2б. Другой фрагмент окна «Создать проект» среды VS

 

 

И смело нажимайте Ok.

 

Появится окно текстового редактора исходного программного кода. В одной из вкладок окна редактора откроется файл с именем Program. cs   (рис. 1.3).

 

 

Рис. 1.3. Страница текстового редактора с файлом Program.cs

 

Вот и добрались до метода Main(). При этом не написали ни одной строчки программного кода.

Строка static void Main(string[] args) есть заголовок метода Main().

 

Можно начинать работать.

Но не надо спешить. Посмотрим на текст файла Program.cs как на заготовку программного кода, которую предлагает использовать среда разработки VS.

В строках со служебными словами using к программе подключаются пространства имен, необходимые для работы нашей программы. Эти пространства имен содержат много-много классов. В этих классах реализованы задачи, которые могут быть использованы в программе, написанной для консольного режима.

Следующая строка начинается с ключевого слова na m espase, за которым объявляется имя нового пространства имен ConsoleApplication1. Это пространство имен, которое будет использоваться в нашем разрабатываемом проекте. Среда VS предлагает использовать имя ConsoleApplication1. Это имя можно поменять.

Далее, в этом пространстве имен организован всего один класс с именем Program:

 class Program.

 Если очень хочется, то и это имя можно поменять. А уже в этом классе есть метод с именем Main(). Вот это имя менять не надо ни в коем случае. Помните, что язык C# – это набор пространств имен, которые состоят из классов и так далее, а метод Main() – это тот метод, с которого начинает выполняться программа. И еще, обратите внимание на фигурные скобки – это границы программных блоков. В предлагаемой программе программные блоки вложены друг в друга. Это вещь серьезная, с ними надо быть внимательными. Ведь все, что объявлено (описано) в программном блоке, можно использовать, или по-другому будет видно только в нем. Что значит «объявлено»? Об этом речь впереди.

Эту заготовку уже можно откомпилировать и запустить на выполнение. Для этого достаточно нажать клавишу F5. На какое-то мгновение появится черное окно и всё на этом закончится. Программа ничего не сделает,  кроме того, что откроет и закроет консольное окно, но формально или с точки зрения компилятора она правильная, написана без ошибок, поэтому она будет успешно откомпилирована и запущена на выполнение.

Итак, уже можно писать код «внутри» метода Main(). А потом? Потом необходимо откомпилировать программу, то есть создать код на промежуточном языке, который затем, при успешной компиляции, можно будет запустить на выполнение. Но сначала надо узнать, что же можно написать в методе Main().

 

 

Пример 1.1. Объявить переменные на C#.

int i,j,k;

double x,y,_FirstX, _firstX;  

      

Обратите внимание на имена _FirstX, _firstX. Это разные имена, так как компилятор в С# различают регистры (маленькие и большие буквы). Однако, имена, отличающиеся только регистрами символов, лучше не использовать. Ошибок будет меньше.

Что же произойдет, если такие строки встретятся в исходном коде программы на C#? Во время выполнения (RunTime) программа обратится к ОП и выделит память под каждую из объявленных переменных: под переменные i, j, k будет отведено по четыре байта, под остальные по восемь байтов для каждой. Для программы всем объявленным переменным будут поставлены в соответствие адреса ОП. Такой процесс называется инициализацией имен. Так происходит с всеми так называемыми значимыми типами данных. Что это означает? Это означает, что объявления имени достаточно для ее инициализации.

Есть и другие типы, которые называются ссылочными. Их инициализация не происходит автоматически при объявлении. Это надо делать «вручную», используя специальные средства. Об этом позже в лекции 4. Все типы из табл. 1.1 являются значимыми типами.

Вернемся к инициализации списком. Память под переменные отведена? Ответ: отведена. Вопрос: какие данные там находятся? В языке C# компилятор следит за этим: если память отведена, но переменной не присвоено какое-либо значение, то компилятор предупредит об этом.

 

Пример 1.2. Объявить переменные на C#.

int i1=1; int j1=1000; int k1=-30;

double x1=2.4; double y1=-2.22E+3;  

          

При таком объявлении будет отведена память под каждую переменную, и туда (в эту память) будет «положено» соответствующее значение. Обратите внимание, что здесь тип придется писать перед каждым именем переменной, то есть, при таком описании нельзя объявлять переменные списком. Здесь возникает вопрос, а что будет если написать int ii=1.5, то есть объявить, что переменная ii целого типа, а далее попытаться присвоить ей действительное значение (1.5). Компилятор «заметит» это и выдаст сообщение об ошибке.

Пример 1.5.

 

 

Для вводимых значений действительных x,y вычислить

 

.

 

 

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

Схема алгоритма решения примера приведена на рис. 1.4, а исходный код в примере 1.6

 

Рис. 1.4. Схема алгоритма примера 1.5

 

Пример 1.6. Исходный текст последовательной программы примера 1.5.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

Лекция 2. Методы класса

 

Итак, методы. Надо вспомнить, как организована программа на C#. Помните «матрешку»? После освоения материалов первой лекции более-менее понятно, как организован метод Main(), то есть возможность написать какой-то код в теле этого метода. Надо идти дальше! Теперь надо разобраться, как организованы методы вообще. Если это будет понятно, то появится возможность писать другие методы в теле класса и вызывать их как в методе Main(), так и в других методах. Таким образом, будет освоен следующий уровень вложения этой «матрешки». И тогда в примере рис.1.4 из предыдущей лекции сможем в классе Program (class Program), который до этого состоял из одного метода Main(), написать свои методы, которые могут вызываться как из метода Main(), так и из других методов этого класса. В дальнейшем поднимемся еще на один уровень в «матрешке» – научимся организовывать свой класс, и использовать экземпляры этого класса, называемые объектами, в своих программах. Вот такой план введения или ввода в объектно-ориентированное программирование (ООП). Но о классах будем говорить в Лекции 4.

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

Определение метода следующее: формально, метод – это часть программного кода, к которому можно обратиться по имени. Значит, у метода есть имя. Это имя является составной частью заголовка метода. Заголовок метода, по сути, является описанием (объявлением) метода. Зная заголовок метода, можно, организовать его вызов. Как? По его имени. Кроме имени в этом описании есть возвращаемый тип, то есть тип данных, который будет возвращен в вызывающую программу при вызове метода. Как? Через имя метода. Заголовок является началом описания метода. После заголовка находится программный блок, который называется телом метода. В нем и записывается тот код, о котором шла речь в определении метода. Кроме операторов в теле метода могут быть объявлены имена, которые называются локальными. Следует напомнить, что программный блок – это код, заключенный в фигурные скобки «{…}» и все имена, описанные внутри программного блока, действуют только внутри этого блока. Поэтому они и называются локальными.

Еще в заголовок метода входитсписок формальных параметров, который записывается в круглых скобках сразу же за именем метода.

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

<модификатор><тип_возвр._рез-та> <имя_метода><(список_форм._пар-ов)>;

После того как появилось описание метода, этот метод может быть вызван. Сколько раз можно вызвать метод? Вызвать метод можносколько угодно раз (сколько требуется согласно алгоритму решения задачи). Как вызвать метод? Метод вызываетсяпо имени со списком фактических параметров в другом методе. Список фактических параметров пишется в скобках за именем метода при вызове метода.

О списках параметров и об их роли в работе программы надо говорить особо. Списков параметров два: список формальных параметров и список фактических параметров. Зачем нужны эти списки параметров? – для надежного обмена информацией между вызывающей программой и вызванным методом.

Первый список - список формальных параметров - это набор переменных с описанием их типов, а также с описанием способа взаимодействия со вторым списком - списком фактических параметров. Но о способах взаимодействия чуть позже. Список формальных параметров есть расширение списка локальных имен, описанных в теле метода, поэтому память под них отводится в той части ОП, которая используется внутри метода. Эта память начинает захватываться в момент вызова метода и передачи ему управления выполнением программы. Эта память освобождается в момент окончания метода и передачи управления назад в вызывающую программу. Список формальных параметров может быть пустым, тогда от него остается только пара скобок. По этим скобкам компилятор отличает имя метода от других имен, использующихся в C#. Зачем нужен список формальных параметров? – еще раз, это один из двух списков параметров, с помощью которых организован обмен информацией между вызываемой программой и методом.

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

Как же взаимодействует эта пара: список фактических параметров и список формальных параметров при вызове метода. Здесь есть два варианта взаимодействия. Первый – при вызове метода формальный параметр получает копию значения соответствующего фактического значения. Такая передача называется передача по значению. Второй – при вызове метода формальный параметр получает ссылку (адрес ОП) на соответствующий фактический параметр. Такая передача называется передача по адресу или по ссылке. Очевидно, что в этом случае в качестве фактического параметра может быть только переменная, так как у нее есть адрес ОП, по которому лежит её значение. При передаче же по значению в качестве фактического параметра может выступать и выражение соответствующего типа. С какой целью так сделано? Если передача фактического параметра организована по значению, то в этом случае информацию можно передать только из вызывающей программы в метод. Передали значение и потеряли связь с фактическим параметром. Если внутри метода формальный параметр будет изменяться, ведь это же переменная, значит, она может меняться, вызывающая программа этого никак не почувствует. Такие параметры можно классифицировать, как входные. Если же передача организована по адресу, то в формальный параметр передается адрес фактического параметра. При изменении формального параметра внутри метода, эти изменения будут проведены по переданному адресу фактического параметра. После того как код метода будет выполнен и передача управления будет передана назад в вызывающий метод, эти изменения останутся по адресу фактического параметра. Так можно передать информацию из метода в вызывающую программу. Естественно, что такие параметры классифицируются, как выходные. Используя этот механизм передачи, можно организовать параметр, который будет и входным и выходным. Так бывает, например, в случае, когда есть начальное значение, которое в процессе вычислений трансформируется в результирующее.

Пора все, что было сказано, продемонстрировать на примере. После этого, надо надеяться, будет более понятно.

 

Пример 2.1. Определить все углы треугольника при заданных значениях сторон a , b , c , и спользуя вычисление угла по теореме косинусов:

                    .

Понятно, что угол, вычисленный по этой формуле, противоположен стороне a треугольника.

Сколько методов надо организовать для решения этой задачи? Можно ли организовать один метод, в котором бы вычислялся один угол? И решить задачку, вызывая этот метод для вычисления всех трех углов? Можно и достаточно просто. Надо написать один метод, допустим, с именем Ugol. У этого метода будет три формальных входных параметра, допустим, a,b,c. В этом методе будет вычисляться противоположный угол к первому параметру a по формуле из примера 2.1. Вызывать этот метод надо, используя разные списки фактических параметров: первый раз вызвать так – ug _ a =Ugol( a , b , c ), второй раз вызвать по-другому – ug _ b =Ug ol ( b , a , c ), третий раз вызвать так – ug _ c =Ugol( c , a , b ). В каждой переменой ug_a, ug_b, ug_c будут лежать противоположные углы, соответствующие первому фактическому параметру метода Ugol. Еще следует сказать, что формальные параметры a,b,c и фактические параметры a,b,c - это разные переменные, хотя и записаны одинаковыми идентификаторами. Почему? Память под них будет отводиться в разных местах ОП. И это хорошо – одни и те же идентификаторы можно использовать и при описании метода, и при его вызове, не опасаясь, что компилятор их перепутает.

В примере 2.2 приведен начальный вариант решения этой задачи.

 

Пример 2.2

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace ConsoleApplication1

{

class Program

{

   static double ugol(double a, double b, double c)

   {

       double aa;

       aa=(b*b+c*c-a*a)/(2*b*c);

       aa = Math.Acos(aa);

       return aa;

   }

 

   static void Main(string[] args)

   {

      double a,b,c;

      Console.WriteLine("Vvedite a,b,c так,чтобы получился треуг-ник");

      a = Convert.ToDouble(Console.ReadLine());

      b = Convert.ToDouble(Console.ReadLine());

      c = Convert.ToDouble(Console.ReadLine());

      double ug_a = ugol(a, b, c); // угол a

      double ug_b = ugol(b, a, c); // угол b

      double ug_c = ugol(c, b, a); // угол c

      Console.WriteLine("a1=" + ug_a + " a2=" + ug_b + " a3=" + ug_c);

      Console.ReadLine();

   }

}

}

 

Запустим её в VS с такими сторонами  треугольника: a=1; b=1; c=1. Это тестовый пример. Все три угла должны быть одинаковыми и равны 60 градусам (рис. 2.1).

 

 

Рис. 2.1. Окно выполнения программы примера 2.1

 

Что-то не так? Углы одинаковые, но не 60 градусов. Да, конечно, углы ведь вычислены в радианах! Но это не страшно. Это подправим, но позже. А сейчас проанализируем  программный код, представленный в примере 2.1.

Внутри class Program теперь два метода: Main() и Ugol().

Разберем построчно метод Ugol().

static double ugol(double a, double b, double c) – это заголовок метода:

– static означает, что метод ugol может быть вызван без инициализации объект класса Program. Пока это непонятно, но потерпите до лекции 6.

– double означает, что в имя метода ugol будет возвращено значение типа double.

– ugol – имя метода.

– (double a, double b, double c) – список формальных параметров. Все они типа double, перед ними ничего не написано, значит способ обмена - передача по значению.

Далее в фигурных скобках – тело метода:

– double aa; - описание локального имени aa типа double.

– aa=(b*b+c*c-a*a)/(2*b*c); - присваивание переменной aa значения выражения, стоящего справа от знака «=». По смыслу задачи в переменной aa будет лежать значение косинуса угла, противоположного стороне из списка формальных параметров как a.

– aa = Math.Acos(aa); - По смыслу задачи переменной aa присваивается значение этого угла. Вызывется метод Acos(*) класса Math, который вычисляет арккосинус своего аргумента (фактического параметра). Угол здесь в радианах.

 – return aa; – возврат aa, то есть значение переменой aa будет возвращено в имя метода.

 

Пример 2.2

static double Rad_v_Grad(double x)

   {

       return 180 * x / Math.PI;

   }

Представляется, что этот код в пояснениях не нуждается.

 

Теперь тело класса Program будет выглядеть, как в примере 2.3.

 

Пример 2.3 Класс Program примера 2.1.

class Program

{

   static double ugol(double a, double b, double c)

   {

       double aa;

       aa=(b*b+c*c-a*a)/(2*b*c);

       aa = Math.Acos(aa);

       return aa;

   }

// Это новое

   static double Rad_v_Grad(double x)

   {

       return 180 * x / Math.PI;

   }

 

   static void Main(string[] args)

   {

      double a,b,c;

      Console.WriteLine("Vvedite a,b,c");

      a = Convert.ToDouble(Console.ReadLine());

      b = Convert.ToDouble(Console.ReadLine());

      c = Convert.ToDouble(Console.ReadLine());

      double ug_a = ugol(a, b, c);

      double ug_b = ugol(b, a, c);

      double ug_c = ugol(c, b, a);

      // это добавили

      ug_a = Rad_v_Grad(ug_a);

      ug_b = Rad_v_Grad(ug_b);

      ug_c = Rad_v_Grad(ug_c);

      Console.WriteLine("a1=" + ug_a + " a2=" + ug_b + " a3=" + ug_c);

      Console.ReadLine();

   }

}

 

 

Рис. 2.2. Результаты работы программы примера 2.3

 

 И последнее. Собственно решение задачи – это последовательность, состоящая из шести вызовов методов в Main()-е. Это много, можно шесть раз ошибиться. Сделаем надежнее. Организуем метод, вызов которого в Main решит всю задачу. На вход этого метода подадим три стороны, на выходе – три угла в градусах. Этим самым возьмем решение всей задачи в свои руки, если так будем делать и  дальше, то скорее всего это приведет к созданию более надежных программ. Ведь теперь ошибиться можно, только перепутав последовательность фактических параметров.

Но есть особенность. Если метод возвращает значение типа double или int или какое-либо другое, то через имя метода можно возвратить всего лишь одно значение. А тут этих значений три. Здесь-то и понадобится механизм передачи параметров по адресу. Ведь значения вычисленных углов – это типичные выходные параметры. Далее. В качестве возвращаемого типа метода может быть записано значение void, что значит буквально «ничего не возвращать». Что при этом изменится по сравнению с методом, у которого возвращаемый тип отличается от void (double, int, string и т.п.)? В теле метода, который ничего не возвращает, не должно быть оператора return, кроме этого изменится вызов метода – он будет вызываться просто по имени.

Чтобы передача параметров была организована по адресу, один из вариантов – использовать служебное слово out. Причем его ( out ) надо писать как перед формальным параметром, сообщая компилятору, что будет передача по адресу, так и перед соответствующими фактическими параметрами. Такая организация передачи адреса говорит о том, что параметры будут строго выходными. Что это значит? Это значит, что в теле метода этому формальному параметру обязательно должно быть присвоено какое-либо значение. А вот соответствующему фактическому параметру до вызова метода никакого значения присваивать не следует.

Другой вариант передачи по адресу – использовать служебной слово ref. В этом случае формальный параметр может быть как входным, так и выходным. При этом в теле метода совершенно необязательно ему присваивать какое-либо значение. А вот соответствующему фактическому параметру присвоить значение обязательно придется еще до вызова метода.

 

Пример 2.4. Окончательный вариант исходного кода программы примера 2.1

 

class Program

{

   static double ugol(double a, double b, double c)

   {

       double aa;

       aa=(b*b+c*c-a*a)/(2*b*c);

       aa = Math.Acos(aa);

       return aa;

   }

 

   static double Rad_v_Grad(double x)

   {

       return 180 * x / Math.PI;

   }

// это новое

   static void TriUgla(double a,double b,double c,out double ug_a,out double ug_b,out double ug_c)

   {

      ug_a = ugol(a, b, c);

      ug_b = ugol(b, a, c);

      ug_c = ugol(c, b, a);

      ug_a = Rad_v_Grad(ug_a);

      ug_b = Rad_v_Grad(ug_b);

      ug_c = Rad_v_Grad(ug_c);

   }

 

   static void Main(string[] args)

   {

      double a,b,c;

      Console.WriteLine("Vvedite a,b,c");

      a = Convert.ToDouble(Console.ReadLine());

      b = Convert.ToDouble(Console.ReadLine());

      c = Convert.ToDouble(Console.ReadLine());

      double ug_a,ug_b,ug_c;

      // здесь изменили

      TriUgla(a, b, c, out ug_a, out ug_b, out ug_c);// решается вся задача

      Console.WriteLine("a1=" + ug_a + " a2=" + ug_b + " a3=" + ug_c);

      Console.ReadLine();

   }

}

 

 

Рис. 2.3. Результаты работы программы примера 2.6

 

 

If (логическое выражение)

{

Оператор1;

  …

ОператорN;

}

Else

{

оператор2;

}

 

В программировании различают простые и сложные операторы. В сложных операторах используются другие операторы. В простых не используются. Условный оператор - это сложный оператор.

Нужны примеры. Но сначала займемся алгоритмизацией на уровне схем алгоритмов.

 

Пример 3.1. Вычислить значение максимума из двух чисел.

Решение представлено на рис. 3.2 в двух вариантах

                      а)                                                  б)

 

Рис. 3.2. Два варианта поиска максимума из двух значений

 

На рис. 3.2а в логическом выражении сравниваются значения (a > b). Если результат сравнения истина ( true или да), то переменной max присваивается значение переменной a: max = a, иначе в переменную max скидывается значение переменной b: max=b.

На рис. 3.2б сделано по-другому. Сначала в переменную max скидывают значение переменной a, то есть сначала предполагают, что максимальное значение - это значение переменной a. А потом проверяют эту гипотезу следующим образом: max < b. Если это условие выполняется, это значит, что есть еще большее значение чем max, значит max надо менять (m ax= b). А если это условие ложно, то max менять не надо (ничего не делать).

Давайте разовьем задачу: найдем максимум из трех чисел a, b и c.

Как её решать? Сведём решение этой задачи к предыдущей, ведь максимум из двух чисел, надо надеяться, искать умеем. Как? Найдем максимум из двух первых чисел, получим результат сравнения двух чисел, а дальше этот результат сравним с третьим числом. Получим результат сравнения трех чисел (рис. 3.3). Именно так и следует решать эту задачу!

 

Рис. 3.3. Поиск максимума из трех значений

 

Теперь решим более сложную, а главное объемную задачу, в которой продемонстрируем структурный стиль создания блок-схем. Этот стиль требует, чтобы у каждого оператора был один вход и один выход, причем выход должен находиться строго под входом. Затем создадим консольное приложение, в котором будет всего лишь один метод, полностью решающий задачу (конечно, кроме метода Main). В этом методе не будем использовать методы m ax и min из класса Math. Почему? Чтобы продемонстрировать структурный стиль написания исходного текста программы, в котором используются уступы, то есть отступы на 2-3 пробела вправо.

Принцип расстановки уступов в программном коде простой: операторы, которые вложены в другие операторы, пишутся с уступом. Операторы, выполняющиеся последовательно, пишутся на одном уровне.

Пример 3.2. Вычислить значение y при вводимых значениях a и х.

 

y=

 

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

 

 

Рис. 3.4. Схема алгоритма примера 3.2

На рис. 3.4 представлена схема алгоритма метода решения задачи с «ручным» поиском максимума из трех чисел и минимума из двух чисел. На блок-схеме наглядно видны вложенные операторы, которые должны быть выделены в исходном тексте уступами. Видны также и операторы, выполняющиеся последовательно. Они должны быть записаны на одном уровне исходного текста.

Почему важно использовать структурный стиль составления блок-схем и записи исходных текстов программ. В этом случае блок-схемы и программы получаются наглядными и легко читаемыми. В конечном счете это приводит к тому, что в них будет меньше ошибок, а если ошибки и будут, то найти их будет значительно проще. При написании кода интеллектуальный текстовый редактор VS сам расставляет пробелы, однако при редактировании уже написанного исходного кода может ошибаться. Следите за этим! Несоблюдение правил структурного написания кода и составления блок-схем может привести к алгоритмическим ошибкам, и, как следствие, приведет к большой потере времени для поиска ошибок.

В примере 3.1 представлен исходный текст приложения, реализующего схему алгоритма, представленного на рис. 3.4, а на рис. 3.5 – результаты работы программы.

 

Пример 3.3.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace ConsoleApplication1

{

class Program

{

   static void Main(string[] args)

   {

       Console.WriteLine("VVedite x,a");

       double x = Convert.ToDouble(Console.ReadLine());

       double a = Convert.ToDouble(Console.ReadLine());

       double y = RazvMaxMin(x, a);

       Console.WriteLine("Dly x=" + x + " a=" + a);

       Console.WriteLine("Result y=" + y);

       Console.ReadLine();

   }

 

   static double RazvMaxMin(double x, double a)

   {

        double y;

       if (a > x)

       {

           double y1=Math.Sqrt(Math.Abs(Math.Cos(a*x)));

           double y2=Math.Pow(Math.Sin(x),2);

           double min;

           if (y1 < y2)

               min=y1;

             else

               min=y2;

           y=min;

       }

       else

           if (a == x)

               y=Math.Exp(a*x);

           else

           {

               double max=a+x;

               if (max < Math.Sqrt(Math.Abs(x))) max=Math.Sqrt(Math.Abs(x));

               if (max < a*x) max=a*x;

               y=max;

           }

       return y;

   }

}

}

 

 

Рис. 3.5. Результат работы программы примера 3.3

 

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

 

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

 

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

Форма записи оператора выбора следующая:

 

switch (выражение)

{

  case константа1:

          последовательность операторов

          break ;

  case константа2:

          последовательность операторов

          break ;
      . . .

  case константа N:

          последовательность операторов

          break ;

   default :

          последовательность операторов

          break ;

}

Где выражение должно быть такого типа, значения которого известны точно, например типа int, byte, short, char, string. Нельзя использовать типы для действительных чисел (double, single, …), так как их значения представлены в двоичном коде с конечной точностью и это есть существенное ограничение в использовании оператора выбора switch. Константы должны иметь тип, совместимый с типом выражения. Не допускается наличия двух одинаковых по значению констант выбора. Ветка default выполняется в том случае, если не одна константа не совпадает с значением выражения. Эта ветка необязательна. Если значение выражения совпало с одной из констант, то выполняется соответствующая последовательность оператора вплоть до оператора break, по которому заканчивается оператор выбора.

Пример 3.4. Написать фрагмент программного кода, в котором проверяется значение переменной на возможные значения 1, 2, 3, 4.


Пример 3.5.

      . . .

       switch (i)

       {

           case 1:

               Console.WriteLine("i=1");

               break;

           case 2:

               Console.WriteLine("i=2");

               break;

           case 3:

               Console.WriteLine("i=3");

               break;

           case 4:

               Console.WriteLine("i=4");

               break;

           default:

               Console.WriteLine("не то !!!!!");

               break;

        }

    . . .

 

В операторе выбора switch не каждая ветвь должна заканчиваться оператором break. Можно организовать работу swicht так, что несколько веток будут ссылаться на одну и ту же последовательность операторов (пример 3.6).

 

Пример 3.6.

          . . .

          switch (i)

       {

           case 1:

           case 2:

           case 3:

               Console.WriteLine("i=1 или 2 или 3");

               break;

           case 4:

               Console.WriteLine("i=4");

               break;

           default:

               Console.WriteLine("не то !!!!!");

               break;

       }

       . . .

 

 

Сокращенная проверка

В C# существует сокращенная проверка, которая удобна, если проверку надо проводить внутри какого-то кода.

Её синтаксис такой:

Условие ? Действие1 : Действие2

 

Как это работает? Пишем условие, которое должно проверяться. После символа «?» пишется действие, которое должно выполняться в случае истинного результата проверки, а после символа «:» записываем действие, которое должно быть выполнено в случае неудачной проверки.

 

Пример 3.6.

int i=10;

Console.WriteLine( i==10 ? " i=10":" i != 10");

Console.WriteLine( i==20 ? " i=20":" i != 20");

 

 

Рис. 3.6. Результат программы примера 3.6 сокращенной проверкой

 

 

Лекция 4. Введение в ООП

 

До эпохи объектно-ориентированного программирования (ООП) в универсальных алгоритмических языках преобладал процедурный метод программирования, в котором реализованы принципы структурного программирования. Согласно этим принципам, задача разбивается на подзадачи, эти подзадачи разбиваются еще на подзадачи и так делается до тех пор, пока каждая из подзадач не станет простой. Этот этап проектирования называется нисходящим программированием. Получившиеся простые подзадачи реализуются в виде процедур. Напоминаю, что соответствии с принципами ООП процедуры начали называть методами. Кроме того использовался этап, который называется восходящим программированием, то есть реализуются процедуры, в которых вызываются уже написаные процедуры. И так далее, пока не будет написана процедура (метод), которая решает поставленную задачу. Все эти подпрограммы работают с данными. Где размещаются эти данные, и кто контролирует эти данные? Те данные, которые описаны в подпрограммах, доступны только внутри них. А есть еще данные, которые доступны всем подпрограммам. К ним доступ имеют все, и нет никакого контроля за их изменением. В этом случае приложение можно представить в виде совокупности процедур (рис. 4.1).

 

 

Рис. 4.1. Приложение (совокупность процедур), реализованное по принципам процедурного программирования

 

В какой-то исторический момент стало понятно, что это является существенным недостатком такого подхода. Более того, организация программы в виде набора подпрограмм недостаточно эффективна в плане структуризации задачи. Надо более строго и вдумчиво заниматься проектированием программы – до написания самого кода. Это и реализуется в ООП.

По большому счету, программирование – это моделирование реального мира. На разных этапах развития программирования уровень этого моделирования был разным. Например, при использовании только последовательных программ возможно было моделировать ситуации, в которых последовательно выполнялись точно определенные действия. При использовании разветвлений уже можно было рассматривать разные варианты поведения. В основе ООП лежит принцип, в котором реальный мир рассматривается как мир, состоящий из объектов. Объектами могут выступать любые элементы (части) реального мира, в которых можно семантически (в соответствии со здравым смыслом) объединить некоторые свойства объектов и некоторые действия над ними. Например: предметы из окружающего мира имеют вес, цвет и т.п. – это свойства. Ими можно пользоваться: телевизор можно включить, можно регулировать звук, яркость – это методы. Животные, люди – это тоже объекты. Город, деревня, страна – объекты. Задача, которую решали в теме разветвления – объект: входные данные, которые необходимы для ее решения – это ее свойства; алгоритм, решающий задачу – метод объекта. Далее, в зависимости от задачи, можно акцентировать внимание на одних или других свойствах и методах. Например, если телевизор работает, то важны одни его характеристики – сколько каналов, какова цветопередача и т.д., а если он сломался и его надо выкинуть – совсем другие – сколько он весит, пройдет ли он в дверь или в окно. Так вот, ООП реализует этот объектный принцип посредством некоторых новых формализованных понятий.

Таким образом, основа ООП - это объект, за которым стоят конкретные данные. Как сказать, что объект описывается такими-то данными и имеет такие-то методы? Это делать для каждого объекта отдельно? Вряд ли. Может быть, сказать, что есть некий тип объектов и конкретные объекты принадлежат этому типу. Пример с телевизором: телевизоров конкретного типа много, а вот тип у них один. Так вот, для описания объекта вводится понятие класс ( class ). Это некоторый каркас, или план построения объекта, некоторая логическая абстракция, которая задает структуру объекта.

Далее. В основу ООП заложены три парадигмы программирования: инкапсуляция, наследование и полиморфизм. (Парадигма программирования – это совокупность идей и понятий, определяющих стиль написания программы.)

Кратко остановимся на каждой парадигме.

Инкапсуляция – это идея, которая заключается в том, что объект представляет собой капсулу, в которой: первоеданные объекта объединены с методами в единое целое; второе – эти данные и методы имеют разную степень доступности. Пример инкапсуляции из реального мира: телевизор – это инкапсуляция свойств телевизора, таких как размеры, вес, мощность, набор микросхем, блок питания, устройства регулировки звука, яркости, цвета, уровень звука, яркости, цвета и т.п. Это данные. Зачем здесь их перечисляли так много? Чтобы пояснить разные степени доступности: размеры, вес, мощность доступны, но менять их возможности нет (только для чтения), набор микросхем, блок питания, устройства регулировки пользователю телевизора вообще неинтересны, значит доступ к ним надо разрешить только специалистам, а вот уровни звука, яркости, цвета будут общедоступными. Методы в телевизоре – включение – общедоступный метод, регулировка звука и т.п. – общедоступный метод, а вот регулировка уровня питающего напряжения – это удел специалистов. Две крайние степени уровня доступа в объекте: private – доступно только внутри объекта и public – доступно всем (и внутри и вне объекта). Эти уровни доступа надо выставлять в каждом объекте или в классе? Пример с телевизором – в одном телевизоре разрешить менять уровень питающего напряжения, в другом запретить? Конечно нет! Уровни доступа надо выставлять в классе.

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

Ну и наконец, полиморфизм. Здесь не все так очевидно. Идея состоит в следующем: надо сделать так, чтобы у разных классов были разные по содержанию методы, но с одинаковыми именами. Тогда возникает вопрос – какой из этих методов (метод какого класса) будет работать при вызове метода, ведь эти методы имеют одинаковые имена. Решение такое – все зависит от объекта, с которым будут работать в данный момент. Представляется, что примером полиморфизма может быть следующая ситуация: В Москве готовятся к Дню города. Мэр дает службам города поручение: подготовиться к Дню города. Как понимают это поручение разные службы? По-разному. Электрики занимаются иллюминацией, дворники начинают мести дворы, строители ремонтируют дороги, полицейские усиливают наряды и т.п. Получается полиморфизм – имя метода одно (привести город в порядок), а содержание у объектов разных классов разное.

При использовании принципов ООП основное внимание уделяется не методам, а структурированным данным, которыми являются объекты. Основными логическими строительными блоками программы становятся классы и объекты, а не алгоритмы, как в процедурном программировании. И тогда проект в виде набора классов будет выглядеть, как на рис. 4.2.

Рис. 4.2. Проект в виде набора классов, созданный на основе ООП

 

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

Здесь можно провести соответствие с типом данных и переменой. Тип - это класс, переменная – объект или экземпляр класса.

 

Ну, а теперь, как это реализовано на C# ?

В лекции 2, в которой говорилось о методах, был пример, где решалась следующая задача: по сторонам треугольника следует вычислить углы этого треугольника (пример 2.1, исходный код пример 2.4).

Метод, с использованием которого можно решить задачу, уже написан. Его заголовок

double ugol(double a, double b, double c);.

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

Есть еще метод

double Rad_v_Drad(double x),

который переводит значения из радиан в градусы.

Посмотрите внимательно, что написано до описания этих методов в примере 2.6. Там есть строка

class Program.

Оказывается, что уже в том примере работали в классе, но не акцентировали на этом внимание.

Теперь надо написать свой класс, инициировать объект этого класса и организовать работу с этим объектом.

Итак, в соответствии с парадигмой инкапсуляции, класс – это объединение данных и методов. Данные хранятся в полях класса, методы соответствуют методам в классе. Что должно находиться в полях нашего класса? Данные, которые нужны для решения задачи – исходные данные – это стороны треугольника, а результаты – три угла? То же в полях класса. А какие методы нужно организовать в классе, чтобы получить результаты? Ответ очевиден – метод ugol () и метод Rad_v_Drad(). Пока сделаем и поля,  и методы общедоступными, то есть объявим их с директивой public        (пример 4.1).

 

Пример 4.1

class CLUgli

{

   public double a, b, c;

   public double ua, ub, uc;

   public double ugol(double a, double b, double c)

   {

       double aa;

       aa = (b * b + c * c - a * a) / (2 * b * c);

       aa = Math.Acos(aa);

       return aa;

   }

 

   public double Rad_v_Drad(double x)

   {

       return 180 * x / Math.PI;

}

}

 

После разбора примера 2.4 и чтения предыдущего абзаца представляется, что здесь и обсуждать почти нечего. Единственое, на что надо обратить внимание, это то, что описание всего класса оформлено, как программный блок ({..}).

Что нужно делать далее? Ведь это только класс, то есть логическая абстракция, каркас для построения объекта. А конкретный треугольник – это объект данного класса. Сколько может быть объектов этого класса? Сколько необходимо, столько и можно. У каждого объекта свои значения полей, а вот методы у всех объектов данного класса одни и те же. Значит, надо завести объект, дальше заполнять поля с исходными данными, вызывать методы, результаты выполнения которых должны заполнить поля с результатами, и в итоге значения этих полей надо будет вывести на экран. Где это будем делать? – в методе Main (пример 4.2).

Пример 4.2

 

Class Program

{

   static void Main(string[] args)

   {

      CLUgli ugli1 = new CLUgli();

// инициализация объекта ugli1 с помощью вызова конструктора без параметров

      ugli1.a = Convert.ToDouble(Console.ReadLine());

     // работа с открытыми полями класса

      ugli1.b = Convert.ToDouble(Console.ReadLine());

      ugli1.c = Convert.ToDouble(Console.ReadLine());

      ugli1.ua = ugli1.ugol(ugli1.a, ugli1.b, ugli1.c);

// вызовы открытых методов

      ugli1.ua = ugli1.Rad_v_Drad(ugli1.ua);

      ugli1.ub = ugli1.ugol(ugli1.b, ugli1.a, ugli1.c);

      ugli1.ub = ugli1.Rad_v_Drad(ugli1.ub);

      ugli1.uc = ugli1.ugol(ugli1.c, ugli1.a, ugli1.b);

      ugli1.uc = ugli1.Rad_v_Drad(ugli1.uc);

     // работа с открытыми полями класса

      Console . WriteLine ("Класс: для сторон a =" + ugli 1. a + " b =" +

                         ugli1.b + " c=" + ugli1.c);

      Console.WriteLine(" Углы получаются a1=" + ugli1.ua + " a2=" +

                         ugli1.ub + " a3=" + ugli1.uc);

      

       Console . ReadLine ();

   }

}

 

Теперь надо построчно разобрать работу метода Main из примера 4.2.

Строка CLUgli ugli1 = new CLUgli();

требует особого внимания. Почему? В ней производятся два действия. Первое – объявляется переменная нашего класса CLUgli с именем ugli 1 . Переменная класса – это и есть объект. Еще в этой строке происходит инициализация этого объекта с использованием команды new и вызова специального метода, который называется конструктор. Напомним, что в C# есть значимые и ссылочные типы, и об этом говорилось еще в лекции 1. Для инициализации (выделения памяти в ОП) значимых типов, переменные достаточно объявить (описать). Для переменных ссылочных типов этого мало. Их надо инициализировать «вручную», то есть используя команду new и вызов специального метода, называемого конструктор. Именно это здесь и сделано. Как компилятор определяет, что метод есть конструктор? – его имя совпадает с именем метода. Как компилятор определяет, что это метод, а не класс? За именем есть круглые скобки, значит это метод. Где в нашем классе описание этого метода? Нет! Если описания конструктора нет в классе, будет вызываться конструктор его предка. В описании нашего класса не указан предок. Но все классы являются потомками класса с именем object. Значит, по умолчанию, будет вызван конструктор класса object. Здесь работает парадигма наследования.

Два действия, которые выполняются в этой строке, можно разделить на две команды: отдельным оператором объявить переменную класса, а потом отдельным оператором провести инициализацию этой переменной

Дальше идет работа с полями и методами объекта ugli 1. Работу проводят с использованием точечной идентификации, согласно которой сначала указывается имя объекта и через точку пишется имя поля или имя метода.

В примерах 4.1 и 4.2 приведены фрагменты первого или даже нулевого варианта решения задачи. Основное возражение у тех, кто владеет методикой ООП, может возникнуть из-за того, что поля класса должны быть обязательно закрытыми ( private ), а доступ к ним должен быть реализован только через методы. Пока этого делать не будем. Это будет позже.

Продемонстрируем использование закрытых методов. Напомним, если метод закрытый, то им можно пользоваться (его можно вызывать) только в методах этого класса. Какой из двух методов можно закрыть? Закроем метод Rad_v_Drad(), а его вызов организуем в методе ugol ( double a , double b , double c ). Что при этом изменится? Метод ugol () будет возвращать угол уже в градусах. А попытка вызова метода Rad_v_Drad() в методе Main() приведет к ошибке, которую выявит компилятор на этапе построения решения. В примере 4.3 продемонстрировано все пространсто имен, включающее в себя и класс CLUgli , предназначенный для решения задачи и класс Program , в котором находится метод Main().

 

Пример 4.3

Пример решения задачи про углы в Windows -приложении

В качестве задачи для Windows-приложения возьмем задачу из примера 2.6 про углы треугольника. В Лекции 4 решение этой задачи было оформлено в виде класса и написано консольное приложение для её реализации       (пример 4.3).

Пора создать Windows-приложение для реализации решения этой задачи. Можно ли использовать при этом уже созданный класс из примера 4.3? Почему нет? В полях и методах этого класса нет каких-то особенных средств, характерных именно для Windows-приложения. Что, по сути, изменится при оконной реализации решения этой задачи? Изменится лишь заполнение полей с исходными данными и выдача результатов на экран. Ведь для чтения с клавиатуры в Widows -приложении не используются методы типа Console.ReadLine() и для выдачи на экран не используются методы типа Console.WriteLine(). А как это можно сделать – смотрите пример 5.1.

Итак, начнем.

Для этого надо найти на рабочем столе компьютера иконку Microsoft Visual Studio и запустить среду. Откроется главное окно, которое состоит из нескольких панелей (рис. 5.1).

 

Рис. 5.1. Фрагмент главного окна среды VS

 

В Visual Studio любая программа заключается в проект. Проект – это папка для файлов. Для создания нового проекта выберите в меню File | New | Project (Файл|Новый|Проект). Откроется окно New Project (Новый проект), как на рис. 5.2.

 

 

Рис. 5.2а. Фрагмент окна «Создать проект» среды VS

 

В левом окне выберите язык C#, в правом окне выберите тип проекта –Windows Forms Application. Внизу есть еще один или два однострочных редактора, в которых надо (или можно) выбрать имя проекта (Name) и расположение папки проекта (Location). Обратите внимание на расположение папки проекта. С использованием кнопки Обзор (View) следует расположить эту папку в удобном месте.

 

Рис. 5.2б. Другой фрагмент окна «Создать проект» среды VS

 

И смело жмите Ok.

 

Среда VS может быть будет такой, как на рис. 5.3.

 

 

Рис. 5.3а. Фрагмент среды VS в Windows-приложении

 

Если это не так, то окно Панель элементов ( Toolbox ) можно открыть, используя пункт главного меню Вид( View). В этом окне представлен набор элементов, которые можно разместить на форме.

В окне среды VS в Windows-приложении есть возможность открыть окно Свойство( Properties). Если оно не открылось, его можно открыть из пункта главного меню Вид( View).

 

Рис. 5.3б. Фрагмент среды VS в Windows-приложении с окном Свойства

 

В этом окне отображаются свойства и обработчики событий сфокусированного оконного элемента. Сейчас (на рис. 5.3) здесь отображаются свойства формы Form1. Что такое свойство? Это некая конструкция языка C#, с помощью которой можно добраться до полей соответствующего объекта, в данном случае объекта For m 1. Пока так, более четко и формально о свойствах смотри в Лекции 6.

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

Сделаем так: исходные данные будем считывать из трех редакторов TextBox, тексты которых лежат в их свойствах Text. Саму задачу будем запускать в обработчике события Click кнопки Button, а результаты будем выводить тоже в редакторы класса TextBox. В дальнейшем, может быть, вывод изменим.

Поместим три редактора класса TextBox на форму Form1. Для этого надо кликнуть мышью на соответствующий пункт Панели элементов (Toolbox) и далее кликнуть мышью на форме. VS предлагает дать им имена textBox1, textBox2, textBox3. Эти имена можно изменить, но не будем спорить с VS. Во время выполнения программы из них будут считываться значения сторон треугольника. На этапе проектирования поместим в них по «1». Для этого в окне Свойство(Properties) каждого из редакторов в маленькое окно свойства Text запишем «1».

Далее поместим на форму кнопку Button. У нее будет имя button1. В окне Свойство(Properties) этой кнопки напишем «Решение задачи».

И наконец, на форме следует разместить еще три редактора с именами textBox4, textBox5, textBox6. В них будем скидывать результаты.

И еще, разместим на форме кнопку в текстом «Выход», а в свойстве Text самой формы Form1 напишем «Задача об углах треугольника».

Первый пользовательский интерфейс закончили. Может, он будет в дальнейшем дорабатываться.

 

 

Рис. 5.4. Оконная форма в задаче про углы

 

Надо заниматься содержательной частью приложения.

Дважды кликните по кнопке button1. На месте Конструктора форм (Design) откроется редактор кода файла Form1.cs (пример 5.1).

Пример 5.1. Окно редактора кода с «пустым» обработчиком события Button1_Click()

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

public partial class Form1 : Form

{

   public Form1()

   {

       InitializeComponent();

   }

 

   private void button1_Click(object sender, EventArgs e)

   {

 

   }

}

}

 

На что надо обратить внимание? Здесь есть описание класса Form1. Этот класс является наследником класса Form. В описании класса Form1 используется слово partial. Это означает, что это часть описания класса. Следовательно, где-то есть еще другая часть описания. Дальше идет описание конструктора класса Form1. В нем вызывается метод InitializeComponent(). Судя по имени, в нем происходит инициализация компонентов, находящихся на форме. Ну и наконец, в этом файле есть обработчик события Click компонента button1 с именем button 1_ Click. Пока это просто заглушка, то есть метод, который ничего не делает.

Вернитесь в конструктор формы и дважды кликните по кнопке «Выход». В редакторе кода появится еще один обработчик события. Напишите туда Close(); (пример 5.2).

 

Пример 5.2

private void button2_Click(object sender, EventArgs e)

{

    Close();

}

 

Теперь откомпилируйте приложение и запустите его на выполнение.

Что оно может делать? Окно можно перемещать по экрану, менять размер, «схлопнуть» в иконку, раскрыть на весь экран, и, наконец, нажав на кнопку «Выход» закончить выполнение приложения. Это много, даже очень много, причем для этого почти ничего не пришлось писать. Но не хватает собственно решения задачи.

Посмотрите пример 4.3. Проанализируем его. В нем есть класс CLUgli. В этом классе есть поля для хранения исходных данных, есть методы, которые эти исходные данные могут переводить в результаты и есть поля, в которых будут лежать эти результаты. Далее, есть метод Main, в котором происходит инициализация объекта класса CLUgli, затем заполняются поля с исходными данными. Эти данные вводятся с клавиатуры. После этого вызываются методы, которые решают задачу и заполняют поля с результатами. Далее значения этих полей выводятся на экран. Сделаем то же самое, но не в методе Main, а в обработчике события кнопки button1. Для этого перенесем описание класса CLUgli в исходном коде в файл Form1.cs. Единственно, что надо сказать, этот код следует поместить ниже описания класса Form1.

А далее, написать аналог тела метода Main в теле обработчика события Click компонента button1. Исходные данные надо брать как строку из свойства Text соответствующего редактора и скидывать результаты надо тоже в соответствующие редакторы, как строки.  

Пример 5.3

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

public partial class Form1 : Form

{

   public Form1()

   {

       InitializeComponent();

   }

 

   private void button1_Click(object sender, EventArgs e)

   {

      CLUgli ugli1 = new CLUgli();

      ugli1.a = Convert.ToDouble(textBox1.Text);

      ugli1.b = Convert.ToDouble(textBox2.Text);

      ugli1.c = Convert.ToDouble(textBox3.Text);

      ugli1.ua = ugli1.ugol(ugli1.a, ugli1.b, ugli1.c);

      ugli1.ub = ugli1.ugol(ugli1.b, ugli1.a, ugli1.c);

      ugli1.uc = ugli1.ugol(ugli1.c, ugli1.a, ugli1.b);

      textBox4.Text = Convert.ToString(ugli1.ua);

      textBox5.Text = Convert.ToString(ugli1.ub);

      textBox6.Text = Convert.ToString(ugli1.uc);

   }

 

   private void button2_Click(object sender, EventArgs e)

   {

       Close();

   }

}

 

   class CLUgli

   {

       public double a, b, c;

       public double ua, ub, uc;

       public double ugol(double a, double b, double c)

       {

           double aa;

           aa = (b * b + c * c - a * a) / (2 * b * c);

           aa = Math.Acos(aa);

           aa = Rad_v_Drad(aa); //новое

           return aa;

       }

 

       private double Rad_v_Drad(double x) //новое

       {

           return 180 * x / Math.PI;

       }

   }

 

}

}

 

Используем компоненты Label для того, чтобы пользовательский интерфейс стал более понятным. Поместим их левее редакторов и изменим их свойства Text в окне Свойство(Properties) cоответственно на “a=”, “b=”, “c=”,” ua=”,”ub=”,”uc=”.

После этого запуск приложения даст результаты, представленные на рис. 5.5.

 

 

Рис. 5.5. Форма с исходными данными и результатами задачи про углы

 

Лекция 6. Windows-приложение, режим пошаговой отладки. Продолжение ООП

Режим пошагового выполнения

Продолжим изучение Windows-приложения языка C#.

 

Ну хорошо: запустили приложение, увидели окно, нажали кнопку, получили результаты. Вроде все понятно? А как же фраза, сказанная в лекции 1: « Приложение представляет собой набор пространств имен. Каждое пространство имен может включать в себя другие пространства имен и набор классов. Каждый класс представляет собой набор данных и методов обработки этих данных. В одном из классов должен находиться метод с именем Main. Вот с него все и начинается. После того как приложение будет запущено на выполнение, вызывается метод Main и стартует процесс выполнения операторов в том порядке, в котором они, операторы, записаны в методе Main». Где же сейчас этот Main, или все это становится неправдой в оконном режиме? Конечно, можно прямо указать на то место в проекте, где находится метод Main, но можно пойти другим путем: в VS есть режим пошагового выполнения приложения – режим Debug.

Чтобы запустить этот режим, надо войти в пункт главного меню Debug и выбрать один из двух режимов Step Into или Step Over (рис. 6.1). При этом будет реализовываться пошаговый режим работы приложения.

 

 

Рис. 6.1. Запуск режима пошагового выполнения программы (Debug)

 

 

После нажатия F8 или Shift-F8 откроется окно с файлом Program.cs  (рис. 6.2), и каждое следующее нажатие клавиши F8 или Shift-F8 приведет к продвижению приложения на выполнение следующего фрагмента исходного кода. Нажимайте F8.

 

Рис. 6.2. Программа в режиме Debug

 

Программа начинает работу с того, что находит класс, в котором есть метод Main, и вызывает его. Значит, все, что говорилось в лекции 1, остается справедливым и для Windows-приложения!

Где же находится этот метод, что же в нем записано и кто это написал? Этот метод находится в файле Program.cs. Текст файла Program.cs приведен в примере 6.1.

Пример 6.1

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

static class Program

{

   /// <summary>

   /// The main entry point for the application.

   /// </summary>

   [STAThread]

   static void Main()

   {

     Application.EnableVisualStyles();

     Application.SetCompatibleTextRenderingDefault(false);

     Application.Run(new Form1());

   }

}

}

Разберем его. Кто это все написал? Этот исходный код автоматически создан средой VS. Здесь есть полное соответствие аналогичному файлу Program.cs, созданному автоматически в консольном режиме (см. рис. 1.3). В обоих файлах есть пространство имен (namespace WindowsFormsApplication1 и namespace ConsoleApplication1), в обоих файлах есть класс Program (static class Program). В обоих файлах есть метод Main. В консольном режиме             сначала это просто заглушка, тело которой нужно наполнить содержанием, в оконном режиме тело метода Main уже написано. Что же в нем написано?

Не вдаваясь в подробности, разберем содержание метода Main. В методе Main оконного режима используется класс Application из пространства имен System . Windows . Forms . В первой строке вызывается статический метод EnableVisualStyles, который, по-видимому, устанавливает, что визуальные компоненты будут отрисовываться в стиле Windows XP, во второй строке, судя по имени (SetCompatibleTextRenderingDefault), вызывается статический метод, в котором делаются кое-какие необходимые установки.

А вот в третьей строке вызывается статический метод Run , которому в качестве фактического аргумента передается объектное выражение new Form 1(). При вычислении этого выражения создается первый объект – экземпляр класса Form 1. Этот объект становится текущим. Для создания объекта вызывается конструктор класса. В процессе работы конструктора осуществляется вызов метода InitializeComponent. Целью этого вызова является текущий объект – уже созданный объект класса Form 1. Ни в конструкторе, ни в вызванном методе новые объекты не создаются. По завершении работы конструктора объект класса Form 1 передается методу Run в качестве аргумента.

Метод Run класса Application – это знаменитый метод. Во-первых, он открывает форму – видимый образ объекта класса Form 1, с которой теперь может работать пользователь. Но главная его работа состоит в том, что он создает настоящее Windows-приложение, запуская цикл обработки сообщений о происходящих событиях. Поступающие сообщения обрабатываются операционной системой согласно очереди и приоритетам, вызывая обработчиков соответствующих событий.

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

Могут быть и статические поля класса. Перед их именами также пишется через точку имя класса. Есть одна особенность таких полей - их значения одинаковы для всех объектов класса. И еще, если класс содержит только статические элементы, экземпляр класса создавать не требуется. Чтобы поле или метод стал статическим, следует использовать модификатор st a tic. Ранее, в консольных приложениях это делалось постоянно.

Продолжим использование режима пошагового выполнения приложения. На рис. 6.2 программа находится в точке, предваряющей вызов метода Run. Продолжайте нажимать F8 до тех пор, пока не попадете в файл Form1.cs как на рис. 6.3.

 

 

Рис. 6.3. Программа в режиме Debug

 

В этот момент программа остановлена в точке вызова конструктора класса Form1. Продолжайте нажимать F8. При этом попадем в тело конструктора Form1(), а дальше есть два варианта: нажимать F8, то попадать внутрь метода InitializeComponent(), или нажимать Shift-F8, тогда метод InitializeComponent() будет вызван, но его пошаговое выполнение будет обойдено. А дальше? А дальше откроется окно и нажатие кнопки button1 приведет к пошаговому вызову метода button1_Click. И так далее.

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

Кроме этого, с использованием этого режима стала понятна структура программы, написанной в оконном режиме. Действительно, все начинается с вызова метода Main, находящегося в файле Program.cs. Это место в программе называют точкой входа, а для большей убедительности часто говорят о «точке большего взрыва», имея в виду то, что с этого начинается Вселенная, то есть программа.

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

 

 

Текущий объект, this

 

Во многих языках программирования есть понятие перегрузки подпрограмм или методов. В C# оно выглядит следующим образом: в составе класса могут быть методы с одинаковыми именами, но с разными списками параметров. Какой из методов следует вызвать в этом случае, компилятор определяет по различиям параметров или, говорят, по контексту. Первым примером такой перегрузки может служить наличие нескольких конструкторов в классе. Типичный пример – это наличие конструктора без параметров и конструктора с параметрами. Часто в конструкторе с параметрами загружаются исходные данные, кроме этого в нем можно проконтролировать корректность их ввода.

Как иллюстрацию этого включим в класс class CLUgli из примера 5.3 конструктор с параметрами, в котором будем заполнять поля с длинами сторон треугольника только в том случае, если эти данные корректны, то есть сумма длин двух сторон больше третьей. Кроме этого заведем поле типа bool, в котором будем хранить результат этой проверки.

Исходный текст данного класса приведен в примере 6.2. На что здесь надо обратить внимание? Конструкторов пришлось делать два: один с параметрами, а еще один без параметров. Почему? В C# сделали так: если конструктора в классе нет, то вызывается конструктор по умолчанию, но если хотя бы один конструктор есть, то никаких умолчаний, все нужно описывать в явном виде. И еще, здесь используется служебное слово this, за которым скрывается текущий объект, то есть тот объект, с которым работает данный метод в данный момент времени. Для чего это сделано? В этом примере для того, чтобы не путать параметры метода a,b,c и поля объекта a,b,c.

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

Пример 6.2

class CLUgli

   {

       public double a, b, c;

       public double ua, ub, uc;

       public bool ok = true;// для проверки корректности

           

       public CLUgli(double a, double b, double c)

       //конструктор с параметрами

       {

           if (a + b > c && a + c > b && b + c > a)

           {

               this.a = a;

               this.b = b;

               this.c = c;

           }

           else

               this.ok = false;

       }

       public CLUgli() //конструктор без параметров

       {

       }

       public double ugol(double a, double b, double c)

       {

           double aa;

           aa = (b * b + c * c - a * a) / (2 * b * c);

           aa = Math.Acos(aa);

           aa = Rad_v_Drad(aa); //новое

           return aa;

       }

 

       private double Rad_v_Drad(double x) //новое

       {

             return 180 * x / Math.PI;

       }

}

Для демонстрации работы с таким конструктором заведем на форме еще одну кнопку. Читать данные и скидывать результаты будем в те же редакторы, но если данные некорректны, будем сообщать об этом. Текст обработчика события Click кнопки button3 приведен в примере 6.3.  Остальной текст файла Form1.cs не изменится.

Пример 6.3

   private void button3_Click(object sender, EventArgs e)

   {

       double a = Convert.ToDouble(textBox1.Text);

       double b = Convert.ToDouble(textBox2.Text);

       double c = Convert.ToDouble(textBox3.Text);

       CLUgli ugli2 = new CLUgli(a,b,c);

       if (ugli2.ok)

       {

          ugli2.ua = ugli2.ugol(ugli2.a, ugli2.b, ugli2.c);

            ugli2.ub = ugli2.ugol(ugli2.b, ugli2.a, ugli2.c);

            ugli2.uc = ugli2.ugol(ugli2.c, ugli2.a, ugli2.b);

           textBox4.Text = Convert.ToString(ugli2.ua);

           textBox5.Text = Convert.ToString(ugli2.ub);

           textBox6.Text = Convert.ToString(ugli2.uc);

       }

       else

       {

           string s = "Данные не корректны";

           textBox4.Text = s;

           textBox5.Text = s;

           textBox 6. Text = s ;

       }

   }

}

 

Запустите приложение на выполнение. Если данные корректные, то результаты одинаковые при работе с обоими конструкторами, но если данные некорректные, то результаты отличаются. Это иллюстрируется на рис. 6.4, 6.5, 6.6.

 

 

Рис. 6.4. Результаты работы программы из примера 6.3

 

 

Рис. 6.5. Результаты работы программы из примера 6.3

 

 

Рис. 6.6. Результаты работы программы из примера 6.3

 

 

Закрытые поля, свойства

 

Ну и последнее из обещанного ранее.

До этого писали классы с открытыми полями, а методы писали как открытыми (public), так и закрытыми (private). Оказывается, с открытыми полями никто не работает. Это плохое программирование. Поля должны быть закрытыми. Этого требуют условия безопасности приложения. Получается, что доступ к полям возможен только через методы класса, и для доступа к данным используется конструкция языка, которая называется свойством. Свойство состоит из двух методов со стандартными именами get и set. Свойство можно понимать как интеллектуальные поля, поддерживающие инкапсуляцию класса. С точки зрения практического применения свойство доступно так же, как открытое поле в классе. Единственным ограничением является то, что свойство не может передаваться в метод как параметр. Можно рассуждать так: между пользователем (вызовом данных) и данными ставят посредника, который называется свойством. Этот посредник может быть наделен некоторыми контрольными и демпферными особенностями.

Как это делается практически.

 Есть закрытое поле

private double a;

Объявляют свойство

public double A

   {

       get { return a; }

       set { a = value; }

   }

Свойство типа double с именем A объявлено открытым. В C# принято поля именовать с маленькой буквы, а соответствующие им свойства с большой. За именем в фигурных скобках пишутся аксессоры ( accessor ) – get и set. Они указывают доступ свойства к полю на чтение ( get ) и запись    ( set ). После имени аксессора в фигурных скобках указываются действия свойства.

В примере в аксессоре get записано – возвратить значение поля a , в аксессоре setполю a присвоено значение виртуальной переменой value. Почему виртуальной? Потому что она нигде не объявлена, но всегда существует внутри set и имеет тот же тип и значение, что и свойство. В итоге, в нашем случае свойство это просто обертка для поля. Да, но она соответствует принципам безопасности.

В сквозном примере про треугольник можно написать так:

public double A

   {

       get{ return a;}

       set{// можно контролировать установку

           if (value > 0)

               a = value;

           else

               ok = false;

          }

   }

Здесь в свойстве выставлен контроль на запись.

А можно так:

public double Ua

   {

       get { return ua; }

   }

Здесь свойство Ua доступно только для чтения, потому что аксессора set нет.

Если много полей, которые не нуждаются в защите, и им нужно быстро создать всего лишь обертки, то можно сделать это быстрее:

public bool Ok { get; set; } Тогда Ok будет и свойством для внешних источников, и полем для внутреннего использования.

В итоге код предыдущего примера 6.3 будет выглядеть так (пример 6.4).

Пример 6.4.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

Do

оператор тела цикла;

while (логическое выражение);

 

где «логическое выражение» – условие продолжения цикла, то есть продолжение цикла происходит при значении логического выражения истина (true), а выход из цикла происходит при значении фальшь (false). «оператор тела цикла» - один любой оператор языка C#. Если в теле цикла требуется выполнить несколько операторов, они объединяются в программный блок ({..}).

Подобные операторы есть во всех универсальных языках программирования. Они, как правило, называются итеративными и в них заранее неизвестно количество повторений операторов тела цикла. Кроме таких циклов во многих универсальных языках программирования выделяют случай, в котором количество повторений цикла заранее известно. Такие циклы часто называют регулярными. Для их записи используется слово for. И в языке C# есть цикл for. Но он используется и для случая, в котором количество повторений заранее неизвестно. Об операторе for из языка C# позже.

 

Рассмотрим примеры.

 



Примеры использования циклических структур

 

Сначала коротенькие примеры на уровне блок-схем для вычисления суммы чисел от 1 до 10 (пример 7.2а) и произведения чисел от 1 до 5 или факториала числа 5 (5!) (пример 7.2б).

 

 

 

Рис. 7.2. Схемы алгоритмов вычисления суммы и произведения чисел

 

Пример 7.3. Для заданной функции построить ее таблицу на отрезке от a до b с шагом h. По этой таблице найти минимальное значение функции и аргумент, доставляющий этот минимум. Определить количество значений функции больших нуля.

 

Схема алгоритма метода, решающего эту задачу с функцией, оформленной тоже в виде метода, представлена на рис. 7.3.

 

Рис. 7.3. Блок-схема задачи построения таблицы функции

 

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

Нужны поля класса a,b,h для хранения исходных данных, а также поля min , xmin , count для хранения результатов. Два метода: закрытый метод, в котором описывается заданная функция, и открытый метод для построения таблицы и вычисления значений результатов.

Возникает вопрос – куда выводить таблицу в оконном режиме? Здесь есть несколько вариантов. Один из них – использовать визуальный компонент класса L istBox, который представляет собой набор строк. Этот визуальный компонент должен быть размещен на форме. Хорошо, если его размеры по ширине будут такими, чтобы там была видна вся строка таблицы. Если вся таблица не уместится в компонент по высоте, то появится скроллинг, который поможет просмотреть всю таблицу. В классе ListBox есть метод Add (), с помощью которого к концу уже имеющегося набора строк можно добавить новую строку. Понятно, что этот метод будет вызываться внутри цикла, в котором вычисляются значения заданной функции в пределах от a до b с шагом h. Конвертируя эти значения, надо будет формировать строку и каждый раз в цикле добавлять полученную строку к уже имеющемуся набору строк. В этом классе есть еще метод C lear(), который очищает список строк. Компонент класса L is t Box, расположенный на форме, следует передавать как параметр в метод, отвечающий за построение таблицы и другие результаты. Исходный текст файла Form1.cs приведен в примере 7.4.

Пример 7.4

 

using System ;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

Пример 7.6.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

Namespace WindFormExp

{

public partial class Form1 : Form

{

   public Form1()

   {

       InitializeComponent();

   }

   private void button1_Click(object sender, EventArgs e)

   {

       ClMyExp ExpWhile=new ClMyExp();

       ExpWhile.X = double.Parse(textBox1.Text);

       ExpWhile.Eps = double.Parse(textBox2.Text);

       ExpWhile.S = ExpWhile.MyExpWhile();

       textBox3.Text =Convert.ToString(ExpWhile.S) + " " + Convert.ToString(ExpWhile.N);

       double xx = Math.Exp(ExpWhile.X);

       textBox4.Text = Convert.ToString(xx);

   }

   private void button2_Click(object sender, EventArgs e)

   {

       ClMyExp ExpFor = new ClMyExp();

       ExpFor.X = double.Parse(textBox1.Text);

       ExpFor.Eps = double.Parse(textBox2.Text);

       ExpFor.S = ExpFor.MyExpFor();

       textBox5.Text = Convert.ToString(ExpFor.S) + " " + Convert.ToString(ExpFor.N);

       double xx = Math.Exp(ExpFor.X);

       textBox4.Text = Convert.ToString(xx);

   }

   private void button4_Click(object sender, EventArgs e)

   {

       ClMyExp ExpDoWhile = new ClMyExp();

       ExpDoWhile.X = double.Parse(textBox1.Text);

       ExpDoWhile.Eps = double.Parse(textBox2.Text);

       ExpDoWhile.S = ExpDoWhile.MyExpDoWhile();

       textBox6.Text = Convert.ToString(ExpDoWhile.S) + " " + Convert.ToString(ExpDoWhile.N);

       double xx = Math.Exp(ExpDoWhile.X);

       textBox4.Text = Convert.ToString(xx);

   }

}

public class ClMyExp

{

   public double X{get;set;}

   public double Eps{get;set;}

   public double S{get;set;}// приближенное значение суммы

   public int N{get;set;} // количество элементов ряда, вошедших в сумму

   public double MyExpWhile()

   {

       N=0;

       double a=1;

       double S=a;

       while (Math.Abs(a)>Eps)

       {

           a = a * X / (N + 1);

           S = S+a;

           N++;

       }

       return S;

   }

   public double MyExpFor()

   {

       double a = 1;

       double S = a;

       for (int n = 0; Math.Abs(a) > Eps; n++)

       {

               a = a * X / (n + 1);

               S = S + a;

               this.N = n;

      }

       return S;

   }

   public double MyExpDoWhile()

   {

       N = 0;

       double a = 1;

       double S = a;

       do

       {

           a = a * X / (N + 1);

           S = S + a;

           N++;

       }

       while (Math.Abs(a) > Eps);

       return S;

   }

}

}

Обратите внимание, что в методе с циклом for переменная n является локальной переменной, объявленной внутри цикла, и «работает» только внутри цикла. Поэтому внутри цикла используется оператор присваивания this.N = n, причем то, что N есть свойство объекта класса ClMyExp подчеркивается служебным словом this, которое означает то, что N есть свойство текущего объекта (см. Лекцию 6, пример 6.2). В принципе можно было обойтись и без этого this.

Результаты выполнения такого приложения приведены на рис. 7.6.

 

 

Рис. 7.6. Результаты работы программы вычисления суммы ряда с требуемой точностью в трех вариантах

 

Приближенные значения экспоненты во всех вариантах одинаковые и отличаются от значения, полученного с использованием метода Exp() класса Math не больше, чем на требуемую точность, а вот количество вошедших в сумму элементов в цикле for отличается на единицу от других результатов. Проанализируйте, пожалуйста, в чем причина?

Список литературы

 

1. Шилдт Г. С# 4.0: полное руководство / Пер. с англ. – М.:             ООО «И.Д. Вильямс», 2011. – 1056 с.: ил.

2. Биллиг В.А. Основы программирования на С#. – М.: Бином, 2009.        - 488с.: ил.

3. Павловская Т.А. С#. Программирование на языке высокого уровня.    - СПб.: Питер, 2009. – 432 с.

4. Фленов М.Е. Библия С#. Изд. 2-е, перераб. и доп. – СПб.:                       БХВ-Петербург, 2013. - 560 с.: ил. + CD-ROM.

5. Фаронов В.В. Создание приложений с помощью С#. Руководство программиста. – М.: Эксмо, 2008. – 576 с.: ил. (Мастер-класс).

6. Климов А.П. С#. Советы программистам. – СПб.: БХВ-Петербург, 2012. – 544 с.: ил. + CD-ROM.

7. Агуров П.В. С#. Сборник рецептов. – СПб.: БХВ-Петербург, 2007.      – 432 с.: ил.


 


Содержание

 

Лекция 1. Начала основ…………………………………………………….   3

1.1. Введение…………………………………………………………….   3

1.2. Представление данных в языке C# …………………………….   7

1.3. Оператор присваивания и операции…………………………… 11

1.4. Преобразование данных………………………………………….. 13

1.5. Ввод  и вывод данных в консольном режиме.   Последовательная программа……………………………………….. 13

Лекция 2. Методы класса………………………………………………….. 18

Лекция 3. Разветвления. Условный оператор, оператор выбора……. 27

Лекция 4. Введение в ООП………………………………………………… 36

Лекция 5. Windows-приложения………………………………………….. 43

5.1. Программирование с управлением по событиям и ООП…… 44

5.2. Пример решения задачи про углы в Windows -приложении…46

Лекция 6. Windows-приложение, режим пошаговой отладки.

    Продолжение ООП…………………………………………………… 54

6.1. Режим пошагового выполнения……………………………….. 54

Андрей Георгиевич Мацкевич

 

Лекции

 

По курсу


ИНФОРМАЦИОННЫЕ ТЕХНОЛОГИИ            С ИЗЛОЖЕНИЕМ ОСНОВ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С#


Часть первая

Учебное пособие

 

 

Подписано в печать 30.11.2015г. Формат 60х90 1/16.

Объём 5,1 усл.п.л. Тираж 200 экз. Изд. № 53. Заказ 20.

ООО «Брис-М». Москва, ул. Авиамоторная, д. 8а.

А.Г. Мацкевич

Лекции

 

По курсу


ИНФОРМАЦИОННЫЕ ТЕХНОЛОГИИ            С ИЗЛОЖЕНИЕМ ОСНОВ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С#


Часть первая

Учебное пособие

 

Москва 2016


ФЕДЕРАЛЬНОЕ АГЕНТСТВО СВЯЗИ                                                  Ордена Трудового Красного Знамени федеральное государственное                                                         бюджетное образовательное учреждение высшего образования

Московский технический университет связи и информатики


А.Г. Мацкевич

 

                                                                                         «Рекомендовано УМО по образованию в области

                                                                                  Инфокоммуникационных технологий и систем связи

                                                                                  в качестве учебного пособия для студентов высших

                                                                                  учебных заведений, обучающихся по направлению

                                                                                  подготовки 11.03.02 – Инфокоммуникационные тех-

                                                                                  нологии и системы связи (уровень высшего образова-                                                                                                                                                               

                                                                                  ния – бакалавриат)».                       

                                                                                         Протокол № 85 от 01.09.2015 г.

Лекции

 

По курсу


ИНФОРМАЦИОННЫЕ ТЕХНОЛОГИИ            С ИЗЛОЖЕНИЕМ ОСНОВ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С#


Часть первая

Учебное пособие

 

Москва 2016

УДК 681.3.07

   Мацкевич А.Г.  Лекции  по  курсу: Информационные  технологии

с изложением основ программирования на языке С#: Учебное пособие /

МТУСИ. – М., 2016. – 82 с.

 

   Данная работа является первой частью учебного пособия, написанного по лекциям, прочитанным автором в течение двух лет, и состоит из семи лекций, что соответствует материалу, изложенному в первом семестре. Задача пособия – преподать основы программирования на С# студентам, не получившим компетенций программирования в курсе «Информатика» средней школы и не имеющим опыта в написании программ на каком-либо языке программирования.

 Ил. 34, табл. 3 список лит. 7 назв.

 

 

Рецензенты: Е.Б.Козеренко, к.ф.н., зав. лаб. (ФИЦ ИУ РАН)

                 М.Я.Клепцов, д.т.н., профессор (МГУПС)

 

                                                       © Московский технический университет                                           связи и информатики, 2016

                            

                                    Какие-либо знания из курса «Информатика»

                                      средней школы не предполагаются.


Лекция 1. Начала основ

 

Введение

Компания Microsoft (MS) разработала платформу .NET Framework, в которую входит библиотечный набор классов и общеязыковая среда выполнения (Common Language Runtime – CLR). Программный код для этой среды можно писать на различных языках программирования, однако самым популярным из них является язык C#, который был создан именно для этой платформы сотрудником MS Андерсом Хейлсбергом.

В этом учебном пособии в виде лекций кратко изложены основы языка высокого уровня C#, причем не предполагается, что читатели имеют хоть какой-то опыт программирования. Вы уже можете возразить, что в двух первых предложениях используются неизвестные понятия: платформа .NET, языки программирования, классы. Пока не обращайте на это внимания.

Итак, язык программирования C#. Это один из языков программирования платформы .NET, на котором можно написать программу для решения практически любой задачи. Программа в среде .NET называется приложением. Начнем с описания структуры приложения на C#. Это позволит посмотреть на приложение «сверху», то есть охватить всю «картину мира C#». Конечно, в дальнейшем освоение этого мира начнется с самых азов.

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

Кажется, что такая «матрешка» уже встречалась в какой-то русской сказке: помните, смерть Кощея Бессмертного лежит в сундуке, который висит на дубе. В сундуке сидит утка. В утке есть яйцо, а в нем игла. Если сломать эту иглу, то Кащей помрет. Получается игла в яйце, яйцо в утке, утка в сундуке, сундук на дубе. Так и в С#: последовательность операторов в методе Main(). Метод Main() в одном из классов. Класс в одном из пространств имен. Приложение состоит из нескольких пространств имен.

Можно провести еще одну параллель – организация файловой системы. Если посмотреть сверху, то можно сказать, что файловая система состоит из папок. Каждая папка состоит из других папок и файлов. В файлах хранится информация. Но это лирика.

Изучение C# начнем с того, что будем записывать необходимую нам последовательность операторов в единственный метод Main() класса. Что такое оператор? Можно считать, что это инструкция, по которой выполняются некоторые известные стандартные действия. Эти действия будут выполнены во время работы приложения после его запуска на выполнение. Класс, в который входит этот метод, а тем более пространство имен, пока трогать не будем.

Для эффективной работы по созданию программных приложений на платформе .NET разработана  интегрированная среда разработки приложений Visual  Studio .NET (VS). Встает вопрос – как работать с приложением в среде VS ?

 Для этого надо найти на рабочем столе компьютера иконку Microsoft Visual Studio и запустить среду. Здесь все примеры сделаны в среде Visual Studio 2010. Есть другие версии VS: 2008, 2012, 2014. На первоначальном уровне освоения это практически не имеет значения. Откроется главное окно, которое состоит из нескольких панелей (рис. 1.1).

 

 

 

Рис. 1.1. Фрагмент главного окна среды VS

 

Однако пришло время определиться с терминологией для того, чтобы в дальнейшем оперировать одинаковыми понятиями.

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

Проект является также единицей компиляции. Результатом компиляции проекта является сборка. Каждый проект содержит всю информацию, необходимую для построения сборки.

В зависимости от выбранного типа проект может быть выполняемым или невыполняемым. К выполняемым проектам относятся, например, проекты типа Console или типа Windows. В результате компиляции такого проекта создается PE-файл (Portable Executablefile) –  выполняемый переносимый файл с расширением . exe. Такой PE-файл может выполняться только на компьютерах, где установлен Framework.Net.

К невыполняемым проектам относятся, например, проекты типа        DLL – динамически связываемые библиотеки, т.е. проекты, предназначенные для включения (связывания) в другой проект. В результате компиляции такого проекта в сборку войдет файл с уточнением . dll. Такие проекты (сборки) непосредственно не могут быть выполнены на компьютере. Они присоединяются к выполняемым сборкам, откуда и вызываются методы классов, размещенных в невыполняемом проекте (DLL). 

Каждый проект, создаваемый в VS, помещается в оболочку, называемую Решением – Solution. Решение может содержать несколько проектов, как правило, связанных общей темой. Решения позволяют придать структуру множеству проектов, что особенно полезно, когда проектов много.

 Теперь в окне «Начальная страница» находим и смело выбираем пункт «Создать проект». Откроется окно «Создать проект», как показано                   на рис. 1.2.

 

 

Рис. 1.2а. Фрагмент окна «Создать проект» среды VS

 

Попасть в это окно можно и по-другому - используя пункты главного меню (по-русски)(File) -> (по-русски)(New) ->(по-русски)(Project).

В окне «Установленные шаблоны» (слева) выбираем язык «Visual C#»,  в среднем окне выбираем «Консольное приложение». Внизу есть еще один или два однострочных редактора, в которых надо (или можно) выбрать Имя проекта (Name) и расположение Папки проекта (Location). Обратите внимание на расположение папки проекта. С использованием кнопки Обзор(View) следует расположить эту папку в удобном месте файловой системы компьютера.

 

 

Рис. 1.2б. Другой фрагмент окна «Создать проект» среды VS

 

 

И смело нажимайте Ok.

 

Появится окно текстового редактора исходного программного кода. В одной из вкладок окна редактора откроется файл с именем Program. cs   (рис. 1.3).

 

 

Рис. 1.3. Страница текстового редактора с файлом Program.cs

 

Вот и добрались до метода Main(). При этом не написали ни одной строчки программного кода.

Строка static void Main(string[] args) есть заголовок метода Main().

 

Можно начинать работать.

Но не надо спешить. Посмотрим на текст файла Program.cs как на заготовку программного кода, которую предлагает использовать среда разработки VS.

В строках со служебными словами using к программе подключаются пространства имен, необходимые для работы нашей программы. Эти пространства имен содержат много-много классов. В этих классах реализованы задачи, которые могут быть использованы в программе, написанной для консольного режима.

Следующая строка начинается с ключевого слова na m espase, за которым объявляется имя нового пространства имен ConsoleApplication1. Это пространство имен, которое будет использоваться в нашем разрабатываемом проекте. Среда VS предлагает использовать имя ConsoleApplication1. Это имя можно поменять.

Далее, в этом пространстве имен организован всего один класс с именем Program:

 class Program.

 Если очень хочется, то и это имя можно поменять. А уже в этом классе есть метод с именем Main(). Вот это имя менять не надо ни в коем случае. Помните, что язык C# – это набор пространств имен, которые состоят из классов и так далее, а метод Main() – это тот метод, с которого начинает выполняться программа. И еще, обратите внимание на фигурные скобки – это границы программных блоков. В предлагаемой программе программные блоки вложены друг в друга. Это вещь серьезная, с ними надо быть внимательными. Ведь все, что объявлено (описано) в программном блоке, можно использовать, или по-другому будет видно только в нем. Что значит «объявлено»? Об этом речь впереди.

Эту заготовку уже можно откомпилировать и запустить на выполнение. Для этого достаточно нажать клавишу F5. На какое-то мгновение появится черное окно и всё на этом закончится. Программа ничего не сделает,  кроме того, что откроет и закроет консольное окно, но формально или с точки зрения компилятора она правильная, написана без ошибок, поэтому она будет успешно откомпилирована и запущена на выполнение.

Итак, уже можно писать код «внутри» метода Main(). А потом? Потом необходимо откомпилировать программу, то есть создать код на промежуточном языке, который затем, при успешной компиляции, можно будет запустить на выполнение. Но сначала надо узнать, что же можно написать в методе Main().

 

 

Представление данных в языке C#

 

Начнем с представления данных в программе. Любая программа работает с некоторыми данными. Эти данные во время выполнения программы размещаются в оперативной памяти компьютера (ОП). ОП представляет собой набор байтов, каждый из которых имеет свой адрес, то есть все байты ОП пронумерованы. Для того чтобы загрузить и в дальнейшем использовать данные, необходимо знать, где они размещены (знать адрес), сколько занимают места (размер памяти), как эта память организована внутри себя и какие операции разрешены для работы с этими данными. Все это регулируется введением такого понятия, как тип данных. Есть разные типы данных, но на первых порах будут использоваться лишь некоторые из них, а точнее такие, который дадут возможность работать с числами и строками. Пока этого хватит.

Начнем с представления чисел. Для представления чисел используются типы данных, которые принципиально разделяются на два вида. Одни предназначены для хранения целых значений (без дробной части), другие – для хранения действительных чисел (с дробной частью). Почему так? Дело в том, что все данные в ОП хранятся в двоичном виде, то есть как набор нулей и единиц. Так вот, для того чтобы целое число представить в двоичном виде, необходимо конечное число двоичных разрядов (битов). У действительных чисел кроме целой части числа есть еще и дробная часть, и для ее точного представления в двоичном виде в теории нужно бесконечное число разрядов. Здесь можно привести аналогичный пример представления числа в десятичной системе: 1/3 = 0.333333… Итак, получается, что целые числа представлены в ОП точно, а для хранения действительных чисел используется конечное число двоичных разрядов, что приводит к тому, что все действительные числа представлены в ОП приближенно, то есть с погрешностью, или говорят, что действительные числа представлены в ОП с конечной точностью.

Чаще всего для работы с целыми числами будет использоваться тип int. Для данных такого типа выделяется (захватывается) четыре байта ОП, в них можно хранить значения в пределах от -216 до 216-1 или в десятичной системе от -2 147 483 646 до… 2 147 483 647 .

Формат хранения действительных чисел требует некоторых пояснений. С чем он связан? Действительные числа могут быть маленькими или очень маленькими, а могут быть большими или очень большими. Как сделать так, чтобы точность представления у них была одинаковой? Ответ такой – хранить числа в форме с плавающей точкой, то есть в представлении следующего формата: ZMP, где Z – знак числа, M – мантисса числа,            P – десятичный порядок. Ну, например: -123.456 = -1.23456е+2. Здесь знак  Z  – это «-», мантисса M это 1.23456 (в целой части одна значащая цифра), десятичный порядок P это +2. Получается, что точность числа зависит от количества значащих цифр в мантиссе числа, а диапазон чисел, которые можно хранить, зависит от десятичного порядка. Так вот, для хранения действительных типов чаще всего будет использоваться тип double. Для данных такого типа используется восемь байт ОП, в мантисе числа правильными можно считать 15-16 десятичных цифр, а диапазон значений лежит в пределах от 5.0e-324 до   1.7e308 . Типы int и double относятся к базовым типам. Другие базовые типы данных представлены в табл. 1.1.

 

 Таблица 1.1

 

Тип данных Размер Диапазон значений

Целочисленные типы данных

sbyte 1 байт, со знаком -128 … 127
byte 1байт, без знака 0 … 255
char 2 байта, символ Unicode 0000 … FFFF
short 2 байта, со знаком -32768 … 32767
ushort 2 байта, без знака 0 … 65535
int 4 байта, со знаком -2 147 483 646 … 2 147 483 647
uint 4 байта, без знака 0 … 4 294 967 295
long 8 байтов, со знаком -9 223 372 036 854 775 808 … 9 223 372 036 854 775 807
ulong 8 байтов, без знака  
decimal 28, 29 десятичных знаков  
float 7 десятичных знаков  
double 15-16 десятичных знаков  
bool 2 байта  
string 4 байта, с пл. точкой  

 

В программировании есть такое понятие как переменная. Переменные применяются во время выполнения программы для временного хранения данных в ОП компьютера. Как использовать переменную в программе? У каждой переменной должно быть свое имя. Кроме этого, надо знать, какие значения может принимать переменная. Обо всём этом говорится (записывается) в программном коде при объявлении переменной.

Для объявления (определения) переменных компилятору нужно следующее:

· имя переменной – по имени компилятор осуществляет связь переменной в программе с оперативной памятью компьютера;

· тип переменной – тип позволяет компилятору определить, какого вида информация хранится в переменной.

 

При создании имен переменных следует руководствоваться следующими правилами:

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

· выбирать имена переменных не длиннее 33 символов, чтобы их было легко читать (хотя имена переменных могут быть практически любой длины);

· делать имена своих переменных описательными, комбинируя, когда это имеет смысл, одно или несколько слов;

· использовать комбинации заглавных и строчных символов и цифр, то есть первую букву каждого слова имени переменной делать заглавной, например, DlinaStroki;

· не использовать в качестве имен переменных ключевые (служебные) слова C#, имена объектов или свойств. Не пугайтесь: если это сделать, то при попытке запуска программы получите сообщение об ошибке;

·  в C# различаются большие и маленькие буквы. Это касается написания всех имен языка.

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

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

 

Пример 1.1. Объявить переменные на C#.

int i,j,k;

double x,y,_FirstX, _firstX;  

      

Обратите внимание на имена _FirstX, _firstX. Это разные имена, так как компилятор в С# различают регистры (маленькие и большие буквы). Однако, имена, отличающиеся только регистрами символов, лучше не использовать. Ошибок будет меньше.

Что же произойдет, если такие строки встретятся в исходном коде программы на C#? Во время выполнения (RunTime) программа обратится к ОП и выделит память под каждую из объявленных переменных: под переменные i, j, k будет отведено по четыре байта, под остальные по восемь байтов для каждой. Для программы всем объявленным переменным будут поставлены в соответствие адреса ОП. Такой процесс называется инициализацией имен. Так происходит с всеми так называемыми значимыми типами данных. Что это означает? Это означает, что объявления имени достаточно для ее инициализации.

Есть и другие типы, которые называются ссылочными. Их инициализация не происходит автоматически при объявлении. Это надо делать «вручную», используя специальные средства. Об этом позже в лекции 4. Все типы из табл. 1.1 являются значимыми типами.

Вернемся к инициализации списком. Память под переменные отведена? Ответ: отведена. Вопрос: какие данные там находятся? В языке C# компилятор следит за этим: если память отведена, но переменной не присвоено какое-либо значение, то компилятор предупредит об этом.

 

Пример 1.2. Объявить переменные на C#.

int i1=1; int j1=1000; int k1=-30;

double x1=2.4; double y1=-2.22E+3;  

          

При таком объявлении будет отведена память под каждую переменную, и туда (в эту память) будет «положено» соответствующее значение. Обратите внимание, что здесь тип придется писать перед каждым именем переменной, то есть, при таком описании нельзя объявлять переменные списком. Здесь возникает вопрос, а что будет если написать int ii=1.5, то есть объявить, что переменная ii целого типа, а далее попытаться присвоить ей действительное значение (1.5). Компилятор «заметит» это и выдаст сообщение об ошибке.

Дата: 2019-11-01, просмотров: 293.