Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
Скачать 5.05 Mb.
|
189 возвращает значение true только в том случае, если сравниваемые объекты содержат одинаковые значения в соответствующих переменных экземпляров. Метод сору() присваивает значения alpha и beta объекта, переданного в качестве аргумента ob , переменным экземпляра alpha и beta вызывающего объекта. Обратите внимание на то, что в обоих случаях в качестве типа параметра указан класс MyClass . Как видно из этой программы, объекты (имеющие тип класса) передаются методам точно так же, как и значения встроенных типов. Как происходит передача аргументов В предыдущем примере передача аргументов методу представляла собой простую задачу. Однако существуют некоторые нюансы, которые там не были показаны. В определенных случаях результаты передачи объекта будут отличаться от результатов передачи необъектных аргументов. Чтобы понять причину, необходимо рассмотреть два возможных способа передачи аргументов. Первый способ называется вызовом по значению (call-by-value). В этом случае значение аргумента копируется в формальный параметр метода. Следовательно, изменения, внесенные в параметр метода, не влияют на аргумент, используемый при вызове. Второй способ передачи аргумента называется вызовом по ссылке (call-by-reference). Здесь для получения доступа к реальному аргументу, заданному при вызове, используется ссылка на аргумент. Это значит, что изменения, внесенные в параметр, окажут воздействие на аргумент, использованный при вызове метода. При передаче методу значения нессылочного типа (например, int или double ) имеет место вызов по значению. Таким образом, то, что происходит с параметром, который получает аргумент, никак не влияет на данные вне метода. Рассмотрим следующую программу: // Демонстрация передачи простых типов по значению. using System; class Test { /* Этот метод не оказывает влияния на аргументы, используемые в его вызове. */ public void noChange(int i, int j) { i = i + j; j = -j; } } class CallByValue { public static void Main() { Test ob = new Test(); int a = 15, b = 20; Console.WriteLine("а и b перед вызовом: " + a + " " + b); ob.noChange(a, b); Console.WriteLine("а и b после вызова: " + a + " " + b); } } 190 Часть I. Язык C# Результаты выполнения этой программы выглядят так: а и b перед вызовом: 15 20 а и b после вызова: 15 20 Как видите, операции, которые выполняются внутри метода noChange() , не влияют на значения а и b , используемые при вызове метода. При передаче методу ссылки на объект ситуация несколько усложняется. Строго говоря, сама ссылка передается по значению, Таким образом, здесь выполняется копирование ссылки, после чего, как мы уже знаем, изменения, вносимые в параметр, не окажут влияния на аргумент. (Например, если заставить параметр ссылаться на новый объект, это не коснется объекта, на который ссылается аргумент.) Однако (внимание, это очень важно) изменения, вносимые в объект, на который ссылается параметр, повлияют самым прямым образом на объект, на который ссылается аргумент. Давайте разберемся, почему так происходит. Вспомните, что при создании переменной типа класса вы создаете лишь ссылку на объект. Следовательно, при передаче этой ссылки методу параметр, который получает ее, будет ссылаться на тот же объект, на который ссылается и аргумент. Это как раз означает, что объекты передаются методу посредством вызова по ссылке. Как следствие, изменения в объекте внутри метода влияют на объект, используемый в качестве аргумента. Рассмотрим, например, следующую программу: // Демонстрация передачи объектов по ссылке. using System; class Test { public int a, b; public Test(int i, int j) { a = i; b = j; } /* Передаем объект. Теперь ob.a и ob.b в объекте, используемом при вызове, будут изменены. */ public void change(Test ob) { ob.a = ob.a + ob.b; ob.b = -ob.b; } } class CallByRef { public static void Main() { Test ob = new Test(15, 20); Console.WriteLine("ob.a и ob.b перед вызовом: " + ob.a + " " + ob.b); ob.change(ob); Console.WriteLine("ob.а и ob.b после вызова: " + ob.a + " " + ob.b); } } Эта программа генерирует такие результаты: ob.a и ob.b перед вызовом: 15 20 ob.a и ob.b после вызова: 35 -20 Глава 8. Подробнее о методах и классах 191 Как видите, в этом случае действия внутри метода change() влияют на объект, используемый в качестве аргумента. Итак, при передаче методу ссылки на объект сама ссылка передается посредством вызова по значению, И поэтому делается копия этой ссылки. Но поскольку передаваемое значение ссылается на некоторый объект, копия этого значения ссылается на тот же объект. Использование ref - и out-параметров Выше мы рассмотрели пример, в котором значения нессылочного типа (например, int или char ) передавались методу по значению. И мы убедились в том, что изменения, вносимые в параметр, который получает значение нессылочного типа, не влияет на реальный аргумент, используемый при вызове метода. Однако такое поведение можно изменить. Используя ключевые слова ref и out , можно передать значение любого нессылочного типа по ссылке. Тем самым мы позволим методу изменить аргумент, используемый при вызове. Прежде чем вникнуть в механизм использования ключевых слов ref и out , стоит понять, когда может потребоваться передача нессылочного типа по ссылке. В общем случае существует две причины: позволить методу менять содержимое его аргументов или возвращать более одного значения. Рассмотрим подробно каждую из причин. Часто программисту нужен метод, способный оперировать реальными аргументами, передаваемыми ему при вызове. Классическим примером служит метод swap() , который меняет местами значения двух аргументов. При передаче значений нессылочного типа по значению невозможно написать метод обмена значениями двух аргументов, например типа int , используя действующий по умолчанию C#-механизм передачи параметров по значению. Эта проблема решается с помощью модификатора ref Как вы знаете, инструкция return позволяет методу возвратить значение тому, кто сделал вызов. Однако метод может вернуть в результате одного вызова только одно значение. А как быть, если нужно вернуть два или больше значений? Например, нужен метод, который разбивает вещественное число на целую и дробную части. Ведь в этом случае метод должен возвратить два значения: целую часть и дробную составляющую. Такой метод невозможно написать, используя только одно возвращаемое значение. Вот с этой проблемой и помогает справиться модификатор out Использование модификатора ref Модификатор параметра ref заставляет C# организовать вместо вызова по значению вызов по ссылке. Модификатор ref используется при объявлении метода и его вызове. Рассмотрим простой пример. Следующая программа создает метод sqr() , который возвращает квадрат целочисленного аргумента. Обратите внимание на использование и расположение модификатора ref // Использование модификатора ref для передачи // значения нессылочного типа по ссылке. using System; class RefTest { /* Этот метод изменяет свои аргументы. Обратите внимание на использование модификатора ref. */ public void sqr(ref int i) { i = i * i; 192 Часть I. Язык C# } } class RefDemo { public static void Main() { RefTest ob = new RefTest(); int a = 10; Console.WriteLine("а перед вызовом: " + a); ob.sqr(ref a); // Обратите внимание //на использование модификатора ref. Console.WriteLine("а после вызова: " + a); } } Обратите внимание на то, что модификатор стоит в начале объявления параметра в методе и предшествует имени аргумента при вызове метода. Приведенные ниже результаты выполнения этой программы подтверждают, что значение аргумента а действительно было модифицировано методом sqr() а перед вызовом: 10 а после вызова: 100 Используя модификатор ref , можно написать метод, который меняет значения двух аргументов нессылочного типа. Например, рассмотрим программу, которая содержит метод swap() , меняющий значения двух целочисленных аргументов, передаваемых ему при вызове. // Обмен значениями двух аргументов. using System; class Swap { // Этот метод меняет местами значения своих аргументов. public void swap(ref int a, ref int b) { int t; t = a; a = b; b = t; } } class SwapDemo { public static void Main() { Swap ob = new Swap(); int x = 10, y = 20; Console.WriteLine("x и y перед вызовом: " + x + " " + y); ob.swap(ref x, ref y); Console.WriteLine("x и y после вызова: " + x + " " + y); } } Глава 8. Подробнее о методах и классах 193 Вот результаты выполнения этой программы: x и y перед вызовом: 10 20 x и y после вызова: 20 10 И еще одно немаловажное замечание. Аргументу, передаваемому методу “в сопровождении” модификатора ref , должно быть присвоено значение до вызова метода. Дело в том, что, если метод получает такой аргумент, значит, параметр ссылается на действительное значение. Поэтому, используя модификатор ref , нельзя использовать метод, присваивая его аргументу начальное значение. Использование модификатора out Иногда приходится использовать ссылочный параметр не для передачи значения методу, а для его получения из метода. Например, может понадобиться метод, который выполняет некоторую функцию, например открывает сетевой сокет, и возвращает код в параметре ссылочного типа, означающем удачное или неудачное выполнение этой операции. В этом случае методу не нужно передавать какую бы то ни было информацию, но от метода необходимо получить определенный результат. Если воспользоваться модификатором ref , то мы должны инициализировать ref -параметр некоторым значением до вызова метода. Таким образом, использование ref -параметра потребовало бы присвоения его аргументу фиктивного значения только для того, чтобы удовлетворить это требование. К счастью, в C# предусмотрена альтернатива получше, а именно использование модификатора out Модификатор out подобен модификатору ref за одним исключением: его можно использовать только для передачи значения из метода. Совсем не обязательно (и даже не нужно) присваивать переменной, используемой в качестве out -параметра, начальное значение до вызова метода. Более того, предполагается, что out -параметр всегда “поступает” в метод без начального значения, но метод (до своего завершения) обязательно должен присвоить этому параметру значение. Таким образом, после обращения к методу out -параметр будет содержать определенное значение. Перед вами пример использования out -параметра. В классе Decompose метод parts() разбивает вещественное число на целую и дробную части. Обратите внимание на то, как возвращается автору вызова этого метода каждый компонент. // Использование модификатора out. using System; class Decompose { /* Метод разбивает число с плавающей точкой на целую и дробную части. */ public int parts(double n, out double frac) { int whole; whole = (int) n; frac = n - whole; // Передаем дробную часть // посредством параметра frac. return whole; // Возвращаем целую часть числа. } } class UseOut { public static void Main() { Decompose ob = new Decompose(); 194 Часть I. Язык C# int i; double f; i = ob.parts(10.125, out f); Console.WriteLine("Целая часть числа равна " + i); Console.WriteLine("Дробная часть числа равна " + f); } } При выполнении этой программы получаем такие результаты: Целая часть числа равна 10 Дробная часть числа равна 0,125 Метод parts() возвращает два значения. Целая часть числа n возвращается с помощью инструкции return . Дробная часть числа n передается автору вызова посредством out -параметра frac . Как показывает этот пример, используя out -параметр, можно добиться того, чтобы метод возвращал не одно, а два значения. Более того, синтаксис языка C# не ограничивает вас использованием только одного out -параметра. Метод может возвращать посредством out -параметров столько значений, сколько вам нужно. Ниже приводится пример использования двух out -параметров. Метод isComDenom() выполняет две функции. Во-первых, он определяет, существует ли у двух заданных целых чисел общий множитель. При этом метод возвращает значение true , если такой множитель существует, и значение false в противном случае. Во-вторых, если эти числа таки имеют общий множитель, метод isComDenom() с помощью out -параметров возвращает наименьший и наибольший общие множители. // Демонстрация использования двух out-параметров. using System; class Num { /* Метод определяет, имеют ли x и v общий множитель. Если да, метод возвращает наименьший и наибольший общие множители в out-параметрах. */ public bool isComDenom(int x, int y, out int least, out int greatest) { int i; int max = x < y ? x : y; bool first = true; least = 1; greatest = 1; // Находим наименьший и наибольший общие множители. for(i=2; i <= max/2 + 1; i++) { if( ((y%i)==0) & ((x%i)==0) ) { if(first) { least = i; first = false; } greatest = i; } } if(least != 1) return true; Глава 8. Подробнее о методах и классах 195 else return false; } } class DemoOut { public static void Main() { Num ob = new Num(); int lcd, gcd; if(ob.isComDenom(231, 105, out lcd, out gcd)) { Console.WriteLine("Lcd для чисел 231 и 105 равен " + lcd); Console.WriteLine("Gcd для чисел 231 и 105 равен " + gcd); } else Console.WriteLine( "Для чисел 35 и 49 общего множителя нет."); if(ob.isComDenom(35, 51, out lcd, out gcd)) { Console.WriteLine("Lcd для чисел 35 и 51 равен " + lcd); Console.WriteLine("Gcd для чисел 35 и 51 равен " + gcd); } else Console.WriteLine( "Для чисел 35 и 51 общего множителя нет."); } } Обратите внимание на то, что в функции Main() переменным lcd и gcd не присваиваются значения до вызова функции isComDenom() . Это было бы ошибкой, если бы эти переменные были не out -, a ref -параметрами. Этот метод в зависимости от существования общих множителей у заданных двух чисел возвращает либо true , либо false . Причем, если общие множители существуют, то наименьший и наибольший из них возвращаются в out -параметрах led и gcd , соответственно. ( Lcd — это аббревиатура от least common denominator, т.е. наименьший общий множитель, a gcd — это аббревиатура от greatest common denominator, т.е. наибольший общий множитель.) Вот каковы результаты выполнения этой программы: Lcd для чисел 231 и 105 равен 3 Gcd для чисел 231 и 105 равен 21 Для чисел 35 и 51 общего множителя нет. Использование модификаторов ref и out для ссылочных параметров Использование модификаторов ref и out не ограничивается параметрами типа значений. Их также можно применить к ссылочным параметрам, т.е. параметрам, обеспечивающим передачу объектов. Если параметр ссылочного типа модифицируется одним из модификаторов ref и out , то по сути реализуется передача ссылки по ссылке. Это позволяет методу изменять объект, на который указывает ссылка-параметр. Рассмотрим программу, в которой используются ссылочные ref -параметры для обмена объектами, на которые указывают две ссылки. 196 Часть I. Язык C# // Обмен двух ссылок. using System; class RefSwap { int a, b; public RefSwap(int i, int j) { a = i; b = j; } public void show() { Console.WriteLine("a: {0}, b: {1}", a, b); } // Этот метод теперь изменяет свои аргументы. public void swap(ref RefSwap ob1, ref RefSwap ob2) { RefSwap t; t = ob1; ob1 = ob2; ob2 = t; } } class RefSwapDemo { public static void Main() { RefSwap x = new RefSwap(1, 2); RefSwap y = new RefSwap(3, 4); Console.Write("x перед вызовом: "); x.show(); Console.Write("y перед вызовом: "); y.show(); Console.WriteLine(); // Обмениваем объекты, на которые ссылаются x и y. x.swap(ref x, ref y); Console.Write("x после вызова: "); x.show(); Console.Write("y после вызова: "); y.show(); } } При выполнении этой программы получаем такие результаты: x перед вызовом: a: 1, b: 2 y перед вызовом: a: 3, b: 4 x после вызова: a: 3, b: 4 y после вызова: a: 1, b: 2 Глава 8. Подробнее о методах и классах 197 В этом примере метод swap() меняет местами объекты, на которые ссылаются два его аргумента. До вызова метода swap() переменная x ссылается на объект, который содержит значения 1 и 2, а переменная y ссылается на объект, который содержит значения 3 и 4. После обращения к методу swap() переменная x ссылается на объект, который содержит значения 3 и 4, а переменная y ссылается на объект, который содержит значения 1 и 2. Если бы здесь не были использованы ref -параметры, то обмен внутри метода swap() никак не повлиял бы на “среду” за его пределами. Это можно доказать, убрав модификаторы из метода swap() Использование переменного количества аргументов При создании метода обычно заранее известно количество аргументов, которые будут ему передаваться. Но иногда необходимо, чтобы метод принимал произвольное число аргументов. Рассмотрим, например, метод, который находит минимальное значение в наборе чисел. Такому методу может быть передано два, три или четыре значения. В любом случае метод должен возвращать наименьшее значение. Такой метод невозможно создать при использовании обычных параметров. Здесь необходимо применить специальный тип параметра, который заменяет собой произвольное количество параметров. Это реализуется с помощью модификатора params Модификатор params используется для объявления параметра-массива, который сможет получить некоторое количество аргументов (в том числе и нулевое). Количество элементов в массиве будет равно числу аргументов, переданных методу. Рассмотрим пример, в котором модификатор params используется для создания метода minVal() , возвращающего минимальное значение из набора. // Демонстрация использования модификатора params. using System; class Min { public int minVal(params int[] nums) { int m; if(nums.Length == 0) { Console.WriteLine("Ошибка: нет аргументов."); return 0; } m = nums[0]; for(int i=1; i < nums.Length; i++) if(nums[i] < m) m = nums[i]; return m; } } class ParamsDemo { public static void Main() { Min ob = new Min(); int min; int a = 10, b = 20; |