Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Подробнее о методах и классах.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
144 Кб
Скачать
☆

8.5.1 Возврат массива из метода

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

Листинг 8.16

// Возвратить массив из метода.

using System;

class Factor

{

/* Метод возвращает массив facts, содержащий множители аргумента

num. При возврате из метода параметр numfactors типа out будет

содержать количество обнаруженных множителей. */

public int[] FindFactors(int num, out int numfactors)

{

int[] facts = new int[80]; // размер массива 80 выбран произвольно

int i, j;

// Найти множители и поместить их в массив facts.

for(i=2, j=0; i < num/2 + 1; i++)

if( (num%i)==0 ) {

facts[j] = i;

j++;

}

numfactors = j;

return facts;

}

}

class FindFactors

{

static void Main()

{

Factor f = new Factor();

int numfactors;

int[] factors;

factors = f.FindFactors(1000, out numfactors);

Console.WriteLine("Factors for 1000 are: ");

for(int i=0; i < numfactors; i++)

Console.Write(factors[i] + " ");

Console.WriteLine();

}

}

При выполнении этой программы получается следующий результат.

Множители числа 1000:

2 4 5 8 10 20 25 40 50 100 125 200 250 500

В классе Factor метод FindFactors () объявляется следующим образом.

public int[] FindFactors(int num, out int numfactors) {

Обратите внимание на то, как указывается возвращаемый массив типа int. Этот синтаксис можно обобщить. Всякий раз, когда метод возвращает массив, он указыва­ется аналогичным образом, но с учетом его типа и размерности. Например, в следую­щей строке кода объявляется метод someMeth (), возвращающий двумерный массив типа double.

public double[,] someMeth() { // ...

8.6 Перегрузка методов

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

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

Ниже приведен простой пример, демонстрирующий перегрузку методов.

Листинг 8.17

// Продемонстрировать перегрузку методов.

using System;

class Overload

{

public void OvlDemo() {

Console.WriteLine("Без параметров");

}

// Перегрузка метода OvlDemo с одним целочисленным параметром.

public void OvlDemo(int a)

{

Console.WriteLine("Один параметр: " + a);

}

// Перегрузка метода OvlDemo с двумя целочисленными параметрами.

public int OvlDemo(int a, int b)

{

Console.WriteLine("Два параметра: " + a + " " + b);

return a + b;

}

// Перегрузка метода OvlDemo с двумя параметрами типа double.

public double OvlDemo(double a, double b)

{

Console.WriteLine("Два параметра типа double: " +

a + " "+ b);

return a + b;

}

}

class OverloadDemo

{

static void Main()

{

Overload ob = new Overload();

int resI;

double resD;

// Вызвать все варианты метода OvlDemo().

ob.OvlDemo();

Console.WriteLine();

ob.OvlDemo(2);

Console.WriteLine();

resI = ob.OvlDemo(4, 6);

Console.WriteLine("Результат вызова метода ob.OvlDemo(4,6): "

+ resI);

Console.WriteLine();

resD = ob.OvlDemo(1.1, 2.32);

Console.WriteLine("Результат вызова метода ob.OvlDemo(1.1,2.32): "

+ resD);

}

}

Вот к какому результату приводит выполнение приведенного выше кода.

Без параметров

Один параметр: 2

Два параметра: 4 6

Результат вызова метода ob.OvlDemo(4, 6): 10

Два параметра типа double: 1.1 2.32

Результат вызова метода ob.OvlDemo(1.1, 2.32): 3.42

Как видите, метод OvlDemo() перегружается четыре раза. Первый его вариант не получает параметров, второй получает один целочисленный параметр, третий  два целочисленных параметра, а четвертый два параметра типа double. Обратите так­же внимание на то, что два первых варианта метода OvlDemo() возвращают значение типа void, а по существу, не возвращают никакого значения, а два других  возвра­щают конкретное значение. И это совершенно допустимо, но, как пояснялось выше, тип возвращаемого значения не играет никакой роли для перегрузки метода. Следова­тельно, попытка использовать два разных (по типу возвращаемого значения) варианта метода OvlDemo() в приведенном ниже фрагменте кода приведет к ошибке.

// Одно объявление метода OvlDemo(int) вполне допустимо.

public void OvlDemo(int a)

{

Console.WriteLine("Один параметр: " + a);

}

/* Ошибка! Два объявления метода OvlDemo(int) не допускаются,

хотя они и возвращают разнотипные значения. */

public int OvlDemo(int a)

{

Console.WriteLine("Один параметр: " + a);

return a * a;

}

Как следует из комментариев к приведенному выше коду, отличий в типах значе­ний, возвращаемых обоими вариантами метода OvlDemo(), оказывается недостаточно для перегрузки данного метода.

В С# предусмотрен ряд неявных (т.е. автоматических) преобразований типов. Эти преобразования распространяются также на параметры перегружаемых методов. В качестве примера рассмотрим следующую программу.

Листинг 8.18

// Неявные преобразования типов могут повлиять

// на решение перегружать метод.

using System;

class Overload2

{

public void MyMeth(int x)

{

Console.WriteLine("В методе MyMeth(int): " + x);

}

public void MyMeth(double x)

{

Console.WriteLine("В методе MyMeth(double): " + x);

}

}

class TypeConv

{

static void Main()

{

Overload2 ob = new Overload2();

int i = 10;

double d = 10.1;

byte b = 99;

short s = 10;

float f = 11.5F;

ob.MyMeth(i); // вызвать метод ob.MyMeth(int)

ob.MyMeth(d); // вызвать метод ob.MyMeth(double)

ob.MyMeth(b); // вызвать метод ob.MyMeth(int) –

//с преобразованием типа

ob.MyMeth(s); // вызвать метод ob.MyMeth(int) –

// с преобразованием типа

ob.MyMeth(f); // вызвать метод ob.MyMeth(double) –

// с преобразованием типа

}

}

При выполнении этой программы получается следующий результат.

В методе MyMeth(int): 10

В методе MyMeth(double): 10.1

В методе MyMeth(int): 99

В методе MyMeth(int): 10

В методе MyMeth(double): 11.5

В данном примере определены только два варианта метода MyMeth(): с параме­тром типа int и с параметром типа double. Тем не менее методу MyMeth() можно передать значение типа byte, short или float. Так, если этому методу передается зна­чение типа byte или short, то компилятор С# автоматически преобразует это зна­чение в тип int и в итоге вызывается вариант MyMeth(int) данного метода. А если ему передается значение типа float, то оно преобразуется в тип double и в результате вызывается вариант MyMeth(double) данного метода.

Следует, однако, иметь в виду, что неявные преобразования типов выполняются лишь в том случае, если отсутствует точное соответствие типов параметра и аргумента. В качестве примера ниже приведена чуть измененная версия предыдущей программы, в которую добавлен вариант метода MyMeth(), где указывается параметр типа byte.

Листинг 8.19

// Добавить метод MyMeth(byte).

using System;

class Overload2

{

public void MyMeth(byte x)

{

Console.WriteLine("В методе MyMeth(byte): " + x);

}

public void MyMeth(int x)

{

Console.WriteLine("В методе MyMeth(int): " + x);

}

public void MyMeth(double x)

{

Console.WriteLine("В методе MyMeth(double): " + x);

}

}

class TypeConv

{

static void Main()

{

Overload2 ob = new Overload2();

int i = 10;

double d = 10.1;

byte b = 99;

short s = 10;

float f = 11.5F;

ob.MyMeth(i); // вызвать метод ob.MyMeth(int)

ob.MyMeth(d); // вызвать метод ob.MyMeth(double)

ob.MyMeth(b); // вызвать ob.MyMeth(byte) –

// на этот раз без преобразования типа

ob.MyMeth(s); // вызвать метод ob.MyMeth(int) –

// с преобразованием типа

ob.MyMeth(f); // вызвать метод ob.MyMeth(double) –

// с преобразованием типа

}

}

Выполнение этой программы приводит к следующему результату.

В методе MyMeth(int): 10

В методе MyMeth(double): 10.1

В методе MyMeth(byte): 99

В методе MyMeth(int): 10

В методе MyMeth(double): 11.5

В этой программе присутствует вариант метода MyMeth(), принимающий аргу­мент типа byte, поэтому при вызове данного метода с аргументом типа byte выбира­ется его вариант MyMeth(byte) без автоматического преобразования в тип int.

Оба модификатора параметров, ref и out, также учитываются, когда принимается решение о перегрузке метода. В качестве примера ниже приведен фрагмент кода, в ко­тором определяются два совершенно разных метода.

public void MyMeth(int x)

{

Console.WriteLine("В методе MyMeth(int): " + x);

}

public void MyMeth(ref int x)

{

Console.WriteLine("В методе MyMeth(ref int): " + x);

}

Следовательно, при обращении

ob.MyMeth(i)

вызывается метод MyMeth(int x), но при обращении

ob.MyMeth(ref i)

вызывается метод MyMeth(ref int x).

Несмотря на то что модификаторы параметров ref и out учитываются, когда при­нимается решение о перегрузке метода, отличие между ними не столь существенно. Например, два следующих варианта метода MyMeth() оказываются недействите­льными.

// Неверно!

public void MyMeth(out int x) { //...

public void MyMeth(ref int x) { //...

В данном случае компилятор не в состоянии различить два варианта одного и того же метода MyMeth() только на основании того, что в одном из них используется пара­метр out, а в другом  параметр ref.

Перегрузка методов поддерживает свойство полиморфизма, поскольку именно та­ким способом в С# реализуется главный принцип полиморфизма: один интерфейс  множество методов. Для того чтобы стало понятнее, как это делается, обратимся к конкретному примеру. В языках программирования, не поддерживающих перегрузку методов, каждому методу должно быть присвоено уникальное имя. Но в программировании зачастую возникает потребность реализовать по сути один и тот же метод для обработки разных типов данных. Допустим, что требуется функция, определяю­щая абсолютное значение. В языках, не поддерживающих перегрузку методов, обычно приходится создавать три или более вариантов такой функции с несколько отличаю­щимися, но все же разными именами. Например, в С функция abs() возвращает аб­солютное значение целого числа, функция labs()  абсолютное значение длинного целого числа, а функция fabs()  абсолютное значение числа с плавающей точкой обычной (одинарной) точности.

В С перегрузка не поддерживается, и поэтому у каждой функции должно быть свое, особое имя, несмотря на то, что все упомянутые выше функции, по существу, делают одно и то же  определяют абсолютное значение. Но это принципиально усложняет положение, поскольку приходится помнить имена всех трех функций, хотя они реали­зованы по одному и тому же основному принципу. Подобные затруднения в С# не воз­никают, поскольку каждому методу, определяющему абсолютное значение, может быть присвоено одно и то же имя. И действительно, в состав библиотеки классов для среды .NET Framework входит метод Abs (), который перегружается в классе System.Math для обработки данных разных числовых типов. Компилятор С# сам определяет, какой имен­но вариант метода Abs() следует вызывать, исходя из типа передаваемого аргумента.

Главная ценность перегрузки заключается в том, что она обеспечивает доступ к свя­занным вместе методам по общему имени. Следовательно, имя Abs обозначает общее выполняемое действие, а компилятор сам выбирает конкретный вариант метода по обстоятельствам. Благодаря полиморфизму несколько имен сводятся к одному. Не­смотря на всю простоту рассматриваемого здесь примера, продемонстрированный в нем принцип полиморфизма можно расширить, чтобы выяснить, каким образом перегрузка помогает справляться с намного более сложными ситуациями в програм­мировании.

Когда метод перегружается, каждый его вариант может выполнять какое угодно действие. Для установления взаимосвязи между перегружаемыми методами не суще­ствует какого-то одного правила, но с точки зрения правильного стиля программи­рования перегрузка методов подразумевает подобную взаимосвязь. Следовательно, использовать одно и то же имя для несвязанных друг с другом методов не следует, хотя это и возможно. Например, имя Sqr можно было бы выбрать для методов, воз­вращающих квадрат и квадратный корень числа с плавающей точкой. Но ведь это принципиально разные операции. Такое применение перегрузки методов противо­речит ее первоначальному назначению. На практике перегружать следует только тесно связанные операции.

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