소년코딩

05.07 - 구조체, struct

객체를 표현하기 위해 하나 이상의 변수가 필요한 프로그래밍 사례가 많이 있다. 예를 들어, 자신을 표현하기 위해 이름, 생일, 키, 몸무게 또는 자신에 대한 기타 특성을 저장할 수 있다.

std::string myName;
int myBirthYear;
int myBirthMonth;
int myBirthDay;
int myHeightInches;
int myWeightPounds;

위에는 어떻게든 그룹화되지 않은 6개의 독립 변수가 있다. 자신에 대한 정보를 함수에 전달하려면 각 변수를 개별적으로 전달해야 합니다. 또한, 다른 사람에 대한 정보를 저장하려면 추가된 사람마다 6개의 변수를 추가로 선언해야 한다!

다행히도 C++ 에서는 고유한 사용자 정의 집계 데이터 유형(user-defined aggregate data type)을 생성할 수 있다. 집계 데이터 유형(aggregate data type)은 여러 개별 변수를 함께 그룹화하는 데이터 유형이다. 가장 단순한 집계 데이터 유형 중 하나는 구조체(struct)다.

즉, 구조체(struct)는 하나 이상의 변수를 그룹 지어서 새로운 자료형을 정의하는 것이다.


구조체 선언 및 정의 (Declaring and defining structs)

구조체(struct)는 사용자 정의 자료형이기 때문에, 먼저 그것을 사용하기 전에 구조체가 어떻게 생겼는지 컴파일러에 말해야 한다. 이를 위해 struct 키워드를 사용해 구조체를 선언해야 한다.

struct Employee
{
    short id;
    int age;
    double wage;
};

위 코드는 컴파일러에 Employee 구조체를 정의한다고 말한다. Employee 구조체는 세 개의 변수를 포함한다. (short id, int age, double wage) 구조체의 일부인 이러한 변수를 멤버(member) 또는 필드(field)라고 한다.

Employee는 단지 선언에 불과하다. 컴파일러에 구조체가 멤버 변수가 있을 것이라고 말하고 있지만, 지금은 어떤 메모리도 할당되지 않는다. 일반적으로 구조체 이름은 대문자로 시작하여 변수 이름과 구분한다.

경고: C++에서 가장 쉬운 실수 중 하나는 구조체 선언의 끝에 세미콜론을 잊어버리는 것이다.

Employee 구조체를 사용하려면 Employee 타입의 변수를 선언하면 된다.

Employee joe; // 구조체 Employee는 대문자로 시작하고, 변수 joe는 소문자로 시작한다.

위 코드는 joe라는 구조체 Employee 타입의 변수를 정의한다. 일반 변수와 마찬가지로 구조체 변수를 정의하면 해당 변수에 대한 메모리가 할당된다.


구조체 멤버 접근 (Accessing struct members)

Employee joe 와 같은 변수를 정의할 때, 변수 joe는 멤버 변수를 포함하는 전체 구조체(struct)를 참조한다. 개별 멤버에 접근하기 위해 멤버 선택 연산자(member selection operator: .) 를 사용하면 된다.

다음은 구성원 멤버 선택 연산자를 사용하여 각 멤버 변수를 초기화하는 예제다.
Employee joe; // create an Employee struct for Joe
joe.id = 14;       // assign a value to member id within struct joe
joe.age = 32;      // assign a value to member age within struct joe
joe.wage = 24.15;  // assign a value to member wage within struct joe

Employee frank;    // create an Employee struct for Frank
frank.id = 15;     // assign a value to member id within struct frank
frank.age = 28;    // assign a value to member age within struct frank
frank.wage = 18.27;// assign a value to member wage within struct frank

일반 변수처럼 구조체 멤버 변수도 초기화되지 않고 쓰레기값이 들어간다. 그러므로 수동으로 초기화해야 한다.

구조체 멤버 변수는 일반 변수와 같게 작동한다.

int totalAge = joe.age + frank.age;

if (joe.wage > frank.wage)
    std::cout << "Joe makes more than Frank\n";
else if (joe.wage < frank.wage)
    std::cout << "Joe makes less than Frank\n";
else
    std::cout << "Joe and Frank make the same amount\n";

// Frank got a promotion
frank.wage += 2.50;

// Today is Joe's birthday
++joe.age; // use pre-increment to increment Joe's age by 1

구조체 초기화 (Initializing structs)

멤버별로 값을 지정하여 구조체를 초기화하는 작업은 매우 번거로우므로 C++은 초기화 목록(initializer list)을 사용하여 구조체를 초기화하는 더 빠른 방법을 지원한다. 이렇게 하면 선언 시간에 구조체의 일부 또는 전체 멤버 변수를 초기화할 수 있다.

struct Employee
{
    short id;
    int age;
    double wage;
};

Employee joe = { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank = { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization)

C++ 11에서는 유니폼 초기화(uniform initialization)를 사용할 수도 있다.

Employee joe { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 (default initialization)

초기화 목록에 일부 멤버의 초기값이 포함되어 있지 않으면 해당 멤버는 기본(defualt)값으로 초기화된다. 위의 예제에서는 frank.wage에 명시적으로 초기값을 지정하지 않았기 때문에 frank.wage가 0.0으로 초기화된다.


C++11/14: Non-static member initialization

C++ 11 부터는 비-정적(non-static) 멤버 변수에 기본값을 지정할 수 있다.

struct Rectangle
{
    double length = 1.0;
    double width  = 1.0;
};

int main()
{
    Rectangle x;   // length = 1.0, width = 1.0

    x.length = 2.0; // you can assign other values like normal

    return 0;
}

그러나 불행하게도, 멤버 초기화 구문이 멤버 초기화 목록 및 유니폼 초기화와 호환되지 않는다. 예를 들어. C++ 11에서는 다음 프로그램이 컴파일되지 않는다.

struct Rectangle
{
    double length = 1.0; // non-static member initialization
    double width  = 1.0;
};

int main()
{
    Rectangle x{ 2.0, 2.0 }; // uniform initialization

    return 0;
}

// Compile Error!

따라서 C++ 11에서는 비-정적(non-static) 멤버 초기화와 유니폼 초기화중 무엇을 사용해야 할지 정해야 한다.

그러나 C++ 14에서는 이 제한이 해제되어 두 가지 모두 사용할 수 있다. 둘 다 사용하면 초기화 목록/유니폼 초기화를 우선한다.


구조체 할당 (Assigning to structs)

C++ 11 이전에는 구조체 멤버에 값을 할당하려면 개별적으로 할당해야 했다.

struct Employee
{
    short id;
    int age;
    double wage;
};

Employee joe;
joe.id = 1;
joe.age = 32;
joe.wage = 60000.0;

위 작업은 멤버가 많은 구조체에서는 매우 번거롭다. C++ 11에서는 초기화 목록(initializer list)을 사용해서 구조체 멤버에 값을 할당할 수 있다.


구조체와 함수 (Structs and functions)

개별 변수보다 구조체의 큰 장점은 구조체를 멤버와 함께 함수에 전달할 수 있다는 것이다.

#include <iostream>

struct Employee
{
    short id;
    int age;
    double wage;
};

void printInformation(Employee employee)
{
    std::cout << "ID:   " << employee.id << "\n";
    std::cout << "Age:  " << employee.age << "\n";
    std::cout << "Wage: " << employee.wage << "\n";
}

int main()
{
    Employee joe = { 14, 32, 24.15 };
    Employee frank = { 15, 28, 18.27 };

    // Print Joe's information
    printInformation(joe);

    std::cout << "\n";

    // Print Frank's information
    printInformation(frank);

    return 0;
}

//program outputs:

ID:   14
Age:  32
Wage: 24.15

ID:   15
Age:  28
Wage: 18.27

또한, 함수는 구조체를 반환함으로써 여러 변수를 반환할 수 있다.

#include <iostream>

struct Point3d
{
    double x;
    double y;
    double z;
};

Point3d getZeroPoint()
{
    Point3d temp = { 0.0, 0.0, 0.0 };
    return temp;
}

int main()
{
    Point3d zero = getZeroPoint();

    if (zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0)
        std::cout << "The point is zero\n";
    else
        std::cout << "The point is not zero\n";

    return 0;
}

// The point is zero

중첩된 구조체 (Nested structs)

구조체는 다른 구조체를 포함할 수 있다.

struct Employee
{
    short id;
    int age;
    double wage;
};

struct Company
{
    Employee CEO; // Employee is a struct within the Company struct
    int numberOfEmployees;
};

Company myCompany;

위 프로그램에서 CEO의 월급을 알고 싶으면 멤버 선택 연산자(member selection operator: .)를 두 번 사용하면 된다.

std::cout << myCompany.CEO.wage << std::endl

myCompany에서 CEO를 선택한 다음 CEO 내에서 wage를 선택하면 된다.

중첩된 구조체에 대해 중첩된 초기화 목록을 사용할 수 있다.

struct Employee
{
    short id;
    int age;
    float wage;
};

struct Company
{
    Employee CEO; // Employee is a struct within the Company struct
    int numberOfEmployees;
};

Company myCompany = {{ 1, 42, 60000.0f }, 5 };

구조체 크기와 정렬 (Struct size and data structure alignment)

일반적으로 구조체의 크기는 모든 멤버의 크기를 합한 것이지만, 항상 그렇지는 않다!

Employee 구조체를 보자. 많은 플랫폼에서 short는 2 바이트, int는 4 바이트, double은 8 바이트이므로 Employee의 크기가 2 + 4 + 8 = 14 바이트가 될 것으로 예상할 수 있다. 정확한 크기를 확인하려면 sizeof 연산자를 사용하면 된다.

struct Employee
{
    short id;     // size: 2
    int age;      // size: 4
    double wage;  // size: 8
};

int main()
{
    std::cout << "The size of Employee is " << sizeof(Employee) << "\n";

    return 0;
}

The size of Employee is 16

그러나 위 프로그램에서 출력된 결과같이 사이즈가 더 클 수도 있다! 성능상의 이유로 컴파일러는 때때로 구조에 간격을 추가한다. 이것을 패딩(padding)이라고 한다.

위의 Employee 구조체에서 컴파일러는 멤버 id 뒤에 보이지 않는 2바이트의 패딩을 추가여 구조체 크기를 14 바이트가 아닌 16 바이트로 만든다. 이 작업을 수행하는 이유는 이 포스트의 내용 범위를 넘어가니 다음에 다룰 예정이다.


마무리

이 포스트에서 소개된 구조체는 멤버가 모두 데이터(=변수) 멤버이므로 올드 데이터 구조체(plain old data structs), 줄여서 "POD" 라고 부른다.

구조체(struct)를 이해하는 것은 객체 지향 프로그래밍을 향한 첫 번 째 단계이므로 매우 중요하다. 나중에 클래스(class)라는 구조체 위에서 설계된 다른 집계 데이터 유형(aggregate data type)을 배울 것이다. 구조체를 잘 이해하면 클래스로 쉽게 전환할 수 있다.


cpp 번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/47-structs/ 입니다.

'C++ 이야기 > 05. 다양한 타입' 카테고리의 다른 글

C++ 05.08 - auto  (0) 2018.07.08
C++ 05.06 - typedef  (1) 2018.07.04
C++ 05.05 - enum class  (1) 2018.07.04
C++ 05.04 - 열거형, enum  (3) 2018.07.03
C++ 05.03 - 문자열, std::string  (1) 2018.07.03
댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기