Warum brauchen Variablen einen Typ?


10

Also schreiben wir:

Customer c = new Customer();

Warum ist das Design nicht so, dass wir schreiben:

c = new Customer();
c.CreditLimit = 1000;

Der Compiler kann c Punkte für einen Kunden berechnen und es ermöglichen, dass Mitglieder des Kunden auf c?

Ich weiß, wir möchten vielleicht schreiben:

IPerson c = new Customer();
IPerson e = new Employee();

um schreiben zu können:

public string GetName(IPerson object)
{
    return object.Name
}

string name = GetName(c); // or GetName(e);

Aber wenn wir geschrieben haben:

c = new Customer();
e = new Employee();

wir konnten noch schreiben:

public string GetName(object)
{
    return object.Name
}    

string name = GetName(c); // or GetName(e);

Der Compiler könnte sich über den Code direkt darüber beschweren, wenn der Typ der Objekt-c-Referenzen keine Name-Eigenschaft unterstützt (da er überprüfen kann, welche Mitglieder für das Argument / den Parameter innerhalb der Methode verwendet werden), oder die Laufzeit könnte sich beschweren.

Selbst mit dem dynamischen Schlüsselwort von C # verwenden wir immer noch eine Variable 'type' (zur Laufzeit festgelegt). Aber warum braucht eine Variable überhaupt einen Typ? Ich bin mir sicher, dass es einen guten Grund geben muss, aber ich kann mir das nicht vorstellen!


12
Aus diesem Grund gibt es dynamische Sprachen wie Python und Ruby (und andere). Auf diese Frage gibt es keine Antwort . Es ist eine Tatsache, dass einige Sprachen Typdeklarationen verwenden und einige Sprachen nicht.
S.Lott

2
"Variablen in diesen Sprachen haben überhaupt keinen Typ?" Richtig. Variablen haben in Python keinen Typ. Objekte haben einen Typ und Variablen sind einfach Verweise auf Objekte. Ihre Frage ist wirklich nur eine Beobachtung. Insbesondere "erfordern nicht alle Sprachen Variablendeklarationen". Es gibt keine Antwort. Es ist also wahrscheinlich, dass es als nicht konstruktiv geschlossen wird.
S.Lott

1
Vielleicht möchten Sie einen Blick auf Boo werfen , mit dem Sie Variablen ohne Typdeklarationen deklarieren können, aber Typinferenz verwenden , um Typen herauszufinden, damit Sie nicht auf die Korrektheits- und Leistungsvorteile einer starken Typisierung verzichten.
Mason Wheeler

2
var x = 1; - Meinten Sie 32-Bit-, 64-Bit-, 16-Bit-Nummer? Byte? schweben? doppelt? Dezimal? Hier geht es nur um Primitive.
Job

4
Sie können varin C # verwenden, und es ist immer gut, genau zu sagen, wo Sie eine Variable deklarieren möchten.
Daniel Little

Antworten:


22

Aber warum braucht eine Variable überhaupt einen Typ?

  1. Dies kann Fehler auffangen, bei denen einer Variablen ein ungültiger, falsch eingegebener Ausdruck zugewiesen wird. Einige Sprachen haben eine dynamische Typisierung , die die Korrektheitsgarantien eines Typs pro Variable für die Art von Flexibilität opfert, die Sie zu wünschen scheinen.
  2. Durch Typen kann der Compiler möglicherweise effizienteren Code generieren. Dynamische Typisierung bedeutet, dass zur Laufzeit Typprüfungen durchgeführt werden müssen.

1
Bitte erläutern Sie die Ablehnung.
Fred Foo

4
Dynamische Typisierung ist außerdem mit Leistungskosten verbunden, da die Typinformationen zur Laufzeit überprüft werden müssen.
Charles Salvia

@ CharlesSalvia: Guter Punkt, fügte das der Antwort hinzu.
Fred Foo

2
@sturdytree: "Wenn es keinen Typ gibt, kann es nicht falsch eingegeben werden" - was die Sprachregeln betrifft, stimmt das. Aus semantischer Sicht kann der Variablen jedoch immer noch der falsche Typ zugewiesen werden , z. B. wenn Sie einen Konstruktornamen falsch eingegeben haben und das Programm weiterhin ausgeführt wird, aber nicht das tut, was Sie möchten.
Fred Foo

5
@sturdytree Während es wahr ist, dass es keine Sprachen gibt, die wirklich typenlose Variablen mit Typprüfung haben, gibt es Sprachen, die Typinferenz haben . Das heißt, die Sprache betrachtet Ihre Verwendung der Variablen und leitet den Typ aus Ihrer Verwendung ab. Wenn ein Konflikt vorliegt (z. B. wenn Sie a = new Bar()eine Methode aus der Klasse ausführen und später aufrufen Baz), gibt der Compiler einen Fehler aus. Sprachen wie Haskell und OCaml waren Pioniere der Typinferenz, aber sie sind in C # mit dem varSchlüsselwort vorhanden.
Quantikel

9

Sie haben einen vollkommen gültigen Punkt, es gibt Sprachen, die den Typ einer Variablen nicht verfolgen, und sie werden als "dynamisch typisiert" bezeichnet. Die Kategorie umfasst Sprachen wie JavaScript, Perl, Lisp und Python.

Der Vorteil einer statisch typisierten Sprache ist eine zusätzliche Fehlerprüfung zur Kompilierungszeit.

Angenommen, Sie haben die folgende Methode:

public addCustomerContact(Customer client, Employee contact) {
   ...
} 

Wenn Sie einen Kunden bobund einen Mitarbeiter jamesin Ihrem Code haben, können Sie versehentlich anrufen addCustomerContact(james, bob), was ungültig ist. Wenn der Compiler die Variablentypen nicht kennt, kann er Sie nicht warnen, dass Sie einen ungültigen Aufruf getätigt haben. Stattdessen tritt zur Laufzeit ein Fehler auf ... und da dynamisch typisierte Sprachen dies nicht überprüfen Typ von Parametern, die an Methoden übergeben werden. Dieses Problem tritt immer dann auf, wenn Ihr Code versucht, Nur-Kunden-Eigenschaften des jamesObjekts oder Nur-Mitarbeiter-Eigenschaften des bobObjekts zu verwenden. Das kann lange dauern, nachdem das Paar (James, Bob) zur Liste der Kundenkontakte hinzugefügt wurde.

Nun fragen Sie sich vielleicht, warum der Compiler den Typ von jamesund nicht immer noch ableiten bobund uns trotzdem warnen kann. Das mag manchmal möglich sein, aber wenn Variablen wirklich keinen Typ haben, könnten wir Folgendes tun:

var james;
var bob;
if (getRandomNumber() > 0.5) {
   james = new Customer();
   bob = new Employee();
} else {
   james = new Employee();
   bob = new Customer();
}

Es ist völlig legal, einer Variablen einen Wert zuzuweisen, da wir sagten, dass Variablen keinen Typ haben. Das bedeutet auch, dass wir den Typ einer Variablen nicht immer kennen können, da es sich um unterschiedliche Typen handeln kann, die auf unterschiedlichen Ausführungspfaden basieren.

Im Allgemeinen werden dynamisch typisierte Sprachen für Skriptsprachen verwendet, in denen kein Kompilierungsschritt vorhanden ist und daher keine Kompilierungsfehler vorhanden sind. Dies bedeutet, dass die zusätzlichen Tastenanschläge, die zum Geben des Variablentyps erforderlich sind, nicht sehr nützlich sind.

Dynamisch typisierte Sprachen bieten auch einige deutliche Vorteile, vor allem, weil weniger Code für die Implementierung desselben Designs benötigt wird: Schnittstellen müssen nicht geschrieben werden, da alles "ententypisiert" ist (es ist uns nur wichtig, welche Methoden / Eigenschaften ein Objekt hat (nicht zu welcher Klasse das Objekt gehört), Variablen müssen keinen expliziten Typ erhalten ... mit dem Kompromiss, dass wir etwas weniger Fehler herausfinden, bevor wir unseren Code ausführen.


Danke Theodore. "Wenn der Compiler die Typen der Variablen nicht kennt, kann er Sie nicht warnen, dass Sie einen ungültigen Aufruf getätigt haben." Wie Sie bereits erwähnt haben, kann der Compiler die Typen der Objekte kennen, auf die die Variablen verweisen.
Sturdytree

Theodore: In Ihrem Beispiel james / bob sollten wir als Programmierer wissen, wie wir unsere Variablen verwendet haben (und eine gute Benennung hilft), und deshalb sehe ich kein Problem damit. Wenn Sie sagen "Wir können den Typ einer Variablen nicht immer kennen", können Sie vermutlich nicht immer den Typ des Objekts kennen, auf das die Variable zeigt, aber der Compiler kann dies herausfinden und so vor falschen Mitgliedern warnen angewendet (dh wir können statische Überprüfung haben).
Sturdytree

2
Ich habe oben ein Beispiel gegeben, in dem Sie die Typen der Objekte nicht statisch kennen können. Abhängig vom Ausführungspfad kann der in einer Variablen ohne Typinformationen gespeicherte Objekttyp unterschiedlich sein. Sie können eine Sprache wie C # verwenden, in der die Typinformationen zur Kompilierungszeit abgeleitet werden können. Soweit ich weiß, gibt es jedoch keine Sprache, die sowohl statische Typprüfungen als auch wirklich typenlose Variablen enthält. Die rechnerische Komplexität der statischen Analyse ist wahrscheinlich auch großartig.
Theodore Murdock

Theodore, danke, Sie haben richtig verstanden, dass es sich um eine Sprache mit statischer Typprüfung (basierend auf dem Objekttyp) und typenlosen Variablen handelt. Traurig zu hören, dass es keine gibt - mir wurde gesagt, dass Python typenlose Variablen hat, aber es klingt so, als hätte es keine statische Typprüfung.
Sturdytree

1
-1; Starke / schwache Typisierung ist orthogonal zur statischen / dynamischen Typisierung. C ist statisch schwach typisiert; Lisp und Python sind beide dynamisch stark typisiert.
Fred Foo

5

Professionelle Programmierer müssen also nicht herausfinden, ob

10 + "10"

is "1010" or 20....

Was es ein Fehler ist, zur Kompilierungszeit mit einer statisch typisierten Sprache oder zur Laufzeit mit einer dynamisch typisierten Sprache. Na ja, vernünftige sowieso.


2
Dh Perl ist keine vernünftige Sprache? :)
Fred Foo

5
@larsmans: in der Tat, nein, ist es nicht. aber das ist reine meinung.
NotMe

2
@ChrisLively: Ich bin froh, dass es jetzt eine Tatsache ist. Bitte kommen Sie jederzeit an meinem Arbeitsplatz vorbei, um meine Perl-liebenden Kollegen zu überzeugen;)
Fred Foo

2
10 + "10" verwendet einen '+' - Operator für ein Objekt vom Typ Integer und ein Objekt vom Typ String. Der Compiler würde einen Fehler ausgeben. Meine Frage betrifft den Variablentyp, nicht das Objekt.
Sturdytree

2
Es ist gültig C: Es ist Zeigerarithmetik.
Dan04

4

Angenommen, Sie hatten eine Variable one(auf 1 gesetzt) ​​und haben versucht, sie auszuwerten one + one. Wenn Sie keine Ahnung vom Typ hatten, ist 1 + 1 nicht eindeutig. Sie können argumentieren, dass 2 oder 11 richtige Antworten sein könnten. Es wird mehrdeutig, wenn der Kontext nicht angegeben wird.

Ich habe gesehen, dass dies in SQLite passiert ist, wo die Datenbanktypen versehentlich auf gesetzt wurden, VARCHARanstatt INTund wenn Operationen ausgeführt wurden, erhielten die Leute unerwartete Ergebnisse.

Wenn der Kontext in c # auf einen Typ folgt, können Sie das varSchlüsselwort verwenden.

var c = new Customer();
var e = new Employer();

Kompiliert c und e mit den abgeleiteten Typen zur Kompilierungszeit.


1
Hat in Python 1 + 1immer den Typ int, aber das muss nicht deklariert werden. Die Frage ist, warum Variablen einen Typ haben, keine Werte .
Fred Foo

Entschuldigung, Sie sollten variablesnicht sehen, valueswann ich 1 + 1in meinem Beispiel verwendet habe. Ich denke es war nicht klar.
Stephen Quan

Dennoch können dynamisch typisierte Sprachen damit umgehen. In Python, one=1; print(one+one)druckt 2. one="1"; print(one+one)druckt 11. Das SQLite-Beispiel ist überzeugender, aber das Problem besteht in einer schwachen Typisierung, sodass es für C # nicht wirklich relevant ist.
Fred Foo

In meinem vorgeschlagenen Schema bedeutet eins = 1 eine Variable, die auf einen ganzzahligen Typ zeigt (ich schlage überhaupt keine Typen vor - Objekte hätten einen Typ). eins + eins wäre dann nicht mehrdeutig (wir fügen zwei ganzzahlige Objekte / Werte hinzu)
sturdytree

1
Hi @ dan04, ich habe es mit ORDER BYversehentlich auf einem VARCHARFeld gemacht gesehen. Siehe stackoverflow.com/questions/9103313/… .
Stephen Quan

4

Einer Variablen muss kein Typ zugeordnet sein. Zu den Sprachen, in denen dies zutrifft, gehören Lisp, Scheme, Erlang, Prolog, Smalltalk, Perl, Python, Ruby und andere.

Es ist auch möglich , eine Variable hat einen Typen, aber Sie könnten nicht haben zu schreiben , die Art im Programm. Dies wird normalerweise als Typinferenz bezeichnet. ML, Haskell und ihre Nachkommen haben eine starke Typinferenz; Einige andere Sprachen haben es in kleineren Formen, wie zum Beispiel die autoDeklarationen von C ++ .

Das Hauptargument gegen Typinferenz ist, dass es die Lesbarkeit beeinträchtigt. Es ist normalerweise einfacher, Code zu verstehen, wenn die Typen aufgeschrieben werden.


1

Wenn Sie den Typ identifizieren, den Ihre Variable darstellt, geben Sie eine Erklärung zu einigen Dingen ab. Sie identifizieren die Speicherzuordnungsanforderungen für Ihre Variable und definieren Kompatibilitäts- und Bereichsregeln für Ihre Variable. Dies bietet eine Möglichkeit, Verwirrung über Ihre Absichten für die von Ihnen gespeicherten Daten zu vermeiden und Ihnen ein relativ billiges Mittel zur Verfügung zu stellen, um potenzielle Probleme in Ihrem Code beim Kompilieren zu identifizieren.

Wenn Sie folgende Variablen deklarieren:

myVar      = 5;
myOtherVar = "C";

Was können Sie über diese Variablen schließen? Ist myVarsigniert oder nicht signiert? Ist es 8-Bit, 64-Bit oder etwas dazwischen? Ist myOtherVarein String (effektiv ein Array) oder ein Char? Ist es ANSI oder Unicode?

Durch die Angabe bestimmter Datentypen geben Sie dem Compiler Hinweise, wie er die Speicheranforderungen für Ihre Anwendung optimieren kann. Einige Sprachen stören sich nicht allzu sehr an solchen Dingen, sodass diese Angelegenheiten zur Laufzeit behandelt werden können, während andere Sprachen eine gewisse dynamische Typisierung zulassen, da durch die Analyse des Codes auf die Datentypen geschlossen werden kann.

Ein weiterer Punkt bei stark typisierten Sprachen ist, dass Sie dem Compiler nicht jedes Mal Anweisungen geben müssen, wenn Sie eine Variable verwenden. Können Sie sich vorstellen, wie schrecklich und unlesbar Ihr Code werden würde, wenn Sie jedes Mal, wenn Sie auf eine Variable zugreifen, gezwungen wären, sie effektiv umzuwandeln, um dem Compiler mitzuteilen, um welche Art von Wert es sich handelt? !!


Guter Punkt, obwohl der Compiler nur den effizientesten Typ (z. B. small int) basierend auf dem Wert verwenden könnte, kann ich sehen, dass "C" eher ein Zeichenfolgenobjekt als ein Zeichen sein soll, um bestimmte Operationen ausführen zu können . In solchen Fällen könnten wir jedoch einfach ein = (Zeichenfolge) "C" angeben. Dadurch wird ein Zeichenfolgenobjekt erstellt und 'a' (nicht typisierte Variable) zeigt nur darauf. Ich sehe das nicht als schrecklicher an als String a = "C";
Sturdytree

0

Ein Computerprogramm ist ein Diagramm von Prozessknoten, das beschreibt, was eine "Maschine", dargestellt durch die Sprachlaufzeit (in den meisten Fällen um Toolkits erweitert), in welcher Reihenfolge oder unter welchen Bedingungen tun sollte. Dieses Diagramm wird durch eine Textdatei (oder eine Reihe von Textdateien) dargestellt, die in einer bestimmten Sprache geschrieben und (teilweise oder vollständig) erstellt wird, wenn der Compiler / Interpreter diese Datei liest (deserialisiert). Es gibt auch einige Umgebungen (UML- oder grafische Programmgenerierungswerkzeuge), in denen Sie dieses Diagramm tatsächlich erstellen und den Quellcode in einer Zielsprache generieren können.

Warum sage ich das? Weil dies zur Antwort auf Ihre Frage führt.

Ihr Programmtext enthält Anweisungen, wie der Computer die eigentliche Aufgabe lösen soll. Er enthält sowohl die Prozessschritte (Bedingungen, Aktionen) als auch die Struktur (welche Komponenten verwenden Sie in der Lösung). Letzteres bedeutet, dass Sie einige Instanzen anderer Komponenten abrufen oder erstellen, sie in benannte Felder (Variablen) einfügen und sie verwenden: Zugriff auf ihre Daten und Dienste.

In einigen Sprachen erhalten Sie einheitliche Kästchen, in denen nur die Beschriftung wichtig ist. Sie können jedoch alles in sie einfügen. Sie können sogar eine Variable mit dem Namen "Ziel" verwenden, um eine "Person" am Anfang und "Auto" am Ende von zu speichern der gleiche Algorithmus. Bei anderen müssen Sie "geformte" Boxen erstellen, daher unterschiedliche für eine Person oder ein Auto - obwohl Sie damit immer noch eine "generische Box" erstellen können (Java Object, C / C ++ void *, Objective C "id" ...) und wirf es wie du willst. Mit typisierten Sprachen können Sie Ihre Struktur in feineren Details ausdrücken und "Typverträge" für Ihre Variablen erstellen (obwohl Sie diese Einschränkung umgehen können), während untypisierte Sprachen diesen Ansatz "Ich werde sicher wissen, was ich dieses Mal in diese Box gesteckt habe" unterstützen als Standard und einziges Verhalten.

Beide Ansätze sind praktikabel, haben ihre Compiler-Intelligenz, viele Programmierbücher, Praktiken und Frameworks, die mit ihnen (und anderen Tonnen von Büchern über diese Frameworks) auf verschiedenen Ebenen geschrieben wurden. Daher scheint die Antwort heute eher eine Frage des Geschmacks und des Wissens des tatsächlichen Programmiererteams zu sein als eine richtig begründete, gemessene und überprüfte Aussage darüber, ob Typen verwendet werden sollen oder nicht.

Ich denke, es ist unnötig zu erwähnen, dass ich Regeln Tricks vorziehe, insbesondere bei langfristigen Projekten mit großen Teams (auch bekannt als "ernst"). Grund: Soweit ich weiß, sind die wahrscheinlichsten Ursachen für ein Versagen / Ausrutschen eines SW-Projekts: unklare Anforderungen und schlechtes Design (80%! Nach einer mir bekannten Studie), und nur wenige Prozent bleiben für die tatsächliche Codierung übrig. Alle Regeln und Verträge erzwingen ein saubereres Design, denken voraus und erfordern, dass die Entscheidungen früher und von den richtigen Personen getroffen werden. Typen bedeuten Regeln: weniger "Freiheit" und "Coolness" - mehr Vorbereitung, Denken, Kodierungsstandards, kontrollierte Teamarbeit. Für mich klingt dies nach einem erforderlichen Erfolgsfaktor und auch nach "Zuhause, süßes Zuhause".

Meine 2 Cent.


-3

AFIAK Alle Sprachen mit dynamischer Typisierung sind interpretierte Sprachen. Das ist bereits sehr ineffizient. Das Hinzufügen der Ineffizienz des dynamischen Tippens wird keinen großen Zeitverlust bedeuten. Eine kompilierte Sprache bezieht sich jedoch nicht auf Dinge mit Namen, wenn sie ausgeführt wird. (Abgesehen von der gelegentlichen Verwendung von .net-Reflektion oder Ähnlichem - Funktionen, die im Vergleich zur zugrunde liegenden Sprache sehr langsam sind.) Die Suche nach all diesen Namen wird langsam, langsam, langsam sein.


3
Du weißt falsch. Es gibt viele Compiler für dynamisch typisierte Sprachen, von denen einige recht schnell sind. Beispiele sind Common Lisp, Scheme und Erlang.
Ryan Culpepper

3
Es gibt keine "interpretierte Sprache". Eine Sprache ist ein abstrakter Satz mathematischer Regeln. Eine Sprache wird weder kompiliert noch interpretiert. Eine Sprache ist einfach. Zusammenstellung und Interpretation sind Merkmale der Implementierung, nicht der Sprache. Jede Sprache kann mit einem Interpreter implementiert werden, und jede Sprache kann mit einem Compiler implementiert werden. Und so ziemlich jede Sprache hat sowohl interpretierte als auch kompilierte Implementierungen, z. B. gibt es Interpreter für C und Compiler für ECMAScript, Ruby und Python.
Jörg W Mittag

-4

Dynamisch typisierte Sprachen werden oft als "objektorientiert" angepriesen. Sie sind nicht. Sie können einkapselungsorientiert sein, aber niemals objektorientiert. Bei der Objektorientierung dreht sich alles um Typen.

"Der Junge fährt mit dem Fahrrad seines Bruders zum Lebensmittelgeschäft und kauft dem Lebensmittelhändler einen Laib Brot." Mithilfe der Objektorientierung kann sofort eine Reihe von Klassen (Typen) geschrieben werden, um dieses reale Szenario zu beschreiben.

In einer dynamisch typisierten Sprache konnte das Szenario nur so dargestellt werden:

"Das Objekt reitet das Objekt seines Objekts zum Objekt und kauft ein Objekt vom Objekt".

Die Kraft der Objektorientierung liegt in ihrer Fähigkeit, die Welt auf natürliche Weise zu modellieren, so dass der Softwareentwickler beide Seiten des Gehirns verwenden kann, um Software zu schreiben und Probleme mehr als Mensch, weniger als Computerprogrammierer zu lösen. Diese Kraft fehlt in dynamisch typisierten Sprachen.

Die statische Typisierung ermöglicht eine bessere Codierungseffizienz, Wiederverwendbarkeit und Wartbarkeit, da integrierte Entwicklungsumgebungen die Variablentypen kennen. Wenn eine IDE die Variablentypen kennt, kann sie eine automatische Vervollständigung bereitstellen, sodass der Programmierer nicht auf die Klassendefinition zurückgreifen muss, um sich zu merken, ob die Member-Eigenschaft "backlightControl" oder "backLightControl" oder "bkLightCtrl" geschrieben wurde.

Die statische Typisierung ermöglicht ein automatisiertes Refactoring, da die IDE jeden Ort kennt, an dem eine Variable eine Instanz des zu refactoringenden Objekts enthält.

Die statische Typisierung ermöglicht eine bessere Wiederverwendbarkeit und Wartbarkeit. Dynamische Eingabe ist besser für Einwegcode. Angenommen, ein neuer Entwickler kommt von der Straße und sieht sich einen vorhandenen Code an. Wenn der Code statisch typisiert ist, kann der Entwickler mit zwei Mausklicks die Klassendefinition jeder der beteiligten Variablen überprüfen, weiß, wofür die Klasse ist, weiß, welche anderen Methoden und Eigenschaften verfügbar sind. Wenn der Code dynamisch eingegeben wird, muss der Entwickler die globale Suche verwenden, um herauszufinden, was los ist.


2
Ich fürchte, ich muss Ihnen im ersten Teil Ihrer Argumentation nicht zustimmen. Dynamisch typisierte Sprachen sind normalerweise "objektorientiert", aber nicht "klassenorientiert". Dies ist eine wichtige Unterscheidung. In Ihrem Beispiel leben Sie tatsächlich in einer Illusion. Sie haben vielleicht eine BoyKlasse, aber ich bezweifle, dass sie alles kann, was ein "Junge" aus der realen Welt tut. In Ihrem dynamischen Beispiel (Das Objekt fährt ...) wissen wir jedoch das einzig Wichtige an diesem "Jungen" -Objekt - es kann fahren . Das ist die Grundphilosophie dynamischer Sprachen. Es hat + ve s und -ve s. Welches du magst, ist deine Meinung.
Chip
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.