Bir C Yapısında varsayılan değerler


93

Bunun gibi bir veri yapım var:

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

ve içinde değişiklik talep etmek için kullanılan update () adlı bir işlev.

  update (42, dont_care, dont_care, new_route);

bu gerçekten uzun ve eğer yapıya bir şey eklersem, HER güncelleme çağrısına bir 'dont_care' eklemem gerekir (...).

Onun yerine bir yapı geçirmeyi düşünüyorum, ancak yapıyı önceden 'dont_care' ile doldurmak, sadece işlev çağrısında açıklamaktan daha sıkıcı. Yapıyı bir yerde öntanımlı önemsiz değerlerle oluşturabilir miyim ve sadece ilgilendiğim alanları yerel değişken olarak ilan ettikten sonra ayarlayabilir miyim?

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

Yalnızca ifade etmek istediğim bilgileri güncelleme işlevine aktarmanın en zarif yolu nedir?

ve diğer her şeyin varsayılan olarak -1 olmasını istiyorum ('umurumda değil' için gizli kod)

Yanıtlar:


156

Makrolar ve / veya işlevler (daha önce önerildiği gibi) çalışacak olsa da (ve başka olumlu etkileri olabilir (yani hata ayıklama kancaları)), gerekenden daha karmaşıktır. En basit ve muhtemelen en zarif çözüm, değişken başlatma için kullandığınız bir sabiti tanımlamaktır:

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

Bu kodun, dolaylı yoldan anlaşılması için neredeyse hiçbir zihinsel ek yükü yoktur ve barsizin ayarlamadıklarınızı (güvenli bir şekilde) görmezden gelirken, içinde hangi alanları açıkça ayarladığınız çok açıktır .


6
Bu yaklaşımın bir başka avantajı da, çalışmak için bir C99 özelliklerine dayanmamasıdır.
D.Shawley

25
Bu 500 satıra geçtiğimde proje "düştü". Keşke bu konuda iki kez oy ekleyebilseydim!
Arthur Ulfeldt

4
PTHREAD_MUTEX_INITIALIZERbunu da kullanıyor.
yingted

Yapıda mevcut elemanların sayısına göre esnek olmak için X-Makrolara güvenerek aşağıya bir cevap ekledim .
Antonio

15

Gizli özel değerinizi 0 olarak değiştirebilir ve C'nin varsayılan yapı-üye anlamından yararlanabilirsiniz.

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

daha sonra başlatıcıda belirtilmemiş çubuğun üyeleri olarak 0'ı geçecektir.

Veya sizin için varsayılan başlatmayı yapacak bir makro oluşturabilirsiniz:

#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 çalışıyor mu? Aynı üyeyi iki kez başlatırsanız derleyici şikayet etmelidir.
Jonathan Leffler

1
Gcc ile denedim ve şikayet etmedi. Ayrıca, standartta buna karşı bir şey bulamadım, aslında üzerine yazmanın özellikle bahsedildiği bir örnek var.
jpalecek

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: Başlatma, başlatıcı listesi sırasında gerçekleşir, her başlatıcı, belirli bir alt nesne için sağlanır ve aynı için önceden listelenen başlatıcıları geçersiz kılar. alt nesne;
altendky

2
Bu doğruysa, tüm başlatıcılara sahip olan DEFAULT_FOO'yu tanımlayıp daha sonra struct foo bar = {DEFAULT_FOO, .id = 10} yapamazsınız; ?
John

7

<stdarg.h>değişken işlevler tanımlamanıza izin verir (bunlar gibi sınırsız sayıda bağımsız değişken kabul eder printf()). Güncellenecek özelliği belirten ve değeri belirleyen rastgele sayıda argüman çifti alan bir işlev tanımlardım. enumÖzelliğin adını belirtmek için bir veya bir dize kullanın.


Merhaba @Matt, enumdeğişkenler structalana nasıl eşlenir ? Kodlama mı? O zaman bu işlev yalnızca özel için geçerli olacaktır struct.
misssprite

5

Bunun yerine bir ön işlemci makro tanımı kullanmayı düşünebilirsiniz:

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

(Struct foo) örneğiniz global ise, elbette bunun için parametreye ihtiyacınız yoktur. Ama muhtemelen birden fazla örneğiniz olduğunu varsayıyorum. ({...}) bloğunun kullanılması, GCC için geçerli olan bir GNU-izmidir; satırları bir blok olarak bir arada tutmanın güzel (güvenli) bir yoludur. Daha sonra makrolara aralık doğrulama denetimi gibi daha fazlasını eklemeniz gerekirse, if / else ifadeleri ve benzeri şeyleri bozma konusunda endişelenmenize gerek kalmaz.

Bu, belirttiğiniz gereksinimlere göre yapacağım şeydir. Bunun gibi durumlar python'u çok kullanmaya başlamamın sebeplerinden biri; varsayılan parametrelerin işlenmesi C ile olduğundan çok daha kolay hale geldi (sanırım bu bir python eklentisi, üzgünüm ;-)


4

Gobject modelinin kullandığı bir model, değişken bir işlev ve her özellik için numaralandırılmış değerlerdir. Arayüz şuna benzer:

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

Bir varargs işlevi yazmak kolaydır - bkz. Http://www.eskimo.com/~scs/cclass/int/sx11b.html . Sadece anahtar -> değer çiftlerini eşleştirin ve uygun yapı niteliklerini ayarlayın.


4

Görünüşe göre sadece update()fonksiyon için bu yapıya ihtiyacınız var , bunun için bir yapı kullanmayın, sadece o yapının arkasındaki niyetinizi şaşırtacaktır. Bu alanları neden değiştirdiğinizi ve güncellediğinizi yeniden düşünmeli ve bu "küçük" değişiklikler için ayrı işlevler veya makrolar tanımlamalısınız.

Örneğin


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

Hatta her değişiklik durumu için bir işlev yazmanız daha da iyi olur. Daha önce fark ettiğiniz gibi, her mülkü aynı anda değiştirmeyiniz, bu nedenle aynı anda yalnızca bir mülkü değiştirmeyi mümkün kılın. Bu sadece okunabilirliği artırmakla kalmaz, aynı zamanda farklı durumları ele almanıza da yardımcı olur, örneğin tüm "dont_care" i kontrol etmek zorunda değilsiniz çünkü sadece mevcut rotanın değiştiğini biliyorsunuz.


bazı durumlarda aynı görüşmede 3 tanesi değişecektir.
Arthur Ulfeldt

Bu sıraya sahip olabilirsiniz: set_current_rout (id, rota); set_route (id, rota); apply_change (id); veya benzeri. Her ayarlayıcı için bir işlev çağrısı yapabileceğinizi düşünüyorum. Ve gerçek hesaplamayı (veya her ne olursa olsun) daha sonraki bir noktada yapın.
quinmars

4

Şunun gibi bir şeye ne dersiniz:

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

ile:

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

ve:

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

ve buna bağlı olarak:

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

C ++ 'da, benzer bir kalıbın şu anda hatırlamadığım bir adı var.

DÜZENLEME : Buna Adlandırılmış Parametre Deyimi denir .


Yapıcı olarak adlandırıldığına inanıyorum.
Unknown

Hayır, yapabileceğiniz şeyi kastettim: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen

En azından burada, "zincirleme Smalltalk tarzı başlatma" olarak adlandırılıyor gibi görünüyor: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen

2

Yapılar konusunda paslandım, bu yüzden muhtemelen burada birkaç anahtar kelimeyi kaçırıyorum. Ama neden varsayılanların başlatıldığı global bir yapıyla başlayıp, onu yerel değişkeninize kopyalayıp sonra değiştirmeyesiniz?

Şunun gibi bir başlatıcı:

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

Sonra onu kullanmak istediğinizde:

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

2

Sorunu bir X-Macro ile çözebilirsiniz

Yapı tanımınızı şu şekilde değiştirirsiniz:

#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

Ve sonra, tüm alanları ayarlayan esnek bir işlevi kolayca tanımlayabilirsiniz dont_care.

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

Buradaki tartışmanın ardından, bu şekilde bir varsayılan değer de tanımlanabilir:

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

Bunun anlamı:

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

Ve de olarak kullanmak hlovdal cevap , burada bakım, yani daha kolaydır otomatik güncelleştirme yapı üye sayısını olacak değişen bu avantajlafoo_DONT_CARE . Not, son "sahte" virgül kabul edilebilir .

X-Macros kavramını ilk olarak bu sorunu çözmem gerektiğinde öğrendim .

Yapıya eklenen yeni alanlar için son derece esnektir. Farklı veri türleriniz varsa dont_care, veri türüne bağlı olarak farklı değerler tanımlayabilirsiniz : buradan , ikinci örnekteki değerleri yazdırmak için kullanılan işlevden ilham alabilirsiniz.

Tüm intyapıya sahipseniz, veri türünü atlayabilir LIST_OF_foo_MEMBERSve yapı tanımının X işlevini olarak değiştirebilirsiniz.#define X(name) int name;


Bu teknik, C ön işlemcisinin geç bağlamayı kullandığından yararlanır ve bu, tek bir yerden bir şeyi tanımlamak için (örneğin durumlar) istediğinizde çok yararlı olabilir ve ardından kullanılan öğelerin her yerde her zaman aynı sırada olduğundan, yeni öğeler olduğundan% 100 emin olun. otomatik olarak eklenir ve silinen öğeler kaldırılır. Liste adı gibi kısa isimler kullanmaktan pek hoşlanmıyorum Xve genellikle _ENTRYliste adına ekleyeceğim , örn #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal

1

En zarif yol, update()işlevi kullanmak zorunda kalmadan yapı alanlarını doğrudan güncellemektir - ancak belki de onu kullanmak için soruda rastlanmayan iyi nedenler olabilir.

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

Veya, Pukku'nun önerdiği gibi, yapının her alanı için ayrı erişim işlevleri oluşturabilirsiniz.

Aksi takdirde düşünebildiğim en iyi çözüm, bir yapı alanındaki '0' değerini 'güncelleme' bayrağı olarak ele almaktır - bu nedenle sıfırlanmış bir yapı döndürmek için bir işlev oluşturursunuz ve ardından bunu güncellemek için kullanırsınız.

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

Bununla birlikte, 0 yapıdaki alanlar için geçerli bir değerse, bu pek uygun olmayabilir.


Bir ağ iyi bir nedendir =) Ama eğer -1 'umursamıyorum' değeriyse, o zaman sadece empty_foo () işlevini yeniden yapılandırın ve bu yaklaşımı kullanın?
gnud

üzgünüm, yanlışlıkla olumsuz oyu tıkladım ve daha sonra fark ettim !!! Bir düzenleme olmadıkça bunu geri almanın bir yolunu bulamıyorum ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.