Ich bin neu in C # und komme aus C++. In C++ können Sie dies tun:
class MyClass{
....
};
int main()
{
MyClass object; // this will create object in memory
MyClass* object = new MyClass(); // this does same thing
}
Während in 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;
}
das kannst du nicht machen Ich wundere mich warum Car x
macht seine Arbeit nicht.
Hier gibt es viele Missverständnisse, sowohl in der Frage selbst als auch in den verschiedenen Antworten.
Lassen Sie mich zunächst die Prämisse der Frage untersuchen. Die Frage ist "Warum brauchen wir das new
Schlüsselwort in C #?" Die Motivation für die Frage ist dieses Fragment von C++:
MyClass object; // this will create object in memory
MyClass* object = new MyClass(); // this does same thing
Ich kritisiere diese Frage aus zwei Gründen.
Erstens diese tun in C++ nicht dasselbe, sodass die Frage auf einem fehlerhaften Verständnis der C++ - Sprache beruht. Es ist sehr wichtig, den Unterschied zwischen diesen beiden Dingen in C++ zu verstehen. Wenn Sie also den Unterschied nicht genau verstehen, suchen Sie sich einen Mentor, der Ihnen beibringt, wie Sie den Unterschied erkennen können. und wann man sie benutzt.
Zweitens setzt die Frage fälschlicherweise voraus, dass diese beiden Syntaxen in C++ dasselbe tun, und fragt dann seltsamerweise: "Warum brauchen wir new
in C #?" Sicherlich ist die richtige Frage bei dieser - wiederum falschen - Annahme: "Warum brauchen wir new
in C++?" Wenn diese beiden Syntaxen dasselbe tun - was sie nicht tun - dann warum haben sie überhaupt zwei Syntaxen?
Die Frage basiert also auf einer falschen Prämisse, und die Frage nach C # ergibt sich eigentlich nicht aus dem - missverstandenen - Design von C++.
Das ist ein Chaos. Lassen Sie uns diese Frage wegwerfen und einige bessere Fragen stellen. Stellen wir die Frage nach C # qua C # und nicht im Kontext der Entwurfsentscheidungen von C++.
Was macht der Operator
new X
In C #, wobei X ein Klassen- oder Strukturtyp ist? (Lassen Sie uns die Delegierten und Arrays für die Zwecke dieser Diskussion ignorieren.)
Der neue Betreiber:
Okay, ich kann die Einwände von C # -Programmierern bereits hören, also lasst uns sie abweisen.
Einwand: Es wird kein neuer Speicher zugewiesen, wenn es sich bei dem Typ um einen Werttyp handelt. Nun, die C # -Spezifikation stimmt nicht mit Ihnen überein. Wenn du sagst
S s = new S(123);
für einige Strukturtypen S
wird in der Spezifikation angegeben, dass neuer temporärer Speicher für den Kurzzeitpool zugewiesen wird, der auf seinen Standardwert initialisiert wurde Werte, der Konstruktor wird mit this
ausgeführt, um auf den temporären Speicher zu verweisen, und dann wird das resultierende Objekt nach s
. Dem Compiler ist es jedoch gestattet , eine Kopieroptimierung zu verwenden, sofern er nachweisen kann, dass die Optimierung in einem sicheren Programm nicht beobachtet werden kann . (Übung: Ermitteln Sie, unter welchen Umständen eine Kopierentfernung nicht durchgeführt werden kann. Nennen Sie ein Beispiel für ein Programm, das sich anders verhält, wenn elision verwendet wurde oder nicht.)
Einwand: Mit default(S)
kann eine gültige Instanz eines Wertetyps erzeugt werden; Es wird kein Konstruktor aufgerufen, höre ich Sie sagen. Das ist richtig. Ich habe nicht gesagt, dass new
die einzige Möglichkeit ist, eine Instanz eines Werttyps zu erstellen.
Tatsächlich sind für einen Werttyp new S()
und default(S)
dasselbe.
Einwand: Wird ein Konstruktor wirklich für Situationen wie new S()
ausgeführt, wenn er nicht im Quellcode in C # 6 vorhanden ist, höre ich Sie sagen. Dies ist ein "Wenn ein Baum in den Wald fällt und niemand es hört, macht es dann ein Geräusch?" Frage. Gibt es einen Unterschied zwischen einem Aufruf eines Konstruktors, der nichts tut, und überhaupt keinem Aufruf? Dies ist keine interessante Frage. Dem Compiler steht es frei, Aufrufe zu ignorieren, von denen er weiß, dass sie nichts tun.
Angenommen, wir haben eine Variable vom Werttyp. Müssen wir die Variable mit einer von
new
erzeugten Instanz initialisieren?
Nein. Variablen, die automatisch initialisiert werden , wie z. B. Felder und Array-Elemente, werden auf den Standardwert, dh den Wert der Struktur, initialisiert wobei alle Felder selbst ihre Standardwerte sind.
Formale Parameter werden natürlich mit dem Argument initialisiert.
Lokale Variablen des Wertetyps müssen vor dem Lesen der Felder definitiv mit etwas zugewiesen werden, es muss sich jedoch nicht um ein new
handeln. Ausdruck.
So effektiv werden Variablen des Wertetyps automatisch mit dem Äquivalent von
default(S)
initialisiert, es sei denn, es handelt sich um lokale Variablen?
Ja.
Warum nicht das Gleiche für Einheimische tun?
Die Verwendung eines nicht initialisierten lokalen Codes ist stark mit fehlerhaftem Code verbunden. Die C # -Sprache lässt dies nicht zu, da dies zu Fehlern führt.
Angenommen, wir haben eine Variable vom Referenztyp. Müssen wir
S
mit einer vonnew
erzeugten Instanz initialisieren?
Nein. Variablen für die automatische Initialisierung werden mit Null initialisiert. Einheimische können mit jedem Verweis initialisiert werden, einschließlich null
, und müssen vor dem Lesen eindeutig zugewiesen werden.
So effektiv werden Variablen des Referenztyps automatisch mit
null
initialisiert, es sei denn, es handelt sich um lokale Variablen?
Ja.
Warum nicht das Gleiche für Einheimische tun?
Gleicher Grund. Ein wahrscheinlicher Fehler.
Warum nicht automatisch Variablen des Referenztyps initialisieren, indem der Standardkonstruktor automatisch aufgerufen wird? Das heißt, warum sollte
R r;
Nicht mitR r = new R();
identisch sein?
Nun, zuallererst haben viele Typen keinen Standardkonstruktor oder überhaupt irgendein zugänglicher Konstruktor. Zweitens erscheint es seltsam, eine Regel für ein nicht initialisiertes lokales Element oder Feld, eine andere Regel für ein formales Element und eine weitere Regel für ein Array-Element zu haben. Drittens ist die bestehende Regel sehr einfach: Eine Variable muss mit einem Wert initialisiert werden. Dieser Wert kann beliebig sein. warum ist die Annahme, dass eine neue Instanz gewünscht wird, gerechtfertigt? Es wäre bizarr, wenn das so wäre
R r;
if (x) r = M(); else r = N();
veranlasste einen Konstruktor, ausgeführt zu werden, um r
zu initialisieren.
Abgesehen von der Semantik des Operators
new
, warum ist es notwendig syntaktisch einen solchen Operator haben?
Es ist nicht. Es gibt eine beliebige Anzahl von alternativen Syntaxen, die grammatikalisch sein können. Am naheliegendsten wäre es, das new
einfach ganz zu eliminieren. Wenn wir eine Klasse C
mit einem Konstruktor C(int)
haben, können wir einfach C(123)
anstelle von new C(123)
sagen. Oder wir könnten eine Syntax wie C.construct(123)
oder etwas Ähnliches verwenden. Es gibt eine beliebige Anzahl von Möglichkeiten, dies ohne den Operator new
zu tun.
Warum also?
Zunächst wurde C # entwickelt, um Benutzern von C++, Java, JavaScript und anderen Sprachen, die new
verwenden, um anzuzeigen, dass ein neuer Speicher für ein Objekt initialisiert wird, sofort vertraut zu sein.
Zweitens ist das richtige Maß an syntaktischer Redundanz äußerst wünschenswert. Objekterstellung ist etwas Besonderes; wir möchten anrufen, wenn es mit seinem eigenen Operator geschieht.
In C # können Sie die ähnliche Sache tun:
// please notice "struct"
struct MyStruct {
....
}
MyStruct sample1; // this will create object on stack
MyStruct sample2 = new MyStruct(); // this does the same thing
Denken Sie daran, dass Primitive wie int
, double
und bool
ebenfalls vom Typ struct
sind, obwohl das Schreiben konventionell ist
int i;
wir können auch schreiben
int i = new int();
im Gegensatz zu C++ verwendet C # keine Zeiger (im abgesicherten Modus) auf Instanzen, C # verfügt jedoch über class
- und struct
-Deklarationen:
class
: Sie haben Verweis auf Instanz, Speicher wird zugewiesen auf Heap, new
ist obligatorisch; ähnlich zu MyClass*
in C++
struct
: Sie haben Wert, Speicher wird (normalerweise) auf Stapel zugewiesen, new
ist optional; ähnlich wie MyClass
in C++
In Ihrem speziellen Fall können Sie Car
einfach in struct
verwandeln.
struct Car
{
public int i;
public int j;
}
und so das fragment
Car x; // since Car is struct, new is optional now
x.i = 2;
x.j = 3;
wird richtig sein
In C # werden Objekte vom Typ class
immer auf dem Heap zugeordnet, d. H. Variablen dieses Typs sind immer Referenzen ("Zeiger"). Das Deklarieren einer Variablen dieses Typs führt nicht zur Zuweisung eines Objekts. Das Zuweisen eines class
-Objekts auf dem Stapel, wie es in C++ üblich ist, ist (im Allgemeinen) keine Option in C #.
Lokale Variablen jeglichen Typs, die nicht zugewiesen wurden, gelten als nicht initialisiert und können erst gelesen werden, wenn sie zugewiesen wurden. Dies ist eine Design-Wahl (eine andere Möglichkeit wäre gewesen, jeder Variablen zum Zeitpunkt der Deklaration default(T)
zuzuweisen), was eine gute Idee zu sein scheint, da es Sie vor einigen Programmierfehlern schützen sollte.
Ähnlich wie in C++ wäre es nicht sinnvoll, SomeClass *object;
Zu sagen und ihm niemals etwas zuzuweisen.
Da in C # alle Variablen vom Typ class
Zeiger sind, führt die Zuweisung eines leeren Objekts bei der Deklaration der Variablen zu ineffizientem Code, wenn Sie der Variablen später tatsächlich nur einen Wert zuweisen möchten, beispielsweise in folgenden Situationen:
// Needs to be declared here to be available outside of `try`
Foo f;
try { f = GetFoo(); }
catch (SomeException) { return null; }
f.Bar();
Oder
Foo f;
if (bar)
f = GetFoo();
else
f = GetDifferentFoo();
ignorieren des Stacks im Vergleich zum Heap:
weil C # die falsche Entscheidung getroffen hat, C++ zu kopieren, wenn sie gerade die Syntax hätten treffen sollen
Car car = Car()
(oder etwas ähnliches). 'Neu' zu haben ist überflüssig.
Wenn Sie referenzierte Typen verwenden, dann in dieser Anweisung
Car c = new Car();
es werden zwei Entitäten erstellt: ein Verweis mit dem Namen c
auf ein Objekt vom Typ Car im Stapel und das Objekt vom Typ Car selbst im Heap.
Wenn du nur schreibst
Car c;
anschließend erstellen Sie eine nicht initialisierte Referenz (vorausgesetzt, c
ist eine lokale Variable), die auf nichts verweist.
Tatsächlich entspricht es C++ - Code, bei dem anstelle von Referenzen Zeiger verwendet werden.
Beispielsweise
Car *c = new Car();
oder nur
Car *c;
Der Unterschied zwischen C++ und C # besteht darin, dass C++ Instanzen von Klassen im Stapel erstellen kann
Car c;
In C # bedeutet dies, eine Referenz vom Typ Auto zu erstellen, die, wie gesagt, nirgendwo hinweist.
Aus dem Microsoft-Programmierhandbuch:
Wenn Sie zur Laufzeit eine Variable eines Referenztyps deklarieren, enthält die Variable den Wert null, bis Sie mit dem Operator new explizit eine Instanz des Objekts erstellen oder ihr mit new ein Objekt zuweisen, das an anderer Stelle erstellt wurde
Eine Klasse ist ein Referenztyp. Wenn ein Objekt der Klasse erstellt wird, enthält die Variable, der das Objekt zugewiesen ist, nur einen Verweis auf diesen Speicher. Wenn die Objektreferenz einer neuen Variablen zugewiesen wird, bezieht sich die neue Variable auf das ursprüngliche Objekt. Änderungen, die über eine Variable vorgenommen wurden, werden in der anderen Variablen wiedergegeben, da beide auf dieselben Daten verweisen.
Eine Struktur ist ein Werttyp. Wenn eine Struktur erstellt wird, enthält die Variable, der die Struktur zugewiesen ist, die tatsächlichen Daten der Struktur. Wenn die Struktur einer neuen Variablen zugewiesen wird, wird sie kopiert. Die neue Variable und die ursprüngliche Variable enthalten daher zwei separate Kopien derselben Daten. An einer Kopie vorgenommene Änderungen wirken sich nicht auf die andere Kopie aus.
Ich denke, dass Sie in Ihrem C # -Beispiel effektiv versuchen, einem Nullzeiger Werte zuzuweisen. In c ++ Übersetzung würde dies so aussehen:
Car* x = null;
x->i = 2;
x->j = 3;
Dies würde natürlich kompilieren, aber abstürzen.