Тема 4 Понятие классов и методов (функции) языка C#. Организация данных в виде строк.
Лекция 13 МЕТОДЫ КЛАССА
13.1 Понятие данных и методов класса
В первой лекции нашей дисциплины мы отмечали, что язык C# является объектно-ориентированным языком программирования и все действия (коды наших первых программ) необходимо записывать в методе Main класса Program.
Формально, класс это новый тип данных, объединяющий в одной структуре данные и методы их обработки (остальные составляющие класса будут рассмотрены в дисциплине «Технологии программирования»).
Данными класса могут быть константы или переменные (поля) класса. При объявлении данных в классе обычно указывается спецификатор доступа к нему, например,
private int a;
Общий формат записи данных класса при их объявлении имеет следующий вид:
[ спецификаторы ] [ const ] тип имя [= начальное_значение].
К данным относятся константы класса, которые предназначены для хранения неизменяемых значений, и поля класса (типы и имена переменных класса).
Если перед данными используются спецификатор public, то они являются доступными «программе» – мы всегда будем использовать спецификатор public.
В технологии объектно-ориентированного программирования данные класса обычно «закрывают для программы» – используют спецификатор private и по умолчанию, как для данных, так и для методов применяется спецификатор private.
Методы класса это поименованные фрагменты кода, предназначенные для работы с данными класса.
Методы определяют набор действий, которые доступны классу (часто говорят, что они определяют поведение класса).
Метод описывается один раз, а может вызываться для различных данных столько раз, сколько необходимо.
Общий формат записи методов класса имеет следующий вид:
[ спецификаторы ] тип метода имя метода ( [ параметры ] )
{тело метода}
Например,
static void Main(string[] args)
{ }
Наиболее часто встречаемые спецификаторы это private, public и static.
Любые методы класса, объявленные со спецификатором private, доступны только в методах данного класса.
Спецификатором public делает метод доступным в любом месте программы.
Спецификатор static означает, что к методу можно обращаться «на уровне класса» не создавая объект класса – это очень важно, так как в данной дисциплине мы будем очень часто использовать статические методы.
Тип метода может задаваться любым определенным в программе или стандартным типом языка C# или void – без типа. Например:
int kol(int a) { … }
public double sym(out float r) { … }
public void poisk(ref float s) { … }
public int funkcij( int a, out int b, params int[] c) { … }
Если задан тип метода (кроме void), то последним оператором тела метода должен быть оператор return, возвращающий результат работы метода. При этом метод необходимо присваивать некоторой переменной или использовать как выражение в операторах языка C#. Часто такие методы называют функциями.
Если перед методом указан тип void, то метод не должен возвращать результат своей работы с помощью оператора return (оператор return в этом случае отсутствует в теле метода). Часто такой метод называют процедурой – ее не надо присваивать переменной, а можно записывать как отдельную подпрограмму – процедуру (имя метода с указанием в круглых скобках ее параметров).
Имя метода – идентификатор, определяемый программистом. Желательно в имя метода закладывать смысловое назначение метода, например, sym, max, poisk и т. д.
13.2 Параметры методов
Параметры методов (формальные параметры) предназначены для обмена данными между методами и программой. Часто параметры метода называют средством «настройки» метода на выполнение необходимого алгоритма.
В языке С# различают следующие параметры методов:
– параметры-значения (входные параметры, т. е. получаемые методом);
– выходные-параметры (помечаются служебным словом out);
– параметры-ссылки (помечаются служебным словом ref);
– параметры-массивы (помечаются служебным словом params).
Параметры-значения не имеют помечающего служебного слова.
Параметров метода класса разделяются запятыми. Параметр-массив в методе может быть только один и должен быть последним в списке параметров.
Если в методе объявлены параметры-значения, то это означает, что метод получает в свое распоряжение копии некоторых переменных. Метод может изменять значения этих копий, но их оригинал (в программе) остается неизменным. По окончании работы метода параметры-значения удаляются из памяти компьютера.
Выходные-параметры метода предназначены для передачи результатов работы метода в программу. В теле метода обязательно должны находиться операторы присваивания выходным-параметрам некоторых значений, иначе сообщение об ошибке во время компиляции программы.
Если в методе объявлены параметры-ссылки, то фактически метод получает в свое распоряжение адреса соответствующих переменных и может их использовать по своему алгоритму (читать или писать новые значения).
Объявленные в методе параметры-массивы предназначены для работы с произвольным числом фактических переменных. При этом формальному параметру, стоящему за служебным словом params ставится в соответствие массив произвольной длины данных.
Таким образом, через свои параметры метод может, как получать необходимые значения (параметры-значения и параметры-ссылки), так и возвращать результаты своей работы (выходные-параметры и параметры-ссылки).
Тело метода содержит фрагмент кода программы, реализующий некоторый алгоритм. При этом метод выступает как некоторый шаблон действия с формальными параметрами. В программе вместо формальных параметров необходимо использовать реальные переменные – фактические параметры и шаблон действия метода будет применяться для реальных переменных.
Работа метода в программе с формальными параметрами невозможна.
13.3 Использование входных параметров в методе класса
Рассмотрим использование входных параметров в функции некоторой учебной программы.
Задача 13.1 В режиме диалога вводятся три целых числа. Необходимо найти и напечатать наибольшее число. Использовать функцию (метод) целого типа.
Алгоритм решения задачи включает следующие шаги:
– необходимо ввести в режиме диалога значения трех переменных, например, a, b,c;
– объявляем, что первая переменная содержит максимальное число (некоторой переменной m присваиваем значение a);
– сравниваем значение переменной m со значением переменной b и наибольшее записываем в переменную m;
– сравниваем значение переменной m со значением переменной c и наибольшее записываем в переменную m;
– выводим значение переменной m на экран монитора.
Оформим процесс сравнения в виде следующей функции целого типа:
static int maxc(int aa, int bb, int cc)
{
int mm;
mm = aa;
if (mm < bb) mm = bb;
if (mm < cc) mm = cc;
return mm;
}
Все три формальных параметра нашей функции maxc являются входными параметрами.
Код всей программы имеет следующий вид:
using System;
namespace ConsoleApplication1
{
class Program
{
static int maxc(int aa, int bb, int cc)
{
int mm;
mm = aa;
if (mm < bb) mm = bb;
if (mm < cc) mm = cc;
return mm;
}
static void Main()
{
int a, b,c, m;
string buf;
Console. Write("Введите целое значение a ");
buf = Console. ReadLine();
a = Convert. ToInt32(buf);
Console. Write("Введите целое значение b ");
buf = Console. ReadLine();
b = Convert. ToInt32(buf);
Console. Write("Введите целое значение c ");
buf = Console. ReadLine();
c = Convert. ToInt32(buf);
m = maxc(a, b,c);
Console. WriteLine("Исходные числа: {0}, {1}, {2}", a, b, c);
Console. WriteLine("Максимальное число = {0}", m);
Console. WriteLine("Для продолжения нажмите клавишу Enter");
Console. ReadLine();
}
}
}
Работа программы:
Введите целое значение a 21
Введите целое значение b 34
Введите целое значение c 17
Исходные числа: 21, 34, 17
Максимальное число = 34
Для продолжения нажмите клавишу Enter
13.4 Понятие локальных и глобальных переменных класса.
В теле функции maxc() мы объявили переменную целого типа mm. В программирование существуют понятия локальных и глобальных переменных класса и правила работы с ними.
Переменные, объявленные внутри любого метода (в то числе и функции), называются локальными переменными.
Переменные, объявленные внутри класса, но вне любого его метода называются глобальными переменными этого класса.
В языке C# не допускается использование одинаковых по названию локальных и глобальных переменных – все переменные программы должны иметь индивидуальные имена.
В терминологии методов класса существует понятие «области видимости» и «время жизни» переменных методов. Любой метод класса «видит» все глобальные переменные своего класса и может их использовать.
«Время жизни» локальной переменной определяется временем работы метода, использующего эту переменную.
13.5 Использование выходных параметров в методе класса
Рассмотрим использование выходных параметров в процедуре некоторой учебной программы.
Задача 13.2 В режиме диалога вводятся три числа. Необходимо напечатать их в убывающем порядке. Использовать метод класса.
Задача понятна, но алгоритм не совсем очевиден. Было бы проще, если бы мы работали не с тремя, а с двумя переменными. Разработаем алгоритм метода для упорядочения двух переменных.
Предположим, что у нас есть входные переменные A и B. Их значения необходимо переписать в выходные переменные X и Y, причем максимальное значение запишем в X, а минимальное в Y. Для этого необходимо сравнить значения входных переменных A и B, если A больше B, значение A присвоим переменной Х, а значение B присвоим переменной Y, иначе Х присвоим значение B, а Y присвоим значение A.
Разработанный алгоритм метода для двух переменных мы можем использовать и для трех переменных следующим образом:
Первоначально сравниваем A и В результат в X и Y.
Сравним Х и C результат в Х и Z. Найдено максимальное значение – оно записано в переменную Х.
Сравним Z и Y результат в Y и Z. Найдены следующие значения чисел в убывающей последовательности – Y и Z.
Таким образом, метод сравнения двух чисел и нахождение максимального и минимального мы использовали три раза. В результате получили последовательность переменных X, Y и Z, в которой значения находятся в убывающем порядке.
Для реализации рассмотренного алгоритма для двух переменных применим метод static void maxmin(int aa, int bb, out int xx, out int yy), в котором два формальных параметра aa и bb являются входными, а два параметра xx и yy – выходными.
Для себя, в имени функции maxmin мы отмечаем, что возвращаемые параметры расположены в порядке максимальное, минимальное.
Исходный код программы:
using System;
namespace ConsoleApplication1
{
class Program
{
static void maxmin(int aa, int bb, out int xx, out int yy)
{
if (aa>bb) {xx = aa; yy = bb;}
else { xx = bb; yy = aa;}
}
static void Main(string[] args)
{
int a, b, c, x, y, z;
string buf;
Console. Write("Введите целое значение a ");
buf = Console. ReadLine();
a = Convert. ToInt32(buf);
Console. Write("Введите целое значение b ");
buf = Console. ReadLine();
b = Convert. ToInt32(buf);
Console. Write("Введите целое значение c ");
buf = Console. ReadLine();
c = Convert. ToInt32(buf);
Console. WriteLine("Исходная последовательность: {0}, {1}, {2}", a, b, c);
maxmin(a, b,out x, out y);
maxmin(c, x,out x, out z);
maxmin(y, z,out y, out z);
Console. WriteLine("Полученная последовательность: {0}, {1}, {2}", x, y, z);
Console. WriteLine("Для продолжения нажмите клавишу Enter");
Console. ReadLine();
}
}
}
Работа программы:
Введите целое значение a 5
Введите целое значение b -3
Введите целое значение c 8
Исходная последовательность: 5, -3, 8
Полученная последовательность: 8, 5, -3
Для продолжения нажмите клавишу Enter
В приведенном примере метод static void maxmin(int aa, int bb, out int xx, out int yy) получает из программы два параметра (int aa, int bb) и возвращает в программу два параметра (out int xx, out int yy). При этом фактические параметры, используемые в программе вместо формальных параметров xx, yy должны иметь значащий тип.
Возврат параметров можно организовать с помощью ссылок ref, но для этого возвращаемые фактические параметры должны быть ссылочного типа.
13.6 Использование оператора return для возврата результата работы метода
В качестве примера использования оператора return для возврата результатов работы функции рассмотрим программную реализацию метода половинного деления для нахождения корней уравнения.
Задача 13.3 Методом половинного деления найти корень уравнения:
cos(2/x) – 2 * sin(1/x) + 1/x = 0
на отрезке от 1 до 2 с точностью t = 0.0001.
Метод половинного деления предполагает, что значения функции на концах заданного отрезка имеют разные знаки, например, «-» и «+». Следовательно, где-то на этом отрезке есть хотя бы одно такое x, при котором функция равна нулю.

Рисунок 13.1 – Работа метода половинного деления
Алгоритм нахождения значения x основан на делении заданного отрезка пополам и выборе той половины отрезка, у которой функция сохраняет разные по знаку значения. Процесс повторяется до тех пор, пока не будет найдено значение, при котором функция равна нулю или пока длина отрезка не станет меньше заданной в условии задачи точности t (в этом случае мы считаем, что тоже нашли корень уравнения).
Алгоритм включает многократное вычисление значений функции при различных значениях аргумента. Поэтому целесообразно выделить этот процесс в самостоятельный метод, например, с именем f.
Исходный код программы:
using System;
namespace ConsoleApplication1
{
class Program
{
static double f(double a)
{ return (Math. Cos(2/a)-2*Math. Sin(1/a)+1/a); }
static void Main()
{
double a, b, x, ya, yb, t;
a = 1;
b = 2;
t = 0.0001;
ya=f(a);
x=(a+b)/2;
yb=f(x);
while (b-a>t && yb!=0)
{
if (ya*yb<0)
{b=x; x=(a+b)/2; yb=f(x);}
else
{ a=x; ya=yb; x=(a+b)/2; yb=f(x);}
}
Console. WriteLine("Корень уравнения = {0}", x);
Console. WriteLine("Для продолжения нажмите клавишу Enter");
Console. ReadLine();
}
}
}
Работа программы:
Корень уравнения = 1,87564086914063
Для продолжения нажмите клавишу Enter
Использование метода f() увеличивает наглядность программы и упрощает процесс ее написания.
13.7 Вопросы для проверки
13.7.1 Где в языке С# задается тип функции?
A) В круглых скобках за именем функции.
B) Тип функции задается после ее имени.
C) Тип функции задается перед ее именем.
D) За именем функции специальным указанием.
E) Тип функции задается после ее имени через двоеточие.
13.7.2 Какие переменные в языке С# называются локальными переменными класса Program?
A) Переменные объявленные вне функции Main.
B) Переменные объявленные в круглых скобках за именем функции Main.
C) Переменные объявленные вне любой функции.
D) Переменные объявленные внутри любой функций.
E) Переменные объявленные в заголовках функций.
13.7.3 Как должна заканчиваться в языке С# функция если задан ее тип?
13.7.4 Какие формальные параметры функции языка С# называются параметры-ссылки?
13.7.5 Как необходимо использовать функцию в программе, если тип возвращаемого значения функции объявлен double?
Продолжение тема 4 Понятие классов и методов (функции) языка C#. Организация данных в виде строк.
Лекция 14 ПЕРЕГРУЗКА МЕТОДОВ, РЕКУРСИЯ
14.1 Перегрузка методов
В языке С# можно определять несколько методов с одним и тем же именем, при этом они могут иметь разные наборы формальных параметров (или разные типы параметров). Этот прием в языке С# называется перегрузкой методов.
При вызове перегруженного метода программа определяет соответствующий метод путём анализа количества, типов и порядка следования аргументов в вызове.
При изучении математических функций Вы уже встречались с указаниями, что функция «перегружена», то есть она корректно работает как с целыми, так и вещественными формальными параметрами.
Рассмотрим простой учебный пример перегрузки методов. Исходный код программы с использованием перегрузки методов имеет следующий вид:
using System;
using System. Collections. Generic;
using System. Linq;
using System. Text;
namespace ConsoleApplication1
{
class Program
{
public static int funk(int x) { return x*x;}
public static double funk(double x) { return x+x; }
public static char funk(char x) { return x; }
static void Main(string[] args)
{
Console. WriteLine("x = 5 funk(5) = {0} ", funk(5));
Console. WriteLine("x = 5.5 funk(5.5) = {0} ", funk(5.5));
Console. WriteLine("x = 'g' funk('g') = {0} ", funk('g'));
Console. WriteLine("Для продолжения нажмите клавишу Enter");
Console. ReadLine();
}
}
}
Работа программы:
x = 5 funk(5) = 25
x = 5.5 funk(5.5) = 11
x = 'g' funk('g') = g
Для продолжения нажмите клавишу Enter
Приведённый пример использует перегруженную функцию funk, отличающуюся типом своих аргументов. Однако перегруженные функции могут так же отличатся и числом своих аргументов.
Перегрузка функций часто используется для написания методов, «понимающих» данные различного типа. Например, текущую дату можно задавать по умолчанию или вводить разными способами. Возможны, например, следующие три варианта значений ввода даты:
целыми числами (месяц, день и год – 23 12 07);
некоторым текстом (23 декабря 2007 года);
набором с использованием дополнительных символов (20.10.07 или 17/09/2007).
Что бы программа «понимала» любой способ ввода этих данных нужна перегрузка метода под каждый тип данных.
14.2 Понятие рекурсии
Рекурсия – это такой способ организации вычислительного процесса, при котором метод в ходе выполнения составляющих его операторов обращается сам к себе.
Классический пример, поясняющий рекурсивные вычисления, это вычисление факториала.
Предположим, необходимо вычислить N!
N! = 1*2* … *(N-1)*N = (N - I) ! * N ;
Таким образом, что бы вычислить N! необходимо знать (N-1)!. В свою очередь (N - 1) ! = 1*2* ...* (N - 2) !* (N - 1) ; и т. д. 2 ! = 1 ! * 2 ; 1 ! = 1.
Факториал N можно вычислить с помощью обычного цикла FOR, например:
pr = 1;
for (i=1; i<=N;i++) pr = pr*i ;
По окончании цикла результат находится в переменной pr.
Использование рекурсии позволяет довольно просто решать многие сложные задачи, в которых присутствуют циклические операции с одним или несколькими условиями. При этом тело цикла с условием записывается в рекурсивную функцию, а использование функции уменьшает исходный код программы и улучшает ее восприятие. В качестве учебного примера программы, использующего рекурсивную функцию, разработаем программу вычисления факториала для целого числа, введенного в режиме диалога с ЭВМ.
Исходный код программы:
using System;
namespace ConsoleApplication1
{
class Program
{
static int fakt(int n)
{
int fu, k;
fu = n;
if (n!= 1) { k = n - 1; fu = fu * fakt(k); }
return fu;
}
static void Main()
{
int a, n;
string buf;
Console. Write("Введите целое значение n ");
buf = Console. ReadLine();
n = Convert. ToInt32(buf);
a = fakt(n);
Console. WriteLine("n! = {0}", a);
Console. WriteLine("Для продолжения нажмите клавишу Enter");
Console. ReadLine();
}
}
}
Работа программы:
Введите целое значение n 5
n! = 120
Для продолжения нажмите клавишу Enter
14.3 Работа рекурсивной функции в программе
В приведенном коде программы нет циклов, и алгоритм программы легко воспринимаем – вводим значение переменной n, вычисляем факториал и выводим результат на экран монитора.
Рассмотрим работу рекурсивной функции fakt() в процессе выполнения программы. Предположим, что во время работы программы мы ввели число 5. Запускается функция вычисления факториала для значения 5 – fakt(5). Но для вычисления функции fakt(5) необходимо вычислить функцию fakt(4). Поэтому работа функции fakt(5) временно приостанавливается, ее параметры помещаются в программный стек, и запускается функция вычисления факториала для значения 4 – fakt(4).
Для вычисления функции fakt(4) необходимо вычислить функцию fakt(3). Поэтому работа функции fakt(4) временно приостанавливается, ее параметры помещаются в стек (над параметрами прерванной функции fakt(5)) и запускается функция вычисления факториала для значения 3 – fakt(3). Далее в стек помещаются параметры функций fakt(3), fakt(2) и только после этого будет вычислено значение функции fakt(1) равное единице.
Имея значение функции fakt(1) программа может вычислить значение функции fakt(2). Для этого параметры fakt(2) необходимо вызвать из стека и продолжить ее работу с результатом работы функции fakt(1). Полученное значение функции fakt(2) можно использовать для вычисления функции fakt(3). Для этого параметры fakt(3) необходимо вызвать из стека и продолжить ее работу с результатом работы функции fakt(2). Далее по аналогичной схеме вычисляются функции fakt(4) и fakt(5).
По завершении вычисления fakt(5) ее результат присваивается переменной a и выводится на экран монитора.
Использование рекурсивных вычислений повышает наглядность программы, но время работы таких программ значительно больше, чем программ, в которых рекурсивные вычисления организованы с помощью обычных операторов цикла.
14.4 Пример использования рекурсии и цикла в программе
В качестве учебного примера рассмотрим работу двух программ, вычисляющих некоторое математическое выражение с помощью рекурсии и с помощью цикла с условием.
Задача 14.1 Вычислить следующее выражение
_____________1_______
y = 1 + __________1_______
3 + _______1_______
5 + ____1______
* * * *
(N-2) +__1__
N
где N – нечетное целое число равное 5555.
Рассмотрим решение задачи 14.1 двумя способами – с помощью цикла с условием while и с помощью рекурсии.
Алгоритм решения задачи с помощью цикла с условием основан на том, что необходимо начинать вычисления от 1/N до 1/(1 + . . .) уменьшая в каждом цикле N на 2. Вычисления продолжать до тех пор, пока N>1.
Исходный код программы 1:
using System;
using System. Collections. Generic;
using System. Linq;
using System. Text;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
double s, k, n;
n=5555;
k=1;
s = 1 / n;
while (n > 1)
{
n = n - 2;
s = 1 / (n + s);
}
Console. WriteLine("Результат = {0} ", s);
Console. WriteLine("Для продолжения нажмите клавишу Enter");
Console. ReadLine();
}
}
}
Результат работы программы:
Результат = 0,761594155955765
Для продолжения нажмите клавишу Enter
Алгоритм рекурсии определяется формулой выражения, что бы вычислить 1/(1+ . . .) необходимо вычислить 1/(3+ . . .) и так далее до 1/N.
Исходный код программы 2:
using System;
using System. Collections. Generic;
using System. Linq;
using System. Text;
namespace ConsoleApplication1
{
class Program
{
static double rec(double k, double n)
{
double rez;
if (k< n) { k = k + 2; rez = (k - 2) + 1 / rec(k, n); }
else rez = n;
return rez;
}
static void Main()
{
double s, k, n;
n=5555;
k=1;
s = 1 / rec(k, n);
Console. WriteLine("Результат = {0} ", s);
Console. WriteLine("Для продолжения нажмите клавишу Enter");
Console. ReadLine();
}
}
}
Результат работы программы:
Результат = 0,761594155955765
Для продолжения нажмите клавишу Enter
Результат работы программ одинаков.
Текст кода программы с рекурсией воспринимается легче.
Если увеличить значение переменной n, например, n = 55555555, то программа с рекурсией потребует перенастройку программного стека (количество допустимых рекурсий определяется объемом программного стека), программа с циклом while работает с числами во всем их диапазоне.
Из-за обращения к программному стеку время работы программы с рекурсией больше времени работы программы с циклами (при больших значениях n).
14.5 Вопросы для проверки
14.5.1 Что напечатает следующий фрагмент программы:
public static void ttt(int a, int b, out int x, out int y)
{ if (a>b) {x = a;y = b;}else {x = b; y = a;} }
static void Main(string[] args)
{
int a, b, c = 0, d = 0;
a = 5;
b = 8;
ttt(a, b, out c, out d);
Console. WriteLine("c = {0} d = {1} ", c, d);
}?
14.5.2 Что напечатает следующий фрагмент программы:
public static void ttt(int a, int b, int x, int y)
{ x = a; a = b; y = a; }
static void Main(string[] args)
{
int a, b, c = 0, d = 0;
a = 5;
b = 8;
ttt(a, b, c, d);
Console. WriteLine(" {0} {1} ", c, d);
}?
14.5.3 Можно ли внутри функции объявлять другую функцию?
14.5.4 Какая функция называется рекурсивной функцией?
14.5.5 Как называется процесс определения нескольких методов с одинаковыми именами?


