Standardwerte in einer C-Struktur

Lesezeit: 6 Minuten

Benutzeravatar von Arthur Ulfeldt
Artur Ulfeldt

Ich habe eine Datenstruktur wie diese:

struct foo {
    int id;
    int route;
    int backup_route;
    int current_route;
}

und eine Funktion namens update(), die verwendet wird, um Änderungen darin anzufordern.

update(42, dont_care, dont_care, new_route);

das ist wirklich lang und wenn ich der Struktur etwas hinzufüge, muss ich JEDEM Aufruf von update( … ) ein ‘dont_care’ hinzufügen.

Ich denke darüber nach, ihm stattdessen eine Struktur zu übergeben, aber das vorherige Ausfüllen der Struktur mit ‘dont_care’ ist noch mühsamer, als es nur im Funktionsaufruf zu buchstabieren. Kann ich die Struktur irgendwo mit Standardwerten von dont care erstellen und einfach die Felder festlegen, die mir wichtig sind, nachdem ich sie als lokale Variable deklariert habe?

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

Was ist der eleganteste Weg, um nur die Informationen, die ich ausdrücken möchte, an die Update-Funktion zu übergeben?

und ich möchte, dass alles andere standardmäßig auf -1 gesetzt wird (der Geheimcode für „egal“)

Benutzeravatar von hlovdal
hlovdal

Während Makros und/oder Funktionen (wie bereits vorgeschlagen) funktionieren (und möglicherweise andere positive Effekte haben (z. B. Debug-Hooks)), sind sie komplexer als nötig. Die einfachste und möglicherweise eleganteste Lösung besteht darin, einfach eine Konstante zu definieren, die Sie zur Variableninitialisierung verwenden:

const struct foo FOO_DONT_CARE = { // or maybe FOO_DEFAULT or something
    dont_care, dont_care, dont_care, dont_care
};
...
struct foo bar = FOO_DONT_CARE;
bar.id = 42;
bar.current_route = new_route;
update(&bar);

Dieser Code hat praktisch keinen mentalen Overhead, um die Indirektion zu verstehen, und es ist sehr klar, welche Felder in bar Sie legen explizit fest, während Sie (sicher) diejenigen ignorieren, die Sie nicht festlegen.

  • Ein weiterer Vorteil dieses Ansatzes besteht darin, dass er nicht auf C99-Funktionen angewiesen ist, um zu funktionieren.

    – D. Shawley

    15. April 2009 um 18:28 Uhr

  • Als ich zu diesem wechselte, “fielen” 500 Zeilen aus dem Projekt heraus. Ich wünschte, ich könnte hier zweimal abstimmen!

    – Arthur Ulfeldt

    12. Juni 2009 um 17:53 Uhr

  • PTHREAD_MUTEX_INITIALIZER nutzt diese auch.

    – yingted

    2. März 2012 um 4:50 Uhr

  • Ich habe unten eine Antwort hinzugefügt, die sich auf X-Makros verlässt, um flexibel in Bezug auf die Anzahl der in der Struktur vorhandenen Elemente zu sein.

    – Antonio

    12. April 2017 um 16:02 Uhr

Benutzeravatar von jpalecek
jpalecek

Sie können Ihren geheimen Spezialwert auf 0 ändern und die standardmäßige Strukturelement-Semantik von C ausnutzen

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

übergibt dann 0 als Mitglieder von bar, die im Initialisierer nicht angegeben sind.

Oder Sie können ein Makro erstellen, das die Standardinitialisierung für Sie durchführt:

#define FOO_INIT(...) { .id = -1, .current_route = -1, .quux = -1, ## __VA_ARGS__ }

struct foo bar = FOO_INIT( .id = 42, .current_route = new_route );
update(&bar);

  • Funktioniert FOO_INIT? Der Compiler sollte sich beschweren, denke ich, wenn Sie dasselbe Member zweimal initialisieren.

    – Jonathan Leffler

    15. April 2009 um 15:17 Uhr

  • Ich habe es mit gcc versucht und es hat sich nicht beschwert. Auch im Standard habe ich nichts dagegen gefunden, tatsächlich gibt es ein Beispiel, wo das Überschreiben ausdrücklich erwähnt wird.

    – jpalecek

    15. April 2009 um 19:40 Uhr

  • C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf: 6.7.8.19: Die Initialisierung soll in der Reihenfolge der Initialisiererliste erfolgen, wobei jeder Initialisierer, der für ein bestimmtes Unterobjekt vorgesehen ist, jeden zuvor aufgelisteten Initialisierer für dasselbe Unterobjekt überschreibt;

    – alterdky

    14. Dezember 2012 um 18:08 Uhr


  • Wenn dies wahr ist, könnten Sie nicht DEFAULT_FOO definieren, das alle Initialisierer enthält, und dann struct foo bar = { DEFAULT_FOO, .id=10 } ausführen; ?

    – John

    23. Dezember 2013 um 18:56 Uhr

Benutzeravatar von Matt J
Matt J

<stdarg.h> ermöglicht es Ihnen, variadische Funktionen zu definieren (die eine unbestimmte Anzahl von Argumenten akzeptieren, wie z printf()). Ich würde eine Funktion definieren, die eine beliebige Anzahl von Argumentpaaren benötigt, eines, das die zu aktualisierende Eigenschaft angibt, und eines, das den Wert angibt. Benutze ein enum oder eine Zeichenfolge, um den Namen der Eigenschaft anzugeben.

  • Hallo @Matt, wie man abbildet enum Variablen zu struct aufstellen? Hardcodieren? Dann ist diese Funktion nur auf bestimmte anwendbar struct.

    – misssprite

    15. Dezember 2014 um 8:20 Uhr

Vielleicht sollten Sie stattdessen eine Präprozessor-Makrodefinition verwenden:

#define UPDATE_ID(instance, id)  ({ (instance)->id= (id); })
#define UPDATE_ROUTE(instance, route)  ({ (instance)->route = (route); })
#define UPDATE_BACKUP_ROUTE(instance, route)  ({ (instance)->backup_route = (route); })
#define UPDATE_CURRENT_ROUTE(instance, route)  ({ (instance)->current_route = (route); })

Wenn Ihre Instanz von (struct foo) global ist, brauchen Sie den Parameter dafür natürlich nicht. Aber ich gehe davon aus, dass Sie wahrscheinlich mehr als eine Instanz haben. Die Verwendung des ({ … })-Blocks ist ein GNU-Ismus, der für GCC gilt; Es ist eine schöne (sichere) Möglichkeit, Linien als Block zusammenzuhalten. Wenn Sie später mehr zu den Makros hinzufügen müssen, wie z. B. Bereichsvalidierungsprüfung, müssen Sie sich keine Gedanken darüber machen, Dinge wie if/else-Anweisungen und so weiter zu beschädigen.

Das würde ich tun, basierend auf den von Ihnen angegebenen Anforderungen. Situationen wie diese sind einer der Gründe, warum ich angefangen habe, Python häufig zu verwenden; Der Umgang mit Standardparametern und dergleichen wird viel einfacher als jemals zuvor mit C. (Ich denke, das ist ein Python-Plug-in, sorry 😉

Ein von Gobject verwendetes Muster ist eine variadische Funktion und aufgezählte Werte für jede Eigenschaft. Die Schnittstelle sieht in etwa so aus:

update (ID, 1,
        BACKUP_ROUTE, 4,
        -1); /* -1 terminates the parameter list */

Das Schreiben einer varargs-Funktion ist einfach – siehe http://www.eskimo.com/~scs/cclass/int/sx11b.html. Passen Sie einfach Schlüssel -> Wert-Paare an und legen Sie die entsprechenden Strukturattribute fest.

Benutzeravatar von quinmars
Quinmare

Da es so aussieht braucht man diese Struktur nur für die update() Funktion, verwenden Sie dafür überhaupt keine Struktur, es verschleiert nur Ihre Absicht hinter diesem Konstrukt. Sie sollten vielleicht überdenken, warum Sie diese Felder ändern und aktualisieren, und separate Funktionen oder Makros für diese “kleinen” Änderungen definieren.

z.B


#define set_current_route(id, route) update(id, dont_care, dont_care, route)
#define set_route(id, route) update(id, dont_care, route, dont_care)
#define set_backup_route(id, route) update(id, route, dont_care, dont_care)

Oder schreiben Sie noch besser eine Funktion für jeden Änderungsfall. Wie Sie bereits bemerkt haben, ändern Sie nicht alle Eigenschaften gleichzeitig, also machen Sie es möglich, jeweils nur eine Eigenschaft zu ändern. Dies verbessert nicht nur die Lesbarkeit, sondern hilft Ihnen auch bei der Handhabung der unterschiedlichen Fälle, zB müssen Sie nicht nach allen “dont_care” suchen, weil Sie wissen, dass nur die aktuelle Route geändert wird.

Paddus Benutzeravatar
Paddu

Wie wäre es mit etwas wie:

struct foo bar;
update(init_id(42, init_dont_care(&bar)));

mit:

struct foo* init_dont_care(struct foo* bar) {
  bar->id = dont_care;
  bar->route = dont_care;
  bar->backup_route = dont_care;
  bar->current_route = dont_care;
  return bar;
}

und:

struct foo* init_id(int id, struct foo* bar) {
  bar->id = id;
  return bar;
}

und entsprechend:

struct foo* init_route(int route, struct foo* bar);
struct foo* init_backup_route(int backup_route, struct foo* bar);
struct foo* init_current_route(int current_route, struct foo* bar);

In C++ hat ein ähnliches Muster einen Namen, an den ich mich gerade nicht erinnere.

BEARBEITEN: Es heißt die Benannter Parameter Idiom.

  • Ich glaube, es heißt Konstruktor.

    – Unbekannt

    14. April 2009 um 20:23 Uhr

  • Nein, ich meinte das, was Sie tun können: update(bar.init_id(42).init_route(5).init_backup_route(66));

    – Reunanen

    14. April 2009 um 20:27 Uhr

  • Es scheint zumindest hier “verkettete Initialisierung im Smalltalk-Stil” genannt zu werden: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…

    – Reunanen

    14. April 2009 um 21:01 Uhr

1420290cookie-checkStandardwerte in einer C-Struktur

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy