1. 구조체
구조체, struct
std::string myName;
int myBirthYear;
int myBirthMonth;
int myBirthDay;
int myHeightInches;
int myWeightPounds;
- 자신에 대한 정보를 함수에 전달하려면 각 변수를 개별적으로 전달해야 한다.
- 또한, 다른 사람에 대한 정보를 저장하려면 추가된 사람마다 6개의 변수를 추가로 선언해야 한다.
- 다행히도 C++ 에서는 고유한 사용자 정의 집계 데이터 유형(user-defined aggregate data type)을 생성할 수 있다.
- 집계 데이터 유형(aggregate data type)은 여러 개별 변수를 함께 그룹화하는 데이터 유형이다.
- 가장 단순한 집계 데이터 유형 중 하나는 구조체(struct)다.
- 즉, 구조체(struct)는 하나 이상의 변수를 그룹 지어서 새로운 자료형을 정의하는 것이다.
- 구조체 선언 및 정의
- 구조체(struct)는 사용자 정의 자료형이기 때문에, 먼저 그것을 사용하기 전에 구조체가 어떻게 생겼는지 컴파일러에 말해야 한다.
- 이를 위해 struct 키워드를 사용해 구조체를 선언해야 한다.
struct Employee
{
short id;
int age;
double wage;
};
- 위 코드는 컴파일러에
Employee 구조체를 정의한다고 말한다.
- Employee 구조체는 세 개의 변수를 포함한다. (short id, int age, double wage) 구조체의 일부인 이러한 변수를 멤버(member) 또는 필드(field)라고 한다.
Employee는 단지 선언에 불과하다. 컴파일러에 구조체가 멤버 변수가 있을 것이라고 말하고 있지만, 지금은 어떤 메모리도 할당되지 않는다.
- 일반적으로 구조체 이름은 대문자로 시작하여 변수 이름과 구분한다.
- Employee 구조체를 사용하려면 Employee 타입의 변수를 정의하면 된다.
Employee joe;
- 위 코드는 joe라는 구조체
Employee 타입의 변수를 정의한다. 일반 변수와 마찬가지로 구조체 변수를 정의하면 해당 변수에 대한 메모리가 할당된다.
- 구조체 멤버 접근
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
구조체 초기화
- 멤버별로 값을 지정하여 구조체를 초기화하는 작업은 매우 번거로우므로 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에서는 이 제한이 해제되어 두 가지 모두 사용할 수 있다. 둘 다 사용하면 초기화 목록/유니폼 초기화를 우선한다.
구조체 할당
- 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)을 사용해서 구조체 멤버에 값을 할당할 수 있다.
구조체와 함수
- 개별 변수보다 구조체의 큰 장점은 구조체를 멤버와 함께 함수에 전달할 수 있다는 것이다.
#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
중첩된 구조체
struct Employee
{
short id;
int age;
double wage;
};
struct Company
{
Employee CEO; // Employee is a struct within the Company struct
int numberOfEmployees;
};
Company myCompany;
- 중첩된 구조체에 대해 중첩된 초기화 목록을 사용할 수 있다.
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 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
- 위의
Employee 구조체에서 컴파일러는 멤버 id 뒤에 보이지 않는 2바이트의 패딩을 추가여 구조체 크기를 14 바이트가 아닌 16 바이트로 만든다.
2. typedef
- typedef를 사용하면 프로그래머가 타입의 별칭을 생성하고, 실제 타입 이름 대신 별칭을 사용할 수 있다.
- 즉, C++에서 이미 정의된 자료형이나 사용자 정의 자료형보다 더 짧거나 의미 있는 이름을 지어줄 수 있다.
- 관습적으로
typedef 별칭은 "_t" 접미사를 사용하여 선언한다.
typedef는 는 새로운 타입을 정의하지 않는다. 기존 타입의 별칭일 뿐이다. tydef는 일반 타입을 사용할 수 있는 곳이라면 어디서든 사용할 수 있다.
typedef long miles_t;
typedef long speed_t;
miles_t distance = 5;
speed_t mhz = 3200;
// miles_t와 speed_t는 실제로 long 타입이기 때문에, 아래 코드는 유효하다.
distance = mhz;
플랫폼 독립 코딩
typedef의 가장 큰 장점은 플랫폼 특정 세부 사항을 숨기는 데 사용할 수 있다는 것이다.
- 플랫폼에 따라서, 정수는 2바이트 또는 4바이트다.
int를 사용해 2바이트가 넘는 정보를 저장하는 것은 잠재적으로 위험할 수 있다.
char, short, int 및 long 은 해당 크기를 나타내지 않으므로 교차 플랫폼(cross-flatform) 프로그램에서 크기(비트)를 나타내는 별칭을 정의하는 것이 매우 일반적이다
typedef를 사용하면 실수를 방지하고 변수의 크기에 대해 더욱 명확히 알 수 있다.
#ifdef INT_2_BYTES
typedef char int8_t;
typedef int int16_t;
typedef long int32_t;
#else
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
#endif
3. 열거형
enum
- C++에는 많은 자료형이 내장되어 있다. 하지만 이 자료형들이 원하는 걸 표현하기에 항상 충분하지는 않다.
- 그래서 C++은 프로그래머들이 자신만의 자료형을 만들 수 있게 해 주는 기능을 포함하고 있다.
- 이러한 자료형을 사용자 정의 자료형이라고 한다.
- 아마도 가장 간단한 사용자 정의 자료형은 열거된 유형일 것이다.
- 열거된 유형(열거형이라고도 함)은 가능한 모든 값이 기호 상수(열거형)로 정의되는 자료형이다.
- 열거형은
enum 키워드를 이용해 정의된다.
// Color라는 새로운 열거형(enum)을 정의한다.
enum Color
{
// 열거자(enumerator)
// 각 열거자는 세미콜론(;)이 아니라 쉼표(,)로 구분된다.
COLOR_BLACK,
COLOR_RED,
COLOR_BLUE,
COLOR_GREEN,
COLOR_WHITE,
COLOR_CYAN,
COLOR_YELLOW,
COLOR_MAGENTA,
}; // 그러나 enum 자체는 세미콜론으로 끝나야 한다.
// 열거형 Color의 변수들 정의
Color paint = COLOR_WHITE;
Color house(COLOR_BLUE);
Color apple { COLOR_RED };
- 각 열거자는 열거 목록의 위치에 따라 정수 값이 자동으로 할당된다.
- 기본적으로 첫 번째 열거자에는 정수 값 0이 할당되며, 각 이후 열거자에는 이전 열거자보다 1 더 큰 값이 할당된다.
enum Color
{
COLOR_BLACK, // assigned 0
COLOR_RED, // assigned 1
COLOR_BLUE, // assigned 2
COLOR_GREEN, // assigned 3
COLOR_WHITE, // assigned 4
COLOR_CYAN, // assigned 5
COLOR_YELLOW,// assigned 6
COLOR_MAGENTA// assigned 7
};
Color paint(COLOR_WHITE);
std::cout << paint; // 4
- 열거자의 값을 명시적으로 정의할 수 있다.
- 이러한 정수 값은 양 또는 음의 값일 수 있으며 다른 열거자와 같은 값을 공유할 수 있다.
- 정의되지 않은 모든 열거자는 이전 열거자보다 1 더 큰 값이 부여된다.
enum Animal
{
ANIMAL_CAT = -3,
ANIMAL_DOG, // assigned -2
ANIMAL_PIG, // assigned -1
ANIMAL_HORSE = 5,
ANIMAL_GIRAFFE = 5, // shares same value as ANIMAL_HORSE
ANIMAL_CHICKEN // assigned 6
};
- 열거형은 특정한 상태 집합을 나타내야 할 때 코드 문서화 및 가독성 목적으로 매우 유용하다.
enum 클래스
- C++은 열거형으로 선언된 값을 암시적으로 정수로 변환하여 평가하기 때문에 의도와 다른 결과를 도출하는 코드가 만들어질 수 있다.
#include <iostream>
int main()
{
enum Color
{
RED, // RED is placed in the same scope as Color
BLUE
};
enum Fruit
{
BANANA, // BANANA is placed in the same scope as Fruit
APPLE
};
Color color = RED; // Color and RED can be accessed in the same scope (no
// prefix needed)
Fruit fruit = BANANA; // Fruit and BANANA can be accessed in the same scope
// (no prefix needed)
if (color == fruit) // The compiler will compare a and b as integers
std::cout << "color and fruit are equal\n"; // and find they are equal!
else
std::cout << "color and fruit are not equal\n";
return 0;
}
// color and fruit are equal
- 서로 다른 enum 자료형으로 평가할 것으로 예상했지만 예상과는 다른 결과가 도출된다.
- 이런 문제를 보완하기 위해 C++ 11은 새로운 컨셉, 열거형 클래스(enum class)를 정의했다. (범의가 지정된 열거형 or 강력한 형식의 열거형이라고도 한다)
- 열거형 클래스(enum class)는 강한 형식과 범위(scope)를 가진다.
- 열거형 클래스(enum class)를 만들려면,
enum 키워드 뒤에 class 키워드를 사용하면 된다.
#include <iostream>
int main()
{
enum class Color // "enum class" defines this as a scoped enumeration instead of a standard enumeration
{
RED, // RED is inside the scope of Color
BLUE
};
enum class Fruit
{
BANANA, // BANANA is inside the scope of Fruit
APPLE
};
Color color = Color::RED; // note: RED is not directly accessible any more, we
// have to use Color::RED
Fruit fruit = Fruit::BANANA; // note: BANANA is not directly accessible any
// more, we have to use Fruit::BANANA
if (color == fruit) // compile error here, as the compiler doesn't know how to
// compare different types Color and Fruit
std::cout << "color and fruit are equal\n";
else
std::cout << "color and fruit are not equal\n";
return 0;
}
- 일반 열거형(enum)을 사용하면 열거자(Ex. RED)는 열거형 자체와 같은 범위에 있으므로 직접 접근할 수 있다.
- 그러나 열거형 클래스(enum class)의 강력한 범위 규칙은 모든 열거자를 열거형 일부로 간주하므로 범위 한정자(
::)를 사용하여 열거자 (Ex. Color::RED)에 접근해야 한다.
- 이는 이름 충돌을 방지하는 데 도움이 된다.
- 열거형 클래스(enum class)의 강력한 형식 규칙은 고유한 자료형으로 간주함을 의미한다.
- 그래서 컴파일러가 다른 열거형의 열거자와 암시적으로 비교하지 않는다.
- 다른 열거형 클래스에 선언된 열거형끼리 비교 연산을 수행하려 할 시 컴파일러는 컴파일 에러를 발생시킨다.
- 그러나 같은 열거형 클래스 내의 열거자 끼리는 비교할 수 있게 돼있다.
#include <iostream>
int main()
{
enum class Color
{
RED,
BLUE
};
Color color = Color::RED;
if (color == Color::RED) // this is okay
std::cout << "The color is red!\n";
else if (color == Color::BLUE)
std::cout << "The color is blue!\n";
return 0;
}