为啥 C# 和 Java 会为“new”运算符而烦恼?
Posted
技术标签:
【中文标题】为啥 C# 和 Java 会为“new”运算符而烦恼?【英文标题】:Why do C# and Java bother with the "new" operator?为什么 C# 和 Java 会为“new”运算符而烦恼? 【发布时间】:2010-09-30 19:42:02 【问题描述】:为什么new operator 存在于 C# 和 Java 等现代语言中?它是纯粹的自我记录代码功能,还是有任何实际用途?
例如下面的例子:
Class1 obj = new Class1();
Class1 foo()
return new Class1();
像 Pythonesque 的写法一样容易阅读:
Class1 obj = Class1();
Class1 foo()
return Class1();
编辑:Cowan 澄清了问题:他们为什么选择这种语法?
【问题讨论】:
【参考方案1】:-
这是一种自我记录功能。
这是一种可以在其他类中将方法命名为“Class1”的方法
【讨论】:
第二点:无论如何你都可以这样做,但必须使用 this.Class1() 来限定它。当类型和成员名称发生冲突时,现在也会发生同样的事情。 另一方面,如果没有 new 运算符,则可以在其他地方创建一个 new() 函数。 ;) 我认为答案很简单:“Java/C# 所针对的程序员看起来更熟悉它。 2.这使得它成为可能,但是你到底为什么要违背良好的命名约定呢?你知道,类应该是名词和方法动词......【参考方案2】:Class1 obj = Class1();
在 C# 和 Java 中,您需要“new”关键字,因为没有它,它会将“Class1()”视为对名为“Class1”的方法的调用。
【讨论】:
是的,但如果不是这样呢?为什么new
运算符首先存在?
但是是的情况。你问的是,“如果不是引擎驱动汽车怎么办?为什么'引擎'首先存在?”
如果一匹马在移动汽车,而发动机只是控制收音机和空调怎么办?或者如果其他类型的汽车可以在没有发动机的情况下行驶怎么办?
我会回答你的问题,但我有一辆火车,它是由一头牛移动的,它的引擎控制着汽笛和车内照明。
这和问这个完全不一样。问题是“他们为什么选择这种语法?”。这更像是“他们为什么选择内燃发动机来驱动汽车?”,这是一个非常明智的问题,并且有非常明智的答案。【参考方案3】:
文档的用处在于 - 它比 Python 更容易区分对象创建和方法调用。
原因是历史性的,直接来自 C++ 语法。 在 C++ 中,“Class1()”是一个在堆栈上创建 Class1 实例的表达式。例如: 矢量 a = 矢量(); 在这种情况下,会创建一个向量并将其复制到向量 a(优化器在某些情况下可以删除冗余副本)。
相反,“new Class1()”在堆上创建一个 Class1 实例,就像在 Java 和 C# 中一样,并返回一个指向它的 指针,具有不同的访问权限语法,与 Java 和 C++ 不同。实际上,new 的含义可以重新定义为使用任何特殊用途的分配器,它仍然必须引用某种堆,以便获得的对象可以通过引用返回。
此外,在 Java/C#/C++ 中,Class1() 本身可以引用任何方法/函数,这会造成混淆。 Java 编码约定实际上会避免这种情况,因为它们要求类名以大写字母开头,方法名以小写字母开头,这可能是 Python 在这种情况下避免混淆的方式。读者期望“Class1()”创建一个对象,“class1()”是一个函数调用,“x.class1()”是一个方法调用(其中“x”可以是“self”)。
最后,由于在 Python 中他们选择让类成为对象,尤其是可调用对象,因此允许使用不带“new”的语法,而允许同时使用另一种语法将是不一致的。
【讨论】:
不仅如此; Python 中的函数和构造函数之间没有语义差异,至少在类外没有。就像你可以使用def inc(a):return a + 1; map(inc,somelist)
来增加somelist
中的所有项目一样,你可以使用map(int,somelist)
来将somelist
中的所有项目转换为int
s。【参考方案4】:
C# 中的 new 运算符直接映射到名为 newobj
的 IL 指令,该指令实际上为新对象的变量分配空间,然后执行构造函数(在 IL 中称为 .ctor)。在执行构造函数时——很像 C++——对初始化对象的引用作为不可见的第一个参数传入(如 thiscall)。
类似于 thiscall 的约定允许运行时只为特定类加载和 JIT 内存中的所有代码,并为该类的每个实例重用它。
Java 在它的中间语言中可能有类似的操作码,虽然我不太熟悉。
【讨论】:
【参考方案5】:C++ 为程序员提供了在堆上或栈上分配对象的选择。
基于堆栈的分配是more efficient:分配更便宜,释放成本真正为零,并且该语言在划分对象生命周期方面提供了帮助,降低了忘记释放对象的风险。 另一方面,在 C++ 中,在发布或共享对基于堆栈的对象的引用时需要非常小心,因为基于堆栈的对象会在堆栈帧展开时自动释放,从而导致指针悬空。
使用 new
运算符,所有对象都在 Java 或 C# 中的堆上分配。
Class1 obj = Class1();
实际上,编译器会尝试找到一个名为Class1()
的方法。
例如以下是一个常见的Java bug:
public class MyClass
//Oops, this has a return type, so its a method not a constructor!
//Because no constructor is defined, Java will add a default one.
//init() will not get called if you do new MyClass();
public void MyClass()
init();
public void init()
...
注意:“所有对象都在堆上分配”并不意味着堆栈分配不会偶尔在后台使用。
例如,在 Java 中,像 escape analysis 这样的热点优化使用堆栈分配。
运行时编译器执行的这种分析可以得出结论,例如,堆上的对象仅在方法中被本地引用,并且没有引用可以从这个范围中逃脱。如果是这样,Hotspot 可以应用运行时优化。它可以在堆栈上或寄存器中分配对象,而不是在堆上。
这种优化并不总是considereddecisive...
【讨论】:
【参考方案6】:Java 选择它的原因是因为 C++ 开发人员熟悉该语法。 C# 之所以选择它,是因为它为 Java 开发者所熟悉。
new
运算符在 C++ 中使用的原因可能是因为在手动内存管理中,明确何时分配内存非常重要。虽然 pythonesque 语法可以工作,但它使得内存分配不太明显。
【讨论】:
【参考方案7】:new 操作符为对象分配内存,这是它的目的;正如您所说,它还记录了您正在使用的哪个实例(即一个新实例)
【讨论】:
在 C# 中为结构调用 new 不会分配内存。 确实如此,只是在堆栈上,而不是在堆上 不是真的,进入函数的时候已经分配好了。【参考方案8】:正如其他人所指出的,Java 和 C# 提供 new
语法,因为 C++ 提供了。 C++ 需要某种方法来区分在堆栈上创建对象、在堆上创建对象或调用返回指向对象的指针的函数或方法。
C++ 使用这种特殊 语法是因为早期的面向对象语言 Simula 使用了它。 Bjarne Stroustrup 受到 Simula 的启发,并试图在 C 中添加类似 Simula 的功能。C 有一个分配内存的函数,但不保证也调用了构造函数。
摘自“C++ 的设计和演变”,1994 年,Bjarne Stroustrup 着,第 57 页:
因此,我引入了一个运算符来确保分配和初始化都完成:
monitor* p = new monitor;
运算符被称为
new
,因为那是对应 Simula 运算符的名称。new
运算符调用一些分配函数来获取内存,然后调用构造函数来初始化该内存。组合操作通常称为实例化或简称为对象创建:它从原始内存中创建一个对象。运算符
new
提供的符号便利性非常重要。 ……”
【讨论】:
【参考方案9】:除了 AFAIK 上面的评论外,他们还计划在早期草稿中删除 Java 7 的新关键字。但后来他们取消了。
【讨论】:
以上是关于为啥 C# 和 Java 会为“new”运算符而烦恼?的主要内容,如果未能解决你的问题,请参考以下文章