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')
sumber
PTHREAD_MUTEX_INITIALIZER
menggunakan ini juga.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);
sumber
<stdarg.h>
memungkinkan Anda untuk menentukan fungsi variadic (yang menerima argumen dalam jumlah tak terbatas, sepertiprintf()
). Saya akan mendefinisikan sebuah fungsi yang mengambil sejumlah sembarang pasangan argumen, yang menentukan properti yang akan diperbarui, dan yang menentukan nilainya. Gunakanenum
atau string untuk menentukan nama properti.sumber
enum
variabel kestruct
lapangan? Hardcode itu? Maka fungsi ini hanya akan berlaku untuk yang spesifikstruct
.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 ;-)
sumber
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.
sumber
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.
sumber
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 .
sumber
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
sumber
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 terupdate
foo_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_care
nilai 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
int
struct, maka Anda dapat menghilangkan tipe datanya dariLIST_OF_foo_MEMBERS
dan cukup mengubah fungsi X dari definisi struct menjadi#define X(name) int name;
sumber
X
dan biasanya menambahkan_ENTRY
nama daftar, mis#define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ...
.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.
sumber