Anfängerfragen zu C++

Huhu, da ich mir die Zeit bis zum nächsten Schulblock mit einem Einstieg in C++ füllen will..:

Wenn ich in Java wissen will, wieviele Elemente ein Array enthält, gebe ich array.length() ein.

In C++ scheint es sowas nicht zu geben? Habe das hier gefunden
Code:
int count = sizeof(values) / sizeof(*values);
(values ist ein Array) und es funktioniert wohl auch, aber warum? Was passiert da?
Ich blicke mit den Pointern noch quasi gar nicht durch. Der Pointer *values ist doch letztendlich die Speicheradresse dort, wo der Array anfängt. Values selbst ist afaik dann das ganze Objekt.

Läuft das so? (Größe in bit oder byte vom ganzen Objekt) geteilt durch (Größe in bit oder byte von einem Element des Arrays)?

Falls ja, warum nicht einfach sizeof(array) / sizeof(array[0]), wozu der Pointer?
So, zweite Frage:

Ich versuche mich gerade an QuickSort als Anfang.
QuickSort <-- bei dem seinem Code, warum wird da
Code:
void quicksort([B]int *a[/B], int left, int right) {
geschrieben, was bringt mir das *a?

Und dritte Frage: So für halbwegs triviale Programme, brauche ich Pointer da überhaupt? Afaik waren die mal sinnvoll als die PCs noch schwach waren, aber braucht man sie in kleinen Programmen ansonsten? Habt ihr vll. ein kleines Code-Beispiel, wo auch ein Anfänger den Nutzen von den Dingern versteht?

Danke/Gruß, Forks
 
Eine Antwort auf die 3ter Frage.

Wir haben in der Uni ein einfaches "Telefonbuch" gebastelt mit Sortier funktion und da kommst du ohne Pointer nicht aus um einträge irgendwo davor oder dahinter zu schieben (ohne enormen Programmieraufwand nur mit Pointern möglich)

Code:
int *a

heist du Initialisierst einen Pointer mit den Datentyp Integer auch nochmal hier gut erklärt:
Zeiger in C++


Mit den sizeof muss ich passen was da genau gemacht wird...
 
dann versuch ich mal die verwirrung aufzuklären ...

sizeof() gibt dir zurück wie groß das da ist, was du ihm gibst ... ist in dem fall values ein array, wird sizeof(values) die gesammtgröße in size_t (gewöhnlich byte) angeben ... um zu erfahren wieviele elemente das nun sind, teilst man durch die größe der elemente, welche hier durch das erste element des arrays vertreten werden ... sizeof(*values) ... das ergebnis entspricht folglich der anzahl der elemente die das array aufnehmen kann ...

deine vermutung ist also richtig, für arrays funktioniert das, ABER: nur für statische arrays ... nicht für arrays die du mit malloc und co dynamisch erzeugt hast ...

von der syntax her ist *values identisch mit values[0]

values[1] ist nur eine andere schreibweise für *(values+1)

zu 2:
*a legt fest dass parameter 1 ein pointer ist und kein einfacher wert

zu 3:

pointer sind keineswegs überholt ... sie haben auch nix mit der geschwindigkeit zu tun ...

das thema vernünftig abzuhandeln dauert vermutlich länger, also schlage ich vor du suchst dir entsprechende texte darüber... gibts wie sand am meer ...

ein einfaches beispiel, was bezug auf arrays nimmt: dein rechner weiß gar nicht was ein array ist ... ein array ist im prinzip ein pointer auf das erste element ... aber was ist ein pointer?

stell es dir wie schubladen vor:

der hauptspeicher sei eine riesiege ansammlung von schubladen, die alle einzeln addressierbar(abrufbar) sind...

nun, was ist ein array? eine reihe von aufeinanderfolgenden schubladen

was ist ein array eintrag? ein zettel in einer schublade ...

was ist ein pointer? ein zettel auf dem die nummer der ersten schublade steht ...

aber auch einen pointer muss man irgendwo speichern ... z.b. in einer andern schublade... hat man nun eine schublade in der so ein pointerzettel liegt, sagen wir mal die nummer der schublade kennen wir als "ptr", dann heißt "*ptr" ... nehme den zettel der in schublade ptr liegt, und gehe zu der schublade die auf dem zettel genannt ist ...

wenn du dagegen schreibst "&ptr" heißt das platt ausgedrückt: nehme die schublade ptr und schreibe ihre nummer auf einen zettel ...

ich hoffe das hilft dir ein wenig ...
 
sehr schöner Erklärungsversuch, GrafZahl.

@Forks:
"modernere" Sprachen haben einfach keine Pointer mehr, weil dort Interpreter oder Compiler automatisch erkennen (sollen), wie Speicher z.B. zu addressieren ist bzw. in manchen Sprachen wird automatisch entschieden, wann eine Instanz eines Objekt geklont wird und wann nur eine Referenz auf das Objekt nötig ist (also ein Pointer) und es wird genauso automatisch entschieden, wann Daten nicht mehr benötigt und Speicher somit freigegeben werden kann. (wie gut oder schlecht das funktioniert, ist von Sprache zu Sprache unterschiedlich)
C++ hat keine automatische Speicher-Verwaltung, keinen GarbageCollector, etc. - du bist komplett selbst dafür zuständig, wie du deinen Speicher sauber hälst und dies ist Fluch und Segen zugleich: auf der einen Seite hast du wesentlich mehr Arbeit, auf der anderen Seite kannst du sehr gut speicher-optimiert programmieren, weil du selbst genau bestimmst, wann eine Variable wieviel Speicher belegt und wann du diesen Speicher freigibst - das hat z.B. gerade in der MicroController-Programmierung große Vorteile, weil man so oftmals recht schmale Ressourcen sehr effizient nutzen kann.
 
Danke ihr Beiden, meine Fragen sind dadurch nicht gänzlich beantwortet und neben dem "Auf Antworten warten" hier lese ich auch, nur leider beschränken sich die meisten Seiten darauf, zu erklären, wie man was mit Pointern macht. Nicht jedoch, warum.

Aus Chakkys Link mal folgenden Satz:
...Sie sprechen Sie normalerweise über den Variablennamen an, so wie Sie das Auto auch über das Kennzeichen erreichen können. Sie können aber auch eine Zeigervariable definieren, die einer Magnetkarte entspricht. Wenn Sie der Zeigervariablen die Adresse einer Variablen zuweisen, können Sie über die Zeigervariablen auf diese Variable zugreifen.
Ich habe also zwei Möglichkeiten, Pointer und den Namen der Variable. Warum Pointer?

Und inwiefern erhöhen Pointer die Dynamik? Ich kann einen Pointer umbiegen, ich kann sicher auch was anderes umbiegen, was den selben Effekt hätte.

Zu meiner zweiten Frage, die mit
Code:
void quicksort([B]int *a[/B], int left, int right) 
...
int values[] = {3, 9, 2, 0, 5, 18, 11, 4, 77, 16}; 
....
quicksort(values, 0, count - 1);

Ich verstehe, dass da ein Pointer auf ne Int erzeugt wird. Ich weis aber immernoch nicht, wozu ^^. Also..vll. liegt das echt an meiner Java-Erfahrung, dass sich mir das nicht erschließt.

An die Funktion quicksort() wird der int-array values übergeben. Die Funktion erwartet aber ein int*. Gut, das scheint so zu passen, die Funktion hat dann am Ende wohl das erste Element vom Array...

Aber warum mit nem Pointer? In Java hätte ich, wenn ich mich recht erinnere, sowas geschrieben: void quicksort(int[] a...)


Edit: Ah, Beavis, hast noch geantwortet während ich am Schreiben war:
Ok..das klingt schonmal ein wenig einleuchtend. Java macht also Dinge im Hintergrund für mich, die ich in C++ selber machen muss. Ich nehme dann mal an, dass auch Java mit so etwas wie Pointern arbeiten muss um überhaupt zu wissen wo was im Speicher steckt...

Also...wenn Pointer keine Eigenheit sind, sondern eine Notwendigkeit, die man nur bei manch anderen Sprachen nicht zu Gesicht bekommt, dann bin ich im Verständnis schonmal ein großes Stück weiter :wink:

Gruß, Forks

Edit: Kurz weitergedacht, ohne zu googlen: Stark simplifiziert killt ein Garbage-Collector einfach alles im Speicher, was nicht mehr durch sowas wie Pointer referenziert wird? Das heist, wenn ich ein fauler C++-Programmierer wäre, würde einfach mein Speicher immer überfüllter weil ich nichts dereferenziere und es somit nicht überschrieben werden darf?
 
Zuletzt bearbeitet:
ein garbage collector kann auf unterschiedlichste art und weise funktionieren, aber auch hier liegst du recht nahe an der üblicherweise bevorzugten methode ...

man zählt mit wieviele pointer auf eine variable/ein objekt/einen speicherplatz zeigen ... gehen diese pointer out-of-scope oder bekommen eine andere adresse zugewiesen, wird der zähler dekrementiert ... erreicht er 0 ist das objekt von nirgendwo mehr referenziert ... alternativ gibts auch methoden die root objekte kennen, die wiederum auf alle irgendwo im scope befindlichen objekte referenzen halten, etc ... bei diesen GCs kann man von den root elementen alle referenzen absuchen um alle elemente zu erkennen die noch benötigt werden ... .NET arbeitet nach diesem prinzip ...

bei manueller speicherverwaltung hast du auch wieder recht: wenn du dich nicht darum kümmerst die nicht mehr referenzierten speicherbereiche frei zu geben, müllt das programm den speicher zu ... stichwort memory-leak
 
@Forks: ach ja - bau dir mal als kleine Übung in C++ einen binären Suchbaum...

Beim Einfügen / Ändern / Löschen von Elementen in dem binären Suchbaum wirst du dann nicht drum heraum kommen, jede Menge Pointer umzubiegen.

Wenn man bedenkt, was da an so 'nem Pointer dann an verketteten Daten dran hängt und wie (relativ) elegant sich solche Einsortier-Geschichten dann gestalten, wenn man einfach ein paar Zeiger hin und her biegt, wirst du da sicherlich feststellen... ich kann dir gerne mal die Liste der qualitätssichernden Maßnahmen raussuchen, die ich im Rahmen meines Studiums für genau dieses Problem angelegt habe (also welche Sonderfälle man da alle beachten muss u.s.w.)
Bei so ner praktischen Anwendung macht das Zeiger-Umbiegen aber wirklich richtig viel Sinn und sogar Spaß (mir zumindestens, aber ich bin eh ziemlicher coding-enthusiast)
 
Ich will ja nicht so klingen, als würde ich behaupten, das Pointer nichts taugen. Ich verstehe sie nur noch nicht komplett, bzw. an welchen Stellen man sie benutzen sollte und drücke das mit ein wenig Argwohn aus. :wink:

Wenn du mir jetzt Code auf Studiengang-Niveau zeigst, verstehe ich den möglicherweise auch nicht, obwohl ichs natürlich versuchen würde. Da ich mir die ganzen Fragen aber erst seit heute morgen stelle, werde ich morgen im Büro erst noch mal ein paar Stunden was drüber lesen. Vll. hab ichs danach dann raus.

Wenn binäre Suchbäume als Einsteigeraufgabe durchgehen, versuche ichs mal.

Danke schonmal. :)
 
code auf zu hohem niveau sollte hier kein großes hinderniss sein ... hier gibts genug leute die dir fragen dazu beantworten können ... trial & error ist ein legitimes lernverfahren ...
 
Es muss ja gar nicht kompliziert sein.
Zum Beispiel gebe ich dir eine Liste "1, 1, 2, 3, 5, 8, 21, 33, 54". Wie speicherst du die am einfachsten ganz ohne Pointer?
Klar, in einem Array der Größe 9.
Jetzt möchte ich aber, dass du zwischen der 8 und der 21 eine 13 einfügt. Jetzt musst du leider das Array vergrößern (da wird die oft nichts anderes übrig bleiben, als ein neues Array der Größe 10 zu allokieren und das alte freizugeben) und dann alles, was hinter der eingefügten 13 steht, eins nach rechts schieben.
Wenn das aber eine Liste ist, die mit Pointern realisiert ist, musst du einfach nur den Pointer, der in dem Objekt mit der Zahl 8 steht, auf ein neues Objekt mit der Zahl 13 setzen und den Pointer in dem Objekt mit der Zahl 13 auf das Objekt mit der Zahl 21.
Der Nachteil ist eben, dass du nicht mehr in O(1) auf einzelne Elemente zugreifen kannst. In einem Array kann man einfach die Anfangsadresse nehmen und dann "(Index, zu dem man will) * (Größe der Elemente im Array)" rechnen und direkt schauen, was an dieser Adresse steht. Bei einer verketteten Liste weiß man nicht so genau, an welcher Stelle im Speicher welches Element tatsächlich steht.

Aber eine große Anwendung von Pointern kann ich ja noch nennen: Die Parameterübergabe "by reference".
Angenommen du liest einen Graphen mit den Straßen in Deutschland ein. Dann hast du eine Datenstruktur, die z.B. 1,2 gb groß ist im Arbeitsspeicher. Wenn du jetzt einer Methode, die z.B. einen Weg berechnet, deine Datenstruktur als Parameter geben willst, dann willst du wahrscheinlich nicht, dass die ganze Datenstruktur kopiert wird und die Methode mit einer Kopie arbeitet. Da übergiebst du doch lieber einen Zeiger, mittels dem die Methode deine Datenstruktur, die schon im Speicher ist, finden kann.


Ich bin auch kein Fan von der Darstellung mit Pfeilen und so. Diese Anschauung ist ja ganz nett, aber ich stelle mir das doch lieber weniger abstrahiert vor:
Wenn du "int a;" schreibst, dann kriegst du vom Betriebssystem eine Adresse (dabei ist dir ziemlich egal, wie diese Adresse tatsächlich aussieht), an die du dann 32 (oder 64) bit schreiben darfst.
Eine Variable "int* b;" ist eine Variable, die vom Typ "Pointer" ist und das heißt dann, dass sie speziell interpretiert wird: "&b" gibt den echten Wert der Variable zurück, während "*b" die Adresse, die in b steht ausliest und dann einfach das zurückgibt, was an dieser Adresse steht.
 
Code:
#include <cstdlib>
#include <stdio.h>
#include <unistd.h>
using namespace std;

int main(int argc, char** argv) {
    menu(argc);
}

void menu(int argc) {

    switch (argc) {
        case 1: { u1(); break; }
    }
}

void u1() {
    // Lass einen Countdown von 10 bis 1 ablaufen, jeweils mit ner Sekunde Pause
    for (int x = 10; x > 0; x--) {
        printf("%d \n", x);
        sleep(1000);
    }
}
(switch, weil das später ein Kommandozeilenparameter-"Menü" werden soll für 17 verschiedene Funktionen/Aufgaben)...

Das kompiliert nicht. (Netbeans + Cygwin, leeres Programm würde erfolgreich kompilieren)

Fehlermeldung:

Code:
main.cpp: In function `int main(int, char**)':
main.cpp:8: Fehler: »menu« nicht deklariert (erste Verwendung dieser Funktion)
main.cpp:8: Fehler: (Jeder nicht deklarierte Bezeichner wird nur einmal für jede Funktion, in der er vorkommt, gemeldet.)
main.cpp: In function `void menu(int)':
main.cpp:11: Fehler: »void menu(int)« vor Deklaration verwendet
main.cpp:14: Fehler: »u1« nicht deklariert (erste Verwendung dieser Funktion)
main.cpp: In function `void u1()':
main.cpp:18: Fehler: »void u1()« vor Deklaration verwendet

Was soll ich da noch deklarieren? :confused:
 
Zuletzt bearbeitet:
unter

Code:
using namespace std;
füge mal bitte diese zeilen ein:
Code:
void menu(int argc);
void u1();

hintergrund: du solltest funktionen deklarieren bevor du sie verwendest, oder deffinierst.
 
Ist das wieder sowas, was Java von alleine macht und C++ nicht? Oder ist nur mein Compiler zu pingelig eingestellt?
 
Fortgeschrittenere Sprachen wie Java oder C# gehen den Source beim kompilieren nicht nur einmal durch, sondern mehrmals. C++ hingegen ist so spezifiziert, dass sämtlicher Quellcode einmal durchgegangen wird, top to down. Somit kann er bei deinem Code in der main Funktion noch gar nicht wissen was das Symbol menu bedeutet, da er es noch nie gesehen hat.
Die beiden Zeile von GrafZahl sind Funktionsprototypen, die den Compiler darauf hinweisen, dass es eine Funktion mit dieser Signatur geben wird. Dadurch ist es möglich die Implementierung der Funktion erst unterhalb der Verwendung anzugeben.

mfg benediktibk
 
Ok, verstanden und danke :thumb_up:

Nächste Frage:

Ich soll eine Funktion schreiben, die mir alle 'Ä's und 'ä's durch 'Ae' und 'ae' ersetzt. Ich komme mit \x84-Zeug durcheinander.

Code:
void u4() {
//alle Ä's und ä's durch Ae's und ae's austauschen

    string satz = "HÄrr Bärt stänkert wägen der vielen Äs";
    
    for (int c = 0; c < satz.length(); c++) {
       
        if (satz[c] == '\x84') { // Ä, funktioniert

            string satz_s1 = satz.substr(0, c-1);
            string satz_s2 = satz.substr(c+1, satz.length());
            satz_s1.append("Ae");
            satz = satz_s1 + satz_s2;
        }

        if (satz[c] == '\x8E') { 
       // sollte eigentlich ein ä sein? funktioniert nicht

            string satz_s1 = satz.substr(0, c-1);
            string satz_s2 = satz.substr(c+1, satz.length());
            satz_s1.append("ae");
            satz = satz_s1 + satz_s2;
        }

printf("%s", satz.c_str());
    }

Ausgegeben wird dann:
HAerr Bärt stänkert wägen der vielen Aes

Laut einer Ansi-Hex-Tabelle ist
Ä = \xC4
ä = \xE4

Laut einer anderen ist
Ä = \x8E
ä = \x84

In meinem Programm funktioniert
Ä = \x84

....? Warscheinlich könnte ich den funktionierenden Hexcode für "ä" auch finden, aber...was wird denn da für ein Format/für eine Tabelle verwendet? Soll ich nach "Ansi Hex" suchen, oder nach Ascii, oder nach Ansi-C oder sowas?
 
Zuletzt bearbeitet:
Also ich kann dir nur sagen das, soweit ich weiß, der Hex Code für ä = e4 war. Ich hab das irgendwie so in errinerung. Warum ich das weiß, weiß ich auch nicht :)
 
Das kommt darauf an, was du fuer ein Encoding verwendest. Bei regulaerem ASCII sind nur zeichen bis 0x7F definiert und da gehoeren keine Sonderzeichen dazu. Wenn du jetzt also deinen Quellcode mitsamt dem von dir definierten String in z.B. UTF-8 abspeicherst, brauchen Zeichen wie Umlaute im allgemeinen zwei Bytes, die sich auch deutlich von den ANSI Codepoints unterscheiden. Wenn du den Quellcode als ANSI mit der Codepage, aus der du die Codierungen fuer die Umlaute entnommen hast, speicherst, sollte das aber funktionieren.

BTW, wo ich grade nochmal das mit 'pendantisch' ueberflogen habe... (Gerade) wenn man C lernt, sollte man seinen Code immer mit den GCC Flags
Code:
-Wall -Werror -pedantic
uebersetzen. Das fuehrt dazu, dass Warnungen als Fehler gewertet werden und auch Dinge, die der Compiler sonst durchgehen lassen wuerde, als Warnungen angezeigt werden und damit zum Fehler fuehren.
 
Zurück
Oben