355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Герберт Шилдт » C# 4.0: полное руководство » Текст книги (страница 15)
C# 4.0: полное руководство
  • Текст добавлен: 6 апреля 2017, 04:00

Текст книги "C# 4.0: полное руководство"


Автор книги: Герберт Шилдт



сообщить о нарушении

Текущая страница: 15 (всего у книги 83 страниц)

    foreach(string s in strs) {

      switch (s) {

      case «один»:

        Console.Write(1);

        break;

      case «два»:

        Console.Write(2);

        break;

      case «три»:

        Console.Write(3);

        break;

      }

    }

    Console.WriteLine();

  }

}

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

12321

ГЛАВА 8 Подробнее о методах и классах

В данной главе возобновляется рассмотрение классов и методов. Оно начинается с пояснения механизма управления доступом к членам класса. А затем обсуждаются такие вопросы, как передача и возврат объектов, перегрузка методов, различные формы метода Main(), рекурсия и применение ключевого слова static.


Управление доступом к членам класса

Поддержка свойства инкапсуляции в классе дает два главных преимущества. Во-первых, класс связывает данные с кодом. Это преимущество использовалось в предыдущих примерах программ, начиная с главы 6. И во-вторых, класс предоставляет средства для управления доступом к его членам. Именно эта, вторая преимущественная особенность и будет рассмотрена ниже.

В языке С#, по существу, имеются два типа членов класса: открытые и закрытые, хотя в действительности дело обстоит немного сложнее. Доступ к открытому члену свободно осуществляется из кода, определенного за пределами класса. Именно этот тип члена класса использовался в рассматривавшихся до сих пор примерах программ. А закрытый член класса доступен только методам, определенным в самом классе. С помощью закрытых членов и организуется управление доступом.

Ограничение доступа к членам класса является основополагающим этапом объектно-ориентированного программирования, поскольку позволяет исключить неверное использование объекта. Разрешая доступ к закрытым

данным только с помощью строго определенного ряда методов, можно предупредить присваивание неверных значений этим данным, выполняя, например, проверку диапазона представления чисел. Для закрытого члена класса нельзя задать значение непосредственно в коде за пределами класса. Но в то же время можно полностью управлять тем, как и когда данные используются в объекте. Следовательно, правильно реализованный класс образует некий "черный ящик", которым можно пользоваться, но внутренний механизм его действия закрыт для вмешательства извне.


Модификаторы доступа

Управление доступом в языке C# организуется с помощью четырех модификаторов доступа: public, private, protected и internal. В этой главе основное внимание уделяется модификаторам доступа public и private. Модификатор protected применяется только в тех случаях, которые связаны с наследованием, и поэтому речь о нем пойдет в главе 11. А модификатор internal служит в основном для сборки, которая в широком смысле означает в C# разворачиваемую программу или библиотеку, и поэтому данный модификатор подробнее рассматривается в главе 16.

Когда член класса обозначается спецификатором public, он становится доступным из любого другого кода в программе, включая и методы, определенные в других классах. Когда же член класса обозначается спецификатором private, он может быть доступен только другим членам этого класса. Следовательно, методы из других классов не имеют доступа к закрытому члену (private) данного класса. Как пояснялось в главе 6, если ни один из спецификаторов доступа не указан, член класса считается закрытым для своего класса по умолчанию. Поэтому при создании закрытых членов класса спецификатор private указывать для них необязательно.

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

public string errMsg;

private double bal;

private bool isError(byte status) { // ...

Для того чтобы стали более понятными отличия между модификаторами public и private, рассмотрим следующий пример программы.

// Отличия между видами доступа public и private к членам класса.

using System;

class MyClass {

  private int alpha;    // закрытый    доступ, указываемый    явно

  int beta;    // закрытый доступ по умолчанию

  public int gamma; // открытый доступ

  // Методы, которым доступны члены alpha и beta данного класса.

  // Член класса может иметь доступ к закрытому члену этого же класса.

  public void SetAlpha(int а) {

    alpha = а;

  }

  public int GetAlpha() {

    return alpha;

  }

  public void SetBeta(int a) {

    beta = a;

  }

  public int GetBeta() {

    return beta;

  }

}

class AccessDemo {

  static void Main() {

    MyClass ob = new MyClass();

    // Доступ к членам alpha и beta данного класса

    // разрешен только посредством его методов,

    ob.SetAlpha(-99) ;

    ob.SetBeta(19) ;

    Console.WriteLine("ob.alpha равно " + ob.GetAlpha());

    Console.WriteLine("ob.beta равно " + ob.GetBeta());

    // Следующие виды доступа к членам alpha и beta

    // данного класса не разрешаются.

    // ob.alpha =10; // Ошибка! alpha – закрытый член!

    // ob.beta =9;    // Ошибка! beta – закрытый член!

    // Член gamma данного класса доступен непосредственно,

    // поскольку он является открытым, ob.gamma = 99;

  }

}

Как видите, в классе MyClass член alpha указан явно как private, член beta становится private по умолчанию, а член gamma указан как public. Таким образом, члены alpha и beta недоступны непосредственно из кода за пределами данного класса, поскольку они являются закрытыми. В частности, ими нельзя пользоваться непосредственно в классе AccessDemo. Они доступны только с помощью таких открытых (public) методов, как SetAlpha() и GetAlpha(). Так, если удалить символы комментария в начале следующей строки кода:

// ob.alpha =10; // Ошибка! alpha – закрытый член!

то приведенная выше программа не будет скомпилирована из-за нарушения правил доступа. Но несмотря на то, что член alpha недоступен непосредственно за пределами класса MyClass, свободный доступ к нему организуется с помощью методов, определенных в классе MyClass, как наглядно показывают методы SetAlpha() и GetAlpha().   Это же относится и к члену beta.

Из всего сказанного выше можно сделать следующий важный вывод: закрытый член может свободно использоваться другими членами этого же класса, но недоступен для кода за пределами своего класса.


Организация закрытого и открытого доступа

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

•    Члены, используемые только в классе, должны быть закрытыми.

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

•    Если изменение члена приводит к последствиям, распространяющимся за пределы области действия самого члена, т.е. оказывает влияние на другие аспекты объекта, то этот член должен быть закрытым, а доступ к нему – контролируемым.

•    Члены, способные нанести вред объекту, если они используются неправильно, должны быть закрытыми. Доступ к этим членам следует организовать с помощью открытых методов, исключающих неправильное их использование.

•    Методы, получающие и устанавливающие значения закрытых данных, должны быть открытыми.

•    Переменные экземпляра допускается делать открытыми лишь в том случае, если нет никаких оснований для того, чтобы они были закрытыми.

Разумеется, существует немало ситуаций, на которые приведенные выше принципы не распространяются, а в особых случаях один или несколько этих принципов могут вообще нарушаться. Но в целом, следуя этим правилам, вы сможете создавать объекты, устойчивые к попыткам неправильного их использования.


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

Для чтобы стали понятнее особенности внутреннего механизма управления доступом, обратимся к конкретному примеру. Одним из самых характерных примеров объектно-ориентированного программирования служит класс, реализующий стек — структуру данных, воплощающую магазинный список, действующий по принципу «первым пришел – последним обслужен». Свое название он получил по аналогии со стопкой тарелок, стоящих на столе. Первая тарелка в стопке является в то же время последней использовавшейся тарелкой.

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

Для стека определены две основные операции: поместить данные в стек и извлечь их оттуда. Первая операция помещает значение на вершину стека, а вторая – извлекает значение из вершины стека. Следовательно, операция извлечения является безвозвратной: как только значение извлекается из стека, оно удаляется и уже недоступно в стеке.

В рассматриваемом здесь примере создается класс Stack, реализующий функции стека. В качестве базовых средств для хранения данных в стеке служит закрытый массив. А операции размещения и извлечения данных из стека доступны с помощью открытых методов класса Stack. Таким образом, открытые методы действуют по упомянутому выше принципу «последним пришел – первым обслужен». Как следует из приведенного ниже кода, в классе Stack сохраняются символы, но тот же самый механизм может быть использован и для хранения данных любого другого типа.

// Класс для хранения символов в стеке.

using System;

class Stack {

  // Эти члены класса являются закрытыми,

  char[] stck; // массив, содержащий стек

  int tos;    // индекс вершины стека

  // Построить пустой класс Stack для реализации стека заданного размера,

  public Stack(int size) {

    stck = new char[size]; // распределить память для стека

    tos = 0;

  }

  // Поместить символы в стек,

  public void Push(char ch) {

    if(tos==stck.Length) {

      Console.WriteLine(« – Стек заполнен.»);

      return;

    }

    stck[tos] = ch;

    tos++;

  }

  // Извлечь символ из стека,

  public char Pop() {

    if(tos==0) {

      Console.WriteLine(« – Стек пуст.»);

      return (char) 0;

    }

    tos– ;

    return stck[tos];

  }

  // Возвратить значение true, если стек заполнен,

  public bool IsFull() {

    return tos==stck.Length;

  }

  // Возвратить значение true, если стек пуст,

  public bool IsEmpty() {

    return tos==0;

  }

  // Возвратить общую емкость стека,

  public int Capacity() {

    return stck.Length;

  }

  // Возвратить количество объектов, находящихся в данный момент в стеке,

  public int GetNum() {

    return tos;

  }

}

Рассмотрим класс Stack более подробно. В начале этого класса объявляются две следующие переменные экземпляра.

// Эти члены класса являются закрытыми,

char[] stck; // массив, содержащий стек

int tos;    // индекс    вершины стека

Массив stck предоставляет базовые средства для хранения данных в стеке (в данном случае – символов). Обратите внимание на то, что память для этого массива не распределяется. Это делается в конструкторе класса Stack. А член tos данного класса содержит индекс вершины стека.

Оба члена, tosnstck, являются закрытыми, и благодаря этому соблюдается принцип «последним пришел – первым обслужен». Если же разрешить открытый доступ к члену stck, то элементы стека окажутся доступными не по порядку. Кроме того, член tos содержит индекс вершины стека, где находится первый обслуживаемый в стеке элемент, и поэтому манипулирование членом tos в коде, находящемся за пределами класса Stack, следует исключить, чтобы не допустить разрушение самого стека. Но в то же время члены stckntos доступны пользователю класса Stack косвенным образом с помощью различных отрытых методов, описываемых ниже.

Рассмотрим далее конструктор класса Stack.

// Построить пустой класс Stack для реализации стека заданного размера,

public Stack(int size) {

  stck = new char[size]; // распределить память для стека

  tos = 0;

}

Этому конструктору передается требуемый размер стека. Он распределяет память для базового массива и устанавливает значение переменной tos в нуль. Следовательно, нулевое значение переменной tos указывает на то, что стек пуст.

Открытый метод Push() помещает конкретный элемент в стек, как показано ниже.

// Поместить символы в стек,

public void Push(char ch) {

  if (tos==stck.Length) {

    Console.WriteLine(« – Стек заполнен.»);

    return;

  }

  stck[tos] = ch;

  tos++;

}

Элемент, помещаемый в стек, передается данному методу в качестве параметра ch. Перед тем как поместить элемент в стек, выполняется проверка на наличие свободного места в базовом массиве, а именно: не превышает ли значение переменной tos длину массива stck. Если свободное место в массиве stck есть, то элемент сохраняется в нем по индексу, хранящемуся в переменной tos, после чего значение этой переменной инкрементируется. Таким образом, в переменной tos всегда хранится индекс следующего свободного элемента массива stck.

Для извлечения элемента из стека вызывается открытый метод Pop(), приведенный ниже.

// Извлечь символ из стека,

public char Рор() {

  if(tos==0) {

    Console.WriteLine (« – Стек пуст.»);

    return (char) 0;

  }

  tos– ;

  return stck[tos];

}

В этом методе сначала проверяется значение переменной tos. Если оно равно нулю, значит, стек пуст. В противном случае значение переменной tos декрементируется, и затем из стека возвращается элемент по указанному индексу.

Несмотря на то что для реализации стека достаточно методов Push() и Pop(), полезными могут оказаться и другие методы. Поэтому в классе Stack определены еще четыре метода: IsFull(), IsEmpty(), Capacity() и GetNum(). Эти методы предоставляют всю необходимую информацию о состоянии стека и приведены ниже.

// Возвратить значение true, если стек заполнен,

public bool IsFull() {

  return tos==stck.Length;

}

// Возвратить значение true, если стек пуст,

public bool IsEmpty() {

  return tos==0;

}

// Возвратить общую емкость стека,

public int Capacity() {

  return stck.Length;

}

// Возвратить количество объектов, находящихся в данный момент в стеке,

public int GetNum() {

  return tos;

}

Метод IsFull() возвращает логическое значение true, если стек заполнен, а иначе – логическое значение false. Метод IsEmpty() возвращает логическое значение true, если стек пуст, а иначе – логическое значение false. Для получения общей емкости стека (т.е. общего числа элементов, которые могут в нем храниться) достаточно вызвать метод Capacity(), а для получения количества элементов, хранящихся в настоящий момент в стеке, – метод GetNum(). Польза этих методов состоит в том, что для получения информации, которую они предоставляют, требуется доступ к закрытой переменной tos. Кроме того, они служат наглядными примерами организации безопасного доступа к закрытым членам класса с помощью открытых методов.

Конкретное применение класса Stack для реализации стека демонстрируется в приведенной ниже программе.

// Продемонстрировать применение класса Stack,

using System;

// Класс для хранения символов в стеке.

class Stack {

  // Эти члены класса являются закрытыми,

  char[] stck; // массив, содержащий стек

  int tos;    // индекс вершины стека

  // Построить пустой класс Stack для реализации стека заданного размера,

  public Stack(int size) {

    stck = new char[size]; // распределить память для стека

    tos = 0;

  }

  // Поместить символы в стек,

  public void Push(char ch) {

    if(tos==stck.Length) {

      Console.WriteLine(« – Стек заполнен.»);

      return;

    }

    stck[tos] = ch;

    tos++;

  }

  // Извлечь символ из стека,

  public char Pop() {

    if(tos==0) {

      Console.WriteLine(« – Стек пуст.»);

      return (char) 0;

    }

    tos– ;

    return stck[tos];

  }

  // Возвратить значение true, если стек заполнен,

  public bool IsFull() {

    return tos==stck.Length;

  }

  // Возвратить значение true, если стек пуст,

  public bool IsEmpty() {

    return tos==0;

  }

  // Возвратить общую емкость стека,

  public int Capacity() {

    return stck.Length;

  }

  // Возвратить количество объектов, находящихся в данный момент в стеке,

  public int GetNum() {

    return tos;

  }

}

class StackDemo {

  static void Main() {

    Stack stk1 = new Stack(10);

    Stack stk2 = new Stack(10);

    Stack stk3 = new Stack(10);

    char ch;

    int i;

    // Поместить ряд символов в стек stk1.

    Console.WriteLine(«Поместить символы А-J в стек stk1.»);

    for(i=0; !stk1.IsFull(); i++)

      stk1.Push((char)('A' + i));

    if(stk1.IsFull())

      Console.WriteLine(«Стек stk1 заполнен.»);

    // Вывести содержимое стека stk1.

    Console.Write("Содержимое стека stk1: ");

    while( !stk1.IsEmpty()) {

      ch = stk1.Pop();

      Console.Write(ch);

    }

    Console.WriteLine();

    if(stk1.IsEmpty())

      Console.WriteLine(«Стек stk1 пуст.»);

    // Поместить дополнительные символы в стек stk1.

    Console.WriteLine(«Вновь поместить символы А-J в стек stk1.»);

    for(i=0; !stk1.IsFull(); i++)

      stk1.Push((char)('A' + i));

    // А теперь извлечь элементы из стека stk1 и поместить их в стек stk2.

    // В итоге элементы сохраняются в стеке stk2 в обратном порядке.

    Console.WriteLine(«А теперь извлечь символы из стека stk1n» +

          «и поместить их в стек stk2.»);

    while( !stk1.IsEmpty()) {

      ch = stk1.Pop();

      stk2.Push(ch);

    }

    Console.Write("Содержимое стека stk2: ");

    while( !stk2.IsEmpty() ) {

      ch = stk2.Pop();

      Console.Write(ch);

    }

    Console.WriteLine(«n»);

    // Поместить 5 символов в стек.

    Console.WriteLine(«Поместить 5 символов в стек stk3.»);

    for(i=0; i < 5; i++)

      stk3.Push((char) ('A' + i)) ;

    Console.WriteLine("Емкость стека stk3: " + stk3.Capacity());

    Console.WriteLine("Количество объектов в стеке stk3: " 

          + stk3.GetNum());

  }

}

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

Поместить символы А-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 после копирования: ");

    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

оb1 и ob2 имеют разные значения.

оb1 после копирования: 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.a и 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() оказали в данном случае влияние на объект, использовавшийся в качестве аргумента.

Итак, подведем краткий итог. Когда объект передается методу по ссылке, сама ссылка передается по значению, а следовательно, создается копия этой ссылки. Но эта копия будет по-прежнему ссылаться на тот же самый объект, что и соответствующий аргумент. Это означает, что объекты передаются методам неявным образом по ссылке.


    Ваша оценка произведения:

Популярные книги за неделю