Когда я передаю stringфункции a, передается ли указатель на содержимое строки или вся строка передается функции в стеке, как если structбы она была?
Когда я передаю stringфункции a, передается ли указатель на содержимое строки или вся строка передается функции в стеке, как если structбы она была?
Ответы:
Ссылка передана; однако технически это не передается по ссылке. Это тонкое, но очень важное различие. Рассмотрим следующий код:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
Чтобы понять, что здесь происходит, нужно знать три вещи:
strMainне передаются по ссылке. Это ссылочный тип, но сама ссылка передается по значению . Каждый раз, когда вы передаете параметр без refключевого слова (не считая outпараметров), вы передаете что-то по значению.Это должно означать, что вы ... передаете ссылку по значению. Поскольку это ссылочный тип, в стек копировалась только ссылка. Но что это значит?
Переменные C # являются либо ссылочными типами, либо типами значений . Параметры C # передаются либо по ссылке, либо по значению . Терминология здесь является проблемой; это звучит как одно и то же, но это не так.
Если вы передаете параметр ЛЮБОГО типа и не используете refключевое слово, значит, вы передали его по значению. Если вы передали его по значению, то на самом деле вы передали копию. Но если параметр был ссылочным типом, то то, что вы скопировали, было ссылкой, а не то , на что она указывала.
Вот первая строка Mainметода:
string strMain = "main";
В этой строке мы создали две вещи: строку со значением, mainхранящимся где-то в памяти, и ссылочную переменную, называемую strMainуказывающей на нее.
DoSomething(strMain);
Теперь мы передаем ссылку на DoSomething. Мы передали его по значению, значит, мы сделали копию. Это ссылочный тип, поэтому мы скопировали ссылку, а не саму строку. Теперь у нас есть две ссылки, каждая из которых указывает на одно и то же значение в памяти.
Вот начало DoSomethingметода:
void DoSomething(string strLocal)
Нет refключевого слова, поэтому strLocalи strMainесть две разные ссылки, указывающие на одно и то же значение. Если мы переназначим strLocal...
strLocal = "local";
... мы не изменили сохраненное значение; мы взяли ссылку под названием strLocalи направили ее на новую строку. Что происходит, strMainкогда мы это делаем? Ничего. Он все еще указывает на старую строку.
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
Давайте на секунду изменим сценарий. Представьте, что мы работаем не со строками, а с каким-то изменяемым ссылочным типом, например с созданным вами классом.
class MutableThing
{
public int ChangeMe { get; set; }
}
Если вы следуете ссылке objLocalна объект, на который он указывает, вы можете изменить его свойства:
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
В MutableThingпамяти все еще есть только один , и как скопированная, так и исходная ссылка все еще указывают на него. Изменились свойства самого MutableThingсебя :
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
Ах, но струны неизменны! Нет ChangeMeсвойства для установки. Вы не можете сделать strLocal[3] = 'H'на C # так, как могли бы с charмассивом в стиле C ; вместо этого вам нужно создать целую новую строку. Единственный способ изменить это strLocal- указать ссылку на другую строку, а это значит, что никакие ваши действия не strLocalмогут повлиять strMain. Значение неизменяемо, а ссылка является копией.
Чтобы доказать разницу, вот что происходит, когда вы передаете ссылку по ссылке:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
На этот раз строка Mainдействительно изменилась, потому что вы передали ссылку, не копируя ее в стек.
Таким образом, даже несмотря на то, что строки являются ссылочными типами, передача их по значению означает, что все, что происходит в вызываемом, не повлияет на строку в вызывающем. Но поскольку они являются ссылочными типами, вам не нужно копировать всю строку в память, когда вы хотите передать ее.
refключевого слова. Чтобы доказать, что передача по ссылке имеет значение, см. Эту демонстрацию: rextester.com/WKBG5978
refключевое слово имеет полезность, я просто пытался объяснить, почему можно подумать о передаче ссылочного типа по значению в C #, похоже на «традиционное» (то есть C) понятие передачи по ссылке (и передачи ссылочного типа по ссылке в C # больше похоже на передачу ссылки на ссылку по значению).
Foo(string bar)можно было бы думать о такой функции, как Foo(char* bar)тогда Foo(ref string bar), когда Foo(char** bar)(или Foo(char*& bar)или Foo(string& bar)в C ++). Конечно, это не то, как вы должны думать об этом каждый день, но на самом деле это помогло мне наконец понять, что происходит под капотом.
Строки в C # являются неизменяемыми ссылочными объектами. Это означает, что ссылки на них передаются (по значению), и после создания строки вы не можете ее изменить. Методы, которые создают измененные версии строки (подстроки, обрезанные версии и т. Д.), Создают измененные копии исходной строки.
Строки - это особые случаи. Каждый экземпляр неизменен. Когда вы меняете значение строки, вы выделяете новую строку в памяти.
Таким образом, в вашу функцию передается только ссылка, но когда строка редактируется, она становится новым экземпляром и не изменяет старый экземпляр.
Uri(класс) и Guid(структура) также являются частными случаями. Я не понимаю, как System.Stringдействует «тип значения» больше, чем другие неизменяемые типы ... либо класса, либо происхождения структуры.
Uri& Guid- вы можете просто присвоить строковой литерал строковой переменной. Строка кажется изменяемой, как intпереназначение, но она создает объект неявно - без newключевого слова.