FileNamen werden nicht vollständig in ein array übernommen

Ich möchte eine SD-Karte vpn einem Adafruit MP3-Player auslesen und die Dateinamen in ein Array einlesen.
Das Auslesen der SD-Karte funktioniert, aber ein paar ArrayFelder übernehmen nicht den Dateinamen !?

/// Files in array einlesen
void FilesEinlesen(File pfad, int numTabs)
{
   int i=0;
   while(true)
   {
     File eintrag =  pfad.openNextFile();                       // Eintrag lesen
     if (! eintrag)                                             // wenn kein Eintrag da ist ...    
     {
       AnzeigeError(2);                                         // Fehlermeldung 2 - Keine Dateien
       break;                                                   // bzw. Ende der Files    
     }     
     FilesEinlesen(eintrag, numTabs+1);                         // File einlesen 
     if (! eintrag.isDirectory() && numTabs == 0)               // Verzeichnis und Systemdatei ausschließen
     {
       i++;
       //Serial.print(numTabs);
       //if (DEBUG == 1) Serial.print(i);
       //if (DEBUG == 1 )Serial.print(" - ");
       if (DEBUG == 1) Serial.print("eintrag.name :  ");   
       if (DEBUG == 1) Serial.print(eintrag.name());   
       mp3Text[i] = eintrag.name();   
       if (DEBUG == 1) Serial.print("  mp3Text[i] :  ");     
       if (DEBUG == 1) Serial.println(mp3Text[i]);   
     }
     maxFiles=i;
     eintrag.close();
   }
}

Der serielle Monitor gibt aus:

Bei 5 Einträgen sind die Arrayfelder leer. :astonished:
So habe ich das Array definiert:

String mp3Text[27];                                                     // maximal 27 Dateien

Wieso bleiben diese Felder leer? "Verschluckt" sich der Arduino UNO?

In diesem Zusammenhang noch eine Frage:
Kann ich die Dateinamen "alphabetisch" einlesen bzw. sortieren?

Auf einem Uno bleibt nicht viel RAM neben der SD Library. Da sind 27 Strings evtl. schon viel. (Je nachdem, was du sonst noch machst)
Mach mal

Serial.print(F("eintrag.name :  ")); 
...  
Serial.print(F("  mp3Text[i] :  "));

Wenn das schon eine kleine Änderung gibt, ist da vermutlich das Problem.

Zeig mal freeRam() an:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Arduino Playground - AvailableMemory
Verändert sich der Wert mit jedem Aufruf von  mp3Text[ i ] = eintrag.name();  ?

michael_x:
Auf einem Uno bleibt nicht viel RAM neben der SD Library. Da sind 27 Strings evtl. schon viel. (Je nachdem, was du sonst noch machst)
Mach mal
...
Wenn das schon eine kleine Änderung gibt, ist da vermutlich das Problem.

Ja es gab eine Änderung. Jetzt fehlen "nur" 2 Einträge.

michael_x:
Zeig mal freeRam() an:

F habe ich wieder rausgenommen und folgende Werte ermittelt;
Mit 27 Dateien (String mp3Text[27]; ) erhalte ich den Wert 319.
Wenn ich auf 25 Dateien reduziere fehlen mir immer noch Einträge. Wert ist nun 311.
Wenn ich auf 21 Dateien reduziere sind alle Einträge da. Der Wert ist nun 310.

michael_x:
Verändert sich der Wert mit jedem Aufruf von  mp3Text[ i ] = eintrag.name();  ?

Nein. Es fehlen jedesmal die gleichen Dateinamen.

Du scheinst Recht zu haben. Der Speicherplatz scheint das Problem zu sein, obwohl er mir beim complieren "nur" 75% verbrauchten Speicher meldet. Ich dachte durch die Definition von "String mp3Text[27]" hätte ich eine Reservierung des Speicherplatzes vorgenommen.
Zur "Not" komme ich auch mit 21 Dateien aus, 25 wären besser. Was löse ich mit dem "F" aus?

Gibt es eine "einfache Sortiermöglichkeit" des Array? Wenn ich jetzt noch eine Funktion dafür schreibe, dann habe ich hinterher wohl gar keinen Speicher mehr für die Dateinamen übrig.

Du scheinst Recht zu haben. Der Speicherplatz scheint das Problem zu sein, obwohl er mir beim complieren "nur" 75% verbrauchten Speicher meldet.

Das kommt davon wenn du nicht verstehst was im Hintergrund abläuft. Die String Klasse verwendet dynamischen Speicher. Zur Compile-Zeit werden da nur ein paar Byte pro Objekt für den Zeiger, die Länge des Strings und die Größe des Arrays angelegt. Der eigentliche Speicher für den Inhalt wird erst dynamisch zur Laufzeit belegt.

Was löse ich mit dem "F" aus?

F = Flash

Normal landen alle String Literale im RAM. Das ist durch die Harvard-Architektur des Prozessors bedingt. D.h. Flash und RAM haben getrennte Adress-Räume. Der Compiler unterstützt das nur bedingt und alle normalen Anweisungen greifen ins RAM. Man kann aber auch Klimmzüge unternehmen um Daten im Flash abzulegen. Das F() Makro tut das für String Literale. Das sollte man bei großen Sketches unbedingt überall verwenden.

Mit 27 Dateien (String mp3Text[27]; ) erhalte ich den Wert 319.
Wenn ich auf 25 Dateien reduziere fehlen mir immer noch Einträge. Wert ist nun 311.
Wenn ich auf 21 Dateien reduziere sind alle Einträge da. Der Wert ist nun 310.

Wenn dein Array kleiner wird, müsste eigentlic der freeRam größer werden. Ist evtl. in Wirklichkeit schon unter 0 :wink:

Wenn der Wert von freeRam ermittelt wurde, bevor die Strings tatsächlich zugewiesen wurden, reicht das wohl keinesfalls.

Verändert sich der Wert mit jedem Aufruf von mp3Text[ i ] = eintrag.name(); ?

Damit meinte ich eher:
verändert sich freeRam im laufenden Betrieb ?

mp3Text[ i ] = eintrag.name(); 
Serial.prinln(freeRam());

Wenn du, was löblich wäre, den Speicher gleich beim Compilieren festlegst, darfst du keine String Objekte nehmen sondern char arrays.

Und wenn du die sowieso umsortieren willst, wäre eine verkettete Liste besser als ein Array von Arrays.

z.B. sowas:

typedef struct FILECHAIN { char name[14]; struct FILELIST * next;} FileChain;
FileChain myFiles[32]; // belegen fest 32 * 16 byte = 512 byte
FileChain* first = NULL;
byte filecount = 0;

void setup() {
  myFiles[filecount].next = NULL; // damit der Compiler nichts wegoptimiert

}

void loop() {
/* Jeder neue Dateiname wird per strcpy nach myFiles[filecount++].name gebracht. 
 * Dann die Kette ab first absuchen auf deine Sortierreihenfolge, und in next einketten.

 *    Sortieren war schon inmmer beliebt, mindestens seit es Computer gibt. Viel Spass.
 */ 
}

Gerade bei Datei-Namen ist das überhaupt kein Problem. Man weiß die haben immer das 8+3 Format. Also immer 8 + 1 + 3 + 1 = 14 Bytes (inklusive Terminator). Dynamischer Speicher ist deshalb überflüssig. Das braucht man gerade wenn die Größe erst zur Laufzeit ermittelt wird.

Außerdem fällt dann der generelle Overhead durch die String-Klasse weg. Jedes Objekt verwaltet wie gesagt zusätzlich zum eigentlichen Text nochmal 6 Bytes:

	char *buffer;	        // the actual char array
	unsigned int capacity;  // the array length minus one (for the '\0')
	unsigned int len;       // the String length (not counting the '\0')

Serenifly:
Das kommt davon wenn du nicht verstehst was im Hintergrund abläuft.

Da hast Du völlig Recht !!! Und als Anfänger verstehe ich noch nicht einmal alles, was im Vordergrund abläuft. :confused: ... siehe unten ...

Serenifly:
Die String Klasse verwendet dynamischen Speicher. Zur Compile-Zeit werden da nur ein paar Byte pro Objekt für den Zeiger, die Länge des Strings und die Größe des Arrays angelegt. Der eigentliche Speicher für den Inhalt wird erst dynamisch zur Laufzeit belegt.

Wieder dazugelernt ! Ich dachte, der Speicher wäre damit schon fest geblockt.

Serenifly:
F = Flash

Normal landen alle String Literale im RAM. Das ist durch die Harvard-Architektur des Prozessors bedingt. D.h. Flash und RAM haben getrennte Adress-Räume. Der Compiler unterstützt das nur bedingt und alle normalen Anweisungen greifen ins RAM. Man kann aber auch Klimmzüge unternehmen um Daten im Flash abzulegen. Das F() Makro tut das für String Literale. Das sollte man bei großen Sketches unbedingt überall verwenden.

.... ist angekommen !

michael_x:
... Damit meinte ich eher:
verändert sich freeRam im laufenden Betrieb ?

Sorry :o Ja, er verändert sich.

Ohne Flash (Position 19 wird "verschluckt")

Mit Flash

michael_x:
Wenn du, was löblich wäre, den Speicher gleich beim Compilieren festlegst, darfst du keine String Objekte nehmen sondern char arrays.

Und wenn du die sowieso umsortieren willst, wäre eine verkettete Liste besser als ein Array von Arrays.

z.B. sowas:

typedef struct FILECHAIN { char name[14]; struct FILELIST * next;} FileChain;

....

Ähhh, ja dann verabschiede ich mich hier mal für mindestens eine Woche und versuche dies mittels googeln zu verstehen. "struct", "*", ... kenne ich (noch) nicht und stellen erst einmal ein Problem dar.
Aber ich möchte ja nicht (ganz) dumm sterben .....

Serenifly:
Außerdem fällt dann der generelle Overhead durch die String-Klasse weg.

Ich glaube Dir alles :-)) Jetzt wird's mir aber zu spanisch, sorry, Du überforderst mich.
Ich schaue mal was Michael_X mir beibringen wollte, vielleicht verstehe ich ja dann ....

Vielen Dank für Eure Unterstützung !!!

Aber ich möchte ja nicht (ganz) dumm sterben

struct generell solltest du zuerst lernen / rauskriegen / ausprobieren.

Das mit dem Zeiger ( * ) generell ist dann dein nächster Punkt.
Das mit dem Feld next in der struct und dem Sortieren kommt danach.
struct an sich ist nicht so schwierig, den Rest der Woche kannst du mit den weiteren Sachen verbringen.

Bei offenen Fragen ist das Forum hier manchmal sogar schneller als tüfteln.
Wir helfen gern ( nicht beim sterben, aber sonst schon :wink: )

Ach ja, das F() Makro geht übrigens nur bei print()/println()

"struct", "*", ... kenne ich (noch) nicht und stellen erst einmal ein Problem dar.

Solltest du dir bei Gelegenheit mal ansehen. Verkettete Listen sind bei deinen Kenntnissen zu hoch gegriffen, aber mit structs kann man sich selbst definierte Datentypen anlegen. Also mehrere Variablen in einer anderen Zusammenfassen. Und dann z.B. Arrays davon anlegen. Mit structs kann man auch einfach mehrere Variablen an eine Funktion übergeben. Es gibt viele Programme die damit übersichtlicher würden.

Ich glaube Dir alles :-)) Jetzt wird's mir aber zu spanisch, sorry, Du überforderst mich.

Overhead = zusätzlicher Aufwand der neben den eigentlichen Nutzdaten anfällt:

Du willst ja nur ein paar Dateinamen Speichern. Die String Klasse braucht aber pro Objekt nochmal 6 Bytes um damit umzugehen. Und ist auch ansonsten Speicher-intensiver (sowohl in Flash und RAM) als direkt C Strings zu verwenden. Das ist nicht immer schlimm, aber fällt auf wenn du sowieso schon wenig RAM frei hast.

Man könnte auch ein zwei-dimensionales char Array für die Strings anlegen. Und dann mit Insertion Sort o.ä. sortieren. Dazu muss man halt ständig Strings umkopieren. Nicht das effizienteste, aber es geht.

Verkettete Listen sind bei deinen Kenntnissen zu hoch gegriffen

Nicht dass er aufgibt.
Wenn er übermogen oder nächste Woche struct und pointer Experte ist, sind Verkettete Listen doch der ideale Anwendungsfall für Sortieren ohne Umkopieren. 8)

michael_x:
Bei offenen Fragen ist das Forum hier manchmal sogar schneller als tüfteln.
Wir helfen gern ( nicht beim sterben, aber sonst schon :wink: )

Super nett !!!!!!!!!!!!

"struct" habe ich schon kapiert. Man legt ein eigenes Datenobjekt an, was aus mehreren Variablen bestehen kann. Daher ist "char name[14]" die erste Variable in meinem Datenobjekt "FileChain".
Das in dem Datenobjekt ein weiteres Datenobjekt "FILELIST" mit * und next definiert ist, braucht noch was ..... Erster Gedanke: FILELIST ist ja gar nicht definiert, muss also was mit dem * und dem next zu tun haben .... Ich werde mal diverse Quellen suchen ...

Das ich mit dem Datenobjekt ein Array anlegen kann, habe ich auch geschluckt. Hier ein Array mit 32 Feldern. Die 16 Bytes bestehen aus 14 Bytes für "name[14]" und 2 Bytes für "FILELIST"?

Das Filechain mit dem * und first wird sich sicherlich klären, wenn ich die Frage oben beantwortet habe.

Und dann wage ich mich mal an strcpy mit first und next :slight_smile:

Wir reden hiervon:

  • ist ein Zeiger. Zeiger sind praktisch Adressen. Zeigt eben darauf wo was im Speicher steht. Bei einer einfach verketteten Liste zeigt jedes Element auf das nächste Element in der Liste. Der letzte Zeiger ist NULL, wodurch man weiß dass die Liste zu Ende ist. So kann man den Inhalt des Elements (hier den String) auf seinem Platz lassen und muss nur die Zeiger umhängen wenn man die Position des Elements in der Liste ändern möchte.

Hier mal einen Weg wie man das machen kann:

struct ListElement 
{ 
  char name[14]; 
  ListElement* next; 
};

const int LIST_SIZE = 32;

ListElement myFiles[LIST_SIZE];
ListElement* first;       //Zeiger auf Anfang der Liste
byte fileCount;           //Anzahl der eingefügten Datei-Namen

//Prototyp explizit, weil dass die IDE nicht selbst schafft (sie schreibt den Prototyp über die struct Deklaration wo dann das struct unbekannt ist)
void sortedInsert(ListElement** first, ListElement* element); 

void setup()
{
  Serial.begin(9600);

  resetList();

  addElement("AAAA.txt");
  addElement("aaaaa.txt");
  addElement("DDDD.txt");
  addElement("BBBB.txt");
  addElement("EEEE.txt");
  addElement("CCCC.txt");
  addElement("jjjj.txt");
  addElement("FFFF.txt");
  addElement("BAAA.txt");
  addElement("BZZZ.txt");
  addElement("Bggg.txt");
  Serial.println("unsortiert:");
  printList();

  sortList();
  Serial.println("sortiert:");
  printList();
}

void loop()
{
}

//Element hinten einfügen
void addElement(const char* name)
{
  if (fileCount < LIST_SIZE)
  {
    strlcpy(myFiles[fileCount].name, name, sizeof(ListElement::name));    //Inhalt kopieren
    myFiles[fileCount].next = NULL;
    
    //vorherigen next Zeiger auf aktuelles Element setzen
    if (fileCount > 0)
      myFiles[fileCount - 1].next = &myFiles[fileCount];

    fileCount++;
  }
}

//Liste zurücksetzen
void resetList()
{
  first = myFiles;
  first->name[0] = '\0';
  first->next = NULL;
  fileCount = 0;
}

void printList()
{
  ListElement* current = first;

  //Liste traversieren und ausdrucken
  while (current != NULL)
  {
    Serial.println(current->name);
    current = current->next;
  }

  Serial.println();
}

//von hier leicht angepasst: http://quiz.geeksforgeeks.org/insertion-sort-for-singly-linked-list/
void sortList()
{
  ListElement* sorted = NULL;
  ListElement* current = first;

  //Liste traversieren
  while (current != NULL)
  {
    ListElement* next = current->next;
    sortedInsert(&sorted, current);   //übergibt einen Zeiger auf die sortierte Liste und das aktuelle Element
    current = next;
  }

  first = sorted;
}

void sortedInsert(ListElement** first, ListElement* element)
{
  ListElement* current;

  //Fallunterscheidung für erstes Element
  if (*first == NULL || strcmp((*first)->name, element->name) >= 0)
  {
    //neuen Listenanfang setzen
    element->next = *first;
    *first = element;
  }
  else
  {
    current = *first;

    //richtigen Platz für jedes Element finden. Hört auf wenn der Nachfolger(!) eines Elements "größer" als das aktuelle ist
    while (current->next != NULL && strcmp(current->next->name, element->name) < 0)
      current = current->next;

    //neues Element zwischen aktuelles Element und Nachfolger des aktuellen Elements einfügen
    element->next = current->next;
    current->next = element;
  }
}

Liefert:

unsortiert:
AAAA.txt
aaaaa.txt
DDDD.txt
BBBB.txt
EEEE.txt
CCCC.txt
jjjj.txt
FFFF.txt
BAAA.txt
BZZZ.txt
Bggg.txt

sortiert:
AAAA.txt
BAAA.txt
BBBB.txt
BZZZ.txt
Bggg.txt
CCCC.txt
DDDD.txt
EEEE.txt
FFFF.txt
aaaaa.txt
jjjj.txt

Da sieht man auch mal wie man mit strcmp() alphabetisch sortieren kann (und warum die Funktion bei Gleichheit 0 zurück gibt). Beachte aber dass strcmp() auf Basis der ASCII Tabelle sortiert. Also kommen alle Groß-Buchstaben vor allen Klein-Buchstaben. Das ist nicht immer gut so.

Betrachte die Sortier-Funktion als Magie wenn du es nicht verstehst. Also einfach verwenden und darauf vertrauen dass es klappt. Ist etwas kompliziert und ich hätte mir das auch nicht auf die schnelle aus dem Arm schütteln können. Also habe es von einer Webseite kopiert und ganz leicht angepasst.
Da sind dann auch nicht nur Zeiger drin, sondern auch ein Zeiger auf einen Zeiger (weil eine Änderung am Zeiger in der aufrufenden Funktion sichtbar sein muss)

Im Prinzip ist es aber fast ein normaler Insertion Sort. Anders als bei einer Array-basierenden Implementierung muss man aber bei einer verketten Liste nicht jedes Element immer um 1 weiter verschieben. Sondern kann gleich die richtige Position suchen und dann nur die Zeiger anpassen.

Man legt sich einen Zeiger ListElement* sorted an der den Anfang der sortierten Liste darstellt. Dann übergibt man den Zeiger und danach jedes Element an eine weitere Funktion, die für dieses Element die korrekte Position bestimmt. Dazu wird die Liste traversiert bis man ein Element findest dessen Nachfolger größer als das neue Element ist. Dann wird das neue Element dazwischen eingefügt.

Das ursprüngliche Erstellen ist vielleicht etwas unorthodox. Wenn du im Internet nach verketteten Listen suchst findest du erst mal hauptsächlich Beispiele wie jedes Element dynamisch angelegt wird. Hier ist aber ein Array vorhanden. Also muss man beim Einfügen nicht über die Zeiger traversieren. Das geschieht erst beim Ausdrucken. Die Strings liegen also sequentiell im Speicher (mit den Zeigern dazwischen), aber beim Sortieren und der Ausgabe wird entsprechend der Verkettung herumgesprungen

Für Extra-Punkte macht man daraus noch eine Klasse :stuck_out_tongue:

Das meinte ich mit "schneller als selber tüfteln" :wink:

Der erste Schritt ist, den Unterschied zwischen ListElement und ListElement* zu verstehen.
Und wann der Adress-Operator & und wann der * verwendet wird.

Das sind verschiedene Datentypen ( Datenelement und Zeiger auf ein solches Datenelement )
Das gleiche wiechar(Ein Buchstabe) undchar*(Der Anfang eines Textes aus mehreren Buchstaben)

char Text[12] = "Hallo"; // Hier ist der eigentliche Speicherplatz, mit definiertem Inhalt
if (Text[0] == 'H') Text[0] = 'h'; // beachte die einzelnen Anführungszeichen ! 
char* cp = Text; // das ist das gleiche wie  & Text[0];  cp zeigt auf den Anfang des Texts 

if (*cp == 'h') {  } // wir vergleichen einen Buchstaben

Und dann gibt es natürlich z.B. auch ListElement** : Das ist ein Zeiger auf einen Zeiger auf ein ListElement.

Den übergibt Serenilfy's Beispiel an die Funktion sortedInsert. Damit du wirklich was zum Knobeln hast.

Eine Alternative/Modifikation zu Serenifly's Vorschlag wäre, die next Kette schon beim Aufbauen der Liste zu sortieren.
Ein neues Element wird im Array auf Platz i kopiert, dann wird sofort geprüft, welcher der letzte Vorgänger ist. Dort next auf das neue Element zeigen lassen und das wohin die Kette vorher gezeigt hat, also den Nachfolger, als next des neuen Elements nehmen.

void FilesEinlesen(File pfad, int numTabs)

Vorsicht Falle: Das ruft sich rekursiv auf.

Oh ja, gar nicht gesehen. Mit Rekursion muss man auf den kleinen Arduinos auch aufpassen, da jeder Funktionsaufruf RAM belegt um bestimmte Register zu sichern. Das lässt sich doch sicherlich iterativ mit Schleifen machen.

Nachtrag:
Hast du die Dateien überhaupt in mehreren verschachtelten Verzeichnissen? Wenn sie alle in einem Verzeichnis liegen braucht man das sowieso nicht. Andererseits wird dann auch keine Rekursion gemacht und es schadet nichts.

Ok, die Pointer zeigen auf einen Speicherplatz. So weit ist das begreifbar. Der Umgang damit - puuuh - muss wohl anständig geübt werden. Obwohl .... ich habe den Sinn noch nicht verstanden.
Jetzt brauche ich für 21 Einträge im Array, auch noch 21 Speicherplätze für die Zeiger. Also MEHR Speicher. Wo liegt da der Sinn? Geschwindigkeit?

Serenifly:
Hast du die Dateien überhaupt in mehreren verschachtelten Verzeichnissen? Wenn sie alle in einem Verzeichnis liegen braucht man das sowieso nicht. Andererseits wird dann auch keine Rekursion gemacht und es schadet nichts.

Nein, die Dateien liegen alle im Root-Verzeichnis. Sie werden einmalig bei Start des Arduinos im setup eingelesen. Das Array dient nur zur Anzeige und Auswahl des gewünschtes Files.

Wobei wir bei einem neuen Thema wären. Die ausgewählte Datei wird mit mp3Player,play("xyz.wav") aufgerufen. Funktioniert auch einwandfrei. Versuche ich jetzt die Formulierung mp3,player(Dateiliste[5]) ein File abzuspielen, meckert der Compiler. "No matching funkction for call ...". Er sagt mir auch, dass er folgendes Format wünscht: mp3.Player(srting&). Meine Variable Dateiliste[] ist als String-array definiert. Lasse ich diese durch den seriellen Monitora anzeigen, erhalte ich auch "xyz.wav". Jetzt habe ich, was ich oben schon angesprochen habe: Der Umgang mit Pointern ... da fehlt es (noch :-). Wie bekomme ich meine Variable an den Pointer übergeben?

Jetzt brauche ich für 21 Einträge im Array, auch noch 21 Speicherplätze für die Zeiger. Also MEHR Speicher. Wo liegt da der Sinn? Geschwindigkeit?

Du vergisst dass du bei der String Klasse mehr Overhead hast. Ein Zeiger auf das Array. Und zwei ints um die Größe zu verwalten (also 6 Bytes + Array). Dazu kommt der generelle Overhead der durch dynamischen Speicher entsteht. Und das Problem, dass man dessen Verbrauch nicht beim Kompilieren sieht. Geschwindigkeit ist ein anderer Punkt. Die String Klasse ist auch ziemlich langsam, aber das ist hier gar nicht so wichtig.

Es geht darum dass du gesagt hattest du wolltest sortieren. Deshalb die Liste. Und nur deshalb. Größere Datenmengen zu sortieren hat das Problem dass man ständig Elemente umkopieren muss. Sowohl bei Divide and Conquer Algorithmen wie Quick Sort, aber besonders bei den einfacheren Algorithmen wie Insertion Sort oder Selection Sort.
In einer verketteten Liste hast du etwas Aufwand ein Element zu finden (da man ständig die Liste traversieren muss), aber das vertauschen einer Position ist simpel. Das Element bleibt an einem Platz und man ändert nur die Zeiger (d.h. die Reihenfolge der Verkettung).

Wie gesagt, wenn du es nicht im Detail versteht, ok. Das ist auch nicht trivial wenn man sich damit auskennt und schon mal Listen oder Sortier-Algorithmen programmiert hat. Du kannst trotzdem die Funktionen verwenden. Ist bei einer Library auch nicht anders. Da verwendest du auch nur fertige Funktionen und kümmerst dich nicht um die Implementierung.
Wenn du das Sortieren verstehen willst, schau dir erst mal an wie Insertion Sort abläuft. Und damit meine ich was gemacht wird. Nicht wie.

Aber du solltest verstehen was der Sinn der Sache ist. Wenn du nicht sortieren willst, kannst du auch ganz einfach ein zwei-dimensionales Array aus char nehmen und den jeweiligen String mit strcpy() oder strlcpy() auf den aktuellen Index kopieren (C Strings kann man nicht mit = zuweisen, da Array-Variablen letztlich nur Zeiger auf das erste Element sind!). Dann hast du keine String Klasse und der belegte Speicher wird gleich angezeigt.

Also einfach das:

char myFiles[32][14];

Das sind 32 C Strings zu je 14 Bytes. Die könnte man theoretisch auch sortieren, aber dann muss man halt ständig Strings kopieren.

Serenifly:
Dazu kommt der generelle Overhead der durch dynamischen Speicher entsteht. Und das Problem, dass man dessen Verbrauch nicht beim Kompilieren sieht.

An den Overhead hatte ich nicht gedacht

Serenifly:
In einer verketteten Liste hast du etwas Aufwand ein Element zu finden (da man ständig die Liste traversieren muss), aber das vertauschen einer Position ist simpel. Das Element bleibt an einem Platz und man ändert nur die Zeiger (d.h. die Reihenfolge der Verkettung).

Die Theorie der Zeiger ist schon angekommen. (erinnert mich an "Index" früher bei Pascal) Die Synatx macht es mir so schwer.

Serenifly:
Aber du solltest verstehen was der Sinn der Sache ist. Wenn du nicht sortieren willst, kannst du auch ganz einfach ein zwei-dimensionales Array aus char nehmen und den jeweiligen String mit strcpy() oder strlcpy() auf den aktuellen Index kopieren (C Strings kann man nicht mit = zuweisen, da Array-Variablen letztlich nur Zeiger auf das erste Element sind!). Dann hast du keine String Klasse und der belegte Speicher wird gleich angezeigt.

Dies wäre mein Ansatz gewesen, aber nun will ich das mit den Pointern in den Griff bekommen.

Kannst Du mir noch etwas zu meinem Problem sagen? Bin ich soweit schon im Thema?
Gefordert ist: mp3Player.play(String&) - (Diese Funktion wird übrigens durch eine Bibliothek zur Verfügung gestellt)
Die Funktion mp3Player.play erwartet den String& (also die Adresse eines Strings wegen "&"). Meine Array-Varaible hat den Wert ("xyz.wav"), der zu irgendeinem Wert der vielen Speicherplätze von String& passt. Soweit richtig?
Jetzt stehe ich auf dem Schlauch ... Wie sage ich denn der Funktion, dass der Wert meines Array-Elementes "xyz.wav" der benötigte "String&" - also Speicherinhalt - ist?

Die Theorie der Zeiger ist schon angekommen. (erinnert mich an "Index" früher bei Pascal) Die Synatx macht es mir so schwer.

Ja, das ist etwas kompliziert. Und Zeiger auf Zeiger macht es nicht einfacher.

int* var => Zeiger auf int
var => der Zeiger selbst
*var => worauf der Zeiger zeigt, d.h. der Inhalt der Variable. Nennt man Dereferenzierung

In dem Code siehst du dann noch oft "next->". Das ist eine Kurzversion von "(*next).". Also Dereferenzierung und Zugriff auf ein Element eines structs oder einer Klasse in einem Schritt

Es ist aber auch oft einfacher erst mal zu verstehen was gemacht wird. Vom Algorithmus her. Wenn man das weiß dann kann man auch den Code besser interpretieren.

mp3Player.play(String&)

Das ist eine Methode die eine Referenz auf einen String als Parameter hat (damit der String beim Aufruf nicht in ein neues Objekt kopiert wird. Siehe "call by reference"). Referenzen sind sind eng mit Zeigern verwandt. Es sind auch Adressen, aber man kann sie nicht ändern und sie können nicht NULL sein. Das macht sie weniger fehleranfällig (mit Zeigern kann man auch ganz schnell, ganz böse reinfallen).

Die Syntax ist etwas simpler. Man kann eine Variable da direkt übergeben ohne irgendwas zu machen:

String test = "blah.wav";
mp3Player.play(test);

Das explizite Nehmen der Adresse mit & wie bei Zeigern entfällt da.