簡體   English   中英

我不明白為什么我們需要'new'關鍵字

[英]I don't understand why we need the 'new' keyword

我是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不會做它的工作。

這里存在很多誤解,無論是在問題本身還是在幾個答案中。

讓我首先研究一下這個問題的前提。 問題是“我們為什么需要C#中的new關鍵字?” 問題的動機是C ++的這個片段:

 MyClass object; // this will create object in memory
 MyClass* object = new MyClass(); // this does same thing

我基於兩個理由批評這個問題。

首先, 這些在C ++中沒有做同樣的事情 ,所以問題是基於對C ++語言的錯誤理解。 理解C ++中這兩件事之間的區別非常重要的 ,所以如果你不清楚什么是差異,找一個可以教你如何知道差異是什么以及何時使用每個東西的導師。

其次,問題預先假設 - 錯誤 - 這兩種語法在C ++中做同樣的事情,然后,奇怪的是,問“為什么我們需要new的C#?” 肯定是正確的問題,因為這一點 - 再次,錯誤 - 預設是“為什么我們需要new的C ++?” 如果這兩個語法做同樣的事情 - 他們沒有 - 那么為什么首先有兩個語法?

所以這個問題都是基於錯誤的前提,而關於C#的問題實際上並沒有遵循 - 誤解 - C ++的設計。

這是一團糟。 讓我們拋出這個問題並提出一些更好的問題。 讓我們問一下關於C# qua C#的問題,而不是在C ++的設計決策環境中。

new X運算符在C#中做了什么,其中X是類或結構類型? (為了討論的目的,讓我們忽略委托和數組。)

新運營商:

  • 導致分配給定類型的新實例; 新實例將其所有字段初始化為默認值。
  • 導致執行給定類型的構造函數。
  • 如果對象是引用類型,則生成對已分配對象的引用 ;如果對象是類型,則生成對值本身的引用。

好吧,我已經可以聽到C#程序員的反對意見,所以讓我們解雇他們。

異議:如果類型是值類型,則不會分配新的存儲空間,我聽到你說。 好吧,C#規范不同意你的看法。 當你說

S s = new S(123);

對於某些結構類型S ,規范說新的臨時存儲在短期池上分配,初始化為其默認值,構造函數使用this set運行以引用臨時存儲,然后將生成的對象復制s 但是, 允許編譯器使用復制省略優化,前提是它可以證明在安全程序中無法觀察到優化。 (練習:在什么情況下不能執行復制省略;如果有或沒有使用elision,請舉例說明會有不同行為的程序。)

異議:可以使用default(S)生成值類型的有效實例; 沒有構造函數被調用,我聽你說。 那是對的。 我沒有說new是創建值類型實例的唯一方法。

實際上,對於值類型, new S()default(S)是相同的。

異議:對於像new S()這樣的情況,構造函數是否真的被執行了,如果不存在於C#6的源代碼中,我聽你說。 這是“如果一棵樹落在森林里,沒有人聽到它,它會發出聲音嗎?” 題。 對沒有做任何事情的構造函數的調用與根本沒有調用之間是否有區別? 這不是一個有趣的問題。 編譯器可以自由地忽略它知道什么都不做的調用。

假設我們有一個值類型的變量。 我們必須用new生成的實例初始化變量嗎?

否。 自動初始化的變量(如字段和數組元素)將初始化為默認值 - 即結構的值,其中所有字段本身都是其默認值。

顯然,正式參數將使用參數初始化。

需要值類型的局部變量的東西被明確賦值讀取領域之前,但它不一定是一個new表達。

如此有效,值類型的變量會自動初始化為等效的default(S) ,除非它們是本地的?

是。

為什么不為當地人做同樣的事呢?

使用未初始化的本地與錯誤代碼密切相關。 C#語言不允許這樣做,因為這樣做會發現錯誤。

假設我們有一個引用類型的變量。 我們必須使用new生成的實例初始化S嗎?

不會。自動初始化變量將使用null初始化。 可以使用任何引用初始化本地,包括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)這樣的語法或類似的東西。 沒有new運算符,有很多方法可以做到這一點。

那為什么呢?

首先,C#被設計為C ++,Java,JavaScript和其他語言的用戶立即熟悉,這些語言使用new來表示正在為對象初始化新存儲。

其次,非常需要正確的句法冗余水平。 對象創建很特別; 我們希望在它自己的運營商發生時呼叫。

在C#中你可以做類似的事情:

  // please notice "struct"
  struct MyStruct {
    ....
  }

  MyStruct sample1; // this will create object on stack
  MyStruct sample2 = new MyStruct(); // this does the same thing

回想一下像intdoublebool這樣的原語也是struct類型,所以即使它是常規的寫

  int i;

我們也可以寫

  int i = new int(); 

與C ++不同,C#不使用指針(在安全模式下)到實例,但C#具有classstruct聲明:

  • 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;

會是對的

在C#, class型對象總是在堆上分配,這樣類型的,即變量始終引用(“指針”)。 只聲明這種類型的變量不會導致對象的分配。 像在C ++中常見的那樣在堆棧上分配class對象(通常)不是C#中的選項。

未分配的任何類型的局部變量都被視為未初始化,並且在分配給它們之前無法讀取它們。 這是一個設計選擇(另一種方式是在聲明時將default(T)分配給每個變量),這似乎是個好主意,因為它可以保護您免受某些編程錯誤的影響。

它類似於在C ++中如何說SomeClass *object;是沒有意義的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();

忽略堆棧與堆棧的事情:

因為C#做出了錯誤的決定來復制C ++,因為他們應該剛剛制作語法

Car car = Car()

(或類似的東西)。 擁有'新'是多余的。

當您在此語句中使用引用的類型時

Car c = new Car();

創建了兩個實體:一個名為c的引用,它是堆棧中Car類型的對象,另一個是Car類型的對象。

如果你只是寫

Car c;

然后你創建一個未初始化的引用(假設c是一個局部變量)指向無處。

實際上它等同於C ++代碼,而不是引用使用指針。

例如

Car *c = new Car();

要不就

Car *c;

C ++和C#之間的區別在於C ++可以在堆棧中創建類的實例

Car c;

在C#中,這意味着創建Car類型的引用,就像我說的那樣無處可尋。

從微軟編程指南:

在運行時,當您聲明引用類型的變量時,該變量包含值null,直到您使用new運算符顯式創建對象的實例,或者使用new為其分配已在其他位置創建的對象。

類是引用類型。 創建類的對象時,為其分配對象的變量僅保留對該內存的引用。 將對象引用分配給新變量時,新變量引用原始對象。 通過一個變量進行的更改會反映在另一個變量中,因為它們都引用相同的數據。

結構是一種值類型。 創建結構時,為其分配結構的變量保存結構的實際數據。 將結構分配給新變量時,會復制該變量。 因此,新變量和原始變量包含相同數據的兩個單獨副本。 對一個副本所做的更改不會影響另一個副本。

我認為在您的C#示例中,您有效地嘗試將值分配給空指針。 在c ++翻譯中,這看起來像:

Car* x = null;
x->i = 2;
x->j = 3;

這顯然會編譯但崩潰。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM