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;
위 코드는 joe라는 구조체 Employee
타입의 변수를 정의한다. 일반 변수와 마찬가지로 구조체 변수를 정의하면 해당 변수에 대한 메모리가 할당된다.
구조체 멤버 접근 (Accessing struct members)
Employee joe
와 같은 변수를 정의할 때, 변수 joe
는 멤버 변수를 포함하는 전체 구조체(struct)를 참조한다. 개별 멤버에 접근하기 위해 멤버 선택 연산자(member selection operator: .
) 를 사용하면 된다.
다음은 구성원 멤버 선택 연산자를 사용하여 각 멤버 변수를 초기화하는 예제다.
Employee joe;
joe.id = 14;
joe.age = 32;
joe.wage = 24.15;
Employee frank;
frank.id = 15;
frank.age = 28;
frank.wage = 18.27;
일반 변수처럼 구조체 멤버 변수도 초기화되지 않고 쓰레기값이 들어간다. 그러므로 수동으로 초기화해야 한다.
구조체 멤버 변수는 일반 변수와 같게 작동한다.
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.wage += 2.50;
++joe.age;
구조체 초기화 (Initializing structs)
멤버별로 값을 지정하여 구조체를 초기화하는 작업은 매우 번거로우므로 C++은 초기화 목록(initializer list)을 사용하여 구조체를 초기화하는 더 빠른 방법을 지원한다. 이렇게 하면 선언 시간에 구조체의 일부 또는 전체 멤버 변수를 초기화할 수 있다.
struct Employee
{
short id
int age
double wage
}
Employee joe = { 1, 32, 60000.0 }
Employee frank = { 2, 28 }
C++ 11에서는 유니폼 초기화(uniform initialization)를 사용할 수도 있다.
Employee joe { 1, 32, 60000.0 };
Employee frank { 2, 28 };
초기화 목록에 일부 멤버의 초기값이 포함되어 있지 않으면 해당 멤버는 기본(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;
x.length = 2.0;
return 0;
}
그러나 불행하게도, 멤버 초기화 구문이 멤버 초기화 목록 및 유니폼 초기화와 호환되지 않는다. 예를 들어. C++ 11에서는 다음 프로그램이 컴파일되지 않는다.
struct Rectangle
{
double length = 1.0;
double width = 1.0;
};
int main()
{
Rectangle x{ 2.0, 2.0 };
return 0;
}
따라서 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 };
printInformation(joe);
std::cout << "\n";
printInformation(frank);
return 0;
}
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;
}
중첩된 구조체 (Nested structs)
구조체는 다른 구조체를 포함할 수 있다.
struct Employee
{
short id
int age
double wage
}
struct Company
{
Employee CEO
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
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;
int age;
double wage;
};
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)을 배울 것이다. 구조체를 잘 이해하면 클래스로 쉽게 전환할 수 있다.
번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/47-structs/ 입니다.