我知道c#ref允许您发送指向对象的指针。我只是想知道这对于实际发送到引擎盖下的功能意味着什么。
据我所知,它就像:
DoSomething(ref exampleObject) - >这里指向exampleObject的指针被发送到函数
DoSomething(exampleObject) - >这里将一个指向对象属性的指针数组发送给该函数。
这是对的吗?第二个函数是否也会为值类型创建指针? (看它们是属性,它们的值必须保存在堆上,以保持对象的引用完整性)
更明确的解释: *对象A位于内存位置5 *它具有属性对象B和对象C
现在我打电话给DoSomething(A) - >因为默认情况下我通过指针处理对象,所以我可以传递指针值为5,这样我就可以操作内存地址5的值.C#如何知道发送对象的属性位于哪个内存地址?
现在我打电话给DoSomething(参考A) - >此时我创建了一个指向另一个指针的新指针(比方说6)。所以现在我传入6,这允许我改变内存地址6的值,这很方便地变成5,即对象A的位置。所以现在我可以改变传入的A对象所指向的内存
我不明白的是如何在内存中找到属性?在某个地方必须发送一个指针数组或一个指针数组的指针,对吗?
答案 0 :(得分:1)
我在第二种情况下的理解
DoSomething(exampleObject)
exampleObject
的地址作为方法DoSomething
的参数给出,这意味着在DoSomething
中可以重新分配对象的属性(假设引用类型是已使用),但exampleObject
的assignmet不会在调用者的范围内更改它,因为exampleObject
的地址在堆栈上被修改,而不是在调用者的范围内。另一方面,在第一种情况下
DoSomething(ref exampleObject)
指向exampleObject
地址的指针作为DoSomething
的参数,这意味着除了更改exampleObject
的属性之外,对exampleObject
本身的引用可以重新分配。因此,可以交换exampleObject
,这种效果在调用者的范围内也是可见的。
答案 1 :(得分:1)
没有。如果你从c ++进入c#,你可以将ref想象成c ++中的引用:
void foo(A*& a)
{
// Here you can change object's state
// Also you can change value of pointer
// Caller will have new value of pointer
}
void foo(A* a)
{
// Here you can change object's state
// Also you can change value of copy of pointer
// Caller will have old value of pointer
}
答案 2 :(得分:0)
如果我们知道或期望DoSomething(ref exampleObject)
可以执行“计算值或创建新对象并将其返回给我们”,我们可以使用DoSomething
。我们知道DoSomething
的声明是否也包含ref
关键字。
如果在通话之前您的exampleObject
包含某个值,或者它指向某个对象(甚至是空),那么在通话之后可能包含一个新值,或可能指向新对象,或者可能已更改为null。
ref
没有确定性或义务,只有如果 DoSomething
决定exampleObject
应该更改,然后我们将获得新值或对象(或null)。否则我们会保留以前的东西。
对于值类型(数字,bool,结构)和引用类型(对象)都是如此,这就是为什么我一直说“新值或新对象或null”。
<强> 编辑: 强>
我故意避免提及指针。除非你做的是低级别的东西,否则在C#/ .NET中你几乎不需要知道指针。通常了解管理它们的概念和规则就足够了。
答案 3 :(得分:0)
我总是比较巫毒娃娃的指针 - 无论你对伏都教娃娃做什么,都会影响巫毒娃娃的人和#34;点&#34;到。
所以C#引用就像一个巫毒娃娃 - 它附着在一个物体上,你可以通过消耗巫毒娃娃来影响物体。
但是,如果我突然想要巫毒娃娃指向不同于以前指向的人,会发生什么?使用巫毒娃娃会影响它指向的人 - 而不是巫毒娃娃本身。解决方案是什么?创造另一个巫毒娃娃 - 到原始巫毒娃娃!通过这种方式,我可以将原始玩偶弄得一团糟,而不仅仅是它所指向的人。
因此C#中的ref
是对引用的引用 - 它允许您修改引用,而不仅仅是它指向的对象。
ref
引用是另一个数字,但它表示原始引用位于RAM的哪个位置,而不是它指向的对象位于何处。
答案 4 :(得分:0)
不,此不正确。
ref
给表带来的是引用语义。
当您调用函数foo(T arg)
- 时,无论T是值类型还是引用类型(我都不能强调这一点) - foo
获取T的副本。
值类型和引用类型之间的区别在于值类型的名称引用了有趣的东西,而引用类型的名称引用了引用(或指针)有趣的东西。因此,当您复制值类型的对象时,您会获得有趣内容的副本,以及何时复制所述有趣内容的引用。
我在Google上找到的一些图片,而不是我自己绘制的图片:
图像中的副本基本上是在你正在谈论的foo(T arg
)时发生的事情。在这两种情况下,我们arg
都包含传递给foo
的内容的副本。如果T
为int
,则表示您感兴趣的数字。如果T
为Circle
,则arg
为“地址” 1 传递给foo
的参数包含。我们可以谈论箭头,而不是谈论指针或参考。引用类型的变量包含有趣内容的箭头,而值类型的变量包含内容本身。
以下代码证明了这一点:
using System;
class MainClass {
public static void Main (string[] args) {
Foo foo1 = new Foo();
foo1.i = 1;
Console.WriteLine("Main(): foo1.i: {0}", foo1.i);
doStuffToFoo(foo1, 5);
Console.WriteLine("Main(): foo1.i: {0}", foo1.i);
Console.WriteLine();
Bar bar1;
bar1.j = 1;
Console.WriteLine("Main(): bar1.j: {0}", bar1.j);
doStuffToBar(bar1, 5);
Console.WriteLine("Main(): bar1.j: {0}", bar1.j);
}
static void doStuffToFoo(Foo aFoo, int aInt) {
aFoo.i = aInt;
aFoo = new Foo();
aFoo.i = 123;
Console.WriteLine("doStuffToFoo: aFoo.i: {0}", aFoo.i);
}
static void doStuffToBar(Bar aBar, int aInt) {
aBar.j = aInt;
aBar = new Bar();
aBar.j = 123;
Console.WriteLine("doStuffToBar: aBar.j: {0}", aBar.j);
}
}
class Foo {
public int i;
}
struct Bar {
public int j;
}
在尝试或阅读之前,看看你是否理解它的印刷品。
aFoo
内的变量doStuffToFoo
是foo1
的副本。干净利落。 aFoo
和foo1
是不同的变量(存储在堆栈的不同位置 - foo1
的堆栈框架中Main
和aFoo
中的doStuffToFoo
的堆栈帧 - 假设有一个堆栈,因为the stack is an implementation detail),它们都包含指向同一Foo
对象的箭头。
当您引用这两个变量的字段时,它们通过各自的箭头访问相同的Foo
对象。但是,当您将new Foo()
分配给aFoo
时,它只会更改aFoo
中的箭头以指向新的Foo
对象。它对第一个Foo
对象的其他箭头没有任何作用,安全地存储在foo1
变量中,并且doStuffToFoo
中的代码无法访问。这就是为什么你不会在Main
中看到“123”。
所有这一切都将我们带到ref
。 ref T v
表示v
不是T类型的新变量(如果T
是值类型,则包含一些有趣的内容;如果T
是v
,则包含一些有趣内容的箭头参考类型)。相反,/***** inside Main: *****/
Foo foo2 = new Foo();
Foo foo3 = foo2;
foo2.i = 1;
Console.WriteLine("Main(): foo2.i: {0}", foo2.i);
Console.WriteLine("Main(): foo3.i: {0}", foo3.i);
doStuffToFooRef(ref foo2, 5);
Console.WriteLine("Main(): foo2.i: {0}", foo2.i);
Console.WriteLine("Main(): foo3.i: {0}", foo3.i);
/***** until here *****/
static void doStuffToFooRef(ref Foo aFoo, int aInt) {
aFoo.i = aInt;
aFoo = new Foo();
aFoo.i = 123;
Console.WriteLine("doStuffToFooRef: aFoo.i: {0}", aFoo.i);
}
是别名,以及作为参数传递的内容。这是其他变量的第二个名称。
如果您将以下代码添加到上面的示例中,您将看到它有效。
aFoo
现在foo2
是aFoo
的另一个名称。当您分配到“foo2
”时,您将分配给“doStuffToFooRef
”,因为这两个名称都指的是同一个变量(存储位置)。这就是i
中的任务期间发生的事情:
(ref
仍然为零,因为它是在我们放置123之前。)
在此处试试:https://repl.it/E80a
C#ref SomeReferenceType x
就像这方面的C ++引用一样,АлександрЛысенко在他的回答中写道。虽然从技术上讲,ref
可能是“指向指针的指针”,正如一些人所说的那样,这并没有抓住ref
的真正含义,并且详细说明了不一定总是如此(它可能会被优化)。这意味着添加ref
会添加指针。那么引用类型的ref-of-a-ref是指向指针的指针吗?每个额外的{{1}}是否会增加一个间接级别?
1 为了讨论的目的,我们不关心它是否真的是一个地址或其他东西。由于对象可能在内存中移动,我们知道它可能不是主机/操作系统的虚拟内存地址,但它是广义上的地址。
有些区分了引用和指针,后者是数字内存地址,但是其他人也不害怕将这些东西称为指针,因为它们指向对象。例如,Java语言规范(§4.3.1)明确地说:
参考值(通常只是引用)是指向这些对象的指针,以及一个特殊的空引用,它指的是没有对象。
他们强调“引用” - 他们引入的新术语 - 但是在他们看来,对于读者来说,指针是显而易见的,并且对象也不是不可移动的AFAIK。