Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ
ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ
«НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ ТЕХНОЛОГИЧЕСКИЙ УНИВЕРСИТЕТ «МИСиС»
УДК 004.45
К93
Рецензент
д-р техн. наук, проф. В.А. Поляков (РГУ нефти и газа им. И.М. Губкина)
канд. техн. наук, проф. В.Б. Глаголев (МЭИ)
Куренкова, Т.В.
Основы алгоритмизации и объектно-ориентированного
К93 программирования : учеб. пособие / Т.В. Куренкова, Г.И. Све-
тозарова. М. : Изд. Дом МИСиС, 2011. 197 с.
ISBN 978-5-87623-466-7
Рассматриваются вопросы программирования на базе платформы Microsoft .NET Framework. Дается описание современного языка объектно-ориентированного программирования C# в объеме, необходимом для иллюстрации основных понятий объектно-ориентированного программирования. Рассматриваются базовые структуры алгоритмов, типовые алгоритмы работы с массивами, основные приемы программирования, базирующиеся на объектном подходе.
Предназначено для изучения основ алгоритмизации и начального знакомства с объектно-ориентированным программированием на языке C# студентами 1-го семестра, всех направлений, а также для самостоятельного изучения.
УДК 004.45
ISBN 978-5-87623-466-7 |
|
Куренкова Т.В., |
Оглавление
[1] Предисловие [2] Введение [3] 1. Основы языка C# [3.1] 1.1. Переменные. Типы данных [3.1.1] Значения и объектные ссылки [3.2] 1.2. Литералы [3.3] 1.3. Операторы объявления. Инициализация переменных [3.4] 1.4. Математические операторы. Выражения [3.4.0.1] Приведение и преобразование типов в С# [3.5] 1.5. Логические операторы. Выражения [3.6] 1.6. Перечисление [3.7] 1.7. Основные операторы [3.7.1] 1.7.1. Условный оператор if [3.7.2] 1.7.2. Оператор цикла for [3.7.3] 1.7.3. Оператор while [3.7.4] 1.7.4. Операторы цикла do-while [3.7.5] 1.7.5. Оператор выбора switch [3.7.6] 1.7.6. Оператор безусловного перехода goto [3.7.7] 1.7.7. Составной оператор (блок) [3.8] 1.8. Ввод-вывод [3.8.1] 1.8.1. Ввод данных [3.8.2] 1.8.2. Вывод данных [4] 2. Реализация простейших алгоритмов на языке C# [4.1] 2.1. Организация циклов [4.1.1] 2.1.1. Циклы по счетчику [4.1.2] 2.1.2. Циклы по условию [4.1.3] 2.1.3. Вложенные циклы
[4.2] 2.2. Организация разветвлений.
[4.3] 2.3. Составление программ для обработки [5] 3. Массивы. Типовые алгоритмы обработки массивов [5.1] 3.1. Одномерные массивы и алгоритмы их обработки [5.2] 3.2. Работа с массивами как с объектами [5.3] 3.3. Работа с матрицами [5.3.0.1] Вопросы для самопроверки [5.3.0.2] Задание для самостоятельного выполнения [6] 4. Структуры и классы [6.1] 4.1. Использование структур [6.1.0.1] Вопросы для самопроверки [6.1.0.2] Задание для самостоятельного выполнения
[6.2] 4.2. Использование конструктора экземпляра [6.2.0.1] Вопросы для самопроверки [6.2.0.2] Задания для самостоятельного выполнения [6.3] 4.3. Использование классов [6.3.0.1] Вопросы для самопроверки [6.3.0.2] Задания для самостоятельного выполнения [7] 5. Методы
[7.1] 5.1. Общие положения. [7.2] 5.2. Использование массивов в качестве параметров [7.3] 5.3. Использование делегата для передачи метода в качестве параметра в другой метод [7.3.0.1] Вопросы для самопроверки [7.3.0.2] Задание для самостоятельного выполнения [8] 6. Работа с текстовыми строками [8.0.0.1] Вопросы для самопроверки [8.0.0.2] Задание для самостоятельного выполнения [9] 7. Файлы данных (Пространство имен System.IO). Файлы и потоки [9.0.0.1] Вопросы для самопроверки [9.0.0.2] Задания для самостоятельного выполнения
[10] 8. Разработка приложений [10.0.0.1] Работа с элементом управления TextBox [10.0.0.2] Работа с элементом управления Button [10.0.0.3] Работа с элементом управления RichTextBox [10.0.0.4] Создание объекта Graphics пространства имен System.Drawing для рисования [10.0.0.5] Вопросы для самопроверки [10.0.0.6] Задания для самостоятельного выполнения [11] 9. Среда разработки [11.1] 9.1. Создание нового проекта [11.2] 9.2. Сохранение проекта [11.3] 9.3. Открытие существующего проекта [11.4] 9.4. Использование интегрированной среды разработки Visual C# [11.4.0.1] Обозреватель решений
[11.4.0.2] Компилятор C# для преобразования исходного кода C# [11.4.0.3] Ошибки построения [11.4.0.4] Отладчик Visual Studio для тестирования программы [11.4.0.5] Обозреватель объектов для просмотра методов и классов, доступных в библиотеках [11.4.0.6] Значки обозревателя объектов [12] Библиографический список [13] ПРИЛОЖЕНИЯ [13.1] Приложение 1 [13.2] Платформа Microsoft .NET Framework
[13.3] [13.4] Таблицы встроенных типов
[13.5] [13.6] Региональные стандарты |
Современное развитие общества предполагает широкое использование компьютерных технологий в различных сферах деятельности. Умение использовать разнообразные возможности, предоставляемые компьютером, обеспечивают конкурентоспособность специалиста.
Настоящее пособие предназначено для приобретения компетенций в области алгоритмизации задач и разработки приложений на базе объектно-ориентированного подхода с использованием современного языка C#.
C# объектно-ориентированный язык, предназначенный для разработки приложений, выполняемых в среде .NET Framework. Приобретаемые при изучении данного пособия навыки являются необходимым этапом для дальнейшего овладения возможностями, предоставляемыми платформой Microsoft .NET Framework.
Пособие содержит большое количество примеров, облегчающих восприятие и освоение материала, и заданий для самостоятельного выполнения различной степени сложности, что позволит оценивать уровень освоения указанных умений и навыков как минимальный, средний или повышенный.
Язык C# (произносится Си шарп) современный язык объектно-ориентированного программирования, предназначенный для разработки программ и систем программ на базе платформы Microsoft .NET Framework. Этот язык ориентирован как на создание обычных автономных приложений Microsoft Windows, допускающих в том числе взаимодействие со стандартными приложениями Windows, так и на разработку Web-приложений. Важнейшим компонентом платформы .NET Framework является обширная библиотека классов, существенно облегчающая разработку программ. В Приложении 1 приводятся подробные сведения о составе платформы .NET Framework и предоставляемых ею возможностях.
Объектно-ориентированное программирование базируется на понятиях: объект, класс, структура, поля, методы, свойства, события, которые будут вводиться, использоваться и углубляться при решении все более сложных задач.
По сравнению с традиционными алгоритмическими языками объектно-ориентированное программирование существенно расширяет возможности и облегчает разработку сложных программных систем благодаря наличию упомянутой выше библиотеке классов, при использовании которой отпадает необходимость в написании кодов многих типовых алгоритмов.
При решении научно-технических задач алгоритмическая составляющая является весьма существенной. В настоящем пособии вопросам алгоритмизации и изучению базовых приемов программирования уделяется значительное внимание. Начальные сведения по алгоритмизации (понятие алгоритма, свойства алгоритма, типовые структуры алгоритма) см., например, в учебном пособии «Информатика. Информационные технологии». Авторы: Светозарова Г.И., Андреева О.В., Крынецкая Г.С. и др. Изд. Дом МИСиС, 2009. Параллельно рассматриваются и используются и объектные свойства языка и на наглядных примерах демонстрируются предоставляемые ими новые возможности.
Для разработки программ на языке C# на компьютере должно быть установлено средство разработки, в котором содержатся необходимые инструменты разработки, такие как, редактор кода, компиляторы и др. (прил. 1). В данном пособии для выполнения примеров используется средство разработки Microsoft Visual C# 2008 Express Edition (Microsoft Visual C# 2008, экспресс-выпуск).
Для выполнения программ, написанных на языке C#, на компьютере должен быть установлен пакет .NET Framework (см. прил. 1).
Прежде чем перейти к систематическому изложению средств языка, рассмотрим простейший пример программы, написанной на языке C#.
Пример. Переменной r присвоить значение 3 и вывести значение r на экран (консоль, Console).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int r = 3;
Console.WriteLine(r);
Console.ReadKey();
}
}
}
Создание и реализация даже самой простой программы (кода) осуществляется в рамках проекта (Project). Поэтому вначале необходимо создать проект. Для создания нового проекта необходимо войти в Visual C#, экспресс-выпуск и в меню «Файл» выбрать «Создать проект». Появится диалоговое окно «Создать проект».
В рамках проекта создадим консольное приложение. Консолью называется окно операционной системы, в котором отображаются вводимые с клавиатуры данные и появляются выводимые результаты. Консоль это средство взаимодействия пользователя с консольным приложением, содержащим код (программу). Для создания консольного приложения необходимо в появившемся окне «Сздать проект» выбрать шаблон «Консольное приложение» и нажать OK.
Приложению автоматически присваивается имя ConsoleApplication1 (при желании это имя можно заменить на любое другое). Появляется окно «Редактор кода», в котором отобразится вкладка Program.cs с панелью кода областью для ввода или редактирования кода. В этой области автоматически создается шаблон программы код класса Program.
Замечание. Чтение последнего абзаца нужно совмещать с выполнением описанных действий на компьютере, что позволит снять возникающие при чтении вопросы.
Во внутренних фигурных скобках остается вписать операторы, которые должны быть выполнены этой программой. Каждый оператор заканчивается точкой с запятой:
int r = 3;
Console.WriteLine(r);
Console.ReadKey();
При наборе одной или нескольких первых букв выпадает список, из которого предлагается выбрать возможный в данном контексте нужный элемент кода, например, имя переменной. Далее нужно щелкнуть по нему два раза или нажать клавишу Enter элемент кода появится в заданном месте программы.
Первый оператор (присваивания) задает тип (в данном примере int целый) и значение переменной r. Второй выводит на экран (консоль) значение r. В соответствии с этим опеатором на экране появляется окно и в него выводится значение (в данном примере 3). Третий оператор помещен здесь, чтобы задержать результат на экране, пока не будет нажата какая-либо клавиша. В противном случае результат очень быстро исчезнет с экрана. Убедитесь в этом сами. Выполнить программу можно, нажав клавишу F5 (или Ctrl/F5, см. гл. 9).
В написанном консольном приложении из предлагаемого шаблона кода необходимыми являются только следующие строки:
using System;
class Program
static void Main(string[] args)
Первая строка директива using - разрешает использование типов, которые содержатся в пространстве имен System библиотеки классов .NET Framework. Пространство имен System содержит фундаментальные и базовые классы. В данном примере директива using обеспечивает доступ к структуре int и классу Console.
Вторая строка объявляет класс с именем Program с помощью ключевого слова class (классу может быть задано любое имя). Содержимое класса метод (методы) класса задается в фигурных скобках.
В последней строке объявляется метод Main. Стандартное содержимое скобок может быть опущено, т.е. эта строка может иметь вид
static void Main()
Таким образом, программа для нашего примера может выглядеть так:
using System;
class Program
{
static void Main()
{
int r = 3;//инициализация переменной
Console.WriteLine(r);
Console.ReadKey();
}
}
Замечание. Текст, расположенный в строке после //, является комментарием и при выполнении программы игнорируется.
Здесь в классе Program определен один метод static void Main (смысл терминов static и void, см. гл. 5). Метод Main должен быть обязательно в любой программе. Именно методу Main передается управление при запуске приложения.
Метод это оформленная специальным образом последовательность инструкций (операторов) для выполнения каких-либо действий над объектами (переменными). (Более строгое и развернутое определение дается см. гл. 5.) Выполнение этих инструкций начинается после вызова метода из другого метода указанием его имени и необходимых аргументов (последнее не относится к методу Main). Метод является некоторым аналогом процедур (функций) в алгоритмических языках.
Метод Main имеет особый статус. Он является точкой входа каждого приложения C# и вызывается при запуске программы. Остальные методы вызываются из метода Main или других методов.
Если метод описан в классе, к которому принадлежит и Main, то для его вызова можно указать только имя и аргументы в круглых скобках или пустые круглые скобки, если аргументов нет. Если метод принадлежит другому классу, то для его вызова нужно указать имя класса (или имя экземпляра класса) и после точки имя метода, далее круглые скобки. В скобках указать аргументы метода, если они есть, или пустые скобки, если аргументов у метода нет. Например, вызов методов Console.WriteLine(r) и Console.ReadKey() класса Console в предыдущем примере.
В библиотеке имеется большое число готовых методов, сгруппированных в классы по своему назначению. Например, класс Math предоставляет константы и статические методы (термин статические поясним позже) для вычисления тригонометрических, логарифмических и других общих математических функций. Так, для вычисления cos(x) необходимо указать класс и после точки метод: Math.сos(x), см. примеры программ в гл. 1.
C# имеет две разновидности типов: типы значений и ссылочные типы. Переменные, основанные на типах значений, содержат непосредственно значения. Переменные ссылочных типов сохраняют ссылки (адреса в специально выделенной памяти) на фактические данные. Таким образом, в переменной может храниться или значение, или объектная ссылка.
Для обозначения переменных используются идентификаторы: сочетание букв, цифр и знака подчеркивания, начинающееся с буквы.
В C# переменные объявляются с определенным типом данных.
Тип данных определяет место для хранения переменной, расположение, в котором будет выделена память для переменной во время выполнения программы, способ представления значения переменной в памяти, множество допустимых значений, а также набор операций, которые можно выполнять с этими данными.
Так как все переменные в C# являются объектами тех или иных классов или структур, то к сведениям, хранимым в типе, также относится базовый тип, которому он наследует, и содержащиеся в нем члены (методы, поля, события и т.д.).
В программах настоящего пособия будем использовать только переменные типа int (принимающие целые значения), double (вещественные значения), string (строка символов), char (отдельный символ) и bool (принимающие значения true (истина) или false (ложь)).
Переменные типа int, double, char, и bool являются экземплярами структур и относятся к типам значений. Переменная типа string является экземпляром класса и относится к ссылочным типам, т.е. переменная содержит адрес ячейки памяти, где размещается строка. К ссылочным типам относятся также массивы (см. гл. 3).
Более полное перечисление элементарных (встроенных) типов, к которым относятся и упомянутые выше типы, и характеристики типов см. в прил. 2.
Значение это двоичное представление данных. Тип данных предоставляет способ представления значения переменной в памяти.
Значения переменных целых типов представляются в памяти как двоичные числа, полученные непосредственно переводом из десятичной системы счисления в двоичную систему счисления (это представление полностью справедливо лишь для положительных чисел). Целые числа в памяти представляются точно, и при выполнении операций с целыми числами никаких ошибок не возникает.
Идея представления в памяти вещественных чисел (конкретные способы могут варьироваться) заключается в следующем. Сначала число приводится к нормализованному виду, когда целая часть числа равна 0, первая цифра после запятой является значащей, а положение запятой в числе определяется значением показателя степени 10. Например, число 0,086 в нормализованной форме имеет вид 0,86101, число 123,45 0.12345103. При этом цифры, расположенные в нормализованной записи после точки, называются мантиссой, а показатель степени 10 это порядок. В памяти отдельно представляется мантисса и отдельно порядком. При этом количество бит, предназначенных для мантиссы, определяет точность представления, а количество бит, предназначенных для порядка, определяет диапазон представляемых чисел. Если количество цифр в двоичном представлении мантиссы превышает количество отведенных под нее разрядов, то последние двоичные цифры теряются, и число оказывается представленным в памяти приближенно. Кроме того, при выполнении арифметических операций ошибки могут накапливаться. Таким образом, в общем случае вещественные числа в памяти представляются приближенно и их сравнение на точное равенство невозможно. Например, равенство 0,2 + 0,2 + 0,2 + 0,2 + 0,2 = 1 может не выполняться, если левая часть его вычисляется на компьютере (гл. пример 2.6). Обычно вещественные переменные используются для обозначения величин, полученных в результате измерений, которые всегда имеют некоторую погрешность, либо в результате вычислений.
Символы всегда представлены 16-разрядными кодами Unicode (Юникод). Подробнее о символьных данных и операциях с ними см. гл. 6.
Логические переменные могут иметь значение true либо false.
Объектная ссылка адрес памяти. При создании объекта память для него выделяется в управляемой куче специальной области памяти, переменная хранит только ссылку на расположение объекта.
Литералы используются в тексте программы для обозначения числовых значений, строк символов или логических констант. Другими словами, литерал представляет собой постоянное значение, у которого нет имени.
Целочисленные литералы состоят из цифр от 0 до 9 со знаком «+», «» или без знака. Например, 25 7 553.
Литералы с плавающей точкой могут быть записаны в виде целой и дробной частей, разделенных точкой (целая часть может отсутствовать, если она равна 0, если дробная часть равна 0; то после точки должен быть записан 0) или в экспоненциальной форме (вместо основания степени 10 используется e или E). Например, 8.1 .23 3.0 5.3 E1 5e4.
Строковые литералы используются для представления текстовых строк. Это строка символов, заключенная в кавычки. Например, «Город Москва», «Результаты вычислений».
С помощью символа \ (обратный слеш) можно указать некоторые специальные и управляющие символы. Последовательность \ и следующего за ним символа называется управляющей последовательностью. Например, наличие в строке последовательности \n означает переход на новую строку, \t горизонтальная табуляция, \\ обратный слеш (\), \" двойная кавычка и т.п.
Символьные литералы используются для представления одиночных символов (заключаются в одинарные кавычки). Например,
char chars = X;
Литералы для представления логических значений: true, false.
Ключевое слово null является литералом, представляющим пустую ссылку, которая не ссылается ни на один объект.
Оператор объявления представляет новую переменную или константу. При объявлении указывается тип и при необходимости переменной может быть присвоено значение. Для константы указание значения обязательно. В языке C# инициализация переменной (т.е. присвоение ей начального значения) является обязательным условием. Начальное значение может быть задано с помощью оператора присваивания при объявлении переменной (см. Пример во Введении), а также в процессе выполнения программы. Если инициализация переменной осуществляется при ее объявлении, то вначале указывается тип данных, затем имя переменной, далее оператор присваивания в виде знака равенства и, наконец, литерал соответствующего типа. Для инициализации одной переменной может быть также использована другая, инициализированная ранее переменная:
int x = 0;
int y = x;
int z;
z = y;
Допустима также запись
int x, y, z;
x = y = z = 1;
Здесь нескольким переменным присваивается одно и то же значение. Присваивание происходит справа налево: сначала z присваивается значение 1, затем y присваивается значение z и далее в x пересылается значение y.
Можно также вначале объявить тип переменной, а затем инициализировать ее отдельным оператором. Например,
int x;
x = 0;
Объявление типа и инициализацию переменной можно выполнить непосредственно при первом использовании переменной в программе, (см. гл. 3, пример 3.4).
В виде констант задаются неизменяемые в программе величины. Например, если объявлена константа g
Const double g = 15.5;
то значение g не может быть изменено в программе.
В общем случае слева от оператора присваивания (знака «=») располагается переменная, справа выражение, значение которого будет присвоено переменной левой части. (Пока речь будет идти только о числовых переменных и выражениях, значениями которых является числовое значение.) Простейшим выражением является простая переменная или литерал.
Выражение может содержать математические операторы сложения +, вычитания , умножения *, деления / и вычисления остатка при целочисленном делении % (операция возведения в степень в языке C# отсутствует) и операнды, над которыми эти операции выполняются. Операндами могут быть литералы, переменные и обращения к методам, результатом которых является одно значение. Если в выражении несколько операторов, то они выполняют действия в соответствии со следующими приоритетами: операторы умножения и деления *, /, % (выполняются по порядку слева направо), операторы сложения и вычитания +, . Если операндом какой-либо операции является обращение к функции (методу), то сначала выполняется это обращение, чтобы получить в качестве операнда числовое значение. В качестве примера вычислим
y = 6 + сos(x) + x x
и выведем результат на экран:
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
double x = 2.3;
double y;
y = 6 + Math.Cos(x) + x * x;
Console.WriteLine(y);
Console.ReadKey();
}
}
}
Порядок вычисления выражения в программе
1. Вычисляется Cos(x) обращением к методу Cos(x)класса Math (обозначим результат через p1).
2. Вычисляется 6 + p1 (обозначим результат этой операции через p2).
3. Вычисляется x x (обозначим результат этой операции через p3).
4. Вычисляется p2 + p3. Результат этой операции помещается в y.
В выражении могут присутствовать операнды разных типов. В приведенном примере первый операнд литерал целого типа. Остальные операнды типа double. Тип результата каждой операции зависит от типа операндов, участвующих в операции. Тип результата последней операции определяет тип результата вычисления выражения.
Определение типа результата операции осуществляется в соответствии с правилами:
1) если оба операнда целые (типа int), результат будет иметь тот же тип (int). В соответствии с этим при делении целого на целое получается целое число. Остаток от деления теряется.
2) если один из операндов вещественный (тип double), то результат будет вещественный (double). Второй операнд при необходимости автоматически преобразуется к типу double.
Замечание. Для часто встречающегося в программах оператора, изменяющего значение переменной типа int на 1 , например,
i=i+1; или i=i1;
можно использовать более короткую запись:
i++; (инкремент) или i; (декремент)
(см. гл. 2, примеры 2.7, 2.13).
Другие возможности сокращенной записи операторов здесь не рассматриваются.
Приведение и преобразование типов в С#
В C# можно выполнять следующие виды преобразований.
Неявные преобразования. Не требуют специального синтаксиса, поскольку преобразование безопасно для типов, и данные не теряются.
Неявное преобразование можно применить для встроенных числовых типов в случае если сохраняемое значение может уместиться в переменной без обрезания или округления до ближайшего.
Пример допустимого преобразования:
int i = 9;
double t = 1.0;
t = i;
Преобразование допустимо, так как переменная типа double (64-разрядное значение с плавающей запятой) может хранить любое значение, которое может хранить переменная типа int (32-разрядное целое число со знаком).
Пример недопустимого преобразования:
int i = 9;
double t = 1.0;
i = t;
Допускается также неявное преобразование от меньшего к большему целому типу (прил. 2).
Явные преобразования (приведения). Для явных преобразований необходим оператор преобразования. Для выполнения приведения необходимо заключить тип, в который производится приведение, в скобки перед преобразуемым значением или переменной.
Пример
int i = 9;
double t = 1.1;
i = (int)t;
При выполнении явного преобразования нужно быть внимательным, так как возможна потеря данных и переполнение.
Преобразования с помощью вспомогательных классов.
Использование статических методов класса Convert пространства имен System. Пример
int xInt = 2;
double xDouble = 5.0;
xInt = Convert.ToInt32(xDouble);
Console.WriteLine("Double: {0}", xInt);
Логические данные имеют тип bool. Например,
bool L1, L2;
К логическим данным могут применяться логические операторы (табл. 1.1). Результатом выполнения логического оператора всегда является логическое значение true или false. Константы и переменные логического типа могут входить в состав логического выражения. Кроме этого, в логическом выражении могут использоваться в качестве операндов отношения (например, x>0 имеет значение true при x положительном и значение false при x отрицательном или равном 0; sqr(a)+sqr(b) <= sqr (c) имеет значение true,если условие удовлетворяется для конкретных значений a, b, c, и false в противоположном случае). Отношение это два арифметических выражения, соединенных знаком операции отношения <, <=, >, >=, = = (равно), != (не равно).
Таблица 1.1
Логические операторы
Название |
Обозначение |
A |
|||
T |
T |
F |
F |
||
B |
|||||
T |
F |
T |
F |
||
Отрицание |
!A |
F |
F |
T |
T |
Логическое «и» |
A && B |
T |
F |
F |
F |
Логическое «или» |
A ׀׀ B |
T |
T |
T |
F |
Примечание: T true, F false.
Например, после выполнении оператора присваивания
L3=(x<=5.5) && (x>=3.5);
L3 будет иметь значение true, если выполняются оба условия, т.е. x принадлежит отрезку [3.5, 5.5].
Заключать отношения в скобки необязательно, так как они имеют более высокий приоритет (см. ниже), но в таком виде выражение имеет более ясный смысл.
После выполнения оператора
L2=(i>10) ׀׀ (a<0.0001);
L2 получит значение true, если выполняется хотя бы одно из условий, т.е. либо i>10, либо a<0.0001.
В логическом выражении могут присутствовать, как мы видим, операции трех типов: 1) арифметические (в левой или правой частях отношения, выполняются в первую очередь); 2) операции отношения; 3) логические. Логические операции имеют самый низкий приоритет. Из них в первую очередь выполняется операция отрицания (!), далее операция «Логическое «и» (&&) и в последнюю очередь «Логическое «или» ( ׀׀). Например,
bool a = true;
bool b = 1 < 0 && a;
Console.WriteLine(b);
Console.ReadLine();
В результате программа распечатает на экране
false
Логические выражения обычно используются в условном операторе if (п. 1.7.1).
Перечислимым называют тип, в котором описан набор пар, состоящих из символьного имени (набора именованных констант, который называется списком перечислителя) и числового значения. Перечислимые типы относятся к типам значениям. Ключевое слово enum используется для объявления перечисления. По умолчанию первому перечислителю задано значение, равное нулю, а значение каждого последующего оператора увеличивается на 1.
Пример
using System;
class Program
{
enum Days { Sat, Sun, Mon, Tue, Wed, Thu, Fri };
static void Main()
{
Console.WriteLine(Days.Tue);
Console.WriteLine((int)Days.Tue);
}
}
В результате программа распечатает на экране
Tue
3
Пример использования перечисления см. гл. 5, пример 5.5.
К основным операторам отнесем операторы, которые позволяют описать типовые структуры алгоритмов:
Перечисленные операторы являются управляющими операторами, так как они управляют последовательностью исполнения строк программы.
К группе основных операторов отнесем также оператор присваивания, при помощи которого происходят изменения значений переменных программы (этот оператор рассмотрен выше).
Рассмотрим более подробно каждый из перечисленных операторов.
Условный оператор if позволяет выбрать одну из двух ветвей вычислительного процесса.
Общий вид оператора:
if (выражение)
{
Операторы1;
}
[Else
{
Операторы2;
}]
Выражение в скобках после if является логическим выражением, значением которого является одно из двух логических значений true (истина) или false (ложь). В первом случае выполняются Операторы1, во втором Операторы2. Вторая ветвь (Операторы2) может отсутствовать. Об этом говорит наличие квадратных скобок в определении оператора. (То, что заключено в квадратные скобки, не является обязательным.) После выполнения какой-либо одной из ветвей условный оператор считается выполненным. Наличие фигурных скобок не является обязательным, если какая-либо ветвь содержит один оператор, в котором нет объявления переменных. Однако рекомендуется фигурные скобки использовать всегда, что делает структуру программы более наглядной. Например,
int i = 1, x;
if (i <= 10)
{
x = 5;
}
else
x = 0;
Console.WriteLine(x);
Если требуется выполнить более одного оператора после if или после else, то их все обязательно помещают внутри скобок {}. Эти операторы образуют блок. Например,
int i = 15, x;
if (i <= 10)
{
x = 5;
Console.WriteLine(x);
}
else
{
x = 2;
Console.WriteLine(x);
}
Каждый из двух блоков условного оператора (наличие обоих блоков необязательно) может содержать любые операторы, в том числе условные операторы. Если условный оператор входит в состав блока Операторы1, выполняемого после if, то он записывается и выполняется по общим правилам. Если проверка условия осуществляется после else, то используется оператор else if (условие), использование которого демонстрирует следующая схема:
if (условие1)
{
Операторы1;
}
else if (условие2)
{
Операторы2;
}
[else
{
Операторы3;
}]
Если истинно условие1, то выполняются Операторы1 и условный оператор заканчивает работу. Если условие1 ложно, то проверяется условие2, и, если оно истинно, то выполняются Операторы2, в противном случае выполняются Операторы3, если присутствует последний else,или конструкция не выполняет никаких действий. Последний else всегда относится к последнему if, для которого еще не было соответствующего else. Например,
int a = -4, x = 0;
if (a > 5)
x = 1;
else if (a > 0 )
x = 2;
else
x = 3;
Console.WriteLine(x);
В результате при a = 3 будет x = 2; при a = - 4 будет x = 3.
Замечания:
1. В рассмотренном примере после каждого условия следует один оператор и фигурные скобки необязательны.
2. В языке C# существует упрощенный вариант записи условного оператора, называемый оператором проверки, который можно использовать в выражении. Общий вид оператора проверки:
(условие) ? значение1: значение2;
Например,
int i = 4>m? 6 : 8;
Если, например, m = 9, то условие не выполняется (имеет значение false) и переменной i будет присвоено значение 8. При использовании оператора проверки следует помнить о правилах приоритета, так как в этом операторе могут использоваться арифметические, логические операторы и операторы отношений.
Оператор цикла for предназначен для выполнения одного оператора или группы (блока) операторов заданное количество раз.
Общий вид оператора for:
for ([ инициализация]; [условие];[приращение])
{
Операторы;
}
Если в цикле выполняется один оператор, то фигурные скобки необязательны. Однако в этом случае их рекомендуется использовать для наглядности. Например,
using System;
class Program
{
static void Main()
{
for (int i = 1; i<=5; i++)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
}
В последовательные строки будут выведены натуральные числа от 1 до 5.
Оператор for выполняется следующим образом: переменной цикла i присваивается начальное значение 1. Проверяется условие. Здесь при i = 1 оно выполняется (имеет значение true). Далее выполняется оператор в цикле, и на консоль выводится значение 1. Затем i увеличивается на 1 и снова проверяется условие. И так до тех пор, пока не станет i > 5 (условие получает значение false) и не произойдет выход из цикла. Заметим, что первая проверка условия производится перед первым выполнением оператора (операторов), расположенных после for, и таким образом возможна ситуация, когда этот оператор (операторы) не будет выполнен ни разу. Цикл for относится к циклам с предусловием.
Все параметры оператора for являются необязательными (при определении оператора каждый параметр заключен в квадратные скобки) и, следовательно, могут отсутствовать либо по отдельности, либо все вместе. При этом организация цикла (в той или иной мере) возлагается на программиста. Например,
for (int i = 1; ; i++)
{
if (i <= 5)
break;
Console.WriteLine(i);
}
Console.ReadKey();
Здесь проверка условия происходит внутри цикла и прерывание выполнения цикла происходит при помощи оператора прерывания break. Оператор continue возобновляет выполнение цикла с первого оператора, игнорируя следующие за ним операторы. Например,
for (int i = 1; ; i++)
{
Console.WriteLine(i);
if (i <= 5)
continue;
else
break;
}
Console.ReadKey();
Здесь пока i не достигло 5, цикл возобновляет свою работу с самого начала. При i>5 выполнение цикла прервется оператором break.
Примеры использования цикла for в программах см. гл. 2.
Оператор while реализует цикл по условию с проверкой условия до первого прохождения цикла (цикл с предусловием). В отличии от цикла for количество проходов цикла while неизвестно до начала выполнения цикла. Общий вид цикла:
While (условие)
{
Операторы
}
Операторы выполняются пока условие имеет значение true. Например:
using System;
class Program
{
static void Main()
{
int i = 1;
while (i < 6)
{
Console.WriteLine(i);
i++;
}
Console.ReadKey();
}
}
В последовательные строки выводятся числа от 1 до 5.
Внутри цикла, как и для цикла for, могут быть операторы break, прерывающий цикл, и continue, передающий управление на следующую итерацию. Их использование аналогично.
Операторы цикла do-while реализуют цикл по условию с проверкой условия после первого прохождения цикла (цикл с постусловием). Общий вид цикла:
do
{
Операторы
}
While (условие);
Операторы выполняются до тех пор пока условие имеет значение true. Например,
using System;
class Program
{
static void Main()
{
int i = 1;
do
{
Console.WriteLine(i);
i++;
}
while (i < 6);
Console.ReadKey();
}
}
Результат выполнения программы будет тот же, что и в предыдущем примере.
Цикл можно прервать оператором break. Для перехода непосредственно к оператору вычисления выражения While (проверке условия) используется оператор continue.
Примеры программ с использованием циклов по условию см. гл. 2.
Оператор выбора switch обеспечивает выполнение одного из многих фрагментов программы (блоков), расположенных последовательно друг за другом, в зависимости от значения некоторой переменной или выражения целочисленного типа. Общий вид оператора:
switch (выражение)
{
case n1:
Операторы1
break;
case n2:
Операторы2
break;
. . .
case nn:
ОператорыN
break;
[default:
Операторы
break;]
}
Управление передается оператору case, номер которого совпадает со значением оператора switch. После выполнения операторов соответствующего case, выполняется оператор break, и управление передается за пределы оператора switch. Если ни одно значение из n1, n2,..., nn не совпадает со значением выражения switch, то управление передается на операторы, следующие за default. Если default отсутствует, управление передается за пределы оператора switch (см. гл. 2, пример 2.22).
Оператор безусловного перехода goto передает управление помеченному оператору. Общий вид оператора:
goto метка;
Метка идентификатор, после которого располагается двоеточие. Размещается в отдельной строке перед оператором, которому будет передано управление при выполнении оператора goto. Например,
goto label1;
. . .
label1:
Console.WriteLine(“конец”)
В современном программировании goto используется редко, в основном для более простого и естественного выхода из сложных вложенных циклов. Для начинающих изучение программирования использование goto категорически не рекомедуется.
Может использоваться также для явного перехода к какой-либо строке case в операторе switch вместо break:
switch (выражение)
{
case n1:
Операторы1
break;
case n2:
Операторы2
goto case n1;
. . .
case nn:
ОператорыN
break;
[default:
Операторы
break;]
}
Отметим также возможность использования пустого оператора. Пустой оператор никак не изображается, но отделяется точкой с запятой. Он не выполняет никаких действий, но может быть помечен. Использование пустого оператора позволяет, в частности, сразу перейти на конец программы.
Составной оператор (блок) это несколько операторов, заключенных в фигурные скобки. Составные операторы (блоки) уже использовались в приведенных выше примерах программ на циклы. Внутри составного оператора можно объявлять переменные (локальные переменные блока составного оператора). Они будут доступны только внутри блока составного оператора.
Ввод данных осуществляется следующим образом: из входного потока при помощи метода ReadLine (класса Сonsole) считывается строка символов. Ее значение присваивается какой-либо переменной типа string . Далее это символьное значение при помощи метода Parse, который есть у каждого встроенного типа значения, преобразуется в числовую форму представления соответствующего типа (для типа double: double.Parse, для тапа int: int.Parse). В случае ошибочного указания типа будет выдано сообщение об ошибке. Например, для типа int:
int a;
string s = Console.ReadLine();
a = int.Parse(s);
Console.WriteLine(a);
В окне «Локальные» отображаются локальные переменные a и s (имя, значение, тип), после выполнения приведенного фрагмента кода.
При вводе строки символов в переменную символьного типа (типа string) никакого преобразования не требуется. Например,
string s = Console.ReadLine();
int i = int.Parse(s);
Console.WriteLine ("i= {0:d}",i);
string g = Console.ReadLine();
Console.WriteLine ("g= {0}",g);
Console.ReadKey();
Здесь предполагается вывод с использованием формата (см. п. 1.8.2).
Для ввода значения числовой переменной можно также использовать одну строку
int i = int.Parse(Console.ReadLine());
Если на клавиатуре набрать, например 7 (7 появляется на экране), и нажать клавишу [Enter], то переменная i получит значение 7. Это значение далее выводится на консоль (экран). При этом производится перевод строки. Далее в соответствии с приведенным кодом следует набрать строку символов (строка появится на экране) и нажать клавишу [Enter]. Введенная последовательность символов будет присвоена переменной g и следующим оператором выведена на консоль.
Замечание. При вводе чисел с дробной частью для отделения дробной части используется запятая (зависит от настроек в окне «Панель управления» окна «Региональные стандарты»).
Можно в одной строке разместить значения нескольких переменных, разделив их, например, пробелами. В этом случае требуется так называемый разбор строки (метод Split, см. гл. 6) с целью извлечения цепочек символов между пробелами. Такой способ ввода используется в гл.3 при работе с массивами.
Вывод данных осуществляется с использованием метода WriteLine (или Write) (класса Сonsole). После выполнения WriteLine производится перевод строки и последующий вывод происходит в новую строку. После выполнения Write перевода строки не происходит. Вывод будет продолжен в текущую строку.
Простейшие варианты использования указанных средств для вывода значений отдельных переменных использованы в приведенных выше программах.
Вывод может быть организован с использованием формата. В этом случае оператор вывода имеет следующий вид:
WriteLine (["строка формата",] список вывода);
Квадратные скобки [] означают необязательный параметр, т.е. строка формата может отсутствовать, но только в том случае, если в списке вывода один элемент. При выводе нескольких элементов использование формата обязательно.
В списке вывода перечисляются через запятую элементы списка. В качестве элементов списка вывода в общем случае могут фигурировать имена переменных, константы или выражения, которые перед выводом вычисляются.
В строке формата для каждого выводимого значения в фигурных скобках указывается:
Для данных типа int используется код d(или D). Например, {0:d} или {0,6:d}.
В первом случае нулевой элемент списка, имеющий значение типа int, выводится в поле, размер которого не указан. Количество позиций, в которые осуществляется вывод, соответствует количеству знаков в числе.
Во втором случае значение выводится в поле размером 6 позиций. Выводимое число прижимается к правой границе. Если размер поля недостаточен для размещения числа, то указание размера игнорируется.
Для данных типа double может использоваться код f(F) или код e(E). Первый используется для вывода в форме целой и дробной частей, разделенных запятой (форма с фиксированной запятой). Второй для вывода вещественного числа в форме с порядком (можно указать также количество цифр после запятой, в целой части выводится всегда одна цифра).
Например, необходимо вывести значение 13,653 и пусть это будет 1-й элемент списка вывода. При выводе в форме с фиксированной запятой можно использовать формат {1,8:f2} или {1,f} (возможны и другие варианты).
В первом случае значение выводится в поле размером 8 позиций с двумя знаками после запятой в правые позиции поля, т.е. будет выведено 13,65.
Во втором случае размер поля и количество знаков после запятой не указаны, и выводятся все знаки, представляющие число, т.е. будет выведено 13,653.
При выводе вещественного числа в форме с порядком (например, 1-го элемента списка) можно в строке формата указать {1,e2}или {1,10:E3} (возможны и другие варианты).
В первом случае число будет выведено в виде 1,37е+001 (при выводе значение округляется в большую сторону), во втором в поле размером 10 позиций в виде 1,365е+001.
Таким образом, для каждого элемента списка имеем {n[,m][:k]}. Кроме того, в строке формата могут содержаться и другие символы, которые обозначают сами себя и выводятся без изменений; наличие в строке символа \t соответствует нажатию клавиши Tab (табулирование), символа \n переводу строки. Другие возможности здесь не рассматриваются.
Пример.
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
double x = 3.356345, y = 15.24567;
Console.WriteLine(
"j={0:d} {2,8:f3} {1} {3:e2}",
3, 8, x, y);
Console.ReadKey();
}
}
}
Здесь выведенная строка соответствует использованному формату: выведено j=, затем три пробела, далее нулевой элемент списка по формату для целых d без указания размера поля, следующим выводится второй (предпоследний) элемент списка по формату для вещественных чисел в поле размером 8 с тремя цифрами после запятой, далее первый элемент списка без указания форматного кода и, наконец, последний (третий) элемент списка по формату с порядком с двумя цифрами после запятой. Обратим внимание на то, что целая часть от дробной при выводе отделяется запятой.
Если в приведенном коде использовать оператор вывода
Console.WriteLine("j= {0:d} \t {2,8:f3} \n {1} {3:e2}", 3, 8, x, y);
то вывод будет следующим (после вывода первых двух элементов следует перевод строки). Обратим также внимание и на эффект от символа табулирования.
Оператор Console.ReadKey() используется для того, чтобы задержать результаты на экране. Иначе они очень быстро исчезнут.
Другие примеры форматированного вывода см. в гл. 2.3.
Циклом называется многократно повторяющаяся последовательность действий (операторов). Цикл типичная структура, характерная для программ, реализуемых на компьютере. Средства языка C# для организации циклов приводятся в п. 1.7.2, 1.7.3, 1.7.4.
Рассмотрим вначале циклы по счетчику, т.е. когда количество повторений цикла известно до начала его выполнения.
При организации цикла по счетчику необходимо:
1) выделить повторяющиеся действия и записать их в общем виде;
2) выбрать управляющую переменную цикла. Это может быть какая-либо величина, имеющаяся в постановке задачи, либо используемая специально в качестве счетчика;
3) определить параметры цикла, т.е. начальное и конечное значения управляющей переменной и шаг ее изменения.
Пример 2.1. Вычислить s = 1 + 2 + 3 + … +100.
Организуем вычисления так, чтобы на каждом шаге выполнялись простые действия, в данном случае это прибавление к сумме очередного слагаемого. Вначале необходимо обнулить переменную, в которой будет накапливаться сумма (s = 0), а затем на каждом шаге добавлять к сумме очередной член, т.е. многократно выполнять операцию
s = s + i, где i = 1, 2, 3, ... , 100.
Таким образом, в качестве управляющей переменной цикла в данном случае можно использовать сам член суммы, который изменяется в заданных пределах (от 1 до 100) с заданным шагом 1. Тогда программа будет иметь следующий вид:
using System;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
int i, s = 0;
for (i = 1; i <= 100; i = i + 1)
s = s + i;
Console.WriteLine("s = {0}", s);
Console.ReadKey();
}
}
}
Результат выполнения программы:
Пример 2.2. Вычислить сумму четных чисел от 2 до 20.
Здесь требуется организация цикла по счетчику с управляющей переменной, например k, изменяющейся с шагом 2 от 2 до 20:
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
int k, s = 0;
for (k = 2; k <= 20; k = k + 2)
s = s + k;
Console.WriteLine("s= {0}", s);
Console.ReadKey();
}
}
}
Или
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int k = 2, s = 0;
do
{
s = s + k;
k = k + 2;
} while (k <= 20);
Console.WriteLine("s = {0}", s);
Console.ReadKey();
}
}
}
Пример 2.3. Вычислить s = 3 + 32 + 33 + … + 38.
На каждом шаге алгоритма необходимо прибавлять к сумме очередное слагаемое: (s = s + 3i), где i = 1, 2, 3, ... , 8.
Вспомним, что операция возведения в степень в языке C# отсутствует, поэтому следующий член суммы будем получать из предыдущего домножением его на 3. В качестве управляющей переменной цикла можно использовать показатель степени, изменяющийся в заданных пределах от 1 до 8 с заданным шагом, равным 1. Программа, таким образом, будет иметь следующий вид (здесь и далее приводится только алгоритмическая часть проекта):
int s = 0, a = 1;
for (int i = 1; i <= 8; i = i + 1)
{
a = a * 3;
s = s + a;
}
Console.WriteLine("s = {0}", s);
Console.ReadKey();
Замечание. Выражение для получения очередного члена последовательности из предыдущего называется рекуррентной формулой.
Пример 2.4. Вычислить s = 5/8 + 7/10 + ... + 31/34.
Для организации цикла в этом случае можно использовать числитель, изменяющийся от 5 до 31 с шагом 2, а знаменатель выразить через числитель (он отличается от числителя на 3):
double s = 0;
for (int a = 5; a <= 31; a = a + 2)
{
s = s + a / (a + 3.0);
}
Console.WriteLine("s = {0}", s);
Console.ReadKey();
Замечание. При делении целого на целое дробная часть теряется (результат получается целым). Поэтому при вычислении знаменателя второе слагаемое записано в виде вещественного числа. Знаменатель будет теперь иметь тип double, и результат деления будет иметь тип double.
Возможны и другие способы организации вычислений. Предложите их самостоятельно.
Для организации цикла в данном примере можно использовать специальную переменную (счетчик), определяющую номер итерации. Для определения общего количества повторений цикла (n) можно использовать формулу
n = (iкон iнач)/h + 1.
В данном случае при iкон = 31, iнач = 5, h = 2 получаем n = 14. Тогда программа будет иметь вид
double s = 0, a;
int n;
double xh = 5, xk = 31, h = 2;
a = xh;
n = (int)((xk - xh) / h + 1);
for (int i = 1; i <= n; i = i + 1)
{
s = s + a / (a + 3);
a = a + h;
}
Console.WriteLine("s = {0}", s);
Console.ReadKey();
Здесь при вычислении n использовано явное преобразование типа указанием типа в скобках перед выражением, так как результат метода Round (округление) имеет тип double.
Пример 2.5. Вычислить .
double s = 0.0, a;
int i, p = 1;
for (i = 1; i <= 25; i = i + 1)
{
p = -p;
a = p * 2 * i / (i * i + 2.0);
s = s + a;
}
s = (2.0 / 3.0) * s;
Console.WriteLine("{0:f4}",s);
Console.ReadKey();
Замечание. В программе использован тот же прием согласования типов, что и в примере 2.4. Порядок вычислений и тип результата операций в выражении правой части при вычислении члена суммы a следующий: вычисляется p 2 тип результата int, результат этой операции умножается на i, тип результата int, вычисляется знаменатель (выражение в скобках) в следующем порядке: вычисляется i i - результат операции имеет тип int, к этому результату прибавляется вещественное число 2.0 (по умолчанию типа double), знаменатель после вычисления имеет тип double. Таким образом, значение выражения правой части имеет тип double.
Пример 2.6. Получить таблицу значений функции при изменении x в пределах от xh = 2,5 до xk = 2,5 с шагом h = 0,5.
const double xh = -2.5, xk = 2.5, h = 0.5;
double x, y;
int i, n;
n = (int)((xk - xh) / h + 1);
x = xh;
for (i = 1; i <= n; i = i + 1)
{
y = Math.Pow(x, 2) + 0.5 * x;
Console.WriteLine("x = {0:f2}\ty = {1:f2}", x, y);
x = x + h;
}
Console.ReadKey();
Замечания:
1. Для определения количества значений аргумента при его изменении в пределах от xh до xk c шагом h использована формула n =(xk - xh)/h + 1.В программе для получения в качестве n целого значения использовано явное преобразование типа указанием его в скобках перед выражением.
2. Для вычисления использован метод Pow класса Math.
3. Наличие символа \t в строке формата обеспечивает табуляцию при выводе каждого значения y, что обеспечивает большую наглядность результата. (Этого эффекта можно добиться и другими способами. Предложите их самостоятельно.)
Второй вариант. Используется цикл do:
const double xh = -2.5, xk = 2.5, h = 0.5;
double x, y;
x = xh;
do
{
y = Math.Pow(x, 2) + 0.5 * x;
Console.WriteLine("x = {0:f2}\ty = {1:f2}",
x, y);
x = x + h;
}
while (x < xk + 0.0001);
Console.ReadKey();
Здесь при проверке условия while сравнение текущего значения x осуществляется с величиной несколько большей верхней границы его изменения. Это связано с особенностями представления в памяти компьютера вещественных чисел. Вещественные числа представляются неточно, и в результате многократного выполнения арифметических операций может накопиться ошибка, так что последнее значение x может быть несколько больше xk и при отсутствии второго слагаемого в условии не попадет в таблицу.
Пример 2.7. Вычислить p = n! при n = 8.
Вычисление факториала можно организовать в цикле, домножая каждый раз значение p на очередной сомножитель:
const int n = 8;
int p = 1;
for (int i = 2; i <= n; i++)
{
p = p * i;
}
Console.WriteLine("p = {0:d}", p);
Console.ReadKey();
Циклы по условию необходимо организовывать, когда количество повторений цикла неизвестно и в ряде случаев является искомой величиной при решении задачи. Средства языка C# для организации циклов по условию приведены в п. 1.1.4.
Пример 2.8. Определить количество (n) членов арифметической прогрессии
,
сумма которых впервые превысит заданное число р.
На каждом шаге алгоритма нужно добавлять к сумме s очередной член m = a + ih (s = s + m), i = 1, 2, ..., но при этом перед каждым прибавлением очередного члена проверять условие s p. Как только в первый раз условие не будет выполнено, т.е. s > p, необходимо выйти из цикла.
Далее приводится программа для решения задачи при a = 2, h = 3, p = 41. В программе текущее значение номера члена суммы обозначено через n. Значение этой переменной, при котором впервые оказалась s > p, и будет результатом:
int s = 0, n = 0, m;
const int a = 2, h = 3, p = 41;
while (s <= p)
{
m = a + n * h;
s = s + m;
n = n + 1;
}
//вычитается 1, прибавленная после
//последнего изменения суммы.
n = n - 1;
Console.WriteLine("{0:d}", n);
Console.ReadKey();
Второй вариант программы использует оператор цикла с проверкой условия после первого прохождения цикла:
int s = 0, n = 0, m;
const int a = 2, h = 3, p = 41;
do
{
m = a + n*h;
s = s + m;
n = n + 1;
} while (s <= p);
n = n - 1;
Console.WriteLine("{0:d}", n);
Console.ReadKey();
Замечание. Член суммы m можно было бы вычислять рекуррентно: m = m + h, задав до начала цикла m = a (в программу при этом нужно внести и некоторые другие изменения, которые рекомендуется выполнить самостоятельно).
Пример 2.9. Вычислить при x = 0,5. Суммирование прекратить, когда очередной член суммы будет меньше заданного ε = 0,0001.
double x = 0.5;
const double eps = 0.0001;
double s = 0, a;
int n = 1;
do
{
a = Math.Cos(n*x) / n;
s = s + a;
n = n + 1;
} while (Math.Abs(a) > eps);
Console.WriteLine("Сумма равна {0:f4}", s);
Console.ReadKey();
Пример 2.10. Методом итераций найти корень уравнения на отрезке [1, 0,3] с точностью ε = 0,0001, принимая за начальное приближение x0 = 0,4 и вычисляя последовательно , i = 1, 2, 3, …, пока не будет выполнено условие .
При программной реализации метода итераций нет необходимости в использовании переменных с индексами. Для организации вычислительного процесса необходимо одновременно иметь в памяти значения лишь двух последовательных приближений (обозначим их х0 и х1). Следующее приближение х1 получается из предыдущего х0. Если условие достижения точности аbs(х1х0) eps не выполняется, то следует переслать значение х1 в переменную х0 (х0 = х1) и получить в х1 следующее приближение.
При организации цикла проверку условия удобно осуществлять после первого прохождения цикла, т.е. использовать цикл do … while:
double x0 = -0.4;
const double eps = 0.0001;
double x1, d;
do
{
x1 = 0.5 * (Math.Sin(x0 * x0) - 1);
d = Math.Abs(x1 - x0);
x0 = x1;
} while (d >= eps);
Console.WriteLine("Корень равен {0:f4}", x1);
Console.ReadKey();
Пример 2.11. Вычислить частное p и остаток q от деления двух натуральных чисел r и t, не используя операцию деления. Число r можно представить в виде . Будем последовательно вычитать t из r и подсчитывать количество вычитаний в переменной p. Значение q результат последнего вычитания, когда в первый раз будет выполнено условие .
int r, t, q, p = 0;
r = 26;
t = 8;
q = r;
while (q >= t)
{
q = q - t;
p = p + 1;
}
Console.WriteLine("Частное {0:d} Остаток {1:d}", p, q);
Console.ReadKey();
Здесь используется цикл с проверкой условия до первого выполнения тела цикла (что важно), так как возможен случай, когда r < t, и тело цикла не должно выполняться ни разу.
Пример 2.12. Корабль должен преодолеть путь в 3000 км. В первый день он прошел 200 км. Каждый следующий день он будет проделывать путь на 5 % больше, чем в предыдущий день. Через какое время он прибудет в порт назначения?
Обозначим путь одного дня через р. Вначале р = 200. Путь следующего дня вычисляется как р = р + 0.05р = 1.05р. Это значение прибавляем к суммарному пути s: s = s + р. Количество дней обозначим через n и будем увеличивать его каждый раз на 1:
double s = 0.0, p = 200.0;
int n = 0;
while (s < 3000)
{
s = s + p;
n = n + 1;
p = p * 1.05;
}
Console.WriteLine("{0:d}", n);
Console.ReadKey();
Пример 2.13. Вычислить при x, изменяющемся в пределах от 0,1 до 1 с шагом 0,05.
Вначале ограничимся вычислением суммы при заданном значении х. Здесь член суммы необходимо вычислять рекуррентно. Для вывода рекуррентной формулы выпишем выражения для двух последовательных членов суммы, например (i 1)-го и i-го, и, разделив i-ый член на (i1)-й, получим выражение, на которое необходимо домножить (i-1)-й член для получения i-го. Итак,
Таким образом, чтобы получить i-й член из предыдущего (i-1)-го члена, его нужно домножить на 2х/i.
Вычисление суммы для фиксированного значения х может быть осуществлено следующим образом:
double s, a, x = 0.1;
s = 1; a = 1;
for (int i = 1; i <= 12; i++)
{
a = a * 2 * x / i;
s = s + a;
}
Console.WriteLine("{0:f4} {1:f4}", x, s);
Эта последовательность операторов должна быть выполнена в цикле по x:
double s, a, x;
double xh = 0.1, xk = 1.0001, h = 0.05;
int n = (int)((xk - xh) / h + 1);
x = xh;
for (int j = 1; j <= n; j++)
{
s = 1; a = 1;
for (int i = 1; i <= 12; i++)
{
a = a * 2 * x / i;
s = s + a;
}
Console.WriteLine("{0:f4} {1:f4}", x, s);
x = x + h;
}
Console.ReadKey();
Такая структура программы, когда цикл выполняется внутри другого цикла, называется вложенными циклами. Далее приводится результат выполнения программы.
Замечание. В операторе вывода строка формата содержит пробелы, что обеспечивает разделение при выводе столбцов значений x и s.
Пример 2.14. Вычислить сумму
для значений х, изменяющихся в пределах от 0,2 до 1 с шагом 0,2. Суммирование прекращать, когда очередной член суммы по абсолютной величине станет меньше = 0,0001.
Задача сводится к организации вложенных циклов. Внешний цикл по счетчику обеспечивает изменение х (см. пример 2.13). Во внутреннем цикле по условию осуществляется вычисление суммы (см. пример 2.9).
Член суммы ai имеет более сложный вид, чем в примере 2.9. Его целесообразно представить в виде двух сомножителей:
ai = ci(2i 1),
где будем вычислять по рекуррентной формуле, выражая последующий член через предыдущий:
Число значений х на отрезке от 0,2 до 1 с шагом 0,2 равно 5. В программе для контроля при каждом значении х вычисляется также функция , которая приближенно может быть представлена в виде указанной суммы:
const double xh = 0.2, h = 0.2, eps = 0.0001;
double a, x, s, y, c;
int n = 5, i;
x = xh;
for (int j = 1; j <= n; j++)
{
s = 1; c = -1; i = 1;
do
{
c = -c * x * x / ((2 * i - 1) * 2 * i);
a = c * (2 * i - 1);
s = s + a;
i = i + 1;
} while (Math.Abs(a) >= eps);
y = Math.Cos(x) + x * Math.Sin(x);
Console.WriteLine("x= {0:f4} s= {1:f4} y= {2:f4}",
x, s, y);
x = x + h;
}
Console.ReadKey();
Вопросы для самопроверки
1. Что такое цикл? Операторы цикла for, while, do … while. Различия между ними.
2. Какие данные необходимы для организации цикла for? Что такое управляющая переменная цикла?
3. Циклы по условию и их организация.
4. Операторы break и continue. В каких случаях они используются?
5. Типовые алгоритмы циклической структуры: вычисление суммы n слагаемых, вычисление произведения n сомножителей, вычисление факториала, табулирование функции.
6. Вычисление суммы с использованием рекуррентных соотношений.
7. Вложенные циклы.
8. Согласование (приведение) типов.
9. Вывод с использованием формата.
Задание для самостоятельного выполнения
1. Вычислить s = 2 + 5 + 8 + ... + 35.
2. Вычислить s = 2/3 + 4/5 + 6/7 + ... + 112/113.
3. Вычислить s = cos x + (cos 2x)/x + (cos 3x)/x2+ ... + (cos 9x)/x8.
4. Вычислить сумму s = cos x + (cos 2x)/22 + ... + (cos nx)/n2. Суммирование прекратить, когда очередной член суммы будет меньше ε = 0,0001.
5. Вычислить сумму квадратов 10 членов арифметической прогрессии
s = p2 + (p + h)2 + ... + (p + 9h)2.
6. Определить количество членов арифметической прогрессии
s = a + (a + h) + ... + (a + nh),
сумма которых не превышает заданного числа р.
7. Получить таблицу функции y(x) = 0,5x2 7x при изменении x от 4 до 4 с шагом 0,5.
8. Вычислить значение факториала числа 6 (6! = 123...6).
9. Вычислить s = 1! + 2! + ... + 6!
10. Вычислить s = (1) 1·51/1! + (1)2·52/2! + ... + (1) 6·56/6!
11. Определить значение n, для которого р = 1 · 4 · 7...n не превышает L = 30 000.
12. Возвести число 3 в 7-ю степень, не используя операцию возведения в степень.
13. Вычислить при заданном x сумму s = 1 + 1/x + 1/x2 + ... + 1/x10.
14. Вычислить s = 1 + x2 + x4 + ... + x2n (x < 1). Вычисления прекратить, когда очередной член суммы будет меньше ε = 0,0001.
15. Начав тренировки, спортсмен в первый день пробежал 10 км. Каждый следующий день он увеличивал дневную норму на 7 % от нормы предыдущего дня. Определить:
а) какой суммарный путь пробежит спортсмен за 7 дней;
б) через сколько дней спортсмен пробежит суммарный путь 100 км;
в) через сколько дней спортсмен будет пробегать в день больше 20 км?
16. Вкладчик положил в банк 10 000 рублей под 8 % в месяц. Определить, через какое время сумма удвоится.
Следующие задачи требуют организации вложенных циклов. Вычислить сумму s, прекращая суммирование, когда очередной член суммы по абсолютной величине станет меньше 0,0001, при изменении аргумента x в указанном диапазоне [a, b] c шагом h. Для сравнения в каждой точке вычислить также функцию y = f(x), являющуюся аналитическим выражением ряда.
17.
18.
19.
20.
21.
22.
23.
24.
25.
Указание. В задании 17 при вычислении суммы для выхода из цикла нужно сравнивать с точностью 0,0001 не весь член суммы, а только xi, так как второй сомножитель при i = 4, 8 , … равен 0, что приведет к прекращению суммирования при i = 4 и таким образом исказит результат.
Разветвление это структура, содержащая две ветви, из которых, в зависимости от условия, будет выполнена только одна. Разветвления организуются с помощью условного оператора if (см. п. 1.7.1):
if (выражение)
{
Операторы1;
}
[Else
{
Операторы2;
}]
. . .
Если выражение имеет значение true, то выполняются Операторы1, и происходит переход к оператору, следующему за if. Если выражение имеет значение false, то выполняются Операторы2, расположенные после else, и далее выполняется оператор, следующий за условным. Если вторая ветвь отсутствует (такая структура называется «обход»), то в случае невыполнения условия никаких действий не производится и сразу выполняется оператор, следующий за условным. Если блок (операторы, заключенные в {}) состоит из одного оператора и не содержит описаний переменных, то скобки можно не использовать.
Далее приводятся примеры программ с использованием разветвлений, разветвлений в цикле и множественного выбора (см. п. 1.7.5).
Пример 2.15. Составить программу для решения квадратного уравнения ax2 + bx + c = 0. Квадратное уравнение имеет решение, если дискриминант d = b2 4ac не является отрицательным, т.е. после ввода коэффициентов a, b и c нужно вычислить d и проверить условие d < 0. Если это условие выполняется, то нужно вывести сообщение «Уравнение не
имеет решения». Если условие не выполняется (d 0), нужно вычислить и напечатать корни (возможность совпадения корней игнорируется).
double a, b, c, d, x1, x2;
Console.WriteLine("Введите a");
a = double.Parse(Console.ReadLine());
Console.WriteLine("Введите b");
b = double.Parse(Console.ReadLine());
Console.WriteLine("Введите c");
c = double.Parse(Console.ReadLine());
d = b * b - 4 * a * c;
if (d < 0)
Console.WriteLine("Уравнение не имеет решения");
else
{
x1 = (-b + Math.Sqrt(d)) / (2 * a);
x2 = (-b - Math.Sqrt(d)) / (2 * a);
Console.WriteLine("{0:f4} {1:f4}", x1, x2);
}
Console.ReadKey();
Пример 2.16. Вывести большее из двух чисел:
double x, y, u;
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
u = x;
if (y > u)
u = y;
Console.WriteLine(u);
Console.ReadKey();
Пример 2.17. Найти максимальное из трех чисел:
double x, y, z, u;
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
Console.WriteLine("Введите z");
z = double.Parse(Console.ReadLine());
u = x;
if (y > u)
u = y;
if (z > u)
u = z;
Console.WriteLine(u);
Console.ReadKey();
Пример 2.18. Определить, лежит ли точка с координатами x, y внутри или вне круга радиусом r с центром вначале координат:
string inside = "точка внутри круга";
string outside = "точка вне круга";
double x, y, r;
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
Console.WriteLine("Введите r");
r = double.Parse(Console.ReadLine());
if (x * x + y * y <= r * r)
Console.WriteLine(inside);
else
Console.WriteLine(outside);
Console.ReadKey();
Здесь использованы символьные константы inside и outside для размещения соответствующей символьной информации, которая выводится в качестве результата.
Второй вариант программы для решения этой задачи составлен с использованием логической переменной, в которую помещается результат (true, если точка внутри круга, false в противоположном случае):
double x, y, r;
bool l = false;
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
Console.WriteLine("Введите r");
r = double.Parse(Console.ReadLine());
if (x * x + y * y <= r * r) l = !l;
Console.WriteLine(l);
Console.ReadKey();
В операторе if логической переменной l присваивается значение true : выполняется операция отрицания (оператор !), если условие выполняется, т.е. точка лежит внутри круга. Подробнее о логических операциях см. гл. 1, п. 1.5.
Пример 2.19. Определить, лежит ли точка с координатами x, y внутри или вне квадрата, вершинами которого являются точки с координатами (1, 1), (1, 1), (1, 1), (1, 1).
Точка лежит внутри квадрата, если одновременно выполняются условия |x| < 1 и |y| < 1. Результат будем получать в логической переменной (см. пример 2.18):
double x, y;
bool l = false;
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
if (Math.Abs(x) <= 1 && Math.Abs(y) <= 1) l = !l;
Console.WriteLine(l);
Console.ReadKey();
В операторе if логической переменной l присваивается значение true: выполняется логическая операция «И» (оператор &&), если условие выполняется, т.е. точка лежит внутри квадрата. Подробнее см. гл. 1, п. 1.5.
Пример 2.20. Определить, лежит ли точка с координатами x, y внутри или вне треугольника с вершинами в точках (0, 1), (0, 1), (1, 0).
Точка принадлежит треугольнику, если одновременно выполняются два условия x 0 и x+|y| 1.
double x, y;
bool l = false;
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
if (x >= 0 && Math.Abs(y) + x <= 1) l = !l;
Console.WriteLine(l);
Пример 2.21. Вводя координаты n точек x, y, определить, сколько из них попадет в круг радиусом r с центром в начале координат:
double x, y, r;
int i, n, k;
Console.WriteLine("Введите количество точек");
n = int.Parse(Console.ReadLine());
Console.WriteLine("Введите радиус");
r = double.Parse(Console.ReadLine());
k = 0;
for (i = 1; i <= n; i++)//вместо i = i + 1
{
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
if (x * x + y * y <= r * r) k++;
}
Console.WriteLine(
"В круг попало {0} точек из {1} ", k, n);
Console.ReadKey();
Пример 2.22. Ввести координаты точки x, y. Вычислить по выбору радиус-вектор точки, площадь прямоугольника с вершинами в точках (0, 0), (x, 0), (0, y), (x, y) или длину окружности, проходящей через точку x, y, с центром в начале координат.
Для выбора варианта вычислений используем оператор switch. Вариант вычислений будем задавать цифрой 1, 2 или 3:
double x, y, r;
int k = 0;
Console.WriteLine("Введите x");
x = double.Parse(Console.ReadLine());
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
Console.WriteLine(
"Признак: 1 = радиус - вектор,"
+ " 2 = площадь прямоугольника, "
+ " 3 - длина окружности");
Console.Write("Введите признак 1, 2 или 3: ");
k = int.Parse(Console.ReadLine());
switch (k)
{
case 1:
r = Math.Sqrt(x * x + y * y);
Console.WriteLine("радиус вектор {0:f}", r);
Console.ReadLine();
break;
case 2:
r = x * y;
Console.WriteLine(
"площадьпрямоугольника {0:f}", r);
Console.ReadKey();
break;
case 3:
r = 2 * Math.PI * Math.Sqrt(x * x + y * y);
Console.WriteLine(
"длина окружности {0:f}", r);
Console.ReadKey();
break;
default:
Console.WriteLine(
"Неверный выбор. Выберите 1, 2 или 3.");
Console.ReadKey();
break;
}
Вопросы для самопроверки
1. Что такое разветвление? Какой оператор используется для организации разветвлений?
2. Обход и его реализация на языке C#.
3. Множественный выбор и его реализация на языке C#.
4. Логические переменные, логические операторы, логические выражения и их использование в программах.
Задание для самостоятельного выполнения
1. На плоскости расположена окружность радиусом r с центром в начале координат. Ввести заданные координаты точки и определить, лежит ли она на окружности. Решить задачу при r=2 для точек с координатами (0, 2), (1,5, 0,7), (1, 1), (3, 0).
Указание. Считать, что точка с координатами х, у лежит на окружности радиусом r, если |х2+у2 r2|103.
Определить, лежит ли заданная точка внутри или вне треугольника с вершинами в точках (1, 0), (1, 0), (0, 1).
Указание. Уравнение прямой, ограничивающей фигуру слева: у = 1+х (х<0), справа: у = 1 х (х 0). Следовательно, точка принадлежит фигуре, если у 0 и у + х 1.
3. Для заданных a и b получить с = max(a, b), если а 0 или с = min(a, b), если а 0.
4. Для заданных a, b, c вычислить z = max(min(a, b), c).
5. Заданы площади: круга r и квадрата s. Определить, поместится ли квадрат в круге. Задачу решить при: 1) r = 70; s = 36,74; 2) r = 0,86; s = 0,74.
6. Для задачи 5 определить, поместится ли круг в квадрате. Задачу решить при: 1) r = 3,2; s=3,5; 2) r =3,2; s = 4; 3) r = 6; s = 9.
7. Вычислить значение функции y при заданном значении аргумента x по формуле y = 1, если >1, или y =, если .
8. Вычислить значение функции y при заданном значении аргумента x по формуле у = 0, если х1, или у = x2 1, если х<1.
9. Вычислить значение функции y при заданном значении аргумента x по формуле у = 0, если х 1, или у = 1 + х, если 1 < x 0, или y = 1, если х>0.
10. Вычислить значение функции y при заданном значении аргумента x по формуле y = 1, если х 1, или у = x, если 1 < x 1, или y = 1, если х>1.
Следующие задачи требуют сочетания циклов и разветвлений, а также использования множественного выбора. Предполагается, что количество вводимых исходных данных n задано (см. пример 3.2.7).
11. Определить средний рост девочек и мальчиков одного класса. В классе учится n учеников.
12. В компьютер вводятся по очереди координаты n точек. Определить, сколько из них попадет в круг радиусом r с центром в точке (a, b).
13. Ученику 1-го класса назначается дополнительно стакан молока (200 мл), если его вес составляет меньше 30 кг. Определить, сколько литров молока потребуется ежедневно для одного класса, состоящего из n учеников. После взвешивания вес каждого ученика вводится в компьютер.
14. В компьютер вводятся по очереди координаты n точек. Определить, сколько из них попадет в кольцо с внутренним радиусом r1 и внешним r2.
15. В соревнованиях по бегу принимают участие 30 спортсменов. Вводя по очереди результаты участников, определить, сколько из них выполнили заданный норматив.
16. В компьютер по очереди вводятся координаты n точек. Определить, сколько из них принадлежит фигуре, ограниченной осью абсцисс и аркой синусоиды, построенной для аргумента от 0 до .
17. В компьютер вводятся координаты n точек, лежащих на плоскости. После ввода координат каждой точки выводить номер квадранта, в котором она находится. Определить количество точек, лежащих по отдельности в 1-м и 3-м квадрантах.
18. В компьютер вводятся координаты n точек, лежащих на плоскости. Напечатать номер точки, ближайшей к началу координат, и величину расстояния от нее до начала координат.
19. Вводя n значений r, вычислить по выбору площадь квадрата со стороной r, площадь круга радиусом r или площадь равностороннего треугольника со стороной r. Использовать множественный выбор.
20. Для n пар значений А, В вычислить по выбору площадь прямоугольника со сторонами А, В; площадь кольца, заключенного между двумя окружностями с радиусами А и В; площадь равнобедренного треугольника со сторонами А, B, В. Использовать множественный выбор.
Часто требуется обрабатывать одинаковым образом данные, поступающие последовательно друг за другом. Возможны два случая:
Например, если все вводимые числа положительные, то в качестве признака конца может использоваться отрицательное число. При этом если одновременно вводятся несколько данных, то для организации цикла нужно выбрать одно из них (обычно первое), которое будет вводиться отдельно от остальных.
Следующие примеры иллюстрируют эти приемы.
Пример 2.23. Определить средний рост учеников класса (в классе n учеников). В цикле будем вводить в переменную r рост очередного ученика и прибавлять его к сумме s. После выхода из цикла разделим суммарный рост на количество учеников (программа составлена для n = 5).
double sr, r, s = 0;
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Введите рост ученика");
r = double.Parse(Console.ReadLine());
s = s + r;
}
sr = s / 5;
Console.WriteLine(sr);
Console.ReadKey();
Пример 2.24. Определить средний рост 14-летних подростков, проходящих диспансеризацию (количество заранее неизвестно).
Чтобы определить средний рост, будем в цикле вводить в переменную r рост очередного подростка и прибавлять его к сумме s, а количество подростков n увеличивать на 1. После выхода из цикла разделим суммарный рост на количество подростков.
Используем цикл do…while, для выхода из цикла будем использовать специальное значение, вводимое в r (например, 0):
double sr, r, s = 0;
int n = 0;
do
{
Console.WriteLine(
"Введите рост ученика, для окончания 0");
r = double.Parse(Console.ReadLine());
if (r == 0) break;
s = s + r;
n = n + 1;
} while (r > 0);
sr = s / n;
Console.WriteLine(sr);
Console.ReadKey();
Пример 2.25. Определить средний рост и вес 14-летних подростков, проходящих диспансеризацию.
Для организации цикла будем использовать специальное значение, вводимое в переменную r (рост), и вводить ее отдельно:
double sr, swr, r, w, s = 0, sw = 0;
int n = 0;
do
{
Console.WriteLine(
"Введите рост ученика, для окончания 0");
r = double.Parse(Console.ReadLine());
if (r == 0) break;
Console.WriteLine("Введите вес ученика");
w = double.Parse(Console.ReadLine());
s = s + r;
sw = sw + w;
n = n + 1;
} while (r > 0);
sr = s / n; swr = sw / n;
Console.WriteLine(
"Средний рост {0:f2} Средний вес {1:f2}",
sr, swr);
Console.ReadKey();
Пример 2.26. Вводя координаты x, y произвольного числа точек, определить в процентах долю точек, попавших в круг радиусом r с центром в начале координат.
Для окончания ввода будем использовать специальное значение. Пусть для всех вводимых точек x < 1000. Тогда значение x = 1000 будем использовать для окончания ввода.
double x, y, r;
int n = 0, k = 0;
Console.WriteLine("Введите радиус");
r = double.Parse(Console.ReadLine());
do
{
Console.WriteLine(
"Введите x, для окончания 1000");
x = double.Parse(Console.ReadLine());
if (x >= 1000) break;
Console.WriteLine("Введите y");
y = double.Parse(Console.ReadLine());
n = n + 1;
if (x * x + y * y <= r * r) k = k + 1;
} while (x < 1000);
if (n != 0) k = k * 100 / n;
Console.WriteLine(
"В круг попало k = {0:d}% точек", k);
Console.ReadKey();
Вопросы для самопроверки
Организация обработки потока данных, количество которых заранее не известно. Использование специального значения.
Особенности организации ввода, если каждая порция данных включает несколько значений.
Задание для самостоятельного выполнения
Решить задачи п. 3.2 для случая, когда количество данных заранее (до начала выполнения программы) неизвестно. В программе необходимо обеспечить прекращение ввода, как только входной поток иссякнет.
Массив это структура данных, содержащая несколько значений одного типа, обозначаемая одним именем. Доступ к элементам массива осуществляется по индексу. Изменяя индексы, можно переходить от одного элемента массива к другому и таким образом обрабатывать единообразно большие наборы данных, используя циклы. Индексация массивов в C# начинается с нуля. Массив может быть одномерным, многомерным, вложенным. Здесь будут рассмотрены только одномерные и двухмерные массивы. Одномерный массив представляет собой линейную структуру. Положение элемента определяется одним индексом. Двухмерный массив можно представить себе как таблицу. Положение элемента определяется двумя индексами: номером строки и номером столбца. Начнем рассмотрение с одномерных массивов.
Все массивы должны быть объявлены и инициализированы перед их использованием. При объявлении массива нужно указать тип элементов массива (элементы массива могут быть любых типов), далее следуют пустые квадратные скобки и имя массива (переменная массива). Например,
int[] array;
(Пустые квадратные скобки указывают на то, что переменная array является массивом.)
В соответствии с этим объявлением под переменную массива array выделяется ячейка памяти для хранения ссылки на одномерный массив элементов типа int. Переменной array присваивается значение null.
Далее объявленной переменной array можно присвоить массив конкретного размера, используя оператор new:
int[] array;
array = new int[5];
Оператор new служит для создания массива (выделения блока памяти для размещения элементов массива и передачи в переменную array адреса этого блока) и инициализации элементов массива со значением по умолчанию. По умолчанию все элементы числовых массивов инициализируются значением 0.
Индексация элементов массива начинается с 0. Массив размера n содержит элементы с индексами от 0 до n1.Описанный выше массив array будет содержать элементы с array [0] по array [4].
Инициализацию массива можно выполнить и при объявлении переменной массива:
int[] array = new int[5];
Действие этого оператора полностью аналогично приведенным выше двум операторам.
При объявлении массива можно явно задать значения элементов. В этом случае спецификация ранга (указание количества элементов массива) необязательна, поскольку она уже предоставлена по числу элементов в списке инициализации. Например,
int[] array1 = new int[] { 1, 3, 5, 7, 9 };
Доступ к отдельному элементу массива осуществляется заданием индекса, указываемого в квадратных скобках после имени массива. Например,
int[] array = new int[5];
…
array[3] = 8;
…
Здесь описан массив array, содержащий 5 элементов от array[0] до array[4]; 4-му элементу этого массива присваивается значение 8.
Использование переменной в качестве индекса позволяет организовывать циклы для задания элементов массива или их обработки с использованием индекса в качестве управляющей переменной цикла. Например,
int[] array = new int[5];
for (int i = 0; i < 5; i++)
{
array[i] = i;
}
Здесь каждому элементу массива array присваивается значение, равное его индексу.
Ввод массива, т.е. заполнение элементов массива заданными значениями, можно осуществлять в цикле. Например, для ввода массива a размера 5 с элементами типа double необходимо использовать цикл.
double[] a = new double[5];
string s;
for (int i = 0; i < 5; i++)
{
s = Console.ReadLine();
a[i] = double.Parse(s);
}
Здесь при каждом вызове статического метода ReadLine() класса Console нужно вводить один элемент массива, после набора на клавиатуре значения очередного элемента нужно нажимать клавишу [Enter]. Введенный элемент помещается методом ReadLine()в переменную s типа string. Для того чтобы получить число из строкового представления числа необходимо вызвать статический метод Parse, который осуществляет разбор строки и возвращает экземпляр типа, который находится в строке. Если в строке храниться число типа double, то надо вызвать статический метод Parse типа double (см. п. 1.8.1).
Можно набирать значения элементов массива на клавиатуре в одной строке, разделяя их пробелами (или любым другим разделителем). Например,
int[] a = new int[5];
Console.WriteLine(
"Введите элементы массива, разделяя"
+ " значения элементов пробелом");
string s = Console.ReadLine();
string[] c = s.Split(' ');
for (int i = 0; i < 5; i = i + 1)
{
a[i] = int.Parse(c[i]);
}
Console.WriteLine("Исходный массив");
for (int i = 0; i < 5; i++)
Console.Write("{0:d} ", a[i]);
Console.ReadKey();
Здесь последовательно набираются на клавиатуре значения элементов массива, разделяемые пробелами (см. окно вывода). Введенная строка помещается в строковую переменную s. Далее производится разбор этой строки с помощью метода Split (см. гл. 6). Определяется положение первого пробела в строке s и символы, расположенные перед ним пересылаются в первый элемент строкового массива c. Далее определяется положение следующего пробела в строке s, и символы, расположенные между текущим и предыдущим пробелами, пересылаются в следующий элемент строкового массива c и до тех пор, пока не будет закончен разбор всей строки. В результате будет сформирован строковый массив с, каждый элемент которого содержит одно число в строковом представлении, Далее эти числа после преобразования по очереди пересылаются в массив a.
Вывод массива осуществляется в строку с использованием формата: нулевой элемент вывода (элемент массива) выводится в формате d для типа int. В строке формата явно присутствует пробел, чтобы выводимые значения зрительно отделялись друг от друга. Этого же эффекта можно добиться, указывая в формате размер поля вывода на 1 больше требуемого. Например,
Console.Write ("{0,2:d} ", a[i]);
или
Console.Write ("{0,-2:d} ", a[i]);
для вывода однозначных чисел. В первом случае пробел выводится перед числом, во втором после. Можно также выводить пробел отдельным оператором после вывода очередного элемента массива. Например,
for (int i = 0; i < 5; i++)
{
Console.Write(a[i]);
Console.Write(" ");
}
При выводе массива необходимо обеспечить наглядность и удобство восприятия выводимых данных. Вывод одномерного массива целесообразно осуществлять в строку, отделяя элементы массива друг от друга пробелами. При этом используется оператор Write, после выполнения которого перевода строки не происходит (см. предыдущий пример).
Вывод одномерного массива в столбец осуществляется с использованием оператора WriteLine, после выполнения которого каждый раз происходит переход на новую строку. Например,
for (int i = 0; i < 5; i++)
{
Console.WriteLine(array[i]);
}
Вывод может осуществляться в заданном формате, например,
double[] array = new double[5];
for (int i = 0; i < 5; i++)
{
array[i] = Math.Sin(i);
Console.Write("{0,4:f} ", array[i]);
}
Console.WriteLine();
Здесь элементы массива array вычисляются и сразу выводятся на экран каждое в четвертой позиции. Оператор WriteLine без параметров обеспечивает перевод строки после вывода всего массива. Пример использования формата приведен выше.
Вывод одномерного массива размера n по m элементов в строке. Код приведен для n = 25, m = 5:
const int n = 25;
int m = 5;
int[] array = new int[n];
for (int i = 0; i < n; i++)
{
array[i] = i*i;
Console.Write("{0,3:d} ", array[i]);
if ((i+1) % m == 0) Console.WriteLine();
}
Console.WriteLine();
Если номер очередного выведенного элемента (i+1) кратен 5 (результат его деления нацело на 5 равен 0), то выполняется оператор WriteLine и происходит перевод строки.
Замечание. Чтобы задержать результаты на экране, нужно использовать метод Console.ReadKey() (см. п. 1.8.2).
Рассмотрим типовые алгоритмы обработки массивов. Приводятся фрагменты кода программ с необходимыми пояснениями.
1. Вычислить сумму элементов массива:
int[] a = new int[5]{ 2, 3, 4, 5, 7};
int s = 0;
for (int i = 0; i < 5; i++)
{
s = s + a[i];
}
Console.WriteLine(s);
Console.ReadKey();
Объявляется массив целых чисел, состоящий из пяти элементов. Элементам массива присваиваются значения из списка инициализации. На каждом шаге к сумме добавляется очередной элемент массива.
Для обработки последовательно всех элементов массива язык C# предлагает вместо обычного for оператор forеасh. Пользоваться им намного проще. Например, приведенный выше фрагмент будет выглядеть так:
int[] a = new int[5]{ 2, 3, 4, 5, 7};
int s = 0;
foreach (int x in a)
{
s = s + x;
}
Console.WriteLine(s);
Console.ReadKey();
В скобках после ключевого слова foreach объявляем переменную типа int (того же типа, что и элементы массива), которой при каждой итерации будут последовательно присваиваться значения элементов массива и ее можно будет использовать в цикле вместо переменной с индексами. При использовании forеасh отпадает необходимость в использовании управляющей переменной цикла (ее не нужно объявлять, изменять и проверять, не вышло ли значение индекса за допустимые пределы).
2. Вычислить среднее арифметическое положительных элементов массива размера 5:
double[] a = new double[5] {
4.0, 3.0, 4.0, -5.0, -7.0 };
double s = 0;
int m = 0;
for (int i = 0; i < 5; i++)
{
if (a[i] > 0)
{
s = s + a[i];
m += 1;//m = m + 1
}
}
double sr = s / m;
Console.WriteLine(sr);
Console.ReadKey();
Здесь в переменной s накапливается сумма положительных элементов, а в переменной m их количество. После выхода из цикла остается разделить сумму на количество.
С использованием оператора foreach код будет следующим:
double[] a = new double[5]{
4.0, 3.0, 5.0, -5.0, -7.0 };
double s = 0;
int m = 0;
foreach (double x in a)
{
if (x > 0)
{
s = s + x;
m += 1;//m = m + 1
}
}
double sr = s / m;
Console.WriteLine(sr);
Console.ReadKey();
3. Найти сумму элементов, расположенных до первого отрицательного элемента массива.
double[] a = new double[5] {
4.0, 3.0, 4.0, -5.0, -7.0 };
double s = 0;
foreach (double x in a)
{
if (x > 0)
{
s = s + x;
}
else
{
break;
}
}
Console.WriteLine(s);
Console.ReadKey();
Оператор break завершает цикл, если не будет выполнено условие x > 0 (т.е. встретится первый отрицательный элемент). Произойдет выход из цикла и суммирование закончится.
Возможен другой вариант программы:
double[] a = new double[5] {
4.0, 3.0, 4.0, -5.0, -7.0 };
double s = 0;
foreach (double x in a)
{
if (x < 0)
{
break;
}
s = s + x;
}
Console.WriteLine(s);
Console.ReadKey();
4. Перестановка элементов в векторе (вектор в программировании то же, что одномерный массив). В примере переставлены нулевой и первый.
double[] a = new double[5] {
4.0, 3.0, 4.0, -5.0, -7.0 };
double p;
foreach (double x in a)
Console.Write("{0} ", x);
Console.WriteLine();
p = a[0]; a[0] = a[1]; a[1] = p;
foreach (double x in a)
Console.Write("{0} ", x);
Console.WriteLine();
Console.ReadKey();
При перестановке используется вспомогательная переменная (в данном случае p).
В общем виде перестановка i-го и j-го элементов осуществляется следующим образом:
p = a[i]; a[i] = a[j]; a[j] = p;
5. Последний отрицательный элемент массива поменять с первым элементом массива:
double[] a = new double[5] {
4.0, 3.0, 4.0, -5.0, -7.0 };
double p;
int i;
for (i = 0; i < 5; i++)
Console.Write("{0:f1} ", a[i]);
Console.WriteLine();
for (i = 4; i >= 0; i--)
if (a[i] < 0) break;
p = a[i]; a[i] = a[0]; a[0] = p;
for (i = 0; i < 5; i++)
Console.Write("{0:f1} ", a[i]);
Console.WriteLine();
Console.ReadKey();
Здесь массив просматривается с конца (начиная с последнего элемента, с шагом 1), и первый встретившийся отрицательный элемент будет последним отрицательным элементом в массиве. Для того чтобы переменная цикла сохранила свое значение при принудительном выходе из цикла, ее надо объявить до использования оператора итерации for, иначе она будет локальной по отношению к циклу и потеряет свое значение вне цикла. Перестановка элементов выполняется с использованием вспомогательной переменной p. Предложить самостоятельно вариант кода с использованием оператора foreach вместо for.
6. Удалить элемент с заданным индексом из массива. Массив сжать.
Удалить элемент с заданным индексом k означает сдвинуть все следующие за ним элементы на одну позицию влево, т.е. выполнить операцию ai = ai+1 для I = k, k + 1,…, n 1:
double[] a = new double[5] {
4.0, 3.0, 4.0, -5.0, -7.0 };
for (int i = 0; i < 5; i++)
Console.Write("{0:f1} ", a[i]);
Console.WriteLine();
int n = 5, k = 1;
n = n - 1;
for (int i = k; i < n; i++)
a[i] = a[i + 1];
for (int i = 0; i < n; i++)
Console.Write("{0:f1} ", a[i]);
Console.WriteLine();
Console.ReadKey();
Размер массива уменьшился на 1. При этом на месте последнего элемента исходного массива остается старое значение, но оно не будет нас интересовать, так как формально не входит в состав результирующего массива (см. приведенное ниже окно «Локальные», в котором отображаются локальные переменные текущего контекста, имена переменных, их значения и тип).
7. Включить заданное значение p после k-го элемента массива.
Размер массива при этом увеличится на 1. Это необходимо предусмотреть при описании массива, указав его размер на 1 больше исходного.
Чтобы не потерять элемент исходного массива, необходимо освободить место для нового значения, передвинув вправо на одну позицию все элементы, начиная с (k + 1)-го, т.е. выполнить операцию ai + 1 = ai для i = n, n 1, …, k + 1. Необходимо также учитывать, что нумерация индексов массива начинается от нуля:
const int n = 6;
int[] a = new int[n];
Console.WriteLine("Введите число P");
string s = Console.ReadLine();
int p = int.Parse(s);
Console.WriteLine("Введите элементы массива");
for (int i = 0; i < n - 1; i++)
{
s = Console.ReadLine();
a[i] = int.Parse(s);
}
int k = 2;
for (int i = n - 2; i >= k + 1; i--)
{
a[i + 1] = a[i];
}
a[k + 1] = p;
for (int i = 0; i < n; i++)
Console.Write("{0:d} ", a[i]);
Console.WriteLine();
Console.ReadKey();
Перемещать элементы нужно начиная с последнего. В противном случае элементы массива, расположенные после (k + 1) элемента, будут иметь одинаковое значение, равное значению (k + 1)-го элемента. Убедитесь в этом самостоятельно.
8. Сформировать массив из элементов другого массива, удовлетворяющих заданному условию.
Пусть требуется переслать в массив b положительные элементы массива а. Сразу заметим, что индексы одинаковых по значению элементов массивов a и b не будут совпадать. Цикл организуем по индексам элементов массива a, индекс пересылаемого элемента в массиве b будем каждый раз увеличивать на 1:
double[] a = new double[5] { 4.0, -3.0,
4.0, -5.0, 7.0 };
double[] b = new double[5];
int k = 0;
foreach (double x in a)
{
if (x > 0)
{
b[k] = x;
k = k + 1;
}
}
После выхода из цикла в переменной k будет содержаться количество элементов массива b (значение индекса последнего элемента будет при этом на 1 меньше) и для вывода массива b необходимо организовать следующий цикл
for (int i = 0; i < k; i++)
Console.Write("{0:f1} ", b[i]);
Console.WriteLine();
Console.ReadKey();
9. Найти максимальный элемент массива и его индекс.
Сохраним значения первого элемента и его индекса в переменных amax = a[0], imax = 0. Далее будем просматривать остальные элементы массива последовательно, начиная со второго, сравнивать очередной элемент a[i] со значением, сохраненным в переменной amax и заменять значение amax и индекс imax , если очередной элемент оказался больше.
int[] a = new int[5] { 4, 3, 9, 5, 7 };
int amax = a[0];
int imax = 0;
for (int i = 1; i < 5; i++)
{
if (a[i] > amax)
{
amax = a[i];
imax = i;
}
}
Console.WriteLine(
"Индекс максимального элемента {0:d}"
+ " Значение максимального элемента {1:d} ",
imax, a[imax]);
Console.ReadKey();
Поиск минимального элемента массива осуществляется аналогично с той лишь разницей, что в операторе if нужно использовать оператор отношения <, а не >.
Если в массиве имеется несколько максимальных элементов, у которых значения одинаковые, а индексы разные, то в переменной, в которой сохраняется индекс, будет сохранен индекс первого из них. Чтобы получить индекс последнего, необходимо в операторе if проверить условие:
if (a[i] >= amax).
Если нужно запомнить индексы всех максимальных элементов, то необходимо предусмотреть массив для хранения их индексов, например im, в котором будут запоминаться индексы одинаковых максимальных элементов. При изменении значения amax массив im начнет заполняться сначала. Если очередной элемент a[i] равен максимальному, то в следующий элемент массива im помещается текущий индекс i:
int[] a = new int[7] { 4, 7, 9, 9, 3, 9, 2 };
int[] im = new int[7];
int amax = a[0];
int k = 0;
for (int i = 0; i < 7; i++)
{
if (a[i] > amax)
{
amax = a[i];
k = 0;
im[k] = i;
}
else if (a[i] == amax)
{
k = k + 1;
im[k] = i;
}
}
for (int i = 0; i <= k; i++)
Console.Write("{0:d} ", im[i]);
Console.WriteLine();
Console.ReadKey();
После выхода из цикла количество элементов массива im находится в переменной k.
10. Упорядочить массив.
Требуется расположить элементы массива a в определенном порядке, например, по убыванию.
Рассмотрим алгоритм упорядочения, базирующийся на двух уже рассмотренных алгоритмах, а именно на алгоритмах поиска максимального элемента и перестановки элементов.
В цикле при i = 0, 1, 2, …, n 2 будем устанавливать на нужное место i-й элемент. Для этого найдем максимальный элемент и его индекс в части массива, начиная с i-го элемента, и далее поменяем местами максимальный элемент с i-м. После окончания цикла элементы массива будут расположены в порядке убывания, при этом на последнем n-м месте окажется самый маленький элемент:
using System;
class Program
{
static void Main()
{
const int n = 7;
int[] a = new int[n]{ 4, 7, 9, 10, 3, 12, 2 };
for (int i = 0; i < n - 1; i++)
{
int amax = a[i];
int imax = i;
for (int j = i + 1; j < n ; j++)
{
if (a[j] > amax)
{
amax = a[j];
imax = j;
}
}
a[imax] = a[i];
a[i] = amax;
}
for (int i = 0; i < n; i++)
Console.Write("{0:d} ", a[i]);
Console.WriteLine();
Console.ReadKey();
}
}
Типовые алгоритмы 11 15 приводятся без соответствующих кодов. Рекомендуется разработать их самостоятельно, используя приведенные схемы алгоритмов, и проверить их на компьютере.
11. Поиск заданного элемента в упорядоченном массиве (бинарный поиск).
Поиск в упорядоченном массиве осуществляется существенно быстрее, чем в неупорядоченном. Поэтому часто целесообразно предварительно произвести упорядочение массива, особенно в случае необходимости многократного поиска и больших массивов.
Требуется в массиве А размером n, упорядоченном по возрастанию, определить наличие заданного элемента X и его индекс, если такой элемент найден.
Схема алгоритма
Реализовать алгоритм самостоятельно.
12. Объединение двух массивов с чередованием элементов.
Требуется объединить два массива одинакового размера A = (a0, a1, …, an1 ) и B = (b0, b1, …, bn1) в один массив C = (a0, b0, a1, b1,…, an1, bn1).
Элементами массива С с четными индексами являются элементы массива A:
c0 = a0; с2 = a1; …; с2i = ai, …,
элементами с нечетными индексами элементы массива B:
c1 = b0, c3 = b1…, … , с2i1 = bi,…
Таким образом, требуется организовать цикл и выполнить операции
с2i= ai, с2i1 = bi 1, для i = 0, 1, 2, , n 1.
Если массивы имеют разные размеры, то больший массив обрезается по размеру меньшего и меньший массив и урезанный большой объединяются в соответствии с предложенным алгоритмом. Далее оставшиеся элементы большего массива пересылаются подряд.
13. Объединение двух упорядоченных массивов в один с сохранением упорядоченности.
Требуется объединить два упорядоченных по убыванию массива A размером n и B размера m в один массив C размером n + m, также упорядоченный.
Схема алгоритма
Начиная с первых элементов массивов A и B, сравниваем элементы A[i] и B[j]. В массив C пересылаем больший из них, например, A[i] (если выполняется условие A[i]>=B[j]).
Реализовать алгоритм самостоятельно.
14. Инвертирование массива
15. Циклический сдвиг
А = (a1, a2, a3, a4, a5) исходный массив;
A = (a4, a5, a1, a2, a3) после циклической перестановки на 2 позиции вправо.
Вариант 1. Используется вспомогательный массив для временного хранения m последних элементов массива А. Далее оставшиеся элементы с нулевого по (n m 1)-й смещаются вправо на m позиций. (Заметим, что перемещение нужно начинать с последнего из перемещаемых элементов, чтобы не испортить элементы массива А.) После этого в первые m элементов пересылаются элементы вспомогательного массива.
Вариант 2. Циклический сдвиг осуществляется с использованием одной вспомогательной переменной, в которую каждый раз пересылается последний элемент массива А, после чего все элементы сдвигаются вправо на 1 позицию (начиная с конца), и на место первого элемента помещается значение вспомогательной переменной. Эта процедура повторяется m раз.
В C# массивы являются объектами (экземплярами). Класс Array предоставляет методы для создания, изменения, поиска и сортировки массивов, т.е. выступает в роли базового класса для всех массивов.
Если мы поставим точку после имени массива, например, a, то мы получим доступ к методам экземпляра и свойствам экземпляра типа Array.
double[] a = new double[5] {
4.0, -3.0, 4.0, -5.0, 7.0 };
Console.WriteLine(a.Length);
Console.WriteLine(a.Rank);
Для одномерного массива размера 5 выбраны свойства, возвращающие длину массива и размерность массива, которые выводятся на консоль.
Поставив точку после имени типа Array, мы получаем доступ к методам типа Array. Выберем в качестве примера статический метод Sort и укажем в качестве его параметра имя массива a.
double[] a = new double[5] { 4.0,-3.0,9.0,-5.0,7.0 };
Array.Sort(a);
for (int i = 0; i < a.Length; i++)
Console.Write("{0:f1} ", a[i]);
Console.WriteLine();
В результате работы программы мы получили массив, элементы которого упорядочены по возрастанию.
Пример 3.1. Задан массив x, содержащий 14 элементов. Вставить после максимального элемента новый элемент, значение которого равно среднему арифметическому положительных элементов, расположенных перед максимальным элементом:
const int n = 15;
double[] x = new double[n]{
5.0, -7.0, -9.0, 6.0, 4.0, 12.0, 2.0,
8.0, 7.0, 6.0, 5.0, 3.0, 1.0, 1.0, 0.0};
//Поиск максимального элемента и его индекса
double amax = x[0];
int imax = 0;
for (int i = 0; i < n; i++)
{
if (x[i] > amax)
{
amax = x[i];
imax = i;
}
}
//Нахождение среднего арифметического
double s = 0.0;
int k = 0;
for (int i = 0; i < imax; i++)
{
if (x[i] > 0)
{
s += x[i];//вместо = s = s + x[i]
k++;
}
}
double sr = s / k;
//вставка нового элемента
for (int i = n - 2; i >= imax + 1; i--)
x[i + 1] = x[i];
x[imax + 1] = sr;
for (int i = 0; i < n; i++)
Console.Write("{0:f} ", x[i]);
Console.WriteLine();
Console.ReadKey();
Пример 3.2. Упорядочить положительные элементы массива a, содержащего 7 элементов, по убыванию, оставив отрицательные элементы массива на прежних местах:
const int n = 7;
int[] a = new int[n] { 4, 7, 9, -10, 3, -12, 8 };
for (int i = 0; i < n - 1; i++)
{
if (a[i] > 0)
{
int amax = a[i];
int imax = i;
for (int j = i + 1; j < n; j++)
{
if (a[j] > amax)
{
amax = a[j];
imax = j;
}
}
a[imax] = a[i];
a[i] = amax;
}
}
for (int i = 0; i < n; i++)
Console.Write("{0:d} ", a[i]);
Console.ReadKey();
Пример 3.3. Включить заданное значение p в качестве элемента в массив x, содержащий 12 элементов, с сохранением упорядоченности (массив упорядочен по убыванию):
const int n = 13;
int[] x = new int[n] {
18, 15, 14, 13, 12, 9,
8, 7, 6, 5, 4, 3, 0 };
int p = 10;
int i;
for (i = 0; i < n - 1; i++)
if (x[i] < p) break;
for (int k = n - 2; k >= i; k--)
x[k + 1] = x[k];
x[i] = p;
for (i = 0; i < n; i++)
Console.Write("{0:d} ", x[i]);
Console.WriteLine();
Console.ReadKey();
Пример 3.4. Массив M содержит оценки 25 студентов по курсу «Информатика». Определить, сколько студентов получили оценку «5», «4», «3», «2» или «0» (в случае неявки на экзамен).
Исходные данные содержатся в массиве M размера 25. Результат получим в массиве K размера 5, где каждый элемент содержит количество соответствующих оценок. Сформируем также вспомогательный массив MK размера 5, в который поместим сами оценки:
int[] M = new int[25] { 4, 4, 5, 0, 2,
3, 0, 3, 3, 3,
4, 3, 0, 4, 4,
5, 2, 4, 3, 4,
3, 3, 5, 4, 4 };
int[] MK = new int[5] { 5, 4, 3, 2, 0 };
int[] K = new int[5];
int i;
for (i = 0; i < 25; i++)
if (M[i] == 0) K[4]++;
else K[5 - M[i]]++;
for (i = 0; i < 5; i++)
Console.Write("{0:d} - {1:d}, ", MK[i], K[i]);
Console.WriteLine();
Console.ReadKey();
Здесь само значение оценки в массиве М (т.е. М[i]) связано с индексом j соответствующего элемента в массиве МK: j = 5 M[i], т.е. количество оценок «5» будет помещаться в 1-й элемент массива МK, количество оценок «4» во 2-й элемент массива МK и т.д. Исключение составляют только значения «0», количество которых необходимо поместить в 5-й элемент массива МK, что и сделано в программе отдельно.
Вопросы для самопроверки
1. Понятие массива. Элемент массива. Индекс элемента массива. Описание массива. Ввод массива. Организация вывода массива в строку, в столбец.
2. Суммирование элементов массива. Суммирование элементов массива, удовлетворяющих условию.
3. Формирование другого массива из элементов заданного массива, удовлетворяющих условию. Перестановка элементов массива.
4. Удаление элемента массива. Включение элемента в массив.
5. Нахождение максимального (минимального) элемента массива.
6. Упорядочение элементов массива.
7. Массивы как объекты. Доступ к методам и свойствам экземпляра типа Array.
8. Доступ к методам типа Array.
Задание для самостоятельного выполнения
1. Найти сумму элементов одномерного массива размера 6. Разделить каждый элемент исходного массива на полученное значение. Результат получить в том же массиве.
2. Положительные элементы массива заменить средним арифметическим среди положительных элементов.
3. Каждый отрицательный элемент заменить первым положительным из расположенных после него.
4. Вычислить сумму и разность двух одномерных массивов размера 4. (Суммой (разностью) двух массивов одинакового размера называется третий массив такого же размера, каждый элемент которого равен сумме (разности) соответствующих элементов исходных массивов.)
5. Найти среднее значение элементов массива размера 5. Преобразовать исходный массив, вычитая из каждого элемента полученное значение.
6. Вычислить скалярное произведение двух векторов размера 4. (Скалярным произведением называется сумма попарных произведений соответствующих элементов массивов.)
7. Вычислить длину вектора размера 5. (Длина вектора вычисляется по формуле
.)
8. Элементы одномерного массива, большие среднего значения элементов массива, заменить на 0.
9. Подсчитать количество отрицательных элементов заданного одномерного массива.
10. Определить, сколько элементов заданного одномерного массива больше среднего значения элементов этого массива.
11. Дан одномерный массив размера 10 и два числа P и Q (P < Q). Определить, сколько элементов массива заключено между P и Q.
12. Сформировать массив из положительных элементов заданного массива размера 10.
13. Определить значение и номер последнего отрицательного элемента массива размера 8.
14. Дан массив размера 10. Сформировать два массива размера 5, включая в первый массив элементы исходного массива с четными индексами, во второй с нечетными.
15. Найти сумму квадратов элементов, расположенных до первого отрицательного элемента массива.
16. Дан одномерный массив размера 10. Минимальный элемент массива увеличить в два раза.
17. Дан одномерный массив размера 10. Найти сумму элементов, расположенных до максимального элемента массива.
18. Все элементы одномерного массива, расположенные перед минимальным, увеличить в 2 раза.
19. В одномерном массиве все элементы, расположенные после максимального, заменить средним значением элементов массива.
20. Задан одномерный массив размера 10. Сформировать другой одномерный массив из отрицательных элементов, расположенных между максимальным и минимальным элементами исходного массива.
21. Задан одномерный массив и число P. Включить элемент, равный Р, после того элемента массива, который наиболее близок к среднему значению его элементов.
22. Увеличить в 2 раза элемент, расположенный непосредственно после максимального элемента массива.
23. Поменять местами максимальный элемент массива и минимальный элемент части массива, расположенной после максимального.
24. Найти среднее арифметическое значение элементов массива, расположенных между минимальным и максимальным элементами массива.
25. Удалить минимальный среди положительных элементов массива.
26. Включить заданный элемент после последнего положительного в массиве.
27. Первый отрицательный элемент массива заменить суммой элементов, расположенных после максимального.
28. Максимальный элемент массива среди элементов с четными индексами заменить значением его индекса.
29. Найти все максимальные элементы одномерного массива (предполагается, что в массиве несколько одинаковых максимальных элементов) за один проход исходного массива. Сформировать массив из их индексов.
30. В одномерном массиве увеличить максимальные элементы на их порядковые номера (1-й максимальный на 1; 2-й на 2; 3-й на 3 и т.д.).
31. В одномерном массиве размера 14 поменять местами соседние элементы (1-й со 2-м, 3-й с 4-м и т.д.), распложенные перед максимальным элементом массива.
32. Упорядочить по возрастанию элементы массива с четными индексами (остальные элементы оставить на своих местах).
33. Задан массив В. Максимальный элемент (или максимальные элементы, если их несколько) заменить суммой элементов массива, расположенных до него (до каждого из них, если их несколько).
34. В массиве А найти максимальное количество следующих подряд упорядоченных по убыванию элементов.
35. Заданы массивы А и В, содержащие n и m элементов соответственно. Вставить массив В между k-м и (k + 1)-м элементами массива А (k задано).
36. Задан массив х размера 10. Вычислить значения функции у = 0,5lnx при значениях аргумента, заданных в массиве х. Вычисленные значения поместить в массив y. Вывести массивы х и y в виде двух столбцов.
37. Поменять местами максимальный и первый отрицательный элементы массива.
38. Все отрицательные элементы переставить в конец массива с сохранением порядка их следования.
39.Определить индексы элементов массива, меньших среднего. Результат получить в виде массива.
40. Упорядочить по убыванию положительные элементы массива, сохраняя остальные элементы на прежних местах.
41. В заданной последовательности чисел длиной n (n 100) определить длину самой большой упорядоченной по возрастанию подпоследовательности.
42. В массиве, заполненном наполовину, продублировать все элементы с сохранением порядка следования. (Например, задан массив Х = (3, 8, ...), получить массив Х = (3, 3, 8, 8, ...)).
43. Из массива удалить повторяющиеся элементы. Массив сжать.
44. Вычислить значения функции y = cosx + xsinx в n точках отрезка [a,b]. Результат получить в двух массивах х (аргумент), y (функция). Используя сформированные массивы, определить все значения аргумента, при которых функция имеет максимум или минимум (включая локальные). Для точек, в которых достигается экстремум, вывести значения аргумента, функции и вид эстремума (максимум или минимум).
Указание. Функция имеет локальный (или глобальный) максимум со значением yi (из массива Y) при значении аргумента xi (из массива Х), если значения функции yi1 и yi+1 в двух соседних точках меньше yi. Аналогично для минимума.
45. Из массива размера 12 удалить все отрицательные элементы. Массив сжать.
Матрица это двухмерный массив, который можно представить себе как совокупность строк (или совокупность столбцов). Положение элемента в массиве определяется двумя индексами: номером строки и номером столбца. Нумерация, как и для одномерных массивов, начинается с нуля. Объявление двухмерного массива выполняется аналогично объявлению одномерных массивов. Так, следующее объявление создает двухмерный массив (матрицу) целых чисел из четырех строк и двух столбцов. Элементам массива при этом автоматически присваивается значение ноль:
int[,] array = new int[4, 2];
Отображение значений и типа элементов массива в окне «Локальные» после выполнения кода:
Для массива из четырех строк (как в данном примере) строки нумеруются от 0 до 3. Аналогично для столбцов.
Массив можно инициализировать при объявлении, например,
int[,] array4 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
Отображение значений и типа элементов массива в окне «Локальные» после выполнения кода:
Доступ к элементу массива осуществляется указанием двух индексов. Например,
array4[2, 1] = 25;
В результате второму элементу третьей строки будет присвоено значение 25.
Каждая строка (и каждый столбец) матрицы представляет собой одномерный массив. Поэтому при обработке матриц в основном используются типовые алгоритмы обработки одномерных массивов.
При работе с матрицами, как правило, используются вложенные циклы, например в цикле по строкам необходимо получить доступ к каждому элементу строки, т.е. организовать цикл по столбцам.
Ввод матриц можно осуществлять поэлементно с использованием вложенных циклов. Элементы матрицы вводятся, как правило, по строкам. После ввода каждого элемента необходимо нажать клавишу [Enter]. Вывод матриц должен осуществляться в наглядной форме, т.е. каждая строка матрицы должна выводиться в новую строку экрана с использованием подходящего формата:
int[,] a = new int[3, 3];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
string s = Console.ReadLine();
a[i, j] = int.Parse(s);
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Типовые алгоритмы работы с матрицами
1. Просуммировать элементы строк матрицы a. Результат получить в виде вектора (одномерного массива) b:
int[,] a = new int[3, 3] { { 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 } };
int[] b = new int[3];
for (int i = 0; i < 3; i++)
{
int s = 0;
for (int j = 0; j < 3; j++)
s += a[i, j];
b[i] = s;
}
for (int i = 0; i < 3; i++)
Console.Write("{0:d} ", b[i]);
Console.WriteLine();
Console.ReadKey();
Аналогично осуществляется формирование одномерных массивов из значений максимальных (минимальных) элементов строк, индексов максимальных (минимальных) элементов строк и т.п. (см. соответствующие алгоритмы для одномерных массивов).
Если необходимо осуществить обработку столбцов матрицы, то внешний цикл необходимо организовать по номеру столбца, а внутренний по номеру строки. В остальном алгоритмы аналогичны.
2. Поменять местами ii-ю и jj-ю строки матрицы.
Поменять местами строки означает поменять местами каждую пару соответствующих элементов этих строк, т.е. необходим цикл по столбцам:
int[,] a = new int[3, 3] { { 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 } };
int p = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
Console.Write("{0:d} ", a[i,j]);
Console.WriteLine();
}
int ii = 1, jj = 2;
for (int k = 0; k < 3; k++)
{
p = a[ii, k]; a[ii, k] = a[jj, k]; a[jj, k] = p;
}
Console.WriteLine();
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Console.ReadKey();
Столбцы меняются местами аналогично (цикл организуется по строкам).
3. Удалить k-ю строку матрицы.
Чтобы удалить строку, необходимо переместить все строки, расположенные после k-й, на одну позицию вверх. Для перемещения строки нужно организовать цикл по элементам строки, т.е. по столбцам:
int[,] a = new int[3, 3] { { 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 } };
int n = 3, m = 3;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
n = n - 1;
int k = 1;
for (int i = k; k < n; k++)
for (int j = 0; j < m; j++)
a[i, j] = a[i + 1, j];
Console.WriteLine();
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Console.ReadKey();
Удаление столбца осуществляется аналогично (внутренний цикл по элементам столбца, т.е. по строкам).
4. Вставить новую строку, заданную вектором b[m], после k-й строки матрицы.
Вначале нужно освободить место для новой строки, переместив строки, расположенные после k-й, на одну позицию вниз. (При объявлении матрицы предусмотреть необходимость соответствующего увеличения ее размера.)
Перемещение строк нужно начинать с последней строки (аналогичный алгоритм для одномерных массивов в см. п. 3.1):
int[,] a = new int[4, 3];
int[] b = new int[3] { 7, 5, 7 };
int n = 3, m = 3;
Random r = new Random();
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
a[i, j] = r.Next(55);
Console.Write("{0:d2} ", a[i, j]);
}
Console.WriteLine();
}
Console.WriteLine();
for (int i = 0; i < m; i++)
{
Console.Write("{0:d2} ", b[i]);
}
Console.WriteLine();
Console.WriteLine();
int k = 1;
for (int i = n - 1; i >= k; i--)
for (int j = 0; j < m; j++)
a[i + 1, j] = a[i, j];
for (int j = 0; j < m; j++)
a[k, j] = b[j];
for (int i = 0; i < n + 1; i++)
{
for (int j = 0; j < m; j++)
Console.Write("{0:d2} ", a[i, j]);
Console.WriteLine();
}
Console.ReadKey();
Замечание. Здесь переменная r экземпляр класса Random, который представляет генератор псевдослучайных чисел. Метод Next(maxValue) создает случайное число в диапазоне значений от нуля до числа maxValue, указанного в качестве аргумента метода. r.Next(55) метод Next, определенный в классе Random и примененный к экземпляру класса r генерирует следующее псевдослучайное число.
Вставка столбца осуществляется аналогично.
5. Найти сумму элементов матрицы.
Здесь нужно обратиться к каждому элементу матрицы и добавить его к сумме:
int[,] a = new int[3, 3] { { 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 } };
int n = 3, m = 3;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
int s = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
s += a[i, j];
}
Console.WriteLine(s);
Аналогично осуществляется поиск максимального элемента во всей матрице.
Далее будут рассмотрены типовые алгоритмы для квадратной матрицы a размера nn.
6. Найти сумму элементов, расположенных на главной диагонали (след матрицы).
Элементы, расположенные на главной диагонали, имеют одинаковые индексы (номера строк и столбцов совпадают) и представляют, таким образом, одномерный массив:
int s = 0;
for (int i = 0; i < n; i++)
{
s += a[i, i];
}
Аналогично можно организовывать и другие алгоритмы для работы с диагональными элементами (нахождение максимального элемента и т.п.).
7. Найти сумму элементов, расположенных ниже главной диагонали (включая диагональ), т.е. просуммировать элементы нижнего треугольника матрицы.
Здесь во внешнем цикле (по строкам) номера строк изменяются от 0 до n 1, но в каждой i-й строке суммируются только элементы, расположенные до диагонального элемента этой строки, т.е. до i-го:
int s = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= i; j++)
s += a[i, j];
}
8. Транспонирование матрицы с получением результата в том же массиве.
Для квадратной матрицы размера n n требуется переставлять элементы, расположенные симметрично относительно главной диагонали:
int p = 0;
for (int i = 0; i < n - 1; i++)
{
for (int j = i + 1; j < n; j++)
{
p = a[i, j]; a[i, j] = a[j, i]; a[j, i] = p;
}
}
Для прямоугольной матрицы размера n m транспонированная матрица может быть получена на месте исходной, если последняя размещена в массиве размера не менее чем n n (предполагается, что n > m). При этом можно использовать приведенный выше алгоритм. Фиктивные столбцы, дополняющие исходную матрицу до квадратной, помещаются в этом случае в фиктивные строки транспонированной матрицы.
9. Умножение матрицы на вектор.
Требуется умножить матрицу а размера n m на вектор b размера m. Для этого необходимо вычислить
c[i] = , i = 0, ..., n 1 .
int[,] a = new int[3, 3] { { 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 } };
int[] b = new int[3] { 3, 4, 1 };
int[] c = new int[3];
int n = 3, m = 3;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
int s;
for (int i = 0; i < n; i++)
{
s = 0;
for (int j = 0; j < m; j++)
{
s = s + a[i, j] * b[j];
}
c[i] = s;
}
Console.WriteLine();
for (int i = 0; i < n; i++)
{
Console.Write("{0:d} ", c[i]);
}
Console.WriteLine();
Console.ReadKey();
10. Умножение матрицы на матрицу.
Требуется умножить матрицу а размера n k на матрицу b размером k m. Для этого необходимо вычислить
…,…, m 1.
int[,] a = new int[3, 3] { { 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 } };
int[,] b = new int[3, 3] { { 3, 4, 1 },
{ 1, 2, 4 },
{ 2, 4, 3 } };
int[,] c = new int[3, 3];
int n = 3, m = 3, k = 3;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < k; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Console.WriteLine();
for (int i = 0; i < k; i++)
{
for (int j = 0; j < m; j++)
Console.Write("{0:d} ", b[i, j]);
Console.WriteLine();
}
int s;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
s = 0;
for (int l = 0; l < k; l++)
{
s = s + a[i, l] * b[l, j];
}
c[i, j] = s;
}
}
Console.WriteLine();
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
Console.Write("{0:d} ", c[i, j]);
Console.WriteLine();
}
Console.WriteLine();
Console.ReadKey();
11. Определение номера k (нумерация начинается с 0) элемента a[i, j] матрицы размера n m (нумерация n и m начинается с 1), заданной в виде одномерного массива по строкам:
k =(i1)m + j 1;
12. Определение номера k (нумерация начинается с 0) элемента a[i, j] симметрической матрицы размера n n (нумерация n с 1), заданной своим верхним треугольником в одномерном массиве b размером (n+1)n/2:
k = (2n (i1) +1)(i 1)/2 + j (i 1) 1; i = 1, 2, ..., n; j = i, i + 1, ..., n.
Элементы нижнего треугольника определяются как
a[i, j] = a[j, i]; i = 2, ..., n; j = 1, ..., i 1.
Пример 3.5. В одномерном массиве b хранится по строкам верхний треугольник квадратной матрицы (элементы, расположенные выше главной диагонали, включая главную диагональ). Напечатать его по строкам. Восстановить исходную матрицу, заполнив ее нижний треугольник нулями. Напечатать по строкам:
const int n = 5;
int k = 0;
const int m = (n * (n + 1)) / 2;
int[] b = new int[m];
int[,] a = new int[n, n];
Console.WriteLine(
"Введите верхний треугольник матрицы по строкам");
for (int i = 0; i < m; i++)
{
b[i] = int.Parse(Console.ReadLine());
}
Console.WriteLine("Верхний треугольник по строкам");
for (int i = 0; i < n; i++)
{
for (int l = 0; l < i; l++) Console.Write(" ");
for (int j = i; j < n; j++)
{
Console.Write("{0:d} ", b[k]); k = k + 1;
}
Console.WriteLine();
}
Console.WriteLine();
//Формирование матрицы
k = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (j < i)
{
a[i, j] = 0;
}
else
{
a[i, j] = b[k]; k = k + 1;
}
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Console.WriteLine();
Console.ReadKey();
Замечание. В приведенном окне первые строки (с вводимыми элементами матрицы) обрезаны.
Пример 3.6. Просуммировать элементы квадратной матрицы размера n ´ n, расположенные в ее верхней четверти, ограниченной главной и побочной главной диагоналями, включая элементы, расположенные на диагоналях.
Номера строк в верхней половине матрицы изменяются от 0 до n/2. Индексы суммируемых элементов в строке изменяются от i до n i:
const int n = 5;
const int m = (n + 1) / 2;
int s = 0;
int[,] a = new int[n, n];
Console.WriteLine("Введите элементы матрицы");
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
a[i, j] = int.Parse(Console.ReadLine());
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Console.WriteLine();
for (int i = 0; i < m; i++)
{
for (int j = i; j < n - i; j++)
{
s += a[i, j];
}
}
Console.WriteLine("{0:d} ", s);
Console.ReadKey();
Замечание. В приведенном окне первые строки (с вводимыми элементами матрицы) обрезаны.
Пример 3.7. Для квадратной матрицы поменять местами минимальный и максимальный элементы главной диагонали, используя перестановку строк и столбцов.
const int n = 5;
int[,] a = new int[n, n];
Console.WriteLine("Введите элементы матрицы");
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
a[i, j] = int.Parse(Console.ReadLine());
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Console.WriteLine();
int amax = a[0, 0]; int imax = 0;
int amin = a[0, 0]; int imin = 0;
for (int i = 1; i < n; i++)
{
if (a[i, i] > amax)
{
amax = a[i, i]; imax = i;
}
if (a[i, i] < amin)
{
amin = a[i, i]; imin = i;
}
}
for (int j = 0; j < n; j++)
{
int p = a[imax, j];//Переменная p локальная.
//Она действует только в пределах данного цикла.
a[imax, j] = a[imin, j]; a[imin, j] = p;
}
for (int i = 0; i < n; i++)
{
int p = a[i, imax];
a[i, imax] = a[i, imin]; a[i, imin] = p;
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
Console.Write("{0:d} ", a[i, j]);
Console.WriteLine();
}
Console.ReadKey();
См. замечание к предыдущей программе.
Пример 3.8. Поменять местами максимальный элемент нижнего треугольника (включая главную диагональ) матрицы х размера 6 × 6 с максимальным элементом верхнего треугольника:
const int n = 6;
int[,] x = new int[n, n];
Console.WriteLine("Введите элементы матрицы");
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
x[i, j] = int.Parse(Console.ReadLine());
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
Console.Write("{0,5:d} ", x[i, j]);
Console.WriteLine();
}
Console.WriteLine();
int xd = x[0, 0], id = 0, jd = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= i; j++)
{
if (x[i, j] > xd)
{
xd = x[i, j]; id = i; jd = j;
}
}
}
int xu = x[0, 0], iu = 0, ju = 0;
for (int i = 0; i < n - 1; i++)
{
for (int j = i + 1; j < n; j++)
{
if (x[i, j] > xu)
{
xu = x[i, j]; iu = i; ju = j;
}
}
}
int t = x[id, jd]; x[id, jd] = x[iu, ju];
x[iu, ju] = t;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
Console.Write("{0,5:d} ", x[i, j]);
Console.WriteLine();
}
Console.ReadKey();
Пример 3.9. В матрице a размером 5 × 4, представленной в виде одномерного массива по строкам (размер одномерного массива равен 20), удалить строку, содержащую максимальный элемент во 2-м столбце (нумерация с 0):
const int n = 20;
int[] a = new int[n];
Console.WriteLine("Введите элементы матрицы");
for (int i = 0; i < n; i++)
{
a[i] = int.Parse(Console.ReadLine());
}
for (int i = 0; i < 20; i++)
{
Console.Write("{0,2:d} ", a[i]);
if ((i + 1) % 4 == 0) Console.WriteLine();
}
Console.WriteLine();
Console.WriteLine();
int amax = a[2], imax = 0;
for (int il = 0; il <= 4; il++)
{
if (a[il * 4 + 2] > amax)
{
amax = a[il * 4 + 2]; imax = il;
}
}
for (int il = imax; il < 4; il++)
{
for (int j = 0; j < 4; j++)
{
a[il * 4 + j] = a[(il + 1) * 4 + j];
}
}
for (int i = 0; i < 16; i++)
{
Console.Write("{0,2:d} ", a[i]);
if ((i + 1) % 4 == 0) Console.WriteLine();
}
Console.WriteLine();
Console.ReadKey();
Замечание. В листинге приведены только выводимые данные.
Вопросы для самопроверки
Задание для самостоятельного выполнения
Программу составить в двух вариантах, представляя матрицу: а) в виде двухмерного массива; б) в виде одномерной последовательности.
Классы и структуры являются двумя основными типами в C#. Классы и структуры представляют собой типы, создаваемые пользователем. Структура является типом значения. Класс является ссылочным типом (см. п. 1.1). Далее рассматривается работа со структурами и с классами (определение, создание объектов соответствующего типа, различные способы инициализации полей и т. п.). Для решения многих задач можно использовать как классы, так и структуры. Однако классы имеют более широкое применение. В частности, классы допускают наследование, что позволяет на базе одного (базового) класса без особых затрат создавать различные производные классы (см. п. 4.3).
Массивы состоят из элементов одного типа. В тех случаях, когда единообразно нужно обрабатывать наборы данных, представляющих совокупность величин различного типа, рассматривая их как единое целое, целесообразно использовать структуры.
Структуры определяются с помощью ключевого слова struct . Далее указывается имя структуры и в фигурных скобках определяются члены структуры. Структуры могут содержать произвольное число различных видов членов: полей, методов и др.
Классы и структуры являются двумя основными конструкциями системы общих типов. Каждая из них по сути является структурой данных, инкапсулирующей набор данных (поля) и поведение (методы). Данные и поведение являются членами класса или структуры. Их объединение в одном типе называется инкапсуляцией. Согласно принципу инкапсуляции, класс или структура может задать уровень доступности каждого из членов по отношению к коду вне класса или структуры. Уровень доступа public (открытый доступ) использован в нижеследующих примерах. Другие уровни доступа здесь не рассматриваются.
В качестве членов структур (в дальнейшем классов) в настоящем пособии будут использоваться только поля и методы.
Поле это переменная, объявленная в структуре. У поля есть имя и тип. Метод это функция, определенная в структуре.
Рассмотрим определение структуры, в которой содержатся два поля разных типов:
struct Sportsmen
{
public string famile;
public int rez;
}
Здесь описана структура с именем Sportsmen с двумя полями: famile типа string и rez типа int . Описание структуры располагается вне метода Main. В связи с этим уровень доступа к полям установлен максимальный (public открытый доступ), что дает возможность доступа к полям из метода Main.
Экземпляр структуры создается в методе Main как обычно указанием типа перед именем переменной:
Sportsmen temp;
Далее в поля этой переменной можно поместить значения (инициализировать поля структуры). Для доступа к полю нужно указать имя переменной и после точки имя поля. Например,
using System;
class Program
{
struct Sportsmen
{
public string famile;
public int rez;
}
static void Main()
{
Sportsmen temp;
temp.famile = "Иванов";
temp.rez = 77;
Console.WriteLine(
"Фам {0}\t Результат {1:f2}",
temp.famile, temp.rez);
Console.ReadKey();
}
}
Или
using System;
struct Sportsmen
{
public string famile;
public int rez;
}
class Program
{
static void Main()
{
Sportsmen temp;
temp.famile = "Иванов";
temp.rez = 7;
Console.WriteLine(
"Фам {0}\t Результат {1:f2}",
temp.famile, temp.rez);
Console.ReadKey();
}
}
Объявление массива структур. Например,
Sportsmen[] sp = new Sportsmen[5];
Здесь объявлен массив sp из пяти элементов, каждый из которых содержит два поля.
Использование структур делает представление данных более компактным и наглядным. Структуры можно пересылать одну в другую, если они идентичны. Возможен, например, оператор
sp[0] = temp;
Пример.
using System;
struct Sportsmen
{
public string famile;
public int rez;
}
class Program
{
static void Main()
{
Sportsmen temp;
temp.famile = "Иванов";
temp.rez = 17;
Sportsmen[] sp = new Sportsmen[5];
sp[0] = temp;
Console.WriteLine("Фам {0}\t Результат {1:f2}",
sp[0].famile, sp[0].rez);
Console.ReadKey();
}
}
Пример 4.1. Протокол соревнований по прыжкам в высоту содержит список фамилий и результатов (одна попытка) в порядке стартовых номеров. Получить итоговую таблицу, содержащую фамилии и результаты в порядке занятых мест. Количество спортсменов не более 30. Для размещения исходных данных используется массив структур. Структура содержит информацию фамилия и результат спортсмена. Массив структур является в данном случае одномерным массивом и для его обработки можно использовать типовые алгоритмы, рассмотренные в гл. 3:
using System;
struct Sportsmen
{
public string famile;
public double rez;
}
class Program
{
static void Main()
{
Sportsmen[] sp = new Sportsmen[5];
string[] s = new string[] {
"Иванов", "Петров", "Сидоров",
"Кузнецов", "Макаров" };
double[] r = new double[] { 1.50,
1.55, 1.47, 1.46, 1.54 };
for (int i = 0; i < sp.Length; i++)
{
sp[i].famile = s[i];
sp[i].rez = r[i];
Console.WriteLine(
"Фамилия {0} \t Результат {1:f2}",
sp[i].famile, sp[i].rez);
}
//Упорядочение по результатам
for (int i = 0; i < sp.Length - 1; i++)
{
double amax = sp[i].rez;
int imax = i;
for (int j = i + 1; j < sp.Length; j++)
{
if (sp[j].rez > amax)
{
amax = sp[j].rez;
imax = j;
}
}
Sportsmen temp;
temp = sp[imax];
sp[imax] = sp[i];
sp[i] = temp;
}
Console.WriteLine();
for (int i = 0; i < sp.Length; i++)
{
Console.WriteLine(
"Фамилия {0} \t Результат {1:f2}",
sp[i].famile, sp[i].rez);
}
Console.ReadKey();
}
}
Здесь исходные данные первоначально заданы в двух массивах: фамилии в массиве s, результаты в массиве r. Далее этими данными заполняются поля структуры.
В результат будет выведено:
Вопросы для самопроверки
1. Структура. Поля структуры. Члены структуры. Объявление структуры. Доступ к отдельным полям структуры.
2. Создание экземпляра структуры. Инициализация полей структуры.
3. Операции со структурами.
4. Преимущества использования структур.
5. Массивы структур и их обработка.
Задание для самостоятельного выполнения
Рассмотрим более подробно различные способы инициализации полей структуры, работы с отдельными экземплярами структуры, в частности, включение методов в описание структуры на примере структуры Sportsmen (см. пример 4.1).
В примере 4.1 экземпляр (объект) структуры объявляется как обычная переменная указанием типа перед именем. В этом случае поля структуры остаются без значений, и их нельзя использовать до инициализации всех полей. Например,
using System;
namespace ConsoleApplication1
{
struct Sportsmen
{
public string famile;
public int rez;
}
class Program
{
static void Main(string[] args)
{
Sportsmen sp;
sp.famile = "Иванов";
sp.rez = 5;
Console.WriteLine(
"Фамилия {0} Результат {1:d}",
sp.famile, sp.rez);
}
}
}
Попытка вывести значения полей сразу после объявления переменной (до задания значений полям) приведет к сообщению об ошибке.
Объект структуры может быть создан и другим способом: с использованием оператора new:
Sportsmen sp = new Sportsmen();
В этом случае при создания объекта вызывается соответствующий конструктор экземпляра, который выполняет инициализацию полей нулями. Значения полей будут следующими:
Вывод значений полей в этом случае не приведет к ошибке.
Конструктор экземпляра это метод с тем же именем, что и структура, вызываемый оператором new. Конструктор может иметь параметры, его можно описать при определении структуры и использовать для инициализации полей отдельных объектов структуры в более компактной и наглядной форме.
Рассмотрим пример 4.1 и в описание структуры включим еще один член: конструктор с параметрами.
{
public string famile;
public double rez;
public Sportsmen(string famile1, double rez1)
{
famile = famile1;
rez = rez1;
}
}
Теперь, если объявлен массив структур
Sportsmen[] sp = new Sportsmen[5];
то задание полей элементов этого массива можно с использованием конструктора выполнить следующим образом:
sp[0] = new Sportsmen("Иванов", 1.50);
sp[1] = new Sportsmen("Петров", 1.55);
sp[2] = new Sportsmen("Сидоров", 1.47);
sp[3] = new Sportsmen("Кузнецов", 1.46);
sp[4] = new Sportsmen("Макаров", 1.54);
При вызове конструктора оператором new на место его первого параметра передается соответствующая фамилия, которая присваивается полю famile, а на место второго параметра результат, который присваивается полю rez соответствующего экземпляра структуры (элементу массива структур).
Внутри конструктора могут выполняться и вычисления, необходимые для определения отдельных полей.
Пусть, например, каждый спортсмен выполняет две попытки (rez1, rez2) и окончательный результат определяется суммой двух попыток (rez = rez1 + rez2). Описание структуры в этом случае может быть следующим:
struct Sportsmen
{
public string famile;
public double rez1, rez2, rez;
public Sportsmen(string famile1,
double rezz1, double rezz2)
{
famile = famile1;
rez1 = rezz1;
rez2 = rezz2;
rez = rez1 + rez2;
}
}
Замечания.
1. Имена параметров конструктора не должны совпадать с именами полей структуры. Если они совпападают, то нужно использовать ключевое слово this. Например:
struct Sportsmen
{
public string famile;
public double rez1, rez2, rez;
public Sportsmen(string famile,
double rez1, double rez2)
{
this.famile = famile;
this.rez1 = rez1;
this.rez2 = rez2;
rez = rez1 + rez2;
}
}
2. В конструкторе должны быть определены все поля структуры одним из двух способов: либо присваиванием значения передаваемого конструктору аргумента, либо вычислением с использованием значений уже определенных полей.
Следующая программа реализует решение задачи примера 4.1 для случая двух попыток с использованием конструктора:
using System;
struct Sportsmen
{
public string famile;
public double rez1, rez2, rez;
public Sportsmen(string famile1,
double rezz1, double rezz2)
{
famile = famile1;
rez1 = rezz1;
rez2 = rezz2;
rez = rez1 + rez2;
}
}
class Program
{
static void Main(string[] args)
{
Sportsmen[] sp = new Sportsmen[5];
sp[0] = new Sportsmen("Иванов", 1.50, 1.52);
sp[1] = new Sportsmen("Петров", 1.55, 1.8);
sp[2] = new Sportsmen("Сидоров", 1.47, 1.5);
sp[3] = new Sportsmen("Кузнецов", 1.46, 1.43);
sp[4] = new Sportsmen("Макаров", 1.54, 1.44);
for (int i = 0; i < sp.Length; i++)
{
Console.WriteLine(
"Фамилия {0}\t Результат {1:f2}",
sp[i].famile, sp[i].rez);
}
Console.ReadKey();
}
}
Теперь при выполнении, например, оператора
sp[0] = new Sportsmen("Иванов", 1.50, 1.52);
в поле rez будет помещаться сумма 3,02.
Результат выполнения программы:
Помимо конструктора описание структуры может содержать и другие методы (см. гл. 5). Например, в описании структуры
struct Sportsmen
{
public string famile;
public double rez;
public double factor(int i)
{
return i * rez;
}
}
описан метод factor для умножнния результата rez на коэффициент i.
После объявления в методе Main объекта структуры этот метод может быть вызван указанием имени объекта структуры и после точки имени метода и его аргумента в круглых скобках:
Sportsmen sp;
sp.famile = "Иванов";
sp.rez = 5.2;
double rez1 = sp.factor(3);
Console.WriteLine(rez1);
Console.ReadKey();
В результате переменная rez1 получит значение 15,6.
В описании структуры может быть описан также статический метод. Статический метод не применяется к конкретному объекту структуры, а относится ко всей структуре, и доступ к нему осуществляется по имени структуры, а не экземпляра (объекта) структуры (см. гл. 5).
В качестве примера рассмотрим описанную выше структуру и добавим в ее описание статический метод для подсчета количества спортсменов. Этот метод будет прибавлять к общему количеству 1 после ввода данных для очередного спортсмена:
struct Sportsmen
{
public string famile;
public double rez;
public static int sportsmenCounter = 0;
public static int AddSportsmen()
{
return sportsmenCounter = sportsmenCounter + 1;
}
}
Если в методе Main имеются операторы
Sportsmen sp;
sp.famile = "Иванов";
sp.rez = 5.2;
Sportsmen.AddSportsmen();//Вызов статического метода
Console.WriteLine (Sportsmen.sportsmenCounter);
sp.famile = "Петров";
sp.rez = 7.4;
Sportsmen.AddSportsmen();
Console.WriteLine(Sportsmen.sportsmenCounter);
то в результате их выполнения первым оператором WriteLine будет выведено 1, а вторым 2.
Пример 4.2. Студенты одной группы (не более 25 человек) в сессию сдают четыре экзамена. Составить список студентов, средний балл которых по всем экзаменам не менее «4». Результаты вывести в виде таблицы с заголовком в порядке убывания среднего балла:
using System;
struct Struct1
{
public string famile;
public double[] x;
public double sred;
public Struct1(string famile1, double[] x1)
{
sred = 0;
famile = famile1;
x = x1;
for (int i = 0; i < 4; i++)
{
sred += x[i];
}
sred /= 4;// sred = sred/4
}
}
class Program
{
static void Main(string[] args)
{
Struct1[] cl = new Struct1[3];
cl[0] = new Struct1("Иванов",
new double[] { 3.0, 5.0, 2.0, 3.0 });
cl[1] = new Struct1("Петров",
new double[] { 5.0, 4.0, 5.0, 3.0 });
cl[2] = new Struct1("Сидоров",
new double[] { 5.0, 4.0, 5.0, 5.0 });
for (int i = 0; i < cl.Length; i++)
Console.WriteLine(
"Фамилия {0}\t Средний балл {1,4:f2}",
cl[i].famile, cl[i].sred);
for (int i = 0; i < cl.Length - 1; i++)
{
double amax = cl[i].sred;
int imax = i;
for (int j = i + 1; j < cl.Length; j++)
{
if (cl[j].sred > amax)
{
amax = cl[j].sred;
imax = j;
}
}
Struct1 temp;
temp = cl[imax];
cl[imax] = cl[i];
cl[i] = temp;
}
Console.WriteLine();
for (int i = 0; i < cl.Length; i++)
{
if (cl[i].sred >= 4)
Console.WriteLine(
"Фамилия {0}\t "
+ "Средний балл {1,4:f2}",
cl[i].famile, cl[i].sred);
}
}
}
Здесь членом структуры является конструктор с двумя параметрами, соответствующими двум полям структуры, которые будут заполняться при вызове конструктора для каждого объекта структуры. При этом значение третьего поля вычисляется в конструкторе с использованием значений элементов массива, являющегося вторым полем структуры.
Вопросы для самопроверки
1. Создание объекта структуры при помощи конструктора. Что такое конструктор экземпляра.
2. Особенности инициализации полей структуры при использовании конструктора экземпляра.
3. Различные способы задания значений полей структуры при использовании конструктора.
4. Возможность использования методов, как членов структуры.
Задания для самостоятельного выполнения
Выполнить задания п. 4.1 с использованием конструктора и различных вариантов объявления структуры (внутри класса, вне класса).
Класс представляет собой создаваемый пользователем тип. Класс является ссылочным типом.
Классы определяются с помощью ключевого слова class. Далее указывается имя класса и в фигурных скобках определяются члены класса. Классы могут содержать произвольное число различных видов членов: полей, методов и др.
Поле это переменная, объявленная в классе. У поля есть имя и тип. Метод это функция, определенная в классе.
Рассмотрим определение класса, в котором содержатся два поля разных типов:
class Sportsmen
{
public string famile;
public int rez;
}
Здесь описан класс с именем Sportsmen с двумя полями: famile типа string и rez типа int. Описание класса располагается вне метода Main. В связи с этим уровень доступа к полям установлен максимальный (public открытый доступ), что дает возможность доступа к полям из метода Main.
При создании экземпляра класса переменная, к которой назначается экземпляр, сохраняет только ссылку на память. Экземпляр класса создается в методе Main как обычно указанием типа перед именем переменной и обязательным использованием ключевого слова new:
Sportsmen temp = new Sportsmen();
Далее в поля этой переменной можно поместить значения (инициализировать поля класса). Для доступа к полю экземпляра класса нужно указать имя переменной и после точки имя поля. Например,
using System;
class Sportsmen
{
public string famile;
public int rez;
}
class Program
{
static void Main(string[] args)
{
Sportsmen temp = new Sportsmen();
temp.famile = "Иванов";
temp.rez = 75;
Console.WriteLine(
"{0}\t{1:f2}", temp.famile, temp.rez);
Console.ReadKey();
}
}
или
Объявление массива классов. Например,
Sportsmen[] sp = new Sportsmen[5];
Здесь объявлен массив sp из 5 элементов, каждый из которых содержит 2 поля.
Далее для каждого элемента массива необходимо выделить память:
for (int i = 0; i < 5; i++) sp[i] = new Sportsmen();
Использование классов так же, как использование структур, делает представление данных более компактным и наглядным.
Можно создать конструктор с параметрами для инициализации полей экземпляра класса:
using System;
class Sportsmen
{
public string famile;
public double rez;
public Sportsmen(string famile1, double rez1)
{
famile = famile1;
rez = rez1;
}
}
class Program
{
static void Main(string[] args)
{
Sportsmen[] sp = new Sportsmen[3] {
new Sportsmen("Иванов", 1.50),
new Sportsmen("Петров", 1.55),
new Sportsmen("Сидоров", 1.47)};
for (int i = 0; i < sp.Length; i++)
Console.WriteLine(
"Фам {0}\t Результат {1:f2}",
sp[i].famile, sp[i].rez);
}
}
В отличие от структур, классы поддерживают наследование, фундаментальную характеристику объектно-ориентированного программирования.
Класс, члены которого наследуются, называется базовым классом, а класс, который наследует эти члены, называется производным классом. При определении класса для наследования от другого класса, производный класс явно получает все члены базового класса, за исключением его конструкторов. Например,
using System;
class Sportsmen
{
public string famile;
public double rez;
}
class Sportsmen1 : Sportsmen
{
public string team;
}
class Program
{
static void Main(string[] args)
{
Sportsmen1 sp = new Sportsmen1();
sp.famile = "Иванов";
sp.rez = 77;
sp.team = "Спартак";
Console.WriteLine(
"Фам {0}\tКоманда {1}\tРезультат {2:f2}",
sp.famile, sp.team, sp.rez);
Console.ReadKey();
}
}
Здесь класс Sportsmen базовый, класс Sportsmen1 наследует ему (является производным). В производном классе могут быть добавлены члены (поля, методы).
В данном примере в классе Sportsmen1 определено еще одно поле team.
В результате будет выведено:
Наследование позволяет создавать новые классы, которые повторно используют, расширяют и изменяют поведение, определенное в других классах.
Вопросы для самопроверки
1. Что такое класс? К какому типу относится класс? Как определить класс?
2. Каковы основные члены класса?
3. Как создать экземпляр класса?
4. Инициализация полей класса.
5. Объявление массива классов. Особенности выделения памяти под массив классов.
6. Использование конструктора экземпляра при работе с классами.
7. Что такое наследование классов и как оно реализуется?
8. Укажите основные различия между классами и структурами.
Задания для самостоятельного выполнения
Выполнить задания п. 4.1 с использованием классов, используя наследование по указанию преподавателя.
Метод это последовательность инструкций (операторов) для решения какой-либо более или менее самостоятельной задачи (подзадачи), оформленная специальным образом. Методы в C# являются членами классов или структур. Первое знакомство с методами у вас уже состоялось при изучении структур. В этой главе методы будут рассмотрены более последовательно и подробно.
Рассмотрим простейший пример.
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int a = 2, c = 3;
Console.WriteLine(p(a, c));
Console.ReadKey();
}
static int p(int a1, int c1)
{
int s = a1 + c1;
return s;
}
}
}
В приведенной программе в классе Program определены методы Main и p. В методе p вычисляется сумма двух переменных целого типа. Метод возвращает одно значение s, которое вычисляется в этом методе. Если метод возвращает значение, то имя переменной, в которую помещается возвращаемое значение, указывается после ключевого слова return, присутствие которого в данном случае обязательно. Если метод возвращает значение, то необходимо указать тип возвращаемого значения, в данном примере int. Таким образом, заголовок метода p включает: ключевое слово static (значение которого обсудим позже), тип возвращаемого значения int, имя метода и в скобках параметры метода с указанием их типов. Все вместе эти элементы образуют подпись метода.
Вызов метода, возвращающего значение, осуществляется указанием имени метода и в скобках аргументов метода, которые заменяют параметры метода перед его выполнением. Обращение к методу записывается в том месте кода, где требуется получить значение, возвращаемое методом. В нашем примере обращение к методу p: p(a, c) записано в операторе вывода. Аргументы, указываемые при вызове метода, должны иметь тот же тип, что и параметры метода в описании метода, и должны получить значения к моменту обращения к методу.
C# имеет две разновидности типов: типы значений и ссылочные типы (см. п. 1.1). В приведенном примере переменные a и c являются экземплярами структуры int и относятся к типу значения.
Для входных параметров метода типа значений может быть использована передача по значению (использована в данном примере для передачи методу значений переменных а и с) или по ссылке (см. ниже). При передаче по значению для параметров метода создаются переменные, в которые пересылаются (копируются) соответствующие аргументы, и любые изменения параметра, выполняемые внутри метода, не влияют на исходные данные, хранящиеся в переменных вызывающего метода.
Так, в данном примере при обращении к методу p в ячейку для параметра a1 пересылается значение a, т.е. 2, в ячейку для параметра c1 значение c, т.е. 3, и переменная s получает значение 5, которое и выводится в окно экрана.
Рассмотрим второй вариант решения той же задачи:
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
int a = 2, c = 3, x;
ps(a, c, out x);
Console.WriteLine(x);
Console.ReadKey();
}
static void ps(int a1, int c1, out int s)
{
s = a1 + c1;
}
}
}
Те же вычисления выполняются в методе, не возвращающем значения. Если метод не возвращает значения, он имеет тип void. В списке параметров метода перечислены входные (a1, c1) и выходной (s) параметры. Ключевое слово out перед выходным параметром, имеющим тип значения, означает передачу параметра по ссылке, т.е. при обращении к методу на место выходного параметра передается адрес аргумента, фигурирующего в обращении к методу. В примере передается не значение переменной x, а адрес переменной x. Параметр s не является типом int; он является ссылкой на тип int, в данном случае ссылкой на переменную x. Поэтому после вызова метода значение переменной x изменяется.
Выходные параметры метода, имеющие тип значения, всегда должны передаваться по ссылке. Если использовать в этом примере передачу по значению, то результат вычисления суммы не будет передан в вызывающий метод и значение x не изменится. В нашем примере в этом случае будет вообще выдано сообщение об ошибке, так как переменная x не инициализирована.
Для передачи по ссылке можно использовать также ключевое слово ref, но в этом случае аргумент, передаваемый по ссылке, должен быть инициирован до обращения к методу (при использовании ключевого слова out это необязательно). Ниже приводится вариант программы с использованием ref:
using System;
class Program
{
static void Main()
{
int a = 2, c = 3, x = 0;
//x присвоено фиктивное значение 0
p(a, c, ref x);
Console.WriteLine(x);
Console.ReadKey();
}
static void p(int a1, int c1, ref int s)
{
s = a1 + c1;
}
}
Замечание. Взаимное расположение методов в пределах одного класса не имеет значения. Управление всегда вначале передается методу Main.
Пример 5.1. Разработать (определить) метод для решения квадратного уравнения Вызвать метод для решения уравнения 3,2 + 4,6t 5 = 0 и, если уравнение имеет решение, вывести на печать больший из корней. Если решения нет, вывести соответствующее сообщение.
Вариант 1. Метод root статический, методы root и Main в одном классе. Передача параметров с помощью ref:
using System;
class Program
{
static void root(double a, double b, double c,
ref double x1, ref double x2, ref bool l)
{
double d;
d = b * b - 4 * a * c;
if (d >= 0)
{
x1 = (-b + Math.Sqrt(d)) / (2 * a);
x2 = (-b - Math.Sqrt(d)) / (2 * a);
l = !l;
}
}
static void Main()
{
bool q = false;
double t1 = 0, t2 = 0, u = 0;
root(3.2, 4.6, -5.0, ref t1, ref t2, ref q);
if (q)
{
u = t1;
if (t2 > u) u = t2;
Console.WriteLine("{0} {1:f3}", q, u);
}
else
Console.WriteLine("решения нет");
Console.ReadKey();
}
}
Замечания.
1. Если два метода находятся в одном и том же классе, и один из них статический (метод Main статический по определению) и из него вызывается другой метод, то вызываемый метод должен быть статическим. Существует только одна копия статического метода. Ниже приводятся варианты (вариант 3 и вариант 4) решения этой же задачи, где использован нестатический метод (instance экземплярный). Нестатический метод связан с экземпляром типа. Из нестатического метода можно обращаться к экземплярным полям и методам, а также к статическим полям и методам.
2. В случае использования ref переменные должны быть инициализированы до вызова. Ниже приводится листинг кода (начальные и конечные строки обрезаны) с выведенными сообщениями об ошибках, вызванных отсутствием инициализации аргументов метода, передаваемых по ссылке.
В случае использования out можно не инициализировать переменные в основной программе, а инициализировать в вызываемом методе. В некоторых случаях инициализация необязательна (см. далее вариант 2). В данном случае инициализация обязательна, так как эти переменные используются в условном операторе. То же касается использования таких переменных в циклах, конструкторах и т.п.
Вариант 2. Метод root статический, методы root и Main в одном классе. Передача параметров с помощью out. В вариантах 2, 3 и 4 возможность отсутствия решения уравнения не учитывается.
using System;
class Program
{
static void root(double a, double b, double c,
out double x1, out double x2, out bool l)
{
double d;
l = false;
d = b * b - 4 * a * c;
x1 = 0;
x2 = 0;
if (d >= 0)
{
x1 = (-b + Math.Sqrt(d)) / (2 * a);
x2 = (-b - Math.Sqrt(d)) / (2 * a);
l = !l;
}
}
static void Main()
{
bool q;
double t1, t2, u = 0;
root(3.2, 4.6, -5.0, out t1, out t2, out q);
if (q)
{
u = t1;
if (t2 > u) u = t2;
Console.WriteLine("{0} {1:f3}", q, u);
}
else
Console.WriteLine("решения нет");
Console.ReadKey();
}
}
Вариант 3. Метод root не статический и метод Main (статический) в одном классе Program:
using System;
class Program
{
void root(double a, double b, double c,
ref double x1, ref double x2, ref bool l)
{
double d;
d = b * b - 4 * a * c;
if (d >= 0)
{
x1 = (-b + Math.Sqrt(d)) / (2 * a);
x2 = (-b - Math.Sqrt(d)) / (2 * a);
l = !l;
}
}
static void Main()
{
bool q = false;
double t1 = 0, t2 = 0, u = 0;
//создается экземпляр класса
Program pr = new Program();
// pr переменная типа Program
//метод root экземпляра pr класса Program
pr.root(3.2,4.6, -5.0, ref t1, ref t2, ref q);
if (q)
{
u = t1;
if (t2 > u) u = t2;
Console.WriteLine("{0} {1:f3}", q, u);
}
else
Console.WriteLine("решения нет");
Console.ReadKey();
}
}
Вариант 4. Методы root и Main в разных классах:
using System;
class test
{
public void root(double a, double b, double c,
ref double x1, ref double x2, ref bool l)
{
double d;
d = b * b - 4 * a * c;
if (d >= 0)
{
x1 = (-b + Math.Sqrt(d)) / (2 * a);
x2 = (-b - Math.Sqrt(d)) / (2 * a);
l = !l;
}
}
}
class Program
{
static void Main()
{
bool q = false;
double t1 = 0, t2 = 0, u = 0;
test pr = new test();
//pr экземпляр класса test
//вызывается метод root
//экземпляра pr класса test
pr.root(3.2, 4.6,-5.0, ref t1, ref t2, ref q);
if (q)
{
u = t1;
if (t2 > u) u = t2;
Console.WriteLine("{0} {1:f3}", q, u);
}
else
Console.WriteLine("решения нет");
Console.ReadKey();
}
}
В последнем варианте метод root имеет модификатор доступа public (открытый), обеспечивающий доступ к этому методу из другого класса.
Пример 5.2. Вычислить число сочетаний из n по m: C = n!/(m!(n m)!), (в программе cnm). Вычисление факториала оформить в виде метода, возвращающего значение.
Вариант 1. Методы Main и fact для вычисления факториала в разных классах.
using System;
class test
{
public int fact (int n)
{
int f = 1;
for (int i = 2; i <= n; i++)
{
f = f * i;
}
return f;
}
}
class Program
{
static void Main()
{
int n = 4, m = 3 ;
int cnm;
test pr = new test();
cnm = pr.fact(n)/(pr.fact(m)*pr.fact(n - m));
Console.WriteLine("{0}",cnm);
}
}
Вариант 2. Методы Main и fact для вычисления факториала в одном классе:
using System;
class Program
{
static int fact(int n)
{
int f = 1;
for (int i = 2; i <= n; i++)
{
f = f * i;
}
return f;
}
static void Main()
{
int n = 5, m = 3 ;
int cnm;
cnm = fact(n)/ (fact(m) * fact(n - m));
Console.WriteLine("{0}",cnm)
Console.ReadKey();
}
}
Массив является ссылочным типом и если массив является параметром метода, то передача его всегда осуществляется по ссылке, независимо от наличия ключевого слова ref. Поскольку массивы являются ссылочными типами, метод может изменять значения элементов. Наличие квадратных скобок после типа в подписи метода означает, что параметром является массив.
Пример 5.3. Максимальный элемент массива а размера 6 поменять местами с максимальным элементом массива b размера 8. Оформить метод для поиска максимального элемента одномерного массива. Результатом метода являются значение и индекс максимального элемента^
using System;
class Program
{
static void maxx(int[] x,
ref int xmax, ref int imax)
{
xmax = x[0];
imax = 0;
for (int i = 0; i < x.Length; i++)
{
if (x[i] >= xmax)
{
xmax = x[i];
imax = i;
}
}
}
static void Main()
{
int[] a = new int[]{ 1, 7, 3, 5, 6, 2 };
int[] b = new int[]{ 4, 2, 1, 8, 9, 3, 5, 6 };
for (int i = 0; i < a.Length; i++)
{
Console.Write("{0:d} ", a[i]);
}
Console.WriteLine();
for (int i = 0; i < b.Length; i++)
{
Console.Write("{0:d} ", b[i]);
}
Console.WriteLine();
Console.WriteLine();
int amax = 0, bmax = 0, iamax = 0, ibmax = 0;
maxx(a, ref amax, ref iamax);
maxx(b, ref bmax, ref ibmax);
a[iamax] = bmax; b[ibmax] = amax;
for (int i = 0; i < a.Length; i++)
{
Console.Write("{0:d} ", a[i]);
}
Console.WriteLine();
for (int i = 0; i < b.Length; i++)
{
Console.Write("{0:d} ", b[i]);
}
Console.WriteLine();
}
}
В методе maxx предусмотрена возможность обработки массивов разного размера. Для определения размера массива-аргумента, обрабатываемого в каждом конкретном случае, используется свойство массива Length.
В методе Main для указания верхней границы индексов в циклах также используется свойство Length, (хотя можно было бы использовать и различные константы):
Пример 5.4. Просуммировать элементы строки матрицы d размера n × m (программа реализована для n = 3, m = 4), содержащей максимальный элемент матрицы. Поиск максимального элемента матрицы осуществлять в методе.
Исходный массив d представим в виде одномерной последовательности с нумерацией индексов от 0 до n × m 1 (в данном примере n=3, m=4, n*m1 = 11). Возвращаемым значением метода является номер строки (нумерация с 0), содержащей максимальный элемент матрицы. После нахождения максимального элемента матрицы необходимо просуммировать следующие подряд m элементов, начиная с первого элемента в строке (это элемент с номером ns × m, где ns номер строки, содержащей максимальный элемент матрицы, строки нумеруются от 0 до n 1, m количество элементов в строке).
Результатом работы метода является одно значение номер строки, содержащей максимальный элемент матрицы. Возможны два варианта организации метода: получить результат как возвращаемое методом значение (вариант 1) или использовать для этого параметр метода (вариант 2). Далее приводятся оба варианта кода. В методе находится индекс максимального элемента в одномерной последовательности imax. Номер строки, содержащей этот элемент, определяется делением этого значения индекса на количество элементов в строке. Вспомним, что при делении двух целых чисел дробная часть отбрасывается.
Вариант 1. Метод возвращает значение:
using System;
class Program
{
const int m = 4;
static int maxx(int[] x)
{
int xmax = x[0];
int imax = 0;
for (int i = 0; i < x.Length; i++)
{
if (x[i] > xmax)
{
xmax = x[i];
imax = i;
}
}
return imax / m;
}
static void Main()
{
int[] d = new int[] { 1, 7, 3, 5, 6, 2, 9, 3,
8, 3, 6, 1 };
for (int i = 0; i < d.Length; i++)
{
Console.Write("{0:d} ", d[i]);
}
Console.WriteLine();
int s = 0;
int ns = maxx(d) * m;
for (int k = ns; k <= ns + m - 1; k++)
s = s + d[k];
Console.WriteLine(s);
Console.ReadKey();
}
}
Вариант 2. Для получения результата используется параметр метода:
using System;
class Program
{
const int m = 4;
static void maxx(int[] x, ref int di)
{
int xmax = x[0];
int imax = 0;
for (int i = 0; i < x.Length; i++)
{
if (x[i] > xmax)
{
xmax = x[i];
imax = i;
}
}
di = imax / m;
}
static void Main()
{
int[] d = new int[] { 1, 7, 3, 5, 6,
2, 9, 3, 8, 35, 6, 1 };
int di = 0;
for (int i = 0; i < d.Length; i++)
{
Console.Write("{0:d} ", d[i]);
}
Console.WriteLine();
int s = 0;
maxx(d, ref di);
int ns = di * m;
for (int k = ns; k <= ns + m - 1; k++)
s = s + d[k];
Console.WriteLine(s);
Console.ReadKey();
}
}
До сих пор в качестве параметров методов использовались простые переменные или массивы. Но параметром метода может быть и другой метод. В качестве наглядного примера рассмотрим следующую задачу. Пусть требуется вычислить две суммы
s1 =
s2 =
Вычисление суммы в методе оформим в общем виде, не конкретизируя зависимость члена суммы от его номера, т.е. оформим функцию для вычисления суммы
S = .
Функция f должна быть включена в список формальных параметров как переменная типа делегат, которая при каждом обращении должна быть заменена фактическим параметром, т.е. именем метода, описывающего вычисление конкретного члена суммы.
Делегат могут вызывать только такие методы, у которых тип возвращаемого значения и список параметров совпадают с соответствующими элементами объявления делегата.
Ключевое слово delegate используется для объявления ссылочного типа. Делегат это тип, который определяет подпись метода и который можно связать с любым методом с совместимой подписью.
Пример 5.5. В качестве примера приводится программа для решения рассмотренной выше задачи вычисления двух сумм:
using System;
class Program
{
delegate int fi(int i);
static int f1(int i)
{
return i * i;
}
static int f2(int i)
{
return i * i * i;
}
static int si(fi f, int n)
{
int s = 0;
for (int i = 0; i <= n; i++)
{
s = s + f(i);
}
return s;
}
static void Main()
{
int s1 = si(f1,12);
int s2 = si(f2,7);
Console.WriteLine("{0} {1}", s1, s2);
}
}
Следующие примеры демонстрируют использование метода в качестве параметра, а также использование перечисления для вычисления определенного интеграла двумя различными методами.
Пример 5.6. Оформить функцию для вычисления . Используя эту функцию, вычислить и . Для вычисления определенного интеграла использовать метод трапеций. Разделим отрезок [a, b] на n отрезков длиной h = (b a)/n. Формула трапеций для вычисления интеграла в этом случае имеет вид
где x1 = a + h; x2 = a + 2h, ....
Выберем n = 20 для вычисления s1 и n = 30 для вычисления s2:
using System;
class Program
{
delegate double fx(double i);
static double f1(double x)
{
return Math.Sin(x)*Math.Sin(x);
}
static double f2(double x)
{
return 1/Math.Sqrt(9 + x * x);
}
static double sw(fx f, double a, double b, int n)
{
double c = 0, x = a, h = (b - a)/n;
for (int i = 1; i < n; i++)
{
x += h; c += f(x);
}
return (2 * c + f(a) + f(b)) * h / 2;
}
static void Main()
{
double s1 = sw(f1, 0.0, Math.PI/2, 20);
double s2 = sw(f2, 0.0, 2.0, 30);
Console.WriteLine("{0:f4} {1:f4}", s1, s2);
}
}
Пример 5.7. Оформить функцию для вычисления . Используя эту функцию, вычислить и Для вычисления определенного интеграла использовать метод трапеций и метод Симпсона. Для выбора метода использовать перечисление (см. п. 1.6).
Разделим отрезок [a, b] на n отрезков (для метода Симпсона n должно быть четным) длиной h = (b a)/n. Формула трапеций для вычисления интеграла приведена выше.
Формула Симпсона имеет вид
где ci = (1)i+1 вычисляется как c = c.
Выберем n =20 для вычисления s1 и n =30 для вычисления s2:
using System;
class Program
{
//объявление перечисления
enum Method { method1, method2 };
delegate double fx(double i);
static double f1(double x)
{
return Math.Sin(x) * Math.Sin(x);
}
static double f2(double x)
{
return 1 / Math.Sqrt(9 + x * x);
}
static double sw(fx f, double a, double b, int n,
Method met)
{
double s = f(a) + f(b), x = a, h = (b - a) / n;
if (met == Method.method1)
{
for (int i = 1; i < n; i++)
{
x += h; s += 2 * f(x);
}
s = s * h / 2;
}
if (met == Method.method2)
{
double c = 1.0;
for (int i = 1; i < n; i++)
{
x += h; s += (3 + c) * f(x); c = -c;
}
s = s * h / 3;
}
return s;
}
static void Main()
{
double s1 = sw(f1, 0.0, Math.PI / 2, 20,
Method.method1);
double s11 = sw(f1, 0.0, Math.PI / 2, 20,
Method.method2);
double s2 = sw(f2, 0.0, 2.0, 30,
Method.method1);
double s22 = sw(f2, 0.0, 2.0, 30,
Method.method2);
Console.WriteLine(
"{0:f4} {1:f4} {2:f4} {3:f4}",
s1, s11, s2, s22);
Console.ReadKey();
}
}
Замечание.Среди параметров метода sw для вычисления интеграла имеется параметр met типа Method. При вызове метода в качестве аргумента указывается конкретный элемент списка перечисления, объявленного в начале кода, в соответствии с чем и выбирается способ вычисления интеграла в методе sw.
Вопросы для самопроверки
1. Что такое метод? Разновидности методов: метод, возвращающий значение и метод, не возвращающий значения. Особенности их оформления.
2. Что такое подпись метода?
3. Вызов метода. Способы передачи параметров: по значению, по ссылке. Правила согласования типов параметров метода и аргументов при обращении к нему.
4. Различные возможности взаимного расположения вызываемого и вызывающего методов: в одном классе, в разных классах.
5. Особенности вызова нестатического метода из статического метода.
6. Использование массивов в качестве параметров.
7. Методы как параметры: использование делегата для передачи метода как параметра другого метода.
Задание для самостоятельного выполнения
1. Определить, сколькими способами можно отобрать команду в составе пяти человек из восьми кандидатов; из 10 кандидатов; из 11 кандидатов. Использовать метод для подсчета количества способов отбора по формуле
.
2. Два треугольника заданы длинами своих сторон a, b и с. Определить треугольник с большей площадью, вычисляя площади треугольников по формуле Герона
,
где p = (a + b + c)/2.
3. Два велосипедиста одновременно начинают движение из одной точки. Первый начинает движение со скоростью 10 км/ч и равномерно увеличивает скорость на 1 км/ч. Второй начинает движение со скоростью 9 км/ч и равномерно увеличивает скорость на 1,6 км/ч. Определить:
а) какой спортсмен преодолеет большее расстояние через 1 ч,
через 4 ч;
б) когда второй спортсмен догонит первого.
Использовать метод для вычисление пути в зависимости от времени по формуле
S = vt + at2/2,
где v начальная скорость;
а ускорение.
4. Поменять местами максимальные элементы матриц А размера 5 × 6 и В размера 3 × 5. Поиск максимального элемента матрицы осуществить в методе.
5. В массивах А размера 9 и В размера 7 заменить максимальные элементы на среднее арифметическое значение элементов, расположенных после максимального, в том массиве, для которого максимальный элемент расположен дальше от конца массива. Поиск максимального элемента осуществить в методе.
6. В матрицах В размера 5 × 5 и С размера 6 × 6 удалить строку, содержащую максимальный элемент на диагонали. Поиск максимального элемента диагонали осуществить в методе.
7. Поменять местами строку матрицы А размера 5 × 5 и столбец матрицы В размера 5 × 5, содержащие максимальные элементы на диагоналях. Поиск максимального элемента на диагонали осуществить в методе.
8. Поменять местами строки матриц А размера 4 × 6 и В размера
6 × 6, содержащие максимальные элементы в 1-м столбце. Поиск максимального элемента в заданном столбце матрицы осуществить в методе.
9. Объединить массивы А размера 7 и В размера 8, предварительно удалив максимальные элементы этих массивов. Результат получить в массиве А. Удаление элемента массива с заданным индексом осуществить в методе.
10. В массив В размера 4 × 5 вставить после строки, содержащей максимальное количество положительных элементов, столбец массива С размера 5 × 6, содержащий максимальное количество положительных элементов. Определение количества положительных элементов в заданной строке (или столбце) матрицы осуществить в методе.
11. Упорядочить по возрастанию элементы массивов А размера 9 и В размера 11, расположенные после максимального элемента. Упорядочение части массива, начинающейся элементом с заданным индексом, осуществить в методе.
12. Даны матрицы А размера 6 × 5 и С размера 7 × 4. Объединить массивы, сформированные из сумм положительных элементов столбцов матриц А и С. Суммирование положительных элементов столбцов с получением результата в виде массива осуществить в методе.
13. Вычислить суммы
Вычисление суммы осуществлять в методе. Для вычисления члена суммы использовать делегата.
14. Японская радиокомпания провела опрос 250 радиослушателей по вопросам:
а) какое животное вы связываете с Японией и японцами?
б) какая черта характера присуща японцам больше всего?
в) какай неодушевленный предмет или понятие вы связываете с Японией?
Большинство опрошенных прислали ответы на все или часть вопросов. Составить программу получения первых пяти наиболее часто встречающихся ответов по каждому вопросу и доли (в %) каждого такого ответа. Предусмотреть необходимость сжатия столбца ответов в случае отсутствия ответов на некоторые вопросы. Обработку информации по каждому вопросу осуществлять в методе.
15. В двух заданных матрицах одинакового размера поменять строки, содержащие максимальное количество отрицательных элементов. Нахождение количества отрицательных элементов заданной строки матрицы осуществлять в методе. Определение номера строки, содержащей максимальное количество отрицательных элементов, осуществлять в методе.
Указание. К методу, возвращающему значение количества отрицательных элементов заданной строки матрицы, следует обращаться для каждой матрицы столько раз, сколько строк в матрице (в цикле по строкам). В результате должны быть сформированы одномерные массивы, содержащие количества отрицательных элементов в строках матриц. Обращение ко второму методу для каждого одномерного массива позволит определить индексы максимальных элементов этих массивов, т.е. номера строк матриц, которые и следует далее поменять местами.
Текстовые строки переменные типа string могут содержать любое количество символов. Каждый символ представлен в кодировке UNICODE, предполагающей представление одного символа в 2 байтах памяти. Работа с текстовыми строками обычно предполагает решение следующих задач: объединить две (или более) строки в одну, вырезать из строки фрагмент, найти в строке заданную подстроку и т.п.
Для текстовых строк определены операции конкатенации (объединение) строк. Эта операция может быть выполнена с помощью оператора «+» или метода String.Сoncat, сравнение строк: операторы равенства == и !=. Оператор [] служит для доступа (только для чтения) к отдельным символам объекта string. Например, объединение строк:
string str1="Катя";
string str2="Иванова";
string res = str1+ " "+ str2;
Console.WriteLine(res);
Строка res будет следующей: ”Катя Иванова”.
Этот же результат можно получить с помощью метода Concat:
string res1 = String.Concat(str1, " ",str2);
В списке аргументов метода Сoncat может быть не более четырех членов. В приведенном выше примере их три.
Текстовые строки имеют сходство с массивами в том смысле, что доступ к отдельным символам осуществляется по индексу (номеру позиции, нумерация начинается с нуля). Переход к следующему символу легко осуществляется изменением номера позиции на 1. Однако следует иметь в виду, что изменение однажды созданной строки не допускается. Чтобы внести требуемые изменения, нужно создать новую строку (см. пример 6.1) или использовать соответствующий метод.
Для работы со строками определены методы класса String.
1. Метод Substring(n, m) извлечение подстроки длиной m, начиная с n-го символа. Если m не указано, то выводится весь «хвост» строки, начиная с n-го символа. Например, применение метода Substring для строки res:
string str1 = "Катя";
string str2 = "Иванова";
string res = str1 + " " + str2;
Console.WriteLine(res);
string res2 = res.Substring(5, 7);
Console.WriteLine(res2);
2. Метод Insert(n, st) вставка подстроки st в исходную строку, начиная с n-ой позиции. При этом необходимо создать новую переменную, в которой вначале будет автоматически продублирована исходная строка, а затем выполнена необходимая операция. (Заметим, что в C# прямая модификация существующей строки невозможна.) Например,
string res3 = "Катя";
string res4 = res3.Insert(4, " Иванова");
Console.WriteLine(res4);
В результате в res4 будет ”Катя Иванова”.
3. Метод Replace(st, st1) замена подстроки st новой подстрокой st1 или замена какого-либо символа во всем тексте на другой символ. Например,
string str = "Катя Иванова";
string str1 = str.Replace("Катя","Екатерина");
В результате будет str1 = "Екатерина Иванова".
4. Метод Remove(n, m) удаление из строки фрагмента заданной длины m, начинающегося с заданной позиции n. Например,
string res = "Катя Иванова моя подруга";
string res1 = res.Remove(4, 8);
Console.WriteLine(res1);
Удаляются 8 символов, начиная с 4-го (пробел после слова ”Катя”). В результате будет res1 = ”Катя моя подруга”.
5. Метод ToString() получение строкового представления объекта числового типа. Внутри скобок может ничего не находиться или может находиться переменная (строка формата), в которой указан способ форматирования числа, сохраненного в строковом виде, при выводе на консоль. Например,
double a = 5.0;
string s = a.ToString("f3");
Console.WriteLine(s);
Console.WriteKey();
6. Метод Split (применается к экземпляру класса String, как и описанные выше методы) осуществляет разбор строки, т.е. позволяет выделить отдельные слова или другие сочетания символов, разделенные какими-либо разделителями, перечисляемыми в массиве символов типа char[], являющемся аргументом метода Split. Например,
string str = "собака, кот играют вместе";
string[] strarr = str.Split(new Char[] {' ',','});
foreach(string res in strarr)
{
Console.WriteLine(res);
}
Console.ReadKey();
Здесь формируется массив strarr из слов исходного текста, отделенных друг от друга пробелом или запятой. Список символов-разделителей помещается в массив и передается методу Split в качесте аргумента.
При выводе после первого слова выведена пустая строка, так как после первого разделителя сразу следует второй, и между ними ничего нет (пусто).
Каждый отдельный символ строки является значением типа char и может быть выделен в переменную типа char. Например, при выполнении оператора
char s = str[5];
для str из предыдущего примера s получит значение a.
Для работы с отдельными символами строки можно использовать методы структуры Char. Например, статический метод Char.IsDigit(s), где s переменная типа char (отдельный символ строки). Этот метод возвращает значение true, если s цифра и false, если s не цифра. Результат выполнения метода показывает, относится ли указанный символ Юникода к категории десятичных цифр или нет.
Пример 6.1. В исходном тексте одно слово от другого отделено одним пробелом. Сформировать текст, в котором одно слово от другого отделяется двумя пробелами:
using System;
class Program
{
static void Main()
{
string str1 =
"Введите верхний треугольник "
+ "матрицы по строкам";
string str2 = "";
for (int i = 0; i < str1.Length; i++)
{
if (str1[i] == ' ')
{
str2 = str2 + str1[i];
}
str2 = str2 + str1[i];
}
Console.WriteLine(str1);
Console.WriteLine(str2);
}
}
Пример 6.2. Выписать из текста слова, начинающиеся и заканчивающиеся на одну и ту же букву:
using System;
class Program
{
static void Main()
{
string str1 = "текст верхний треугольник"
+ "тееекст теекст треугольник ";
Console.WriteLine(str1);
Console.WriteLine();
int i = 0, j = 0;
while (i < str1.Length)
{
if (str1[i] == ' ')
{
if (str1[i - 1] == str1[j])
{
Console.WriteLine(
str1.Substring(j, i - j));
}
j = i + 1; i++;
}
else
{
i++;
}
}
}
}
Пример 6.3. Подсчитать, сколько слов в тексте начинается на букву т. Слова в тексте разделены пробелами:
using System;
class Program
{
static void Main()
{
string str1 = "текст верхний треугольник"
+ "теекст треугольник верхний";
Console.WriteLine(str1);
Console.WriteLine();
string[] masstr = str1.Split(' ');
int k = 0;
for (int i = 0; i < masstr.Length; i++)
{
if (masstr[i][0] == 'т') k++;
}
Console.WriteLine(k);
Console.ReadKey();
}
}
Пример 6.4. Определить, сколько в заданном тексте отдельно цифр (Digit), букв (Letter), заглавных букв (Upper) и разделителей (Separator):
using System;
class Program
{
static void Main()
{
string str = "g E r t R 1 2 d K";
Console.WriteLine(str);
Console.WriteLine();
int k = 0, l = 0, m = 0, j = 0;
for (int i = 0; i < str.Length; i++)
{
if (Char.IsDigit(str[i])) k++;
if (Char.IsLetter(str[i])) l++;
if (Char.IsUpper(str[i])) m++;
if (Char.IsSeparator(str[i])) j++;
}
Console.WriteLine(
"цифры {0} буквы {1} заглавные буквы "
+"{2} разделитель {3}", k, l, m, j);
}
}
Вопросы для самопроверки
1. Как задать текстовую строку?
2. Какие операции определены для текстовых строк?
3. Как получить доступ к отдельным символам строки?
4. Методы, определенные для символьных строк.
5. Методы, определенные для отдельных символов.
6. Как можно изменить строку в процессе выполнения программы?
Задание для самостоятельного выполнения
1. Задан текст, содержащий не более 255 символов. Определить частоту, с которой встречаются в тексте различные буквы русского алфавита (в долях от общего количества букв).
2. Ученики зашифровывают свои записки, записывая все слова наоборот. Составить программу, зашифровывающую и расшифровывающую сообщение.
3. Разбить исходный текст на строки длиной не более 50 символов. Перенос на новую строку осуществлять на месте пробела (слова не переносить).
4. Назовем сложностью предложения сумму количества слов и знаков препинания. Определить сложность заданного предложения.
5. Задан текст символов. Напечатать буквы, на которые начинаются слова в тексте, в порядке убывания частоты их употребления.
6. Определить, сколько слов в тексте содержит один слог, два слога, три слога и т.д.
7. Задан текст символов. Выписать все слова, включающие заданную последовательность букв (например, выписать однокоренные слова).
8. Разделить заданный текст (не более 1000 символов) на строки, содержащие не более 50 символов. (Перенос осуществлять на месте пробела). Добавить равномерно пробелы, чтобы каждая строка содержала ровно 50 символов.
9. Для хранения текста в сжатом виде найти часто повторяющиеся последовательности из двух букв и заменить их кодом. В качестве кода использовать символы, не встречающиеся в тексте. Составить также таблицу кодов.
10. В предыдущей задаче декодировать текст, хранящийся в сжатом виде, используя заданную таблицу кодов.
11. Список фамилий, разделенных запятыми, задан в произвольном порядке. Упорядочить его по алфавиту.
12. Считая, что в памяти компьютера хранится таблица кодов часто встречающихся слов, ввести текст в массив, заменяя слова кодами после ввода. Распечатать текст в исходном виде, т.е. заменяя коды словами.
13. Определить долю в процентах слов, начинающихся на различные буквы. Выписать эти буквы и доли начинающихся на них слов.
14. Текст содержит слова и целые числа от 1 до 10. Найти сумму включенных в текст чисел.
15. Текст содержит слова и целые числа произвольного порядка. Найти сумму включенных в текст чисел.
Файл данных это совокупность (последовательность) компонент, имеющая имя, расположенная на внешнем носителе. Файлы могут быть объединены в каталоги (директории, папки), также имеющие имя. Использование файлов данных позволяет хранить данные на внешнем носителе, обрабатывая при необходимости порциями (например, при больших объемах данных), многократно использовать один и тот же набор данных (например, при отладке), а также использовать результаты выполнения одной программы (формируя из них файл) как входные данные при выполнении другой программы и т.п.
Для существующего файла данных, который хранится на внешнем носителе и имеет имя, при обращении к файлу создается поток с целью сохранения данных в резервном хранилище. Резервное хранилище это устройство хранения информации, например, диск или память.
Поток это абстракция последовательности байтов, например файл или другое устройство, предоставляющее данные. Класс Stream (поток) и его производные классы предоставляют универсальное представление различных типов ввода и вывода, избавляя программиста от необходимости знания отдельных сведений операционной системы и базовых устройств.
Потоки включают три основные операции:
1) чтение из потока перенос информации из потока в структуру данных, такую как массив байтов;
2) запись в поток передача данных из структуры данных в поток;
3) потоки также могут поддерживать поиск.
Программы, составленные на языке C#, работают с каталогами, файлами и потоками при помощи специально предназначенных для этого классов, входящих в состав библиотеки классов Microsoft.NET Framework и содержащихся в пространстве имен System.IO, которое необходимо подключить, чтобы обеспечить доступ к классам, определенным для потоков ввода-вывода (см. пример 7.1).
Для работы с директориями и файлами предназначены следующие основные классы:
• Directory предоставляет статические методы операций создания, перемещения и перечисления в директориях и поддиректориях. Класс DirectoryInfo предоставляет методы экземпляра;
• File предоставляет статические методы для создания, копирования, удаления, перемещения и открытия файлов, а также помогает при создании объектов FileStream;
• класс FileInfo предоставляет методы экземпляра.
Для работы с потоками предназначены следующие основные классы:
• FileStream предоставляет поток в файле, поддерживая операции чтения и записи;
• Класс StreamReader считывает символы из потоков с учетом кодировки;
• класс StreamWriter записывает символы в потоки, используя кодировку для преобразования символов в байты (см. пример 7.1).
Стандартные потоки связаны, как правило, с консолью и клавиатурой. Для вывода данных в стандартный поток вывода и для ввода из стандартного потока ввода используются методы класса Console: Console.ReadLine, Console.Write и Console.WriteLine. Эти методы использовались до сих пор во всех примерах программ.
Количество классов, предназначенных для работы с файлами, достаточно велико. Здесь будут рассмотрены только классы, предназначенные для чтения из текстового файла или записи в текстовый файл: StreamReader и StreamWriter.
Для ввода из файла (созданного заранее в текстовом редакторе) необходимо вначале открыть поток класса StreamReader, связав его с файлом. В приведенном ниже примере файл, из которого предполагается считывать данные, расположен по адресу С:\st\Koord.txt (это полный путь к файлу). Открытие потока и его привязка к файлу осуществляются с помощью конструктора (возможны и другие способы, которые здесь не рассматриваются):
StreamReader sr = new StreamReader(path);
Здесь sr экземпляр класса StreamReader, а аргумент path передает конструктору строку, содержащую полный путь к файлу (в качестве аргумента можно использовать и константу, содержащую полный адрес файла). Далее строки из файла (в программе это поток sr) по очереди считываются в переменную line, из которой далее, как обычно, извлекаются отдельные значения.
После окончания работы с объявленным потоком, его следует закрыть методом Close
sr.Close();
Пример 7.1. Координаты произвольного количества точек на плоскости размещены в файле Koord.txt на диске С в папке (директории) st по два числа (значения x и y) в строке. В первой строке файла размещено одно число радиус окружности r. Требуется определить, сколько точек попадет в круг радиуса r:
using System;
using System.IO;
class Program
{
static void Main()
{
string path = "c:\\st\\Koord.txt";
StreamReader sr = new StreamReader(path);
int n = 0;
string line;
line = sr.ReadLine();
int r = int.Parse(line);
Console.WriteLine("Радиус {0}", r);
while ((line = sr.ReadLine()) != null)
{
string[] koord = line.Split(' ');
int x = int.Parse(koord[0]);
int y = int.Parse(koord[1]);
Console.WriteLine(
"Координаты точек x = {0} y = {1}",
x, y);
if (x * x + y * y < r * r) n = n + 1;
}
sr.Close();
Console.WriteLine(
"{0} точки попадут в круг", n);
}
}
Замечание. В программе в адресе файла вместо одной наклонной черты используются две. Одна наклонная черта в строке могла бы восприниматься как первый символ управляющей последовательности. Использование двух наклонных позволяет избежать этой двусмысленности. Теперь наклонная черта будет восприниматься как символ строки, а не как управляющая последовательность (см. п. 1.2). В C# предусмотрен также способ объявления строки, в которой все символы между кавычками трактуются как часть строки. Это специальное объявление буквальные строки задается указанием символа @ перед всей строкой и обычно используется для задания пути к файлу. С использованием этого объявления задание строки path может выглядеть так:
string path = @"c:\st\Koord.txt";
При выводе в файл необходимо выполнить аналогичные действия: открыть поток класса StreamWriter, задав имя потока и связав его с файлом, предназначенным для размещения выводимых результатов, вывести в этот поток (т.е. в указанный файл) необходимые результаты и закрыть поток оператором Close().
Если в примере 7.1 предполагается также вывод результата в файл, то необходимо добавить следующие инструкции:
string path1 = "c:\\st\\Koord1.txt";
StreamWriter sw = new StreamWriter(path1);
sw.WriteLine(n);
sw.Close();
Далее рассмотрим пример, в котором осуществляется ввод из файла исходных данных и вывод результатов в файл с учетом регионального стандарта (см. прил. 3).
Пример. 7.2. Протокол соревнований по прыжкам в высоту содержит список фамилий и результатов (одна попытка) в порядке стартовых номеров. Получить итоговую таблицу, содержащую фамилии и результаты в порядке занятых мест. Количество спортсменов не более 30. Для размещения исходных данных используется массив структур. Структура содержит информацию фамилия и результат спортсмена. Ввод данных осуществлять из заранее подготовленного файла, вывод итоговой таблицы осуществлять в файл. (В примере 4.1 эта же задача решена без использования файлов данных.)
Исходный файл
using System;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
struct Sportsmen
{
public string famile;
public double rez;
}
class Program
{
static void Main(string[] args)
{
Sportsmen[] sp = new Sportsmen[5];
string line;
string path = "c:\\st\\Sportsmen.txt";
//кодовая страница операционной системы
//Windows для кирилицы имеет идентификатор
//1251
StreamReader sr = new StreamReader(
path,Encoding.GetEncoding(1251));
int i = 0;
while ((line = sr.ReadLine()) != null)
{
string[] sports = line.Split(' ');
sp[i].famile = sports[0];
sp[i].rez = double.Parse(sports[1]);
Console.WriteLine(
"Фамилия {0}\t Результат {1:f2}",
sp[i].famile,sp[i].rez);
i++;
}
sr.Close();
for (i = 0; i < sp.Length - 1; i++)
{
double amax = sp[i].rez;
int imax = i;
for (int j = i + 1;j < sp.Length;j++)
{
if (sp[j].rez > amax)
{
amax = sp[j].rez;
imax = j;
}
}
Sportsmen temp;
temp = sp[imax];
sp[imax] = sp[i];
sp[i] = temp;
}
Console.WriteLine();
for (i = 0; i < sp.Length; i++)
{
Console.WriteLine(
"Фамилия {0}\t Результат {1:f2}",
sp[i].famile,sp[i].rez);
}
string path1 = "c:\\st\\Sportsmen1.txt";
StreamWriter sw = new StreamWriter(path1);
for (i = 0; i < sp.Length; i++)
{
sw.WriteLine("{0} {1:f2}",
sp[i].famile, sp[i].rez);
}
sw.Close();
}
}
}
Файл с результатами:
Замечание. Дополнительно о региональных стандартах см. в прил. 3.
Вопросы для самопроверки
1. Что такое файл, директория, поддиректория?
2. В чем преимущества использования файлов для ввода и вывода?
3. Что такое поток? Какие операции определены для потока?
4. Открытие потока для чтения и его привязка к файлу.
5. Что такое «полный путь к файлу»?
6. Считывание из файла в переменные программы.
7. Открытие потока для вывода и привязка его к файлу, предназначенному для вывода результатов.
8. Закрытие потоков.
Задания для самостоятельного выполнения
Выполнить задания п. 4.1 с вводом исходных данных из файла и формированием файлов с результатами выполнения программы.
При создании программы вместо стандартного окна для ввода и вывода можно использовать экранные формы, создаваемые специально для конкретной программы. Это позволяет обеспечить ввод данных и вывод результатов в том виде, который требуется в данной задаче, что создает необходимую гибкость и удобство в работе. Элементы управления, помещаемые на форму, обеспечивают возможность вызова метода, связанного программно с этим элементом, в любой удобный пользователю момент, и делает выполнение программы более наглядным.
Далее на наглядных примерах рассматриваются основные возможности, предоставляемые при использовании экранных форм, и их реализация.
• Запустите Visual C#. В меню «Файл» выберите команду «Создать проект».
• Выберите шаблон «Приложение Windows Forms» (в поле Имя можно ввести любое имя проекта вместо стандартного) и нажмите кнопку ОК.
• Откроется конструктор Windows Forms с формой Windows. Это пользовательский интерфейс для создаваемого приложения:
• В меню «Вид» выберите команду «Панель элементов», чтобы открыть список элементов управления.
• Разверните список «Стандартные элементы» управления и перетащите два элемента управления TextBox на форму:
Дважды щелкните на форму Windows (Form1), чтобы открыть редактор кода. Visual C# вставил метод с именем Form1_Load, который выполняется при загрузке формы, обработчик события Load, связанный с запуском приложения. Откроется редактор кода, при этом положение курсора окажется внутри обработчика событий. Обработчик событий это метод, определяющий действия, которые требуется выполнить при возникновении события. События позволяют классу или объекту уведомлять другие классы или объекты о возникновении каких-либо ситуаций.
Замечание. При двойном щелчке на форму подпись метода и его содержимое (пустые кавычки { })
private void Form1_Load(object sender, EventArgs e)
{
}
генерируются автоматически. Одновременно автоматически генерируется код для вызова метода, который помещается в файл Form1.Designer.cs. Поэтому, если попытаться просто набрать код самостоятельно(без щелчка мышью), это не даст нужного эффекта (код для вызова метода не будет сгенерирован) и приведет к ошибке.
Работа с элементом управления TextBox
Текстовые поля форм Windows Forms используются для приема данных, вводимых пользователем, или для отображения текста. В текстовых полях можно выводить несколько строк текста, размещать текст в соответствии с размером элемента управления и применять основные элементы форматирования. Для вывода числовых данных в текстовое окно необходимо получить их строковое представление. После ввода числовых данных в текстовое окно необходимо использовать метод Parse для получения числового значения из строкового представления.
Пример 8.1
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
int b = 2;
textBox1.Text = b.ToString();
int a = int.Parse(textBox1.Text);
a += 2;
textBox2.Text = a.ToString();
}
}
}
TextBox это класс пространства имен: System.Windows.Forms. Экземпляр класса textBox1 создается при добавлении элемента управления на форму. Здесь Text это свойство, которое имеет тип string и возвращает или задает текст.
В первое текстовое поле выведено значение переменной b. Обратите внимание на то, что перед выводом числа в окно оно должно быть преобразовано в строковую форму. Далее числовое значение, представленное в этом текстовом поле в строковой форме, преобразуется в числовую форму (при помощи метода Parse) и присваивается переменной a. Далее значение a изменяется (увеличивается на 2) и выводится во второе текстовое поле, будучи перед этим преобразовано в строковую форму при помощи метода ToString().
В данном примере можно поместить значение 2 непосредственно в текстовое поле, используя оператор
textBox1.Text = «2»;
(число 2 представлено как строковая константа).
Далее это значение можно использовать в вычислениях, преобразовав его к числовой форме, как в рассмотренном выше примере:
Если пользователю необходимо вводить исходные данные в элемент управления формы «Текстовое поле», то необходимо изменить пример и перенести код из обработчика события формы Load в обработчик события Click кнопки (см. пример 8.3).
Работа с элементом управления Button
Button класс пространства имен System.Windows.Forms, представляет элемент управления Windows «Кнопка».
1. В меню «Вид» выберите команду «Панель элементов», чтобы открыть список элементов управления.
2. Разверните список «Стандартные элементы управления» и перетащите элемент управления Button на форму.
При добавлении кнопки на форму автоматически создается экземпляр класса Button с именем button1. Можно открыть файл Form1.Designer.cs и посмотреть соответствующий код:
3. Дважды щелкните на кнопку, чтобы открыть редактор кода. Visual C# вставил метод с именем button1_Click (обработчик события Click), который будет выполняться при нажатии на кнопку.
Пример 8.2. Изменение цвета кнопки после нажатия на нее:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
textBox1.Text = "2";
int a = int.Parse(textBox1.Text);
a += 2;
textBox2.Text = a.ToString();
}
private void button1_Click(
object sender, EventArgs e)
{
button1.BackColor = Color.Blue;
}
}
}
Изменить свойства элемента управления можно как программно, так и с использованием интегрированной среды разработки. В последнем случае необходимо перейти из окна редактора кода в конструктор Windows Forms с формой Windows и в меню «Вид» выбрать команду «Окно свойств»:
Выделить кнопку, нажав на нее мышью и в окне «Свойства» выбрать необходимое свойство и назначить ему нужное значение. Например, изменить фоновый цвет кнопки:
В примере 8.1 демонстрировалась возможность размещения в текстовом окне констант. В следующем примере рассматривается возможность ввода данных в текстовое окно. Для этого необходимо перенести код из обработчика события формы Load в обработчик события Click кнопки.
Пример 8.3
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
int a = int.Parse(textBox1.Text);
a += 2;
textBox2.Text = a.ToString();
}
}
}
Щелчок мышью по кнопке button1 является событием, которое вызывает выполнение метода button1_Click, соответствующего этому событию. При этом текст, введенный в текстовое поле textBox1, преобразуется в целое число и присваивается переменной a, значение которой далее увеличивается на 2 и выводится в текстовое поле textBox2.
Работа с элементом управления RichTextBox
Элемент управления Windows Forms RichTextBox используется для отображения, ввода и изменения текста (если необходимо, с форматированием). Методы этого класса предоставляют возможности, схожие с возможностями текстовых редакторов, например, таких как Microsoft Word. По сравнению с классом TextBox он обладает более широкими возможностями. В частности, позволяет считывать данные из файла, а также выводить в файл.
Откройте окно конструктора Windows Forms с формой Windows.
1. В меню «Вид» выберите команду «Панель элементов», чтобы открыть список элементов управления.
2. Разверните список «Стандартные элементы управления» и перетащите два элемента управления RichTextBox и кнопку на форму.
3. Дважды щелкните на кнопку, чтобы Visual C# вставил метод с именем button1_Click и дважды щелкните на форму Windows (Form1), чтобы Visual C# вставил метод с именем Form1_Load.
Пример 8.4. Вычислить сумму r = a + b + c, используя элемент управления RichTextBox. Значение a поместим в первую строку, два других, разделенных пробелом, во вторую строку RichTextBox1. Результат поместим RichTextBox2:
namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(
object sender, EventArgs e)
{
}
private void button1_Click(
object sender, EventArgs e)
{
int r;
string text = richTextBox1.Text;
string[] s = text.Split(' ', '\n');
int a = int.Parse(s[0]);
int b = int.Parse(s[1]);
int c = int.Parse(s[2]);
r = a + b + c;
richTextBox2.AppendText("Результат"+"\n");
richTextBox2.AppendText(r.ToString());
}
}
}
Метод Split осуществляет разбор текста, введенного в RichTextBox1, выделяя в массив s последовательности символов, разделенных пробелом или символом перевода строки (см. гл. 6), преобразуя их далее в переменные типа int. Для вывода результата в RichTextBox2 полученное числовое значение должно быть преобразовано в текстовую форму (метод ToString) и при помощи метода AppendText добавлено в richTextBox2.
Пример 8.5. Координаты произвольного количества точек на плоскости размещены в файле Koord.txt, сохраненном на диске С в папке (директории) st по два числа (x и y) в строке. В первой строке файла размещено одно число радиус окружности r. Требуется определить, сколько точек попадет в круг радиусом r (см. пример 6.1):
using System;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(
object sender, EventArgs e)
{
string path = "c:\\st \\Koord.txt";
//метод читает данные из файла в элемент
//управления
richTextBox1.LoadFile(path,
RichTextBoxStreamType.PlainText);
}
private void button1_Click(
object sender, EventArgs e)
{
string text = richTextBox1.Text;
string[] s = text.Split(' ', '\n');
int r = int.Parse(s[0]);
int n = 0;
richTextBox2.AppendText(
"Координаты точек" + "\n");
for (int i = 1; i < 6; i += 2)
{
int x = int.Parse(s[i]);
int y = int.Parse(s[i + 1]);
richTextBox2.AppendText(
x.ToString() + " " + y.ToString()
+ "\n");
if (x * x + y * y < r * r) n = n + 1;
}
richTextBox2.AppendText(
n.ToString()
+ " точек попадет в круг радиуса "
+ r.ToString() + "\n");
}
}
}
Создание объекта Graphics пространства имен System.Drawing для рисования
Класс Graphics является основой интерфейса GDI+ (специальная библиотека). Этот класс непосредственно выполняет рисование прямых и кривых линий, геометрических фигур, вывод рисунков и текста.
Перед тем как рисовать линии и фигуры, отображать текст, выводить изображения и управлять ими, необходимо создать объект класса Graphics. Объект Graphics представляет поверхность рисования и является объектом, который используется для создания графических изображений.
Откройте окно конструктора Windows Forms с формой Windows. Выделите форму. В окне «Свойства» нажмите значок событие :
Выберите из списка событие Paint и дважды нажмите мышью строку списка:
Visual C# вставил метод с именем Form1_Paint, который выполняется при перерисовке элемента управления формы. Далее необходимо получить ссылку на объект Graphics из объекта PaintEventArgs в событии Paint:
1) объявите объект Graphics;
2) присвойте переменной ссылку на объект Graphics, передаваемый как часть PaintEventArgs (PaintEventArgs класс, предоставляющий данные для события Paint);
3) вставьте код для рисования формы или элемента управления.
Пример 8.5. Рисование линии на форму:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(
object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawLine(new Pen(Color.Blue, 2.0f), 100,
100, 200, 200);
}
}
}
Замечания
1. При создании экземпляра g класса Graphics ему передается ссылка на форму, куда нужно выводить рисунок (график). Информация о форме содержится в свойстве Graphics экземпляра e класса PaintEventArg.
2. Метод DrawLine предназначен для вывода линии, Pen класс, предоставляющий перо для вычерчивания линии. Первый аргумент задает цвет линии, второй ее толщину, выраженную числом типа float. Остальные параметры задают координаты начала и конца линии.
При рисовании графика необходимо иметь в виду, что координата (0, 0) формы находится в верхнем левом углу. Перенести точку (0, 0) начала координат можно используя метод TranslateTransform, указав в качестве аргументов величину сдвига вдоль осей X и Y. Ось Y направлена вниз. Поэтому необходимо отразить график относительно оси Y, поставить перед координатой y знак минус.
Пример 8.6. Построить по точкам график функции y = x2 при
x = 10, 9, 8,… ,10:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(
object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
// this в данном случае форма
int xc = this.Width / 2;
int yc = this.Height / 2;
g.TranslateTransform(xc, yc);
g.DrawEllipse(new Pen(Color.Red, 8.0f),
0, 0, 1, 1);
int x, y;
//вычерчивание осей координат
g.DrawLine(new Pen(Color.Brown, 1.0f),
-200, 0, 200, 0);
g.DrawLine(new Pen(Color.Brown, 1.0f),
0, -200, 0, 200);
//Вычерчивание осей координат
for (x = -10; x <= 10; x += 1)
{
y = x * x;
g.DrawEllipse(
new Pen(Color.Blue, 2.0f),
x * 5, -y, 1, 1);
}
}
}
}
Замечания
1. Здесь введен масштаб по оси х, растягивающий ось в 5 раз.
2. Метод DrawEllipse рисует окружность, вписанную в квадрат со сторонами 1, 1 (последние два аргумента метода DrawEllipse, определяющие толщину точки) для каждой точки графика.
3. Точка начала координат (xc, yc) переносится в середину формы, Для этого вначале xc, yc вычисляются делением пополам ширины Width и высоты Height формы.
Вопросы для самопроверки
1. Что такое экранные формы и каковы преимущества их использования?
2. Элементы управления TextBox, Button, RichTextBox. В каких случаях целесообразно их использование?
3. Класс Graphics. Рисование прямых линий.
4. Рисование графиков. Масштабирование при рисовании графиков.
Задания для самостоятельного выполнения
Составить программы для решения указанных задач. В задачах 18 использовать элемент управления «Кнопка» (Button). В задачах 1, 2 использовать элемент управления TextBox.
1. Вычислить c = a + b, вводя исходные данные в два текстовых поля (TextBox), в третье вывести результат.
2. Вычислить сумму первых n натуральных чисел, вводя значение n в одно текстовое поле, результат вывести во второе текстовое поле.
В задачах 3 8 использовать элемент управления RichTextBox.
3. Решить задачу 1, вводя исходные данные в элемент управления RichTextBox, результат вывести в текстовое поле TextBox.
4. Ввести пять различных чисел в RichTextBox1, разделяя их пробелами. Умножить каждое из этих чисел на 2 и вывести в RichTextBox2, каждое в новую строку, снабдив результат заголовком.
5. Элементы массива размера 6 поместить в RichTextBox1. Найти максимальный элемент массива и вывести его в TextBox.
6. Решить задачу 5, помещая результат с заголовком в RichTextBox.
7. Два массива размера 6 поместить в RichTextBox1 и RichTextBox2. Найти сумму этих массивов, суммируя каждую пару элементов. Результат с заголовком поместить в RichTextBox3.
8. Матрицу размера 4 × 4 разместить в RichTextBox1. Сформировать массив из сумм элементов строк и поместить его в RichTextBox2.
В задачах 915 построить график функции при , разбивая отрезок на n частей. График начертить точками или отрезками прямых линий по указанию преподавателя, выполнив предварительно масштабирование. При вычерчивании графика функции предусмотреть вывод координатных осей.
9.
10.
11.
12.
13. z = x2 18x + 72; a = 5, b = 20, n = 40.
14. z = x3 + 5x2 + 14x 56; a = 1, b = 10, n = 40.
15.
Во введении на простом примере была представлена последовательность действий для создания консольного приложения. Повторим коротко эти шаги.
1. Создать новый проект Visual Studio C#. В меню «Файл» выбрать команду Создать проект:
2. Создать в рамках этого проекта «Консольное приложение» (выбрать в открывшемся окне, шаблон «Консольное приложение» и нажать OK). Откроется основное окно Visual C# и окно «Редактор кода» с областью для ввода и редактирования кода:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
}
3. Набрать код, расположив его между двумя внутренними фигурными скобками.
Для сохранения проекта в меню «Файл» выберите команду «Сохранить». Откроется окно «Сохранить проект»:
Нажатием на кнопку «Обзор» выберите каталог для сохранения решения. Проект входит в «Решение». «Решение» может содержать несколько проектов. Введите имя папки, в которой хотите сохранить проект. Нажмите кнопку «Сохранить».
В меню «Файл» выберите команду «Открыть проект». Откроется окно «Открыть проект». В этом окне проект необходимо войти в папку ConsoleApplication1 (или другое имя, которое вы выбрали для консольного приложения на этапе создания), выбрать файл ConsoleApplication1 с расширением .sln и нажать кнопку «Открыть».
Интегрированная среда разработки Visual C# представляет собой набор средств разработки, предоставляемых через единый пользовательский интерфейс.
Наиболее важные средства и окна в Visual C#:
• обозреватель решений для просмотра и управления файлами и параметрами проекта;
• редактор кода для написания исходного кода окно «Код»;
• компилятор C# для преобразования исходного кода C# в исполняемую программу;
• отладчик Visual Studio для тестирования программы;
• обозреватель объектов для просмотра методов и классов, доступных в библиотеках.
Обозреватель решений
В меню «Вид» выберите команду «Обозреватель решений». Откроется окно «Обозреватель решений». В окне «Обозреватель решений», в виде иерархического дерева показаны все файлы проекта. Если в открытом проекте не отображается окно редактора кода, то в обозревателе решений необходимо нажать правую кнопку мыши на имя файла Program.cs и выбрать команду «Перейти к коду».
Компилятор C# для преобразования исходного кода C#
в исполняемую программу
Построить и запустить приложение можно в рамках одной операции, для этого необходимо нажать клавишу F5 или в меню «Отладка» выбрать команду «Начать отладку». Результат работы программы появится в окне, которое быстро закроется. Для того чтобы окно не закрывалось, необходимо выбрать в меню «Отладка» команду «Запуск без отладки» или использовать сочетание клавиш Ctrl/F5. Результат работы программы будет выведен в окно (после результата выводится строка «Для продолжения нажмите любую клавишу ..._»):
Ошибки построения
При наличии ошибок приложение не будет построено, и в окне «Список ошибок» откроется список ошибок. Для того чтобы открыть окно «Список ошибок» необходимо в меню «Вид» выбрать команду «Список ошибок». Ниже приводятся примеры ошибок и их описаний, выдаваемых на этапе отладки:
Если приложение не построилось из-за наличия ошибки, то необходимо в меню «Отладка» выбрать команду «Остановить отладку».
Отладчик Visual Studio для тестирования программы
На любом этапе работы в редакторе кода можно задать точку останова на строке кода и далее выбрать в меню «Отладка» команду «Начать отладку». Приложение остановится на этой строке, и можно будет увидеть значение заданной переменной в окне «Локальные». Для того чтобы остановить отладку необходимо выбрать в меню «Отладка» команду «Остановить отладку». Для того чтобы продолжить выполнение программы по строкам кода, можно воспользоваться клавишей F10.
Обозреватель объектов для просмотра методов и классов, доступных в библиотеках
В меню «Вид» выберите команду «Другие окна» и команду «Обозреватель объектов».
На рисунке отображены названия типов. Класс Console статический класс пространства имен System в окне обозревателя объектов. В верхней правой части окна обозревателя отображены имена членов типа Console названия методов и свойств.
Обозреватель объектов состоит из трех областей: области объектов слева, области членов в верхней правой части и области описаний в нижней правой части.
Значки обозревателя объектов
В обозревателе объектов отображаются значки, служащие для представления сущностей кода, таких как пространства имен, классы, функции и переменные.
Обозреватель объектов позволяет выбирать и анализировать символы, доступные к использованию в рамках проектов. В следующей таблице приведены значки с их описанием.
Значок |
Описание |
Значок |
Описание |
Namespace пространство имен |
Метод |
||
Класс |
Событие |
||
Интерфейс |
Свойство |
||
Структура |
Поле или переменная |
||
Перечисление |
Константа |
||
Delegate |
Элемент перечисления |
Рихтер. Дж. Программирование на платформе Microsoft .NET Framework. М.: Издательско-торговый дом «Русская редакция»; СПб.: Питер, 2005. 512 стр.
Фролов А.В., Фролов Г.В. Язык С#. Самоучитель. М.: ДИАЛОГ-МИФИ, 2003. 560 с.
Библиотека MSDN (по-русски) http://msdn.microsoft.com/ru-ru/library/ default.aspx
Спецификация языка C# http://msdn.microsoft.com/ru-ru/vcsharp/ aa336809.aspx
Microsoft .NET технология предоставляет:
1) современный набор инструментальных средств для разработки программного обеспечения;
2) общеязыковую исполняющую среду, которая предоставляет объектно-ориентированную модель программирования;
3) библиотеку классов, которая предоставляет объектно-ориентированный интерфейс, используемый всеми моделями приложений;
и позволяет:
1) выбрать язык программирования;
2) разрабатывать различные типы приложений и служб;
3) задействовать готовые технологии:
• ADO.NET доступ к данным из приложения;
• ASP.NET веб-платформа, предоставляющая все необходимые службы для создания серверных веб-приложений.
Для работы с материалом пособия необходимо установить:
1) распространяемое бесплатно средство разработки Microsoft Visual C# 2008 Express Edition (Microsoft Visual C# 2008, экспресс-выпуск) доступное по ссылке http://www.microsoft.com/express/ Downloads/#2008-Visual-CS;
2) распространяемый бесплатно пакет: .NET Framework, который устанавливается автоматически при установке Microsoft Visual C# 2008 Express Edition.
После установки желательно с помощью Microsoft Update установить последние обновления.
NET Framework платформа разработки.
Основными компонентами .NET Framework являются:
• общеязыковая среда исполнения (common language runtime, CLR)
• библиотека классов (Framework Class Library, FCL)
Сайт предоставляющий возможность бесплатно скачать
Microsoft Visual Studio Express
Microsoft Visual Studio среда разработки включает:
• средства управления проектами;
• редактор исходного текста;
• компиляторы;
• компоновщики;
• документацию, отладчики;
• конструкторы пользовательского интерфейса.
Библиотека MSDN основной источник информации для разработчиков, использующих инструментальные средства, продукты и технологии Microsoft. Студент может использовать справочную систему на русском языке доступную по ссылке: http://msdn.microsoft.com/ru-ru/library/default.aspx.
Таблица целых типов
Тип |
Диапазон |
Размер |
sbyte |
От 128 до 127 |
8-разрядное целое число со знаком |
byte |
От 0 до 255 |
8-разрядное целое число без знака |
char |
от U+0000 до U+ffff |
16-разрядный символ Юникода |
short |
От 32 768 до 32 767 |
16-разрядное целое число со знаком |
ushort |
От 0 до 65 535 |
16-разрядное целое число без знака |
int |
От 2 147 483 648 до 2 147 483 647 |
32-разрядное целое число со знаком |
uint |
От 0 до 4 294 967 295 |
32-разрядное целое число без знака |
long |
От 9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 |
64-разрядное целое число со знаком |
ulong |
От 0 до 18 446 744 073 709 551 615 |
64-разрядное целое число без знака |
Таблица типов с плавающей запятой
Тип |
Приблизительный диапазон |
Точность |
float |
От ±1,5e−45 до ±3,4e38 |
7 знаков |
double |
От ±5,0e−324 до ±1,7e308 |
1516 знаков |
Ключевое слово decimal обозначает 128-разрядный тип данных. По сравнению с типом данных с плавающей запятой, тип decimal имеет более точный и узкий диапазон, благодаря чему он походит для финансовых расчетов.
Тип |
Приблизительный диапазон |
Точность |
decimal |
от ±1,0 × 10−28 до ±7,9 × 1028 |
2829 значимых цифр |
Ключевое слово bool используется для объявления переменных для хранения логических значений true и false.
Тип данных string это последовательность, не содержащая ни одного или содержащая любое число знаков Юникода.
Тип object представляет собой псевдоним для Object в платформе .NET Framework. В унифицированной системе типов C# все типы, предопределенные и пользовательские, ссылочные типы и типы значений, наследуют непосредственно или косвенно от Object. Переменным типа object можно назначать значения любых типов.
Все типы, за исключением типов object и string, относятся к типам значений, переменные этих типов являются экземплярами структур. Типы object и string относятся к ссылочным типам, переменные этих типов являются экземплярами классов.
Экземпляры классов создаются с помощью ключевого слова new. Переменная типа String является экземпляром класса и хранит адрес в памяти, где размещается строка, и одновременно тип String является встроенным типом, поэтому нужно создавать строки без использования ключевого слова new.
Региональные стандарты выбираются как параметры установки пользователем при установке Windows.
Платформа .NET Framework предоставляет широкие возможности для разработки международных приложений. При разработке международных приложений рекомендуется разделять процесс на три этапа: глобализация, обеспечение возможности локализации и локализация.
Глобализация является первым этапом разработки международных приложений. На этом шаге пишется исполняемый код приложения. По-настоящему глобальные приложения должны быть нейтральны в отношении языка и региональных параметров.
Перед переходом к локализации необходимо выполнить промежуточную проверку, позволяющую определить Локализуемость приложения. Если приложение локализуемо, то исполняемый код приложения корректно отделен от его ресурсов. При правильной оценке локализуемости приложения нет необходимости изменять исходный код приложения во время локализации.
Последний шаг при построении международных приложений это Локализация, которая заключается в настройке приложения под определенные языки и регион. Если глобализация и локализуемость были выполнены правильно, то локализация должна состоять в основном только в переводе пользовательского интерфейса.
Локализация это процесс перевода ресурсов приложения в локализованные версии для каждого языка и региональных параметров, которую поддерживает приложение.
Идентификатор языкового стандарта определяет региональные параметры и язык для конкретной географической области. К некоторым категориям, зависящим от языкового стандарта, относится формат дат и отображения денежных значений.
Язык определяет соглашения по форматированию текста и даты, а страна или регион определяют национальные соглашения. Для каждого языка существует уникальное сопоставление, представленное кодовыми страницами, в которых содержатся символы, отличные от символов в алфавите (знаки препинания и цифры). Кодовая страница это набор символов, связанный с языком. А языковой стандарт это уникальная комбинация языка, страны или региона и кодовой страницы.
Различные языки могут использовать разные кодовые страницы. Например, кодовая страница 1252 используется для английского и большинства европейских языков, а кодовая страница 932 используется для японского иероглифического языка, кодовая страница 1251 используется для русского языка.
Подробнее в http://msdn.microsoft.com/ru-ru/library/8w60z792.aspx.
Учебное издание
Куренкова Татьяна Васильевна
Светозарова Галина Ивановна
Основы алгоритмизации и объектно-ориентированного программирования
Учебное пособие
Редактор М.Б. Линчевская
Компьютерная верстка А.В. Калинкиной, И.Г. Иваньшиной
Подписано в печать 00.04.11 |
Бумага офсетная |
|
Формат 60 90 1/16 |
Печать офсетная |
Уч.-изд. л. 12,3 |
Рег. № 209 |
Тираж 1350 экз. |
Заказ |
Национальный исследовательский
технологический университет «МИСиС»,
119049, Москва, Ленинский пр-т, 4
издательский Дом МИСиС,
119049, Москва, Ленинский пр-т, 4
тел. (495) 638-45-22
Отпечатано в типографии издательского Дома МИСиС
119049, Москва, Ленинский пр-т, 4
тел. (499) 236-76-17, тел./факс (499) 236-76-35