Die Klasse List in Python programmieren

Du hast eine Idee für ein Projekt?
wiexd
User
Beiträge: 2
Registriert: Montag 15. Februar 2021, 21:44

Guten Abend erstmal! :)
Ich bin Lila, 15 Jahre alt und habe das Fach Informatik in der Schule. Ich interessiere mich total für Programmiersprachen und habe jetzt auch schon Java in der Schule gelernt. Ich bin neu und hoffe, dass ihr wir alle gut auskommen werden und ihr mich in eure Gemeinschaft aufnehmt.
Jetzt habe ich beschlossen eine neue Sprache zu lernen und widme mich deswegen nun Python! Ich habe mir als kleines Projekt vorgenommen die Klasse List in Python zu programmieren. Damit habe ich nun angefangen und bin auf einige Probleme gestoßen. So sieht meinProgramm momentan aus:

Code: Alles auswählen

class List:
    
    
    class List_node:
        def __init__(self,content_object):
            self.content_object = content_object
            self.next = None
            
        def get_content_object(self):
            return self.content_object
        
        def set_content_object(self,content_object):
            self.content_object = content_object
            
        def get_next_node(self):
            return self.next
        
        def set_next_node(self, list_node):
            self.next = list_node
            
    
    
    def __init__(self):
        first = None
        last = None
        current = None
    
    def is_empty(self):
        return self.first is None
        
    def has_access(self):
        return self.current is not None
    
    def next(self):
        if self.has_access():
            self.current = self.current.get_next_node()
    
    def to_first(self):
        if self.is_empty() == False:
            self.current = self.first
            
    def to_last(self):
        if self.is_empty() == False:
            self.current = self.last
            
    def get_content(self):
        if self.has_access():
            return self.current.get_content_object()
        else:
            return None
    
    def set_content(self,content_object):
        if content_object is not None and self.has_access():
            self.current.set_content_object(content_object)
    
    def insert(self, content_object):
        if content_object is not None:
            if self.has_access():
                new_node = List_node(content_object)
                if self.current != self.first:
                    previous = self.get_previous(self.current)
                    new_node.set_next_node(previous.get_next_node())
                else:
                    previous.set_next_node(self.first)
                    self.first = new_node
            else:
                if self.is_empty():
                    new_node = List_node(content_object)
                    self.first = new_node
                    self.last = new_node
    
    def append(self, content_object):
        if content_object is not None:
            if self.is_empty():
                self.insert(content_object)
            else:
                new_node= List_node(content_object)
                self.last.set_next_node(new_node)
                self.last = new_node
    
    def concat(self, list):
        if list != self and list is not None and not list.is_empty():
            if self.is_empty():
                self.first = list.first
                self.last = list.last
            else:
                self.last.set_next_node(list.first)
                self.last = list.last
                
            list.first = None
            list.last = None
            list.current = None
    
    def remove(self):
        if self.has_access() and not self.is_empty():
            if self.current == self.first:
                self.first == self.first.get_next_node()
            else:
                previous = self.get_previous(self.current)
                if self.current == self.last:
                    self.last == previous
                previous.set_next_node(self.current.get_next_node)
            temp = self.current.get_next_node
            self.current.set_content_object(None)
            self.cuurent.set_next_node(None)
            self.current = temp
            if self.is_empty():
                self.last = None
    
    def get_previous(self, list_node):
        if list_node is not None and list_node != self.first and not self.is_empty():
            temp = self.first
            while temp is not None and temp.get_next_node() != list_node:
                temp = temp.get_next_node
            return temp
        else:
            return None

Mich verwirrt, da ich nur Java kenne, erstmal natürlich das self. Jedoch habe ich nun das Gefühl das wenigstens einigermaßen verstanden zu haben. Fehlermeldungen habe ich immer dann, wenn ich bei insert() und append() ein Liste_node -Objekt erzeugen möchte, woran liegt das? Außerdem bin ich mir super unsicher, wie ich das mit dem <ContentType> machen muss, was es sonst in Java bei Listen gibt. Hier habe ich ja nie String, int, etc. angegeben. Auch weiß ich nicht, wie ich es hinbekomme den Konstruktor so zu haben, dass durch das typische [ ] eine Liste entsteht.
Könntet ihr mir vielleicht helfen und mal über das Programm gucken und mich berichten falls euch Fehler auffallen oder mir bei meinen Fragen ein bisschen weiterhelfen? Vielleicht hat ja sogar einer von euch Lust mitzumachen und mich dabei zu unterstützen! Ich bin für jede Hilfe extrem dankbar, da ich ja noch nicht so viel Erfahrung habe. :)

Ich freue mich auf eure Antworten und natürlich euch kennenzulernen! :lol:

Lila
Benutzeravatar
sparrow
User
Beiträge: 4144
Registriert: Freitag 17. April 2009, 10:28

Hallo und willkommen im Forum.
Grundsätzlich ist es gut, eine zweite Programmiersprache zu lernen. Im Moment, versuchst du Java in Python-Syntax zu schreiben. Zum reinen verstehen der Syntax sicher in Ordnung, aber du musst dich darauf einstellen, dass manche Dinge in Python anders gehandhabt werden.

Zuerst: Klassen gehören nicht verschachtelt. List_node (sollte eigentlich ListNode heißen) gehört auf die selbe Ebene wie List.

Dann beheben wir schnell deine Fehler:

self ist immer die aktuelle Instanz der Klasse. in der __init__ von List legst du 3 Variablen an: first, last und current. Und nach dem abarbeiten von __init__ sind diese verschwunden, denn sie sind nur im Namensraum der Funktion gültig. Du musst hier, wie du es auch in List_Node tust, self.variablenname in der __init__ verwenden.

Setter und Getter, die nichts weiter tun als den Zugriff auf eine Instanzvariable gestatten, sind in Python unüblich und nicht nötig.

Code: Alles auswählen

node = ListNode()

# warum?
something = note.get_content_object()
# wenn doch das auch funktioniert (umbenannt, weil alles ein object ist)
something = note.content

# oder
note.set_next_node(something)
# wenn das auch geht:
note.next = something
Du könntest also ListNode erst einmal eindampfen auf:

Code: Alles auswählen

class ListNode:
    def __init__(self, content):
        self.content = content
        self.next = None
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Python ist nicht Java. Man schachtelt keine Klassen in andere Klassen. Die ganzen set- und get-Methoden sind überflüssig, da man auch direkt auf die Attribute zugreifen kann. Bleibt also:

Code: Alles auswählen

class List_node:
    def __init__(self, content):
        self.content = content
        self.next = None
Das `self` ist das `this` in Java. In List.__init__ fehlen die self bei den Attributen. Satt auf == False prüft man mit not auf das Gegenteil.
Warum darf man bei set_content nicht auf None setzen?
In `remove` hast Du einen Schreibfehler: cuurent
In `remove` und `get_previous` solltest Du `get_next_node` auch aufrufen.

Code: Alles auswählen

class List:    
    def __init__(self):
        self.first = None
        self.last = None
        self.current = None
    
    def is_empty(self):
        return self.first is None
        
    def has_access(self):
        return self.current is not None
    
    def next(self):
        if self.has_access():
            self.current = self.current.next
    
    def to_first(self):
        if not self.is_empty():
            self.current = self.first
            
    def to_last(self):
        if not self.is_empty():
            self.current = self.last
            
    def get_content(self):
        if self.has_access():
            return self.current.content
        else:
            return None
    
    def set_content(self, content):
        if self.has_access():
            self.current.content = content_object
    
    def insert(self, content):
        new_node = List_node(content)
        if self.has_access():
            if self.current != self.first:
                previous = self.get_previous(self.current)
                new_node.next = previous.next
            else:
                previous.next = self.first
                self.first = new_node
        elif self.is_empty():
            self.first = new_node
            self.last = new_node
    
    def append(self, content):
        new_node= List_node(content)
        if self.is_empty():
            self.first = new_node
        else:
            self.last.next = new_node
        self.last = new_node
    
    def concat(self, list):
        if list != self and list is not None and not list.is_empty():
            if self.is_empty():
                self.first = list.first
            else:
                self.last.next = list.first
            self.last = list.last
    
    def remove(self):
        if self.has_access() and not self.is_empty():
            if self.current == self.first:
                self.first == self.first.next
            else:
                previous = self.get_previous(self.current)
                if self.current == self.last:
                    self.last == previous
                previous.next = self.current.next
            temp = self.current.next
            self.current = temp
            if self.is_empty():
                self.last = None
    
    def get_previous(self, list_node):
        if list_node is not None and list_node != self.first and not self.is_empty():
            temp = self.first
            while temp is not None and temp.next != list_node:
                temp = temp.next
            return temp
        else:
            return None
Die Eckigen Klammern sind für den internen list-Typ reserviert.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Zusätzliche Anmerkungen: Am Entwurf könnte man kritisieren, dass da so etwas wie ein Iterator in der `List`-Klasse eingebaut ist, der vielleicht besser ein eigener Datentyp sein sollte. Und der Iterator entspricht nicht dem Iterator-Konzept/”Iterable-Protokoll” von Python. Das sollte man noch implementieren und auch die anderen ”magischen” Methoden die man so bei einer Liste erwarten würde. So etwas wie `is_empty()` würde man dann eher weglassen, weil das schon durch einen Test auf ”Wahrheit” abgedeckt ist, wie das bei den anderen Containertypen in Python der Fall ist.

Neben den magischen Methoden würde ich auch die üblichen Methoden von Sequenzen zur Verfügung stellen. Oder zumindest nicht Methoden von Sequenzen die bei `List` plötzlich eine andere Signatur und/oder Semantik haben.

Durch die besondere Behandlung von `None` kann `None` nicht in der Liste als Wert gespeichert werden. Das ist überraschend und unschön.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
wiexd
User
Beiträge: 2
Registriert: Montag 15. Februar 2021, 21:44

Danke erstmal für die ganzen Antworten und, dass ich so herzlich aufgenommen worden bin.! :D
Ich habe jetzt erstmal ein paar Änderungen vorgenommen und hoffe, dass das Programm nun funktioniert. Ich habe jetzt die Getter und Setter erstmal behalten, da mir meine Lehrerin das so immer eifrig eingebläut hat xD. jetzt will ich eine Testklasse mit Hunden machen um das alles erstmal zu testen. Ich muss ja schließlich gucken, ob jede Methode so funktioniert, wie sie soll ;)
So sieht jetzt meine fertige Listenklasse aus:

class ListNode:
def __init__(self,content_object):
self.content_object = content_object
self.next = None

def get_content_object(self):
return self.content_object

def set_content_object(self,content_object):
self.content_object = content_object

def get_next_node(self):
return self.next

def set_next_node(self, list_node):
self.next = list_node



class ListPython:
def __init__(self):
self.first = None
self.last = None
self.current = None

def is_empty(self):
return self.first is None

def has_access(self):
return self.current is not None

def next(self):
if self.has_access():
self.current = self.current.get_next_node()

def to_first(self):
if not self.is_empty():
self.current = self.first

def to_last(self):
if not self.is_empty():
self.current = self.last

def get_content(self):
if self.has_access():
return self.current.get_content_object()
else:
return None

def set_content(self,content_object):
if content_object is not None and self.has_access():
self.current.set_content_object(content_object)

def insert(self, content_object):
if content_object is not None:
if self.has_access():
new_node = ListNode(content_object)
if self.current != self.first:
previous = self.get_previous(self.current)
new_node.set_next_node(previous.get_next_node())
else:
previous.set_next_node(self.first)
self.first = new_node
else:
if self.is_empty():
new_node = ListNode(content_object)
self.first = new_node
self.last = new_node

def append(self, content_object):
if content_object is not None:
if self.is_empty():
self.insert(content_object)
else:
new_node= ListNode(content_object)
self.last.set_next_node(new_node)
self.last = new_node

def concat(self, list_python):
if list != self and list_python is not None and not list_python.is_empty():
if self.is_empty():
self.first = list_python.first
self.last = list_python.last
else:
self.last.set_next_node(list_python.first)
self.last = list_python.last

list_python.first = None
list_python.last = None
list_python.current = None

def remove(self):
if self.has_access() and not self.is_empty():
if self.current == self.first:
self.first == self.first.get_next_node()
else:
previous = self.get_previous(self.current)
if self.current == self.last:
self.last == previous
previous.set_next_node(self.current.get_next_node)
temp = self.current.get_next_node
self.current.set_content_object(None)
self.cuurent.set_next_node(None)
self.current = temp
if self.is_empty():
self.last = None

def get_previous(self, list_node):
if list_node is not None and list_node != self.first and not self.is_empty():
temp = self.first
while temp is not None and temp.get_next_node() != list_node:
temp = temp.get_next_node
return temp
else:
return None








Und das hier ist meine Testklasse:

class Hund:
def __init__(self, farbe, alter, groeßer_als_1Meter):
self.farbe = farbe
self.alter = alter
self.groeßer_als_1Meter =groeßer_als_1Meter

def bellen(self):
if self.get_groeßer_als_1Meter() == True:
print("Wuff!")
if self.get_groeßer_als_1Meter()== False:
print("Miau.")
if self.get_groeßer_als_1Meter()is None:
print("Wie groß ist denn jetzt der Hund?!")

def werde_aelter(self):
if self.alter is not None:
self.alter += self.alter
else:
print("kein Alter angegeben")

def rate_farbe(self, farbe):
if self.get_farbe() == farbe:
print("Farbe richtig erraten! Die Farbe ist:", self.farbe)
else:
print("Farbe falsch geraten! Die richtige Farbe ist:", self.farbe)

def get_farbe(self):
return self.farbe

def set_farbe(self, farbe):
self.farbe = farbe

def get_alter(self):
return self.alter

def set_alter(self, alter):
self.alter = alter

def get_groeßer_als_1Meter(self):
return self.groeßer_als_1Meter

def set_groeßer_als_1Meter(self, groeßer_als_1Meter):
self.groeßer_als_1Meter = groeßer_als_1Meter



hunde_Warteschlange = ListPython()
tim = Hund("grau", 3, True)
bello = Hund("braun", 15, False)
bob = Hund("weiß", 2,False)
karl = Hund("gefleckt", 7, False)
hunde_Warteschlange.insert(bob)
hunde_Warteschlange.to_first()
print(hunde_Warteschlange.get_content())



Leider bin ich erneut auf Probleme gestoßen. Wenn ich die Methode bellen() benutze und in ein print() packe, wird immer noch mit der eigentlichen Aussage ein None ausgegeben. Woran liegt das? Mein anderes, wichtigeres, Problem ist, dass ich, wenn ich durch die letzte Operation, das get_content() das Element erhalten möchte, dass ich zuvor eingefügt habe, also den Hund bob, diese Ausgabe bekomme:

<__main__.Hund object at 0x10e6bb760>

Ich erhalte lediglich eine Referenz zu dem besagten Objekt bob. Wie bekomme ich es hin, dass direkt "bob" ausgegeben wird?
Eine weitere Frage ist, ob, wenn ich eine Liste in Python mit den Objekten ausgeben lassen möchte, wie in Java wieder einfach eine for-Schleife benutzen kann. Eigentlich müsste das ja funktionieren, sobald ich das Problem mit Bob behoben habe.

Danke erstmal für die ganze vorherige Hilfe und die zahlreichen Antworten! Ich gebe mein Bestes alles zu verstehen und es in ein möglichst funktionierendes Programm umzusetzen. Aber ich habe ja schließlich noch einen weiten Weg vor mir.

LG nochmals,
Lila
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Das sollte doch sofort auffallen, dass die unsinnigen Getter und Setter nur mehr Code bedeuten und schlechter zu lesen ist. Ein Hund-Objekt hat gar keinen Namen, kann also auch nicht ausgegeben werden. Du musst also Deine Klasse um den Namen erweitern.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wiexd: Das wichtigste zuerst: Lass diese trivialen Getter und Setter weg. Die haben in Java einen Sinn — in Python aber nicht, denn in Python wird das anders gelöst. Python kennt `property()` um ”berechnete Attribute” zu erstellen, was Java nicht kennt und deshalb überall Getter und Setter braucht.

Das Problem mit `bob` kannst Du in Python genau so wenig Lösen wie in Java. `bob` ist ein Name im Programm an den das `Hund`-Objekt gebunden wird. Ein Objekt kann aber an beliebig viele oder sogar gar keinen Namen gebunden sein und das Objekt weiss davon nichts. Das ist in Java doch genau so:

Code: Alles auswählen

        var hunde = new LinkedList();
        var tim = new Hund("grau", 3, true);
        var bello = new Hund("braun", 15, false);
        var bob = new Hund("weiß", 2, false);
        var karl = new Hund("gefleckt", 7, false);
        hunde.insert(bob);
        hunde.to_first();
        System.out.println(hunde.get_content().toString());
Wie würdest Du denn da versuchen "bob" auszugeben? Das ist in Java ja noch weniger ein Wert, weil hier nicht einmal garantiert ist, dass dieser Name nach dem Kompilieren noch vorhanden ist.

Weitere Anmerkung: Man macht keine Vergleiche mit literalen Wahrheitswerten. Bei dem Vergleich kommt doch nur wieder ein Wahrheitswert bei heraus. Entweder der, den man sowieso schon hatte; dann kann man den auch gleich nehmen. Oder das Gegenteil davon; dafür gibt es ``not``. In `bellen()` sollten das auch nicht unabhängige ``if``\s sein wenn doch nur eine Bedingung davon zutreffen kann. Da nimmt man ``elif``.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

wiexd hat geschrieben: Montag 15. Februar 2021, 22:18 Mich verwirrt, da ich nur Java kenne, erstmal natürlich das self.
Hallo Lila,

das "self" läßt sich recht einfach erklären... oder ausführlicher.

Die einfache Erklärung ist, daß manche objektorientierten Programmiersprachen wie das Dir bekannte Java, aber auch etwa C++, jeder Methode implizit einen "Zeiger" auf die Instanz mitgeben, auf der die Methode aufgerufen wird, über die die Methode auf andere Methoden oder Eigenschaften der Klasse zugreifen kann. Dafür gibt es in Java und C++ ein eigenes Schlüsselwort namens "this", das kennst Du bestimmt.

Für die etwas ausführlichere Erklärung möchte ich auf etwas C zurückgreifen. C hat keine Unterstützung für objektorientierte Programmierung und kennt daher keine Klassen und keine Methoden, allerdings kennt es Datenstrukturen. Nehmen wir als Beispiel eine einfache Person mit Vor- und Nachnamen:

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>

typedef struct { char* vorname; char* nachname; } Person_t;

Person_t* anlegen(char* vorname, char* nachname) {
    Person_t* rv = (Person_t*)malloc(sizeof(Person_t));
    rv->vorname = vorname;
    rv->nachname = nachname;
    return rv;
}
void ausgeben(Person_t* p) { printf("Die Person heisst %s %s\n", p->vorname, p->nachname); }
void bayerisch(Person_t* p) { printf("Jo des is dera %s %s.\n", p->nachname, p->vorname); }


int main(void) {
    Person_t* p = anlegen("Hans", "Wurst");
    ausgeben(p);
    bayerisch(p);
    return 0;
}
Du siehst: es gibt eine Datenstruktur namens "Person_t" und drei frei stehenden Funktionen, die mit "Person_t" oder Instanzen davon erst einmal gar nichts zu tun haben. Die Funktion "anlegen()" wäre quasi der Konstruktor (den ich in einer echten Implementierung ganz anders schreiben würde, aber...), während "ausgeben()" und "bayerisch()" (bitte verzeiht mir, liebe Bayern) wären Methoden. Das Programm funktioniert, ist allerdings etwas blöd, aus mehrererlei Hinsicht. Da wäre zum Einen der fehlende Bezug der Funktionen "anlegen()", "ausgeben()" und "bayerisch()" zu der Datenstruktur Person_t, die sie erzeugen, be- und verarbeiten sollen; ich kann dem Compiler im Prinzip allen möglichen Unsinn als Parameter dieser Funktionen übergeben, solange es Zeiger sind. Ein wohlerzogener Compiler wird zwar meckern, wenn es kein Zeiger auf eine Person_t ist, den Code aber nichtsdestotrotz übersetzen... aber dann stürzt mein Programm beim Aufruf natürlich ab. Zusätzlich gibt es da aber auch noch ein Problem mit den Benamsungen. Wenn ich jetzt eine Bibliothek von einem Kollegen einbinden will, der eine seiner Funktionen ebenfalls so benannt hat wie ich meine, dann weiß der Compiler nämlich nicht mehr, was er aufrufen soll, und weist mich mit einer Fehlermeldung darauf hin, daß ich doof bin.

Also haben sich kluge Leute, allen voran ein gewisser Alan Kay, überlegt, daß es doch vielleicht sinnvoller sei, die Datenstrukturen und die Funktionen, die sie erzeugen, be- und verarbeiten, irgendwie zusammen zu fassen. Das Konzept, das er sich damals überlegt hat, hat er "Objektorientierung" genannt. In der (weitgehend zu C abwärtskompatiblen) objektorientierten Sprache C++ sieht die Implementierung dann so aus:

Code: Alles auswählen

#include <iostream>
#include <string>

struct Person {
    std::string vorname;
    std::string nachname;    
    Person(std::string vorname, std::string nachname) {
	this->vorname = vorname;
	this->nachname = nachname;
    }
    void ausgeben() { std::cout << "Die Person heisst " << this->vorname << " " << this->nachname << "." << std::endl; }
    void bayerisch() { std::cout << "Jo des is dera " << this->nachname << " " << this->vorname << "." << std::endl; }
};

int main(void) {
    Person p{"Hans", "Wurst"};
    p.ausgeben();
    p.bayerisch();
    return 0;
}
Hier heißt der Konstruktor wie seine Klasse, nämlich "Person()", und Du siehst: auf einmal müssen "ausgeben()" und "bayerisch()" gar keinen Zeiger auf ihre Person-Instanz mehr bekommen, weil sie den mit "this" bereits vollautomatisch erhalten. Yay! Und wenn ein anderer Kollege seine Klasse baut, kann er seine Methoden gerne genauso nennen wie ich meine -- indem unsere Methoden an unsere Klassen und damit an die Instanzen unserer Klasse gebunden sind, funktioniert das ganz wunderbar und ohne Kollisionen. Klar, mit der Objektorientierung kommen noch ein paar andere nützliche Features, zum Beispiel Polymorphie und Vererbung mit Late-Binding über Virtual Method Tables und solche Dinge, aber im Kern geht es erstmal um diese gezeigten Dinge. Tatsächlich wird Dir allerdings vielleicht auch aufgefallen sein, daß ich in C++ auf Zugriffsmodifikatoren wie public, protected und private verzichtet und statt einer "class" eine "struct" deklariert habe -- das liegt an dem Unterschied zwischen diesen beiden. Bei einer Klasse "class" in C++ sind alle Methoden und Attribute per default private, während sie in einer Struktur "struct" in C++ per default "public" sind.

Okay, kommen wir zu Python. Das ist, wie Du bestimmt schon gemerkt hast, in vielerlei Hinsicht... etwas anders als kompilierte Sprachen wie Java und C++. Schauen wir uns doch einfach mal eine Python-Implementierung unserer Person-Klasse an -- und zwar auch diesmal ohne fortgeschrittene Features, die hab' ich in C++ ja auch bewußt weggelassen (Initialisierungslisten, Operatorenüberladung, ...).

Code: Alles auswählen

#!/usr/bin/env python

class Person:
    def __init__(self, vorname, nachname):
        self.vorname = vorname
        self.nachname = nachname
    def ausgeben(self): print("Die Person heisst %s %s"%(self.vorname, self.nachname))
    def bayerisch(p): print("Jo des is dera %s %s."%(p.nachname, p.vorname))

if __name__ == '__main__':
    p = Person('Hans', 'Wurst')
    p.ausgeben()
    p.bayerisch()
Klar, auch hier gilt das oben für die Objektorientierung Gesagte, und auch hier wird der Zeiger auf die Instanz als erster Parameter übergeben -- aber hier wird das in der Methodensignatur, also: in ihrer Parameterliste, ausdrücklich angegeben. Per Konvention heißt das, was da explizit mitgegeben wird und in Java und C++ als "this" automatisch mitgegeben wird, "self". Aber Achtung: das heißt nur per Konvention so, es kann auch (wie in meinem Python-Beispiel die Methode "bayerisch()" zeigt -- ganz anders heißen. (Bitte weiche in realem Code niemals ohne wirklich gute Gründe von dieser Konvention ab, sofern Du keine Erfahrungen mit einem wütenden Mob mit Fackeln und Mistgabeln machen willst und weder geteert noch gefedert werden möchtest!)

Zuletzt möchte ich Dir sagen, daß ich es wirklich toll finde, wenn sich ein junger Mensch für Programmierung interessiert. Viel Spaß und Erfolg dabei!
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Bei dem C-Beispiel ist beim `malloc()` der „cast“ überflüssig und ich würde da nicht die grösse von dem Typ direkt nehmen, sondern von dem Typ auf den `rv` zeigt:

Code: Alles auswählen

    Person_t* rv = malloc(sizeof *rv);
Das Programm hat ein Speicherleck, weil das `free()` zum `malloc()` fehlt.

Man muss nicht mal Pointer übergeben, auch ein `int` muss nur zu einer Warnung führen, aber in der Praxis wird einen der Compiler warnen und man würde diese Warnungen ernst nehmen, also kann man da faktisch doch nur das übergeben was angedacht war. Problematisch wird das erst wenn man Vererbung ”händisch” löst, weil dann muss man casten und kann dabei natürlich Fehler machen. Aber so weit geht das Beispiel ja nicht.

Das Problem mit den Namenskollisionen ist nicht zwingend ein Problem das mit OOP gelöst werden müsste, da würden Namensräume reichen, oder die Möglichkeit Namen beim einbinden umzubennenen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Freitag 21. Mai 2021, 00:40 Bei dem C-Beispiel ist beim `malloc()` der „cast“ überflüssig und ich würde da nicht die grösse von dem Typ direkt nehmen, sondern von dem Typ auf den `rv` zeigt:

Code: Alles auswählen

    Person_t* rv = malloc(sizeof *rv);
Nein. Wenn man das sauber machen will ist der Typecast sauber, wichtig, und richtig.
__blackjack__ hat geschrieben: Freitag 21. Mai 2021, 00:40 Das Programm hat ein Speicherleck, weil das `free()` zum `malloc()` fehlt.
Meine Güte. Das sind Beispiele... steht sogar mehrmals 'dran. Und wenn man mal ganz genau hinschaut, stellt man fest: huch, da fehlen die "const"s und ein Haufen anderes Zeug. Es wäre auch... schwierig, Konstanten zu deallokieren. Stack und Heap und so. Da kann man ein paar... widerwärtige Dinge tun, um sie unerreichbar zu machen, aber das möchte man ja nicht. Wenn Du in diesem Thread was Besseres erklären kannst, bitte, laß' mal was Kluges (und anfängertaugliches) sehen. Na, kannst Du? Und: warum hast Du Dich bisher so zurückgehalten? ;-)
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@LukeNukem: wenn man es sauber machen will, dann vermeidet man unnötige Wiederholungen im Code, weil das ein potentielle Fehlerquelle ist. `sizeof` ist keine Funktion, die Klammern also immer verwirrend. Bei Typcasts prüft C nicht, ob das sinnvoll ist, ob genug Speicher reserviert wurde, etc. Außer dass ein Mensch mehr lesen muß, hilft das also hier nichts.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LukeNukem: An dem cast ist nichts sauber, wichtig, und richtig, der ist einfach nur überflüssig, und in *C* wird da auch dringend von abgeraten, schon sehr lange in der C-FAQ und auch in aktuellen Standardwerken wie „Modern C“.

Nötig war der cast vor ANSI-C, als es Compiler gab, die kein `void*` kannten, und wo `malloc()` deshalb den Rückgabetyp ``char*`` hatte. So etwas setzt wohl heute keiner mehr ein.

Der Tipp nicht unnötig zu casten und die Grösse nicht über den Typnamen, sondern über den Variablennamen zu bestimmen, ist anfängertauglich. Es vermeidet, dass man den Typen drei mal angibt und wenn man die *eine* Angabe des Typen ändert, bleibt der Code korrekt. Man kann nicht vergessen alle Angaben zu ändern, oder dabei einen Fehler machen. Den Typen unnötigerweise drei mal hin zu schreiben verletzt das DRY-Prinzip.

Was meinst Du mit zurückgehalten? Und was impliziert diese Frage denn? Das man nur was zum Thema sagen darf wenn man schon mal was zum Thema gesagt hat? Ob C und C++ für jemanden der Java (kurz) kennt und Python lernt, jetzt wirklich anfängertauglich ist…

Das `self` in Java ``this`` ist, hat Sirius3 bereits geschrieben. Wenn man das noch mal deutlicher hervorheben wollte, wäre ein Java-Beispiel, also etwas das schon bekannt ist, für wiexd sinnvoller. Hier kann man sich einfach vorstellen das implizit in jeder Methode `this` als erstes Argument übergeben wird, was bei Python halt explizit `self` ist:

Code: Alles auswählen

public class Person {
    private final String vorname;
    private final String nachname;

    public Person(String vorname, String nachname) {
        this.vorname = vorname;
        this.nachname = nachname;
    }

    public void ausgeben() {
        System.out.printf("Die Person heisst %s %s.%n", this.vorname, this.nachname);
    }

    public void bayerisch() {
        System.out.printf("Jo des is dera %s %s.%n", this.vorname, this.nachname);
    }

    public static void main(String... args) {
        var person = new Person("Hans", "Wurst");
        person.ausgeben();
        person.bayerisch();
    }
}
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Freitag 21. Mai 2021, 08:44 Was meinst Du mit zurückgehalten? Und was impliziert diese Frage denn? Das man nur was zum Thema sagen darf wenn man schon mal was zum Thema gesagt hat?
Nein, im Gegenteil: daß man nur etwas zu einem Thema sagen sollte, wenn man etwas dazu zu sagen hat. Das hattest Du bisher aber leider nicht und hast Du bislang leider auch nicht. Auch in Deiner Antwort auf meinen Beitrag hast Du es leider (wieder) nicht geschafft, der TO etwas Konstruktives zu erklären, das sie weitergebracht hätte. Schade.
__blackjack__ hat geschrieben: Freitag 21. Mai 2021, 08:44 Das `self` in Java ``this`` ist, hat Sirius3 bereits geschrieben. Wenn man das noch mal deutlicher hervorheben wollte, wäre ein Java-Beispiel, also etwas das schon bekannt ist, für wiexd sinnvoller.
Darum ging es mir aber gar nicht, und Dein schickes Java-Beispiel erklärt meine Punkte leider nicht. Es ging mir darum, ganz andere Dinge zu zeigen.

Erstens darum, zu zeigen, daß das implizite "this" in Java und C++ nichts anderes als ein Schlüsselwort für etwas ist, das in Python explizit übergeben wird, nämlich ein "Verweis" auf die Instanz, auf der die Methode aufgerufen wird. Das wäre mit Java ohnehin nicht vernünftig gegangen, und es wäre obendrein vollkommen sinnlos gewesen. Denn unsere TO kann Java ja schon und so etwas Triviales bekommt sie sicher selbst hin -- aber es zeigt ihr nichts Neues.

Zweitens ging es mir darum, zu erklären, daß ebendieser "Verweis" in Python nicht zwangsläufig "self" heißen muß, dies aber eine so beliebte und verbreitete Konvention ist, daß man davon niemals abweichen sollte.

Dazu kommen, was den bemäkelten Typecast angeht, noch ein paar Kleinigkeiten hinzu. Etwa daß der Cast nach dem Malloc bei einem C++-Compiler absolut zwingend ist, weil der Compiler sonst mit einem Fehler aussteigt... aber zudem noch ein bisschen anderer, praxisbezogener Kleinkram, der hier zu weit führen würde.

Insofern, bitte seid mir nicht böse, aber wenn Ihr abseits formaler und nicht ganz korrekter Korinthenkakerei etwas Inhaltliches zu meiner Darstellung der Objektorientierung sagen könnt, seid Ihr jederzeit herzlich willkommen. Ich finde es immer super, wenn jemand konstruktive Kritik äußern kann und mir dadurch hilft, Dinge besser zu verstehen oder gar zu erklären. Viel Glück! ;-)
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LukeNukem: Ich hatte schon in zwei Beiträgen vor Dir etwas zum Thema geschrieben. Dieses komische ich sollte doch nichts schreiben wenn ich nichts zum Thema schreibe, kannst Du Dir sparen. Du magst anscheinend einfach keine Kritik. Kannst Du auch gerne sagen, aber musst trotzdem damit Leben.

Das das implizite ``this`` in Java in Python das explizite `self`-Argument ist, wurde doch bereits vor Deinem Beitrag gesagt. Du versuchst das nur zusätzlich mit Programmiersprachen zu zeigen die wiexd nicht kennt. Ich bin da halt lieber bei Java geblieben, weil das schon bekannt ist. Wobei ich vermute auch nicht wirklich gefestigt bekannt, aufgrund des Settings (Schule) und des Alters. Da kann man jetzt sicher trefflich streiten in wiefern da jetzt Beispiele in C mit Zeigern und C++ hilfreich sind. Damit kann man das vielleicht vernünftiger zeigen was hinter den Kulissen abläuft, aber halt auch nur wenn man davon ausgehen kann, das die Fragestellerin C und Zeiger überhaupt versteht.

Den cast jetzt irgendwie mit C++ begründen willst, in einem Programmbeispiel in C macht keinen Sinn. Das hat nichts miteinander zu tun. In C braucht man den cast nicht und in C++ würde man `malloc()` nicht verwenden. Es macht auch keinen Sinn hier beide Sprachen zu mischen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Montag 24. Mai 2021, 22:55 Das das implizite ``this`` in Java in Python das explizite `self`-Argument ist, wurde doch bereits vor Deinem Beitrag gesagt. Du versuchst das nur zusätzlich mit Programmiersprachen zu zeigen die wiexd nicht kennt.
Mir ging es darum, Objektorientierung zu zeigen. Die Herkunft. Wo kommt das her? Was ist der Gedanke dahinter? Wie funktioniert das?
__blackjack__ hat geschrieben: Montag 24. Mai 2021, 22:55 In C braucht man den cast nicht und in C++ würde man `malloc()` nicht verwenden.
Dann ist die cstdlib in der C++-STL überflüssig?

Schau, ich habe zu erklären versucht, wie Objektorientierung funktioniert. Du dagegen ziehst Dich an irgendeiner dummen Klugscheißerei hoch, kannst aber nichts zum Thema beitragen. Danke für Deine "Beiträge".
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

To whom it may concern:

“I made up the term ‘object-oriented’, and I can tell you I didn’t have C++ in mind.” ~ Alan Kay

“OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.” ~ Alan Kay
In specifications, Murphy's Law supersedes Ohm's.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

pillmuncher hat geschrieben: Dienstag 25. Mai 2021, 02:41 To whom it may concern:

“I made up the term ‘object-oriented’, and I can tell you I didn’t have C++ in mind.” ~ Alan Kay

“OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.” ~ Alan Kay
"Premature optimization is the root of all evil." ~ Donald E. Knuth

"Our job is to remind us that there are more contexts than the one that we’re in — the one that we think is reality." ~ Alan Kay
Benutzeravatar
sparrow
User
Beiträge: 4144
Registriert: Freitag 17. April 2009, 10:28

Ich mag ja immer mir vorzustellen, wie solche Fälle wohl außerhalb dieses Forums passieren würden.

Da kommt ein Mensch mit seinem Auto in die Werkstatt und sagt: "Hey, ich habe mir hier mal einer eigenen Kolben gebaut, geht das so? Ich habe früher nur an Motorrädern geschraubt und vielleicht kann hier mal jemand drauf schauen?"
Und weil alle Motorräder und Autos kennen, ist das Thema schnell erledigt und alle sind glücklich.
Und dann kommt 3 Monate später jemand mit einer Dampfmaschine durch die Tür und will es damit noch einmal erklären...
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LukeNukem: Die C-Standardbibliothek in der C++-Standardbibliothek ist teilweise überflüssig, ja. Nämlich da wo die C++ als Sprache oder die C++-Standardbibliothek eigenes hat was das gleiche macht. Du hast ja beispielsweise bei der Ausgabe auch den C++-Weg gewählt statt `printf()` zu verwenden.

Was wäre denn für Dich ein Grund `malloc()` in C++ zu verwenden? Und warum trifft der in diesem Beispiel zu? Hast Du beim Beispiel in C++ dann ja auch nicht gemacht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Dienstag 25. Mai 2021, 20:29 @LukeNukem: Die C-Standardbibliothek in der C++-Standardbibliothek ist teilweise überflüssig, ja. Nämlich da wo die C++ als Sprache oder die C++-Standardbibliothek eigenes hat was das gleiche macht.
Das ist zweifellos nicht ganz flashc, man hätte einige der alten Zöpfe abschneiden können. Allerdings war die Abwärtskompatibilität zu C eines der wesentlichen Designziele von C++ und ist bis heute zweifellos einer der wichtigsten Gründe, warum C++ so erfolgreich ist. Und dann...
__blackjack__ hat geschrieben: Dienstag 25. Mai 2021, 20:29 Du hast ja beispielsweise bei der Ausgabe auch den C++-Weg gewählt statt `printf()` zu verwenden.
Ja, weil mein Beispielcode auf einem fetten Eisen läuft. Denn auch mit Optimierung mit -Os und strip(1) ist das C++-Binary laut size(1) wesentlich größer, das C-Binary ist hier "a" und das C++-Binary "b":

Code: Alles auswählen

       text       data        bss      total filename
       621       1803          8       2432 a
      1533       3795        280       5608 b
Nun, die Dateigrößen unterscheiden sich nicht wesentlich, aber die Größen der einzelnen Segmente sehr wohl. Wie gesagt, auf einer richtigen Maschine spielt das keine Rolle, aber in meiner Freizeit spiele ich manchmal auch gerne mit kleinen 8-Bit-SOC wie den Atmel (jetzt Microchip) AVRs, zum Beispiel dem AtTiny13. Der hat maximal 20 MHz Taktfrequenz, gerade mal ein Kilobyte Flash-Speicher für den Code und nur jeweils 64 Bytes RAM und EEPROM. Du kannst Dir sicherlich vorstellen, daß auf so einem winzigen SOC die Größe der Codesegmente eine wichtige Rolle spielt, weswegen man auf etliche schicke Features von C++ leider verzichten und bei anderen sehr genau darauf achten muß, was und wie man es nutzt. Templates gehen ziemlich gut, aber schon die Vererbung krankt bei zeitkritischen Dingen daran, daß sie für Methodenaufrufe auf die interne Virtual Method Table zugreifen muß, was zwar nicht viele, dennoch aber Prozessortakte kostet. Dynamische Sachen wie Exceptions und die STL kannst Du auf so kleinen Dingern natürlich auch knicken... aber wenn man sich auf die "guten Features" beschränkt, ist der optimierte C++-Code nicht größer als ein äquivalenter C-Code. Diese Umstände sind übrigens der Grund dafür, warum es im Forum von Mikrocontroller.net immer noch häufig zu echauffierten Diskussionen zwischen den Verfechtern von Assembler, C und C++ kommt und der Sinn der Objektorientierung oft immer noch ganz grundsätzlich in Frage gestellt und zum Teil sogar rundheraus abgelehnt wird. Daß es in den Neunzigern einige... Scharlatane gegeben hat, die ihrem alten C-Code lediglich ein paar Klassen spendiert und wie die Maikäfer alles vererbt haben, was nicht schnell genug weglaufen konnte, hat allerdings auch nicht unbedingt zur Popularität oder gar Beliebtheit der OOP in diesen Kreisen beigetragen... :-(

Übrigens, für den Fall, daß jemand von Euch sich mal in dem anderen Forum umsehen möchte: Python ist dort von der Moderation und vielen Anwendern zwar wohlgelitten und oft auch gerne gesehen, allerdings gibt es dort noch eine sehr hartnäckige Fraktion, für die Python pures Teufelszeug ist. Die Syntax mit dem Whitespace, das ist ja wie Fortran77, und überhaupt ist Python viel zu langsam und zu bloaty und zu unverstanden und alle, die Python mögen, sind irgendwelche pickeligen Skriptkiddies, die es nicht besser können und ihren Code sowieso nur zusammenkopieren, ohne ihn zu verstehen. Okay, das wird dort auch gerne über Arduino und Co. gesagt, und, ach ja: Micropython ist natürlich eine Ausgeburt der Hölle! Nur daß Ihr Bescheid wißt, nicht daß es hinterher heißt, ich hätte Euch nicht gewarnt. ;-)
__blackjack__ hat geschrieben: Dienstag 25. Mai 2021, 20:29 Was wäre denn für Dich ein Grund `malloc()` in C++ zu verwenden?
Hier? Weil ich's kann -- und weil ich etwas zeigen wollte, nämlich die Idee hinter der Objektorientierung. Da wollte ich nicht, daß der Code zu weit von einander abweicht, um halbwegs vergleichbar zu bleiben. Auf einem AtTiny gehen natürlich auch Funktionen wie (s)printf() nicht so wirklich, das Parsen des Formatstrings und die Verarbeitung von varargs sind viel zu teuer -- deswegen muß man auf größeren AVRs dann auch derlei Funktionen mit eigenen Compilerschaltern ausdrücklich aktivieren. Ähnliches gilt für malloc()... bei 64 Byte (nicht KByte, Byte!) RAM kannst Du dynamische Speicherallokation und Ähnliches gepflegt knicken, und auch auf größeren AVRs ist das eine ziemlich riskante Nummer. Nicht selten steht der Entwickler in solchen Fällen dann vor der Aufgabe, externen Arbeitsspeicher anzubinden und bestimmte Codesegmente mit eigenen Linkerskripten in diesen externen Speicher zu verschieben, siehe dazu beispielsweise auch: https://www.nongnu.org/avr-libc/user-manual/malloc.html -- HF! ;-)
Antworten