Valori di default in un C Struct


92

Ho una struttura dati come questa:

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

e una funzione chiamata update () che viene utilizzata per richiedere modifiche al suo interno.

  aggiornamento (42, dont_care, dont_care, new_route);

questo è veramente lungo e se aggiungo qualcosa alla struttura devo aggiungere un 'dont_care' ad OGNI chiamata per aggiornare (...).

Sto invece pensando di passargli una struttura, ma riempire la struttura con 'dont_care' in anticipo è ancora più noioso che spiegarlo semplicemente nella chiamata di funzione. Posso creare la struttura da qualche parte con i valori predefiniti di dont care e impostare semplicemente i campi che mi interessano dopo averla dichiarata come variabile locale?

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

Qual è il modo più elegante per passare solo le informazioni che desidero esprimere alla funzione di aggiornamento?

e voglio che tutto il resto sia predefinito su -1 (il codice segreto per "non importa")

Risposte:


155

Sebbene le macro e / o le funzioni (come già suggerito) funzioneranno (e potrebbero avere altri effetti positivi (es. Hook di debug)), sono più complesse del necessario. La soluzione più semplice e possibilmente più elegante è semplicemente definire una costante da utilizzare per l'inizializzazione delle variabili:

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);

Questo codice non ha virtualmente alcun sovraccarico mentale per la comprensione dell'indirizzamento indiretto, ed è molto chiaro quali campi barimpostati in modo esplicito ignorando (in modo sicuro) quelli non impostati.


6
Un altro vantaggio di questo approccio è che non si basa sulle funzionalità di un C99 per funzionare.
D.Shawley

24
Quando sono passato a queste 500 righe "cadde" dal progetto. Vorrei poter votare due volte su questo!
Arthur Ulfeldt

4
PTHREAD_MUTEX_INITIALIZERusa anche questo.
yingted

Ho aggiunto una risposta di seguito facendo affidamento su X-Macro per essere flessibile sul numero di elementi presenti nella struttura.
Antonio

15

Puoi cambiare il tuo valore speciale segreto su 0 e sfruttare la semantica dei membri della struttura predefinita di C.

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

passerà quindi 0 come membri della barra non specificati nell'inizializzatore.

Oppure puoi creare una macro che eseguirà l'inizializzazione predefinita per te:

#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);

FOO_INIT funziona? Il compilatore dovrebbe lamentarsi, credo, se inizializzi lo stesso membro due volte.
Jonathan Leffler

1
L'ho provato con gcc e non si è lamentato. Inoltre, non ho trovato nulla contro di esso nello standard, infatti, c'è un esempio in cui la sovrascrittura è specificamente menzionata.
jpalecek

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: L'inizializzazione deve avvenire nell'ordine dell'elenco degli inizializzatori, ogni inizializzatore fornito per un particolare oggetto secondario sovrascrive qualsiasi inizializzatore precedentemente elencato per lo stesso suboggetto;
altendky

2
Se questo è vero, potresti non definire DEFAULT_FOO, che ha tutti gli inizializzatori, e quindi eseguire struct foo bar = {DEFAULT_FOO, .id = 10}; ?
John

7

<stdarg.h>ti permette di definire funzioni variadiche (che accettano un numero indefinito di argomenti, come printf()). Definirei una funzione che ha un numero arbitrario di coppie di argomenti, una che specifica la proprietà da aggiornare e una che specifica il valore. Utilizzare una enumo una stringa per specificare il nome della proprietà.


Ciao @ Matt, come mappare le enumvariabili sul structcampo? Hardcode vero? Quindi questa funzione sarà applicabile solo a specifiche struct.
misssprite

5

Forse prendi in considerazione l'utilizzo di una definizione di macro del preprocessore:

#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); })

Se la tua istanza di (struct foo) è globale, allora non hai bisogno del parametro per quello ovviamente. Ma presumo tu abbia probabilmente più di un'istanza. L'uso del blocco ({...}) è uno GNU-ismo che si applica a GCC; è un modo carino (sicuro) per tenere insieme le linee come un blocco. Se in seguito è necessario aggiungere altro alle macro, come il controllo della convalida dell'intervallo, non dovrai preoccuparti di rompere cose come le istruzioni if ​​/ else e così via.

Questo è quello che farei, in base ai requisiti che hai indicato. Situazioni come questa sono uno dei motivi per cui ho iniziato a usare molto python; gestire i parametri predefiniti e questo diventa molto più semplice di quanto non sia mai stato con C. (immagino che sia un plug-in Python, scusa ;-)


4

Un modello gobject utilizzato è una funzione variadica e valori enumerati per ciascuna proprietà. L'interfaccia è simile a:

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

Scrivere una funzione varargs è facile: vedere http://www.eskimo.com/~scs/cclass/int/sx11b.html . Basta abbinare le coppie chiave -> valore e impostare gli attributi di struttura appropriati.


4

Dato che sembra che tu abbia bisogno di questa struttura solo per la update()funzione, non usare affatto una struttura per questo, oscurerà solo la tua intenzione dietro quel costrutto. Forse dovresti ripensare al motivo per cui stai modificando e aggiornando quei campi e definire funzioni o macro separate per queste "piccole" modifiche.

per esempio


#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)

O ancora meglio scrivere una funzione per ogni caso di modifica. Come hai già notato, non modifichi tutte le proprietà contemporaneamente, quindi rendi possibile cambiare solo una proprietà alla volta. Questo non solo migliora la leggibilità, ma ti aiuta anche a gestire i diversi casi, ad esempio non devi controllare tutti i "dont_care" perché sai che è cambiato solo il percorso corrente.


alcuni casi ne cambieranno 3 nella stessa chiamata.
Arthur Ulfeldt

Puoi avere questa sequenza: set_current_rout (id, route); set_route (id, route); apply_change (id); o simili. Quello che penso è che puoi avere una chiamata di funzione per ogni setter. E fai il calcolo reale (o cosa mai) in un secondo momento.
quinmars

4

Che ne dici di qualcosa come:

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

con:

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;
}

e:

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

e di conseguenza:

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 ++, un modello simile ha un nome che non ricordo solo ora.

EDIT : Si chiama Named Parameter Idiom .


Credo che sia chiamato un costruttore.
Unknown

No, intendevo la cosa che puoi fare: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Riunione

Sembra essere chiamata "inizializzazione in stile Smalltalk concatenato", almeno qui: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen

2

Sono arrugginito con le strutture, quindi probabilmente mi mancano alcune parole chiave qui. Ma perché non iniziare con una struttura globale con i valori predefiniti inizializzati, copiarla nella variabile locale, quindi modificarla?

Un inizializzatore come:

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

Quindi quando vuoi usarlo:

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

2

Potresti risolvere il problema con un X-Macro

Cambieresti la definizione della tua struttura 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

E poi saresti in grado di definire facilmente una funzione flessibile che imposta tutti i campi su dont_care.

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

Seguendo la discussione qui , si potrebbe anche definire un valore predefinito in questo modo:

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

Che si traduce in:

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

E usalo come nella risposta hlovdal , con il vantaggio che qui la manutenzione è più facile, cioè la modifica del numero di membri della struttura si aggiornerà automaticamentefoo_DONT_CARE . Nota che l'ultima virgola "spuria" è accettabile .

Ho imparato per la prima volta il concetto di X-Macro quando ho dovuto affrontare questo problema .

È estremamente flessibile l'aggiunta di nuovi campi alla struttura. Se hai tipi di dati diversi, potresti definire dont_carevalori diversi a seconda del tipo di dati: da qui potresti prendere spunto dalla funzione utilizzata per stampare i valori nel secondo esempio.

Se sei d'accordo con una intstruttura all , allora potresti omettere il tipo di dati da LIST_OF_foo_MEMBERSe semplicemente cambiare la funzione X della definizione della struttura in#define X(name) int name;


Questa tecnica sfrutta il fatto che il preprocessore C utilizza l' associazione tardiva e questo può essere molto utile quando si desidera che un unico posto definisca qualcosa (ad esempio gli stati) e quindi essere sicuri al 100% che ovunque quando gli elementi utilizzati siano sempre nello stesso ordine, nuovi elementi vengono aggiunti automaticamente e gli elementi eliminati vengono rimossi. Non mi piace usare nomi brevi come Xe in genere si aggiungono _ENTRYal nome dell'elenco, ad es #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal

1

Il modo più elegante sarebbe aggiornare direttamente i campi della struttura, senza dover usare la update()funzione, ma forse ci sono buone ragioni per usarlo che non si incontrano nella domanda.

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

Oppure puoi, come suggerito da Pukku, creare funzioni di accesso separate per ogni campo della struttura.

Altrimenti la soluzione migliore a cui posso pensare è trattare un valore di "0" in un campo struct come un flag "non aggiornare", quindi devi semplicemente creare una funzione per restituire una struttura azzerata e quindi usarla per aggiornare.

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);

Tuttavia, questo potrebbe non essere molto fattibile, se 0 è un valore valido per i campi nella struttura.


Una rete è una buona ragione =) Ma se -1 è il valore "non importa", allora basta ristrutturare la funzione empty_foo () e usare questo approccio?
gnud

scusa, ho cliccato per errore sul voto negativo e l'ho notato solo dopo !!! Non riesco a trovare alcun modo per annullarlo ora a meno che non ci sia una modifica ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.