[C++] 구조체(structure type) 개념 및 활용

2022. 6. 17. 12:39♣ C++

이번 포스팅에선 구조체에 대해 알아보도록 하겠습니다.

구조체 : 사용자 정의 타입

다양한 타입의 변수 집합을 하나의 타입으로 나타내는 것이 구조체입니다.

예를 들어, 책 제목/ 저자/ 가격의 집합을 하나의 book 타입으로 정의하는 것이 구조체입니다.

 

개발 환경 : VSCode, Windows 10

 

 

 

 

 

기본 개발 세팅은 다음과 같습니다.

#include <iostream>
#include <string.h>
#include <string>
using namespace std;

int main()
{
	return 0;
}

 

 

 

 

 

📌 구조체 선언하기

 

구조체는 main 함수 밖에서 선언합니다. 

이번 포스팅에선 Book이란 이름으로 선언하도록 하겠습니다.

struct 구조체이름 {};

 

Book 구조체 선언

struct Book
{
    char title[30];
    char author[20];
    int price;
    int year;
};


int main()
{    
    return 0;
}

Book 구조체 안에는 title, author 문자열과 price, year 정수형 데이터가 들어갈 수 있습니다. 

 

 

 

 

📌 구조체 변수 선언 & 초기화

 

구조체를 선언했다면, 그 타입의 변수를 선언해야 합니다.

struct 구조체이름 변수이름;
struct 구조체이름 변수이름 = {내용물};

 

2권의 소설을 변수로 선언하고, 초기화해보았습니다.

struct Book
{
    char title[30];
    char author[20];
    int price;
    int year;
};


int main()
{    
    struct Book fiction = {"kangmong", "kinger", 12000, 1940};
    struct Book fiction_2 = {"love is fair", "mong", 12000};
    cout << "소설의 이름은 " << fiction.title << ", 소설의 작가는 " << fiction.author << ", 소설의 가격은 " << fiction.price << ", 소설의 발행년도는 " << fiction.year << " 입니다." << endl;
    cout << "소설의 이름은 " << fiction_2.title << ", 소설의 작가는 " << fiction_2.author << ", 소설의 가격은 " << fiction_2.price << ", 소설의 발행년도는 " << fiction_2.year << " 입니다." << endl;
    return 0;
}

 

결과

소설의 이름은 kangmong, 소설의 작가는 kinger, 소설의 가격은 12000, 소설의 발행년도는 1940 입니다.
소설의 이름은 love is fair, 소설의 작가는 mong, 소설의 가격은 12000, 소설의 발행년도는 0 입니다.

 

 

 

💥 주의해야 할 점

구조체 변수를 초기화한다고 생각해봅시다. fiction_2처럼 가격까지만 초기화한 경우, 나머지 값인 year에 대해서는 자동으로 0으로 초기화됩니다. 이점을 숙지해야 합니다.

 

 

 

 

📌 구조체 활용

 

1. 함수와 구조체

함수의 인수로 구조체를 전달할 수 있습니다. 

전달받은 인수를 빼는 함수를 구현하고, 구조체를 전달해봅시다.

 

struct Book
{
    char title[30];
    char author[20];
    int price;
    int year;
};

int Calculator(int,int);

main 함수를 호출하기 전, struct 구조체를 선언하고 함수 원형을 선언했습니다.

 

int main()
{    
    struct Book fiction = {"kangmong", "kinger", 12000, 1940};
    struct Book fiction_2 = {"love is fair", "mong", 10500};
    int CalcPrice;
    CalcPrice = Calculator(fiction.price, fiction_2.price);
    cout << "두 소설책의 값 차이는 " << CalcPrice << " 입니다." << endl;
    return 0;
} 

int Calculator(int a, int b){
    return a - b;
}

CalcPrice 변수를 만들고, 빼기 함수의 결과 값을 받도록 했습니다.

 

 

결과

두 소설책의 값 차이는 1500 입니다.

구조체를 함수의 인수로 넣을 수 있음을 확인했습니다. 

다만, 구조체를 전달하는 것보다 구조체의 주소를 직접 전달하는 것이 함수의 속도를 더 높일 것입니다.

구조체를 전달하면 컴퓨터가 다시 구조체의 주소를 (1) 찾아서 (2) 접근하지만, 구조체의 주소를 전달하면 (1) 바로 접근만 하면 되기 때문입니다.

 

 

 

 

2. 함수의 인수로 구조체의 주소를 직접 전달하기

실행 속도를 더 높이기 위해 구조체의 주소를 직접 전달해봅시다.

 

struct Book
{
    char title[30];
    char author[20];
    int price;
    int year;
};

int Calculator(Book*, Book*);

이번엔 구조체 포인터를 직접 인수로 전달합니다.

 

int main()
{    
    struct Book fiction = {"kangmong", "kinger", 12000, 1940};
    struct Book fiction_2 = {"love is fair", "mong", 10500};
    int CalcPrice;
    CalcPrice = Calculator(&fiction, &fiction_2);
    cout << "두 소설책의 값 차이는 " << CalcPrice << " 입니다." << endl;
    return 0;
} 

int Calculator(Book* fiction1, Book* fiction2){
    return (fiction1->price - fiction2->price);
}

구조체 변수 fiction과 fiction_2의 주소 값을 Calculator의 인수로 전달합니다.

밑의 Calculator는 인수를 받아서 계산하고 있는 것을 볼 수 있습니다.

 

 

결과

두 소설책의 값 차이는 1500 입니다.

 

 

 

 

3. 함수 내에서는 인수를 수정할 수 없도록 하기

위의 방법으로 함수를 운용하는 경우, 언제든 함수에 전달된 인수가 함수 안에서 바뀔 수 있다는 가능성을 가집니다.

 

struct Book
{
    char title[30];
    char author[20];
    int price;
    int year;
};

int Calculator(Book*, Book*);


int main()
{    
    struct Book fiction = {"kangmong", "kinger", 12000, 1940};
    struct Book fiction_2 = {"love is fair", "mong", 10500};
    int CalcPrice;
    CalcPrice = Calculator(&fiction, &fiction_2);
    cout << "두 소설책의 값 차이는 " << CalcPrice << " 입니다." << endl;
    return 0;
} 

int Calculator(Book* fiction1, Book* fiction2){
    fiction1 -> price = 20000;
    return (fiction1->price - fiction2->price);
}

밑의 Calculator에서 fiction1의 price를 20000원으로 변경할 경우, 변경된 그대로 계산이 진행됩니다.

 

 

결과

두 소설책의 값 차이는 9500 입니다.

 

 

 

이런 가능성을 없애는 방법은 Calculator 함수가 상수 인수(const 변수)를 받는 것입니다.

struct Book
{
    char title[30];
    char author[20];
    int price;
    int year;
};

int Calculator(const Book*, const Book*);


int main()
{    
    struct Book fiction = {"kangmong", "kinger", 12000, 1940};
    struct Book fiction_2 = {"love is fair", "mong", 10500};
    int CalcPrice;
    CalcPrice = Calculator(&fiction, &fiction_2);
    cout << "두 소설책의 값 차이는 " << CalcPrice << " 입니다." << endl;
    return 0;
} 

int Calculator(const Book* fiction1, const Book* fiction2){
    fiction1 -> price = 20000;
    return (fiction1->price - fiction2->price);
}

이렇게 코드를 입력하고, fiction1 -> price = 20000; 을 작성하면 다음과 같은 오류가 뜹니다.

 

식이 수정할 수 있는 lvalue여야 합니다.

 

이처럼 const를 이용하여 상수 인수를 전달하면 함수 내에선 변경할 수 없습니다.

 

 

 

 

📌 중첩된 구조체

 

구조체는 멤버 변수로 또 다른 구조체를 포함할 수 있습니다. 

Book 구조체를 선언함과 동시에, 그 멤버 변수들 중 하나인 author_name 구조체도 선언했습니다.

struct author_name
{
    char first[10];
    char last[20];
};

struct Book
{
    char title[30];
    author_name name;
    int price;
    int year;
};

 

중첩된 구조체를 사용하는 방법은 간단합니다.

구조체.멤버 구조체.멤버 구조체의 멤버 변수

 

예시

int main()
{    
    struct Book fiction = {
        "kangmong", 
        {"kang", "mongo"}, 
        12000, 
        1940
        };
    cout << "소설 kangmong의 저자는 " << fiction.name.first << fiction.name.last << " 입니다." << endl;
    return 0;
}

fiction.name.first를 통해 fiction 안의 name 구조체에서 first라는 매개 변수를 가져온 것을 알 수 있습니다.

 

 

결과

소설 kangmong의 저자는 kangmongo 입니다.

 

 

 

 

📌 구조체의 크기

 

구조체의 크기는 기본적으로 멤버 변수에 제일 큰 영향을 받습니다.

하지만 언제나 멤버 변수의 합=구조체 크기아닙니다.

 

구조체 Book의 크기를 살펴보겠습니다.

struct author_name
{
    char first[10];
    char last[20];
};

struct Book
{
    char title[30];
    author_name name;
    int price;
    int year;
};

 

 

int형이 2개, char형이 1개, author_name형이 1개입니다. 이들의 합은 다음과 같습니다.

int main()
{    
    cout << "int = " << sizeof(int) << ", " << "char = " << sizeof(char) << ", " << "author_name = " << sizeof(author_name) << endl;
    cout << "멤버 변수들의 합 : " << sizeof(int) + sizeof(int) + sizeof(char) + sizeof(author_name) << endl;
    return 0;
}

 

결과

int = 4, char = 1, author_name = 30
멤버 변수들의 합 : 39

 

 

하지만 Book 구조체의 크기를 보면 어떨까요?

int main()
{    
    cout << "Book 구조체의 크기 : " << sizeof(Book);
    return 0;
}

 

결과

Book 구조체의 크기 : 68

 

이렇게 서로의 결과가 다르게 나오는 것은 바이트 패딩 규칙 때문입니다.

바이트 패딩 규칙에 대해선 추후에 다루도록 하겠습니다. 다만, 구조체 멤버 변수들의 합이 무조건 구조체의 크기는 아니라는 점을 알아두시길 바랍니다.

 

 

 

 

 

📌 공용체와 열거체

 

마지막으로 살펴볼 구조체들은 공용체와 열거체입니다.

간단하게 보고 가겠습니다.

 

 

🎨 공용체

모든 멤버 변수가 하나의 메모리 공간을 공유합니다. 다양한 타입의 데이터를 저장할 수 있습니다. 하나의 멤버 변수만 초기화하면, 나머지 멤버 변수들도 같은 데이터를 공유합니다.

 

공용체는 union으로 선언하며, 멤버 변수 앞에 unsigned를 붙여줍니다.

union ShareData
{
    unsigned char a;
    unsigned short b;
    unsigned int c;
};

int main()
{   
    ShareData var; 
    var.c = 0x12345678;
    cout << hex;
    cout << var.a << endl;
    cout << var.b << endl;
    cout << var.c << endl << endl;
    return 0;
}

var 변수를 선언한 후, 0x12345678로 초기화합니다.

cout << hex; 명령어를 사용하여 16진수로 바꾼 후에, var 구조체의 멤버 변수들을 불러옵니다.

 

 

결과

x
5678
12345678

 

각각의 멤버 변수들이 0x12345678에서 각각에 맞는 수와 문자열을 가져온 것을 확인할 수 있습니다.

이렇듯 공용체는 하나의 메모리 공간에서 각각의 멤버 변수가 자신의 것들을 가져오는 형태를 취합니다.

 

 

 

 

🎨 열거체

새로운 타입을 선언하면서 동시에 그 타입이 가질 수 있는 정수형 상수값도 같이 명시합니다.

가독성을 높이고, 변수에 의미를 부여하기 위해서 사용됩니다.

 

열거체는 enum으로 선언되며, 초기화할 때 정수형 상수값을 같이 부여합니다.

다음은 임의로 선언한 Weather 열거체입니다.

enum Weather {SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30};

 

열거체를 활용한 코드입니다.

int main()
{   
    int input;
    Weather today_weather;

    cout << "오늘의 날씨를 입력해주세요 : " << endl;
    cout << "(SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30)\n" << endl;
    cin >> input;
    today_weather = (Weather)input;

    return 0;
}

먼저 today_weather 열거체를 선언해줍니다.

input을 통해 정수 혹은 문자열을 전달받고, today_weather에 대입합니다.

 

 

int main()
{   
    int input;
    Weather today_weather;

    cout << "오늘의 날씨를 입력해주세요 : " << endl;
    cout << "(SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30)\n" << endl;
    cin >> input;
    today_weather = (Weather)input;

    switch (today_weather)
    {
    case SUNNY :
        cout << "오늘의 날씨는 끝내줘요!";
        break;

    case CLOUD :
        cout << "오늘의 날씨는 선선해요!";
        break;

    case RAIN :
        cout << "오늘의 날씨는 운치있어요!";
        break;

    case SNOW :
        cout << "오늘의 날씨는 신나요!";
        break;

    default:
        cout << "정확한 상숫값을 입력해주세요.";
        break;
    }

    cout << endl << "열거체 Weather의 각 상숫값은 " << SUNNY << ", " << CLOUD << ", "
        << RAIN << ", " << SNOW << "입니다.";

    return 0;
}

today_weather에 입력된 데이터를 토대로 switch문을 돌려서 알맞은 데이터가 나오도록 합니다.

 

 

결과

오늘의 날씨를 입력해주세요 : 
(SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30)
0
오늘의 날씨는 끝내줘요!
열거체 Weather의 각 상숫값은 0, 10, 20, 30입니다.

날씨를 입력하라는 명령에 0을 입력했고,  그에 따라 SUNNY가 반환되었습니다.

그에 맞는 문자열이 출력된 것을 볼 수 있습니다.

 

 

 

 

 

지금까지 구조체에 대해 알아보았습니다.

구조체 선언, 구조체 변수 선언 및 초기화, 크기, 공용체와 열거체 등 많은 개념을 학습한 포스팅입니다.

그러니 복습을 필수적으로 하고 다음 단계로 넘어가시길 바랍니다.

고생하셨습니다.