C 구조체의 기본값


92

다음과 같은 데이터 구조가 있습니다.

    struct foo {
        int id;
        int 경로;
        int backup_route;
        int current_route;
    }

변경을 요청하는 데 사용되는 update ()라는 함수가 있습니다.

  update (42, dont_care, dont_care, new_route);

이것은 정말 길고 구조에 무언가를 추가하면 update (...)에 대한 모든 호출에 'dont_care'를 추가해야합니다.

대신 구조체를 전달하려고 생각하고 있지만 구조체를 'dont_care'로 미리 채우는 것은 함수 호출에서 철자를 쓰는 것보다 훨씬 더 지루합니다. dont care의 기본값을 사용하여 어딘가에 구조체를 만들고 지역 변수로 선언 한 후에 관심있는 필드를 설정할 수 있습니까?

    struct foo bar = {.id = 42, .current_route = new_route};
    업데이트 (& bar);

내가 표현하고 싶은 정보 만 업데이트 기능에 전달하는 가장 우아한 방법은 무엇입니까?

그리고 나는 다른 모든 것을 기본값으로 -1 ( 'dont care'의 비밀 코드)을 원한다.

답변:


155

매크로 및 / 또는 함수 (이미 제안 된대로)가 작동하지만 (그리고 다른 긍정적 인 효과 (예 : 디버그 후크)를 가질 수 있음) 필요 이상으로 복잡합니다. 가장 간단하고 가장 우아한 해결책은 변수 초기화에 사용하는 상수를 정의하는 것입니다.

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 2009

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

그러면 이니셜 라이저에서 지정되지 않은 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 2009

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

7

<stdarg.h>가변 함수를 정의 할 수 있습니다 (예 : 무한한 수의 인수를 허용 함 printf()). 업데이트 할 속성을 지정하는 인수와 값을 지정하는 인수의 임의 수 쌍을 사용하는 함수를 정의합니다. 사용하여 enum속성의 이름을 지정하거나 문자열입니다.


안녕하세요 @Matt, enum변수를 struct필드 에 매핑하는 방법은 무엇입니까? 하드 코딩 하시겠습니까? 그러면이 기능은 특정 struct.
misssprite 2014

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)의 인스턴스가 전역이면 당연히 매개 변수가 필요하지 않습니다. 하지만 아마도 하나 이상의 인스턴스가 있다고 가정합니다. ({...}) 블록을 사용하는 것은 GCC에 적용되는 GNU-ism입니다. 라인을 블록으로 함께 유지하는 좋은 (안전한) 방법입니다. 나중에 범위 유효성 검사와 같이 매크로에 더 많은 것을 추가해야하는 경우 if / else 문 등과 같은 작업을 중단하는 것에 대해 걱정할 필요가 없습니다.

이것은 당신이 지시 한 요구 사항에 따라 내가 할 일입니다. 이런 상황은 제가 파이썬을 많이 사용하게 된 이유 중 하나입니다. 기본 매개 변수를 처리하는 것은 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 2009

이 시퀀스를 가질 수 있습니다. set_current_rout (id, route); set_route (id, route); apply_change (id); 또는 유사합니다. 내가 생각하는 것은 모든 setter에 대해 하나의 함수 호출을 가질 수 있다는 것입니다. 그리고 나중에 실제 계산 (또는 무엇이든)을 수행하십시오.
quinmars 2009

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 ++에서 비슷한 패턴에 지금은 기억 나지 않는 이름이 있습니다.

편집 : 명명 된 매개 변수 관용구 라고합니다 .


나는 그것이 생성자라고 믿습니다.
알 수 없음

아니요, 저는 당신이 할 수있는 일을 의미했습니다 : update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen 2009

최소한 여기에서 "체인 스몰 토크 스타일 초기화"라고하는 것 같습니다. cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen 2009

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 대답 에서 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

가장 우아한 방법은 update()함수 를 사용하지 않고 구조체 필드를 직접 업데이트하는 것입니다. 하지만 질문에서 오지 않는 이유가있을 수 있습니다.

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

또는 Pukku가 제안한 것처럼 구조체의 각 필드에 대해 별도의 액세스 함수를 만들 수 있습니다.

그렇지 않으면 내가 생각할 수있는 가장 좋은 해결책은 구조체 필드의 '0'값을 '업데이트하지 않음'플래그로 처리하는 것입니다. 따라서 함수를 생성하여 0으로 된 구조체를 반환 한 다음이를 사용하여 업데이트하면됩니다.

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 2009

죄송합니다. 실수로 다운 투표를 클릭했고 나중에 확인했습니다 !!! 편집이 없으면 나는 지금을 취소 할 수있는 방법을 찾을 수 없습니다 ...
치로 틸리가郝海东冠状病六四事件法轮功
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.