Поместить символы А-J в стек stk1. Стек stk1 заполнен. Содержимое стека stk1: JIHGFEDCBA Стек stk1 пуст. Вновь поместить символы А-J в стек stk1. А теперь извлечь символы из стека stk1 и поместить их в стек stk2. Содержимое стека stk2: ABCDEFGHIJ Поместить 5 символов в стек stk3. Емкость стека stk3: 10 Количество объектов в стеке stk3: 5 ## Передача объектов методам по ссылке В приведенных до сих пор примерах программ при указании параметров, пере даваемых методам, использовались типы значений, например int или double. Но в методах можно также использовать параметры ссылочного типа, что не только пра вильно, но и весьма распространено в ООП. Подобным образом объекты могут пере даваться методам по ссылке. В качестве примера рассмотрим следующую программу.
// Пример передачи объектов методам по ссылке. using System;
class MyClass { int alpha, beta; public MyClass(int i, int j) { alpha = i; beta = j; } // Возвратить значение true, если параметр ob // имеет те же значения, что и вызывающий объект. public bool SameAs(MyClass ob) { if((ob.alpha == alpha) & (ob.beta == beta)) return true; else return false; } // Сделать копию объекта ob. public void Copy(MyClass ob) { alpha = ob.alpha; beta = ob.beta; } public void Show() { Console.WriteLine("alpha: (0), beta: (1}", alpha, beta); } }
class PassOb { static void Main() { MyClass ob1 = new MyClass(4, 5); MyClass ob2 = new MyClass(6, 7); Console.Write("ob1: "); ob1.Show(); Console.Write("ob2: "); ob2.Show(); if(ob1.SameAs(ob2)) Console.WriteLine("ob1 и ob2 имеют одинаковые значения."); else Console.WriteLine("ob1 и ob2 имеют разные значения."); Console.WriteLine(); // А теперь сделать объект ob1 копией объекта ob2. ob1.Copy(ob2); Console.Write("оЫ после копирования: "); ob1.Show(); if(ob1.SameAs(ob2)) Console.WriteLine("ob1 и ob2 имеют одинаковые значения."); else Console.WriteLine("ob1 и ob2 имеют разные значения."); } } Выполнение этой программы дает следующий результат.
ob1: alpha: 4, beta: 5 ob2: alpha: 6, beta: 7 ob1 и ob2 имеют разные значения. ob1 после копирования: alpha: 6, beta: 7 ob1 и оb2 имеют одинаковые значения. Каждый из методов SameAs() и Сору() в приведенной выше программе получа ет ссылку на объект типа MyClass в качестве аргумента. Метод SameAs() сравнивает значения переменных экземпляра alpha и beta в вызывающем объекте со значени ями аналогичных переменных в объекте, передаваемом посредством параметра ob. Данный метод возвращает логическое значение true только в том случае, если оба объекта имеют одинаковые значения этих переменных экземпляра. А метод Сору() присваивает значения переменных alpha и beta из объекта, передаваемого по ссылке посредством параметра ob, переменным alpha и beta из вызывающего объекта. Как показывает данный пример, с точки зрения синтаксиса объекты передаются методам по ссылке таким же образом, как и значения обычных типов. ### Способы передачи аргументов методу Как показывает приведенный выше пример, передача объекта методу по ссылке делается достаточно просто. Но в этом примере показаны не все нюансы данного про цесса. В некоторых случаях последствия передачи объекта по ссылке будут отличаться от тех результатов, к которым приводит передача значения обычного типа. Для выяс нения причин этих отличий рассмотрим два способа передачи аргументов методу. Первым способом является вызов по значению. В этом случае значение аргумента копируется в формальный параметр метода. Следовательно, изменения, вносимые в параметр метода, не оказывают никакого влияния на аргумент, используемый для вы зова. А вторым способом передачи аргумента является вызов по ссылке. В данном случае параметру метода передается ссылка на аргумент, а не значение аргумента. В методе эта ссылка используется для доступа к конкретному аргументу, указываемому при вы зове. Это означает, что изменения, вносимые в параметр, будут оказывать влияние на аргумент, используемый для вызова метода. По умолчанию в C# используется вызов по значению, а это означает, что копия ар гумента создается и затем передается принимающему параметру. Следовательно, при передаче значения обычного типа, например int или double, все, что происходит с параметром, принимающим аргумент, не оказывает никакого влияния за пределами метода. В качестве примера рассмотрим следующую программу.
// Передача аргументов обычных типов по значению. using System; class Test { / Этот метод не оказывает никакого влияния на аргументы, используемые для его вызова. / public void NoChange(int i, int j) { i = i + j; j = -j; } }
class CallByValue { 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); } } Вот какой результат дает выполнение этой программы.
а и b до вызова: 15 20 а и b после вызова: 15 20 Как видите, операции, выполняемые в методе NoChange(), не оказывают никакого влияния на значения аргументов а и b, используемых для вызова данного метода. Это опять же объясняется тем, что параметрам i и j переданы копии значений аргументов а и b, а сами аргументы а и b совершенно не зависят от параметров i и j. В частности, присваивание параметру i нового значения не будет оказывать никакого влияния на аргумент а. Дело несколько усложняется при передаче методу ссылки на объект. В этом случае сама ссылка по-прежнему передается по значению. Следовательно, создается копия ссылки, а изменения, вносимые в параметр, не оказывают никакого влияния на аргу мент. (Так, если организовать ссылку параметра на новый объект, то это изменение не повлечет за собой никаких последствий для объекта, на который ссылается аргумент.) Но главное отличие вызова по ссылке заключается в том, что изменения, происходя щие с объектом, на который ссылается параметр, окажут влияние на тот объект, на который ссылается аргумент. Попытаемся выяснить причины подобного влияния. Напомним, что при создании переменной типа класса формируется только ссыл ка на объект. Поэтому при передаче этой ссылки методу принимающий ее параметр будет ссылаться на тот же самый объект, на который ссылается аргумент. Это означает, что и аргумент, и параметр ссылаются на один и тот же объект и что объекты, по суще ству, передаются методам по ссылке. Таким образом, объект в методе будет оказывать влияние на объект, используемый в качестве аргумента. Для примера рассмотрим сле дующую программу.
// Передача объектов по ссылке. using System;
class Test { public int a, b; public Test(int i, int j) { a = i; b = j; } / Передать объект. Теперь переменные ob.а и ob.b из объекта, используемого в вызове метода, будут изменены. / public void Change(Test ob) { ob.a = ob.a + ob.b; ob.b = -ob.b; } }
class CallByRef { static void Main() { Test ob = new Test(15, 20); Console.WriteLine("ob.а и 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 Как видите, действия в методе Change() оказали в данном случае влияние на объект, использовавшийся в качестве аргумента. Итак, подведем краткий итог. Когда объект передается методу по ссылке, сама ссыл ка передается по значению, а следовательно, создается копия этой ссылки. Но эта ко пия будет по-прежнему ссылаться на тот же самый объект, что и соответствующий ар гумент. Это означает, что объекты передаются методам неявным образом по ссылке. ## Использование модификаторов параметров ref и out Как пояснялось выше, аргументы простых типов, например int или char, переда ются методу по значению. Это означает, что изменения, вносимые в параметр, прини мающий значение, не будут оказывать никакого влияния на аргумент, используемый для вызова. Но такое поведение можно изменить, используя ключевые слова ref и out для передачи значений обычных типов по ссылке. Это позволяет изменить в самом методе аргумент, указываемый при его вызове. Прежде чем переходить к особенностям использования ключевых слов ref и out, полезно уяснить причины, по которым значение простого типа иногда требуется пе редавать по ссылке. В общем, для этого существуют две причины: разрешить методу изменить содержимое его аргументов или же возвратить несколько значений. Рассмо трим каждую из этих причин более подробно. Нередко требуется, чтобы метод оперировал теми аргументами, которые ему пере даются. Характерным тому примером служит метод Swap(), осуществляющий пере становку значений своих аргументов. Но поскольку аргументы простых типов пере даются по значению, то, используя выбираемый в C# по умолчанию механизм вызова по значению для передачи аргумента параметру, невозможно написать метод, меняю щий местами значения двух его аргументов, например типа int. Это затруднение раз решает модификатор ref. Как вам должно быть уже известно, значение возвращается из метода вызывающей части программы с помощью оператора return. Но метод может одновременно воз вратить лишь одно значение. А что, если из метода требуется возвратить два или более фрагментов информации, например, целую и дробную части числового значения с плавающей точкой? Такой метод можно написать, используя модификатор out. ### Использование модификатора параметра ref Модификатор параметра ref принудительно организует вызов по ссылке, а не по значению. Этот модификатор указывается как при объявлении, так и при вызове мето да. Для начала рассмотрим простой пример. В приведенной ниже программе создает ся метод Sqr(), возвращающий вместо своего аргумента квадрат его целочисленного значения. Обратите особое внимание на применение и местоположение модификато ра ref.