Coding/Unreal, C++

[C++ Primer Plus] 4. Compound Types

출처

C++ Primer Plus 6판 챕터 4

대답해야 하는 질문들

  • wchar_t, char16_t, char32_t를 쓰는 string을 정의하는 방법이 무엇인가. 
  • struct에서 bit field는 무엇인가.
  • int에서 enum, enum에서 int로의 캐스팅 차이점이 무엇인가.
  • union은 어떻게 사용되는가.

서론

  • C++은 복합 자료형(Compound Type)을 제공한다. 복합 자료형은 integer와 float 자료형으로 이뤄진다.
  • 가장 광범위한 복합 자료형은 Class다. 이 뿐만 아니라 C++은 C에서 가져온 복합 자료형 또한 제공한다. 예를 들어, array는 같은 자료형으로 여러개의 값을 가질 수 있다. Structure는 다양한 자료형의 여러개의 값을 가질 수 있다.

Introducing Arrays

  • Array는 여러개의 자료를 담을 수 있는 데이터 형식이다. Array를 정의하는 문법은 다음과 같다.
    typeName arrayName[arraySize];
    이 때, arraySize는 literal 값이거나 const 값이어야 한다. 즉 compile 시점에 값이 고정돼야 한다.

Initialization Rules for Arrays

  • Initialization form은 오직! array를 선언할 때만 가능하다.
int cards[4] = {3, 6, 8, 10}; // 가능
int hand[4]; // 가능
hand[4] = {5, 6, 7, 9}; // 불가능
hand = cards; // 불가능
  • Initialization을 부분만 하는 것도 가능하다.
float hotelTips[5] = {5.0, 2.5};
  • 이렇게 일부분만 하면 컴파일러가 나머지는 0으로 설정해준다. 그래서 0으로 초기화하고 싶으면 다음과 같이 쓰기도 한다.
long totals[500] = {0};
  • 괄호 []를 비워놓을 수 있다. 이러면 컴파일러가 알아서 딱맞는 크기로 array를 설정한다.
short things[] = {1, 5, 3, 8};
  • C++11은 괄호를 이용한 선언을 광범위하게 허용한다.
  • 첫번째, = 기호를 생략할 수 있다.
double earnings[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; // okay with C++11
  • 두번째, 빈 중괄호{}로 모든 원소를 0으로 초기화할 수 있다.
unsigned int counts[10] = {}; // all elements set to 0
float balances[100] {}; // all elements set to 0
  • 세번째, 이러한 list-initialization은 업캐스팅 방식의 형변환을 방지한다.
long plifs[] = {25, 92, 3.0}; // 3.0 때문에 불가능
char slifs[4] {'h', 'i', 1122011, '\0'}; // 1122011 때문에 불가능
char tlifs[4] {'h', 'i', 112, '\n'}; // 가능

Strings

  • String은 메모리에 연속되게 저장된 char 배열이다.
  • C++에서 string을 다루는 2가지 방법이 있다.
    • C에서 가져온 방법
    • string class library
  • C-style string은 중요한 특징이 있다: 마지막 글자가 '\0'이라는 것이다.
char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // NOT a string!
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // a string!
  • string 마지막에 있는 null 글자는 매우 중요한 역할을 한다. string을 받는 여러 함수들(cout 포함)이 string을 null 글자가 있는데까지 사용하기 때문이다.
  • 쌍 따옴표를 사용해서 string을 좀더 예쁘게 선언할 수 있다.
char bird[11] = "Mr. Cheeps"; // \0가 끝에 포함된다.
char fish[] = "Bubbles"; // \0가 끝에 포함된다. 컴파일러가 크기를 세어준다.
  • 이렇게 쌍따옴표로 정의된 string은 (배열의 크기가 충분하다면) 마지막에 null 글자를 포함한다.
  • 그렇기 때문에 쌍따옴표로 선언된 한글자는 실제로 두글자에 해당한다.
char shirt_size = "C"; // ERROR

Concatenating String Literals

  • String을 한줄에 쭉 쓰기엔 애매한 경우가 있다. 이를 위해, C++에서는 String 리터럴값을 공백(띄어쓰기, 줄바꿈)으로 이어붙이면 하나로 합쳐진다. 이 때, 첫번째 string에 있던 null은 두번째 string의 첫글자로 대체된다.
cout << "I'd give my right arm to be" " a great violinist.\n";
cout << "I'd give my right arm to be a great violinist.\n";
cout << "I'd give my right arm to be"
" a great violinist.\n";

Adventures in String Input

  • 이런 코드가 있다고 해보자.
cout << "Enter your name:\n";
cin >> name;
cout << "Enter your favorite dessert:\n";
cin >> dessert;
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
  • 문제는 아래와 같은 입력을 줬을 때다.
Enter your name:
Alistair Dreeb
Enter your favorite dessert:
I have some delicious Dreeb for you, Alistair
  • Alistair Dreeb이라는 이름만 입력했을 뿐인데, 이름은 쪼개지고 다음 입력까지 자동으로 입력돼 버렸다.
  • 그 이유는 cin이 어떻게 string의 끝을 결정하는가에서 비롯된다. cin은 입력받을 string의 끝을 공백(띄어쓰기, 줄바꿈)으로 결정한다.
  • 이로 인해 첫번째 입력은 Alistair가 되고 두번째 입력은 cin에 남아있던 Dreeb가 되는 것이다.

Reading String Input a Line at a Time

  • istream에 정의된 getline()과 get()을 통해 단어별로 입력을 받는게 아니라 줄별로 입력을 받을 수 있다.
  • getline()은 한줄의 입력을 받은 뒤 \n을 없애고 get()은 \n을 입력 큐에 남겨놓는다.
  • getline()의 첫번째 매개변수는 입력을 받아낼 string이고 두번째 매개변수를 통해 최대 몇글자까지 받을지 결정한다.
cout << "Enter your name:\n";
cin.getline(name, ArSize);
cout << "Enter your favorite dessert:\n";
cin.getline(dessert, ArSize);
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
  • get()으로 입력을 받을 때는 좀더 신경을 써줘야 한다. 위에서 말했듯 \n을 입력 큐에 남겨놓고 오기 때문이다. 남겨놓은 \n을 없애기 위해 get()을 한번 더 해줘야 한다.
cin.get(name, ArSize);
cin.get();
cin.get(dessert, Arsize);
  • 이렇게 쓰는 게 안이쁘다고 생각된다면 연결해서 작성할 수 있다. 이렇게 쓸 수 있는 이유는 get()이 cin을 반환하기 때문이다.
cin.get(name, ArSize).get();
  • 이 방식도 그리 이쁘진 않은데, 왜 get()을 왜 쓰냐고 생각할 수 있다. 이유는 두가지다.
    1. 과거 C++ 버전엔 getline()이 없을 수 있다.
    2. get()을 쓰면 getline()보다 조심스럽게 입력을 받아낼 수 있다. 예를 들어, 입력을 받은 후 정말로 모든 입력을 받은 것인지, 아니면 입력 길이가 커서 다 못받은 것인지 판단할 수 있다. (getline()과 get() 모두 남은 입력을 내버려두는데, getline()은 failbit를 설정하고 더 이상의 입력을 닫아버린다)
  • 즉, getline()이 쓰기 편하지만, get()은 에러 핸들링할 때 편하다.

Mixing String and Numeric Input

  • String과 숫자 자료형을 연속해서 받을 때도 문제가 발생할 수 있다.
cout << "What year was tour house built?\n";
int year;
cin >> year;
cout << "What is its street address\n";
char address[80];
cin.getline(address, 80);
cout << "Year built: " << year << endl;
cout << "Address: " << address << endl;
cout << "Done!\n";
  • 이렇게 해서 돌려보면 다음과 같은 결과를 얻는다.
What year was your house built?
1966
What is its street address?
Year built: 1966
Address:
Done!
  • 그 이유는 cin >> year를 할 때, 사용자가 입력한 \n가 input queue에 남아 있어서 getline()이 \n만 보고 끝내버리기 때문이다. 이를 방지하려면 getline() 이전에 cin.get()을 넣어두면 된다.

Introducing string class

  • ISO/ANSI C++98이 제공하는 string class는 string의 array 특성을 감추어서 마치 string을 다른 값처럼 다룰 수 있도록 해준다.
  • string에서 C++11의 list initialization을 쓸 수 있다.
char first_date[] = {"Hello, world"};
char first_date[] {"Hello, world"};
string first_date = {"Hello, world"};
string first_date {"Hello, world"};

Other Forms of String Literals

  • C++는 char의 확장으로서 wchar_t를 제공한다. C++11은 여기에 char16_t, char32_t를 추가했다. 해당 자료형으로 string literal을 정의하려면 앞에 prefix를 붙여야 한다.
wchar_t title[] = L"Chief Astrogator";
char16_t name[] = u"Felonia Ripova";
char32_t car[] = U"Humber Super Snipe";

Introducing structure

  • structure는 array에 비해 다양하게 쓸 수 있다. array와 달리 내부에 다양한 data type을 가질 수 있기 때문이다.
  • structure를 선언하는 문법은 다음과 같다.
struct inflatable
{
	char name[20];
    float volume;
    double price;
};
  • 이 때, inflatable이 해당 struct type의 tag가 된다.
  • C에서는 struct 변수를 선언할 때, 앞에 struct를 붙여줘야 했는데, C++은 선택사항이다. (이건 C++에서 struct를 새로운 타입의 정의로서 받아들이다는 의미라고 해석할수도..?)
struct inflatable goose; // C style
inflatable vincent; // C++ style

Structure Initialization

  • struct initialization은 다음과 같이 단순화 할 수 있다.
inflatable guest = // C++11에서는 =를 뺄 수 있다.
{
	"Glorious Gloria",
    1.88,
    29.99
};
  • definition과 creation을 함께 할 수도 있다. 다만 일반적으로 권장되지는 않는다.
struct perks
{
	int key_number;
    char car[12];
} mr_smith, ms_jones;

struct perks
{
	int key_number;
    char car[12];
} mr_smith =
{
	7,
    "Packard"
};
  • struct의 tag를 빼놓고 creation만 해서 딱 그 변수만 해당 struct를 가지도록 할 수도 있다.
struct
{
	int x;
    int y;
} position;

Bit Fields in Structures

  • C와 마찬가지로 C++에서도 멤버 변수 별로 몇 bit를 소유할 것인지 결정할 수 있다. 이 때, 이름이 없는 필드에 bit를 할당해 spacing을 구현할 수 있다. 이렇게 비트 수가 정해진 필드를 bit field라고 한다.
struct torgle_register
{
	unsigned int SN : 4; // SN에 4bit 할당
    unsigned int : 4; // 4bit를 비워둔다.
    bool goodIn : 1; // goodIn에 1bit 할당
    bool goodTorgle : 4; // goodTorgle에 1bit 할당
};
  • union은 다양한 데이터 타입을 저장할 수 있지만 한번에 하나의 타입만 저장하는 자료형이다.
  • 문법은 struct와 유사하나 의미는 완전히 다르다.
union one4all
{
	int int_val;
    long long_val;
    double double_val;
};

one4all pail;
pail.int_val = 15;
cout << pail.int_val;
pail.double_val = 1.38; // 기존 int_val은 사라진다.
cout << pail.double_val;
  • union은 2개 이상의 다른 타입의 데이터를 저장해야 하는데, 절대로 동시에 쓰이지는 않을 때 메모리 절약을 위해 쓴다.

Enumerations

  • C++ enum 기능은 const를 통해 symbolic constant를 만들어 낼 필요가 없게 도와준다.
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
  • 위 코드는 두 가지를 의미한다.
    1. spectrum이라는 새로운 타입을 선언한다.
    2. red, orange, yellow 등 Enumerator에 int 0~7에 해당하는 symbolic constants를 적용한다. 
  • Enumeration도 type이다. 즉 다음과 같이 변수를 선언하면 된다.
spectrum band;

band = blue;
  • Enumeration이 내부적으로 int라고 했는데, casting은 양방향으로 작동하지 않는다.
spectrum band;
int color;

band = 3; // 불가능. int -> enum은 암묵적으로 불가능
color = blue; // 가능. enum -> int는 암묵적으로 가능
short tell[10];
cout << tell << endl;
cout << &tell << endl;
  • 두 출력값이 다를까? 똑같다.
  • 그렇다면 tell과 &tell은 같은 의미인가? 그렇지 않다.
  • Array에서 tag는 첫번째 원소의 주소를 의미한다.
  • 즉, tell은 &tell[0], 그러니까 short* 형이다. 이에 비해, &tell은 전체 tell array를 가리킨다. 즉 short[10]* 형이다.