Standardwerte in einer C-Struktur

92

Ich habe eine Datenstruktur wie diese:

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

und eine Funktion namens update (), mit der Änderungen angefordert werden.

  Update (42, dont_care, dont_care, new_route);

Das ist wirklich lang und wenn ich der Struktur etwas hinzufüge, muss ich JEDEM Aufruf zum Aktualisieren (...) ein 'dont_care' hinzufügen.

Ich denke darüber nach, stattdessen eine Struktur zu übergeben, aber das vorherige Ausfüllen der Struktur mit 'dont_care' ist noch mühsamer, als sie nur im Funktionsaufruf zu formulieren. Kann ich die Struktur irgendwo mit den Standardwerten von dont care erstellen und nur 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 Aktualisierungsfunktion zu übergeben?

und ich möchte, dass alles andere standardmäßig -1 ist (der Geheimcode für 'egal')

Arthur Ulfeldt
quelle

Antworten:

155

Makros und / oder Funktionen (wie bereits vorgeschlagen) funktionieren zwar (und können andere positive Auswirkungen haben (z. B. Debug-Hooks)), sind jedoch komplexer als erforderlich. Die einfachste und möglicherweise eleganteste Lösung besteht darin, nur eine Konstante zu definieren, die Sie für die 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 Aufwand für das Verständnis der Indirektion, und es ist sehr klar, welche Felder in barIhnen explizit festgelegt werden, während diejenigen, die Sie nicht festgelegt haben, (sicher) ignoriert werden.

hlovdal
quelle
6
Ein weiterer Vorteil dieses Ansatzes besteht darin, dass er nicht auf C99-Funktionen angewiesen ist.
D.Shawley
24
Als ich zu diesen 500 Zeilen wechselte, fielen sie aus dem Projekt heraus. Ich wünschte, ich könnte zweimal darüber abstimmen!
Arthur Ulfeldt
4
PTHREAD_MUTEX_INITIALIZERnutzt dies auch.
Yingted
Ich habe unten eine Antwort hinzugefügt , die sich auf X-Makros stützt, um hinsichtlich der Anzahl der in der Struktur vorhandenen Elemente flexibel zu sein.
Antonio
15

Sie können Ihren geheimen Sonderwert in 0 ändern und die Standard-Semantik für Strukturelemente von C ausnutzen

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

wird dann 0 als Mitglieder der Leiste übergeben, die im Initialisierer nicht angegeben sind.

Oder Sie können ein Makro erstellen, das die Standardinitialisierung für Sie ausfü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);
jpalecek
quelle
Funktioniert FOO_INIT? Der Compiler sollte sich beschweren, denke ich, wenn Sie dasselbe Mitglied zweimal initialisieren.
Jonathan Leffler
1
Ich habe es mit gcc versucht und es hat sich nicht beschwert. Außerdem habe ich im Standard nichts dagegen gefunden. Tatsächlich gibt es ein Beispiel, in dem das Überschreiben ausdrücklich erwähnt wird.
Jpalecek
2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: Die Initialisierung muss in der Reihenfolge der Initialisiererliste erfolgen, wobei jeder Initialisierer für ein bestimmtes Unterobjekt vorgesehen ist, das einen zuvor aufgelisteten Initialisierer für dasselbe überschreibt Unterobjekt;
Altendky
2
Wenn dies zutrifft, können Sie nicht DEFAULT_FOO definieren, das alle Initialisierer enthält, und dann struct foo bar = {DEFAULT_FOO, .id = 10} ausführen. ?
John
7

<stdarg.h>Mit dieser Option können Sie verschiedene Funktionen definieren (die eine unbestimmte Anzahl von Argumenten akzeptieren, z. B. printf()). Ich würde eine Funktion definieren, die eine beliebige Anzahl von Argumentpaaren verwendet, eines, das die zu aktualisierende Eigenschaft angibt, und eines, das den Wert angibt. Verwenden Sie eine enumoder eine Zeichenfolge, um den Namen der Eigenschaft anzugeben.

Matt J.
quelle
Hallo @Matt, wie ordne ich enumVariablen dem structFeld zu? Hardcode es? Dann ist diese Funktion nur auf bestimmte anwendbar struct.
Misssprite
5

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 weitere Makros hinzufügen müssen, z. B. die Überprüfung der Bereichsüberprüfung, müssen Sie sich keine Sorgen mehr machen, dass Dinge wie if / else-Anweisungen usw. beschädigt werden.

Dies würde ich tun, basierend auf den von Ihnen angegebenen Anforderungen. Situationen wie diese sind einer der Gründe, warum ich viel mit Python angefangen habe. Der Umgang mit Standardparametern und dergleichen wird mit C viel einfacher als je zuvor (ich denke, das ist ein Python-Plug, sorry ;-)

Digijock
quelle
4

Ein Musterobjekt, das verwendet wird, ist eine variable Funktion und Aufzählungswerte für jede Eigenschaft. Die Oberfläche sieht ungefähr 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 -> Wertepaare an und legen Sie die entsprechenden Strukturattribute fest.

John Millikin
quelle
4

Da es so aussieht, als ob Sie nur diese Struktur für die update()Funktion benötigen, verwenden Sie dafür überhaupt keine Struktur, da dies nur Ihre Absicht hinter diesem Konstrukt verschleiert. 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 jede Eigenschaft gleichzeitig. Machen Sie es also möglich, jeweils nur eine Eigenschaft zu ändern. Dies verbessert nicht nur die Lesbarkeit, sondern hilft Ihnen auch bei der Behandlung der verschiedenen Fälle, z. B. müssen Sie nicht nach allen "dont_care" suchen, da Sie wissen, dass nur die aktuelle Route geändert wird.

Quinmars
quelle
In einigen Fällen werden 3 davon im selben Aufruf geändert.
Arthur Ulfeldt
Sie können diese Sequenz haben: set_current_rout (id, route); set_route (id, route); apply_change (id); o.ä. Ich denke, dass Sie für jeden Setter einen Funktionsaufruf haben können. Und führen Sie die eigentliche Berechnung (oder was auch immer) zu einem späteren Zeitpunkt durch.
Quinmars
4

Wie wäre es mit so 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.

EDIT : Es heißt Named Parameter Idiom .

Reunanen
quelle
Ich glaube, es heißt Konstruktor.
Unbekannt
Nein, ich meinte das, was Sie tun können: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen
Zumindest hier scheint es als "verkettete Initialisierung im Smalltalk-Stil" bezeichnet zu werden: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen
2

Ich bin verrostet mit Strukturen, daher fehlen mir hier wahrscheinlich ein paar Schlüsselwörter. Beginnen Sie jedoch mit einer globalen Struktur mit initialisierten Standardeinstellungen, kopieren Sie sie in Ihre lokale Variable und ändern Sie sie dann.

Ein Initialisierer wie:

void init_struct( structType * s )
{
   memcopy(s,&defaultValues,sizeof(structType));
}

Dann, wenn Sie es verwenden möchten:

structType foo;
init_struct( &foo ); // get defaults
foo.fieldICareAbout = 1; // modify fields
update( &foo ); // pass to function
Steven Fisher
quelle
2

Sie können das Problem mit einem X-Macro beheben

Sie würden Ihre Strukturdefinition ändern in:

#define LIST_OF_foo_MEMBERS \
    X(int,id) \
    X(int,route) \
    X(int,backup_route) \
    X(int,current_route)


#define X(type,name) type name;
struct foo {
    LIST_OF_foo_MEMBERS 
};
#undef X

Und dann können Sie einfach eine flexible Funktion definieren, auf die alle Felder gesetzt sind dont_care.

#define X(type,name) in->name = dont_care;    
void setFooToDontCare(struct foo* in) {
    LIST_OF_foo_MEMBERS 
}
#undef X

Nach der Diskussion hier könnte man auch einen Standardwert auf diese Weise definieren:

#define X(name) dont_care,
const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo };
#undef X

Was bedeutet:

const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,};

Und verwenden Sie es wie in hlovdal answer , mit dem Vorteil, dass hier die Wartung einfacher ist, dh die Änderung der Anzahl der Strukturelemente wird automatisch aktualisiertfoo_DONT_CARE . Beachten Sie, dass das letzte "falsche" Komma akzeptabel ist .

Ich habe das Konzept der X-Makros zum ersten Mal gelernt, als ich dieses Problem angehen musste .

Es ist äußerst flexibel, wenn neue Felder zur Struktur hinzugefügt werden. Wenn Sie unterschiedliche Datentypen haben, können Sie dont_careje nach Datentyp unterschiedliche Werte definieren : von hier aus können Sie sich von der Funktion inspirieren lassen, mit der die Werte im zweiten Beispiel gedruckt werden.

Wenn Sie mit einer All- intStruktur einverstanden sind, können Sie den Datentyp weglassen LIST_OF_foo_MEMBERSund einfach die X-Funktion der Strukturdefinition in ändern#define X(name) int name;

Antonio
quelle
Diese Technik nutzt aus, dass der C-Präprozessor eine späte Bindung verwendet. Dies kann sehr nützlich sein, wenn Sie möchten, dass ein einzelner Ort etwas definiert (z. B. Zustände) und dann 100% sicher ist, dass überall, wenn verwendete Elemente immer in derselben Reihenfolge sind, neue Elemente vorhanden sind werden automatisch hinzugefügt und gelöschte Elemente entfernt. Ich mag es nicht, Kurznamen wie Xund normalerweise _ENTRYan den Listennamen anzuhängen , z #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
Hlovdal
1

Der eleganteste Weg wäre, die Strukturfelder direkt zu aktualisieren, ohne die update()Funktion verwenden zu müssen - aber vielleicht gibt es gute Gründe für die Verwendung, die in der Frage nicht vorkommen.

struct foo* bar = get_foo_ptr();
foo_ref.id = 42;
foo_ref.current_route = new_route;

Oder Sie können, wie von Pukku vorgeschlagen, separate Zugriffsfunktionen für jedes Feld der Struktur erstellen.

Ansonsten ist die beste Lösung, die ich mir vorstellen kann, einen Wert von '0' in einem Strukturfeld als 'Nicht aktualisieren' -Flag zu behandeln. Sie erstellen also einfach eine Funktion, um eine auf Null gesetzte Struktur zurückzugeben, und verwenden diese dann zum Aktualisieren.

struct foo empty_foo(void)
{
    struct foo bar;
    bzero(&bar, sizeof (struct bar));
    return bar;    
}

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

Dies ist jedoch möglicherweise nicht sehr realisierbar, wenn 0 ein gültiger Wert für Felder in der Struktur ist.

Gnud
quelle
Ein Netzwerk ist ein guter Grund =) Aber wenn -1 der Wert 'egal' ist, dann restrukturieren Sie einfach die Funktion empty_foo () und verwenden Sie diesen Ansatz?
Gnud
Entschuldigung, ich habe versehentlich auf das Downvote geklickt und es erst später bemerkt !!! Ich kann keine Möglichkeit finden, dies jetzt rückgängig zu machen, es sei denn, es gibt eine Bearbeitung ...
Ciro Santilli 法轮功 冠状 病. 事件 法轮功