Skip to main content

Command Palette

Search for a command to run...

구조체의 정의와 typedef 선언

Published
9 min readView as Markdown

typedef 선언

typedef 선언은 기존에 존재하는 자료형의 이름에 새 이름을 부여하는 것을 목적으로 하는 선언

// 예를 들어서 다음과 같이 typedef 선언을 하게 되면,
typedef int INT; // int의 또 다른 이름 INT를 부여!
/*
이로 인해 다음의 뜻을 컴파일러에게 전달
"자료형의 이름 int에 INT라는 이름을 추가로 붙여줍니다"
따라서 위의 선언 이후로는 다음의 형태로 int형 변수를 선언할 수 있다.
*/
INT num;  // int num; 과 동일한 선언
INT * ptr;  // int * ptr; 과 동일한 선언
#include <stdio.h>

typedef int INT;
typedef int * PTR_INT;

typedef unsigned int UINT;
typedef unsigned int * PTR_UINT;

typedef unsigned char UCHAR;
typedef unsigned char * PTR_UCHAR;

int main(void)
{
    INT num1 = 120;     // int num1 = 120;
    PTR_INT pnum1 = &num1;      // int * pnum1 = &num1;

    UINT num2 = 190;        // unsinged int num2 = 190;
    PTR_UINT pnum2 = &num2;     // unsigend int * pnum2 = &num2;

    UCHAR ch = 'Z';     // unsigned char ch = 'Z';
    PTR_UCHAR pch = &ch;        // unsigned char * pch = &ch;

    printf("%d, %u, %c\n", *pnum1, *pnum2, *pch);
    return 0;
}

/* 결과
120, 190, Z
*/

구조체의 정의와 typedef 선언

// 구조체 변수의 선언에 있어서 struct 선언을 생략할 수 있는 방법
struct point
{
    int xpos;
    int ypos;
};

typedef struct point Point;  // struct point에 Point라는 이름을 부여!
// 이는 struct point를 대신할 수 있도록 Point라는 이름을 정의한 셈이니, 이후로는 다음과 같이 struct 선언을 생략한 형태로 구조체 변수를 선언할 수 있다
Point pos;
// 때문에 구조체의 정의와 typedef의 선언을 한 데 묶을 수 있고, 이게 일반적인 선언 방식이다.
typedef struct point
{
    int xpos;
    int ypos;
} Point;
#include <stdio.h>

struct point
{
    int xpos;
    int ypos;
};

typedef struct point Point;

typedef struct person
{
    char name[20];
    char phoneNum[20];
    int age;
} Person;

int main(void)
{
    Point pos={10, 20};
    Person man={"이승기", "010-1212-0001", 21};
    printf("%d %d\n", pos.xpos, pos.ypos);
    printf("%s %s %d\n", man.name, man.phoneNum, man.age);
    return 0;
}

/* 결과
10 20
이승기 010-1212-0001 21
*/

구조체의 이름 생략

// 앞서 보인 예제에서는 구조체 person을 다음의 형태로 정의
typedef struct person
{
    char name[20];
    char phoneNum[20];
    int age;
} Person;
/*
이렇게 정의가 되면, 구조체의 이름 person은 사실상 별 의미를 갖지 않게 됌
따라서, 다음과 같이 구조체의 이름을 생략하는 것도 가능
*/
typedef struct
{
   char name[20];
   char phoneNum[20];
   int age;
} Person;

함수로의 구조체 변수 전달과 반환

함수의 인자로 전달되고 return문에 의해 반환되는 구조체 변수

void SimpleFunc(int num) { . . . . }
int main(void)
{
    int age=24;
    SimpleFunc(age);  // age에 저장된 값이 매개뱐수 num에 전달(복사)
    . . . .
}
#include <stdio.h>

typedef struct 
{
    int xpos;
    int ypos;
} Point;

void ShowPosition(Point pos)
{
    printf("[%d, %d]\n", pos.xpos, pos.ypos);
}

Point GetCurrentPosition(void)
{
    Point cen;
    printf("Input current pos: ");
    scanf("%d %d", &cen.xpos, &cen.ypos);
    return cen;
}

int main(void)
{
    Point curPos=GetCurrentPosition();
    ShowPosition(curPos);
    return 0;
}

/* 결과
Input current pos: 2 4
[2, 4]
*/
#include <stdio.h>

typedef struct
{
    char name[20];
    char phoneNum[20];
    int age;
} Person;

void ShowPersonInfo(Person man)
{
    printf("name: %s\n", man.name);
    printf("phone: %s\n", man.phoneNum);
    printf("age: %d\n", man.age);
}

Person ReadPersonInfo(void)
{
    Person man;
    printf("name? "); scanf("%s", man.name);
    printf("phone? "); scanf("%s", man.phoneNum);
    printf("age? "); scanf("%d", &man.age);
    return man;
}

int main(void)
{
    Person man=ReadPersonInfo();
    ShowPersonInfo(man);
    return 0;
}

/* 결과
name? Jung
phone? 010-12XX-34XX
age? 22
name: Jung
phone: 010-12XX-34XX
age: 22
*/
#include <stdio.h>

typedef struct
{
    int xpos;
    int ypos;
} Point;

void OrgSymTrans(Point * ptr)       // 원점대칭
{
    ptr->xpos = (ptr->xpos) * -1;
    ptr->ypos = (ptr->ypos) * -1;
}

void ShowPosition(Point pos)
{
    printf("[%d, %d]\n", pos.xpos, pos.ypos);
}

int main(void)
{
    Point pos={7, -5};
    OrgSymTrans(&pos);      // pos의 값을 원점 대칭이동시킨다.
    ShowPosition(pos);
    OrgSymTrans(&pos);      // pos의 값을 원점 대칭이동시킨다.
    ShowPosition(pos);
    return 0;
}

/* 결과
[-7, 5]
[7, -5]
*/

구조체 변수를 대상으로 가능한 연산

구조체 변수를 대상으로는 매우 제한된 형태의 연산만 허용

허용되는 가장 대표적인 연산은 대입연산이며, 그 이외로 주소 값 반환을 목적으로 하는 & 연산이나 구조체 변수의 크기를 반환하는 sizeof 정도의 연산만 허용

#include <stdio.h>

typedef struct
{
    int xpos;
    int ypos;
} Point;

int main(void)
{
    Point pos1={1, 2};
    Point pos2;
    pos2=pos1;      // pos1의 멤버 대 pos2의 멤버 간 복사가 진행됨

    printf("크기: %lu\n", sizeof(pos1));     // pos1의 전체 크기 반환
    printf("[%d, %d]\n", pos1.xpos, pos1.ypos);
    printf("크기: %lu\n", sizeof(pos2));     // pos2의 전체 크기 반환
    printf("[%d, %d]\n", pos2.xpos, pos2.ypos);
    return 0;
}

/* 결과
크기: 8
[1, 2]
크기: 8
[1, 2]
*/

구조체 안에는 배열도 존재할 수 있고 포인터 변수도 존재할 수 있다. 뿐만 아니라 다른 구조체의 변수도 존재할 수 있다.

따라서 구조체 변수 대상의 덧셈과 뺄셈을 하려면 함수를 정의해야 한다.

#include <stdio.h>

typedef struct 
{
    int xpos;
    int ypos;
} Point;

Point AddPoint(Point pos1, Point pos2)
{
    Point pos={pos1.xpos+pos2.xpos, pos1.ypos+pos2.ypos};
    return pos;
}

Point MinPoint(Point pos1, Point pos2)
{
    Point pos={pos1.xpos-pos2.xpos, pos1.ypos-pos2.ypos};
    return pos;
}

int main(void)
{
    Point pos1={5, 6};
    Point pos2={2, 9};
    Point result;

    result = AddPoint(pos1, pos2);
    printf("[%d, %d]\n", result.xpos, result.ypos);
    result = MinPoint(pos1, pos2);
    printf("[%d, %d]\n", result.xpos, result.ypos);
    return 0;
}

/* 결과
[7, 15]
[3, -3]
*/

구조체의 유용함에 대한 논의와 중첩 구조체

구조체를 정의하는 이유

“구조체를 통해서 연관 있는 데이터를 하나로 묶을 수 있는 자료형을 정의하면, 데이터의 표현 및 관리가 용이해지고, 그만큼 합리적인 코드를 작성할 수 있게 된다.”

#include <stdio.h>

typedef struct 
{
    char name[20];      // 학생 이름
    char stdnum[20];    // 학생 고유번호
    char school[20];    // 학교 이름
    char major[20];     // 선택 전공
    int year;           // 학년
} Student;

void ShowStudentInfo(Student * sptr)
{
    printf("학생 이름: %s\n", sptr->name);
    printf("학생 고유번호: %s\n", sptr->stdnum);
    printf("학교 이름: %s\n", sptr->school);
    printf("선택 전공: %s\n", sptr->major);
    printf("학년: %d\n", sptr->year);
}

int main(void)
{
    Student arr[7];
    int i;

    for(i=0 ; i<7 ; i++)
    {
        printf("이름: "); scanf("%s", arr[i].name);
        printf("번호: "); scanf("%s", arr[i].stdnum);
        printf("학교: "); scanf("%s", arr[i].school);
        printf("전공: "); scanf("%s", arr[i].major);
        printf("학년: "); scanf("%d", &arr[i].year);
    }

    for(i=0 ; i<7 ; i++)
        ShowStudentInfo(&arr[i]);
    return 0;
}

중첩된 구조체의 정의와 변수의 선언

구조체 안에 구조체 변수가 멤버로 존재하는 경우를 가리켜 ‘구조체의 중첩’이라 한다.

#include <stdio.h>

typedef struct 
{
    int xpos;
    int ypos;
} Point;

typedef struct 
{
    Point cen;
    double rad;
} Circle;

void ShowCircleInfo(Circle * cptr)
{
    printf("[%d, %d]\n", (cptr->cen).xpos, (cptr->cen).ypos);
    printf("radius: %g \n\n", cptr->rad);
}

int main(void)
{
    Circle c1={{1, 2}, 3.5};
    Circle c2={2, 4, 3.9};
    ShowCircleInfo(&c1);
    ShowCircleInfo(&c2);
    return 0;
}

/* 결과
[1, 2]
radius: 3.5 

[2, 4]
radius: 3.9
*/

공용체(Union Type)의 정의와 의미

구조체 vs 공용체

typedef struct sbox  // 구조체 sbox의 정의
{
    int mem1;
    int mem2;
    double mem3;
} SBox;

typedef union ubox  // 공용체 ubox의 정의
{
    int mem1;
    int mem2;
    double mem3;
} UBox;
/* 위의 구조체와 공용체를 대상으로 각각 다음 연산을 하면, */
printf("%d \n", sizeof(SBox));  // 16 출력
printf("%d \n", sizeof(UBox));  // 8 출력
/* 여기서 16은 모든 멤버의 크기를 합한 결과이고, 8은 멤버 중에서 가장 크기가 큰 double의 크기만 계산된 결과 */
#include <stdio.h>

typedef struct sbox     // 구조체 sbox의 정의
{
    int mem1;
    int mem2;
    double mem3;
} SBox;

typedef union ubox      // 공용체 ubox의 정의
{
    int mem1;
    int mem2;
    double mem3;
} UBox;

int main(void)
{
    SBox sbx;
    UBox ubx;
    printf("%p %p %p\n", &sbx.mem1, &sbx.mem2, &sbx.mem3);
    printf("%p %p %p\n", &ubx.mem1, &ubx.mem2, &ubx.mem3);
    printf("%lu %lu\n", sizeof(SBox), sizeof(UBox));
    return 0;
}

/* 결과
0x16f5a2f58 0x16f5a2f5c 0x16f5a2f60
0x16f5a2f50 0x16f5a2f50 0x16f5a2f50
16 8
UBox형 변수를 구성하는 멤버 mem1, mem2, mem3의 주소 값이 동일하다
공용체 변수가 선언되면, 공용체를 구성하는 멤버는 각각 할당되지 않고, 그 중 크기가 가장 큰 멤버의 변수만 하나 할당되어
공유하게 된다.
*/
#include <stdio.h>

typedef union ubox      // 공용체 ubox의 정의
{
    int mem1;
    int mem2;
    double mem3;
} UBox;

int main(void)
{
    UBox ubx;       // 8 바이트 메모리 할당
    ubx.mem1=20;        // 상위 4 바이트의 메모리 공간에 20을 저장
    printf("%d\n", ubx.mem2);
    /* mem2는 int형 변수이므로 이 이름으로 접근할 경우 상위 4바이트의 메모리 공간을 참조하게 된다. 그런데 앞서 이 공간에 20을 저장했으므로 20이 출력*/
    ubx.mem3=7.15;
    /* mem3에 실수를 저장, 결과적으로 13행을 통해 저장된 값을 덮어써버리게 됌 */
    printf("%d\n", ubx.mem1);
    printf("%d\n", ubx.mem2);
    /* 실수를 저장하면서 덮어버렸기 때문에, 상위 4 바이트를 읽어서 출력하면 알 수 없는 값이 출력 */
    printf("%g\n", ubx.mem3);
    return 0;
}

/* 결과
20
-1717986918
-1717986918
7.15
*/

공용체의 유용함은 다양한 접근방식을 제공하는 데 있습니다.

공용체의 유용함은 ‘하나의 메모리 공간을 둘 이상의 방식으로 접근할 수 있다'

/* 상위 2 바이트와 하위 2 바이트 값을 양의 정수, 상위 1 바이트와 하위 1 바이트에 저장된 값의 아스키 문자 */
#include <stdio.h>

typedef struct
{
    unsigned short upper;
    unsigned short lower;
} DBShort;

typedef union
{
    int iBuf;
    char bBuf[4];
    DBShort sBuf;
} RDBuf;

int main(void)
{
    RDBuf buf;
    printf("정수 입력: ");
    scanf("%d", &(buf.iBuf));

    printf("상위 2 바이트: %u\n", buf.sBuf.upper);
    printf("하위 2 바이트: %u\n", buf.sBuf.lower);
    printf("상위 1 바이트 아스키 코드: %c\n", buf.bBuf[0]);
    printf("하위 1 바이트 아스키 코드: %c\n", buf.bBuf[3]);
    return 0;
}

/* 결과
정수 입력: 1145258561
상위 2 바이트: 16961
하위 2 바이트: 17475
상위 1 바이트 아스키 코드: A
하위 1 바이트 아스키 코드: D

공용체의 적절한 정의를 통해서 4 바이트 메모리 공간을 2 바이트씩, 그리고 1 바이트씩 접근할 수 있다.
*/

열거형의 정의와 의미

열거형의 정의와 변수의 선언

/* 열거형으로 syllable이라는 이름의 자료형을 정의한다는 것은 다음의 의미를 지닌다. 
"syllable형 변수에 저장이 가능한 정수 값들을 결정하겠다!" 
열거형의 경우에는 저장이 가능한 값 자체를 정수의 형태로 결정 */
enum syllable  // syllable이라는 이름의 열거형 정의
{
    Do=1, Re=2, Mi=3, Fa=4, So=5, La=6, Ti=7
};
/*
"Do를 정수 1을 의미하는 상수로 정의, 그리고 이 값은 syllable형 변수에 저장이 가능"
*/
#include <stdio.h>

typedef enum syllable       // typedef 선언이 추가된 열거형의 정의
{
    Do=1, Re=2, Mi=3, Fa=4, So=5, La=6, Ti=7
} Syllable;

void Sound(Syllable sy)
{
    switch(sy)
    {
        case Do:
            puts("도는 하얀 도라지"); return;
        case Re:
            puts("레는 둥근 레코드"); return;
        case Mi:
            puts("미는 파란 미나리"); return;
        case Fa:
            puts("파는 예쁜 파랑새"); return;
        case So:
            puts("솔은 작은 솔방울"); return;
        case La:
            puts("라는 라디오고요~"); return;
        case Ti:
            puts("시는 졸졸 시냇물"); return;
    }
    puts("다 함께 부르세~ 도레미파 솔라시도 홀 도~ 짠~");
}

int main(void)
{
    Syllable tone;
    for(tone=Do ; tone<=Ti ; tone++)
        Sound(tone);
    return 0;
}

/* 결과
도는 하얀 도라지
레는 둥근 레코드
미는 파란 미나리
파는 예쁜 파랑새
솔은 작은 솔방울
라는 라디오고요~
시는 졸졸 시냇물

위 반복문에서 Do와 Ti는 각각 1과 7을 의미하는 상수이니
for(tone=1 ; tone<=7 ; tone++)
로 대신해도 그 결과는 동일하다.
*/

열거형 상수의 값이 결정되는 방식

열거형을 정의하는데 있어서 상수의 값을 명시하지 않으면

enum color {RED, BLUE, WHITE, BLACK};
/* 위 정의에는 상수의 이름만 선언되었을 뿐 값은 선언되어 있지 않다. 이러한 경우 열거형 상수의 값은 0에서부터 1씩 증가하는 형태로 결정 */
enum color {RED=0, BLUE=1, WHITE=2, BLACK=3}; // 위와 동일
/* 이번에는 정의 형태가 조금 다른 예를 보자. */
enum color {RED=3, BLUE, WHITE=6, BLACK};
/* 값이 선언되지 않으면 앞서 선언된 상수보다 1이 증가된 값이 할당 */
enum color {RED=3, BLUE=4, WHITE=6, BLACK=7}; // 위와 동일

열거형의 유용함은 이름있는 상수의 정의를 통한 의미의 부여에 있습니다.

“Do, Re, Mi와 같이 연관이 있는 이름을 동시에 상수로 선언할 수 있다.”

“둘 이상의 연관이 있는 이름을 상수로 선언함으로써 프로그램의 가독성을 높이는데 있다.”

More from this blog

선행처리기와 매크로

선행처리는 컴파일 이전의 처리를 의미 선행처리는 선행처리기에 의해서, 컴파일은 컴파일러에 의해서, 링크는 링커에 의해서 진행 소스파일 → 선행처리기 → 선행처리 거친 소스파일 → 컴파일러 → 오브젝트 파일 → 링커 → 실행파일 컴파일 과정을 거치게 되면 바이너리 데이터로 이루어진 오브젝트 파일이 생성 선행처리기가 하는 일 삽입해 놓은 선행처리 명령문대로 소스코드의 일부를 수정 여기서 수정이란, 단순 치환의 형태 /* 간단한 선행처리 명령문 */...

Oct 10, 20246 min read

C 언어의 메모리 구조

프로그램을 실행하면 해당 프로그램의 실행을 위한 메모리 공간이 운영체제에 의해서 미리 마련 그리고 바로 이 메모리 공간 내에서 변수가 선언되고, 문자열이 선언되는 것 메모리의 구성 프로그램 실행 시 운영체제에 의해서 마련되는 메모리의 구조는 네 개의 영역으로 구분 코드 영역 / 데이터 영역 / 힙 영역 / 스택 영역 메모리 공간을 나눠놓은 이유는 유사한 성향의 데이터를 묶어서 저장을 하면, 관리가 용이해지고 메모리의 접근속도가 향상 메모리 영역...

Oct 9, 20246 min read

파일과 스트림(Stream), 그리고 기본적인 파일의 입출력

저기 저 파일에 저장되어 있는 데이터를 읽고 싶어요. 프로그램 상에서 파일에 저장되어 있는 데이터를 참조하길 원한다고(읽기 원한다고) 가정했을 때, 우리가 구현한 프로그램과 참조할 데이터가 저장되어 있는 파일 사이에 데이터가 이동할 수 있는 다리를 놓는 일을 먼저 해야 한다. 이러한 데이터 이동의 경로가 되는 다리를 가리켜 ‘스트림(stream)’이라 함. 스트림이라는 것은 운영체제에 의해서 형성되는 소프트웨어적인 상태를 의미하는 것 따라서, ...

Oct 8, 20249 min read

JinGood

31 posts