Forum: Mikrocontroller und Digitale Elektronik Für was gehört der Pointer ?


von D. C. (joker89)


Lesenswert?

Hallo, ich versuche mir gerade ein Menü zu erstellen, davor will ich 
aber diesen Code verstehen lernen, ich komme besten willen nicht darauf 
für was der Pointer *menu_strings[MENU_ITEMS] gedacht ist ?

Gruß


#define MENU_ITEMS 4
char *menu_strings[MENU_ITEMS] = { "First Line", "Second Item", 
"3333333", "abcdefg" };


uint8_t menu_current = 0;
uint8_t menu_redraw_required = 0;
uint8_t last_key_code = KEY_NONE;


void drawMenu(void) {
  uint8_t i, h;
  u8g_uint_t w, d;

  u8g.setFont(u8g_font_6x13);
  u8g.setFontRefHeightText();
  u8g.setFontPosTop();

  h = u8g.getFontAscent()-u8g.getFontDescent();
  w = u8g.getWidth();
  for( i = 0; i < MENU_ITEMS; i++ ) {
    d = (w-u8g.getStrWidth(menu_strings[i]))/2;
    u8g.setDefaultForegroundColor();
    if ( i == menu_current ) {
      u8g.drawBox(0, i*h+1, w, h);
      u8g.setDefaultBackgroundColor();
    }
    u8g.drawStr(d, i*h, menu_strings[i]);
  }
}

void updateMenu(void) {
  if ( uiKeyCode != KEY_NONE && last_key_code == uiKeyCode ) {
    return;
  }
  last_key_code = uiKeyCode;

  switch ( uiKeyCode ) {
    case KEY_NEXT:
      menu_current++;
      if ( menu_current >= MENU_ITEMS )
        menu_current = 0;
      menu_redraw_required = 1;
      break;
    case KEY_PREV:
      if ( menu_current == 0 )
        menu_current = MENU_ITEMS;
      menu_current--;
      menu_redraw_required = 1;
      break;
  }
}


void setup() {
  // rotate screen, if required
  // u8g.setRot180();

  uiSetup();                                // setup key detection and 
debounce algorithm
  menu_redraw_required = 1;     // force initial redraw
}

void loop() {

  uiStep();                                     // check for key press

  if (  menu_redraw_required != 0 ) {
    u8g.firstPage();
    do  {
      drawMenu();
    } while( u8g.nextPage() );
    menu_redraw_required = 0;
  }

  updateMenu();                            // update menu bar

}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

D. Chung schrieb:
> ich komme besten willen nicht darauf
> für was der Pointer *menu_strings[MENU_ITEMS]

Das ist nicht ein Pointer, das ist ein Array aus Pointern.

von sebi707 (Gast)


Lesenswert?

Wie Rufus schon sagte ist menu_strings ein Array von Pointern. Genauer 
ein Array von char Pointer und ein char Pointer ist quasi ein string. In 
dem von dir gezeigten Code werden hier die verschiedenen Menüeinträge 
abgespeichert.

von D. C. (joker89)


Lesenswert?

Ja wenn ich noch dabei bin hab ich mich falsch ausgedrückt, aber warum 
das ganze ?Wie Pointer funktionieren das versteh ich,...Vill könntest du 
mir das erklären wieso ?

Gruß

von sebi707 (Gast)


Lesenswert?

Um das Ganze möglichst flexibel zu gestalten. Kein Mensch will die 
Menüeinträge wild verteilt in den Code werfen und Konstruktion wie
1
for( i = 0; i < MENU_ITEMS; i++ )
2
{
3
  if(i == 0)
4
  {
5
    d = (w-u8g.getStrWidth("First Line"))/2;
6
    u8g.drawStr(d, i*h, "First Line");
7
  }
8
  if(i == 1)
9
  {
10
    d = (w-u8g.getStrWidth("Second Item"))/2;
11
    u8g.drawStr(d, i*h, "Second Item");
12
  }
13
  // usw...
14
}
bauen. Bis aus den anzuzeigenden Text ist der Code zum Menüträge 
anzeigen offensichtlich identisch. Also packt man alle Einträge in ein 
Array und arbeitet dies ab.

von D. C. (joker89)


Lesenswert?

Danke für die schnellen Antworten, ja das mit dem Array ist mir ja klar 
aber mich stört der Stern ... es würde doch auch ohne Pointer gehen. Ich 
erkenne den Sinn dahinter nicht in diesem Beispiel, das ist mein Problem

würde jetzt

menu_strings[MENU_ITEMS] = { "First Line", "Second Item",...

dort stehen müsste es doch auch funktionieren ?

von Bitflüsterer (Gast)


Lesenswert?

D. Chung schrieb:
> Danke für die schnellen Antworten, ja das mit dem Array ist mir ja klar
> aber mich stört der Stern ... es würde doch auch ohne Pointer gehen. Ich
> erkenne den Sinn dahinter nicht in diesem Beispiel, das ist mein Problem
>
> würde jetzt
>
> menu_strings[MENU_ITEMS] = { "First Line", "Second Item",...
>
> dort stehen müsste es doch auch funktionieren ?

Nein. Das würde der Compiler mit einer Fehlermeldung wegen eines 
fehlenden Datentyps quittieren.

Schriebst Du jetzt, aber:

char menu_strings[MENU_ITEMS] = { "First Line", "Second Item",...

so erhälst Du damit ein Array, das nur aus einem MENU_ITEMS langem 
String besteht, versuchst aber das Array mit mehreren Strings zu 
initialisieren.

Nimm's mir bitte nicht übel, aber das ist das Paradebeispiel für eine 
Gelegenheit, auf C-Bücher zu verweisen.

von sebi707 (Gast)


Lesenswert?

Erstmal geht menu_strings[MENU_ITEMS] alleine sowieso nicht. Da fehlt 
noch der Typ, also wenn dann char menu_strings[MENU_ITEMS]. Und nein so 
funktioniert das nicht. Wenn du das so machst hast du ein Array in dem 
du MENU_ITEMS chars also einzelne Zeichen speichern möchtest. 
Tatsächlich möchtest du aber MENU_ITEMS strings in dem Array Speichern.

Hier noch einige Beispiele zu strings:
1
// Array aus chars. Text kann verändert werden
2
char str[] = "Bla Blupp";
3
4
// Pointer zu char. Der Text kann in diesem Fall nicht verändert werden weil es von einem string Literal initialisiert wurde
5
char* str = "Bla Blupp";
6
7
// Array von char Pointern. Text kann auch nicht verändert werden, siehe oben
8
char* str[] = {"Bla", "Blupp"};

von D. C. (joker89)


Lesenswert?

Ja der Datentyp muss natürlich dabei sein.

@Bitflüsterer

Ich glaub langsam komme ich dahinter bzw. hab wohl was übersehene

char menu_strings[] = { "First Line", "Second Item"}

würde doch ein Array aus 2 char s bilden.

char menu_strings[1]-> First Line
char menu_strings[2]-> Second Item

Würde eine Zahl darin stehen wie

char menu_strings[2] = { "First Line", "Second Item"}

würde das heißen das in menu_strings[2] -> "First Line", "Second Item" 
drin stehen würde ?

Und bei
#define MENU_ITEMS 4
char *menu_strings[MENU_ITEMS] = { "First Line", "Second Item",
"3333333", "abcdefg" };

Steht in

char *menu_strings[1] -> First Line
char *menu_strings[2] -> Second Item
char *menu_strings[3] -> 3333333
char *menu_strings[4] -> abcdefg


Ich hoffe ich bin auf dem richtigen Pferd ?

von Bitflüsterer (Gast)


Lesenswert?

> Ich hoffe ich bin auf dem richtigen Pferd ?

Nein. Durchaus nicht. Bitte lies ein C-Buch. :-)

von sebi707 (Gast)


Lesenswert?

Ich glaube du brauchst tatsächlich ein gutes C Buch...

Wenn du ein Array definierst besagt die Zahl in den eckigen Klammern wie 
viele Elemente dein Array haben soll. Man kann die Zahl auch weglassen 
dann kriegt das Array so viele Elemente wie du in den runden {} Klammern 
liefest. Wenn du später auf das Array zugreifst dann gibst du in den 
eckigen Klammern an auf welches Element zu zugreifen willst. Die Zählung 
beginnt auch bei 0 und nicht bei 1 wie in deinem Beispiel oben.

von D. C. (joker89)


Lesenswert?

sebi707 schrieb:
> Ich glaube du brauchst tatsächlich ein gutes C Buch...
>
> Wenn du ein Array definierst besagt die Zahl in den eckigen Klammern wie
> viele Elemente dein Array haben soll. Man kann die Zahl auch weglassen
> dann kriegt das Array so viele Elemente wie du in den runden {} Klammern
> liefest. Wenn du später auf das Array zugreifst dann gibst du in den
> eckigen Klammern an auf welches Element zu zugreifen willst. Die Zählung
> beginnt auch bei 0 und nicht bei 1 wie in deinem Beispiel oben.

Sebi707
@
Hä das selbe hab ich doch gerade gemeint ? Bis auf das mit der 0.

Beispiel

char a=menu_strings[1]

in a steht dann Second Item?

von sebi707 (Gast)


Lesenswert?

Nein. In einem char kannst du keinen kompletten String speichern. Du 
brauchst entweder ein Array oder einen Pointern. So muss das aussehen:
char* a=menu_strings[1];

Dann steht da drin "Second Item".

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

D. Chung schrieb:
> char a=menu_strings[1]
>
> in a steht dann Second Item?

Nein. Ein char ist ein einzelnes Zeichen.

von D. C. (joker89)


Lesenswert?

sebi707 schrieb:
> Nein. In einem char kannst du keinen kompletten String speichern. Du
> brauchst entweder ein Array oder einen Pointern. So muss das aussehen:
> char* a=menu_strings[1];
>
> Dann steht da drin "Second Item".

Weil ein char 8 bit lange ist... Sollte ich eigentlich wissen, bin schon 
zulange davon weg sry.

von Karl H. (kbuchegg)


Lesenswert?

Das hier
1
char *menu_strings[MENU_ITEMS] = { "First Line", "Second Item", "3333333", "abcdefg" }

baut im Speicher diese Struktur auf
1
  menu_strings
2
   +-------+             +-+-+-+-+-+-+-+-+-+-+--+
3
   |   o---------------->|F|i|r|s|t| |L|i|n|e|\0|
4
   +-------+             +-+-+-+-+-+-+-+-+-+-+--+
5
   |   o-----------+
6
   +-------+       |       +-+-+-+-+-+-+-+-+-+-+-+--+
7
   |   o-------+   +------>|S|e|c|o|n|d| |I|t|e|m|\0|
8
   +-------+   |           +-+-+-+-+-+-+-+-+-+-+-+--+
9
   |   o-----+ |
10
   +-------+ | |      +-+-+-+-+-+-+-+--+
11
             | +----->|3|3|3|3|3|3|3|\0|
12
             |        +-+-+-+-+-+-+-+--+
13
             |   +-+-+-+-+-+-+-+--+
14
             +-->|a|b|c|d|e|f|g|\0|
15
                 +-+-+-+-+-+-+-+--+

Ein Array von Pointern unter dem Namen menu_strings, wobei jeder 
einzelne der Pointer auf einen Text zeigt.

Man hätte natürlich auch ein 2D Array von chars machen können
1
char menu_strings[4][12] = { "First Line", "Second Item", "3333333", "abcdefg" };
2
3
   menu_strings
4
  +-+-+-+-+-+-+-+-+-+-+-+-+
5
  |F|i|r|s|t| |L|i|n|e|\| |
6
  +-+-+-+-+-+-+-+-+-+-+-+-+
7
  |S|e|c|o|n|d| |I|t|e|m|\|
8
  +-+-+-+-+-+-+-+-+-+-+-+-+
9
  |3|3|3|3|3|3|3|\| | | | |
10
  +-+-+-+-+-+-+-+-+-+-+-+-+
11
  |a|b|c|d|e|f|g|\| | | | |
12
  +-+-+-+-+-+-+-+-+-+-+-+-+
das hat aber den 'Nachteil', das alle 'Zeilen' des 2D Arrays gleich lang 
sein müssen. Was wiederrum Speicherverschwendung bedeutet, wenn 
gleichzeitig relativ lange und relativ kurze Texte in diesem Array 
gespeichert werden sollen. Denn die Anzahl der Spalten ist ja über das 
komplette 2D Array in allen Zeilen gleich und muss sich logischerweise 
am längsten zu speichernden Text orientieren.

In der Lösung mit dem Pointer Array brauch ich das nicht. Die Texte sind 
ja unabhängig vom Array. Das Array organisiert die nur insofern, dass es 
die Verweise enthält, wo die tatsächlichen Texte gespeichert sind. Und 
die können unabhängig voneinander beliebig lang sein.

: Bearbeitet durch User
von D. C. (joker89)


Lesenswert?

Okay danke, das leuchtet ein. Jetzt wenn ich dieses Thema weiter 
ausbauen will und noch Sub Menü s erzeugen will hätte ich anfangs das 
Array um eine Dimension erweitert, dann war mein Gedanke dazu das ich 
Plätze im Array verschwende, weil ich unterschiedliche Sub menues evtl 
habe...

Meine zweite Lösung besteht aus mehreren Switch case bzw genau der 
Anzahl der main menues, damit wäre ich in der Anzahl der unter Menues 
variable.
Das wird aber irgendwann unübersichtlich denk ich.

Jetzt könnte ich mir das auch mit Pointer vorstellen, das wäre die 
elegantere Lösung oder ?Das wäre dann doch eine Pointer auf Pointer 
Struktur ?

Gruß

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Der ganze Ansatz ist schon zweifelhaft. Denn ein Menü besteht ja nicht 
nur aus Texten. Spätestens wenn du noch Submenüs haben willst und ev. 
sogar direkt Funktionen an Menüpunkte hängen willst, solltest du mal ein 
gutes C Buch konsultieren, welche Möglichkeiten zur Datenstrukturierung 
du noch hast. Ein Array ist beileibe nicht alles, was dir an 
Möglichkeiten zur Verfügung steht. Da gibt es zb noch die Struktur.

> Das wird aber irgendwann unübersichtlich denk ich.

Im Idealfall hast du EINEN Code, der ein Menü in all seinen Ausprägungen 
behandeln kann. Die Daten steuern die Menüabaerbeitung. Der Code 
interpretiert nur die Daten in der aufgebauten Datenstruktur. So ein 
Design beginnt damit, dass man sich auf Papier mit Bleistift die 
Datenstruktur anhand eines konkreten Beispiels aufmalt. Dann sieht man 
schon, wie man das ganze strukturiert.

Aber wenn dir das Pointer Array schon kopfzerbrechen bereitet hat, 
solltest du dir vielleicht erst mal die Latte nicht so hoch legen, bis 
du deine Programmiersprache nicht nur rudimentär beherrscht sondern 
tatsächlich mit den Sprachmitteln auch umgehen kannst.

von embedded (Gast)


Lesenswert?

@KHB
Danke für die tolle Darstellung. Wie biegen sich die Zeiger, wenn ich 
keinen linearen Speicher, sondern RAM und ROM getrennt mit gleichem 
Adressbereich habe?

von Karl H. (kbuchegg)


Lesenswert?

embedded schrieb:
> @KHB
> Danke für die tolle Darstellung. Wie biegen sich die Zeiger, wenn ich
> keinen linearen Speicher, sondern RAM und ROM getrennt mit gleichem
> Adressbereich habe?

Im Prinzip genau gleich. Denn in der Darstellung ist das ja 
uninteressant. Du musst nur im Hinterkopf behalten was in welchem 
Speicher liegt (kann nicht schaden, wenn man sich das in der Zeichnung 
markiert) und je nach Compiler dann eben die entpsrechende Umsetzung in 
den C Code machen. Je nach Compiler ist das unterschiedlich. Bei manchen 
kommt dann eben zb ein 'const' in die Datenstruktur rein, bei anderen 
wieder gibt es spezielle Zugriffsfunktionen für zb den ROM Bereich.
Aber grundsätzlich fürs Design ist das erst mal recht uninteressant. In 
der Zeichnung ist ein Pointer nichts anderes als ein Pfeil, der in einem 
Kästchen anfängt und dessen Spitze irgendwo anders hinzeigt.

von D. C. (joker89)


Lesenswert?

Ich hab mir mal Gedanken dazu gemacht, ich denke der Ansatz wäre sehr 
einfach bezüglich weil ich in einem späteren Switch case die id abfragen 
kann sollte man zb. in dieser Menü Ebene was abfragen wollen.
Strukturen kenne ich noch, das wird aber jetzt eine Struktur der sich 
wohl ein Array befinden muss

struct menu{

    const char* Menu_name;     // Menue Name
    int id;                    // Menue ID,damit ich mich orientieren 
kann
    int Number;                // Anzahl der Submenues
    {
      "Submenue 1, id"       //Name des Submenues mit der ID
      "Submenue ....
    ......
    }
  }
}

Das Array würde dann so aussehen

menuEntry []={Submenue 1, id}

Jetzt stellt sich mir aber die Frage wie mache ich die Anzahl der Array 
s variable ich habe ja unterschiedlich viele Sub menues

menuSet []={SubSet_1, id}
menueHome  []={SubSet_2, id}
....

Wäre der Ansatz okay ? Ich weiß nur gerade einfach nicht wie ich mit dem 
Array umgehen muss...

Gruß

von D. C. (joker89)


Lesenswert?

Zum Array wäre mir noch was eingefallen, ich verpacke die arrays wieder 
in eine Struktur , dann wäre die Anzahl doch variable ?




struct menu{

    const char* Menu_name; // Menue Name
    int id;                // Menue ID,damit ich mich orientieren kann
    int Number;            // Anzahl der Submenues
    struct Sub_menue[];    //
  }
}

Und dann habe ich eine Struct in dem sich die Arrays finden mit zb dem 
Namen und wieder der ID

Damit hätte ich auch alle Parameter mitgeliefert die ich für ein Menü 
brauche

-Anzahl Submenues damit ich Anfang und Ende kenne.
-Id damit ich den Menue abschnitte kenne
-Und den Namen zur ausgabe am Display.

: Bearbeitet durch User
von D. C. (joker89)


Lesenswert?

Ich hab das mal runter getippt.



void Menues(void){

  struct menu{

    const char* Menu_name;  // Menue Name
    int id;                 // Menue ID , damit ich mich orientieren 
kann
    int Number;             // Anzahl der Submenues
    strukt Sub_menue [];
  }


struct menue Main =
{
  "Main",
  10,
  2,
  {
    {"Info", 100},
    {"back", 90},
  }
};

struct menu Data_Log =
{
  "Data Log",
  20,
  4,
  {
    {"Start", 200},
    {"Stop", 201},
    {"Settings", 202},
    {"back", 203},
  }
};



Danke schon mals

von sebi707 (Gast)


Lesenswert?

Was soll "strukt Sub_menue [];" sein? Da fehlt irgendwie ein 
Variablenname oder der Typ (je nachdem was Sub_menue sein soll). 
Außerdem ist struct falsch geschrieben. Compilierst du deinen Code gar 
nicht zwischendurch?

Hast du mitlerweile das C Buch von Anfang bis Ende gelesen und bist 
jetzt C-Meister und möchtest unbedingt flexible array member benutzen? 
Wenn nicht dann überleg dir bitte eine andere Speichermöglichkeit, die 
ohne unterschiedlich große Arays in structs auskommt. Z.B. irgendwas mit 
Pointern.

von D. C. (joker89)


Lesenswert?

Danke nett für deine Antwort, besonders nett der Hinweis das ich mich 
bei einem pseudo Code vertippt habe.

Es gibt auch noch Anfänger hier das sollte man nicht vergessen, zu dem 
sieht man bei mir das ich mir nicht die Arbeit machen lasse und selbst 
mit Ideen komme. C und C++ ist bei mir einfach auch schon mal wieder 
lange her!!!
Man kann sich auch anständig unterhalten oder ?

Und zum  strukt Sub_menue []; alias  struct Sub_menue [];

ja zu diesem Datentyp bin ich mir nicht wirklich sicher , zumal ich mir 
die Frage stelle ob das möglich ist.

Mit Stukturen zu arbeiten wäre mir auf anhieb einleuchtend, ich lass 
mich aber gern eines besseren belehren,..

Gruß

von sebi707 (Gast)


Lesenswert?

Der Grund warum hier mehrere zu einem C Buch raten ist, dass du 
offensichtlich Defizite bei wichtigen Themen wie z.B. Pointern hast. 
Viele deiner Antworten sind irgendwie hingeschriebener C Code oder wie 
du ja jetzt sagst Pseudo-Code. Ohne richtiges Verständnis welche 
Konstruktionen es in C gibt und welche nicht kommt man meistens nicht 
weit. In Pseudo-Code kann man theoretisch alles hinschreiben, aber ob 
das in C dann so umzusetzen ist, ist noch eine ganz andere Frage. Wenn 
ich mir ein Design überlege dann schreibe ich das direkt in der 
Programmiersprache hin, in der ich es auch umsetzen möchte und sehe dann 
spätestens beim Compilieren ob das so überhaupt funktioniert.

Um auf dein eigentliches Problem zurückzukommen: Du möchtest eine 
Menüstruktur mit Funktionen für einzelne Menüpunkte, sowie Untermenüs 
darstellen. Das Ganze soll auf einem µC laufen? Hast du nirgendwo 
erwähnt scheinbar. Arrays mit variabler Länge in structs gibt es zwar in 
C, ist aber eher ein spezial Feature und sollte man nicht unbedingt 
verwenden, da es immer anders geht. Der ursprüngliche Ansatz mit einem 
Array von char* war gar nicht so schlecht. Wenn man statt char* ein 
struct für ein Menüeintrag erstellt kommt man schon weiter. Ich habe mir 
jetzt in ein paar Minuten mal diese Struktur überlegt:
1
struct MenuItem
2
{
3
  const char* name;
4
  void (*func)(void);
5
  MenuItem* submenu;
6
};
7
8
// Beispielmenü
9
MenuItem submenu[] = {
10
  {"First submenu function", &sfunc1, NULL},
11
  {"Second submenu function", &sfunc2, NULL},
12
  {NULL, NULL, NULL}
13
};
14
15
MenuItem mainMenu[] = {
16
  {"First function", &func1, NULL},
17
  {"Submenu", NULL, submenu},
18
  {"Second function", &func2, NULL},
19
  {NULL, NULL, NULL}
20
};

Ein Menüeintrag besteht aus einem Namen, einem Functionpointer (bitte 
Nachlesen wenn dir das nichts sagt), sowie einem Pointer auf ein 
Untermenü. Falls beim Auswählen eines Menüpunktes eine Funktion 
aufgerufen werden soll trägst du die Funktion in den Functionpointer 
ein. Versteckt sich hinter einem Menüeintrag ein Untermenü dann trägst 
du den submenu Pointer ein. Die einzelnen Menüs werden dann wieder in 
Arrays gespeichert, mit einem abschließenden Menüeintrag, der nur aus 
NULL Einträgen besteht. Der NULL Eintrag markiert das Ende eines Menüs. 
Dadurch kann man einfach auf ein Untermenü verweisen und muss nicht auch 
noch die Länge irgendwo mitgeben.

von D. C. (joker89)


Lesenswert?

Bin gerade unterwegs und danke für deine Antwort, ich hab das ganze 
jetzt schnell überflogen.
Ganz kurz aber noch-->

 MenuItem mainMenu[] = {
  {"First function", &func1, NULL},
  {"Submenu", NULL, submenu},
  {"Second function", &func2, NULL},
  {NULL, NULL, NULL}
};


Main Menue ist ja der oberste Menuepunkt, bei den Submenues was wäre für 
dich jetzt eine First function und eine Second funktion?

Wenn ich die Hierarchie richtig verstanden habe
dann würde das in meinem Fall so aussehen -->

 MenuItem mainMenu[] = {
  {""Main"",Submenue_menue, NULL},
  {"Data Log",Submenue_Datalog, Null},
  {"Diagnostic",Submenue Diagnistic, NULL},
  {NULL, NULL, NULL}
};


MenuItem Submenue_Datalog[] = {
  {""Sub1",Sub11,&func0 },
  {" Sub2",Sub12,&func1},
  {" Sub3",Sub13,&func2},
  {NULL, NULL, NULL}

}


Ich versuch das jetzt mal umzusetzen, klingt alles noch logisch danke.

Gruß

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.