본문 바로가기

# IT, Computer Science/C , C++

typedef의 사용

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

c에서는 typedef을 사용하여 기존의 자료형에 새로운 이름을 만들 수 있다. typedef의 일반적인 형식은 다음과 같다.

    typedef old-name new-name;

이러한 새로운 이름은 변수를 선언할 때 사용될 수 있다. 예를 들어, 다음 프로그램에서 smallint는 signed char의 새로운 이름이며, 변수 i를 선언하는데 사용된다.

    #include <stdio.h>

    typedef signed char smallint;

    int main(void) {
        smallint i;

        for(i=0; i<10; i++)
            printf("%d", i);

        return 0;
    }

다음 두 가지를 명심한다. 첫째, typedef는 원래의 이름을 못쓰게 하지 않는다. 예를 들어, 앞의 프로그램에서 signed char는 여전히 유효한 형이다. 둘째, 몇 개의 typedef를 사용하여 같은 자료형을 여러가지 새로운 이름으로 만들 수 있다.

typedef를 사용하는 기본적인 이유는 두 가지이다. 첫째, 여러 시스템 환경에서 사용할 수 있는 프로그램을 만들기 위해서이다. 예를 들어, 16비트 정수를 쓰는 컴퓨터뿐만 아니라 32비트 정수를 쓰는 컴퓨터에서도 실행될 프로그램을 작성하고 싶을 때, 그리고 실행되는 컴퓨터에 관계없이 어떤 변수를 16비트로 사용하고자 하면, 16비트 컴퓨터에서 프로그램을 컴파일할 때는 다음과 같이 typedef를 사용할 수 있다.

    typedef int myint;

이 때, 32비트 컴퓨터에서 프로그램을 컴파일하기 전에, 다음과 같이 typedef를 변경한다.

    typedef short int myint;

이 것은 32비트 정수를 사용하는 컴퓨터에서, short int는 16비트이기 때문에 가능하다. 16비트의 길이를 만들고 싶은 모든 변수들을 선언하기 위해 myint를 사용했다고 가정할 때, myint를 사용하여 선언된 모든 변수들의 형을 변경하기 위해서는 단 하나의 문장만 변경하면 된다.

typedef를 사용하는 두번째 이유는 자체적으로 문서화된 프로그램을 작성하는데 도움을 주기 위해서이다. 예를 들어, 재고관리 프로그램을 작성하려 한다면, 다음과 같은 typedef문을 사용할 수 있다.

    typedef double subtotal;

이제, 이 프로그램을 읽은 사람이면 누구라도 subtotal로 선언된 변수는 소계를 저장하는데 사용된다는 것을 알 것이다.

typedef에는 포인터를 사용한 다음과 같은 용법도 존재한다.

    typedef int *pin;
    pin a;             //int *a와 같다.

.. 자료형에 포인터를 추가하여 정의할 수 있다는 것에 주의할 것.

복잡한 선언

typedef의 일부기능은 #define과 함께 사용될 수 있다. 예를 들어, 다음과 같이 하면

    #define BYTE unsigned char

전처리기가 BYTE를 unsigned char로 대체한다. #define과 함께 사용할 수 없는 기능이 한가지 있다.

    typedef char *STRING;

키워드 typedef가 없다면, 이것은 STRING이 char형을 가리키는 포인터라는 것을 의미한다. 키워드 typedef가 있으면, STRING은 char형을 가리키는 포인터를 의미하는 식별자가 된다. 따라서 다음과 같은 선언은

    STRING name, sign;

다음과 같이 선언하는 것과 같다.

    char *name, *sign;

그렇게 하지 않고 다음과 같이 정의했다고 가정하자.

    #define STRING char *

그러면 다음과 같은 선언은

    STRING name, sign;

다음과 같이 번역될 것이다.

    char *name, sign;

이 경우에는 name만 포인터가 된다. 또한, typedef를 구조체와 함께 사용할 수도 있다.

    typedef struct complex {
        float real;
        float imag;
    } COMPLEX;

이제 struct complex 대신에 COMPLEX형을 사용하여 복소수를 나타낼 수 있다. typedef를 사용하는 한가지 이유는, 자주 쓰이는 데이터형을 위한 편리하고 쉽게 인식할 수 있는 이름을 만드는 것이다. 예를 들면, 대부분의 사람들은 앞의 예에서 STRING이나 그와 동등한 것을 사용하기를 더 선호한다.

typedef를 사용하여 구조체형에 이름을 붙일 때 태그를 생략할 수 있다.

    typedef struct (double x; double y;} rect;

typedef를 다음과 같이 사용한다고 가정하자.

    rect r1 = {3.0, 6.0};
    rect r2;

이것은 다음과 같이 해석된다.

    struct (double x; double y;} r1 = {3.0, 6.0};
    struct {double x; double y;} r2;
    r2 = r1;

동일한 멤버들을 가지는 (이름과 데이터형이 모두 일치하는) 두 구조체가 태그없이 선언된다면, C는 두 구조체가 동일한 데이터형이라고 판단한다. 그러므로 앞의 예에서 r1을 r2에 대입하는 세번째 명령문은 유효하다.

typedef를 사용하는 두번째 이유는, 복잡한 데이터형을 typedef이름으로 간단하게 표현할 수 있기 때문이다. 예를 들어, 다음과 같은 같은 선언은

    typedef char (*FRPTC()) [5];

FRPTC를 char형 5개짜리 배열을 가리키는 포인터를 리턴하는 함수의 데이터형으로 만든다.
typedef를 사용할 때, 새로운 데이터형을 만드는 것이 아니라는 것을 명심하라. 그 대신에 단지 편리한 레이블을 만드는 것이다. 예를 들어, 이것은 우리가 만든 STRING형의 변수를, char형을 가리키는 포인터형을 기대하는 함수의 전달인자로 사용할 수 있다는 것을 의미한다.

C에서는 사용자가 복합한 데이터형도 만들 수 있다. 어떤 선언을 만들 때, 그 이름(또는 식별자)에 변경자를 붙여 선언을 변경할 수 있다.

변경자     의미
*     포인터를 나타낸다.
()     함수를 나타낸다.
[]     배열을 나타낸다.

한 번에 하나 이상의 변경자를 사용할 수 있다. 그것이 다음의 예와 같이, 다양한 데이터형을 만들 수 있게 한다.

    int board[8][8];         // int형 배열의 배열
    int **ptr;               // int형을 가리키는 포인터를 가리키는 포인터
    int *risks[10];          // int형을 가리키는 포인터 10개짜리 배열
    int (*rusks)[10];        // int형 10개짜리 배열을 가리키는 포인터
    int *oof[3][4];          // int형을 가리키는 포인터들의 3x4배열
    int (*uuf)[3][4];        // int형 3x4배열을 가리키는 포인터
    int (*uof[3])[4];        // int형 4개짜리 배열을 가리키는 포인터 3개짜리 배열

이 선언들의 의미를 쉽게 이해하는 방법은, 변경자들을 적용하는 순서를 더듬어 보는 것이다. 예를 들어, 다음과 같은 규칙을 익혀야 한다.

1. 배열을 나타내는 []과 함수를 나타내는 ()는 우선순위가 같다. 이들은 간접연산자 *보다 우선순위가 높다. 그래서 다음과 같은 선언은 risks를 배열을 가리키는 포인터가 아니라 포인터들의 배열로 만든다.

    int *risks[10];

2. []과 ()는 왼쪽으로 오른쪽으로 결합한다. 그러므로 다음과 같은 선언은 goods를 int형 12개짜리 배열을 50개 가지는 배열이 아니라 int형 50개짜리 배열을 12개 가지는 배열을 만든다.

    int goods[12][50];

3. []과 ()는 우선순위가 같다. 그러나 왼쪽에서 오른쪽으로 결합하기 때문에 다음과 같은 선언은 각괄호를 적용하기 전에 *과 rusks를 결합한다. 이것은 rusks를 int형 10개짜리 배열을 가리키는 포인터로 만든다.

    int (*rusks)[10];

이 규칙들을 다음과 같은 선언에 적용해 보자.

    int *oof[3][4];

[3]이 *보다 우선순위가 높고, 왼쪽에서 오른쪽으로 결합되기 때문에 [4]보다도 우선순위가 높다. 그러므로 oof는 3개의 원소를 가지는 배열이다. 그 다음 우선순위는 [4]다. 그래서 oof의 각 원소는 어떤 원소 4개짜리 배열이다. 그러고 나서 그 어떤 원소가 포인터라는 것을 알려준다. int가 이 모든 것을 완성한다. 즉, oop는 int형을 가리키는 포인터 4개짜리 배열을 3개 가지는 배열이다. 요약하면, int형을 가리키는 포인터들의 3x4배열이다. 포인터 12개를 저장할 수 있는 기억공간이 할당된다.

이제 다음과 같은 선언을 살펴보자.

    int (*uuf)[3][4];

괄호가 *변경자를 1순위로 만든다. 이것은 uuf를 int형 3x4배열을 가리키는 포인터로 만든다. 또한 이 규칙들은 다음과 같은 데이터형을 만든다.

    char *fump();        // char형을 가리키는 포인터를 리턴하는 함수
    char (*fump)();      // char형을 리턴하는 함수를 가리키는 포인터
    char (*fump[3])();   // char형을 리턴하는 함수들을 가리키는 포인터 3개짜리 배열

또한 typedef를 사용하여 관련된 데이터형들의 시퀀스를 구성할 수 있다.

    typedef int arr5[5];
    typedef arr5 = p_arr5;
    typedef p_arr5 arrp10[10];
    arr5 togs;                  // togs는 int형 5개짜리 배열
    p_arr5 p2;                  // p2는 int형 5개짜리 배열을 가리키는 포인터
    arrp10 ap;                  // ap는 int형 5개짜리 배열을 가리키는 포인터 10개짜리 배열

예제

typedef를 사용하여 생성된 새로운 이름은 또 다른 이름을 생성하기 위해, 차후의 typedef에서 사용될 수 있다. 예를 들어 다음의 단락을 고려해 보자.

    typedef int height;
    typedef height length;
    typedef length depth;

    depth d;

여기서, d는 여전히 정수이다.

기본적인 자료형 뿐만 아니라 좀 더 복잡한 자료형에도 typedef를 사용할 수 있다. 예를 들어 다음은 유효하다.

    enum e_type { one, two, three };
    typedef e_type mynums;
    mynums num;

여기서, num은 e_type형의 변수이다.

unsigned long을 위한 새로운 이름 UL을 만들어 보아라. UL을 이용하여 변수를 선언하고 값을 치환하고, 그 값을 화면에 출력하는 간단한 프로그램을 작성한다.

    #include <stdio.h>

    typedef unsigned long UL;

    int main(void) {
        UL count;

        count = 312323;

        printf("%lu", count);

        return 0;
    }


    [출처] http://yatoyato.tistory.com/783