Значение в левой части оператора присваивания называется
lvalue,
а в правой части —
rvalue.
В качестве
rvalue
может быть любая константа, переменная, число или выражение, результат которого совместим с
lvalue.
Между тем
lvalue
должно быть переменной определенного типа. Дело в том, что значение копируется из правой части в левую. Таким образом, для нового значения должно быть выделено физическое адресное пространство. Например, можно написать /' =
4,
поскольку для / есть место в памяти — в стеке или в куче — в зависимости от типа переменной /. А вот оператор
Скажем, у вас два объекта:
testl
и
test2.
Если вы укажете
testl
=
test2, testl
не будет копией
test2.
Они будут совпадать! Объект
testl
указывает на ту же память, что и
test2,
и любые изменения объекта
testl
приведут к изменениям
test2.
Вот программа, которая это иллюстрирует:
using System;
class Foo {
public int i; }
class RefTestlApp {
public static void MainO {
Foo testl = new Foo(); testl.i = 1;
Foo test2 = new Foo(); test2.i = 2;
Console.WriteLine("До назначения объектов"); Console.WriteLine("test1.i={0>", testl.i); Console.WriteLine("test2.i={0}", test2.i); Console.WriteLine("\n");
testl = test2;
Console.Writel_ine("После назначения объектов");
Console.WriteLine("test1.i={0}", testl.i); Console.WriteLine("test2.i={0}", test2.i); Console.WriteLine("\n");
testl.i = 42; ;'
Console.WriteLine("Пocлe изменения только члена TEST1"); Console.WriteLine("test1.i={0}", testl.i); Console.WriteLine("test2.i={0}", test2.i); Console.WriteLine("\n"); } }
Выполнив этот код, вы увидите:
До назначения объекта
test1.i=1
test2.i=2
После назначения объекта
testt.i=2
test2.i=2
После изменения только члена TEST1
test1.i=42
test2.i=42
Посмотрим, что происходит на каждом этапе выполнения этого примера.
Foo —
это простой к класс с единственным членом, /. В методе Main создаются два экземпляра этого класса:
testl
и
test2
— и их члены
i
устанавливаются в 1 и 2 соответственно. Затем эти значения выводятся, и, как и ожидалось,
testl.i
равен 1, a
test2.i
— 2. И тут начинается самое интересное! В следующей строке объекту
testl
присваивается
test2.
Читатели, программирующие на Java, знают, что будет дальше. Однако большинство программистов на C++ будут ожидать, что член / объекта
testl
теперь равен члену объекта
test2
(если исходить из предположения, что при компиляции такого приложения будет выполнена некая разновидность оператора копирования членов объектов). Выводимый результат это вроде подтверждает. Однако на самом деле связь между объектами теперь гораздо глубже. Присвоим значение 42 члену
testl.i
и снова выведем результат. И?! При изменении объекта
testl
изменился и
testZ
Это произошло из-за того, что объекта
testl
больше нет. После присваивания ему
test2
объект
testl
утерян, так как приложение на него больше не ссылается и в результате он «вычищается» сборщиком мусора (garbage collector, GC). Теперь
testl
и
test2
указывают на одну и ту же память в куче. Следовательно, при изменении одной переменной пользователь увидит изменение и другой.
Обратите внимание на две последние выводимые строки: хотя в коде изменялось только значение
testl.i,
значение
test2.i
также изменилось. Еще раз: обе переменные теперь указывают на одно место в памяти — такое поведение и ожидали программисты на Java. Однако это совершенно не соответствует ожиданиям разработчиков на C++, поскольку в этом языке производится именно копирование объектов: каждая переменная имеет свою уникальную копию членов и изменения одного объекта не влияют на другой. Поскольку это ключ к пониманию работы объектов в С#, сделаем небольшое отступление и посмотрим, что будет происходить при передаче объекта методу:
using System;
class Foo {
public int i; }
class RefTest2App {
public void ChangeValue(Foo f)
{
f.i = 42;
}
public static void Main() {
RefTest2App app = new RefTest2App();
Foo test = new Foo(); test.i = 6;
Console.WriteLine("До вызова метода"); Console.WriteLine("test.i={0}", test.i); Console.WriteLine("\n");
app.ChangeValue(test);
Console.WriteLine("После вызова метода"); Console.WriteLine("test.i={0}", test.i); Console.WriteLine("\n"); > }
В большинстве языков, кроме Java, этот код будет копировать созданный объект
test в
локальный стек метода
RefTest2App.ChangeValue.
В таком случае объект
test,
созданный в методе
Main,
никогда не увидит изменений объекта/, производимых в методе
ChangeValue.
Однако еще раз повторю, что метод
Main
передает ссылку на выделенный в куче объект
test.
Когда метод
ChangeValue
манипулирует своей локальной переменной //, он так же напрямую манипулирует объектом
test
метода
Main.