Giá trị mặc định trong C Struct


92

Tôi có cấu trúc dữ liệu như sau:

    struct foo {
        int id;
        tuyến đường int;
        int backup_route;
        int current_route;
    }

và một hàm được gọi là update () được sử dụng để yêu cầu các thay đổi trong nó.

  cập nhật (42, dont_care, dont_care, new_route);

điều này thực sự dài và nếu tôi thêm thứ gì đó vào cấu trúc, tôi phải thêm 'dont_care' vào MỌI cuộc gọi để cập nhật (...).

Tôi đang suy nghĩ về việc chuyển cho nó một cấu trúc thay thế nhưng việc điền trước cấu trúc bằng 'dont_care' thậm chí còn tẻ nhạt hơn là chỉ đánh vần nó trong lệnh gọi hàm. Tôi có thể tạo cấu trúc ở đâu đó với giá trị mặc định là dont care và chỉ đặt các trường tôi quan tâm sau khi tôi khai báo nó dưới dạng biến cục bộ không?

    struct foo bar = {.id = 42, .current_route = new_route};
    cập nhật (& bar);

Cách thanh lịch nhất để chỉ chuyển thông tin tôi muốn thể hiện đến chức năng cập nhật là gì?

và tôi muốn mọi thứ khác mặc định là -1 (mã bí mật cho 'dont care')

Câu trả lời:


155

Mặc dù macro và / hoặc các hàm (như đã được đề xuất) sẽ hoạt động (và có thể có các tác động tích cực khác (tức là các móc gỡ lỗi)), chúng phức tạp hơn mức cần thiết. Giải pháp đơn giản và có thể là thanh lịch nhất là chỉ cần xác định một hằng số mà bạn sử dụng để khởi tạo biến:

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

Mã này hầu như không có chi phí tinh thần để hiểu hướng dẫn và rất rõ ràng những trường barnào bạn đặt một cách rõ ràng trong khi (một cách an toàn) bỏ qua những trường bạn không đặt.


6
Một điểm cộng khác của phương pháp này là nó không dựa vào các tính năng của C99 để hoạt động.
D.Shawley

24
Khi tôi thay đổi đến 500 dòng này "rơi ra" khỏi dự án. Ước gì tôi có thể bỏ phiếu hai lần cho cái này!
Arthur Ulfeldt

4
PTHREAD_MUTEX_INITIALIZERcũng sử dụng cái này.
yingted

Tôi đã thêm một câu trả lời bên dưới dựa vào X-Macros để linh hoạt trên số lượng phần tử có trong cấu trúc.
Antonio

15

Bạn có thể thay đổi giá trị đặc biệt bí mật của mình thành 0 và khai thác ngữ nghĩa thành viên cấu trúc mặc định của C.

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

sau đó sẽ chuyển 0 dưới dạng các thành viên của thanh không xác định trong trình khởi tạo.

Hoặc bạn có thể tạo một macro sẽ thực hiện khởi tạo mặc định cho bạn:

#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 có hoạt động không? Tôi nghĩ rằng trình biên dịch sẽ phàn nàn nếu bạn khởi tạo cùng một thành viên hai lần.
Jonathan Leffler

1
Tôi đã thử nó với gcc và nó không phàn nàn. Ngoài ra, tôi không tìm thấy bất kỳ điều gì chống lại nó trong tiêu chuẩn, trên thực tế, có một ví dụ mà việc ghi đè được đề cập cụ thể.
jpalecek

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: Quá trình khởi tạo sẽ diễn ra theo thứ tự danh sách trình khởi tạo, mỗi trình khởi tạo được cung cấp cho một subobject cụ thể sẽ ghi đè bất kỳ trình khởi tạo nào được liệt kê trước đó cho cùng một subobject;
altendky 14/12/12

2
Nếu điều này là đúng, bạn không thể xác định DEFAULT_FOO, có tất cả các trình khởi tạo, và sau đó thực hiện struct foo bar = {DEFAULT_FOO, .id = 10}; ?
John

7

<stdarg.h>cho phép bạn xác định các hàm khác nhau (chấp nhận một số lượng vô hạn đối số, chẳng hạn như printf()). Tôi sẽ định nghĩa một hàm lấy một số cặp đối số tùy ý, một hàm chỉ định thuộc tính sẽ được cập nhật và một hàm chỉ định giá trị. Sử dụng một enumhoặc một chuỗi để chỉ định tên của thuộc tính.


Xin chào @Matt, làm cách nào để ánh xạ enumcác biến vào structtrường? Hardcode nó? Sau đó, chức năng này sẽ chỉ được áp dụng cho cụ thể struct.
misssprite

5

Có thể xem xét sử dụng định nghĩa macro bộ xử lý trước để thay thế:

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

Nếu phiên bản (struct foo) của bạn là toàn cục, thì tất nhiên bạn không cần tham số cho nó. Nhưng tôi cho rằng bạn có thể có nhiều hơn một trường hợp. Sử dụng khối ({...}) là GNU-ism áp dụng cho GCC; đó là một cách hay (an toàn) để giữ các dòng lại với nhau như một khối. Nếu sau này bạn cần thêm nhiều macro, chẳng hạn như kiểm tra xác thực phạm vi, bạn sẽ không phải lo lắng về việc vi phạm những thứ như câu lệnh if / else, v.v.

Đây là những gì tôi sẽ làm, dựa trên các yêu cầu bạn đã chỉ ra. Những tình huống như thế này là một trong những lý do mà tôi bắt đầu sử dụng python rất nhiều; việc xử lý các tham số mặc định và như vậy trở nên đơn giản hơn bao giờ hết với C. (Tôi đoán đó là một trình cắm python, xin lỗi ;-)


4

Một mẫu mà gobject sử dụng là một hàm đa dạng và các giá trị được liệt kê cho mỗi thuộc tính. Giao diện trông giống như sau:

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

Viết một hàm varargs thật dễ dàng - xem http://www.eskimo.com/~scs/cclass/int/sx11b.html . Chỉ cần khớp các cặp khóa -> giá trị và đặt các thuộc tính cấu trúc thích hợp.


4

Vì có vẻ như bạn chỉ cần cấu trúc này cho update()hàm, không sử dụng cấu trúc nào cho hàm này, nó sẽ chỉ làm xáo trộn ý định của bạn đằng sau cấu trúc đó. Bạn có thể nên suy nghĩ lại tại sao bạn lại thay đổi và cập nhật các trường đó và xác định các hàm hoặc macro riêng biệt cho những thay đổi "nhỏ" này.

ví dụ


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

Hoặc tốt hơn là viết một hàm cho mọi trường hợp thay đổi. Như bạn đã nhận thấy, bạn không thay đổi mọi thuộc tính cùng một lúc, vì vậy hãy chỉ thay đổi một thuộc tính tại một thời điểm. Điều này không chỉ cải thiện khả năng đọc mà còn giúp bạn xử lý các trường hợp khác nhau, ví dụ như bạn không phải kiểm tra tất cả "dont_care" vì bạn biết rằng chỉ có tuyến đường hiện tại được thay đổi.


một số trường hợp sẽ thay đổi 3 trong số đó trong cùng một cuộc gọi.
Arthur Ulfeldt

Bạn có thể có chuỗi này: set_current_rout (id, route); set_route (id, route); apply_change (id); hoặc tương tự. Điều tôi nghĩ là bạn có thể có một lệnh gọi hàm cho mỗi bộ cài. Và thực hiện tính toán thực sự (hoặc những gì đã từng) vào thời điểm sau đó.
quinmars

4

Làm thế nào về một cái gì đó như:

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

với:

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

và:

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

và tương ứng:

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

Trong C ++, một mẫu tương tự có tên mà tôi không nhớ bây giờ.

CHỈNH SỬA : Nó được gọi là Thành ngữ tham số được đặt tên .


Tôi tin rằng nó được gọi là một hàm tạo.
Không xác định

Không, ý tôi là điều bạn có thể làm: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen

Nó dường như được gọi là "khởi tạo kiểu Smalltalk chuỗi", ít nhất là ở đây: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen

2

Tôi rất quen thuộc với các cấu trúc, vì vậy có lẽ tôi đang thiếu một vài từ khóa ở đây. Nhưng tại sao không bắt đầu với cấu trúc toàn cục với các giá trị mặc định được khởi tạo, sao chép nó vào biến cục bộ của bạn, sau đó sửa đổi nó?

Trình khởi tạo như:

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

Sau đó, khi bạn muốn sử dụng nó:

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

2

Bạn có thể giải quyết vấn đề với X-Macro

Bạn sẽ thay đổi định nghĩa cấu trúc của mình thành:

#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

Và sau đó, bạn sẽ có thể dễ dàng xác định một hàm linh hoạt đặt tất cả các trường thành dont_care.

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

Sau cuộc thảo luận ở đây , người ta cũng có thể xác định một giá trị mặc định theo cách này:

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

Dịch thành:

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

Và sử dụng nó như trong câu trả lời hlovdal , với ưu điểm là ở đây việc bảo trì dễ dàng hơn, tức là thay đổi số lượng thành viên struct sẽ tự động cập nhậtfoo_DONT_CARE . Lưu ý rằng dấu phẩy "giả" cuối cùng được chấp nhận .

Lần đầu tiên tôi biết đến khái niệm X-Macros khi tôi phải giải quyết vấn đề này .

Nó cực kỳ linh hoạt đối với các trường mới được thêm vào cấu trúc. Nếu bạn có các kiểu dữ liệu khác nhau, bạn có thể xác địnhdont_care giá trị tùy thuộc vào kiểu dữ liệu: từ đây , bạn có thể lấy cảm hứng từ hàm được sử dụng để in các giá trị trong ví dụ thứ hai.

Nếu bạn đồng ý với all intstruct, thì bạn có thể bỏ qua kiểu dữ liệu LIST_OF_foo_MEMBERSvà chỉ cần thay đổi hàm X của định nghĩa struct thành#define X(name) int name;


Kỹ thuật này khai thác rằng bộ tiền xử lý C sử dụng liên kết muộn và điều này có thể rất hữu ích khi bạn muốn một nơi duy nhất để xác định một thứ gì đó (ví dụ: các trạng thái) và sau đó chắc chắn 100% rằng mọi nơi khi các mục đã sử dụng luôn có cùng thứ tự, các mục mới được tự động thêm vào và xóa các mục đã xóa. Tôi không quá thích sử dụng những cái tên ngắn như Xvà thường thêm _ENTRYvào tên danh sách, ví dụ #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal

1

Cách tốt nhất là cập nhật trực tiếp các trường struct mà không cần phải sử dụng update()hàm - nhưng có thể có những lý do chính đáng để sử dụng nó mà bạn không gặp trong câu hỏi.

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

Hoặc bạn có thể, như Pukku đã đề xuất, tạo các hàm truy cập riêng biệt cho từng trường của cấu trúc.

Nếu không, giải pháp tốt nhất mà tôi có thể nghĩ đến là coi giá trị '0' trong trường struct như một cờ 'không cập nhật' - vì vậy bạn chỉ cần tạo một funciton để trả về một cấu trúc bị xóa và sau đó sử dụng nó để cập nhật.

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

Tuy nhiên, điều này có thể không khả thi lắm, nếu 0 là giá trị hợp lệ cho các trường trong cấu trúc.


Mạng là một lý do chính đáng =) Nhưng nếu -1 là giá trị 'dont care', thì chỉ cần cấu trúc lại hàm void_foo () và sử dụng cách tiếp cận đó?
gnud

xin lỗi, tôi đã nhấp vào biểu tượng xuống do nhầm lẫn và chỉ nhận thấy sau đó !!! Tôi không thể tìm thấy bất kỳ cách nào để hoàn tác điều đó ngay bây giờ trừ khi có chỉnh sửa ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.