ค่าเริ่มต้นในโครงสร้าง C


93

ฉันมีโครงสร้างข้อมูลดังนี้:

    Struct foo {
        รหัส int;
        เส้นทาง int;
        int backup_route;
        int current_route;
    }

และฟังก์ชั่นที่เรียกว่า update () ที่ใช้เพื่อร้องขอการเปลี่ยนแปลง

  อัปเดต (42, dont_care, dont_care, new_route);

นี่มันยาวมากและถ้าฉันเพิ่มบางอย่างลงในโครงสร้างฉันต้องเพิ่ม 'dont_care' ในทุก ๆ การเรียกเพื่ออัปเดต (... )

ฉันกำลังคิดที่จะส่ง Struct แทน แต่การกรอก struct ด้วย 'dont_care' ไว้ล่วงหน้านั้นน่าเบื่อยิ่งกว่าการสะกดคำในการเรียกฟังก์ชัน ฉันสามารถสร้างโครงสร้างที่ใดที่หนึ่งด้วยค่าเริ่มต้นของการไม่สนใจและตั้งค่าฟิลด์ที่ฉันสนใจหลังจากที่ฉันประกาศเป็นตัวแปรภายใน

    โครงสร้าง foo = {.id = 42, .current_route = new_route};
    อัปเดต (& bar);

วิธีใดเป็นวิธีที่ดีที่สุดในการส่งต่อข้อมูลที่ฉันต้องการแสดงไปยังฟังก์ชันการอัปเดตเท่านั้น

และฉันต้องการให้ทุกอย่างอื่นเริ่มต้นเป็น -1 (รหัสลับสำหรับ 'ไม่สนใจ')

คำตอบ:


156

แม้ว่ามาโครและ / หรือฟังก์ชัน (ตามที่แนะนำไปแล้ว) จะใช้งานได้ (และอาจมีผลในเชิงบวกอื่น ๆ (เช่น debug hooks)) แต่ก็มีความซับซ้อนมากกว่าที่จำเป็น วิธีแก้ปัญหาที่ง่ายที่สุดและสวยงามที่สุดคือการกำหนดค่าคงที่ที่คุณใช้สำหรับการเริ่มต้นตัวแปร:

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

รหัสนี้แทบไม่มีค่าใช้จ่ายทางจิตใจในการทำความเข้าใจทิศทางและเป็นที่ชัดเจนว่าช่องใดในbarคุณตั้งค่าไว้อย่างชัดเจนในขณะที่ (ปลอดภัย) ละเว้นสิ่งที่คุณไม่ได้ตั้งค่า


6
โบนัสอีกประการหนึ่งของแนวทางนี้คือไม่ต้องพึ่งพาคุณสมบัติ C99 ในการทำงาน
D.Shawley

24
เมื่อฉันเปลี่ยนเป็น 500 บรรทัดนี้ "หลุด" จากโครงการ หวังว่าฉันจะโหวตได้สองครั้งในอันนี้!
Arthur Ulfeldt

4
PTHREAD_MUTEX_INITIALIZERก็ใช้สิ่งนี้เช่นกัน
yingted

ฉันได้เพิ่มคำตอบด้านล่างโดยอาศัย X-Macros เพื่อให้มีความยืดหยุ่นกับจำนวนองค์ประกอบที่มีอยู่ในโครงสร้าง
Antonio

15

คุณสามารถเปลี่ยนค่าลับพิเศษของคุณเป็น 0 และใช้ประโยชน์จากความหมายของสมาชิกโครงสร้างเริ่มต้นของ C

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

จากนั้นจะส่งผ่าน 0 ในฐานะสมาชิกของแถบที่ไม่ได้ระบุในตัวเริ่มต้น

หรือคุณสามารถสร้างมาโครที่จะเริ่มต้นเริ่มต้นให้คุณ:

#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 ทำงานหรือไม่ ฉันคิดว่าคอมไพเลอร์ควรบ่นถ้าคุณเริ่มต้นสมาชิกคนเดียวกันสองครั้ง
Jonathan Leffler

1
เคยลองกับ gcc แล้วก็ไม่บ่น นอกจากนี้ฉันไม่พบสิ่งใดที่ต่อต้านในมาตรฐานอันที่จริงมีตัวอย่างหนึ่งที่กล่าวถึงการเขียนทับโดยเฉพาะ
jpalecek

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: การเริ่มต้นจะเกิดขึ้นในลำดับรายการตัวเริ่มต้นตัวเริ่มต้นแต่ละตัวที่จัดเตรียมไว้สำหรับวัตถุย่อยเฉพาะที่แทนที่ตัวเริ่มต้นที่ระบุไว้ก่อนหน้านี้สำหรับสิ่งเดียวกัน วัตถุย่อย;
altendky

2
หากเป็นจริงคุณไม่สามารถกำหนด DEFAULT_FOO ซึ่งมีตัวเริ่มต้นทั้งหมดได้หรือไม่จากนั้นให้ทำ struct foo bar = {DEFAULT_FOO, .id = 10}; เหรอ?
จอห์น

7

<stdarg.h>อนุญาตให้คุณกำหนดฟังก์ชันตัวแปร (ซึ่งยอมรับอาร์กิวเมนต์ไม่ จำกัด จำนวนเช่นprintf()) ฉันจะกำหนดฟังก์ชันที่รับอาร์กิวเมนต์จำนวนหนึ่งโดยพลการหนึ่งซึ่งระบุคุณสมบัติที่จะอัปเดตและอีกฟังก์ชันหนึ่งที่ระบุค่า ใช้enumหรือสตริงเพื่อระบุชื่อของคุณสมบัติ


สวัสดี @Matt วิธีแมปenumตัวแปรกับstructฟิลด์ Hardcode มัน? structจากนั้นฟังก์ชั่นนี้จะใช้ได้กับเฉพาะ
misssprite

5

อาจพิจารณาใช้นิยามมาโครตัวประมวลผลล่วงหน้าแทน:

#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) ของคุณเป็นแบบโกลบอลคุณก็ไม่จำเป็นต้องใช้พารามิเตอร์นั้นแน่นอน แต่ฉันสมมติว่าคุณอาจมีมากกว่าหนึ่งอินสแตนซ์ การใช้บล็อก ({... }) เป็น GNU-ism ที่ใช้กับ GCC เป็นวิธีที่ดี (ปลอดภัย) ในการรวมเส้นเข้าด้วยกันเป็นบล็อก หากคุณต้องการเพิ่มในมาโครในภายหลังเช่นการตรวจสอบความถูกต้องของช่วงคุณจะไม่ต้องกังวลเกี่ยวกับการทำลายสิ่งต่างๆเช่นคำสั่ง if / else เป็นต้น

นี่คือสิ่งที่ฉันจะทำตามข้อกำหนดที่คุณระบุ สถานการณ์เช่นนี้เป็นสาเหตุหนึ่งที่ทำให้ฉันเริ่มใช้ python เป็นจำนวนมาก การจัดการพารามิเตอร์เริ่มต้นและสิ่งนี้จะง่ายกว่าที่เคยเป็นมามากกับ C. (ฉันเดาว่านั่นคือปลั๊กหลามขออภัย ;-)


4

รูปแบบหนึ่งใช้ gobject คือฟังก์ชันตัวแปรและค่าที่แจกแจงสำหรับคุณสมบัติ อินเทอร์เฟซมีลักษณะดังนี้:

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

เขียน varargs ฟังก์ชั่นเป็นเรื่องง่าย - ดูhttp://www.eskimo.com/~scs/cclass/int/sx11b.html เพียงจับคู่คีย์ -> คู่ค่าและตั้งค่าคุณสมบัติโครงสร้างที่เหมาะสม


4

เนื่องจากดูเหมือนว่าคุณต้องการโครงสร้างนี้สำหรับupdate()ฟังก์ชันเท่านั้นอย่าใช้โครงสร้างนี้เลยมันจะทำให้ความตั้งใจของคุณที่อยู่เบื้องหลังโครงสร้างนั้นสับสนเท่านั้น คุณควรคิดใหม่ว่าเหตุใดคุณจึงเปลี่ยนและอัปเดตฟิลด์เหล่านั้นและกำหนดฟังก์ชันหรือมาโครแยกต่างหากสำหรับการเปลี่ยนแปลง "เล็กน้อย" นี้

เช่น


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

หรือเขียนฟังก์ชันสำหรับทุกกรณีการเปลี่ยนแปลงได้ดียิ่งขึ้น ดังที่คุณสังเกตเห็นแล้วว่าคุณไม่ได้เปลี่ยนทุกคุณสมบัติในเวลาเดียวกันดังนั้นให้เปลี่ยนคุณสมบัติทีละรายการ สิ่งนี้ไม่เพียงช่วยเพิ่มความสามารถในการอ่านเท่านั้น แต่ยังช่วยให้คุณจัดการกับกรณีต่างๆได้อีกด้วยเช่นคุณไม่จำเป็นต้องตรวจหา "dont_care" ทั้งหมดเพราะคุณทราบว่ามีการเปลี่ยนแปลงเฉพาะเส้นทางปัจจุบันเท่านั้น


บางกรณีจะเปลี่ยน 3 รายการในการโทรเดียวกัน
Arthur Ulfeldt

คุณสามารถมีลำดับนี้: set_current_rout (id, route); set_route (id เส้นทาง); apply_change (id); หรือคล้ายกัน สิ่งที่ฉันคิดคือคุณสามารถเรียกใช้ฟังก์ชันเดียวสำหรับตัวตั้งค่าทุกตัว และทำการคำนวณจริง (หรือสิ่งที่เคย) ในภายหลัง
quinmars

4

แล้วสิ่งที่ชอบ:

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

กับ:

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

และ:

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

และตามลำดับ:

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 ++ รูปแบบที่คล้ายกันมีชื่อที่ฉันจำไม่ได้ในตอนนี้

แก้ไข : มันเรียกว่าชื่อพารามิเตอร์สำนวน


ฉันเชื่อว่ามันเรียกว่าตัวสร้าง
Unknown

ไม่ฉันหมายถึงสิ่งที่คุณสามารถทำได้: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen

ดูเหมือนว่าจะเรียกว่า "Chained Smalltalk-style initialization" อย่างน้อยก็ที่นี่: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen

2

ฉันเป็นสนิมกับโครงสร้างดังนั้นฉันอาจจะพลาดคำหลักบางคำที่นี่ แต่ทำไมไม่เริ่มต้นด้วยโครงสร้างส่วนกลางโดยเริ่มต้นค่าเริ่มต้นคัดลอกไปยังตัวแปรภายในของคุณจากนั้นแก้ไข

ตัวเริ่มต้นเช่น:

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

จากนั้นเมื่อคุณต้องการใช้:

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

2

คุณสามารถแก้ไขปัญหาด้วยX-Macro

คุณจะเปลี่ยนนิยามโครงสร้างของคุณเป็น:

#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

dont_careและจากนั้นคุณจะสามารถได้อย่างง่ายดายกำหนดฟังก์ชั่นที่มีความยืดหยุ่นที่กำหนดเขตข้อมูลทั้งหมดไป

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

หลังจากการอภิปรายที่นี่เราสามารถกำหนดค่าเริ่มต้นด้วยวิธีนี้:

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

ซึ่งแปลเป็น:

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

และใช้งานได้ในขณะที่คำตอบ hlovdal , ด้วยความได้เปรียบที่นี่การบำรุงรักษาได้ง่ายขึ้นเช่นการเปลี่ยนแปลงจำนวนสมาชิก struct foo_DONT_CAREจะปรับปรุงโดยอัตโนมัติ โปรดทราบว่าสุดท้าย "ปลอม" จุลภาคเป็นที่ยอมรับ

ฉันได้เรียนรู้แนวคิดของ X-Macros เป็นครั้งแรกเมื่อฉันต้องแก้ไขปัญหานี้

มีความยืดหยุ่นอย่างมากสำหรับฟิลด์ใหม่ที่ถูกเพิ่มเข้าไปในโครงสร้าง หากคุณมีประเภทข้อมูลที่แตกต่างกันคุณสามารถกำหนดdont_careค่าที่แตกต่างกันได้โดยขึ้นอยู่กับประเภทข้อมูลจากที่นี่คุณสามารถใช้แรงบันดาลใจจากฟังก์ชันที่ใช้พิมพ์ค่าในตัวอย่างที่สอง

หากคุณตกลงกับโครงสร้างทั้งหมดintคุณสามารถละเว้นประเภทข้อมูลจากLIST_OF_foo_MEMBERSและเปลี่ยนฟังก์ชัน X ของนิยามโครงสร้างเป็น#define X(name) int name;


เทคนิคนี้ใช้ประโยชน์จากการที่ตัวประมวลผลก่อนหน้า C ใช้การโยงล่าช้าและจะมีประโยชน์มากเมื่อคุณต้องการให้ที่เดียวกำหนดบางสิ่งบางอย่าง (เช่นสถานะอินสแตนซ์) และมั่นใจได้ 100% ว่าทุกที่ที่มีรายการที่ใช้จะอยู่ในลำดับเดียวกันเสมอรายการใหม่ จะถูกเพิ่มและลบรายการโดยอัตโนมัติ ผมไม่ชอบมากเกินไปของการใช้ชื่อสั้น ๆ เช่นXและมักจะผนวกเข้ากับชื่อรายการเช่น_ENTRY #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ...
hlovdal

1

วิธีที่ดีที่สุดคือการอัปเดตฟิลด์ struct โดยตรงโดยไม่ต้องใช้update()ฟังก์ชัน แต่อาจมีเหตุผลที่ดีในการใช้งานที่ไม่พบในคำถาม

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

หรือคุณสามารถสร้างฟังก์ชันการเข้าถึงแยกกันสำหรับแต่ละฟิลด์ของโครงสร้างเช่นเดียวกับที่คุณ Pukku แนะนำ

มิฉะนั้นวิธีแก้ปัญหาที่ดีที่สุดที่ฉันคิดได้คือการรักษาค่า '0' ในฟิลด์ struct เป็นแฟล็ก 'ไม่อัปเดต' ดังนั้นคุณเพียงแค่สร้าง funciton เพื่อส่งคืนโครงสร้างที่เป็นศูนย์จากนั้นใช้สิ่งนี้เพื่ออัปเดต

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

อย่างไรก็ตามสิ่งนี้อาจไม่เป็นไปได้มากนักหาก 0 เป็นค่าที่ถูกต้องสำหรับฟิลด์ในโครงสร้าง


เครือข่ายเป็นเหตุผลที่ดี =) แต่ถ้า -1 เป็นค่า 'ไม่สนใจ' ให้จัดโครงสร้างฟังก์ชัน empty_foo () ใหม่และใช้แนวทางนั้นหรือไม่?
gnud

ขออภัยฉันกดโหวตลงโดยไม่ได้ตั้งใจและสังเกตเห็นในภายหลัง !!! ฉันหาวิธียกเลิกไม่ได้ในตอนนี้เว้นแต่จะมีการแก้ไข ...
Ciro Santilli 郝海东冠状病六四事件法轮功
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.