c# im dritten Anlauf



  • In C# hab ich die Antwort auf ein Problem gefunden, was so in AnsiC nicht lösbar wäre, nämlich die Länge von Textfeldern durch Variablen/Konstanten festzulegen.

    Das Problem tritt auf, wenn man struct mit gemischen Datentypen int, double, char in Text verwandelt und wieder zurückverwandeln will. Dann ist es wichtig, die Feldlänge zu kennen.

    Wir haben in AnsiC die Möglichkeit, mit #define Präprozessoranweisungen zu geben, z.B.

    #define DOUBLELEN 12

    double BETRAG_STRING[DOUBLELEN}="";

    Wenn man die String-Länge aller Doubles auf 14 erhöhen will, geht das in Ansi-C mit der Änderung von #define, und die Änderung geht durch das ganze Programm durch.

    Aber:

    Angepaßt werden muß auch die weitere Verarbeitung, und wenn die mit sprintf() stattfindet, können die dort eingesetzten Feldlängenbegrenzer nicht mit Variablen oder #define ersetzt werden.

    denn:

    sprintf(buffer,"%12.2f ...

    Die 12.2 ist durch nichts zu ersetzen.

    Das muß alles händisch angepaßt werden und ist eine sehr kritische Fehlerquelle bei Änderungen am Programm, vor allem, was vorhandene Dateien betrifft, weil bei der Rückumwandlung mit falschen Feldlängen natürlich nur Unsinn dabei herauskommt.

    Jetzt habe ich entdeckt, daß in C# die Lösung ganz einfach ist.

    Man deklariert eine Klasse, als Ersatz für die struct in AnsiC, und legt die Feldlänge fest. Also z. B. für

    class Konto

    {

    int kontonr;
    int kontonr_LEN;

    ... und so weiter

    Initialisiert diese mit kontonr_LEN=6; // nicht die Kontonummer, sondern die Stringlänge

    Damit wird für jeden Feldbezeichner mit _LEN die Stringlänge festgelegt, die er bei der Umwandlung und Rückumwandlung in string haben soll.

    Jetzt kann man je nachdem rechts- oder linksbündig umwandeln mit

    Konto k = new Konto(); // der Handle auf die Klasse
    teilstring=k.kontonr.ToString();
    teilstring=teilstring.PadLeft(k.kontonr_LEN);

    Der Witz ist, daß wir hier die in der Klasse festgelegten Feldlängen verwenden. Das ist mit sprintf() nicht möglich, aber in C# geht es. Wir haben die Konto-Nr. mit einer festen Feldlänge als String. Damit kann man den gemischten Datensatz von int, double, string etc. in einen String verwandeln und aus der Textdatei wieder zurücklesen und wieder in gemischte Datentypen zurückverwandeln. In C# bietet sich dafür die Klasse Convert. an.

    Also, ich entdecke fortlaufend sehr schöne Dinge in C#.

    Pascal



  • pascal2009 schrieb:

    sprintf(buffer,"%12.2f ...

    Die 12.2 ist durch nichts zu ersetzen.

    Doch: sprintf(buffer, "%*.*f", 12, 2, ...);



  • Bashar schrieb:

    pascal2009 schrieb:

    sprintf(buffer,"%12.2f ...

    Die 12.2 ist durch nichts zu ersetzen.

    Doch: sprintf(buffer, "%*.*f", 12, 2, ...);

    Die Frage ist, ob man die 12 durch eine Variable ersetzen kann. Nach meinem Kenntnisstand geht das nicht. Man kann nicht für die 12 ein "x" einsetzen. Oder irre ich?

    Pascal



  • Das sollte doch jetzt offensichtlich sein 😕

    int x = 12;
    sprintf(buffer, "%*.2f", x, ...);
    


  • Bashar schrieb:

    Das sollte doch jetzt offensichtlich sein 😕

    int x = 12;
    sprintf(buffer, "%*.2f", x, ...);
    

    C++ oder Ansi-C??? In Ansi kann ich mit dem Platzhalter * nichts anfangen. Den kenne ich gar nicht.

    Pascal



  • C natürlich, C++ hat mit printf nichts am Hut.

    In Ansi kann ich mit dem Platzhalter * nichts anfangen.

    Aber dein Compiler.

    Den kenne ich gar nicht.

    Klar, sonst hätte ich dir von ihm ja nicht erzählen müssen.



  • Bashar schrieb:

    C natürlich, C++ hat mit printf nichts am Hut.

    In Ansi kann ich mit dem Platzhalter * nichts anfangen.

    Aber dein Compiler.

    Den kenne ich gar nicht.

    Klar, sonst hätte ich dir von ihm ja nicht erzählen müssen.

    Wenn das so gehen würde, Kasten Bier franco wär ja das mindeste.

    Denn speziell darüber würde ich mich sehr freuen, wenn es geht.

    Werd das morgen ausprobieren, heute zu müde.

    Pascal

    PS: Wenn DAS funktioniert, kann ich nur sagen: echt cool, daß du solche Sachen weißt. Da wär ich im Traum nicht drauf gekommen.



  • pascal2009 schrieb:

    PS: Wenn DAS funktioniert, kann ich nur sagen: echt cool, daß du solche Sachen weißt. Da wär ich im Traum nicht drauf gekommen.

    Und das ist das Kernproblem Deiner ganzen Bemühungen. Bei Lernen einer Sprache oder Bibliothek geht es selten darum, auf etwas zu "kommen". Man kann das alles nachlesen und systematisch lernen.



  • Nix für ungut, aber du tust es dir und uns extrem schwer. Trenne zunächst GUI-Dinge und interne Berechnungen.
    Vergiss alle durch Bibliotheken verbesserte Ansätze wie printf und co. Gilt sowohl für C# als auch C++

    Mache dich vertraut mit dem Konzepten von .NET / C# - vor allem mit den Begriffen Eigenschaften (properties),
    Methoden (Funktionen) und Ereignissen (events). Der Einstieg ist nicht besonders schwer. Ein völliger
    Neueinsteiger hat es da deutlich leichter, weil er frei im Kopf ist.



  • @bashar

    Das mit dem * funktioniert 1A. Danke!

    @my @bernie

    Ich hab mir jetzt statt learning by doing doch tatsächlich ein Buch gekauft. Frischalowski Visual C# Einstieg für Anspruchsvolle. Bin jetzt bei Seite 98 angekommen.

    Das Problem ist wohl weniger der Code als die Denke. Von prozedural zu OOP. OOP hab ich ehrlich gesagt bisher nicht verstanden. Vielleicht ändert sich das ja. 😉

    In der .NET Bibliothek finden sich ganz feine Sachen wie ArrayList. Mit Sort-Algorithmus. Das sind alles dynamische Strukturen, die in AnsiC natürlich auch möglich sind, aber einen ziemlichen Aufwand erfordern und außerdem beim kleinsten Fehler den Speicher abschießen. In AnsiC muß man eben alles zu Fuß machen, in C# .NET gibt es die Bibliotheksfunktionen. Solche Dinge sind mit C# schon gut gelöst, muß man zugeben.

    Pascal



  • pascal2009 schrieb:

    Warum das nicht gleich klar war, weiß ich nicht. Ich kann es mir im nachhinein nicht erklären

    Falls das nicht nur eine rhetorische Frage war, sondern ernstgemeint... Nun hier findet sich ein Hinweis:

    pascal2009 schrieb:

    Ich hab mir jetzt statt learning by doing doch tatsächlich ein Buch gekauft. Frischalowski Visual C# Einstieg für Anspruchsvolle. Bin jetzt bei Seite 98 angekommen.

    Soso, Du glaubst also keine Bücher zu brauchen weil Du Dich für etwas Besseres hälst. Und wenn Du Dich schon (als absoluter Anfänger) dazu herablässt ein Buch zu kaufen, dann _muss_ es ja gleich etwas für Anspruchsvolle sein um Deinem überlegenen Intellekt zu schmeicheln. Daher musste es ja auch zwangsläufig an der Sprache liegen (C#) denn an Dir konnte es ja nicht liegen gelle?

    Ich formulierte es schonmal so: Der Grad der Selbstüberschätzung ist zumeist umgekehrt proportional zum Kenntnisstand eines Programmierers.



  • pascal2009 schrieb:

    In der .NET Bibliothek finden sich ganz feine Sachen wie ArrayList.

    Vergiss das Ding wieder. Nimm eine List<T>. ArrayList ist ein Überbleibsel aus .NET 1.1 Zeiten.



  • loks schrieb:

    Ich formulierte es schonmal so: Der Grad der Selbstüberschätzung ist zumeist umgekehrt proportional zum Kenntnisstand eines Programmierers.

    Du kannst nach ein paar Wochen unregelmäßiger Beschäftigung mit dem Thema nicht zuviel erwarten. Gesunde Selbsteinschätzung führt auch dazu, daß man sich mit der nötigen Leidensbereitschaft durcharbeitet und nicht gleich aufgibt.

    Also, der Kenntnisstand C# ist einige Wochen damit beschäftigt. Eine ganze Menge Dinge sind schon klar, insbesondere mit der GUI Forms und WPF komme ich ganz gut zurecht. Vieles ist aber noch unklar. Es fehlt an systematischen Kenntnissen der Zusammenhänge. Dazu lese ich ja dieses Buch, z. B.

    Hier ist ein Stück Code, was der Compiler nimmt, was aber einen Laufzeitfehler auslöst: Null-Exception nicht behandelt. Das kommt aus der i-Schleife heraus. Ursache ist mir nicht klar. Public muß sein, weil ich die Daten aus dem Class-Array Land[] an anderer Stelle auslesen will. Falls jemand einen freundlichen Hinweis dazu hätte, danke.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Domination_WinForms
    {
        public class CL_World
        {
            public const int MAX_NACHBARN = 4;
            public const int MAX_LÄNDER = 3;
    
            public class CL_Land
            {
                public int nr;
                public string name;
                public int gehört_zu_spieler;
                public int farbe;
                public int armeen;
                public int[] nachbar;
            }
    
            public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; 
    
            public bool init_Länder()
            {
                for (int i = 0; i < MAX_LÄNDER; i++)
                {
                    Land[i] = new CL_Land();
                    Land[i].nr = i;
                    for (int k=0;k<MAX_NACHBARN;k++)Land[i].nachbar[k]=-1; // ausnullen
                } 
                            Land[0].name = "England";
                            Land[0].nachbar[0] = 1;
                            Land[0].nachbar[1] = 2;
    
                            Land[1].name = "Schottland";
                            Land[1].nachbar[0] = 0;
    
                            Land[2].name = "Belgien";
                            Land[2].nachbar[0] = 0;
    
                            return true;
    
            } // init Länder
    
        } // class CL_World
    
    } // namespace
    


  • nachbar ist null.

    Mit dem Debugger hättest du das sofort gefunden.



  • Bashar schrieb:

    nachbar ist null.

    Mit dem Debugger hättest du das sofort gefunden.

    Entschärfte Version, Array Nachbar[] rausgenommen.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Domination_WinForms
    {
        public class CL_World
        {
            public const int MAX_LÄNDER = 3;
    
            public class CL_Land
            {
                public int nr;
                public string name;
    //            public int gehört_zu_spieler;
    //            public int farbe;
    //            public int armeen;
                public int nachbar1;
                public int nachbar2;
                public int nachbar3;
            }
    
            public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; 
    
            public bool init_Länder()
            {
                for (int i = 0; i < MAX_LÄNDER-1; i++)
                {
                    Land[i] = new CL_Land();
                    Land[i].nr = i;
                    Land[i].name = ""; // ausnullen
                    Land[i].nachbar1 = -1;
                    Land[i].nachbar2 = -1;
                    Land[i].nachbar3 = -1;
    
                } 
                            Land[0].name = "England";
                            Land[0].nachbar1 = 1;
                            Land[0].nachbar2 = 2;
    
                            Land[1].name = "Schottland";
                            Land[1].nachbar1 = 0;
    
                            Land[2].name = "Belgien";
                            Land[2].nachbar1 = 0;
    
                            return true;
    
            } // init Länder
    
        } // class CL_World
    
    } // namespace
    

    Laufzeitfehler: Null ReferenceException wurde nicht behandelt.

    Debugger sagt:

    [0].name = England etc. die anderen Felder alles ok.
    [1].name = Schottland etc. ok.
    [2] ist null.

    Datensatz mit Index 2 ist also insgesamt Null. Warum jetzt 0+1 funktionieren und Nr. 2 nicht, nun ja. Wäre wirklich interessant zu wissen, warum.

    Sorry, Fehler gefunden es ist

    for (int i = 0; i < MAX_LÄNDER-1; i++)
    

    Da hängt noch eine -1. Aus dem Austesten.
    So geht´s natürlich nicht. 🤡

    Aber wenn ich ein geschachteltes Array mit gemischten Typen deklariere, muß ich für die geschachtelten Array mit new wieder separat Speicher anfordern? Also Speicher innerhalb des Speichers?

    Pascal



  • pascal2009 schrieb:

    Aber wenn ich ein geschachteltes Array mit gemischten Typen deklariere, muß ich für die geschachtelten Array mit new wieder separat Speicher anfordern? Also Speicher innerhalb des Speichers?

    Ja.

    Statt rohe Arrays solltest Du die schon mehrfach empfohlenen Container verwenden. List<T> ist dabei Allzweckwaffe.

    List<CL_Land> Land = new List<CL_Land>();
    
    CL_Land land = new CL_Land();
    land.name = "Belgien";
    Land.Add(land);
    

    Falls der Vorteil nicht sofort ersichtlich ist: Diese Container wachsen dynamisch. Überbleibsel im C-Stil wie Arrays fester Größe und irgendwelchen MAX_LÄNDER Konstanten sind nicht nötig.



  • µ schrieb:

    pascal2009 schrieb:

    Aber wenn ich ein geschachteltes Array mit gemischten Typen deklariere, muß ich für die geschachtelten Array mit new wieder separat Speicher anfordern? Also Speicher innerhalb des Speichers?

    Ja.

    Statt rohe Arrays solltest Du die schon mehrfach empfohlenen Container verwenden. List<T> ist dabei Allzweckwaffe.

    List<CL_Land> Land = new List<CL_Land>();
    
    CL_Land land = new CL_Land();
    land.name = "Belgien";
    Land.Add(land);
    

    Falls der Vorteil nicht sofort ersichtlich ist: Diese Container wachsen dynamisch. Überbleibsel im C-Stil wie Arrays fester Größe und irgendwelchen MAX_LÄNDER Konstanten sind nicht nötig.

    Das werde ich mir mal zu Gemüte führen. Danke für den Hinweis.

    Feste Arrays sind dann Ballast, wenn man die Datenmenge nicht kenn. Wenn es sich aber wie bei dem Brettspiel Risiko um 42 Länder handelt und um 4 Spieler, kann man drüber wegsehen.

    Als learner by doer oder try and error hab ich den Code jetzt doch zum Laufen gebracht. Das ist allerdings wirklich nicht elegant, mit new innerhalb der Klasse für jedes Array nochmal Speicher anzufordern. Wie gesagt, ich werde mir das mit List<T> mal zu Gemüte führen.

    So läuft es jedenfalls fehlerfrei (man bemüht sich, sich in den Compiler hineinzudenken 🤡 ):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Domination_WinForms
    {
        public class CL_World
        {
            public const int MAX_LÄNDER = 3;
            public const int MAX_NACHBARN = 4;
    
            public class CL_Land
            {
                public int nr;
                public string name;
    //            public int gehört_zu_spieler;
    //            public int farbe;
    //            public int armeen;
                public int[] nachbar;
            }
    
            public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; 
    
            public bool init_Länder()
            {
                for (int i = 0; i < MAX_LÄNDER; i++)
                {
                    Land[i] = new CL_Land();
                    Land[i].nr = i;
                    Land[i].name = ""; // ausnullen
                    Land[i].nachbar=new int[MAX_NACHBARN];
                    for (int k = 0; k < MAX_NACHBARN; k++)
                    {
                        Land[i].nachbar[k] = new int();
                        Land[i].nachbar[k] = -1;
                    }
                } 
                            Land[0].name = "England";
                            Land[0].nachbar[0] = 1;
                            Land[0].nachbar[1] = 2;
    
                            Land[1].name = "Schottland";
                            Land[1].nachbar[0] = 0;
    
                            Land[2].name = "Belgien";
                            Land[2].nachbar[0] = 0;
    
                            return true;
            } // init Länder
        } // class CL_World
    } // namespace
    

    Und zeigt dann auf dem Hauptbildschirm der Form1 sehr schön die Länder mit den Daten an.

    Ich sehe es aus der AnsiC Perspektive so, daß mit dem

    public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; sowie
    Land[i].nachbar=new int[MAX_NACHBARN]; für das eingeschlossene Array

    jeweils Zeiger auf den Startpunkt bzw. die Elemente der Array-Liste bereitgestellt werden, aber kein Speicherplatz für die Elemente bereitsteht, die man dann für jedes Element wieder neu anfordern muß mit:

    Land[i] = new CL_Land(); sowie für die eingeschlossenen Array-Elemente mit:
    Land[i].nachbar[k] = new int();

    Das ist verglichen mit AnsiC wirklich etwas heftig, muß man überhaupt erstmal drauf kommen. Man kommt dahin, wenn man die (kritischen) Programmelemente stufenweise ausklammert. Dann bekommt man einen Hinweis, wo der Hase im Gebüsch sitzen könnte.

    Pascal



  • new int() ist nicht notwendig. Für Wertetypen muss man den Speicher nicht mit new besorgen.

    Das ist allerdings wirklich nicht elegant, mit new innerhalb der Klasse für jedes Array nochmal Speicher anzufordern.

    Stimmt. Momentan ist Deine Klasse aber nicht mehr als ein Struct im C-Stil. Die Eleganz kommt noch, lies einfach mal weiter.



  • µ schrieb:

    new int() ist nicht notwendig. Für Wertetypen muss man den Speicher nicht mit new besorgen.

    Das ist allerdings wirklich nicht elegant, mit new innerhalb der Klasse für jedes Array nochmal Speicher anzufordern.

    Stimmt. Momentan ist Deine Klasse aber nicht mehr als ein Struct im C-Stil. Die Eleganz kommt noch, lies einfach mal weiter.

    Du hast recht, wenn man das rausnimmt, läuft es auch. Irgendwie klar, daß man für Wertetypen keinen Speicher auf dem Heap anfordert. Ogottogott 🤡 Sind eben Übergangsprobleme.

    Ich hoffe, deine Prognose bzgl. der Eleganz wird eintreffen. 😉 Ich arbeite dran.

    Pascal



  • pascal2009 schrieb:

    Du hast recht, wenn man das rausnimmt, läuft es auch. Irgendwie klar, daß man für Wertetypen keinen Speicher auf dem Heap anfordert. Ogottogott 🤡 Sind eben Übergangsprobleme.

    Achtung!
    new ist in C# erstmal nur Syntax.
    new heisst nicht unbedingt dass Speicher vom managed Heap besorgt wird!
    Es heisst nur: hier wird ein neues Objekt erzeugt/initialisiert.

    Wenn der verwendete Typ ein Value-Type (short, int, long, ..., DateTime, TimeSpan, eigene structs, ...) ist, dann wird nix vom Heap genommen.

    Für Value-Types wird nur was vom Heap genommen, wenn sie "boxed" werden. Was man allerdings nicht dadurch erreicht dass man "new" schreibt, sondern indem man sie als "object" behandelt/übergibt.

    Wenn der verwendete Typ dagegen ein Reference-Type ist ("class"), dann schon.


Anmelden zum Antworten