Programming/C

C언어 - 구조체와 사용자 정의 자료형(2)

JunsuKim 2021. 10. 13.
728x90

typedef 선언

typedef 선언은 기존에 존재하는 자료형의 이름에 새 이름을 부여하는 것이다.

예를 들어보자.

typedef int INT;

이는 int라는 자료형의 이름에 INT라는 이름을 추가하는 것이 된다.

따라서

INT num;

이와 같이 해도 int num;과 같은 선언이 되는 것이다.

포인터 변수 또한 선언할 수 있다.

INT *ptr;

typedef 선언에 있어 새로운 이름의 부여는 가장 마지막에 등장하는 단어를 중심으로 이뤄진다.

typedef name1 name2 mane3;

이러한 선언이 있다면 name3가 "name1 name2"에 부여된 새로운 이름이 되는 것이다.

예를 들어보자.

typedef unsigned char UCHAR;

이는 unsigned char의 새로 부여된 이름이 UCHAR가 되는 것이다.

 

이처럼 typedef 선언을 사용하면, 복잡한 유형의 자료형 선언을 매우 간결하게 처리할 수 있다.

typedef로 정의되는 자료형의 이름은 대문자로 시작해야 기본 자료형의 이름과 구분할 수 있기 때문에 대문자로 시작하는 것이 좋다.

구조체에 typedef 선언

구조체 변수의 선언에 있어 typedef 선언을 통해 struct 선언을 생략할 수 있다.

다음과 같은 구조체가 정의돼 있다 하자.

struct point {
    int xpos;
    int ypos;
};

여기서 구조체 변수를 선언하려면

struct point pos;

이와 같다.

이를  typedef 선언을 통해 간결히 하면

typedef struct point Point;
Point pos;

이와 같이 할 수 있다.

또한 구조체 정의와 typedef 선언을 한 번에 묶을 수 있다.

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

구조체 이름 생략

typedef 선언을 한다면 구조체의 이름을 생략해도 된다.

typedef 선언을 하면서 새로운 이름을 부여받기 때문이다.

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

이름을 생략하면

struct point pos;

이와 같은 형태로 구조체 변수를 선언할 수 없다.

하지만 새로 부여받은 이름이 있기 때문에 struct 선언을 추가하면서 변수를 선언할 일은 거의 없을 것이다.

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

#include<stdio.h>

void SimpleFunc(int num) { . . . . }

int main() {
    int age = 20;
    SimpleFunc(age);
    . . . .
}

이와 같은 코드가 실행되면 인자로 전달되는 age의 값은 매개변수에 복사된다.

이처럼 구조체 변수도 함수의 인자로 전달될 수 있고, 인자를 전달받도록 구조체 변수가 매개변수의 선언으로 올 수 있다. 또한 전달되는 구조체 변수의 값은 매개변수에 통째로 복사가 된다.

#include<stdio.h>

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

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

int main() {
    Point pos;
    printf("Input current pos: ");
    scanf("%d %d", &pos.xpos, &pos.ypos);
    ShowPosition(pos);
    return 0;
}

또한 구조체의 멤버로 배열이 선언되어도 동일한 형태의 복사가 진행된다.

인자의 전달과정, 값의 반환과정에서 구조체의 멤버로 선언된 배열도 통째로 복사가 되는 것이다.

#include<stdio.h>

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

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

Person InputInfo() {
    Person man;
    printf("what your name?: ");
    scanf("%s", man.name);
    printf("what your phoneNum?: ");
    scanf("%s", man.phoneNum);
    printf("How old you?: ");
    scanf("%d", &man.age);
    return man;
}

int main() {
    Person info = InputInfo();
    ShowInfo(info);
    return 0;
}

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

기본 자료형 변수를 대상으로는 사칙연산, 비교연산 등 다양한 종류의 연산이 가능하지만,

구조체 변수를 대상으로는 매우 제한된 연산만 허용이 된다.

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

 

구조체 변수를 대상으로 사칙연산도 가능하긴 하나, 구조체 안에는 배열이 존재할 수도 있고 포인터 변수, 다른 구조체의 변수도 존재할 수 있다.

이 때문에 구조체 변수 대상의 사칙연산의 결과를 정형화하는 데는 무리가 있다.

만약 사칙연산을 하고 싶다면 함수를 만들어서 결과를 직접 정의하면 된다.

#include<stdio.h>

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

Point Add(Point p1, Point p2) {
    Point pos = {p1.xpos + p2.xpos, p1.ypos + p2.ypos};
    return pos;
}

Point Sub(Point p1, Point p2) {
    Point pos = {p1.xpos - p2.xpos, p1.ypos - p2.ypos};
    return pos;
}

int main() {
    Point p1 = {1, 2};
    Point p2 = {3, 4};
    Point result;
    
    result = Add(p1, p2);
    printf("[%d, %d]\n", result.xpos, result.ypos);
    result = Sub(p1, p2);
    printf("[%d, %d]\n", result.xpos, result.ypos);
    return 0;
}

구조체를 정의하는 이유

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

#inlcude<stdio.h>

typedef struct student {
    char name[20];
    char studentNum[20];
    char school[20];
    char major[20];
    int year;
} Student;

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

int main() {
    Student arr[7];
    
    for(int i=0; i<7; i++) {
        printf("이름: ");
        scanf("%s", arr[i].name);
        printf("고유번호: ");
        scanf("%s", arr[i].studentNum);
        printf("학교명: ");
        scanf("%s", arr[i].school);
        printf("전공: ");
        scanf("%s", arr[i].major);
        printf("학년: ");
        scanf("%s", &arr[i].year);
    }
    
    for(int i=0; i<7; i++) {
        ShowStudentInfo(&arr[i]);
    }
    return 0;
}

이 코드는 단순히 학생정보를 입력받아 저장하고, 저장된 내용을 출력한다.

이를 구조체를 선언하지 않고 코드를 작성하려 하면 매우 복잡하고 긴 함수를 만들어야 할 것이다.

이 때문에 구조체의 정의가 필요한 것이다.

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

구조체의 멤버로 배열이나 포인터 변수가 선언될 수 있듯이, 구조체 변수도 구조체의 멤버로 선언될 수 있다.

이를 "구조체의 중첩"이라 한다. 

예제를 보자.

#include<stdio.h>

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

typedef struct circle {
    Point cen;
    double radius;
} Circle;
 
 void ShowCircleInfo(Circle * ptr) {
    printf("[%d, %d]\n", (ptr->cen).xpos, (ptr->cen).ypos);
    printf("radius: %g\n", ptr->radius);
}
 
int main() {
    Circle c1 = {{1, 2}, 3.5};
    Circle c2 = {2, 4, 3.9};
    ShowCircleInfo(&c1);
    ShowCircleInfo(&c2);
    return 0;
}

구조체 안의 구조체 멤버를 초기화할 때는 위에 보이듯이 중괄호로 묶어서 초기화를 해도 되고, 나열을 해도 된다.

구조체 변수를 초기화하는 경우 배열의 초기화와 마찬가지로 초기화하지 않은 일부 멤버는 0으로 초기화된다.

공용체

공용체는 union 키워드를 통해 정의한다.

구조체와 비교하면 쉽게 이해할 수 있다.

typedef struct sbox {
    int mem1;
    int mem2;
    double mem3;
} SBox;

typedef union ubox {
    int mem1;
    int mem2;
    int mem3;
} UBox;

위에서 보이듯이 정의 방식에서의 차이점이라고는 struct 선언이냐, union 선언이냐 밖에 없다.

하지만 각각의 변수가 메모리 공간에 할당되는 방식과 접근의 결과에 많은 차이가 있다.

#include<stdio.h>

typedef struct sbox {
    int mem1;
    int mem2;
    double mem3;
} SBox;

typedef union ubox {
    int mem1;
    int mem2;
    double mem3;
} UBox;

int main() {
    SBox sb;
    UBox ub;
    printf("%p %p %p\n", &sb.mem1, &sb.mem2, &sb.mem3);
    printf("%p %p %p\n", &ub.mem1, &ub.mem2, &ub.mem3);
    printf("%d %d\n", sizeof(SBox), sizeof(UBox));
    return 0;
}

결과를 보면 UBox형 변수를 구성하는 멤버들의 주소 값이 동일하다는 것을 알 수 있다. 이는 공용체의 할당 특성의 결과이다.

구조체 변수가 선언되면 구조체를 구성하는 멤버는 각각 할당이 되지만

공용체 변수가 선언되면 공영체를 구성하는 멤버는 각각 할당이 아닌 그중 가장 크기가 큰 멤버의 변수만 하나 할당되어 이를 공유한다.

 

* 공용체는 "하나의 메모리 공간을 둘 이상의 방식으로 접근할 수 있다"라는 장점이 있다.

열거형

열거형 또한 구조체나 공용체처럼 자료형을 정의하는 방법이다.

하지만 구조체, 공용체는 멤버에 저장할 값의 유형을 결정한 방면 열거형은 저장이 가능한 값 자체를 정수의 형태로 결정한다.

enum syllable {
    Do = 1, Re = 2, Mi = 3, Fa = 4, So = 5, La = 6, Ti = 7
};

구조체와 정의 방식에 있어 차이가 있어 보이지만 기본적인 구성은 같다. 

struct 대신 enum이 오고, 자료형의 이름 syllable이 등장했다. 그리고 이와 관련된 내용을 중괄호 안에 선언한다.

 

만약 열거형을 정의하는데 상수의 값을 명시하지 않으면 상수의 값은 0에서부터 시작하여 1씩 증가한다.

enum color {RED, BLUE, WHITE, BLACK};

enum color {RED = 1, BLUE = 2, WHITE = 3, BLACK = 4};

만약 다음과 같이 정의가 돼있다 하자.

enum color {RED = 3, BLUE, WHITE = 6, BLACK};

이 때는 앞에 정의된 상수보다 1이 증가된 값이 할당된다.

enum color {RED = 3, BLUE = 4, WHITE = 6, BLACK = 7};

 

* 열거형은 "둘 이상의 연관이 있는 이름을 상수로 선언함으로써 프로그램의 가독성을 높인다"라는 장점이 있다.

728x90

댓글