我是C#的新手,来自C ++背景。在C ++中,您可以这样做:
class MyClass{
....
};
int main()
{
MyClass object; // this will create object in memory
MyClass* object = new MyClass(); // this does same thing
}
然而,在C#:
class Program
{
static void Main(string[] args)
{
Car x;
x.i = 2;
x.j = 3;
Console.WriteLine(x.i);
Console.ReadLine();
}
}
class Car
{
public int i;
public int j;
}
你不能这样做。我想知道为什么Car x
不会做它的工作。
答案 0 :(得分:44)
这里存在很多误解,包括问题本身和几个答案。
首先让我先研究一下这个问题的前提。问题是"为什么我们需要C#中的new
关键字?"问题的动机是C ++的这个片段:
MyClass object; // this will create object in memory
MyClass* object = new MyClass(); // this does same thing
我以两个理由批评这个问题。
首先,这些在C ++中没有做同样的事情,所以问题是基于对C ++语言的错误理解。理解C ++中这两件事之间的区别非常重要,所以如果你不清楚它们之间的区别是什么,找一位可以教你如何知道差异的导师,何时使用每个。
其次,问题预设 - 错误 - 这两种语法在C ++中做同样的事情,然后,奇怪的是,问我们为什么在C#中需要new
?"当然,在这个问题上提出正确的问题 - 再次,错误 - 预设是"为什么我们在C ++中需要new
?"如果这两个语法做同样的事情 - 他们没有 - 然后为什么首先有两个语法?
所以这个问题都是基于一个错误的前提,而关于C#的问题实际上并没有遵循 - 误解 - C ++的设计。
这是一团糟。让我们抛出这个问题并提出一些更好的问题。让我们问一下关于C# qua C#的问题,而不是在C ++设计决策的背景下。
new X
运算符在C#中做什么,其中X是类或结构类型? (为了讨论的目的,让我们忽略委托和数组。)
新操作员:
好吧,我已经可以听到C#程序员的反对意见了,所以让我们解雇他们。
异议:如果类型是值类型,则不会分配新存储,我听到你说。好吧,C#规范不同意你的看法。当你说
S s = new S(123);
对于某些结构类型S
,规范说明新临时存储在短期池上分配,初始化为其默认值,构造函数以{{1}运行设置为引用临时存储,然后将生成的对象复制到this
。但是,编译器允许使用复制省略优化,前提是它可以证明在安全程序中无法观察到优化。 (练习:在什么情况下不能进行复制省略;如果有或没有使用elision,请举例说明会有不同行为的程序。)
异议:可以使用s
生成值类型的有效实例;没有构造函数被调用,我听你说。这是对的。我并没有说default(S)
是唯一方式来创建值类型的实例。
事实上,对于值类型new
和new S()
是相同的事情。
异议:如果构造函数真正执行过default(S)
这样的情况,如果C#6中的源代码中没有,我听说你说。这是一个"如果一棵树落在森林里,没有人听到它,它会发出声音吗?"题。对没有做任何事情的构造函数的调用与根本没有调用之间是否有区别?这不是一个有趣的问题。编译器可以自由地忽略它知道什么都不做的调用。
假设我们有一个值类型的变量。我们必须使用
new S()
生成的实例初始化变量吗?
没有。 自动初始化的变量(如字段和数组元素)将初始化为默认值 - 即结构的值,其中所有字段本身都是其默认值。
显然,正式参数将使用参数初始化。
在读取字段之前,需要使用 something 明确赋值值类型的局部变量,但它不必是new
表达式。
如此有效,值类型的变量会自动初始化为
new
,除非它们是本地人?
是
为什么不为当地人做同样的事?
使用未初始化的本地与错误代码密切相关。 C#语言不允许这样做,因为这样做会发现错误。
假设我们有一个引用类型的变量。我们必须使用
default(S)
生成的实例初始化S
吗?
没有。自动初始化变量将使用null初始化。可以使用任何引用初始化本地,包括new
,并且必须在读取之前明确分配。
如此有效,引用类型的变量会自动用
null
初始化,除非它们是本地人?
是
为什么不为当地人做同样的事?
同样的道理。可能是一个错误。
为什么不通过自动调用默认构造函数来自动初始化引用类型的变量?也就是说,为什么不将
null
与R r;
相同?
首先,许多类型没有默认构造函数,或者就此而言,任何可访问的构造函数。其次,对于未初始化的本地或字段有一个规则,对于正式的另一个规则,以及对于数组元素的另一个规则,似乎很奇怪。第三,现有规则非常简单:必须将变量初始化为一个值;这个价值可以是你喜欢的任何东西; 为什么假设需要新实例??如果这个
会很奇怪R r = new R();
导致构造函数运行以初始化R r;
if (x) r = M(); else r = N();
。
除了
r
运算符的语义之外,为什么语法需要这样的运算符?
不是。有许多可以语法化的替代语法。最明显的是完全消除new
。如果我们有一个带有构造函数new
的类C
,那么我们可以简单地说C(int)
而不是C(123)
。或者我们可以使用new C(123)
等语法或类似的东西。没有C.construct(123)
运算符,有很多方法可以做到这一点。
为什么要这样?
首先,C#旨在让C ++,Java,JavaScript和其他使用new
的语言的用户立即熟悉,以指示正在为对象初始化新存储。
其次,非常需要正确的句法冗余级别。对象创建很特别;我们希望在它自己的运营商发生时呼叫。
答案 1 :(得分:28)
在C#中你可以做类似的事情:
// please notice "struct"
struct MyStruct {
....
}
MyStruct sample1; // this will create object on stack
MyStruct sample2 = new MyStruct(); // this does the same thing
回想一下像int
,double
和bool
这样的原语也属于struct
类型,所以即使它是传统的写
int i;
我们也可以写
int i = new int();
与C ++不同,C#不使用指针(在安全模式下)到实例,
但是C#有class
和struct
声明:
class
:您有引用到实例,
内存在堆上分配,
new
必填;类似于 C ++中的MyClass*
struct
:您有值,
内存(通常)分配在堆栈上,
new
可选;类似于 C ++中的MyClass
在您的特定情况下,您只需将Car
转为struct
struct Car
{
public int i;
public int j;
}
所以片段
Car x; // since Car is struct, new is optional now
x.i = 2;
x.j = 3;
将是正确的
答案 2 :(得分:15)
在C#中,class
类型对象总是在堆上分配,即这些类型的变量总是引用("指针")。只声明这种类型的变量不会导致对象的分配。在C ++中分配class
对象就像它在c ++中常见的一样,并不是(通常)C#中的一个选项。
任何未分配的类型的局部变量都被视为未初始化,并且在分配给它们之前无法读取它们。这是一个设计选择(另一种方式是在声明时将default(T)
分配给每个变量),这似乎是个好主意,因为它可以保护您免受某些编程错误的影响。
它类似于在C ++中如何说SomeClass *object;
并且永远不会为其分配任何内容。
因为在C#中所有class
类型变量都是指针,所以在声明变量时分配一个空对象会导致代码效率低下,而实际上只是想稍后为变量赋值,例如在类似的情况下这样:
// Needs to be declared here to be available outside of `try`
Foo f;
try { f = GetFoo(); }
catch (SomeException) { return null; }
f.Bar();
或者
Foo f;
if (bar)
f = GetFoo();
else
f = GetDifferentFoo();
答案 3 :(得分:12)
忽略堆栈与堆的一面:
因为C#做出错误决定复制C ++时他们应该只是制作语法
Car car = Car()
(或类似的东西)。拥有' new'是多余的。
答案 4 :(得分:7)
当您使用引用的类型时,请参阅本声明
Car c = new Car();
创建了两个实体:一个名为c
的引用到堆栈中Car类型的对象,以及类型为Car的对象本身在堆中。
如果你要写
Car c;
然后你创建一个未初始化的引用(假设c
是一个局部变量),指向无处。
实际上它等同于C ++代码,而不是引用使用指针。
例如
Car *c = new Car();
或只是
Car *c;
C ++和C#之间的区别在于C ++可以在堆栈中创建类的实例,如
Car c;
在C#中,这意味着创建一个Car类型的引用,就像我说的那样无处可寻。
答案 5 :(得分:2)
从微软编程指南:
在运行时,当您声明引用类型的变量时,该变量包含值null,直到您使用new运算符显式创建对象的实例,或者为其分配一个已在其他位置创建的对象新
类是引用类型。创建类的对象时,为其分配对象的变量仅保留对该内存的引用。将对象引用分配给新变量时,新变量引用原始对象。通过一个变量进行的更改会反映在另一个变量中,因为它们都引用相同的数据。
结构是一种值类型。创建结构时,为其分配结构的变量保存结构的实际数据。将结构分配给新变量时,会复制该变量。因此,新变量和原始变量包含相同数据的两个单独副本。对一个副本所做的更改不会影响另一个副本。
我认为在您的C#示例中,您有效地尝试将值分配给空指针。在c ++翻译中,这看起来像:
Car* x = null;
x->i = 2;
x->j = 3;
这显然会编译但崩溃。