Nilai default dalam Struktur C.

93

Saya memiliki struktur data seperti ini:

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

dan fungsi yang disebut update () yang digunakan untuk meminta perubahan di dalamnya.

  update (42, dont_care, dont_care, new_route);

ini sangat panjang dan jika saya menambahkan sesuatu ke struktur, saya harus menambahkan 'dont_care' ke SETIAP panggilan untuk memperbarui (...).

Saya berpikir untuk memberikannya struct tetapi mengisi struct dengan 'dont_care' sebelumnya bahkan lebih membosankan daripada hanya mengejanya dalam pemanggilan fungsi. Dapatkah saya membuat struct di suatu tempat dengan nilai default dont care dan hanya mengatur bidang yang saya pedulikan setelah saya mendeklarasikannya sebagai variabel lokal?

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

Apa cara paling elegan untuk hanya menyampaikan informasi yang ingin saya ungkapkan ke fungsi pembaruan?

dan saya ingin yang lainnya default ke -1 (kode rahasia untuk 'dont care')

Arthur Ulfeldt
sumber

Jawaban:

156

Meskipun makro dan / atau fungsi (seperti yang telah disarankan) akan berfungsi (dan mungkin memiliki efek positif lainnya (yaitu kait debug)), mereka lebih kompleks dari yang dibutuhkan. Solusi paling sederhana dan mungkin paling elegan adalah dengan menetapkan konstanta yang Anda gunakan untuk inisialisasi variabel:

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

Kode ini hampir tidak memiliki overhead mental untuk memahami tipuan, dan sangat jelas bidang mana yang barAnda setel secara eksplisit sementara (dengan aman) mengabaikan bidang yang tidak Anda setel.

hlovdal.dll
sumber
6
Bonus lain dari pendekatan ini adalah tidak bergantung pada fitur C99 untuk bekerja.
D. Shawley
24
Ketika saya mengubah ke 500 baris ini "jatuh" dari proyek. Seandainya saya bisa memberi suara dua kali untuk yang satu ini!
Arthur Ulfeldt
4
PTHREAD_MUTEX_INITIALIZERmenggunakan ini juga.
yingted
Saya telah menambahkan jawaban di bawah ini dengan mengandalkan X-Macro karena fleksibel pada jumlah elemen yang ada di struct.
Antonio
15

Anda dapat mengubah nilai khusus rahasia Anda menjadi 0, dan memanfaatkan semantik anggota struktur default C.

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

kemudian akan melewati 0 sebagai anggota bar yang tidak ditentukan di penginisialisasi.

Atau Anda bisa membuat makro yang akan melakukan inisialisasi default untuk Anda:

#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.dll
sumber
Apakah FOO_INIT berfungsi? Kompilator harus mengeluh, menurut saya, jika Anda menginisialisasi anggota yang sama dua kali.
Jonathan Leffler
1
Saya sudah mencobanya dengan gcc dan tidak mengeluh. Juga, saya belum menemukan apa pun yang menentangnya dalam standar, pada kenyataannya, ada satu contoh di mana penimpaan disebutkan secara khusus.
jpalecek
2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: Inisialisasi akan terjadi dalam urutan daftar penginisialisasi, setiap penginisialisasi disediakan untuk subobjek tertentu menimpa penginisialisasi terdaftar sebelumnya untuk hal yang sama subobjek;
altendky
2
Jika ini benar, tidak bisakah Anda mendefinisikan DEFAULT_FOO, yang memiliki semua penginisialisasi, dan kemudian lakukan struct foo bar = {DEFAULT_FOO, .id = 10}; ?
Yohanes
7

<stdarg.h>memungkinkan Anda untuk menentukan fungsi variadic (yang menerima argumen dalam jumlah tak terbatas, seperti printf()). Saya akan mendefinisikan sebuah fungsi yang mengambil sejumlah sembarang pasangan argumen, yang menentukan properti yang akan diperbarui, dan yang menentukan nilainya. Gunakan enumatau string untuk menentukan nama properti.

Matt J.
sumber
Hai @Matt, bagaimana cara memetakan enumvariabel ke structlapangan? Hardcode itu? Maka fungsi ini hanya akan berlaku untuk yang spesifik struct.
tulis
5

Mungkin pertimbangkan untuk menggunakan definisi makro preprocessor sebagai gantinya:

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

Jika instance Anda dari (struct foo) bersifat global, maka Anda tidak memerlukan parameter untuk itu tentunya. Tapi saya berasumsi Anda mungkin memiliki lebih dari satu contoh. Menggunakan blok ({...}) adalah GNU-isme yang berlaku untuk GCC; ini adalah cara yang bagus (aman) untuk menyatukan garis sebagai satu blok. Jika nanti Anda perlu menambahkan lebih banyak ke makro, seperti pemeriksaan validasi rentang, Anda tidak perlu khawatir tentang melanggar hal-hal seperti pernyataan if / else dan sebagainya.

Inilah yang akan saya lakukan, berdasarkan persyaratan yang Anda tunjukkan. Situasi seperti ini adalah salah satu alasan saya mulai banyak menggunakan python; menangani parameter default dan semacamnya menjadi jauh lebih sederhana daripada sebelumnya dengan C. (Saya rasa itu adalah plug python, maaf ;-)

digijock.dll
sumber
4

Salah satu pola yang digunakan gobject adalah fungsi variadic, dan nilai yang disebutkan untuk setiap properti. Antarmukanya terlihat seperti:

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

Menulis fungsi varargs itu mudah - lihat http://www.eskimo.com/~scs/cclass/int/sx11b.html . Cukup cocokkan kunci -> pasangan nilai dan setel atribut struktur yang sesuai.

John Millikin
sumber
4

Karena sepertinya Anda hanya membutuhkan struktur ini untuk file update() fungsinya, jangan gunakan struktur untuk ini sama sekali, ini hanya akan mengaburkan maksud Anda di balik konstruksi itu. Anda mungkin harus memikirkan kembali mengapa Anda mengubah dan memperbarui bidang tersebut dan menentukan fungsi atau makro terpisah untuk perubahan "kecil" ini.

misalnya


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

Atau bahkan lebih baik menulis fungsi untuk setiap kasus perubahan. Seperti yang sudah Anda ketahui, Anda tidak mengubah setiap properti pada saat yang sama, jadi buatlah mungkin untuk mengubah hanya satu properti dalam satu waktu. Ini tidak hanya meningkatkan keterbacaan, tetapi juga membantu Anda menangani kasus yang berbeda, misalnya Anda tidak perlu memeriksa semua "dont_care" karena Anda tahu bahwa hanya rute saat ini yang diubah.

quinmars
sumber
beberapa kasus akan mengubah 3 di antaranya dalam panggilan yang sama.
Arthur Ulfeldt
Anda dapat memiliki urutan ini: set_current_rout (id, route); set_route (id, rute); apply_change (id); atau serupa. Apa yang saya pikirkan adalah Anda dapat memiliki satu pemanggilan fungsi untuk setiap penyetel. Dan lakukan perhitungan nyata (atau apa pun) di lain waktu.
quinmars
4

Bagaimana dengan sesuatu seperti:

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

dengan:

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

dan:

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

dan dengan demikian:

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

Di C ++, pola serupa memiliki nama yang barusan saya tidak ingat.

EDIT : Ini disebut Idiom Parameter Bernama .

Reunanen
sumber
Saya percaya ini disebut konstruktor.
Tidak diketahui
Tidak, maksud saya hal yang dapat Anda lakukan: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen
Tampaknya ini disebut "inisialisasi gaya Smalltalk yang dirantai", setidaknya di sini: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen
2

Saya berkarat dengan struct, jadi saya mungkin kehilangan beberapa kata kunci di sini. Tetapi mengapa tidak memulai dengan struktur global dengan default yang diinisialisasi, salin ke variabel lokal Anda, lalu modifikasi?

Penginisialisasi seperti:

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

Kemudian saat Anda ingin menggunakannya:

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

Anda dapat mengatasi masalah ini dengan X-Macro

Anda akan mengubah definisi struct Anda menjadi:

#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

Dan kemudian Anda akan dapat dengan mudah menentukan fungsi fleksibel yang menyetel semua bidang ke dont_care.

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

Setelah pembahasan di sini , seseorang juga dapat menentukan nilai default dengan cara ini:

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

Yang diterjemahkan menjadi:

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

Dan gunakan seperti pada jawaban hlovdal , dengan keunggulan maintenance disini lebih mudah yaitu merubah jumlah struct member secara otomatis akan terupdatefoo_DONT_CARE . Perhatikan bahwa koma "palsu" terakhir dapat diterima .

Saya pertama kali mempelajari konsep X-Macro ketika saya harus mengatasi masalah ini .

Ini sangat fleksibel untuk bidang baru yang ditambahkan ke struct. Jika Anda memiliki tipe data yang berbeda, Anda dapat menentukan dont_carenilai yang berbeda tergantung pada tipe datanya: dari sini , Anda dapat mengambil inspirasi dari fungsi yang digunakan untuk mencetak nilai pada contoh kedua.

Jika Anda setuju dengan semua intstruct, maka Anda dapat menghilangkan tipe datanya dari LIST_OF_foo_MEMBERSdan cukup mengubah fungsi X dari definisi struct menjadi#define X(name) int name;

Antonio
sumber
Teknik ini mengeksploitasi bahwa preprocessor C menggunakan pengikatan terlambat dan ini bisa sangat berguna ketika Anda ingin satu tempat untuk mendefinisikan sesuatu (misalnya status) dan kemudian 100% yakin bahwa di mana-mana ketika item yang digunakan selalu dalam urutan yang sama, item baru secara otomatis ditambahkan dan item yang dihapus dihapus. Saya tidak terlalu suka menggunakan nama pendek seperti Xdan biasanya menambahkan _ENTRYnama daftar, mis #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal
1

Cara paling elegan adalah dengan memperbarui bidang struct secara langsung, tanpa harus menggunakan update()fungsi - tapi mungkin ada alasan bagus untuk menggunakannya yang tidak muncul dalam pertanyaan.

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

Atau Anda dapat, seperti yang disarankan Pukku, membuat fungsi akses terpisah untuk setiap bidang struct.

Jika tidak, solusi terbaik yang dapat saya pikirkan adalah memperlakukan nilai '0' dalam bidang struct sebagai tanda 'jangan perbarui' - jadi Anda cukup membuat funciton untuk mengembalikan struct yang dihilangkan, dan kemudian gunakan ini untuk memperbarui.

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

Namun, ini mungkin tidak terlalu layak, jika 0 adalah nilai yang valid untuk bidang di struct.

gnud
sumber
Jaringan adalah alasan yang bagus =) Tetapi jika -1 adalah nilai 'dont care', maka hanya merestrukturisasi fungsi empty_foo (), dan gunakan pendekatan itu?
gnud
maaf, saya salah mengklik downvote dan baru menyadarinya nanti !!! Saya tidak dapat menemukan cara untuk membatalkannya sekarang kecuali ada edit ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功