В C++ мы можем использовать ptr = nullptr
для изменения самого указателя и *ptr
для изменения объекта, на который ссылается указатель. Например:
int* ptr = new int(5);
*ptr = 10; // Modifies the object
ptr = nullptr; // Changes the pointer itself
В C# есть похожие сценарии со ссылками. Например:
StringBuilder sb = new StringBuilder("Hello");
sb.Append(" World"); // Modifies the object
sb = null; // Sets the reference to null
Мой вопрос касается синтаксиса и того, почему он ведет себя по-другому. Конкретно:
Когда я использую sb.Append()
, он работает с объектом, на который указывает sb
, а не с самой ссылкой.
Когда я использую sb = null
, он устанавливает ссылку на null
, но не влияет на объект.
Почему синтаксис sb = null
в C# не интерпретируется как присвоение объекту значения null
, аналогично тому, как ptr = nullptr
работает в C++? Или, другими словами, почему бы мне не разыменовать sb
, чтобы вызвать метод Append()
? Почему в C# нет необходимости явно разыменовывать ссылку для выполнения операций над объектом и как он узнает, когда разыменовывать указатель, а когда манипулировать им напрямую?
Я читал об этом, но не нашел хорошего объяснения. Все, что я узнал до сих пор, это то, что C#, вероятно, не позволяет нам явно разыменовывать, но разве это не очень озадачивает? Концептуально я мог бы знать, что sb = null
не может установить указатель на null
, но разве синтаксис не говорит об обратном или, по крайней мере, не является однозначным?
Я хочу прояснить, почему синтаксис C# не соответствует напрямую манипулированию указателями в C++ и как язык обеспечивает это различие.
в C# есть понятие «ссылочные типы», которое в значительной степени его охватывает. Вам не нужно удалять ссылку на какой-либо указатель в C#, поскольку указатели вообще редко существуют (хотя они существуют). Вместо этого вы обычно имеете дело с объектами по их соответствующим ссылкам. Итак, сказав это, ваши вопросы основаны на разных понятиях, которые просто не применимы в С#.
возможный дубликат Разница между указателем в C++ и ссылочным типом в C#
Это похоже на сравнение яблок с апельсинами. В C++ тоже есть ссылки, поэтому с помощью StringBuilder& sb
вы можете написать sb.Append(" World");
. (Однако, в отличие от указателей, ссылка C++ не допускает значения NULL, и вы не используете new
).
Я не понимаю разницы, которую вы видите. В обоих случаях xyz = null / nullptr;
оставляет объект без изменений.
«аналогично тому, как ptr = nullptr работает в C++?»
хотя я согласен с другими. Непонятно, почему вы ожидаете, что два разных языка будут одинаковыми. «C# не соответствует напрямую манипулированию указателями в C++», с чего бы это?
Я предлагаю прочитать о ценностной и ссылочной семантике. Я надеюсь, что это прольет для вас некоторый свет на проблему
@ 463035818_is_not_an_ai типы значений и ссылочные типы здесь также не применимы, поскольку это подразумевает, что один из них является указателем, а другой — объектом.
@MakePeaceGreatAgain, почему это не применимо? В C++ есть семантика значений, а в C#, похоже, есть ссылочная семантика. Я говорил не о «типах значений и ссылочных типах», а о семантике значений и ссылок в целом, что, я считаю, поможет ОП понять разницу между языками, когда они прочитают об этом.
@ 463035818_is_not_an_ai именно, речь идет о семантике, а не синтаксисе. На семантическом уровне ни типы значений, ни ссылочные типы не нуждаются в арифметике указателей. И, как вы сказали: в C++ также есть типы значений (структуры), хотя в нем есть понятие указателей. Таким образом, семантика значений и ссылок существует и в C++.
@MakePeaceGreatAgain очень извините, но я не понимаю, как все, что вы пишете, связано с моим комментарием о семантике val и ref.
На уровне памяти C# и C++ ведут себя очень по-разному (и в других областях тоже), даже new
не делает одно и то же. Объекты имеют разные жизненные циклы, поэтому ваш вопрос в каком-то смысле не имеет смысла. (явное управление памятью или сбор мусора, значение или ссылочная семантика).
Спасибо за все ваши комментарии, они очень полезны. Я хотел бы отметить одну вещь: я не ожидаю, что эти два языка будут вести себя одинаково. Я просто пытаюсь описать, что именно меня смущает: откуда он знает, что sb = null устанавливает ссылку на ноль, а не пытается установить нуль для объекта? Например, почему он не выполняет автоматическое разыменование (или не пытается разыменовать и не выдает ошибку или что-то в этом роде) при sb = null. Откуда он знает, что для ссылки установлено значение null, а не для объекта, на который ссылается?
Кроме того, простите мое невежество: у меня сложилось впечатление, что ссылки и указатели — это одно и то же. Теперь я понимаю, что это не так, но первоначальный вопрос все еще остается: как он узнает, когда следует разыменовывать, а когда нет? Я хочу понять синтаксис.
@aim в C++ указатель — это тип, и операции с переменной типа указателя будут влиять только на указатель, одна из операций — это оператор разыменования, который при применении к указателю возвращает ссылку на объект другого типа (ссылочный тип), и любая модификация ссылочного объекта изменяет реальный объект.... вам нужно понимать, что это два разных типа, и операции с переменной зависят от ее типа.
Например, x += 1
зависит от типа x, если x является типом указателя int*
указатель увеличивается, но если x является ссылочным типом int&
тогда увеличивается базовый объект, на который ссылается, вы можете преобразовать указатель и ссылку с помощью этих три оператора * and & and ->
(последний похож на (*x).
, но в нём две буквы вместо 4)
О, это многое объясняет, я не могу поверить, что никогда не знал, что указатели и ссылки — это разные вещи. Извините за излишество: но откуда C# узнает, что sb = null пытается установить ссылку на ноль, а не объект, на который ссылается? (Если sb является ссылкой, а sb.Append() изменяет объект, на который ссылается, не должен ли sb = null также попытаться установить для ссылочного объекта значение null?)
@aim в C++ у вас может быть указатель на указатель... или ссылка на указатель и т. д. .... в C# у вас есть ссылка и значения, вот и все, это не имеет ничего общего с указателем или ссылкой C++, это это просто имя..... присвоение ссылочным типам изменяет ссылку, тогда как присвоение типам значений изменяет значения, и у вас есть странная ссылка на тип значения ( ref
), которая имеет ту же симантику, что и тип значения, за исключением того, что они готовы на объекте, на который ссылаются (аналогично ссылкам в С++)
Это только для оператора присваивания, не пытайтесь вывести поведение оператора из другого языка. В C# оператор точки вызывает функцию для объекта, на который ссылается, это отличается от присваивания, которое изменяет ссылку, а не объект. .. оба оператора изменяют объект в C++, но не в C#, не переводите операторы с одного языка на другой.
Попытка соотнести идиомы C# с идиомами C++ покажет только то, что языки различны, а конструкции в языках мало соответствуют друг другу — лишь некоторые сходства и специфичный для языка жаргон, используемый с разной семантикой. (Слишком сложно говорить об обоих языках, используя жаргон каждого языка.)
Поэтому каждый раз, когда я пытаюсь использовать оператор « = ", он изменяет ссылку, а когда я использую оператор «.». оператор, он изменяет объект, на который ссылается. Это правильно? P.S всем спасибо за помощь, действительно многое прояснилось по ходу дела.
@aim для ссылочных типов в C# да, для типов значений и ссылок на типы значений нет... опять же, это зависит от типа объекта
Например, @aim int
в C# — это тип значения, присвоение ему изменяет объект, тогда как String
— это ссылочный тип, присвоение ему изменяет ссылку.
Спасибо большое, проблема решена. Не могли бы вы также объяснить, как работает ссылка на типы значений? Был бы очень благодарен.
Я думаю, что msdn — лучший источник Learn.microsoft.com/en-us/dotnet/csharp/language-reference/… но, короче говоря, поскольку типы значений передаются по значению, то есть: путем копирования, ключевое слово ref
позволяет вам создать.... ссылку на значение.... чтобы при изменении ссылки (путем присвоения) изменялось значение, на которое ссылается.... потому что именно так определяется этот тип... очень полезно при создании out
параметр или изменение элемента в списке, это просто псевдоним, другой способ изменения исходного объекта, во многом похожий на ссылки в C++.
На самом деле синтаксис не так уж сильно отличается от C++. Концептуально переменные ссылочного типа в C# и указатели в C++ имеют общую основу, хотя существует немало различий в деталях. Но поскольку вы спросили о синтаксисе, дело обстоит проще:
В C++ вы бы написали:
StringBuilder* sb = new StringBuilder();
(*sb).Append(...);
sb = nullptr; // well, rather delete first, but we don't care here.
(*sb).Append()
обычно сокращается до sb->Append()
. И вот мы уже очень близки к C#, потому что можно просто подумать, что в C# оператор ->
тоже просто записывается как .
. Просто компилятор достаточно «умен», чтобы знать, имеет ли оператор доступа к членам ссылочный тип или тип значения в левой части.
Большое спасибо за ваш ответ. Однако есть небольшие вопросы: можем ли мы сказать, что оператор «=» изменяет ссылку, но для «.» оператор, компилятор определяет, предназначен ли последующий метод для ссылки или для объекта, на который ссылается, и затем запускается соответственно?
В основном да. Если объект является ссылочным типом (классом), то операция .
всегда применяется к объекту, на который ссылается. Только если объект имеет тип значения (структуру или простой тип, например int), значение может измениться (в этом случае мы не называем его ссылкой). Однако это также очень редкий случай, поскольку большинство типов структур спроектированы так, чтобы их нельзя было изменить. Таким образом, почти во всех практических случаях вы можете предположить, что файл . оператор работает с объектом, на который ссылается.
Мой вопрос касается синтаксиса и того, почему он ведет себя по-другому. Потому что C# не имеет ничего общего с C++. Это два разных компьютерных языка. Тот факт, что один язык похож на другой, не означает, что вы можете понять один язык, используя идиомы и практики другого.