Непреложное недоразумение или ошибка в Документах?
Я только что видел это в документах MS Visual Studio, и часть, выделенная жирным шрифтом, не имеет смысла для меня. Это неправильно или я не правильно понимаю? Если вы запустите это, b будет содержать "привет" (как я и ожидал), а не "h".
Строки являются неизменными- содержимое строкового объекта не может быть изменено после создания объекта, хотя синтаксис заставляет его выглядеть так, как будто вы можете это сделать. Например, когда вы пишете этот код, компилятор фактически создает новый строковый объект для хранения новой последовательности символов, а переменная b продолжает содержать "h".
строка b = "h";
b + = "элло";
13 ответов
Вы сделали дополнение И задание за один шаг. Строки являются неизменяемыми, но также являются ссылочными типами.
string b = "h";
b = b + "ello";
Мы можем посмотреть на псевдо-память так:
string b = "h"; // b := 0x00001000 ["h"]
string tmp1 = "ello"; // tmp1 := 0x00002000 ["ello"]
string tmp2 = b + tmp1; // tmp2 := 0x00003000 ["hello"]
string b = tmp2; // b := 0x00003000 ["hello"]
Я не совсем уверен, откуда вы получаете этот текст, потому что, когда я читаю документацию для строкового класса, я нахожу (не то, чтобы я думал, что "h" фактически собирает мусор):
Строки являются неизменными - содержимое строкового объекта не может быть изменено после создания объекта, хотя синтаксис заставляет его выглядеть так, как будто вы можете это сделать. Например, когда вы пишете этот код, компилятор фактически создает новый строковый объект для хранения новой последовательности символов, и этот новый объект назначается для b. Строка "h" тогда подходит для сборки мусора.
@Jon Skeet говорит, что "h" никогда не будет собирать мусор из-за интернирования строк, и я согласен с ним, но даже более того, с ним согласен стандарт C#, иначе следующее из §2.4.4.5 строковые литералы не могут быть истинными:
Каждый строковый литерал не обязательно приводит к новому экземпляру строки. Когда два или более строковых литералов, которые эквивалентны в соответствии с оператором равенства строк (§7.9.7), появляются в одной и той же программе, эти строковые литералы ссылаются на один и тот же экземпляр строки.
Кажется, люди не понимают вопроса. Никто не утверждает, что строковые объекты не являются неизменяемыми. Суть спора в том, что он смел:
и переменная b продолжает держать "ч"
Я согласен с ФП в том, что эта часть документа неверна по двум причинам:
(1) В очевидном интуитивном смысле, что если вы напечатаете (b) (или что-то правильное на этом языке) после двух строк с примерами, вы получите "привет" в качестве результата.
(2) В строгом смысле, что переменная b не содержит "h", "hello" или какого-либо строкового значения. Он содержит ссылку на строковый объект.
Содержимое переменной b изменяется в результате присваивания - оно изменяется от точки к строковому объекту "h" до указателя на строковый объект "привет".
Когда они говорят "держи", то на самом деле они имеют в виду "указывает на". И они не правы, после назначения b больше не указывает на "h".
Я думаю, что пример, который они действительно хотели привести, таков:
string a = "h";
string b = a;
b += "ello";
Дело в том, что я бы, по-моему, все еще указывал на "ч"; т. е. присваивание b не изменяет объект, на который оно указывало, оно создает новый объект и изменяет b так, чтобы оно указывало на него.
(На самом деле я не пишу на C#, но это мое понимание.)
Документы не правы. Переменная b теперь содержит "привет". Строка является неизменной, но переменная может быть переназначена.
Недоразумение здесь о ссылочных типах:
Строка является ссылочным типом, а не типом значения. Это означает, что ваша переменная b не является объектом типа string, это ссылка на объект типа string в памяти.
Документ говорит, что объект в памяти неизменен.
Тем не менее, ваша ссылка на объект может быть изменена, чтобы указывать на некоторый другой (неизменный) объект в памяти.
Для вас это может выглядеть так, как будто содержание объекта изменилось, но в памяти это не изменилось, и это все, что является неизменной вещью.
Сама строка неизменна. То, что изменил ваш пример, это не строковый класс в памяти, а ссылка, на которую указывает ваша переменная.
Посмотрите этот слегка измененный код:
string b = "h";
string m1 = b;
b += "ello";
// now b == "hello", m1 == "h"
В конце b укажет "привет", а m1 укажет "h". Для вас может показаться, что "h" изменился на "привет", но это не так. b+="ello" создал новый класс строк, содержащий "hello", и присвоил его b, в то время как старый b все еще присутствует в памяти и все еще содержит "b".
Если строка не является неизменной, m1 также будет содержать "hello" вместо "b", потому что и b, и m1 указывают на одну и ту же ссылку.
Да, документы не так. (Документы для ряда строковых методов также подразумевают изменчивость. Они в основном плохо написаны.)
Черт, даже использование "компилятора" для создания нового строкового объекта отключено. В основном это делает:
string b = "h";
b = string.Concat(b, "ello");
На этом этапе работа компилятора завершена - это фреймворк, который создает новый строковый объект.
Строка не может быть изменена, но строковой переменной может быть присвоено другое значение. То, что вы делаете, ближе к:
string b = "h";
string temp = b + "ello";
b = temp;
Чтобы показать фактическую неизменность строки, это не удастся:
string b="hello";
if(b[0] == 'h') // we can read via indexer
b[0] = 'H'; // but this will fail.
Теперь есть три строки. Один - это оригинальное "h", один - "ello", а третий - "hello". Ваша переменная b указывает на строку "привет". Две другие строки не имеют ссылок на них и могут быть выброшены сборщиком мусора.
Возможно, было бы яснее рассматривать все хранилища классов класса как "идентификаторы объектов". Предположим, что изначально компилятор назначил ID #123
к строке "ч" и назначил ID #547
на строку "Элло". Потом после заявления b = "h";
переменная b
будет держать ID #123
, Заявление b += "ello";
заставит компилятор пройти ID #123
а также ID #547
к +
оператор для строки, которая, в свою очередь, передаст их String.Concat
метод. Этот метод в свою очередь попросит систему создать новый объект (например, ID #915
) типа System.String
, держа пять символов "hello"
и вернуть этот объект вызывающей стороне. Компилятор сохранит ID #915
в b
,
Попробуй это:
string b = "h";
string c = b + "ello"; // b still == "h", c = "hello"
string d = string.concat(b, "ello"); // d == hello, b still "h"
Почему б все еще "ч"? Поскольку "b" не является объектом, это ссылка на объект. Вы ничего не можете сделать с объектом, на который ссылается b, чтобы изменить его. Если строки изменчивы, используйте:
string b = "ello";
string f = b.Insert("h",0);
изменил бы b на "hello" (потому что h был вставлен в позицию 0), но поскольку он неизменен, b остается "ello".
Если вы измените ссылку на другой объект, это другая вещь.
b = "ello";
b = "Some other string";
// b not references "Some other string" , but the object "ello" remains unchanged.
Я надеюсь, что это помогает (и работает:S)
Происходит то, что вы создаете новую переменную, которая содержит "hello", а затем меняете b для ссылки на это, память для "старого" b по-прежнему содержит "h", но это больше не требуется, поэтому сборщик мусора будет очищать это до. Вот почему так хорошо использовать строители строк при переборе и объединении строк - смотрите это для получения дополнительной информации.
Я не знаю, что делает C#, но я читал об этом в Java, и реализация, основанная на Java, была бы больше похожа на это:
строка b = "h";
b = (новый StringBuilder(b)).Append("ello").ToString();
Дело в том, что "+" или "Append" не существует для строки, потому что строка неизменна.
Строка b = "h"; b += "элло";
b это просто ссылка на объект в куче. На самом деле, после операции "+=", b больше не ссылается на исходную "h". Теперь, это ссылка на новый строковый объект "hello", который является объединением "h" и "ello". Строка "h" будет собрана GC.
Проще говоря, строки не могут быть изменены на месте (если строка представляет собой массив символов)