1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 29
Текст из файла (страница 29)
функции передается значение, содержащееся в этой переменной, но не сама переменная.При такой передаче изменение значения соответствующей переменной внутрифункции не вызовет изменения значения переданной переменной в вызывающейпрограмме, что и демонстрируется следующей программой:// PassByValue - программа для демонстрации// передачи аргумента по значениюusingSystem;namespaceсемантикиPassByValue{publicclassProgram{156Часть III.Объектно-основанное программировал// Update - функция п ы т а е т с я модифицировать з н а ч е н и я// аргументов,переданные ей; обратите внимание, что// функции в к л а с с е м о г у т быть о б ъ я в л е н ы в любом п о р я д к еpublic s t a t i c v o i d U p d a t e ( i n t i,double d){i = 10;d = 2 0.0;}publics t a t i cvoidMain(string []args){// Объявляем и и н и ц и а л и з и р у е м , д в е п е р е м е н н ы еint i = 1;double d = 2.0;Console.WriteLine("Перед вызовом U p d a t e ( i n t , d o u b l e ) : " ) ;C o n s o l e .
W r i t e L i n e ( " i = " + i + ", d = " + d ) ;// Вызываем функциюU p d a t e ( i , d) ;// О б р а т и т е в н и м а н и е — з н а ч е н и я 1 и 2 . 0 не и з м е н и л и с ьConsole . W r i t e L i n e ("После вызова U p d a t e ( i n t , d o u b l e ) :") ,C o n s o l e . W r i t e L i n e ( " i = " + i + ", d = " + d) ;// Ожидаем п о д т в е р ж д е н и я п о л ь з о в а т е л яConsole.WriteLine("Нажмите <Enter> для " +"завершения программы...");Console.Read();Выполнение программы дает такой вывод на экран:Перед в ы з о в о м U p d a t e ( i n t , d o u b l e ) :i=1, d = 2После в ы з о в а U p d a t e ( i n t , d o u b l e ) :i = 1, d = 2Нажмите < E n t e r > д л я з а в е р ш е н и я п р о г р а м м ы . .
.Вызов U p d a t e () передает функции значения 1 и 2.0, а не ссылки на переменные i и.Таким образом, изменение их значений в функции никак не влияет на значения исходныхпеременных в вызывающей программе.Передача по с с ы л к еПередача функции переменных с типами-значениями по ссылке имеет ряд преимуществ — этот метод передачи используется, в частности, когда вызывающая программахочет предоставить функции возможность изменять значение передаваемой в качествеаргумента переменной. Приведенная далее программа P a s s B y R e f e r e n c e демонстрирует эту возможность.С# дает программисту возможность передачи аргументов по ссылке с использованием ключевых слов r e f и o u t . Слегка измененная программаP a s s B y V a l u e демонстрирует, как это делается.Глава 7.Функции функций157// PassByReference - программа для// передачи аргумента по ссылкеusingSystem;namespaceдемонстрациисемантикиPassByValue{publicclassProgram{// Update - функция пытается модифицировать значения// аргументов,переданные ей; обратите внимание,на// передачу аргументов как ref и outpublic s t a t i c void Update(refint i,out double d){i =d =10;20.0;}publics t a t i cvoidMain(string[]args){// Объявляем две переменные и одну инициализируемi n t i = 1;doubled;Console.WriteLine("Перед вызовом " +"Update(refint,out double):");Console.WriteLine("i = " + i +", d н е и н и ц и а л и з и р о в а н а " ) ;'// Вызываем функциюUpdate(ref i,out d);////Обратите вниманиеравно20.0—значениеConsole.WriteLine("Послевызоваiравно10,значениеd""Update (refint,out double):"),C o n s o l e .
W r i t e L i n e ( " i = " + i + ", d = " + d ) ;/ / Ожидаем п о д т в е р ж д е н и я п о л ь з о в а т е л яConsole.WriteLine("Нажмите <Enter> для"завершения"+программы...");Console.Read();Ключевое слово r e f указывает С#, что в функцию следует передать ссылку на i, а непросто значение этой переменной. Следовательно, изменения, выполненные с этой переменной, оказываются экспортированы обратно вызывающей программе.Подобно этому, ключевое слово o u t говорит: "Передай ссылку на эту переменную,но можешь никак не заботиться о ее значении, поскольку оно все равно не будет использоваться и будет перезаписано в процессе работы функции". Это ключевое слово годитсятогда, когда переменная применяется исключительно для того, чтобы вернуть значениевызывающей программе.158Часть III.
Объектно-основанное программированиеВыполнение рассмотренной программы P a s s B y R e f е г е п с е приводит к следующему выводу на экран:Перед в ы з о в о м U p d a t e ( r e f i n t , o u t d o u b l e ) :i = 1, d н е и н и ц и а л и з и р о в а н аПосле в ы з о в а U p d a t e ( r e f i n t , o u t d o u b l e ) :i = 10, d = 20Нажмите < E n t e r > д л я з а в е р ш е н и я п р о г р а м м ы . . .Аргумент, передаваемый как o u t , всегда считается передаваемым так же какr e f , т.е. писать r e fo u t — это тавтология. Кроме того, при передаче аргументов по ссылке вы должны всегда передавать только переменные — передача литеральных значений, например просто числа 2 вместо переменной типаi n t , в этом случае приводит к ошибке.Обратите внимание, что начальные значения i и d переписываются в функции U p d a t e d .
После возврата в функцию M a i n () эти переменные остаются с измененнымив функции U p d a t e () значениями. Сравните это поведение с программой P a s s B y Value, где внесенные изменения не сохраняются при выходе из функции.Не передавайте переменную по с с ы л к е д в а ж д ыНикогда не передавайте по ссылке одну и ту же переменную дважды в однуфункцию, поскольку это может привести к неожиданным побочным эффектам.
Описать эту ситуацию труднее, чем просто продемонстрировать пример программы. Внимательно взгляните на функцию U p d a t e () в приведенном листинге.// PassByRef e r e n c e E r r o r - д е м о н с т р а ц и я п о т е н ц и а л ь н о/ / ошибочной с и т у а ц и и п р и в ы з о в е ф у н к ц и и с п е р е д а ч е й// аргументов по с с ы л к еusing S y s t e m ;namespacePassByRef e r e n c e E r r o r{publicclassProgram{// Update - э т а функция п ы т а е т с я изменить з н а ч е н и я// переданных ей а р г у м е н т о вpublic s t a t i c v o i d D i s p l a y A n d U p d a t e ( r e fint nVarl,refi n t nVar2){Console.WriteLine("НачальноеnVarl);nVarl = 10;значениеnVarl-"+Console.WriteLine("НачальноеnVar2);n V a r 2 = 2 0;значениеnVar2-"+}publicstaticvoidMain(string[]args){Глава 7.Функции функций159// О б ъ я в л я е м и и н и ц и а л и з и р у е м переменнуюi n t п = 1;C o n s o l e .
W r i t e L i n e ( " П е р е д вызовом " +"Update(ref n,ref n ) : " ) ;Console.WriteLine("n = " + n);Console.WriteLine();// Вызываем функциюDisplayAndUpdate(refn,refn);// Обратите внимание на то, как изменяется значение// - не т а к , к а к ожидалось от э т о й п е р е м е н н о й и// функции, в которую она была п е р е д а н аConsole.WriteLine();Console.WriteLine("После вызова " +"Update(ref n,ref n ) : " ) ;C o n s o l e . W r i t e L i n e ( " n = " + n) ;п// Ожидаем п о д т в е р ж д е н и я п о л ь з о в а т е л яConsole.WriteLine("Нажмите <Enter> для " +"завершения программы..
. " ) ;Console.Read();}}В этом примере функция U p d a t e ( r e f i n t , r e f i n t ) объявлена как функциюс двумя целыми аргументами, передаваемыми по ссылке — ив этом нет ничего некорректного или необычного. Проблема возникает тогда, когда в функцию передается однаи та же переменная как в качестве первого, так и второго аргумента. Внутри функциипроисходит изменение переменной n V a r l , которая ссылается на переменную п, от ееначального значения 1 до значения 10.
Затем происходит изменение переменной nVar:которая тоже ссылается на переменную п, от ее начального значения 1 до значения 20и побочное действие заключается в том, что переменная п теряет ранее присвоенноезначение 10 и получает новое значение 20.Это видно из приведенного ниже вывода программы на экран:Перед вызовом U p d a t e ( r e f n,ref n ) :n = 1НачальноеНачальноезначениезначениеnVarlnVar2-110После вызова U p d a t e ( r e f n,refn = 20Нажмите < E n t e r > д л я з а в е р ш е н и яn):программы...Понятно, что ни программист, который писал функцию U p d a t e ( ) , ни программист, ее использовавший, не рассчитывали на такой экзотический результат.
Вся проблема оказалась в том, что одна и та же переменная была переданв одну и ту же функцию по ссылке больше одного раза. Никогда так не поступайте, если только вы не абсолютно уверены в том, чего именно вы хотите добиться таким действием.160Часть III. Объектно-основанное программированыВы можете передавать одно и то же значение в функцию сколько угодно раз,если передаете его по значению.Почему некоторые аргументы используются только д л я возврата значений?С# по мере возможности старается защитить программиста от всех глупостей, которыеон может вольно или невольно сделать. Одна из этих глупостей — программист можетзабыть проинициализировать переменную перед ее первым применением (особенно часто это случается с переменными-счетчиками). С# генерирует ошибку, когда вы пытаетесь использовать объявленную, но не инициализированную переменную:int n V a r i a b l e ;Console.WriteLine("Этоn V a r i a b l e = 1;Console.WriteLine("Аошибкаэто-нет"+,nVariable);"+nVariable);Однако C# не в состоянии отслеживать переменные в функции:voidSomeFunction(refintnVariable){Console.WriteLine("Ошибкаилинет?"+nVariable);},Откуда функция S o m e F u n c t i o n () может знать, была ли переменная n V a r i a b l eинициализирована перед вызовом функции? Это невозможно.