PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : class mit übergebenen array Größen erstellen



HaWe
03.12.2018, 19:41
hi,
ich möchte eine class tMenu erstellen, die per tMenu::init(a,b) zur Laufzeit einen array tMenu::array[a][b] erzeugen kann.

In der Art:


class tMenu {

protected:

public:

tMenu () :
{ }

char CAPTLEN;
char MENULEN;

void init(char captlen, char menulen) {
CAPTLEN = captlen;
MENULEN = menulen;
}

char list[MENULEN][CAPTLEN];
};

//

klar geht das so nicht, wegen const für array-Dimensionen -
aber wie kriegt man es dennoch hin?

- - - Aktualisiert - - -

ich bin jetzt 1 Schritt weiter, über assert -
leider kann ich damit nur einen 1-dim array erstellen, keinen 2-dim.

wie geht das denn nun wohl...?




#include <cassert>

class tMenu {

protected:
char * list;
char MENULEN, CAPTLEN;

public:

tMenu (char menulen, char captlen) // constructor
{
assert(menulen > 0);
assert(captlen > 0);
list = new char[menulen];
// list = new char[menulen][captlen]; // alternat. >> error!
MENULEN = menulen;
CAPTLEN = captlen;
}


~tMenu() // destructor
{
// Dynamically delete the array we allocated earlier
delete[] list ;
}

};

shedepe
04.12.2018, 10:07
Du bist schon ganz gut in der richtigen Richtung unterwegs.

Ein multidimensional array in C++ ist eigentlich eine Liste von Pointer. D.h. list[0] z.B. liefert einen Pointer zurück, und auf diesem wird mit list[0][1] z.B. auf das 2. Element des Pointer zugegriffen.
Deshalb kann man wie in Variante 1 das mit einer Schleife initialisieren.

Variante 1


char * list;
list = new char[menulen * captlen]


Alternativ kann man auch einen außreichend großen Block Speicher allokieren.

Variante 2


char ** list;
list = new char*[menulen];
for(int i = 0; i < captlen; i++)
{
list[i] = new char[captlen];
}
//Zugriff auf stelle x, y
char val = list[x * captlen + y)



Nachzulesen gibt es das z.B. hier: https://stackoverflow.com/questions/936687/how-do-i-declare-a-2d-array-in-c-using-new

Ein Tipp außerdem: Solltest du das auf einem Mikrocontroller machen wollen solltest du auf new verzichten. Am PC würde ich auch auf die Arrays verzichten und stattdessen einen std::vector<std::vector<char>> list verwenden. Dann muss man nämlich den Speicher nicht von Hand aufräumen und hat dynamische Größen geschenkt bekommen.

HaWe
04.12.2018, 10:14
Du bist schon ganz gut in der richtigen Richtung unterwegs.

Ein multidimensional array in C++ ist eigentlich eine Liste von Pointer. D.h. list[0] z.B. liefert einen Pointer zurück, und auf diesem wird mit list[0][1] z.B. auf das 2. Element des Pointer zugegriffen.
Deshalb kann man wie in Variante 1 das mit einer Schleife initialisieren.

Variante 1


char * list;
list = new char[menulen * captlen]


Alternativ kann man auch einen außreichend großen Block Speicher allokieren.

Variante 2


char ** list;
list = new char*[menulen];
for(int i = 0; i < captlen; i++)
{
list[i] = new char[captlen];
}
//Zugriff auf stelle x, y
char val = list[x * captlen + y)



Nachzulesen gibt es das z.B. hier: https://stackoverflow.com/questions/936687/how-do-i-declare-a-2d-array-in-c-using-new

Ein Tipp außerdem: Solltest du das auf einem Mikrocontroller machen wollen solltest du auf new verzichten. Am PC würde ich auch auf die Arrays verzichten und stattdessen einen std::vector<std::vector<char>> list verwenden. Dann muss man nämlich den Speicher nicht von Hand aufräumen und hat dynamische Größen geschenkt bekommen.

danke,
ich wollte es tatsächlich auf einem ESP oder ARM machen, evtl. aber auch kompatibel zu einem Raspi.
(schön ntl, wenn es später auch auf AVRs läuft.)
vector kenne ich noch nicht, aber für später ist es wichtig, dass ich prinzipiell auf diese Weise den Zugriff habe auf 2-dim arrays, sowohl intern als auch dann von der aufrufenden Funktion:

this->char list[10][20]={"line0", "line1", "line2", "line3",..., "line9"};
Serial.println(this->list[k]);

tMenu mymenu(10,20);
strcpy(mymenu.list[5], "5 new testline", sizeof(testline));
mymenu.list[5][0]='>';

also Zugriff direkt auf die 2-dim Struktur, ohne immer rechnen zu müssen
x * captlen + y

Wie macht man es also am besten, cross-over-Plattform-kompatibel?

shedepe
04.12.2018, 11:13
Für den ESP32 kann ich leider nichts sagen.
Deshalb erläutere ich kurz die Überlegung dahinter warum man auf einem Mikrocontroller in der Regel keinen Speicher dynamisch allokieren möchte.
Das Problem dabei ist: Wenn man Speicher mit malloc oder new allokiert, wird dieser intern von der libc verwaltet. Diese schaut prinzipiell wo ist noch Speicher übrig bzw. wo ist genügend speicher am Stück übrig. Wenn man jetzt aber z.B. auf einem Atmega sehr wenig Speicher hat, wird dieser sehr schnell durchlöchtert wenn man häufiger Speicher reserviert bzw. wieder frei gibt. Die libc implementiert zwar Algorithmen um diesen Speicher möglichst intelligent zu verwalten, das funktioniert nur mit sowenig Speicher nicht so super bzw. kann sehr viel Performance klauen. Zudem kann man nicht zur compilezeit sagen ob der Speicher überhaupt reicht.

Bei größeren Controllern wie dem ESP32 kann es durchaus möglich sein, dass effizient mit dynamisch allokiertem Speicher umgegangen werden kann.

Ob es Sinn macht die Container aus der C++ Standardlib zu verwenden auf dem ESP32, müsstest du auch noch mal recherieren. Diese machen einem das Leben wesentlich leichter, kosten aber natürlich auch etwas mehr Speicher und CPU Leistung.

HaWe
04.12.2018, 11:55
neinnein, ich habe "nur" einen esp8266, alternativ M0, M3, M4, aber es wäre ntl schön auch auf dem Raspi und auch AVR Mega oder gar Nano: grundsätzlich eben Plattform-unabhängig.
Das mit den Löchern verstehe ich, aber es wird vorraussichtlich nicht so häufig passieren mit dem destructor, viel häufiger werden constructors nacheinander aufgerufen werden.

Was würdest du also vorschlagen für die Erzeugung von 2-dim arrays
- wichtig, wie gesagt, ist der direkte Zugriff auf alle einzelnen Speicherzellen beider Dimensionen, ohne so etwas wie
x * captlen + y
rechnen zu müssen,
so z.B. wie bereits oben erwähnt

tMenu mymenu(10,20);
strcpy(mymenu.list[5], "5 new testline", sizeof(testline));
mymenu.list[5][0]='>';

shedepe
04.12.2018, 12:47
Mein Vorschlag wäre: Mache eine Klasse, wie du sie jetzt schon hast. Lege aber die Instanzen des Klassen auf dem Stack an.
Also statt tMenu * mymenu = new tMenu(); lieber tMenu myMenuy;

Dann solltest du die Größen statt über den Konstruktor lieber als C++ Template übergeben. Dann können die zur Compilezeit ersetzt werden.
Dann kannst du dein array auch mit: char mylist[x][y] anlegen.

Beispiel


template <int x_size,int y_size>
class Menu{
private:
char list[x_size][y_size];



};

Verwendung:
Menu<10,20> myMenu;


Noch ein Hinweis zum Rechnen (Falls das mal relevant für dich werden sollte):
Einfach den Zugriff in einer Methode Kapseln die das Rechnen macht und dann ist das auch gar nicht so schlimm :)

HaWe
04.12.2018, 13:05
hmm, vielen lieben Dank, das klingt alles wirklich gut. Ich muss jetzt nur noch alles ineinander kriegen, tatsächlich ist dieses hier erst mein zweites C++ OOP Projekt überhaupt.

Erstmal, ist private besser als protected? Oder geht beides? Ist protected evtl flexibler für Zugriffe von "außen" und "innen"?

dann zu

Also statt tMenu * mymenu = new tMenu(); lieber tMenu myMenu;
ja: so hatte ich es ja auch vor als Instanz: tMenu myMenu;


Das wäre jetzt, was ich aus deinen Vorschlägen verstanden habe, mit int16_t als Kompromiss zw. char und int für die sizes,
allerdings völlig unklar wie der constructor jetzt aussehen muss , also wie genau ich den array samt seines Speicherbedarfs dynamisch erzeugen soll:




class tMenu{
private :

int16_t MENULEN, CAPTLEN;
char list[MENULEN][CAPTLEN];

public:

tMenu (int16_t menulen, int16_t captlen) // constructor
{
MENULEN = menulen;
CAPTLEN = captlen;

// .... und wie weiter hier?
}


~tMenu() // destructor
{
// Dynamically delete the array we allocated earlier
delete[] list ;
}

};

Edit:
zusätzlich rechnen möchte ich ja nicht, weder innerhalb der Klasse noch außerhalb bei Verwendung von Instanzen.
Instanziierung würde ich auch am liebsten ohne template machen, wenn möglich, einfach per

tMenu mymenu(10,20);

für solche Manipulationen in main oder loop:

strcpy(mymenu.list[5], "5 new testline", sizeof(testline));
mymenu.list[5][0]='>';

HaWe
04.12.2018, 17:08
wie muss denn also dafür der construcor für den "echten" dynamischen 2-dim array aussehen?

denn diese beiden Methoden


char * list;
list = new char[menulen * captlen]

char ** list;
list = new char*[menulen];
for(int i = 0; i < captlen; i++)
{
list[i] = new char[captlen];
}

//Zugriff auf stelle x, y
char val = list[x * captlen + y)

scheinen ja nur jew. einen 1-dim array zu erzeugen....?

HaWe
04.12.2018, 20:16
also dies funktioniert nicht



class tMenu {

protected:
int16_t MENULEN, CAPTLEN;
char list[MENULEN][CAPTLEN];


public:

tMenu (int16_t menulen, int16_t captlen) // constructor
{
MENULEN = menulen;
CAPTLEN = captlen;

list = new char*[menulen];
for(int i = 0; i < captlen; i++)
{
list[i] = new char[captlen];
}

}

};

exit status 1
invalid use of non-static data member 'tMenu::MENULEN'


so auch nicht:


class tMenu {

protected:
int16_t MENULEN, CAPTLEN;
char **list;

public:

tMenu (int16_t menulen, int16_t captlen) // constructor
{
MENULEN = menulen;
CAPTLEN = captlen;

list = new char*[menulen];
for(int i = 0; i < captlen; i++)
{
list[i] = new char[captlen];
}

}
};

tMenu mymenu(20,10);
//...
char test[15]="5 newline_";
strcpy(mymenu.list[5], test);


exit status 1
'char** tMenu::list' is protected


dies funktioniert offenbar, von außen aufgerufen



class tMenu {

protected:
int16_t MENULEN, CAPTLEN;


public:
char **list;
tMenu (int16_t menulen, int16_t captlen) // constructor
{
MENULEN = menulen;
CAPTLEN = captlen;

list = new char*[menulen];
for(int i = 0; i < captlen; i++)
{
list[i] = new char[captlen];
}

}


~tMenu() // destructor
{
// Dynamically delete the array we allocated earlier
delete[] list ;
}

};


tMenu mymenu(20,10);

void setup(void)
{
// Start Serial

Serial.begin(115200);
delay(2000); // wait for Serial()
Serial.println("Serial started");

char test[15]="5 newline_";
strcpy(mymenu.list[5], test);
Serial.println(mymenu.list[5]);
mymenu.list[5][0]='>';
Serial.println(mymenu.list[5]);
//
}


- - - Aktualisiert - - -

was allerdings nicht funktioniert, ist die Benutzung der array Zellen intern,
z.B. per init cstrings reinkopieren:



class tMenu {

protected:
int16_t MENULEN, CAPTLEN;
char buf[20];


public:
char **list;
tMenu (int16_t menulen, int16_t captlen) // constructor
{
MENULEN = menulen;
CAPTLEN = captlen;

list = new char*[MENULEN];
for(int i = 0; i < CAPTLEN; i++)
{
list[i] = new char[CAPTLEN];
}
}

void init() {
for(int i=0; i<MENULEN; i++) {
sprintf(buf,"%d line %d", i);
strcpy(list[i], buf);
}
}


~tMenu() // destructor
{
// Dynamically delete the array we allocated earlier
delete[] list ;
}

};

//
tMenu mymenu;
mymenu.init();





Exception (29):
epc1=0x40205d7b epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000

ctx: cont
sp: 3fff0250 end: 3fff0550 offset: 01a0

>>>stack>>>
3fff03f0: 00000022 3ffef4fc 00000028 402025a3
3fff0400: 3ffe9d80 4010560e feefeffe feefeffe
3fff0410: feefeffe feefeffe 00000000 4000050c
3fff0420: 3fffc278 40105464 3fffc200 00000022
3fff0430: 3fff0440 feefeffe feefeffe feefeffe
3fff0440: 402040f9 00000030 0000001e ffffffff
(usw...)
<<<stack<<<

- - - Aktualisiert - - -

auch so geht es nicht:

void init() {
for(int i=0; i<MENULEN; i++) {
sprintf(buf,"%d line %d", i);
strcpy(list[i*CAPTLEN], buf);
}
}

wie muss man jetzt stattdessen this->init() richtig codieren?

HaWe
05.12.2018, 09:54
also noch mal 1 Schritt zurück:
offenbar lässt sich list[MENULEN][CAPTLEN] intern noch nicht richtig ansprechen, indem man Werte für die Zellen , z.B. cstrings, in die Zellen reinkopiert, genau wie man es von außen per Aufruf einer Instanz können soll:


strcpy(mymenu.list[5], test);
Serial.println(mymenu.list[5]);
mymenu.list[5][0]='>';
Serial.println(mymenu.list[5]);

wie muss man intern in der class einen 2-dim array
list[MENULEN][CAPTLEN]
erzeugen, damit man auch intern mit Objekt-Methoden seine Zellen einzeln ansprechen und manipulieren kann?
Noch nicht einmal mit "rechnen"
list[i*CAPTLEN]...
geht es ja bisher...?





class tMenu{
private :

int16_t MENULEN, CAPTLEN;


public:

tMenu (int16_t menulen, int16_t captlen) // constructor
{
MENULEN = menulen;
CAPTLEN = captlen;

// .... wie ist public list[MENULEN][CAPTLEN] zu erzeuen? }


};

HaWe
05.12.2018, 18:07
niemand eine Idee, warum die Funktion
mymenu.init();
oben die runtime errors produziert,

warum man anscheinend damit keine strings intern kopieren/initialisieren kann,

warum selbst beim "rechnen" per
list[i*CAPTLEN]...
der runtime error passiert,

und wie man stattdessen korrekt einen einen intern adressierbaren und beschreibbaren 2-dim array konstruieren kann?

shedepe
05.12.2018, 18:44
Testest du das am PC? Dann kompilier es mit Debug informationen (-g bei gcc) und führe es mit einem Debugger aus. Dann siehst du genau das Problem.


char ** list;
list = new char*[menulen];
for(int i = 0; i < captlen; i++)
{
list[i] = new char[captlen];


Dann solltest du auch sehen, dass du in der For Schleife die falsche Variable verwendest ;)

for(int i = 0; i < captlen; i++)

zu:
for(int i = 0; i < menulen; i++)

HaWe
05.12.2018, 19:34
danke,
habe es geändert,
trotzdem sofort nach mymenu.init wieder der selbe runtime error mit der exeption



Exception (29):
epc1=0x40205d63 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000

ctx: cont
sp: 3fff0280 end: 3fff0560 offset: 01a0

>>>stack>>>
3fff0420: feefeffe 00000028 3ffef250 402025a1
3fff0430: feefeffe feefeffe feefeffe feefeffe
3fff0440: feefeffe feefeffe feefeffe feefeffe
3fff0450: feefeffe feefeffe feefeffe feefeffe




tMenu mymenu(20,10);

void setup(void)
{

Serial.begin(115200);
delay(2000); // wait for Serial()
Serial.println("Serial started");

char test[15]="5 newline_";
strcpy(mymenu.list[5], test);
Serial.println(mymenu.list[5]);

mymenu.list[5][0]='>';
Serial.println(mymenu.list[5]);
Serial.println();
for(byte i=0; i<10; i++) {
Serial.print("-");
Serial.println(mymenu.list[i]);
}

// bis hierhin no problem


mymenu.init(); // >>> runtime error, exeption
// ...
}






class tMenu {

protected:
int16_t MENULEN, CAPTLEN;
char buf[20];


public:
char **list;
tMenu (int16_t menulen, int16_t captlen) // constructor
{
MENULEN = menulen;
CAPTLEN = captlen;

list = new char*[MENULEN];
for(int i = 0; i < MENULEN; i++)
{
list[i] = new char[CAPTLEN];
}
}

void init() {
for(int i=0; i<MENULEN; i++) {
sprintf(buf,"%d line %d", i);
strcpy(list[i*MENULEN], buf);
}
}


};




(habe keinen debugger, nur die Arduino IDE)

shedepe
05.12.2018, 20:22
strcpy(list[i*MENULEN], buf);

Warum mal *MENULEN?
Du schreibst damit meilenweit aus deinem Array aus.
strcpy(list[i], buf);

Dein Array ist ja eigentlich eine Liste von char pointern.
Für jeden char pointer allokierst du speicher. Und dann willst du den Speicher der von dem Char Pointer an der Stelle i allokiert wird mit Text beschreiben.

Eine Sache außerdem noch: Du solltest aufpassen, dass dir nicht der Speicher ausgeht. Das sind dann nämlich wirklich komische Fehler und die sind in der Konstruktion nicht zur Compilezeit zu erkennen.

HaWe
05.12.2018, 20:40
der array geht doch so:
list[MENULEN][CAPTLEN],
also muss ich doch alle Zellen von 0<i<MUNULEN mit den cstrinds beschreiben?
ohne die i*MENULEN hat er aber denselben Fehler erzeugt - muss ich nochmal kontrollieren...

ich habe den esp8266, der müsste zumindest jetzt noch genug Speicher für 1 solches Menü haben.

Moppi
05.12.2018, 20:40
Ich hab versucht, dem zu folgen. Habe aber den Eindruck, dass die Arrays nicht richtig gehandhabt werden. Was mir auch nicht ganz klar war: warum für einen Index der Datentyp "char" und nicht "int" verwendet wird.
Deshalb habe ich einen Sketch erstellt, um auch ein Array zur Laufzeit zu erstellen.


int MENULEN=2;
int CAPTLEN=32;
char buf[]="Hallo";
int i=2;
int a=0;




void setup() {


Serial.begin(115200);
delay(1000);






}


void loop() {
char list[MENULEN][CAPTLEN];
if(a==0){
strcpy(list[i], buf);
a++;
}


Serial.println(list[i]);
delay(1000);
}

Zumindest so funktioniert es. Bloß ob das jetzt im Sinne des Erfinders (HaWe) war?

MfG

HaWe
05.12.2018, 20:45
update
stimmt immer noch, gerade kontrolliert:
selber runtime error auch für



void init() {
for(int i=0; i<MENULEN; i++) {
sprintf(buf,"%d line %d", i);
strcpy(list[i], buf); // editiert
}
}

- - - Aktualisiert - - -

Zähler ist ggf auch char, weil es nur von 0 bis 20 geht maximal

- - - Aktualisiert - - -

@moppi:
ja, so mit der externen Funktion ging es bereits vorher.
Das Problem ist ja gerade, es per OOP-Methode intern zu machen, genau solche r/w Zugriffe brauche ich später noch öfters .

Moppi
05.12.2018, 22:52
Wie kommst Du auf einen Runtime Error?

Hier mal die Ausgabe, die ich mit Deinem Code habe, das "Hallo" habe ich in loop() eingefügt, er schreibt es immer wieder hin, das Programm arbeitet also weiter:


Serial started
5 newline_
> newline_


- 8245
-⸮O⸮⸮


-s⸮⸮


-[⸮⸮


-⸮⸮⸮


-> newline_
-⸮⸮*⸮


-οy⸮


-⸮⸮


-⸮}⸮t


Hallo
Hallo
Hallo
Hallo





Wenn ich die init() ändere:



void init() {
for(int i=0; i<MENULEN; i++) {
sprintf(buf,"%d line %d", i);
strcpy(list[i], buf); //i*MENULEN
}


kommt das raus:



0 line 8245
1 line 8245
2 line 8245
3 line 8245
4 line 8245
5 line 8245
6 line 8245
7 line 8245
8 line 8245
9 line 8245


Jedenfalls kein Error und kein Abbruch bei mir.

HaWe
05.12.2018, 23:18
manno, du hast Recht, danke!
Vor lauter indices schwirrt mir schon der Schädel.
Auch ich kriege jetzt

0 line 126
1 line 65280
2 line 65280
3 line 65280
4 line 65280
5 line 65280
6 line 65280
7 line 65280
8 line 65280
9 line 65280

und es muss ntl heißen
sprintf(buf,"%d line %d", i, i);

dann steht da auch
0 line 0
1 line 1
2 line 2
3 line 3
4 line 4
5 line 5
6 line 6
7 line 7
8 line 8
9 line 9


vielen Dank nochmal! 8)

Moppi
06.12.2018, 06:31
Dennoch verstehe ich nicht, wie es zum Runtime Error kam? - Du hast es doch in den Code rein geschrieben? Oder war das nur, um etwas Verwirrung zu stiften? ;)

Davon abgesehen, shedepe hatte schon was von falschen Variablen in der Schleife geschrieben und mir war das auch aufgefallen, dass die Handhabung der Arrays irgendwie etwas konfus war. Hatte ja Gestern, 20.40 Uhr geschrieben: "Habe aber den Eindruck, dass die Arrays nicht richtig gehandhabt werden." Das hast Du dann wohl irgendwie übersehen ...

MfG

HaWe
06.12.2018, 09:55
es war ein copy- und paste-Error.
In dem kopierten neuen Code stand zuerst
strcpy(list[i*CAPSLEN], buf);
und auch mal
strcpy(list[i*MENULEN], buf);

aber dann statt
strcpy(list[i], buf);

fälschlicherweise dann durch falsches copy+paste und falsches heraus-löschen
strcpy(list[MENULEN], buf);
und hier beim letzten gabs NATÜRLICH wieder einen Fehler

Moppi
06.12.2018, 10:33
Ach Du liebe Zeit, oh oh oh ;)

Hauptsache es funktioniert mal jetzt.

Aber das sind eben so Sachen, die man immer im Kopf haben muss: habe ich die Funktion richtig angewendet, sind Parameter richtig übergeben, Variablen entsprechend deklariert und sind die auch definiert, sehe ich Schreibfehler im Code.

MfG
:)

HaWe
06.12.2018, 10:57
tja, nach 36 Stunden erfolglosem rumprobieren... und dann ist man ja oft eh gegenüber eigenen Fehlern betriebsblind... ;)