Ein Blick auf Go durch die Augen eines .NET-Entwicklers. Woche # 1

Hallo allerseits!

Mein Name ist Lex und ich bin der Moderator des YouTube- Kanals " IT Beard ". Und ich bin ein 6-jähriger Mitarbeiter. Vor kurzem hatte ich den Wunsch, über meine Kerntechnologie (C # /. NET) hinauszugehen und die Essenz des Blob-Paradoxons zu verstehen. Ich entschied fest, dass ich mich in einer anderen Sprache versuchen würde, und die Wahl fiel zufällig auf Go.

Um strukturiertes Wissen zu erlangen, habe ich mich für einen Kurs bei mail.ru angemeldet, den Sie hier finden: https://www.coursera.org/learn/golang-webservices-1 . Was ich aus der ersten Trainingswoche in diesem Kurs gelernt habe, wird weiter besprochen. Lass uns gehen!


Ich werde mit einem kleinen Intro über die Sprache beginnen. Go wurde bereits zur Zeit der Multi-Core-Prozessoren (2007-2009) entwickelt, daher ist hier alles sehr gut, da die Arbeit über die Kerne hinweg parallelisiert wird. Darüber hinaus eignet sich die Sprache hervorragend für wettbewerbsfähige (gleichzeitige) Anfragen. Im Allgemeinen ein Fund für alle Arten von Webdiensten und geladenen Websystemen. Genau das, was ich brauche, weil ich in den letzten Jahren eine Web-API (Web-API) entwickelt habe.

Starten Sie


Um mit der Sprache zu arbeiten, reicht es aus, das Softwarepaket „Go tools“ mit einer Größe von 118 MB zu installieren, und Sie können mit dem Codieren beginnen.

Die Skripte haben die Erweiterung * .go und werden vom Befehl go in der Befehlszeile ausgeführt (ich bin ein Anhänger der Windows-Befehlszeile). Der Befehl gofmt platziert alle Einrückungsbereiche und erstellt Süßigkeiten aus der Datei (Code-Verschönerer aus der Box). Wie in meinem bevorzugten Subnetz beginnt die Programmausführung in Go mit der Hauptmethode.
Eine coole Funktion ist mir sofort aufgefallen - Sie können kein Semikolon am Ende der Zeile einfügen :)

Go ist eine andere Sprache (zusammen mit Python, c #, php, js, ts und anderen), mit der Sie in VSCode bequem arbeiten können. VSCode selbst liefert beim Schreiben des Codes absolut alle erforderlichen Pakete und Abhängigkeiten. Führen Sie beim Speichern der IDE freundlicherweise gofmt für Sie aus und machen Sie den Code noch schöner.

Variablen


Hier ist alles bekannt: Es gibt viele verschiedene Typen, es gibt Standardwerte. Das für einen Spender ungewöhnliche Schlüsselwort var bedeutet, dass die Variable weiter geht und der Typ der Variablen bereits nach dem Variablennamen deklariert werden kann oder nicht. Ungewöhnlich.

Im Allgemeinen gibt es viele Möglichkeiten, Variablen zu deklarieren und zu initialisieren. Sie können sogar mehreren Variablen, die durch Kommas getrennt sind, in derselben Zeile Werte zuweisen. Übrigens gibt es keine impliziten Besetzungen.

Und Go hat auch einen Operator " : = ", mit dem Sie neue (und fast immer nur neue) Variablen deklarieren und sofort initialisieren können. Hallo Pascal;)

Datensatzansicht:

perem1, perem2 := 2, 3 
Entweder werden zwei neue Variablen erstellt oder eine davon, und die zweite weist einfach einen neuen Wert zu. Dies bedeutet, dass wenn beide Variablen bereits vorhanden sind, dies nicht funktioniert und Sie eine Fehlermeldung erhalten. Sie sagen, dass Sie die übliche Zuordnung verwenden. Operator : = muss etwas erstellen :)

Es gibt auch eine lustige Fälschung - den Unterstrich "_". Es bedeutet das Fehlen einer Variablen, so etwas :) (wie sich herausstellte, ist es bei den Sharps auch: https://docs.microsoft.com/en-us/dotnet/csharp/discards )

Bild

Ein lustiger Moment mit Zeichenfolgen: Die Standardanzahl der Zeichenfolgenlänge einer Zeichenfolge len (str) zählt Bytes , und in UTF8 kann ein Zeichen mehr wiegen. Um die als Runen bezeichneten Zeichen zu zählen, müssen Sie die Methode utf8.RuneCountInString (str) aus dem Paket „utf8“ verwenden. Und doch ändern wir die Zeilen nicht (genau wie bei Ihrem Favoriten).

Konstanten


Konstanten können stapelweise deklariert werden. Konstanten haben einen interessanten Typ oder so - iota . Ich werde es Jopta nennen. Mit Jopta können Sie einige Konstanten auf der Basis anderer ausführen, so etwas wie einen Iterator für Konstanten (etwas, das an die Ausbeute eines Subnetzes erinnert).

Konstanten dürfen jedoch nicht eingegeben werden. Für solche Konstanten entscheidet der Compiler zum richtigen Zeitpunkt, welcher Typ sie sind. Praktischerweise, was ist schon da.



Zeiger


Ja Ja! Hier gibt es Hinweise. Aber es scheint nicht sehr beängstigend. Sie sagen, dass sie nicht nummeriert sind und Sie den nächsten Zeiger nicht erhalten können, indem Sie dem Zeiger eine Gerade hinzufügen. Hier ist es wie eine Variable, die einen Link zu einer anderen Variablen speichert (bisher habe ich es irgendwie verstanden).

Der Zeiger wird durch ein kaufmännisches Mittel oder die neue (Typ-) Methode angegeben (um einen Zeiger auf einen Typ und keine Variable zu erhalten):



Wenn Sie direkt auf den Zeiger zugreifen, ändert sich der Link. Und wenn über den Operator " * ", ändert sich der Wert der Variablen, die hinter dem Zeiger liegt (auf den er zeigt). Im Allgemeinen ist es noch nicht sehr klar, aber dann, so heißt es, wird es klarer.

Arrays


Die Arraygröße ist Teil des Datentyps! Arrays mit unterschiedlichen Dimensionen sind also tatsächlich unterschiedliche Datentypen und nicht kompatibel.



Aber was für Arrays werden benötigt, wenn sie so trocken und unveränderlich sind (sie können zur Laufzeit nicht geändert werden). Daher haben wir solche Slices (Slices) entwickelt, die auf Arrays basieren.



Eine Scheibe hat eine Länge und Kapazität . Es erinnerte mich ein wenig an den Datentyp nvarchar aus SQL. Das heißt, Sie können Speicherplatz (Speicherzuordnung) für Ihre Arrays zuweisen, dort jedoch Arrays beliebiger Länge (bis zur Kapazität) platzieren. Kurz gesagt, die Sache ist interessant, man muss sich daran gewöhnen. Für die bequeme Verwendung von Slices gibt es alle Arten von Methoden wie make (), append (), len (), cap () und natürlich auch andere. Aus einem Slice (Slice) können Sie leicht ein Sub-Slice (Slice Slice) erhalten. Übrigens erweitert append () die Slice-Kapazität.

Wenn das Sub-Slice gleich dem Slice ist, beziehen sie sich tatsächlich auf ein Stück Speicher. Dementsprechend führt eine Änderung des Werts in einem Slice zu einer Änderung des zweiten Slice. Wenn jedoch einer von ihnen durch append () erweitert wird , wird ein neuer Speicher zugewiesen.
Kurz gesagt, alles ist ernst mit dem Gedächtnis :)

Map, Hash-Tabelle, assoziatives Array


All dies ist ein und dasselbe. Go hat Hash-Tabellen für die schnelle Schlüsselsuche. Wie folgt initialisiert:



Sie können Untertabellen (Map Map) usw. erstellen. (jede Art von Verschachtelung, ähnlich). Es gibt einen Standardwert, der für nicht vorhandene Schlüssel zurückgegeben wird. Es wird vom Schlüsseltyp übernommen (für bool dann false). Zu wissen, ob eine Variable standardmäßig zurückgegeben wurde, d.h. dass der Schlüssel fehlt - verwenden Sie den Schlüssel zur Existenz des Schlüssels (interessante Sache):



Kontrollstrukturen


Es gibt ein Wenn-Sonst. Etwas interessanter als bei Sharps, da Sie die Initialisierungsbedingungen für if verwenden können.

Es gibt einen Swith-Fall. Pausen müssen nicht eingestellt werden, und das ist ein Unterschied. Umkehrung der Logik) Wenn Sie möchten, dass auch die folgende Bedingung überprüft wird, müssen Sie Fallthrough schreiben.

Es gibt für. Und das alles mit Zyklen.



Mit iterativem Slice mehr Spaß (Range Operator):



Und selbst mit der Hash-Karte können Sie iterieren (obwohl die Reihenfolge oft unterschiedlich ist, weil die Karte nicht gerichtet ist):



Bei Typzeichenfolgen wird der Bereich über Runen und nicht über Bytes iteriert.

Funktionen


Über das Schlüsselwort func deklariert. Darüber hinaus wird die Inversion der Logik erneut mit dem Subnetz verglichen - zuerst werden die Eingabeparameter und dann der Typ des Rückgabewerts angezeigt (Sie können ihn sofort benennen).

Funktionen können mehrere Ergebnisse zurückgeben , genau wie Tupel in einem Subnetz .

Die benannten Werte, die die Funktion zurückgibt, werden standardmäßig mit Standardwerten für den Typ initialisiert.

Sie können jedoch variable Funktionen erstellen, die eine unbegrenzte Anzahl von Eingabeparametern desselben Typs aufweisen (wie Parameter im Subnetz). Und dann kommt ein bestimmter Slice-Typ zur Eingabe der Funktion.

Funktionen können Namen haben oder ohne Namen sein - anonym . Und sie können aufgerufen, Variablen zugeordnet werden. Es sieht aus wie JavaScript. Sie können sogar Typen basierend auf Funktionen erstellen! Funktionen können als Parameter übergeben werden (Delegaten aus Subnetz lesen)

Es gibt sogar eine Schließung (uuuh, beängstigend)! Sie können Variablen über die Funktion außerhalb erreichen (lesen Sie in der übergeordneten Funktion). Das sind die Kuchen.

Sie können die verzögerte Ausführung von Funktionen deklarieren. Dies geschieht über das Schlüsselwort defer . Das heißt, Zurückgestellte Funktionen werden am Ende der Funktion ausgeführt, in der sie deklariert sind, in umgekehrter Reihenfolge wie die Deklaration dieser zurückgestellten Funktionen. Hier. Darüber hinaus erfolgt die Initialisierung der Argumente verzögerter Funktionen, wenn der Verzögerungsblock deklariert wird . Dies ist wichtig, insbesondere wenn eine andere Funktion als Argument verwendet wird - sie wird viel früher ausgeführt als erwartet!

Panik! Go hat eine solche Funktion - Panik () . Es ist wie einwerfen , aber schlimmer. Diese Funktion stoppt die Programmausführung. Dieselbe Panik kann jedoch durch Aufschieben verarbeitet werden, da sie am Ende der Funktion auch nach einer Panik auf irgendeine Weise ausgeführt wird. Und doch - Panik, das ist kein Versuch. Das ist schlimmer!

Strukturen (nahe Objekte)


Go soll keine OOP-Paradigmensprache sein. Aber es gibt Dinge wie Strukturen . Wenn Sie aus der Welt des Subnetzes stammen, wissen Sie, dass wir auch Strukturen haben - dies sind Zuordnungen von Objekten, die auf dem Stapel gespeichert werden können (Werttypen). In Go ist Struktur die einzige Möglichkeit, so etwas wie ein Objekt zu tun. Ich kann noch nicht über die Werttypen sprechen, aber die Art der Interaktion mit Strukturen ist der detaillierten sehr ähnlich.

Jede Struktur ist im Wesentlichen ein separater Typ und kann eine Reihe von Eigenschaften bestimmter Typen aufweisen (einschließlich des Typs einer anderen Struktur oder des Funktionstyps):



Außerdem ist hier ein bestimmter Mechanismus der direkten Objektvererbung vom Subnetz zu sehen. Im obigen Bild ist die Kontostruktur mit der Personenstruktur verschachtelt. Dies bedeutet, dass alle Felder von Person in einer Variablen vom Typ Konto verfügbar sind. Wenn die Eigenschaftsnamen übereinstimmen (im obigen Beispiel ID, Name ), liegt kein Konflikt vor, aber der Wert eines höheren Felds wird verwendet:



Methoden


Ja, es gibt nicht nur die Ähnlichkeit von Objekten, sondern auch Methoden. Fast OOP :) Eine Methode ist eine Funktion , die an einen bestimmten Typ gebunden ist (z. B. an eine Struktur). Eine Methode unterscheidet sich von einer Funktion in einer Deklaration: Nach dem Schlüsselwort func müssen Sie in Klammern den Typ angeben, zu dem diese Methode gehört, und die Rolle der Übergabe einer Variablen dieses Typs. Was ist die Rolle? Wenn Sie ein Sternchen vor den Typ setzen, wird die Variable als Referenz übergeben (denken Sie an die Zeiger, sie war auch dort), und als Ergebnis arbeiten wir innerhalb der Methode mit einem bestimmten Typ, nicht mit seiner Kopie.



Aus dem obigen Bild können wir schließen, dass UpdateName keinen Sinn ergibt, da es die Kopie der Struktur und nicht das Original ändert. Und diese Kopie wird nicht zurückgegeben. Während SetName die ursprüngliche Struktur ändert (dank des Sternchens und der Übergabe als Referenz).

Methoden in Strukturen werden vererbt (und gemäß den Regeln der Eigenschaftsvererbung), d.h. Die übergeordnete Struktur hat Zugriff auf alle in ihren Strukturen verschachtelten Methoden.

Andere Typen können Methoden haben, nicht nur Strukturen. Dies mag wie Erweiterungsmethoden aus dem Subnetz erscheinen, aber nein. Methoden in Go können nur für lokale Typen erstellt werden, d. H. Typen, die in diesem Paket deklariert sind (über Pakete etwas weiter).

Pakete, Umfang, Namespaces


Gerade wurde mir klar, dass Go keinen NEIMSPACE hat! Generell! Ich erkannte dies, als nach der Kompilierung ein Fehler auftrat, der besagt, dass ich in einem Ordner zwei Dateien mit der Hauptmethode habe. Es stellt sich heraus, dass bei der Zusammenstellung alles von Papa auf einer Leinwand zusammengeklebt zu sein scheint! Ein Ordner ist in der Tat ein Namespace. Das ist so magisch, Kameraden :)

Hier steht übrigens das Dock:



Hinweis für sich selbst: Rauchen Sie ein Dock
Und hier ist ein weiterer kleiner Artikel zum Thema: https://www.callicoder.com/golang-packages/#the-main-package-and-main-function

Die grundlegende Ordnerstruktur sieht also so aus:


bin - kompilierte Binärdateien
pkg - temporäre Objektdateien
src - Quellen
Pakete beziehen sich auf den Namen des Ordners, in dem sich die Paketdateien befinden. Ja, ein Paket kann viele Dateien enthalten, die ineinander importiert werden.

Eine weitere interessante Regel: Funktionen, Eigenschaften, Variablen und Konstanten, die mit einem Großbuchstaben beginnen, können außerhalb des Pakets verwendet werden , d. H. importiert werden . Alles mit einem kleinen Buchstaben wird nur innerhalb der Verpackung verwendet. Ein Analogon von Zugriffsmodifikatoren aus dem Subnetz.

Das Schlüsselwort import funktioniert in einer Datei, nicht in einem Paket.

Schnittstellen


Die Schnittstellen hier sind interessant. Wir müssen keine Schnittstellen von unseren Strukturen erben. Es reicht aus, dass die Struktur, die als Eingabeparameter für eine bestimmte Methode mit einem Schnittstellentyp dient, die Methoden dieser Schnittstelle implementiert. Und es gibt keine Probleme. Das heißt, Schnittstellenmethoden müssen nicht unbedingt von der Struktur implementiert werden. Die Struktur sollte jedoch die Implementierung aller Methoden enthalten, die für eine bestimmte Funktion erforderlich sind, wobei der Schnittstellentyp als Eingabeparameter verwendet wird.

Es stellt sich heraus, dass die Schnittstelle im Gegensatz zu Go ein Merkmal der Funktion ist, in der sie verwendet wird, und kein Merkmal einer bestimmten Struktur (Objekt).

Wenn es noch einfacher ist, lautet die Logik: Sie müssen nicht quaken können, um eine Ente zu sein. Wenn Sie wissen, wie man quakt, dann sind Sie höchstwahrscheinlich eine Ente :)
Übrigens, wenn Ihre Struktur alle Methoden einer Schnittstelle implementiert, kann diese Struktur als Werte der Variablen der implementierten Schnittstelle zugewiesen werden . Irgendwie so :)

Schaltergehäuse eingeben


Go verfügt über ein spezielles Switch-Case, das je nach Eingangstyp funktionieren kann. Coole Sache:



Auf diese Weise können Sie übrigens einen Typ in einen anderen konvertieren (z. B. einen Schnittstellentyp in einen Strukturtyp, um Strukturfelder abzurufen, auf die über die Schnittstelle nicht zugegriffen werden kann):



ok - Boolesches Zeichen dafür, dass die Konvertierung erfolgreich war. Wir sind bereits auf die Zeichen gestoßen :)

Die leere Schnittstelle ist das Biest in der Welt von Go. Es kann jeden Typ annehmen. So etwas wie Dynamik oder Objekt in. Beispielsweise wird es in fmt.Println () und ähnlichen Funktionen verwendet, die in der Implementierung eine leere Schnittstelle verwenden.

Schnittstellen können ineinander eingebaut werden und somit die Zusammensetzung von Schnittstellen festlegen (was in Buchstabe I (Schnittstellentrennung) der SOLID- Prinzipien gesagt wird).

Tests


In Go beginnen alle Testfunktionen mit dem Wort Test , und die Eingabe des Testmodultests wird akzeptiert. Dateien werden mit dem Namen der Testdatei + Wort _test ( main_test.go - Tests für die Datei main.go ) benannt. Darüber hinaus sind sowohl Tests als auch Testdateien in einem Paket enthalten!



Nachwort


Das ist alles! Vielen Dank für Ihre Aufmerksamkeit, und ich bin bereit, Probleme in den Kommentaren zu diskutieren. Ich werde dich in den folgenden Notizen aus meinen Klassen treffen!

PS Sie können sich alle meine Code-Wanderungen in dieser Woche des Trainings auf Github ansehen

Source: https://habr.com/ru/post/de455830/


All Articles