[C언어] 구조체와 사용자 정의 자료형 1

김민정·2024년 9월 5일
0
post-thumbnail

Chapter 22. 구조체와 사용자 정의 자료형 1

22-1 "구조체란 무엇인가?"

구조체의 정의

구조체(structure)라는 것은 하나 이상의 변수(포인터 변수와 배열 포함)을 묶어서 새로운 자료형을 정의하는 도구다.
즉, 구조체를 기반으로 우리는 새로운 자료형을 정의할 수 있다.

예를 들어, 프로그램상에서 마우스의 좌표정보를 저장하고 관리해야 한다고 생각해보자.
그렇다면 우리는 x좌표, y좌표를 위한 두 개의 변수를 선언해야한다.

int xpos;	// 마우스의 x좌표
int ypos;	// 마우스의 y좌표

하지만, 마우스의 위치라는 하나의 정보를 표현하기 위해서 저 둘을 개별적인 정보로 표현하는 것은 프로그램 상에서의 데이터 표현과 관리의 용이함 측면에서 봤을 때 비효율 적이다.

이러한 이유로 등장한 것이 구조체이며, 다음과 같이 구조체를 정의함으로써 위의 두 변수를 하나로 묶을 수 있다.

struct point	// point라는 이름의 구조체 정의
 {
 	int xpos;
    int ypos;
 };

point라는 이름의 구조체를 정의함으로써 int나 double과 같은 자료형의 이름이 되는 것이다. (새로운 자료형 만든 것)
이를 가리켜 사용자 정의 자료형(user defined data type)이라 한다.
이번에는 사람의 이름과 나이, 전화번호의 정보를 묶을 수 있도록 정의된 구조체를 정의해보자.

struct person	// person이라는 이름의 구조체 정의
{
	char name[20];	// 이름 저장을 위한 멤버
    int age;	// 나이 저장을 위한 멤버
    char phoneNum[20];	// 전화번호 저장을 위한 멤버
};

배열도 값의 저장이 가능한 변수의 성격을 띠기 때문에 구조체의 멤버가 될 수 있다.
구조체를 정의할 때 마지막 ;를 빼먹지 않도록!⭐

구조체 변수의 선언과 접근

앞서 정의한 새로운 자료형들을 대상으로 변수를 선언할 수 있다. 이렇게 선언되는 변수를 가리켜 구조체 변수라 한다.
구조체 변수의 선언의 기본 형태는 다음과 같다.

struct type_name val_name;

구조체 변수를 선언할 때에는 맨 앞에 struct 선언을 추가해야하며 이어서 구조체의 이름과 구조체 변수의 이름을 선언해야 한다.
따라서, 앞서 정의한 point 구조체의 변수 pos를 선언하는 것과, person 구조체의 변수 man을 선언하고자 하면 각각 다음과 같이 문장을 구성해야 한다.

struct point pos;
struct point man;

이 두 변수의 형태는 다음과 같다.

그렇다면 구조체 변수 안에 존재하는 멤버에 접근하기 위한 방법은 어떻게 될까?
접근의 기본 형식은 다음과 같다.

구조체_변수의_이름.구조체_멤버의_이름

어디서 많이 본 형태 아닌가?!
python에서 모듈을 불러오고 그 안에 있는 기능을 사용하려 할 때 저렇게 온점으로 하위 기능들(함수들)을 불러왔었다.
예를 들어서 위 그림의 구조체 변수 pos의 멤버 xpos에 20을 저장하거나, 구조체 변수 man의 멤버 name에 저장된 내용을 출력하려면 각각 다음과 같은 문장을 구성해야 한다.

pos.xpos = 20;
printf("%s \n", man.name);

이렇듯 구조체 변수의 멤버에 접근할 때에는 .연산자를 사용한다.
point 구조체 기반으로 두 점에 대한 x, y 좌표정보를 입력 받고 두 점 사이의 거리를 계산하는 프로그램 예시를 보자.

#include <stdio.h>
#include <math.h>

struct point    // 구조체 point 정의
{
    int xpos;
    int ypos;
};
int main()
{
    struct point pos1, pos2;
    double distance;

    fputs("point1 pos: ", stdout);
    scanf("%d %d", &pos1.xpos, &pos1.ypos);

    fputs("point2 pos: ", stdout);
    scanf("%d %d", &pos2.xpos, &pos2.ypos);

    // Calculate distance
    distance = sqrt(pow(pos2.xpos - pos1.xpos, 2) + pow(pos2.ypos - pos1.ypos, 2));
    /* 교재에서는
    distasnce = sqrt((double)((pos1.xpos-pos2.xpos) * (pos1.xpos-pos2.xpos) + 
    			(pos1.ypos-pos2.ypos) * (pos1.ypos-pos2.ypos)));
    로 정의되어 있다.*/
    
    printf("두 점 사이의 거리는 %g 입니다. \n", distance);

    return 0;
}

> 출력
point1 pos: 1 3
point2 pos: 4 5
두 점 사이의 거리는 3.60555 입니다.

제곱근을 반환하기 위해서 헤더파일 math.h에 선언되어 있는 double sqrt(double x); 함수를 호출했다.
두 점 사이를 구하는 공식을 함수로 구현하여 독립시키는 것이 여러 가지 측면으로 볼 때 더 타당할 수 있다.
함수는 하나의 기능을 지녀야 하는 것으로 정의하면 좋다.

구조체 person 기반의 예제에서는 "구조체의 멤버로 배열이 선언되었기 때문에 배열의 접근 방식을 취하면 되고, 구조체의 멤버로 포인터 변수가 선언되었다면 포인터 변수의 접근 방식을 취하면 되는 구나"를 알면 된다.
예제를 보자.

#include <stdio.h>
#include <string.h>

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

int main()
{
    struct person man1, man2;
    strcpy(man1.name, "안성준");
    man1.age = 23;
    strcpy(man1.phoneNum, "010-1122-3344");

    printf("이름 입력: ");
    scanf("%s", man2.name);
    printf("나이 입력: ");
    scanf("%d", &(man2.age));
    printf("전화번호 입력: ");
    scanf("%s", man2.phoneNum);

    printf("<man1>\n이름: %s \n", man1.name);
    printf("나이: %d \n", man1.age);
    printf("전화번호: %s \n", man1.phoneNum);

    printf("<man2>\n이름: %s \n", man2.name);
    printf("나이: %d \n", man2.age);
    printf("전화번호: %s \n", man2.phoneNum);

    return 0;
}

> 출력
이름 입력: 김수정
나이 입력: 27
전화번호 입력: 010-0001-0002
<man1>
이름: 안성준
나이: 23
전화번호: 010-1122-3344
<man2>
이름: ```	// 왜 디코딩이 안될까...
나이: 27
전화번호: 010-0001-0002

자주 쓰이는 방법은 아니지만, 구조체와 동시에 구조체 변수를 선언하는 방법도 있다.

struct point    // 구조체 point 정의와 변수 세 개 선언
{
    int xpos;
    int ypos;
} pos1, pos2, pos3;

구조체 변수의 초기화

구조체 변수도 선언과 동시에 초기화할 수 있다.
그리고 이 초기화 방법은 배열의 초기화와 유사(동일)하다.
즉, 멤버의 순서대로 초기화할 대상을 나열하면 된다.
예제를 통해 알아보자.

#include <stdio.h>

struct point
{
    int xpos;
    int ypos;
};

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

int main()
{
    struct point pos = {10, 20};
    struct person man = {"이승기", 21, "010-1212-3434"};
    
    printf("Point position: (%d, %d)\n", pos.xpos, pos.ypos);
    printf("Person's information: name - %s, age - %d, phone number - %s \n", man.name, man.age, man.phoneNum);
    
    return 0;
}

> 출력
Point position: (10, 20)
Person's information: name - 이승기, age - 21, phone number - 010-1212-3434

예제를 통해 알 수 있는 것이 초기화 과정에서는 문자열 저장을 위해서 strcpy함수를 호출하지 않아도 된다.
이전 예제에서는 구조체 변수 선언 이후 구조체의 멤버에 문자열을 저장하기 위해서 strcpy함수를 호출해야만 했다.
하지만, 선언과 동시에 초기화 할 때는 멤버에 저장할 데이터를 나열하기만 하면 된다.


22-2 "구조체와 배열 그리고 포인터"

전화번호부의 구현을 위해서는 여러 사람의 정보를 저장해야한다. 그렇다면 다수의 구조체 변수가 필요한데 어떻게 선언하면 될까?

구조체 배열의 선언과 접근

다수의 int형 변수를 선언할 때는 int형 배열의 선언을 했다.
다수의 구조체 변수를 선언할 때에는 구조체 배열의 선언을 생각해봐야한다.
구조체 배열의 선언방법은 일반적인 배열의 선언방법과 동일하다.

// int형 변수와 int형 배열의 선언
int num;
int arr[10];

// 구조체 변수의 선언과 구조체 배열의 선언
struct point pos{....};
struct point arr[10];

만약 struct point arr[4]라는 구조체 배열을 선언한다면 다음과 같은 구조로 배열이 할당된다.

예제를 통해 구조체 배열의 선언과 접근 방법에 대해 알아보자.

#include <stdio.h>

struct point
{
    int xpos, ypos;
};

int main()
{
    struct point arr[3];
    int i;

    for(i=0; i<3; i++)
    {
        printf("점%d의 좌표 입력: ", i+1);
        scanf("%d %d", &arr[i].xpos, &arr[i].ypos);
    }

    for(i=0; i<3; i++)
        printf("(%d, %d) ", arr[i].xpos, arr[i].ypos);
    
    return 0;
}

> 출력
점1의 좌표 입력: 2 42의 좌표 입력: 3 63의 좌표 입력: 8 9
(2, 4) (3, 6) (8, 9)

구조체 배열의 초기화

구조체 변수를 선언과 동시에 초기화할 때에는 중괄호를 통해서 초기화할 값을 명시한다.
비슷한 방법으로 구조체 배열을 선언과 동시에 초기화할 때에는 배열의 길이만큼 중괄호를 이용해서 초기화를 진행하면 된다.

struct person arr[3] = {
	{"강호동", 43, "010-1212-3434"},
    {"이수근", 40, "010-5656-7878"},
    {"은지원", 39, "010-1234-5678"}
};

예제를 통해 한번 더 살펴보자.

#include <stdio.h>

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

int main()
{
    struct person arr[3] = {
        {"강호동", 43, "010-1212-3434"},
        {"이수근", 40, "010-5656-7878"},
        {"은지원", 39, "010-1234-5678"}
    };

    int i;
    for(i=0; i<3; i++)
        printf("이름: %s\t나이: %d\t전화번호: %s\n", arr[i].name, arr[i].age, arr[i].phoneNum);

    return 0;
}

> 출력
이름: 강호동    나이: 43        전화번호: 010-1212-3434
이름: 이수근    나이: 40        전화번호: 010-5656-7878
이름: 은지원    나이: 39        전화번호: 010-1234-5678

여기서 \t은 tab을 의미한다.

구조체 변수와 포인터

구조체 포인터 변수의 선언 및 연산의 방법도 일반적인 포인터 변수의 선언 및 연산의 방법과 비슷하다.

struct point pos = {11, 22};
struct point * pptr = &pos;	// 포인터 변수 pptr이 구조체 변수 pos를 가리킴
(*pptr).xpos = 10;	// pptr이 가리키는 구조체 변수의 멤버 xpos에 10 저장
(*pptr).ypos = 20;	// pptr이 가리키는 구조체 변수의 멤버 ypos에 20 저장

// 다음과도 같이 쓸 수도 있다.
pptr->xpos = 10;
pptr->ypos = 20;

*연산과 .연산을 하나의 ->연산으로 대신할 수 있고 의미도 100% 동일하다.
예제를 통해 더 알아보자.

#include <stdio.h>

struct point
{
    int xpos, ypos;
};

int main()
{
    struct point p1 = {1, 2};
    struct point p2 = {100, 200};
    struct point * pptr = &p1;

    (*pptr).xpos += 4;
    (*pptr).ypos += 5;
    printf("point1 [%d, %d]\n", pptr->xpos, pptr->ypos);

    pptr=&p2;
    pptr->xpos += 1;
    pptr->ypos += 2;
    printf("point2 [%d, %d]\n", (*pptr).xpos, (*pptr).ypos);

    return 0;
}

> 출력
point1 [5, 7]
point2 [101, 202]

포인터 변수를 구조체의 멤버로 선언하기

배열이 구조체의 멤버로 선언될 수 있듯이, 포인터 변수도 구조체의 멤버가 될 수 있다.
설명보다는 예제를 통해 바로 알아보자.

#include <stdio.h>

struct point
{
    int xpos, ypos;
};

struct circle
{
    double radius;
    struct point * center;
};

int main()
{
    struct point cen = {2, 7};
    double rad = 5.5;

    struct circle circ = {rad, &cen};
    printf("원의 반지름: %g \n", circ.radius);
    printf("원의 중심 [%d, %d] \n", circ.center->xpos, circ.center->ypos);

    return 0;
}

> 출력
원의 반지름: 5.5 
원의 중심 [2, 7]

위에서 선언한 구조체 변수 circ과 cen의 관계는 다음 그림과 같다.

TYPE형 구조체 변수의 멤버로 TYPE형 포인터 변수를 둘 수 있다는 것을 알아두어야 한다.
즉, 다음과 같은 선언이 가능다하는 것이다.

struct point
{
	int xpos, ypos;
    struct point * ptr;	// 구조체 point의 포인터 변수 선언
}

삼각형을 이루는 세 점의 연결관계 표현에 대해 다음 예제를 통해 알아보자.

#include <stdio.h>

struct point
{
    int xpos, ypos;
    struct point * ptr;
};

int main()
{
    struct point pos1 = {1, 1};
    struct point pos2 = {2, 2};
    struct point pos3 = {3, 3};

    pos1.ptr = &pos2;   // pos1과 pos2를 연결
    pos2.ptr = &pos3;   // pos2와 pos3을 연결
    pos3.ptr = &pos1;   // pos3과 pos1을 연결

    printf("점의 연결관계... \n");
    printf("[%d, %d]와(과) [%d, %d] 연결\n", pos1.xpos, pos1.ypos, pos1.ptr->xpos, pos1.ptr->ypos);
    printf("[%d, %d]와(과) [%d, %d] 연결\n", pos2.xpos, pos2.ypos, pos2.ptr->xpos, pos2.ptr->ypos);
    printf("[%d, %d]와(과) [%d, %d] 연결\n", pos3.xpos, pos3.ypos, pos3.ptr->xpos, pos3.ptr->ypos);

    return 0;
}

> 출력
[1, 1]() [2, 2] 연결
[2, 2]() [3, 3] 연결
[3, 3]() [1, 1] 연결

구조체 변수의 주소 값과 첫 번째 멤버의 주소 값

구조체 변수의 주소 값은 구조체 변수의 첫 번째 멤버의 주소 값과 동일하다.
예제를 통해 내용을 확인하자.

#include <stdio.h>

struct point
{
    int xpos, ypos;
};

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

int main()
{
    struct point pos = {10, 20};
    struct person man = {"이승기", 21, "010-1212-3434"};

    printf("%p %p \n", &pos, &pos.xpos);
    printf("%p %p \n", &man, man.name);	// 행렬이기 때문에 &연산자 없는 것.

    return 0;
}

> 출력
0061FF18 0061FF18 
0061FEEC 0061FEEC

<Review>

구조체란 것이 상당히 매력적이다...!
여태까지 배운 것들을 하나의 묶음(?)으로 모두 표현이 가능하다는 점과 파이썬의 tuple 느낌인건지...!
애용하고 싶다는 생각이 들었다.🤗
다음 Chapter를 통해 구조체에 대해서 좀 더 알아보자.

profile
백엔드 코린이😁

0개의 댓글

관련 채용 정보