음악, 삶, 개발

2 - Variables and Basic Types 본문

개발 공부/C++ Primer (5th Edition)

2 - Variables and Basic Types

Lee_____ 2020. 8. 13. 02:21

< 목차 >

 


    소개

    type 은 모든 프로그램의 기본이 되는것이다.

    type 은 우리의 데이터가 무엇을 의미한지, 이 데이타가 어떤 operation 이 가능한지 말해준다.

    C++ 는 type 에 대한 광범위한 support 를 제공한다.

    C++ 언어 자체가 몇가지 원시 type (문자, 정수, 소수 등..) 을 정의하고,

    사용자가 직접 type 을 정의할수있는 메카니즘을 제공한다.

    library 는 이 메카니즘을 사용하여 가변 길이의 string 이나 vector 와 같은 복잡한 type 을 정의한다.

    이 챕터에서는 built-in type 들과, C++ 가 어떻게 더 복잡한 type 들을 지원하는지에대해 다룰것이다.

    type 은 우리의 프로그램안에서 데이터의 의미와 operation 을 결정한다.

    i = i + j;

    위와 같은 단순한 statement 라 하더라도, 

    ij 가 어떤 type 이냐에 따라 의미가 달라진다.

    ij 가 정수라면, 이 statement 는 우리가 일반적으로 생각하는 덧셈을 할것이다.

    하지만 ijSales_item 객체라면 이 statement 는 2개의 객체의 구성요소(component)를 더 하는것이다.


    2.1 Primitive Built-in Types

    C ++는 산술 type (arithmetic type) 과 void라는 특수 type을 포함하는 원시 type 들을 정의한다.

    산술 type 은 character (문자), integer(정수), boolean, floating-point number (소수) 등을 나타낸다.

    void type 은 값과 연관이 없고 몇가지 상황에서만 사용될수있는데,

    대부분 값을 return 하지않기위해 함수의 return type 으로 사용된다. 


    2.1.1 Arithmetic Types (산술 type)

    산술 type 을 2가지로 나눌수있다.

    1. 정수형(integral) type : 문자, boolean 도 여기에 포함된다.
    2. 소수 type : floating-point

    산술 type 의 크기(bit) 는 기계에 따라 다양하다.

    standard 의 최소 크기는 아래의 그림과 같다.

    각 type 의 최소 size

    하지만 compiler 는 이 type 들을 더 큰 크기로 사용하는것이 허용된다.

    이처럼 bit 수는 다양하기때문에, 각 type 이 나타낼수있는 가장 큰 값과 작은 값 역시 다양할수있다.

     

    bool type 은 값으로 true false 를 나타낸다.

    문자 (character) 는 여러 type 이 있으며, 국제화를 지원하기위해 존재한다.

    가장 기본적인 문자 type 은 char 이다.

    char 는 기계의 기본 문자 집합의 문자에 해당하는 숫자 값을 보유 할 수있을만큼 충분한 크기를 보장한다.

    즉, char 는 단일 머신 byte 와 동일한 크기이다.

    나머지 type 인 wchar_t, char16_t, char32_t 는 확장 문자 set 을 위해 사용된다.

    wchar_t type은 시스템의 가장 큰 확장 문자 집합에있는 모든 문자를 포함 할 수있을만큼 충분한 크기를 보장한다.

    char16_tchar32_t 는 Unicode 문자용이다.

    Unicode는 본질적으로 모든 자연어에서 사용되는 문자를 나타내는 표준이다.

    나머지 정수형(integral) type은 (잠재적으로) 다른 크기의 정수 값을 나타낸다.

    int 는 적어도 short 보다 크고,

    long 은 적어도 int 보다 크고,

    long long 은 적어도 long 보다 크도록 보장되어있다.

    long long type 은 새로운 표준으로 소개되었다.

    C+11 Machine-Level Representation of the Built-in Types

    컴퓨너는 데이터를 일련의 bit 로 저장하여, 각 bit 는 0 또는 1의 값을 보관한다.

    대부분의 컴퓨터는 메모리를 2의 거듭 제곱 크기의 bit 덩어리(chunk)로 처리합니다.

    주소 (address) 를 지정 가능한 메모리의 가장 작은 덩어리를 byte 라고 한다.

    일반적으로 작은 byte수인 기본 저장 단위를 word 라고한다.

    C++에서 byte는 시스템의 기본 문자 집합에 문자를 저장하는 데 필요한만큼의 bit를 포함한다.

    대부분의 시스템에서 byte는 8 bits를 포함하고 word는 32 또는 64 bits, 즉 4 또는 8 bytes 이다.

    대부분의 컴퓨터는 숫자(address 라고 함) 를 메모리의 각 byte와 연결합니다.

    8-bit bytes와 32-bit words가있는 컴퓨터에서는 다음과 같이 메모리 word를 볼 수 있다.

    위의 그림에서 왼쪽이 byte 의 address 이다.

    각 byte 는 8개의 bit 로 이루어져있다.

    우리는 address를 사용하여 해당  address에서 시작하는 다양한 크기의 bit 의 collection 을 참조 할 수 있다.

    address 736424 에있는 word 또는 address 736427에 있는 byte를 말할 수 있다.

    주어진 address 에서 메모리에 의미를 부여하려면 거기에 저장된 값의 type을 알아야한다.

    type은 사용되는 bit 수와 해당 bit를 해석하는 방법을 결정한다.

    만약 위치 736424 에 위치한 객체가 float type 을 가지고있다면,

    그리고 float 이 해당 machine 에서 32 bits (4 bytes) 로 저장된다면, 

    우리는 이 주소에 있는 해당 객체가 word 전체에 걸쳐 저장된다는것을 알수있다.

    해당 float의 값은 machine이 float 저장하는 방법에 대한 세부 사항에 따라 다르다.

    또한, 위치 736424의 개체가 ISO-Latin-1 문자 집합을 사용하는 시스템의 unsigned char인 경우

    해당 주소의 byte는 세미콜론을 나타낸다.

    floating-point type 은 single-, double-, extended-precision 값을 나타낸다. (정밀도)

    표준은 최소 유효 자릿수를 지정한다.

    대부분의 compiler는 지정된 최소값보다 더 많은 정밀도(precision)를 제공한다.

    일반적으로 float 는 한 개의 word (32 bits),

    double 은 2개의 word (64 bits), long double 은 3 개 또는 4개의 word (96 또는 128 bits) 로 표현된다.

    float double 유형은 일반적으로 각각 약 7 및 16 개의 유효 자릿수를 생성한다.

    long double type은 특수한 목적의 floating-point 하드웨어를 수용하는 방법으로 자주 사용된다.

    이때 정밀도는 implementation마다 다를 가능성이 크다.

    Signed and Unsigned Types

    bool 및 확장 문자 type을 제외하고, 정수  type은 signed 이거나 unsigned 이다.

    signed type 은 0 을 포함하여 양수, 음수를 모두 나타내고

    unsigned type 은 오직 0 보다 크거나 같은 값만을 나타낸다.

    int, short, long, long long 은 모두 signed 이다.

    unsigned long과 같이 unsigned를 type명 앞에 추가하여 해당하는 unsigned 유형을 얻는다.

    unsiged int 의 약어는 unsigned 다. 

    다른 정수형 type 들과 달리 3가지의 조금 특이한 문자 type 들이 있다. : char, signed char, unsigned char

    특히, char signed char 와 같은 type 이 아니다. 

    3개의 문자 type 이 있지만 오직 2가지의 표현만이 존재한다. : signed, unsigned

    따라서 char type 은 이 2가지중 하나를 사용하는것이다.

    이 2가지 문자 표현중 char 가 무엇을 의미하는지는 compiler 마다 다르다.

    unsigned type 에서 모든 bit 는 값을 나타낸다. 

    예를 들어, 8-bit 의 unsigned char 는 0 부터 255 까지의 값을 포함할수있다.

    표준은 signed type이 표현되는 방법을 정의하지 않지만,

    범위는 양수(positive) 값과 음수(negative) 값으로 균등하게 나누어 져야한다.
    따라서, 8-bit 의 signed char 는 -127 부터 127 까지 포함할수있다.

    대부분의 현대 machine 은 -128 부터 127 까지의 값을 허용한다.

    Advice : Deciding whiche Type to Use

    C++ 나 C 는 프로그램이 하드웨어에 가까이 접근할수있도록 설계되었다.

    산술 type은 다양한 종류의 하드웨어 특성에 맞게 정의된다.

    따라서 C++의 산술 type의 수는 당황스러울정도로 많다.

    대부분의 프로그래머는 사용하는 type을 제한하여 이러한 복잡성을 무시하는것이 좋다.

    사용할 type을 결정하는데있어 몇 가지 유용한 규칙을 알려주겠다.

     

    1. 절대 음수(negative) 가 될수없는 값이라면 unsiged type 을 사용하라.

    2. 정수 산술에 int 를 사용하라. short 는 너무 작고, long 은 대부분 int 와 같은 크기이다.

    너의 데이터 값이 int 가 보장하는 최소값보다 크다면 long long 을 사용하라.

    3. 산술 표현식에 일반 char bool 을 사용하지말라. 

    char 는 문자를 보관하기위해, bool 은 참,거짓 값을 보관하기위해서만 사용하라.

    어떤 시스템에서는 charsigned 이고, 어떤 시스템에서는 unsigned 이다.

    이러한 이유로 char를 사용하는 계산(computation)은 많은 예상치못한 문제를 야기할수있다.

    만약 당신이 매우 작은 정수가 필요하다면, signed char 인지 unsigned char 인지 반드시 명시적으로 지정하라.

    4. floating-point 계산에 double 을 사용하라. float 는 대부분 충분한 정밀도를 가지고있지못하며,

    single-precision 과 double-precision 사이에서 발생하는 비용은 무시할만하다.

    심지어 어떤 기계에서는 double-precision 이 single-precision 보다 빠르기도하다.

    long double이 제공하는 정밀도는 일반적으로 불필요하며 종종 상당한 run-time cost 가 수반된다.


    2.1.2 Type Conversions (Type 변환)

    객체의 type 은 객체가 가질수있는 data 와 객체가 어떤 operation 을 수행할수있는지 정의한다.

    많은 type이 지원하는 operation 중에는 지정된 type의 객체를 다른 관련 type으로 변환 (convert) 하는 기능이 있다.

    type 변환은 어떠한 type 의 객체가 기대되는 곳에 다른 type 의 객체를 사용할때 자동적으로 발생한다.

    더 자세한것은 4.1.1 (p. 159) 에서 다룰것이다.

    하지만 우리가 어떠한 type 의 객체에 다른 type 의 값을 대입할때 어떤 일이 발생하는지를 이해하는것은 유용할것이다.

    산술 type 을 다른 산술 type 으로 대입할때의 예를 보도록 하자.

    bool b = 42; // b is ture
    int i = b; // i has value 1
    i = 3.14; // i has value 3
    double pi = i; // pi has value 3.0
    unsigned char c = -1; // assuming 8-bit chars, c has value 255.
    signed char c2 = 256; // assuming 8-bit chars, the value of c2 is undefined.

    위의 코드처럼, 어떠한 일이 발생할지는 각 type 이 허용하는 범위에 따라 다르다.

    정리하면 아래와 같다.

     

    - bool 이 아닌 산술 type 을 bool 객체에 대입할때, 값이 0 이면 false, 그렇지않으면 true 이다.

    - bool 을 다른 산술 type 으로 대입할때, 값이 true 1, false 0 이다.

    - floating-point 값을 다른 정수 type 의 객체에 대입하면, 소수점을 제외한 값이 저장된다. (truncated)

    - 정수형값을 floating-point type 의 객체에 대입하면, 소수 파트는 0이 된다.

    이때, 정수가 floating-point 객체가 수용 할 수있는 것보다 많은 bit를 가지고있으면 정밀도가 손실 될 수 있다.

    - unsiged type 의 객체에 범위 바깥의 값을 대입하면, type 이 가진값을 %로 나눈 값에 나머지가 된다.

    예를 들어, 8-bit unsigned char 는 0 부터 255 까지 포함할수있다.

    이때, 우리가 범위 밖의 값을 대입하면, compiler 는 256 을 % 값의 나머지를 대입한다.

    따라서 -1 을 8-bit unsigned char 객체에 대입하면 값은 255 가 된다.

    - signed type 의 객체에 범위 밖의 값을 대입하면, 결과는 undefined 이다. 

    프로그램은 작동하는것처럼 보일수있지만, crash 될수있고 또는 쓰레기값 (garabage value) 를 만들어낼수도있다.

    Advice : Avoid Undefined and Implementation-Defined Behavior 

    Undefined behavior 는 compiler가 감지 할 필요가없는 (때로는 감지 할 수없는) error로 인해 발생한다.

    코드가 compile 되더라도 undefined expression을 실행하는 프로그램에는 error 가 있다.

    불행히도 Undefined behavior를 포함하는 프로그램은, 

    특정 상황 또는 일부 compiler에서 올바르게 실행되는 것처럼 보일 수 있다.

    다른 compiler 나 동일한 compiler의 후속 릴리스에서 compile 된 동일한 프로그램이

    계속 올바르게 실행된다는 보장은 없다.

    따라서 int의 크기가 고정되고 알려진 값이라고 가정하는 것과 같은 Implementation-Defined Behavior 를 피해야한다.

     

    이러한 프로그램을 non-portable (이식 불가능) 하다고 한다.
    프로그램이 다른 시스템으로 이동하면 Implementation-Defined Behavior에 의존하는 코드가 실패 할 수 있다는것이다.

    이전에 작업 한 프로그램에서 이러한 종류의 문제를 추적하는 것은 매우 힘든일이다.

     

    compiler 는 특정 산술 type 의 값이 와야할 곳에 다른 산술 type 의 오면 동일한 type 변환을 한다.

    예를 들어, 우리가 non-bool 값을 conditon 안에 사용하면 값은 bool 값으로 변환된다.

    int i = 42;
    
    // condition will evaluate as true
    if (i) { 
    
    	i = 0;
    
    }

    값이 0 이라면, condition 은 false 가 되고, 0 이 아닌 모든 다른 값은 true 를 반환한다.

    이와 같은 방식으로, 우리가 산술 표현식에 bool 을 사용하면 값은 항상 0 또는 1로 변환된다.

    결과적으로 산술 표현식에 bool 을 사용하는건 거의 대부분 잘못된것이다.

    Expressions Involving Unsigned Types

    unsigned type의 객체에 의도적으로 음수 값을 할당 할 가능성은 낮지만

    암시적으로 (implicitly) 그렇게하는 코드를 (너무 쉽게) 작성할 수 있다.

    예를 들어, 산술 표현식에 unsignedint 값을 둘다 사용한다면, 

    int 값은 일반적으로 unsinged 로 변환된다.

    int unsigned 로 변환하는것은 우리가 int unsigned 에 대입하는것과 같은 방식으로 실행된다.

    unsigned u = 10;
    int i = -42;
    std::cout << i + i << std::endl; // prints -84
    std::cout << u + i << std::endl; // if 32-bit ints, prints 4294967264

    첫번째 표현식에서 우리는 2개의 음수를 더하였고, 예상했던 결과를 얻었다.

    반면 두번째 표현식에서는 int -42 가 덧셈이 실행되기전에 unsigned 로 변환된다.

    음수를 unsinged 로 변환하는것은 정확히 우리가 음수값을 unsinged 객체에 대입한것과 같은 방식으로 작동한다.

    따라서 값은 위에서 설명한대로 "wraps around" 둘러싸인다.

    하나 또는 두개의 피연산자가 unsigned 인지 여부에 관계없이 값을 빼면
    unsigned에서 결과가 음수가 될 수 없음을 확인해야합니다.

    unsigned u1 = 42, u2 = 10;
    std::cout << u1 - u2 << std::endl; // ok : result is 32
    std::cout << u2 - u1 << std::endl; // ok : but the result will wrap around

    unsigned 는 0 보다 작을수없다는 사실은 우리가 loop 을 작성하는 방식에도 영향을 미친다.

    예를 들어, 1.4.1 (p13) 에서 우리는 10 부터 0까지 print 하기위해 -- 연산자를 사용하는 loop 을 작성했었다.

    for (int i = 10; i >= 0; --i) {
    
        std::cout << i << std::endl;
    
    }

    아마 이 책을 읽는 몇몇은 음수를 print 하고자하지않느다면,

    unsigned 를 사용하여 loop 을 작성할수있다고 생각할수도있을것이다.

    하지만 이 작은 변화는 우리의 loop 이 영원히 끝나지않도록 만든다.

    // Wrong! : you can numver be less than 0; the condition will always succeed.
    for (unsinged u = 10; u >= 0; --u) {
    
        std::cout << u << std::endl;
    
    }

    위의 코드에서 u 0 이 되었을때 무엇이 일어날지 생각해보라.

    u0 이 된후, --u 를 하면 -1 을 빼는것인데, 결과인 -1unsigned 값이 아니다.

    우리는 결과적으로 범위 밖의 값을 대입한 꼴이기때문에, -1 은 unsigned 값으로 변환된다.

    32-bit int 라고 가정하면, u 0 이 되고, --u 후의 값은 4294967295 가 된다....

    unsigned 를 사용하여 loop 을 만드는 한가지 방법은 for 대신 while 을 사용하는것이다.

    while 은 값을 print 하기전 -- 를 할수있게 해준다.

    unsigned u = 11; // start the loop one past the first element we want to print
    
    while (u > 0) {
    
        --u; // decrement first, so that the last iteration will print 0
    
        std::cout << u << std::endl;
    
    }

    위의 loop 은 loop control 변수값을 -- 하면서 시작한다.

    마지막 iteration 에서 u 1 이 될것이다. 이 값을 -- 하면 0 을 print 하게 된다.

    이 다음 uwhile 의 condition 에서 테스트하면 값은 u0 이기때문에 loop 을 빠져나가게된다. (exit)

    우리는 u-- 하면서 시작하기때문에, while 전에 print 하고자하는 첫번째 값보다 1 크게 초기화해야한다.

    따라서 u11 로 초기화하였고, 첫번째 print 된 값은 10 이다.

    Caution : Don't Mix Singed and Unsigned Types

    signed unsigned 값을 혼합하는 표현식은 signed 값이 음수일때 놀라운(나쁜 의미의) 값을 산출할수있다.

    언제나 명심해야할것은 signed 값은 자동적으로 unsigned 값으로 변환된다는것이다.

    예를 들어 a * b 와 같은 표현식이 있고, a -1, b 1 일때 둘다 int 라면 결과값은 예상대로 -1 이다.

    하지만 a int 이고 bunsigned 라면 결과값은 int 가 해당 machine 에서 얼마나 많은 bit 이냐에 따라 다를것이다.

    우리의 machine 에서는 4294967295 가 산출될것이다.


    2.1.3 Literals (리터럴)

    42 는 값이 자명하기때문에 리터럴이다.

    모든 리터럴은 type 을 가지고있고, 리터럴의 값과 형태가 type 을 결정한다.

    Integer and Floating-Point Literals

    정수 리터럴은 decimal (10진법), octal (8진법), hexadecimal (16진법) 를 사용하여 작성할수있다.

    0 으로 시작하는 리터럴은 8진수로 해석된다.

    0x 또는 0X 로 시작하는 리터럴은 16진수로 해석된다.

    예를 들어, 20 을 아래의 3가지 방법으로 작성할수있다.

    정수 literal 의 3가지 방법

    정수 리터럴의 type 은 값과 기보법에 의해 결정된다.

    default 로, 10진법 리터럴signed, 8진법와 16진법 리터럴signed 또는 unsigned 이다.

    10 진법 리터럴은 리터럴의 값이 맞는 가장 작은 유형의 int, long 또는 long long (즉,이 목록의 첫 번째 type)을 갖는다.

    8 진법 및 16 진법 리터럴리터럴의 값이 맞는 가장 작은 유형의 int, unsigned int, long, unsigned long, long long 

    또는 unsigned long long을 갖는다.

    가장 큰 관련 type에 맞추기에는 너무 큰 리터럴을 사용하는 것은 error 이다.

    short type 에는 리터럴이 없다.

    suffix 를 사용하여 이 default 들에 override 할수있다.

    아래의 표를 보자.

    Specifying the Type of a Literal

    기술적으로 이야기하자면 ,정수 리터럴signed 로 저장된다하더라도 

    10진법 리터럴의 값은 음수 (negative number) 가 될수없다.

    만약 -42 같이 음수처럼 보이는 10진법 리터럴을 작성한다면, -리터럴의 값을 피연산자로 사용하여 음수화한다.

    부동 소수점 (Floating-point) 리터럴은  과학적 표기법을 사용하여 지정된 소수점 또는 지수 (exponent) 가 포함된다.

    과학적 표기법을 사용하여 지수는 E 또는 e로 표시된다.

    default 로 floating-point 리터럴의 type 은 double 이다.

    이 default 는 suffix 를 사용하여 override 할수있다.

    Character and Character String Literals (문자 및 문자열 리터럴)

    ' ' (single quotes) 로 둘러싸인 문자는 char type 의 리터럴이다.

    " " (double quotation) 로 둘러싸인 0개 이상의 문자들은 string 리터럴이다.

    'a' // character literal
    "Hello World!" // string literal

    string type 의 리터럴은 상수 (constant) char 들의 배열이다. (array - 3.5.4 (p. 122)

    컴파일러는 null 문자인 '\0' 을 모든 string 리터럴뒤에 붙인다. (append) 

    따라서, string 리터럴의 실제 크기는 원래 크기의  + 1 이다.

    예를 들어, 리터럴 'A' 는 단일 문자 A 를 나타내지만, string 리터럴 "A" 는 A 와 null 문자로 이루어진 2개의 문자열을 나타낸다.

    오직 space, tab, new line 으로 나뉘어진 여러개의 인접한 string 리터럴은 단일 string 리터럴로 연결된다.

    이러한 형식의 리터럴은 한 줄에 다 적기에는 너무 클때 사용한다.

    // multiline string literal
    std::cout << "a really, really long string literal " 
    "that spans two lines" << std::endl;

    Escape Sequences

    backspace 나 control 문자들은 눈으로 보여지지않는다.

    이런 문자들은 인쇄할수없는것들이다.

    single, double quatation, question, backslash 등은 언어에서 특별한 의미를 지닌다.

    우리의 프로그램은 이런 문자들을 직접적으로 사용할수없으며, 이런 문자를 표현하기위해

    escape sequence 를 사용한다.

    escape sequence 는 backslash 로 시작한다.

    C++ 언어는 여러가지 escape sequence 를 아래와 같이 정의한다.

    escape sequences

    escape squence 는 단일 문자인것처럼 사용한다.

    또한 일반화 된 이스케이프 시퀀스를 작성할 수 있다.

    \ x 뒤에 하나 이상의 16 진수 또는 \ 뒤에 1, 2 또는 3 개의 8 진수가 온다.

    값은 문자의 숫자 값을 나타낸다. 몇 가지 예 (Latin-1 문자 집합 가정)

    escape sequence 는 우리가 일반 문자를 표현하듯 사용할수있다.

    \ 뒤에 3 개이상의 8진수가 온다면, 오직 첫번째 3개만 \ 와 연관되어있음을 기억해야한다.

    예를 들어, "\1234" 는 2개의 문자들인 8진수값인 123 과 문자 4 를 나타낸다.

    이와 대조적으로 \x 는 뒤에 붙는 모든 16진수를 사용한다.

    예를 들어, "\x1234"는 4 개의 16 진수에 해당하는 bit로 구성된 단일 16-bit 문자를 나타낸다.

    하지만 대부분의 기계들은 8-bit char 를 가지고있기때문에 위와 같은 값은 거의 사용되지않을것이다.

    일반적으로 8 비트 이상의 16 진수 문자는 Table 2.2의 접두사 중 하나를 사용하는 확장 문자 집합과 함께 사용된다.

    Specifiyng the Type of a Literal (리터럴의 type 지정)

    정수, 부동 소수점, 문자 리터럴의 type 은 Table 2.2 에 있는 suffix 나 prefix 를 리터럴에 붙여줌으로써 override 할수있다.

    literal type override

    long 리터럴에서 suffix 나 prefix 를 붙일때 소문자 l 대신 대문자 L 을 사용하는것이 best practice 다.

    소문자 l 을 숫자 1로 잘못 타이핑하기 너무 쉽기때문이다. 

    정수형 리터럴의 크기와 signed unsigned 선택은 독립적으로 지정할수있다.

    suffix 가 U 를 포함한다면 리터럴unsigned tpye 이다.

    따라서 10진수, 8진수, 16진수에 U 가 붙어있다면, 이들의 type 은 unsigned int, unsigned long, unsinged long long

    해당 리터럴의 값에 맞는 가장 작은 type 이 지정된다.

    suffix 가 LL 를 포함한다면, 리터럴의 type 은 long long, unsigned long long 중 하나 일것이다.

    LLL U 와 결합할수있다.

    예를 들어, UL 이 붙어있는 리터럴은 unsigned long 또는 unsigned long long 이며, 값이 unsinged long 에 맞는지

    여부에 따라 지정된다.

    Boolean and Pointer Literals 

    단어 true false bool type 의 리터럴이다.

    bool test = false;

    단어 nullptr 은 포인터 리터럴이다. 

    포인터와 nullptr 에 대해서는 2.3.2 (p.52) 에서 자세히 다룬다.


    2.2 Variables (변수)

    변수는 우리의 프로그램이 조작할수있는 이름을 가진 storage 를 제공한다.

    C++ 의 각 변수들은 type 을 가지고있다.

    이 type 은 변수의 메모리의 레이아웃과 크기에 따라 결정된다.

    type은 변수 메모리의 크기와 레이아웃, 해당 메모리에 저장할 수있는 값의 범위,

    또 변수에 적용 할 수있는 연산들을 결정한다.

    C++ 프로그래머들은 변수를 객체(object) 라고 부르기도한다.


    2.2.1 Variable Definitions

    변수의 정의는 type 지정자 (type specifier) 뒤에 쉼표(,) 로 구분 된 하나 이상의 변수 이름들의 목록이 뒤 따르고

    마지막으로 세미콜론(;) 으로 끝이난다.

    이 list 안에 각 이름들은 type 지정자에 의해 정의된 type 을 가지고있다.

    정의는 (선택적으로) 하나 이상의 이름에 대한 초기 값을 제공 할 수 있다.

    // sum, value, and units_sold have type int
    // sm and units_sold have initial value 0
    int sum = 0, value, units_sold = 0;  
    
    Sales_item item; // item has type Sales_item
    
    // string is a library type, representing a variable-length sequence of characters
    std::string book("0-201-78345-X"); // book initialized from string literal

    book 의 정의는 std::string type 을 사용한다.

    iostream 처럼 string은 namepsace std 안에 정의되어있다.

    Chapter 3 에서 string type 에 관해 더 이야기할것이다.

    string 은 가변 길이의 문자열을 표현하는 type 이다.

    string library 는 string 객체를 초기화할수있는 몇가지 방법을 제공하는데,

    이 방법중 하나는 string 리터럴의 복사이다.

    따라서 book 은 문자열 "0-201-78345-X" 로 초기화되었다.

    Terminology : What is an Object?

    C ++ 프로그래머는 "객체"라는 용어를 너무 무심하게 사용하는 경향이 있다.

    가장 일반적으로 "객체"는 데이터를 포함 할 수 있고 type이 있는 메모리의 영역이다.

    몇몇은 "객체"라는 용어를 클래스 type의 변수 또는 값을 이야기하기위해 사용하고,

    또다른 몇몇은 명명 된 객체와 명명되지 않은 객체를 구분한다.

    또 다른 사람들은 프로그램에 의해 변경 될 수있는 데이터에 대해 "객체"라는 용어를 사용하고

    읽기 전용 데이터에 대한 용어 "value"를 사용하여 객체와 값을 구분합니다.

    이 책에서는 < "객체"는 type이있는 메모리 영역> 이라는 보다 일반적인 사용법을 따를 것이다.

    따라서 객체가 built-in 또는 클래스 type 을 가지고 있는지,

    이름이 지정되거나 이름이 지정되지 않았거나, 읽거나 쓸 수 있는지 등의

    여부와 관계없이 "객체"라는 용어를 자유롭게 사용할 것이다.

    Initializers

    초기화 된 객체는 생성되는 순간에 지정된 값을 가져온다.

    변수를 초기화하는 데 사용되는 값은 복잡한 표현식 일수도있다.

    정의에서 두 개 이상의 변수를 정의하면 각 객체의 이름은 즉시 표시된다.

    따라서 이전에 정의 된 변수로 다른 변수를 초기화 할 수 있다.

    // ok : price is defined and initialized before it's used to initialize discout
    double price = 109.99, discout = price * 0.16;
    
    // ok : call applyDiscout and use the return value to initialize salePrice
    double salePrice = applyDiscout(price, discout);

    C++ 에서 초기화는 굉장히 복잡한 주제이고, 우리는 이 주제로 계속해서 돌아오게 될것이다.

    많은 프로그래머들이 변수를 초기화할때 기호 = 의 사용을 해깔려한다.

    초기화를 일종의 할당(assignment) 으로 생각하기 쉽다.

    하지만 C++ 에서 초기화와 대입은 다른 연산이다.

    이 개념이 특히 해깔릴수있는 이유는 많은 다른 언어들에서 대입과 초기화의 구분은

    무의미하고 무시할수있기때문이다.

    게다가, C++ 에서 조차도 자주 이런 구분이 전혀 문제가 안될때가 있다.

    그럼에도 불구하고, 이것은 매우 중요한 개념이기때문에 본문 전체에서 반복할것이다.

    Warning

    초기화는 대입이 아니다. (Initialization is not assignment) 

    초기화는 변수가 생성 될 때 값이 주어지면 발생한다.

    대입은 개체의 현재 값을 지우고 해당 값을 새 값으로 바꾼다.

    List Initialization

    초기화가 복잡한 주제인 이유중 하나는 언어가 초기화를 하는데에 다양한 형태를 제공하기때문이다.

    예를 들어, int type 의 units_sold 라는 변수를 0 으로 초기화한다고 해보자.

    int units_sold = 0;
    int units_sold = {0};
    int units_sold{0};
    int units_sold(0);

    초기화에서 { } 의 사용은 새로운 C++ 11 표준으로 소개된것이다.

    { } 는 과거 C++ 에서는 한정된 상황에서만 허용되는 초기화 방식이었다. (이유 : 3.3.1 (p.98))

    이 초기화 형태를 list initialization 이라고 한다.

    이제 객체를 초기화 할 때마다 그리고 어떤 경우에는 객체에 새 값을 할당 할 때마다 { } 를 사용할수있다.

    { } 를 사용하여 built-in type 의 변수를 초기화할때, { } 에는 하나의 중요한 속성이 있다.

    컴파일러는 초기화가 정보의 유실을 발생시키는 상황이라면 error 를 발생시킨다.

    long double ld = 3.1415926536;
    
    int a {ld}, b = {ld}; // error : narrowing conversion required
    int c (ld), d = ld; // ok : but value will be truncated

    컴파일러는 ab 의 초기화를 거부한다.

    왜냐면 int 를 초기화하기위해 long double 을 사용하는것은 데이터 유실이 발생하기때문이다.

    최소한 ld 의 소수 부분은 짤린다.

    게다가, ld 의 정수 부분은 int 에 집어넣기에 너무 크다.

    당신은 long doubleint 를 초기화하기위해 사용할일이 있냐고 말할수있을것이다.

    하지만 이런 식의 초기화는 우리가 의도와 다르게 일어날 가능성이 다분하다.

    이 형태의 초기화에 대해 3.2.1 (p.84) 와 3.3.1 (p.98) 에서 자세히 다루겠다.

    Default Initialization

    변수를 초기화하지않고 정의한다면, default 로 초기화되어 default 값을 받는다.

    어떠한 값이 default 값인지는 변수의 type 에 따라 혹은 변수가 정의된 위치에 따라 다르다.

    명시적으로 초기화되지 않은 built-in type 의 객체 값은 정의 된 위치에 따라 다르다.

    함수 body 의 바깥에 있는 변수는 0 으로 초기화되고,

    함수 안에 있는 built-in type 의 변수는 초기화되지않는다.

    built-in type 의 초기화되지않았을때의 값은 undefined 이다.

    값이 정의되지 않은 (undefined) 변수의 값을 복사하거나 액세스하려고하면 오류가 발생한다.

    각 클래스는 해당 클래스 type의 객체를 초기화하는 방법을 제어합니다.

    initializer 없이 해당 type 의 객체를 정의 할 수 있는지 여부는 클래스에 달려 있다.

    이것이 가능한 경우 클래스는 객체가 가질 값을 결정한다.

    대부분의 클래스는 명시적 초기화없이 객체를 정의 할 수 있다.

    이런 클래스들은 우리에게 적절한 default 값을 제공한다.

    예를 들어, string 클래스의 객체를 만들때 초기화하지않는 경우 객체의 값은 빈 string 이다.

    std::string empty // empty implicitly initialized to the empty string
    Sales_item item; // default-initialized Sales_item object

    몇몇 클래스는 명시적인 초기화를 요구할수있다.

    이런 상황에서 초기화없이 객체를 정의한다면 컴파일러는 우리에게 불평할것이다.

    Note

    함수의 body 안에 있는 초기화되지않은 내장 type 의 객체의 값은 undefined 이다.

    명시적으로 초기화되지않은 클래스 type 의 객체는 class 가 정의한 값을 갖는다.


    2.2.2 Variable Declarations and Definitions

    프로그램을 분리된 logic 으로 작성하는것을 허용하기위해,

    C++ 는 seperate compilation (분리된 컴파일) 을 지원한다.

    분리된 컴파일은 우리의 프로그램을 여러개의 파일로 쪼갤수있고, 독립적으로 컴파일되게 해준다.

    프로그램을 다수의 파일로 분리할때 이 파일들이 서로 코드를 공유할수있는 방법이 필요하다.

    예를 들어, 하나의 파일에 있는 코드는 다른 파일에 정의된 변수를 사용해야할수도있다.

    std::coutstd::cin 을 생각해보라.

    이것들은 표준 라이브러리 어딘가에 정의된 객체이며, 우리는 이 객체들을 사용할수있다.

    Caution : Uninitialized Variables Cause Run-Time Problems

    초기화되지않은 변수는 불확실한 값을 가지고있다.

    이 초기화되지않은 값을 사용하려 시도했을때 발생되는 error 는 매우 debug 하기 어렵다.

    게다가 컴파일러는 이런 에러를 발견하지못한다. 

    (몇몇 컴파일러는 초기화되지않은 변수를 사용을 경고해주기도하지만.)

    초기화되지않은 변수를 사용했을때 일어날수있는 일을 우리는 알수없다.

    우리의 프로그램이 이런 객체를 접근하려하자마자 프로그램이 꺼진다면 그건 행운이다.

    이런 crash 를 추적해서 내려가다보면, 대부분 초기화되지않은 변수를 쉽게 볼게될것이다.

    다른 경우는 프로그램은 완료되지만, error 스러운 결과를 생산하는것이다.

    더 나쁜 것은 한 번의 프로그램 실행에서는 결과가 올바르게 표시되지만 후속 실행에서는 실패 할 수 있다는 것이다.

    더욱이 프로그램에 관련없는 위치에 코드를 추가하면 

    올바른 프로그램이라고 생각한 것이 잘못된 결과를 생성하기 시작할 수 있다.

     

    Tip

     

    내장 type의 모든 객체를 초기화하는 것이 좋다.

    항상 필요한 것은 아니지만 initializer를 생략해도 안전하다는 확신이 들때까지

    initializer를 제공하는 것이 더 쉽고 안전하다.

     

    분리된 컴파일을 지원하기위해, C++ 는 선언(declaration)과 정의(definition)를 구분한다.

    선언(declaration)은 프로그램에게 이름을 알려주는것이다.

    다른 곳에 정의 된 이름을 사용하려는 파일에는 해당 이름에 대한 선언이 포함된다.

    정의(definition) 은 연관된 본체를 생성한다. 

    변수의 선언은 변수의 type 과 이름을 지정한다.

    변수의 정의는 선언이다.

    추가적으로 이름과 type 을 지정하기위해서, 정의는 공간을 할당하고, 변수에 초기값을 제공한다.

    정의가 아닌 선언을 얻기위해 extern 이라는 키워드를 사용하며, 명시적 initializer 를 제공하지않는다.

    extern int i; // declares but does not defiene i
    int j; // declares and defines j

    명시적으로 초기화를 하는 선언은 모두 정의이다.

    extern으로 정의 된 변수에 initializer를 제공 할 수 있지만 그렇게하면 extern이 재정의(override)된다.

    initializer 를 가진 extern 은 정의이다.

    extern double pi = 3.1416; // definition

    함수안에 extern 에게 initializer 를 제공하는것은 error 이다.

     

    Note

     

    변수는 오직 단 한번 정의되어야한다. 하지만 선언은 계속해서 할수있다.

     

    현재 시점에서 선언과 정의의 구분은 모호하게 느껴질것이다. 

    하지만 이것은 매우 중요하다.

    변수를 하나 이상의 파일에서 사용하는것은 변수의 정의와 분리된 선언을 필요로 한다.

    다수의 파일에서 같은 변수를 사용하기위해, 변수는 반드시 하나의 파일에서 단 한번만 정의되어야한다.

    이 변수를 사용하는 다른 파일들은 이 변수를 자신의 파일안에 반드시 선언은 해야하고, 정의는 하지말아야한다.

    이런 C++ 의 분리된 컴파일에 해새 2.6.3 (p.76) 과 6.1.3(p. 207) 에서 자세히 다룰것이다.

    Key Concept : Static Typing (정적 타이핑)

    C++ 는 정적 type 의 언어이다. 

    이 말의 의미는 type 이 컴파일 타임에 check된다는것이다.

    type 을 checking 하는 프로세스를 type checking 이라고 한다.

    지금까지 살펴본 것처럼 객체의 type은 객체가 수행 할 수있는 작업을 제한한다.

    C ++에서 컴파일러는 우리가 사용하는 작업을 확인한다.

    type이 지원하지 않는 작업을 시도하면 컴파일러는 error 메시지를 생성하고 실행 파일을 생성하지 않는다.

    우리의 프로그램이 더 복잡 해짐에 따라서 정적 type checking 이 버그를 찾는 데 도움이 된다는것을 보게될것이다.

    그러나 정적 checking의 결과는 사용하는 모든 entity의 type을 컴파일러에 알려야한다는 것이다.

    한 예로, 변수를 사용하기 전에 변수의 유형을 선언해야한다.


    2.2.3 Identifiers (식별자)

    C ++의 식별자는 문자 (letter), 숫자 (digit) 및 밑줄 (underscore character)로 구성 될 수 있다.

    이름의 길이 제한은 없다.

    식별자는 문자 또는 밑줄로 시작해야하고, 대소문자를 구분한다.

    // defines four different int variables
    int somename, someName, SomeName, SOMENAME;

    C++ 는 아래 Table 2.3 과 Table 2.4 에 있는 이름들을 사용하고있으며,

    이 이름들은 식별자로 사용할수없다.

    Table 2.3. C++ Keywords

    C++ Keywords

    Table 2.4. C++ Alternative Ooperator Names

    C++ Alternative Ooperator Names

    표준은 또한 표준 라이브러리에서 사용할 이름들을 예약한다.

    우리의 프로그램에서 정의한 식별자는 두 개의 연속 된 밑줄을 포함 할 수 없으며

    식별자는 밑줄로 시작하고 바로 뒤에 대문자가 올 수 없습니다.

    또한 함수 외부에 정의 된 식별자는 밑줄로 시작할 수 없다.

    Conventions for Variable Names

    변수 이름 지정에 대해 일반적으로 허용되는 여러 가지 규칙이 있다.

    이러한 규칙을 따르면 프로그램의 가독성을 높일 수 있다.

     

    - 식별자는 그 의미를 표시해야한다.

    - 변수 이름은 일반적으로 소문자다. ex) index, not Index or INDEX

    - 우리가 정의하는 클래스는 일반적으로 대문자로 시작한다. ex) Sales_item

    - 여러 단어가있는 식별자는 각 단어를 시각적으로 구분해야한다. ex) student_lee 또는 studentLee (studentlee 말고)

    Best Practices

    명명 규칙을 일관되게 따르는것이 중요하다.


    2.2.4 Scope of a Name (이름의 범위)

    프로그램의 특정 지점에서 사용 중인 각 이름은 특정 실체(변수, 함수, 유형 등)를 가리킨다. 

    그러나 주어진 이름을 재사용하여 프로그램의 서로 다른 지점에서 다른 실체를 가리킬 수 있다.

    Scope 는 이름이 특정 의미를 갖는 프로그램의 일부이다.

    C ++에서 대부분의 범위는 중괄호 { } 로 구분된다.

    동일한 이름이 서로 다른 범위의 서로 다른 실체를 나타낼 수 있다.

    이름은 선언 된 지점부터 선언이 표시되는 범위의 끝까지 표시된다.

    #include <iostream>
    
    int main() {
    
        int sum = 0;
    
        // sum values from 1 through 10 inclusive
        for (int  val = 1; val <= 10; ++val) {
    
            sum += val; // equivalent to sum = sum + val
    
        }
    
        std::cout << "Sum of 1 to 10 inclusive is " << sum << std::endl;
    
        return 0;
    
    }

    이 프로그램은 3개의 이름 - main, sum, val 과 namespace std 를 사용하고,

    std 는 2개의 이름 cout endl 를 사용한다.

    이름 main { } 바깥에서 정의되었다. 

    이름 main 처럼 함수 바깥에서 정의된 이름은 global scope (전역 범위) 를 가지고있다.

    일단 선언되면 전역 범위의 이름은 프로그램 전체에서 접근할 수 있다.

    이름 summain 함수의 body인 block scope 내에서 정의되었다.

    summain 함수내의 선언 지점부터 나머지 부분 전체에 걸쳐 접근할 수 있지만 외부에서는 접근할 수 없다.

    변수 sum block scope 를 가지고 있다.

    이름 val for문의 범위에서 정의되었다.

    val for문 안에서는 사용할수있지만, main 의 다른곳에서는 사용할수없다.

    Advice : Define Variables Where You Frist Use Them

    일반적으로 객체가 처음 사용되는 지점 근처에서 객체를 정의하는 것이 좋다. 

    이렇게하면 변수 정의를 쉽게 찾을 수 있으므로 가독성이 향상된다.

    더 중요한 것은 변수가 처음 사용되는 위치에 가깝게 정의 될 때 변수에 유용한 초기 값을 제공하는 것이 더 쉽다.

    Nested Scope (중첩된 범위)

    scope 는 다른 scope 를 포함할수있다.

    중첩된 scope 는 inner scope 라고도 부른다.

    포함하는 scope 는 outer scope 라고 부른다.

    scope에 이름이 선언되면 해당 scope 내에 중첩 된 scope 에서 해당 이름을 사용할 수 있습다.

    outer scope에서 선언 된 이름을 inner scope에서 재정의 할 수도 있다.

    #include <iostream>
    
    // Program for illustration purposes only : It is bad style for a function
    // to use a global variable and also define a local variable with the same name
    
    int reused = 42; // reused has global scope
    
    int main() {
    
        int unique = 0; // unique has block scope
    
        // output #1 : uses global reused; prints 42 0 
        std::Cout << reused << " " << unique << std:: endl;
        int reused = 0; // new, local object named reused hides global reused.
    
        // output#2 : uses local reused; prints 0 0 
        std::cout << reused << " " << unique << std::endl;
    
        // output#3 : explicitly requests the global reused; prints 42 0
        std::cout << ::reused << " " << unique << std::endl;
    
        return 0;
        
    }

    Warning

    함수가 사용하거나 사용할 수있는 glocbal 변수와 동일한 이름으로 local 변수를 정의하는 것은 항상 bad idea 이다.


    2.3 Compound Types

    compound type은 다른 type으로 정의 된 type이다.

    C++ 은 몇가지 compound type 을 가지고있는데, 이 챕터에서 다룰것은 reference 와 pointer 이다.

    compound type 의 변수를 정의하는것은 우리가 지금까지 봤던 선언들보다 복잡하다.

    앞서 2.2 (p. 41) 에서 단순한 선언은 변수의 이름이 붙는 type 이라고 배웠다.

    보다 일반적으로 선언은 선언자 목록이 뒤 따르는 기본 type이다.

    각 선언자는 변수의 이름을 지정하고 기본 type과 관련된 type을 변수에 제공한다.

    지금까지 우리가 본 선언은 변수의 이름에 지나지 않았다.

    이러한 변수의 type은 선언의 기본 type이다.

    더 복잡한 선언자는 선언의 기본 type에서 생성된 복합 type으로 변수를 지정한다.


    2.3.1 References (참조)

    Note

     

    새로운 reference 의 종류가 소개되었다. : "rvalue reference" (13.6.1 p.532)

    이 reference 는 class 안에서 주로 사용된다.

    일반적으로 우리가 용어 reference 를 사용하는것은 "lvalue reference" 를 의미한다.

     

    reference 는 객체의 다른 이름을 정의한다.

    reference type 은 다른 type 을 참조(refer)한다.

    reference type 은 &d 형태의 선언자를 사용함으로써 정의할수있다. 

    int ival = 1024;
    int &refVal = ival; // refVal refers to (is another name for) ival
    int &refVale2; // error : a reference must be initialized

    일반적으로 변수를 초기화할 때 initializer의 값이 생성중인 객체에 복사된다. 

    reference를 정의 할때,  initializer 값을 복사하는 대신 referenceinitializer에 묶는다 (bind).

    일단 초기화된 reference는 초기 객체에 bind된 상태로 유지된다.

    다른 객체를 참조하기 위해 reference를 rebind하는 방법은 없다.

    referencerebind하는 방법이 없기 때문에 reference를 초기화해야한다.

     

    Note

     

    reference 는 객체가 아니다. 

    reference 는 존재하는 객체의 다른 이름이다.

    A Reference is an Alias (reference 는 별칭이다)

    reference 가 정의된후에, reference 가 할수있는 모든 operation 은 

    reference 가 bind 한 객체가 할수있는 operation 이다.

    refVal = 2; // assigns 2 to the object to which refVal refers i.e to ival
    int ii = refVale; // same as ii = ival

    reference 에 무언가를 대입한다면, reference 가 bind 한 객체에 대입하는것이다.

    reference 로부터 값을 받아올때 (fetch), reference 가 bind 한 객체의 값을 받는것이다.

    이와 유사하게, reference 를 initializer 로 사용한다면, reference 가 bind 한 객체를 사용하는것이다.

    // ok : refVal3 is bound to the object to which refVale is bound, i.e to ival
    int &refVal3 = refVal;
    
    // initializes i from the value in the object to which refVal is bound
    int i = refVal; // ok : initializes i to the same values as ival

    reference 는 객체가 아니기때문에, reference 를 다른 reference 로 정의할수없다.

    Reference Definitions

    단일 정의로 다수의 reference 를 정의할수있다.

    reference 인 각 식별자 앞에는 기호 &가 와야한다.

    int i = 1024, i2 = 2048; // i and i2 are both ints
    int &r = i, r2 = i2; // r is a reference bound to i : r2 is an int
    int i3 = 1024, &ri = i3; // i3 is an int; ri is a refernece bound to i3
    int &r3 = i3, &r4 = i2; // both r3 and r3 are references

     

    우리가 2.4.1(p. 61) 과 15.2.3(p.601) 에서 다룰 2가지 예외는,

    reference 의 type 과 reference 가 참조하는 객체의 type 이 정확하게 일치해야한다는것이다.

    더 나아가, reference 는 오직 객체에만 bind 될수있다.

    literal 이나 일반적인 표현식의 결과로는 bind 될수없다.

    int &refVal4 = 10; // error : initializer must be an object
    double dval = 3.14;
    int &refVal5 = dval; // error : initializer must be an int objet

    2.3.2 Pointers

    pointer 는 다른 type 을 가리키는 (point to) compound type 이다.

    reference 처럼, pointer 는 다른 객체를 우회적으로 접근하기위해 사용된다.

    reference 와 달리, pointer 는 자신의 권리를 가진 객체이다.

    단일 pointer 는 자신의 수명(lifetime)동안 여러개의 다른 객체를 가리킬수있다. 

    reference 와 달리, pointer 는 정의됨과 동시에 초기화될 필요는 없다.

    다른 built-in type 처럼 pointer 는 block scope 에서 초기화되지않는다면 값은 undefined 이다.

     

    Warning

     

    pointer 는 이해하기 어렵다.

    숙련된 프로그래머들 조차도 pointer 로 인해 발생된 bug 는 매우 debug 하기 어렵다.

     

    pointer type 은 이름앞에 * 를 붙임으로써 정의할수있다.

    * 은 반드시 각 pointer 변수에 반복되어야한다.

    int *ip1, *ip2; // both ip1 and ip2 are pointers to int.
    double dp, *dp2; // dp2 is a pointer to double; dp is a double 

    Taking the Address of an Object

    pointer 는 다른 객체의 주소(address) 를 저장한다.

    객체의 주소는 address 연산자 & 를 사용하여 얻을수있다.

    int ival = 42;
    int* p = &ival; // p holdes the address of ival : p is a pointer to ival

    2번째 문장은 pint 에 대한 pointer 로 정의하고, pival 이란 int 객체를 가리키도록 초기화한다.

    reference 는 객체가 아니기때문에, 주소가 없다.

    이로인해 reference 에 대한 pointer 는 정의할수없다.

    2개의 예외 (2.4.2 p.62, 15.2.3 p, 601) 는 pointer 의 type 과 가리킬 객체의 type 이 반드시 일치해야한다는것이다.

    double dval;
    double* pd = &deval; // ok : initizlier is the address of a double
    double* pd2 = pd; // ok : initializer is a pointer to double
    int* pi = pd; // error : types of pi and pd differ
    pi = &dval; // error : assigning the address of a double to a pointer to int

    pointer의 type은 pointer가 가리키는 객체의 type을 추론하는 데 사용되기 때문에 type이 일치해야한다.

    pointer가 다른 유형의 객체에 주소를 지정하면 기본 객체에 대해 수행된 작업이 실패하게 된다.

    Pointer Value

    Pointer에 저장된 값 (즉, 주소)은 다음 네 가지 상태 중 하나이다.

     

    1. 객체를 가리킬수있다.

    2. 객체 끝 바로 지난 위치를 가리킬 수 있다.

    3. 어떤 객체에도 bind 되지않았음을  나타내는 null pointer 일수있다.

    4. 유효하지 않을 수 있다. (invalid) 앞의 세 가지 이외의 값은 유효하지않다.

     

    유효하지않은 pointer 를 접근 또는 복사하려하는것은 error 다.

    초기화되지않은 변수를 사용하려하는것처럼, 컴파일러는 이런 error 를 발견하지못한다.

    유효하지않은 pointer 를 접근하는것의 결과는 undefined 이다.

    따라서, 우리는 반드시 pointer 가 유효한지 알아야한다.

    위의 4가지중 2번째와 3번째 경우가 유효하다고 하더라도 

    저런 pointer 로 할수있는것은 제한적이다.

    이런 pointer 들은 어떠한 객체도 가리키고있지않기때문에,

    이 pointer 가 가라키는 객체를 접근하려하는것을 할수없다.

    만약 이런 pointer 들에 객체를 접근하려한다면 결과는 역시 undefined 이다.

    Using a Pointer to Access an Object

    pointer 가 객체를 가리킬때, 이 객체에 접근하기위해 * (dereference - 역참조) 연산자를 사용할수있다.

    int ival = 42;
    int* p = &ival; // p holds the address of ival; p is a pointer to ival
    cout << *p; // * yields the object to which p points; prints 42

    pointer 를 역참조하는것은 pointer 가 가리키는 객체를 산출한다.

    역참조의 결과에 대입하여 해당 객체에 대입할 수 있다.

    *p = 0; // * yields the object; we assign a new value to ival through p
    cout << *p; // prints 0

    *p 에 대입하는것은, p 가 가리키는 객체에 대입하는것이다.

     

    Note

     

    오직 유효한 pointer, 즉 객체를 가리키는 pointer 만을 역참조 (dereference) 해야한다.

    Key Concept : Some Symbols Have Multiple Meanings 

    몇몇의 기호는 다중 의미를 지니고있다.

    &* 같은 기호는 표현식에서는 연산자로 또는 선언의 일부로 사용될수있다.

    이 기호들이 사용되는 문맥이 각 기호의 의미를 결정한다.

    int i = 42;
    int& r = i; // & follows a type and is part of a declaration : r is a reference
    int* p; // * follows a type and is part of a delcaration : p is a pointer
    p = &i; // & is used in an expression as the address of operator
    *p = i; // * is used in an expression as the dereference operator
    int& r2 = *p; // & is part of the declaration; * is the dereference operator

    선언에서 &* 은 compound type (복합 type) 을 형성하기위해 쓰인다.

    반면, 표현식에서는 연산자를 나타내는데 사용된다.

    동일한 기호가 문맥에 따라 다양한 의미로 사용되기때문에, 각 기호의 모습을 무시하고,

    애초에 이것들이 서로 다른 기호라고 생각하는것이 좋다.

    Null Pointers

    null pointer 는 다른 객체를 가리키지않는다.

    코드상에서 pointer 가 null 인지 사용하기전에 확인할수있다.

    아래는 null pointer 를 얻는 몇가지 방법이다.

    int* p1 = nullptr; // equivalent to int* p1 = 0;
    int* p2 = 0; // directly initializes p2 from the literal constant 0
    
    #include cstdlib
    
    int* p3 = NULL; // equivalent to int* p3 = 0;

    C+11

    pointer 를 초기화하는 가장 좋은 방법은 C+11 에서 새로 소개된 리터럴 nullptr 을 사용하는것이다.

    nullptr 은 어떠한 pointer type 으로든지 변환될수있는 특수 type 을 가진 리터럴이다.

    대안으로, pointer 를 0 으로 초기화할수도있다.

    오래된 프로그램들은 cstdlib 헤더가 0 으로 정의하는 preprocessor (전처리기) 변수 NULL을 사용하기도한다.

    2.6.3(p. 77) 에서 전처리기에 대해 더 자세히 다룰것이다.

    간단히 먼저 설명해주자면,

    전처리기는 컴파일러전에 작동하는 프로그램이다.

    전처리기 변수들은 전처리기에 의해 관리되며, namespace std 의 일부가 아니다.

    결과적으로, 이 변수들은 std:: 를 앞에 붙이지않고 사용할수있다.

    전처리기 변수들을 사용할때, 전처기이는 자동적으로 변수를 그 변수의 값으로 대체한다.

    따라서, pointer 를 NULL 로 초기화하는것은, 0 으로 초기화하는것과 동일하다.

    현대 C++ 프로그램들은 NULL 을 피하고 nullptr 를 쓰는것이 좋다.

    변수의 값이 0 이라 하더라도, int 변수를 pointer 에 대입하는것은 illegal 이다.

    int zero = 0;
    pi = zero; // error : cannot assign an int to a pointer

    Advice : Initialize all Pointers

    초기화되지않은 pointer 는 run-time error 의 가장 주된 원인중 하나이다.

    다른 초기화되지않은 변수들처럼, 초기화되지않은 pointer 를 사용할때 발생할수있는 일은 알수없다.

    초기화되지않은 pointer 의 사용은 대부분은 run-time crash 를 일으킨다.

    하지만, 이 crash 의 원인을 debug 하는 일은 매우 매우 힘들다.

    대부분의 컴파일러에서 초기화되지 않은 pointer를 사용할 때 pointer가있는 메모리의 bit가 주소로 사용된다.

    초기화되지 않은 pointer를 사용하는 것은 추정 된 위치에서 추정 된 객체에 액세스하라는 요청이다.

    pointer가 할당 된 메모리에있는 비트로 구성된 잘못된 주소와 유효한 주소를 구별 할 방법이 없다.

    모든 변수를 초기화하라는 권장 사항은 pointer에 특히 중요하다.

    가능하면 가리켜야하는 객체가 정의 된 후에만 pointer를 정의하라.

    pointer에 bind 할 객체가 없으면 pointernullptr 또는 0으로 초기화하라.

    이렇게하면 프로그램은 pointer가 객체를 가리키지 않음을 감지 할수있다.

    Assignment and Pointers

    pointer 와 reference 둘다 객체에 대한 우회적인 접근을 제공한다.

    하지만 접근을 어떻게 제공하는지에대해서 이 둘은 중요한 차이를 가진다.

    중요한 차이는 reference 는 객체가 아니다.

    reference 를 최초로 정의한후에, reference 가 다른 객체를 가리키게 할수없다.

    우리가 reference 를 사용할때, 언제나 reference 가 초기화할때 bind 한 객체를 얻는것이다.

    반면, pointer 는 다른 변수들처럼, pointer 에게 새로운 값을 줄수있다.

    pointer 에 새로운 주소값을 대입하면, 그 pointer 는 다른 객체를 가리키게된다.

    int i = 42;
    int* pi = 0; // pi is initialized but addresses no object
    int* pi2 = &i; // pi2 initialized to hold the address of i
    int* pi3; // if pi3 is defined inside a block, pi3 is uninitialized.
    pi3 = pi2; // pi3 and pi2 address the same object.
    pi2 = 0; // pi2 now addresses no object

    대입이 pointer 를 바꾼것인지, pointer 가 가리키는 객체를 가리키는것인지를 파악하는것은 어려울수있다.

    항상 명심해야할것은, 대입(assignment) 은 왼쪽 피연산자를 변경한다는것이다.

    *pi = 0; // value in ival is changed; pi is unchanged

    Other Pointer Operations

    pointer 가 유요하다면, pointer 를 조건으로 사용할수있다.

    우리가 조건으로 일반 산술값을 사용하는것처럼, pointer 가 0 이라면 조건은 false 다.

    0 이 아닌 pointer 는 true 를 산출한다.

    int ival = 1024;
    int* pi = 0; // pi is a valid, null pointer
    int* pi2 = &ival; // pi2 is a valid pointer that holdes the address of ival
    
    if (pi) // pi has value 0, so condition evaluates as false
    if (pi2) // pi2 points to ival, so it is not 0; the condition evaulates as true

    동일한 type 을 가진 2개의 유요한 pointer 는 == 또는 != 연산자를 사용하여 비교할수있다.

    이 연산의 결과는 bool type 이다.

    2개의 pointer 가 같은 주소값을 가지고있다면, 결과는 true, 아니면 false 다.

    2개의 pointer 가 같은 주소를 가지고있거나, 둘다 null 이거나, 동일한 객체를 가리키고있거나,

    동일한 객체의 하나 앞을 가리키고있다면 true 다.

    객체에 대한 pointer 와, 다른 객체의 하나 앞을 가라키는 pointer 가 동일한 주소값을 가질수있다. 이럴때도 true 다.

    왜냐면, 이 비교 연산은 pointer 의 값을 사용하기때문이다.

    이때 조건이나 비교에 쓰이는 pointer 는 유효해야한다.

    유효하지않은 pointer 를 조건이나 비교에 사용할때 결과는 undefined 이다.

    3.5.3(p. 117) 에서 pointer 연산에 대해 더 자세히 다룰것이다.

    void* Pointers

    void* 는 어떠한 object 의 주소든 저장할수있는 특수 type 이다.

    다른 pointer 처럼 void* 는 주소를 저장하지만, 주소가 가리키는 객체의 type 은 알수없다.

    double obj = 3.14, *pd = &obj;
    // ok : void* can hold the address value of any data pointer type.
    void* pv = &obj; // obj can be an object of any type.
    pv = pd; // pv can hold a pointer to any type

    void* pointer 는 몇가지 제한적인 것들만이 가능한데,

    다른 pointer 와 비교할수있고, 함수의 인자로 넘기고 return 할수있고,

    다른 void* pointer 에 대입할수있다.

    반면, void* 로는 어떤 type 의 객체를 가리키고있는지 알수없기때문에,

    그 객체와 관련된 연산은 할수없다.

    type 이 해당 객체가 할수있는 연산을 결정하기때문이다.

    일반적으로, void* pointer 는 메모리안에 저장된 객체에 접근하기보다, memory 자체를 다루는데에 사용된다.

    어떻게 void* 를 사용하는지는 19.1.1 (p.821) 과 4.11.3(p. 163) 에서 자세히 다룰것이다.


    2.3.3 Understanding Compound Type Declarations

    지금까지 보아왔듯이, 변수의 정의는 base type 과 선언자들의 리스트로 구성된다.

    각 선언자는 동일한 정의의 다른 선언자와 다르게 해당 변수를 기본 type 에 연결할수있다.

    따라서, 단일 정의로 다른 type 들의 변수들을 정의할수있다.

    int i = 1024, *p = &i, &r = i;

    Warning

     

    많은 프로그래머가 기본 type과 선언자의 일부일 수있는 type 수정 간의 상호 작용을 해깔려한다.

    Defining Multiple Variables

    type 수정자 (* 또는 &)가 단일 명령문에 정의 된 모든 변수에 적용된다고 생각하는 것은 일반적인 오해다.

    type 수정자와 선언되는 이름 사이에 공백을 둘 수 있기 때문에 문제가 발생한다.

    int* p; // legal but might be misleading

    이 정의는 int* 가 해당 명령문에서 선언 된 각 변수의 type임을 암시하기 때문에 오해의 소지가있을수있다.

    외관에도 불구하고 이 선언의 기본 type은 int* 가 아니라 int다.

    *p 의 type 을 수정할뿐이고, 같은 명령문에 있는 다른 객체에게는 아무것도 말하지않는다.

    int* p1, p2; // p1 is a pointer to int; p2 is an int

    pointer 또는 reference type으로 여러 변수를 정의하는 데 사용되는 두 가지 일반적인 스타일이 있다. 

    첫 번째는 식별자 옆에 type 수정자를 배치하는것이다.

    int *p1, *p2; // both p1 and p2 are pointers to int

    이 스타일은 변수에 표시된 compound type이 있음을 강조한다.

    두 번째는 type 수정자를 type과 함께 배치하지만 명령문 당 하나의 변수만을  정의하는것이다.

    int* p1; // p1 is a pointer to int
    int* p2; // p2 is a pointer to int

    이 스타일은 선언이 compound type을 정의함을 강조한다.

     

    Tip

     

    pointer 나 reference 를 정의하는 단 한가지의 옳은 방법이 있는것은 아니다.

    중요한건 한가지 스타일만을 일관되게 사용해야한다.

    이 책에서 우리는 첫번째 스타일을 사용할것이고, * 또는 & 을 변수 이름옆에 위치시킬것이다.

    Lee : 나는 2번째 스타일을 사용하기때문에, 계속 2번째 스타일로 내용을 고쳐서 하겠음

    Pointers to Pointers

    일반적으로 선언자에 적용 할 수있는 type 수정자 수에는 제한이 없다.

    둘 이상의 수정자가 있으면 논리적이지만 항상 분명하지는 않은 방식으로 결합된다.

    예를 들어, pointer 는 객체이고, 다른 객체처럼 주소가 있다.

    따라서 pointer 의 주소를 다른 pointer 에 저장할수있다.

    각 pointer 의 level 은 * 로 나타낸다.

    ** 를 는 pointer 에 대한 pointer.

    *** 은 pointer 에 대한 pointer 에 대한 pointer...

    int ival = 1024;
    int* pi = &ival; // pi points to an int
    int** ppi = &pi; // ppi points to a pointer to an int

    여기서 pi 는 ival 을 가리키는 pointer 이고, ppiint 를 가리키는 pointer 를 가리키는 pointer 이다.

    그림으로 아래와 같이 표현할수있다.

    int 에 대한 pointer 를 역참조(dereference) 하면 int 를 산출하고,

    pointer 에 대한 pointer 를 역참조하면 pointer 를 산출한다.

    아래에 깔려있는 객체에 접근하기위해서 최초의 pointer 를 2번 역참조해야한다.

    cout    << "The value of ival\n"
            << "direct value : " << ival << "\n"
            << "indrect value : " << *pi << "\n"
            << "doubly indrect value : " << **ppi
            << endl;

    이 프로그램은 ival 을 3가지의 다른 방법으로 print 한다

    1. 직접적으로

    2. int 를 가리키는 pointer pi 를 통하여

    3. ppi 를 2번 역참조하여 

    References to Pointers

    reference 는 객체가 아니다. 따라서, reference 를 가리키는 pointer 를 가질수없다.

    반면 pointer 는 객체이기때문에, pointer 를 가리키는 reference 는 정의할수있다.

    int i = 42;
    int* p; // p is a pointer to int
    int*& r = p; // r is a reference to the pointer p
    r = &i; // r referes to a pointer : assigning &i to r makes p points to i
    *r = 0; // dereferncing r yields i, the object to which p points : changes i to 0 

    r 과 같은 type 을 이해하는 쉬운 방법은, 오른쪽에서 왼쪽으로 정의를 읽어나가는것이다.

    변수 이름에 가장 가까운 기호 (이 경우 &r에서 & )는 변수 type에 가장 즉각적인 영향을 미치는 기호이다.

    따라서 우리는 r 이 reference 라는것을 알수있다.

    선언자의 나머지는 r 이 가리킬 type 을 결정한다. 

    이 경우 다음 기호 *r 이 가리키는 type 이 pointer type 이라는것을 말해준다.

    마침내, 선언자의 기본 type 은 rint 에 대한 pointer 의 reference 라는것을 말한다.

     

    Tip

     

    복잡한 pointer 나 reference 선언을 이해하기위해 오른쪽에서 왼쪽으로 읽어나간다.


    2.4 const Qualifier (const 한정자)

    때때로 우리는 값을 변경할수 없는, 그리고 이 사실을 우리가 알수있는 변수를 정의하고 싶다.

    예를 들어, 버퍼 사이즈를 나타내는 변수를 정의한다고 해보자.

    변수를 사용하면 원래 크기가 필요하지 않다고 판단한 경우 버퍼의 크기를 쉽게 변경할 수 있다.

    반면에 우리는 우리의 코드가 버퍼 크기를 나타내는 데 사용한 변수에 실수로 새 값을 제공하는 것을 방지하고자한다.

    이때 사용하는것이 변수를 변경불가하게 만들어주는 const 이다.

    const int bufSize = 512; // input buffer size

    bufSize 는 constant (상수) 이기때문에, bufSize 에 대입하는 시도이든 error 이다.

    bufSize = 512; // error : attempt to write to const object

    const 객체는, 생성된후 변경할수없기때문에, 생성할때 반드시 초기화되어야한다.

    initializer 는 평소처럼 복잡한 표현식일수있다.

    const int j = get_size(); // ok : initialized at run time
    const int j = 42; // ok : initialized at compile time
    const int k; // error : k is uninitialized const

    Initialization and const

    우리가 지금까지 누누히 여러번 들었던 이야기.

    "객체의 type 이 객체가 수행할수있는 연산을 결정한다"

    const type 은 대부분의 연산을 할수있지만 non-const 가 할수있는 모든것을 할수있는것은 아니다.

    한가지 제한사항은 객체를 변경하는 연산을 할수없다는것이다.

    예를 들어, const int 는 non-const int 를 사용하는 방식으로 똑같이 사용할수있다.

    const intbool 로 변환하는것도, non-const int 와 동일하다. 

    객체의 값을 변경하지않는 연산은 초기화이다.

    어떤 객체를 다른 객체를 초기화하는 용도로 사용할때 이 객체가 const 인지 아닌지는 문제되지않는다.

    int i = 42;
    const int ci = i; // ok : the value in i is copied into ci
    int j = ci; // ok : the value in ci is copied into j

    ciconst int 라하더라도, ci 의 값은 int 이다.

    ci 의 상수성(constness) 는 ci 를 변경하려는 연산을 시도하려할때만 문제가된다.

    cij 를 초기화하기위해 복사할때, ciconst 인지 아닌지는 우리가 알빠가 아니다.

    객체를 복사하는것은, 객체를 바꾸는것이 아니기때문이다.

    복사가 끝났다면, 새 객체는 원래 객체에 더 이상 접근할수없다.

    By Default, const Objects Are Local to a File

    const int bufSize = 512; // input buffer size

    위의 bufSize 처럼 const 객체가 compile-time 상수로부터 초기화되는경우,

    컴파일러는 코드안에서 사용된 해당 변수를 해당 값으로 컴파일 중에 모두 대체한다.

    즉, 컴파일러는 우리의 코드가 bufSize 라고 사용한 모든 위치를 512 라는 값으로 대체하여 코드를 생성한다는것이다.

    변수 값을 대체하기위해서 컴파일러는 변수의 initializer를 확인해야한다.

    우리의 프로그램을 여러개의 파일들로 쪼갤때, const 를 사용한 모든 파일은 initializer 에 접근할수있다.

    initializer를 보려면 변수의 값을 사용하려는 모든 파일에 변수를 정의해야한다 (2.2.2, p. 45).

    이 사용법을 지원하면서 동일한 변수의 다중 정의를 방지하기 위해 const 변수는 파일에 로컬로 정의된다.

    여러 파일에서 동일한 이름으로 const를 정의하면 각 파일에서 별도의 변수에 대한 정의를 작성한 것과 같다.

    때때로 우리는 여러 파일에서 공유하려는 const 변수가 있다.
    그러나 initializer는 상수 표현식이 아니다.

    이 경우 우리는 컴파일러가 각 파일에 별도의 변수를 생성하는 것을 원하지 않는다.

    대신, 우리는 const 객체가 다른 non-const 변수처럼 행동하기를 원한다.

    하나의 파일에 const를 정의하고 해당 객체를 사용하는 다른 파일에 선언하기를 원한다는것이다.

    const 변수의 단일 인스턴스를 정의하기 위해서는 정의와 선언 모두에 extern 키워드를 사용해야한다.

    // file_1.cc defines and initializes a const that is accessible to other files
    extern const int bufSize = fcn();
    
    // file_1.h
    extern const int bufSize; // same bufSize as defined in file_1.cc

    이 프로그램에서, file_1.cc 는 bufSize 를 정의하고 초기화한다.

    이 선언은 initializer 를 포함하고있기때문에, 선언이자 정의이다. 

    하지만 bufSize const 이기때문에, bufSize 를 다른 파일에서 사용하게 해주기위해 extern 을 지정해야한다.

    file_1.h 의 선언 역시 extern 이다.

    이 경우 extern bufSize 가 이 파일에만 존재하는 local 이 아님을 나타내고, 

    이 정의는 어디에서든 사용할수있다.

     

    Note

     

    const 객체를 여러 파일간에 공유하기위해서, extern 변수를 반드시 정의해야한다.


    2.4.1 References to const

    다른 객체처럼, reference 를 const type 의 객체에 bind 할수있다.

    const 에대한 refernece 를 사용하기위해서, reference 는 const type 을 나타내야한다.

    일반적인 reference 와 달리, const 에 대한 reference 는 reference 가 bind 한 객체를 변경하기위해 사용할수없다.

    const int ci = 1024;
    const int& r1 = ci; // ok : both reference and underlying object are const
    r1 = 42; // error : r1 is a reference to const
    int& r2 = ci; // error : non-const reference to a const object

    ci 에 대입할수없는 이유는, ci 를 바꾸려는 reference 를 사용할수없기때문이다.

    따라서, r2 의 초기화는 error 다.

    이 초기화가 legal 일려면, r2 를 사용하여 객체를 변경할수있어야한다.

    Terminology : const Reference is a Reference to const

    C++ 프로그래머들은 "const 에 대한 reference" 를 "const reference" 라고 줄여서 말하는 경향이 있다.

    이렇게 약어로 말하는것은, 당신이 이것이 약어임을 알때 말이된다.

    기술적으로 이야기하자면, const reference 라는것은 없다.

    reference 는 개체가 아니다. 따라서, reference 를 const 자체로 만들수없다.

    사실, reference 가 다른 객체를 가리키게 할수있는 방법은 없다.

    따라서 모든 reference 는 어찌보면 const 다.

    refernce 가 const 또는 non-const type을 참조하는지 여부는

    reference 자체의 bind를 변경할 수 있는지 여부가 아니라

    해당 reference로 수행 할 수있는 작업에 영향을줍니다.

    Initialization and References to const

    2.3.1 (p.51) 에서 reference 의 type 이 reference 가 가리키는 객체의 type 가 정확히 일치해야하지만,

    2가지 예외가 있다고 이야기했었다.

    첫번째 예외는 const 에 대한 reference 를 reference 의 type 으로 변환될수있는 표현식으로 초기화하는 경우이다.

    특히, const 에 대한 reference 를 리터럴이나 일반적인 표현식같은 non-const 객체로 bind 할수있다.

    int i = 42;
    const int& ri = i; // we can bind a const int& to a plain int object
    const int& r2 = 42; // ok : r2 is a reference to const
    const int& r3 = r1 * 2; // ok : r3 is a reference to const
    int& r4 = r * 2; // error : r4 is a plain, non const reference

    초기화 규칙의 차이를 이해하는 쉬운 방법은 다른 type 의 객체에 reference 를 bind 할때

    double dval = 3.14;
    const int& ri = dval;

    무엇이 일어날지 고려해보는것이다.

     

    여기서 riint 가리킨다. ri 에서할수있는 연산은 정수 연산들일것이다.

    하지만 dval 은 정수가 아니라, 부동소수점이다.

    ri 가 bind 하는 객체가 int 임을 분명히하기위해, 컴파일러는 아래와 같이 코드를 변경한다.

    const int temp = dval; // create a temporary const int from the double
    const int& ri = temp; // bind ri to that temporary

    이 경우 ri temporary (일시적인) 객체를 bind 하고있다.

    temporary 객체는 표현식에서 산출되는 결과를 저장하기위해, 컴파일러가 생성하는 이름이 없는 객체이다.

    C++ 프로그래머들은 temporary 객체의 약어로 그냥 "temporary" 라고 부르기도한다.

    이제 riconst 가 아니었을때, 초기화가 허용되면 무슨일이 생기는지 생각해보라.

    ri const 가 아니었다면, 우리는 ri 에 무언가를 대입할수있고, 이렇게 하는것은 ri 가 bind 하는 객체를 변경할수있음을 의미한다.

    그 객체는 dval 이 아니라, temporary 이다. 

    ri dval 을 참조하도록 만든 프로그래머는 ri 에 대입하는것이 dval 을 변경하는것임을 기대했을것이다.

    결국 ri가 bind한 객체를 변경하려는 의도가 아닌 이상 ri에 대입하는 이유는 무엇인가?

    reference 를 temporary 로 bind하는것은 은 프로그래머가 의도 한 바가 확실하지 않기 때문에

    언어가 이를 illegal로 만든다.

    A Reference to const May Refer to an Object That Is Not const 

    const 에 대한 reference 가 오직 우리가 reference 를 통해 할수있는것들을 제한하는것임을 기억해야한다.

    const 에 대한 reference 를 객체에 bind 하는것이, 그 객체가 const 인지 아닌지를 이야기하지않는다.

    const 에 대한 reference 가 참조하는 객체는 const 가 아닐수도있다.

    int i = 42;
    int& ri = i; // r1 bound to i
    const int& r2 = i; // r2 also bound to i; but cannot be used to change i;
    r1 = 0; // r1 is not const; i is now 0
    r2 = 0; // error : r2 is a reference to const

    r2 를 non-const 인 int i 로 bind 하는것은 legal 이다.

    하지만 r2i 를 변경하기위해 사용할수없다. 

    그렇다하더라도,  i 는 여전히 변경할수있다.

    i 를 변경하기위해 직접접으로 대입하거나, r1 같이 i 에 bind 하는 다른 reference 에 대입하는것이다.


    2.4.2 Pointers and const

    reference 처럼, pointer 도 const 나 non-const type 을 가리킬수있다.

    const 에 대한 reference 처럼, const 에 대한 pointer 는 pointer 가 가리키는 객체를 바꾸기위해 사용될수없다.

    const 에 대한 pointer 는 오직 const 객체의 주소값을 저장할뿐이다.

    const double pi = 3.14; // pi is const; it's value may not be changed
    double* ptr = &pi; // error : ptr is a plain pointer
    const double* cptr = &pi; // ok : cptr may point to a double that is const
    *cptr = 42; // error : cannot assign to *cptr

    2.3.2(p. 52) 에서 pointer 의 type 과 pointer 가 가리키는 객체의 type 이 정확히 일치해야하지만,

    2가지 예외가 있다고 이야기했었다.

    첫번째 예외는 const 에 대한 pointer 가 non-const 객체를 가리킬수있다는것이다.

    double dbval = 3.14; // dval is double; it's value can be changed
    cptr = &dval; // ok : but can't change dval through cptr

    const 에 대한 reference 처럼, const 에 대한 pointer 에게 자신이 가리키는 객체가 const 인지 아닌지는 알빠가 아니다.

    pointer 를 const 에 대한 pointer로 정의하는것은 우리가 이 pointer 로 뭘 할수있는지에 대해서만 영향을 미친다.

    따라서, const 에 대한 pointer 가 가리키는 객체가 변할수없는 객체라는 보장이 없다는것을 기억하는것이 중요하다.

     

    Tip

     

    const에 대한 pointer와 reference를 "const를 가리 키거나 참조한다고 생각하는" pointer 또는 reference로

    생각하면 도움이 될 수 있다.

    const Pointers

    reference 와 달리, pointer 는 객체다.

    따라서, 다른 객체 type 처럼 자기 자신이 const 인 pointer 를 가질수있다.

    다른 const 객체처럼, const pointer 는 반드시 생성할때 초기화해야하고, 초기화가 되었다면 (pointer 의 값 : 주소) 

    이 값은 바뀔수 없다.

    pointer 를 const 로 나타나려면, const * 뒤에 붙인다.

    이 위치는 이것은 pointer 이자  const 임을 나타낸다.

    int errNumb = 0;
    int* const curErr = &errNumb; // curErr will always point to errNumb
    const double pi = 3.14159;
    const double* const pip = &pi; // pip is a const pointer to a const object

    우리가 2.3.3(p.58) 에서 봤듯이, 이렇게 복잡한 선언들을 해석할때는 우측에서 좌측으로 읽어나간다.

    이 경우, curErr 와 가까운 기호는 const 이며, currErr 자신이 const 객체임을 의미한다.

    이 객체의 type 은 남은 선언자로 구성된다. 

    다음 선언자에서 첫번째 기호는 * 이며, currErr const pointer 임을 의미한다.

    마지막 선언의 기본 type 은 currErr 의 type 을 완료하고, 이는 int type 의 객체에 대한 const pointer 임을 나타낸다.

    유사하게, pip const double type 의 객체에 대한 const pointer 이다.

    pointer 자신이 const 라는 사실은 이 pointer 가 자신이 가리키는 객체를 바꿀수있는지 아닌지의 여부에 대해서는

    아무것도 말하지않는다.

    우리가 객체를 바꿀수있는지의 여부는, 철저하게 pointer 가리키는 객체의 type 에 달려있다.

    예를 들어, pip const 에 대한 const pointer 이다.

    따라서, pip 의 주소값을 가진 객체 (pip 가 가리키는 객체) 의 값은 바뀔수없고,

    pip 자기 자신이 저장한 주소값 또한 바뀔수없다.

    반면에, curErr 는 non-const int 의 주소값을 가지고있어서, errNumb 의 값을 바꾸기위해 currErr 를 사용할수있다.

    *pip = 2.72 // error : pip is a pointer to const
    
    // if the object to which curErr points(i.e., errNumb) is non-zero
    if (*currErr) {
    
        errorHandler();
        *curError = 0;  // ok : reset the value of the object to which curErr is bound
    
    }

    2.4.3 Top-Level const

    앞서 보아왔듯이, pointer 는 다른 객체를 가리킬수있는 객체다.

    결과적으로, pointer 가 const 인지 아니면 pointer 가 가리키는 객체가 const 인지 독립적으로 나누어서 말할수있다.

    용어 "top-level const" 는 pointer 자기 자신 자체가 const 임을 나타낸다.

    pointer 가 가리키는 객체가 const 일때, 이 const low-level const 라고 한다.

    일반적으로 top-level const 는 객체 자신이 const 임을 나타낸다.

    top-level const 는 어떠한 객체 type 에서든지 나타날수있다.

    ex) 내장 산술 type, class type, pointer type..

    low-level const 는 pointer 나 reference 같은 compound type 의 기본 type 에서 볼수있다.

    다른 type 과 달리, pointer type 은 top-level 과 low-level const 를 둘다 독립적으로 가질수있다.

    int i = 0;
    int* const p1 = &1; // we can't change the value of p1; const is top-level
    const int c1 = 42; // we can't change ci, const is top-level
    const int* p2 = &c1; // we can change p2; const is low-level
    const int* const p3 = p2; // right-most const is top-level, left-most is not.
    const int &r = ci; // const in reference types is always low-level

    top-level 과 low-level 의 구분은 우리가 객체를 복사할때 중요하다.

    객체를 복사할때, top-level 의 const 는 무시된다.

    i = ci; // ok : copying the value of ci : top-level const in ci is ignored
    p2 = p3; // ok : pointed-to type matches : top-level const in p3 is ignored

    반면에, low-level const 는 절대 무시되지않는다.

    객체를 복사할때, 두 객체는 모두 동일한 low-level const 한정자를 가지고있거나,

    2 객체간의 변환이 있어야한다.

    일반적으로, non-const 는 const 로 변환할수있지만, const 를 non-const 로 변환할수는없다.

    int* p = p3; // error : p3 has a low-level const but p doesn't.
    p2 = p3; // ok : p2 has the same low-level const qualification as p3
    p2 = &i; // ok : we can covert int* to const int*
    int& r = ci; // error : can't bind an oridnary int& to a const int object
    const int& r2 = i; // ok : can bind const int& to plain int

    p3 는 top-level const 이자 low-level const 이다.

    p3 를 복사할때, p3 의 top-level const 는 무시되지만, p3 가 가리키는 객체의 type 이 const 라는 사실은 무시되지않는다.

    따라서, non-const int 를 가리키는 p 를 초기화하기위해 p3 를 사용할수없다.

    반면에, p3 p2 에 대입할수있다. 이 pointer 둘은 모두 low-level const type 이기때문이다.

    따라서, p3const pointer (top-level const) 라는 사실은 문제가 되지않는다. 


    2.4.4 constexpr and Constant Expressions

    constant expression(상수식) 은 값을 바꿀수없고, 컴파일 타임에 평가할수있는 표현식이다.

    Lee : constant expression 은 너무 길어서, 상수식으로 한글로 바꿔서 쓰겠음.

    리터럴은 상수식이다. 

    상수식으로부터 초기화되는 const 객체 또한 상수식이다.

    앞으로 살펴보겠지만, 언어에는 상수식을 요구하는 여러 상황이 있다.

    주어진 객체 또는 표현식이 상수식인지 아닌지는 type 과 initializer 에 달려있다.

    const int max_files = 20; // max_files is a constant expression
    const int limit = max_files + 1; // limit is a constant expression
    int staff_size = 27; // stat_size is not a constant expression
    const int sz = get_size(); // sz is not a constant expression

    staff_sizeconst int 가 아니라, non-const int 이기때문에

    리터럴로 초기화되고있지만,  상수식이 아니다.

    반면에 szconst 이지만, initializer 의 값을 run-time 까지 알수없기때문에 역시 상수식이 아니다.

    constexpr Variables

    거대한 시스템에서 initiazlier 가 상수식이여할지 아닐지를 확실히 결정하는것은 어렵다.

    아마 대부분은 우리가 상수식이라고 생각하는 initializer 로 const 변수를 정의할것이다.

    하지만 우리가 그 변수를 상수식이 필요한 자리에 사용하려할때,

    마침내 우리가 만든 initializer 가 상수식이 아니었음을 발견하게될것이다.

    일반적으로 객체의 정의와 이러한 문맥에서의 사용은 광범위하게 분리 될 수 있다.

     

    C+11

    C+11 의 새로운 표준에서는  constexpr 를 사용하여,  

    변수가 상수식인지 확인하도록 컴파일러에 요청할 수 있다.

    constexpr 로 선언된 변수들은 암시적으로 const 이며, 반드시 상수식으로 초기화되어야한다.

    constexpr int mf = 20; // 20 is a constant expression
    constexpr int limit = mf + 1; // mf + 1 is a constant expression
    constexpr int sz = size(); // ok only if size is a constexpr function

    일반적인 함수를 constepxr 변수의 initializer 로 사용할수없다하더라도,

    6.5.2(p. 239) 에서 새로운 표준이 constexpr 함수로 정의할수있게 해주는것에 대해 자세히 다룰것이다.

    constexpr 함수들은 컴파일러가 컴파일 시간에 평가할수있도록 하기위해, 반드시 매우 단순한 함수여야만한다.

    constexpr 함수는 constepxr 변수의 initializer 로 사용할수있다.

     

    Best Practices

     

    일반적으로, 우리가 상수식을 의도하여 사용하려는 변수에게 constexpr 을 사용하는것이 좋다.

    Literal Types

    상수식은 컴파일 타임에 평가할수있는 식이다.

    따라서, constexpr 선언에서 사용할수있는 type 은 제한되어있다.

    constexpr 에서 사용할수있는 type 은 "리터럴" type 이다.

    지금까지 우리가 사용해왔던 산술, reference, pointer type 역시 리터럴 type 이다.

    우리의 Sales_item 클래스와 IO library, string type 등은 리터럴 type 이 아니다.

    띠라서, 이러한 type 들은 constexpr 변수로 정의할수없다.

    다른 종류의 리터럴 type 들은 7.5.6(p. 299) 와 19.3(p. 832) 에서 자세히 배울것이다.

    pointer 와 reference 를 둘 다 constexpr 로 정의할수있다고 하지만,

    이들을 초기화하기위해 우리가 사용할수있는 객체는 매우 제한적이다.

    constexpr pointer 는 nullptr 리터럴 또는 리터럴 0 으로 초기화할수있다.

    또한 고정된 주소를 사용하는 객체를 가리킬수도있다.

    여러가지 이유로 인해 (§ 6.1.1 p.204 에서 자세히 다룸),

    함수안에 정의된 변수는 일반적으로 고정된 주소로 저장되지않는다. 

    따라서, constexpr pointer 가 저런 변수들을 가리키도록 사용할수없다.

    반면에 함수 바깥에서 정의된 객체의 주소는 상수식이며, constexpr pointer 를 초기화하는데에 사용될수있다.

    § 6.1.1 (p. 205)에서 함수가 해당 함수에 대한 호출에서 존재하는 변수를 정의 할 수 있음을 확인할 수 있을것이다.

    함수 밖에서 정의된 객체처럼, 이 특수 local 객체들은 고정된 주소를 가지고있다.

    따라서, constexpr reference 나 constexpr pointer 는 이런 변수들을 bind 하거나 가리킬수있다.

    Pointers and constexpr

    constexpr 선언에서 pointer 를 정의할때, constexpr 지정자가 pointer 에 적용되는것이지,

    pointer 가 가리키는 type 에 적용되는것이 아니라는것을 명심해야한다.

    const int* p = nullptr; // p is a pointer a const int
    constexpr int* q = nullptr; // q is a const pointer to int

    이 둘은 외모가 비슷하지만, pq 의 type 은 매우 다르다.

    pconst 에 대한 pointer 이고, qconstant pointer 이다.

    따라서 constexpr 은 top-level const 를 암시한다는것을 기억하라.

    다른 constant pointer 처럼, constexpr pointer 는 const 나 non-const type 을 둘다 가리킬수있다.

    constexpr int* np = nullptr; // np is a constant pointer to int that is null
    
    int j = 0;
    constexpr int i = 42; // type of i is const int
    
    // i and j must be defined outside any function
    constexpr const int* p = &i; // p is a constant pointer to the const int i
    constexpr int* p1 = &j; // p1 is a constant pointer to the int j
    

    2.5 Dealing with Types

    우리의 프로그램이 복잡해질수록, 우리가 사용하는 type들 역시 복잡해질것이다.

    type 사용의 복잡성은 2가지 방식으로 나타난다.

    몇몇 type 들은 철자가 어렵다. 즉, 그들은 우리가 키보드로 작성할때 실수를 하게 만든다.

    게다가, 복잡한 type 의 형태는 목적과 의미가 모호할수있다.

    다른 복잡성의 근원은 우리가 필요한 정확한 type 을 결정하는것이 가끔 어렵다는것이다.

    그렇게하려면 프로그램의 맥락을 되돌아보아야한다.


    2.5.1 Type Aliases (type 별칭)

    type alias (type 별칭) 은 다른 type 의 동의어가 되는 이름이다.

    type alias 는 복잡한 type 의 정의를, 쉽게 사용할수있도록 단순화해준다.

    type alias 는 우리가 사용하는 type 의 목적을 강조해준다.

    type alias 는 2가지의 방식으로 사용할수있는데, 전통적으로 우리는 typedef 를 사용할것이다.

    typedef double wages; // wages is a synonym for double
    typedef wages base, *p; // base is a synonym for double, p for double*

    키워드 typedef 는 선언의 기본 type 의 일부로 등장한다. (2.3 p.50)

    typedef 를 포함하는 선언은 변수를 정의하기보다 type 의 별칭을 정의한다.

    다른 일반적인 선언들처럼, 선언자는 정의의 기본 type에서 빌드된  compound type을 정의하는 type 수정자를 포함할수있다.

    C+11

    새 표준은 type alias 를 정의하는 2번째 방법을 소개했다. alias 선언을 하는것이다.

    using SI = Sales_item; // SI is a synonym for Sales_item

    alias 선언은 using 이라는 키워드로 시작하여, alias 이름, 그리고 = 를 붙인다.

    alias 선언은 = 의  우측에 있는 type 을 = 의 좌측의 이름으로 정의한다.

    type alias 는 type 이름이고, 따라서 type name 이 필요한 자리에 대체하여 사용할수있다.

    wages hourly, weekly; // same as double horely, weekly;
    SI item; // same as Sales_item item

    Pointers, const, and Type Aliases

    compound type과 const 를 나타내는 type alias 를 사용하는 선언은 놀라운(?) 결과를 산출할수있다.

    예를 들어, char* type 의 별칭인 pstring 을 선언한 예를 보자.

    typedef char* pstring;
    const pstring cstr = 0; // cstr is a constant pointer to char
    const pstring* ps; // ps is a pointer to a constant pointer to char

    이 선언의 base type 은 const pstring 이다.

    일반적으로 base type 에 등장하는 const 는 주어진 type 을 수정한다.

    pstring 의 type 은 char 에 대한 pointer 이다.

    따라서, const pstringchar 에대한 const pointer 이지,  const char 에 대한 pointer 가 아니다.

    type 을 개념적으로 대체하는 type alias 의 사용에 대한 해석은 매우 부정확할수있다.

    const char* cstr = 0; // wrong interpretation of const pstring cstr

    하지만 이 해석은 틀렸다.

    pstring 을 선언에서 사용할때, 선언의 base type 은 pointer type 이다.

    char* 를 이용하여 선언을 재작성할때, base type 은 char 이고, * 는 선언자의 일부이다.

    이 경우, const char 가 base type 이다.

    이 재작성은 cstr char 에 대한 const pointer 로 선언하는게 아니라,

    const char 에 대한 pointer 로 선언한다.

    Lee : 이 파트의 후반부는 전혀 무슨 말인지 사실 잘 모르겠다...

    2.5.2 The auto Type Specifier (C+ 11)

    표현식의 값을 변수에 저장하는것은 드문 일이 아니다.

    변수를 선언하기위해서는 표현식의 type 을 알아야한다.

    우리가 프로그램을 작성할때, 표현식의 type 을 결정하는것은 생각보다 매우 어렵고,

    심지어 불가능할때도 있다.

    C++11 의 새로운 표준에서는 auto type 지정자를 사용하여 

    컴파일러가 직접 변수의 type을 파악하도록 할 수 있다.

    double 과 같은 type 지정자와 달리, auto 는 컴파일러가 initializer로부터 type 을 추론하도록 명령한다.

    암시적으로, auto 를 type 지정자로 사용하는 변수는 반드시 initializer 를 가지고있어야한다.

    // the type of item is deduced from the type of the result of adding val1 and val2
    auto item = val1 + val2; // item initialized to the result of val1 + val2;

    여기서 컴파일러는 item 의 type 을 val1 + val2 를 return 함으로써 얻어지는 type 으로부터 추론할것이다.

    val1 val2 Sales_item 객체라면, item Sales_item type 이 될것이다.

    이 변수들이 double 이라면, item 의 type 은 double 이 될것이다.

    다른 type 지정자처럼, auto 를 사용하여 다수의 변수를 정의할수있다.

    선언은 오직 한개의 base type 을 포함할수있기때문에,

    모든 변수를 위한 선언안에 initialzier들은 반드시 서로 일관적인 type 을 가져야한다.

    auto i = 0, *p = &i; // ok : i is int and p is a pointer to int
    auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi

    Compound Types, const, and auto

    컴파일러가 auto에 대해 추론하는 type은 initializer의 type과 항상 정확히 동일하지는 않다.

    대신 컴파일러는 일반 초기화 규칙을 따르도록 type을 조정한다.

    첫번째로, 우리가 바왔던것처럼, reference 를 사용할때, 우리는 실제로 reference 가 참조하는 객체를 사용하는것이다.

    특히, reference 를 initializer 로 사용할때, initializer 는 해당하는 객체이다.

    컴파일러는 이 객체의 type 을 auto 의 type 추정을 위해 사용한다.

    int i = 0, &r = i;
    auto a = r; //  a is an int(r is an alias for i, which has type int)

    두번째로, auto는 일반적으로 top-level const 를 무시한다.

    일반적인 초기화들처럼, low-level const (예를 들어, const 에 대한 pointer)만이 유지된다.

    const int ci = i, &cr = ci;
    auto b = ci; // b is an int(top-level const in ci is dropped)
    auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
    auto d = &i; // d is an int* (& of an int object is int* )
    auto e = &ci; // e is const int*(& of a const object is low-level const)

    top-level const 를 추정 type 으로 원한다면, 명시해야한다.

    const auto f = ci; // deduced type of ci is int; f has type const int

    또한, auto 로 추정된 type 에 대한 reference 를 얻을수도있다.

    일반 초기화 규칙이 적용된다.

    auto& g = ci; // g is a const int& that is bound to ci
    auto& h = 42; // error : we can't bind a plain reference to a literal 
    const auto& j = 42; // ok : we can bind a const reference to a literal

    auto 로 추정된 type 에 대한 reference 를 요청할때, initializer 의 top-level const 는 무시된다.

    일반적으로, reference 를 initializer 로 bind 할때 const 는 top-level 이 아니다.

    동일한 명령문에서 여러개의 변수를 정의할때, 

    reference 나 pointer 는 특정 선언자의 부분이지, 선언의 base type 의 부분이 아님을 명심해야한다.

    따라서, 초기화는 반드시 일관적인 auto 추정 type 을 제공해야한다.

    auto k = ci, &l = i; // k is int; l is int&
    auto &m = ci, *p = &ci; // m is a const int&; p is a pointer to const int
    
    // error : type deduced from i is int; type dedueced from &ci is const int
    auto &n = i, *p2 = &ci; 

    2.5.3 The decltype Type Specifier

    때때로 우리는 컴파일러가 표현식에서 추론하는 type으로 변수를 정의하고 싶지만,

    변수를 초기화하는 데 이 표현식을 사용하고자하지는 않을때가 있따.

    이런 경우를 위해, C++ 의 새 표준은 type 지정자 decltype 을 소개한다.

    decltype 은 피연산자의 type 을 return 한다.

    컴파일러는 type 을 결정하기위해 표현식을 분석하지만, 이 표현식을 평가하지는않는다.

    C+11

    decltype(f()) sum = x; // sum has whatever type f returns

    여기서 컴파일러는 함수 f 를 호출하지않는다, 단지 이 함수가 호출되었을때 return 하는 type만을 sum 에게 사용한다.

    즉, 컴파일러는 sum 에게 우리가 f 를 호출되었을때 return 되는 type 과 동일한 type 을 준다는것이다.

    deltype 이 top-level const 와 reference 를 다루는 방법은 auto 가 하는방법과 약간 차이가 있다.

    decltype을 적용한 표현식이 변수이면 decltype은 해당 변수의 유형을 top-level const 와 reference 를 포함하여 반환한다.

    const int ci = 0, &cj = ci;
    decltype(cj) x = 0; // x has type const int
    decltype(cj) y = x; // x has type const int& and is bound to x
    decltype(cj) z; // error : z is a reference nad must be initialized

    cj 는 reference 이기때문에, decltype(cj) 역시 reference type 이다.

    다른 reference 처럼, z 는 반드시 초기화되어야한다.

    It is worth noting that decltype is the only context in which a variable defined as
    a reference is not treated as a synonym for the object to which it refers. (이해안되서, 번역못함)

    decltype and References

    decltype 을 변수가 아닌 표현식에 적용할때는 표현식이 산출하는 type 을 얻는다.

    4.1.1(p. 135) 에서 보겠지만, 몇몇 표현식은 decltype 이 reference type 을 산출하도록 한다.

    일반적으로 decltype은 대입의 왼쪽에있을 수있는 객체를 생성하는 표현식에 대한 reference type을 return한다.

    // decltype of an expression can be a reference type
    int i = 42, *p = &i, &r = i;
    decltype(r + 0) b; // ok : addition yields an int; b is an(uninitialized) int
    decltype(*p) c; // error : c is int& and must be initialized

    여기서 r 은 reference 이다. 따라서 decltype(r) 역시 reference type 이다.

    r 이 참조하는 type 을 얻고자한다면, r + 0 처럼 r 을 표현식으로 사용할수있다.

    이 표현식은 reference type 이 아닌 값을 산출한다.

    반면에, 역참조 연산자는 decltype 이 reference 를 return 하는 표현식의 예이다.

    게다가 우리는 이 객체에 대입할수있다.

    따라서, decltype(*p) 에 추정된 type 은 int 가 아니라 int& 다.

    decltype auto 의 또다른 중요한 차이는 decltype 에 의한 type 추정이 주어지는 표현식의 형태에 달려있다는것이다.

    혼란스러울수있는 것은 변수 이름을 괄호로 묶으면 decltype이 반환하는 type에 영향을 미친다는 것이다.

    decltype 을 괄호가 없는 변수에 적용하면, 이 변수의 type 을 얻는다.

    이 변수를 1개 내지 2개의 괄호로 묶으면, 컴파일러는 피연산자를 표현식으로 평가한다.

    변수는 대입의 왼쪽이 될수있는 표현식이다.

    결과적으로 이런 표현식에서 decltype 은 reference 를 산출한다.

    // decltype of a parenthesized variable is always a reference
    decltype((i)) d; // error : d is int& and must be initialized
    decltype(i) e; // ok : e is an (uninitialized) int

    Warning

    기억해야할것은, decltype((variable)) 은 언제나 reference type 이다.

    하지만 decltype(variable) 은 오직 variable 이 reference 일때만 reference type 이다.


    2.6 Defining Our Own Data Structures

    자료 구조는 연관된 데이터 요소들을 그룹으로 묶고, 이 데이터를 사용하는 전략이다.

    예를 들어, 우리의 Sales_item 클래스는 ISBN을 그룹화하며, 이 책의 사본 수 및 해당 판매와 관련된 수익을 집계한다.

    또한, isbn 함수와 >>, <<, +, 와 += 연산자를 사용한 연산을 제공한다.

    C++ 에서, 클래스를 정의하여 사용자 정의의 데이터 type 을 정의한다.

    우리가 Chapter 1 에서 사용했던 Sales_item type 처럼,

    라이브러리의 string, istream, ostream 도 모두 클래스로 정의되어있다.

    클래스에 대한 C++ 지원은 광범위하다. 

    사실 Part III와 IV는 클래스 관련 특징을 설명하는 데 주로 사용된다.

    Sales_item 클래스는 매우 간단하지만, 14장까지 우리 자신의 연산자를 쓰는 방법을 배울 때까지는 

    이 클래스를 완전히 정의할 수 없을 것이다.


    2.6.1 Defining the Sales_data Type

    우리는 아직 Sales_item 클래스를 작성할수는없지만,

    동일한 데이터 요소들을 가진 구체적인 클래스를 작성할수있다.

    이 클래스를 사용하는 우리의 전략은 사용자가 데이터 요소들에 접근할수있고,

    그들 스스로가 연산을 구현할수있게 해주어야한다.

    우리의 데이터 구조는 어떠한 연산도 지원하지않기때문에,

    Sales_item 과 구분하기위해 Sales_data 라고 명명하겠다.

    Sales_Data 클래스는 아래와 같이 정의할수있다.

    struct Sales_data {
    
        std::string bookNo;
        unsigned units_sold = 0;
        double revenue = 0.0;
    
    };

    우리의 클래스는 struct 라는 키워드로 시작하여, 그 다음은 클래스 이름, 그 다음 클래스 body 이다.

    클래스 body 는 { } 로 둘러쌓여지면서 새로운 scope 를 형성한다.

    클래스안에서 정의된 이름들은 클래스안에서는 서로 unique 해야한다.

    하지만, 클래스 바깥에서 이 이름들을 똑같이 재사용하는것은 괜찮다. 

    클래스 body 를 끝내는 닫힌 괄호 } 는 반드시 세미콜론 ; 으로 끝나야한다.

    세미콜론 ; 이 요구되는 이유는 클래스 body 이후에 변수들을 정의할것이기때문이다.

    struct Sales_data { /* ... */} accum, trans, *salesptr;
    
    // equivalent, but better way to define thse objets
    struct Sales_data { /* ... */ };
    Sales_data accum, trans, *salesptr;

    세미콜론은 선언자 리스트의 끝을 나타난다.

    일반적으로, 클래스 정의의 일부로 객체를 정의하는것은 bad idea 이다.

    단일 명령문에서 변수와 클래스를 둘다 정의하는것은 코드를 모호하게 만든다.

     

    Warning

     

    클래스 정의이후에 세미콜론을 빠뜨리는것은 초보 프로그래머들가 매우 자주 하는 실수이다.

    Class Data Members

    클래스의 바디는 클래스의 member 들을 정의한다.

    객체의 데이터 member 를 수정하는것은 다른 Sales_data 객체의 데이터를 변경하지않는다.

    데이터 member 를 정의하는것은 일반적인 변수를 정의하는 방법과 동일하다.

    1개 이상의 선언자에 의해 기본 type 을 지정한다.

    우리으 클래스는 3개의 member 를 가지고있다.

    string type 의 bookNo, unsigned type 의 units_sold, double type 의 revenue 이다.

    Sales_data 객체는 이 3가지의 member 를 가질것이다.

    C+11

    C+ 11 의 새로운 표준은, 데이터 member 를 위해 in-class initializer 를 지원하다.

    우리가 객체를 생성할때, 이 in-class initializer 는 데이터 멤버들을 초기화하는데에 사용된다.

    initializer 가 없는 멤버들은 default 로 초기화 된다. (2.2.1,  p.43)

    따라서, Sales_data 객체를 정의할때, units_soldrevenue 0 으로 초기화되고,

    bookNo 는 빈 string 으로 초기화될것이다.

    in-class initializer 는 형태가 제한이 되어있는데,

    { } 또는 = 을 사용하여야하며, ( ) 는 사용할수없다.

    7.2 (p.268) 에서 우리의 데이터 구조를 정의하기위해 사용하는 C++ 의 2번째 키워드 class 를 배울것이다.

    이때 왜 struct 를 먼저 여기서 배웠는지 설명할것이다.

    클래스와 관련된 기능들을 설명하기전까지는 일다 struct 를 데이터 구조를 정의하기위해 사용하겠다.


    2.6.2 Using the Sales_data Class

    Sales_item 클래스와는 달리, 우리의 Sales_data 클래스는 어떠한 연산도 제공하지않는다.

    Sales_data 의 사용자들은 그들 스스로가 필요한 연산을 직접 작성해야한다.

    2개의 transacation 을 더하는 프로그램을 만들어보자.

    각 transaction 은 ISBN, 빨린 책의 수, 각 책이 팔린 가격을 가지고있다.

    Adding Two Sales_data Objects

    Sales_data 는 어떠한 연산도 제공하지않기때문에, 우리 스스로 input, output, 더하기 연산에 대한 

    코드를 작성해야한다.

    Sales_data 클래스는 Sales_data.h 에 정의되어있다고 가정해보자.

    어떻게 이 헤더를 정의하는지는 2.6.3(p.76) 에서 배울것이다.

    이 프로그램은 우리가 지금까지 작성했던 프로그램보다 코드가 길것이기때문에,

    분리된 파트로 나누어 설명하겠다.

    전반적으로 우리의 프로그램은 다음과 같은 구조를 갖는다.

    #include <iostream>
    #include <string>
    #include "Sales_data.h"
    
    int main() {
    
        Sales_data data1, data2;
        // code to read into data 1 and data2
        // code to check whether data1 and dat2 have the same ISBN
        // and if so print the sum of data1 and data2
    
    }

    우리의 기존 프로그램처럼, header 를 추가후, input 을 보관할 변수들을 정의한다.

    Sales_item 과 달리, 우리의 새 프로그램은 string 헤더를 include 한다.

    왜냐면, 우리의 코드가 string 인 멤버 bookgNo 를 관리해야하기때문이다.

    Reading Data into a Sales_data Object

    챕터 3 에서 10까지 string 라이브러리에 대해 자세히 다루지는 않겠지만,

    ISBN 멤버를 정의하고 사용하기위해 몇가지만 배우고 넘어가자.

    string type 은 일련의 문자들을 저장한다.

    또한 >>, <<, == 연산자들을 읽고, 쓰고, string 을 서로 비교하기위해 사용한다.

    이 지식을 바탕으로 첫번째 transaction 을 읽어보자.

    double price = 0; // price per book, used to caculate total revenue
    // read the first transactions : ISBN, nmber of books sold, price per book
    std::cin >> data1.bookNo >> data1.units_sold >> price;
    // caculate total revenue from price and units_sold
    data1.revenue = data1.units_sold * price;

    우리의 transaction 은 각 책이 팔린 가격을 포함하지만,

    우리의 데이터 구조는 전체 수입만을 저장한다.

    우리는 transaction 데이터를 double type 의 price 로 읽어들이고 revenue 멤버를 계산할것이다.

    std::cin >> data1.bookNo >> data1.units_sold >> price;

    이 input 명령문은 . 연산자를 data1 의 멤버인 bookNo units_sold 로 input 을 읽어드리기위해 사용한다.

    마지막 명령문은 data1.units_sold 와 price 를 data1 의 멤버 revenue 로 대입한다.

    우리의 프로그램은 data 2 를 읽기위해, 동일한 코드를 반복한다.

    // read the second transaction 
    std::cin >> data2.bookNo >> data2.units_sold >> price; data2.revenue = data2.units_sold * price;

    Printing the Sum of Two Sales_data Objects

    우리가 해야할 다른 작업은 transcations 이 동일한 ISBN 을 가지고있는지 확인해야한다.

    둘이 서로 동일하다면, sum 을 인쇄할것이고, 그렇지않다면 error 메세지를 인쇄한다.

    if (data.bookNo == data2.bookNo) {
    
        unsigned totalCnt = data1.units_sold + data2.units_sold;
        double totalRevenue = data1.revenu + data2.revenu;
        // print : ISBN, total sold, total revenue, average price per book
        std::cout << data1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
    
        if (totalCnt != 0) {
    
            std::cout << totalRevenue / totalCnt << std::endl;
    
        }
    
        else {
    
            std::cout << "(no sales)" << std::endl;
    
        }
    
        return 0; // indicate success
    
    
    } else {
    
        std::cerr << "Data must refer to the same ISBN" << std::endl;
    
        return -1; // indicate failure
    
    }

    첫번째 ifdata1 data2 의 멤버 bookNo 를 비교한다.

    이 멤버들이 동일한 ISBN 이라면, { } 안에 있는 코드가 실행되고,

    이 코드는 2개의 변수를 서로 더한다.

    왜냐면 평균 가격을 인쇄해야하므로 판매 된 총 판매량과 수익을 계산하여 각각 totalCnt totalRevenue에 저장한다.

    이 값들을 인쇄하였다면, 다음으로 팔린 책이 있는지 체크하고, 

    그렇다면 각 책당 명균 가격을 계싼한다.

    만약 팔린 책이 없다면, "(no sales)" 를 인쇄한다.


    2.6.3 Writing Our Own Header Files

    19.7(p. 852) 에서 보겠지만, 제한된 기능으로 함수안에서 클래스를 정의할수있다.

    결과적으로, 함수안에서 클래스를 일반적으로 정의하지않는다.

    함수밖에서 클래스를 정의할때, 어떠한 소스 파일이든 클래스의 정의는 단 한번 이루어져야한다.

    게다가, 우리가 여러개의 다른 파일에 클래스를 사용하려 한다면, 각 파일의 클래스 정의는 모두 동일해야한다.

    각 파일의 클래스 정의가 확실히 동일하게 만들기위해, 클래스를 헤더 파일에 주로 정의한다.

    일반적으로 클래스는 클래스의 이름으로부터 파생된 파일명을 가진 헤더 파일안에 저장된다.

    예를 들어, string 라이브러리는 string 헤더안에 저장되어있다.

    이와 유사하게, 우리가 봤던 Sales_data 클래스는 Sales_data.h 라는 파일안에 저장될것이다.

    헤더는 일반적으로 오직 한번만 정의할수있는 클래스의 정의, const 또는 constexpr 변수들을 포함한다.

    예를 들어, 우리의 Sales_data 클래스는 string 멤버를 가지고있기때문에,

    Sales_data.h 는 반드시 #include string 을 해야한다.

    우리가 보아왔듯이, Sales_data 를 사용하는 프로그램 역시 bookNo 멤버를 사용하기위해 string 헤더가 필요하다.

    결과적으로 Sales_data 를 사용하는 프로그램은 string 헤더를 2번 include 해야한다.

    한번은 직접적으로, 한번은 Sales_data.h 를 포함하는 부작용으로.

    헤더 한번 이상 포함될수있기때문에, 우리는 헤더가 여려번 include 되어도 안전하도록 작성해야한다.

     

    Note

     

    헤더가 업데이트될때마다, 이 헤더를 사용하는 소스파일들은 새로운 또는 변화된 선언을 얻기위해 다시 컴파일되어야한다.

    A Brief Introduction to the Preprocessor (전처리기에 대한 간략한 소개)

    헤더를 여러번 include 하는것을 안전하게 하기위하여 사용되는 가장 일반적인 기술은 

    preprocessor(전처리기)에 의존하는것이다.

    C++ 가 C 로부터 상속받는 전처리기는 컴파일전에 구동하여 우리의 프로그램의 소스들을 변경하는 프로그램이다.

    우리의 프로그램은 이미 전처리기 장비 #include 에 의존하고있다.

    전처기기가 #include 를 본 순간, #include 를 헤더안에 모든 컨텐츠로 대체한다.

    C++ 프로그램은 또한 header guards 에 대한 전처리기를 사용한다.

    헤더 가드는 전처기기 변수에 의존한다.

    전처기기 변수는 2가지의 가능한 상태중 하나를 갖는다. : define 또는 not defined

    #define 지시어는 이름을 가져가 전처리기의 변수로 정의한다.

    주어진 전처리기 변수가 정의되었는지를 테스트하는 2개의 다른 지시어가 있다.

    #ifdeftrue 면 이 변수는 정의된것이고, #ifndeftrue 면 이 변수는 정의되지않은것이다.

    테스트가 참이면 #ifdef 또는 #ifndef 다음의 모든 항목이 일치하는 #endif까지 처리된다.

    이 설비를 복수의 include 를 방지하기위해 사용할수있다.

    #ifndef SALES_DATA_H
    #define SALES_DATA_H
    #include <string>
    
    struct Sales_data {
    
        std::string bookNo;
        unsigned units_sold = 0;
        double revenue = 0.0;
        
    };
    
    #endif

    Sales_data.h 가 처음으로 포함되었을때, #ifndef 의 테스트는 true 일것이다.

    따라서 전치리기는 #ifndef 부터 #endif 까지를 프로세스 할것이다.

    결과적으로 전처리기 변수 SALES_DATA_H Sales_data.h 의 내용으로 정의되고,

    우리의 프로그램에 복사될것이다.

    이후에 우리가 Sales_data.h 를 같은 파일에 다시 추가하려한다면, #ifndef 지시어는 false 가 될것이다.

    따라서, #endif 지시어까지의 모든 내용들은 무시될것이다.

     

    Warning

     

    전처리기 변수 이름은 C++ 의 scope 규칙을 따르지않는다.

     

    헤더 가드의 이름을 포함하여 전처리기 변수는 반드시 프로그램 내내 unique 해야한다.

    일반적으로, 헤더안의 클래스명을 기반으로 헤더 가드 이름을 짓는다.

    우리 프로그램의 다른 것들과의 이름 충돌을 피하기위하여, 

    전처리기 변수명은 주로 all uppercase (모두 대문자) 로 짓는다.

     

    Best Practices

     

    헤더는 아직 다른 헤더에 포함되어있지않더라도, 반드시 가드가 필요하다.

    헤더 가드는 작성하기가 쉽지 않으며 습관적으로 정의함으로써 필요한지 여부를 결정할 필요가없다.


    Chapter Summary

    type 은 C++ 프로그래밍의 근간이다.

    각 type은 해당 type의 객체가 수행 할 수있는 연산 및 저장소 요구 사항을 정의한다.

    C++ 언어는 int char 같은 내장 type 을 제공하고, 이들은 하드웨어와 밀접하게 연결된다.

    type 은 const 또는 non-const 가 될수있으며,

    const 객체는 반드시 초기화되어야하고, 초기화가 되었다면 변경할수없다.

    추가적으로, 우리는 pointer 나 referene 같은 compound type 을 정의할수있다.

    compound type 은 다른 type 으로 정의된 type이다.

    C++ 언어는 클래스를 정의함으로써 우리 고유의 type 을 정의할수있게 해준다.

    라이브러리는 클래스 기능을 사용하여 IO string type과 같은 상위 수준 추상화 집합을 제공한다. (high-level abstraction)


    Defined Terms

    address

    alias declaration

    artithemtic types

    array

    auto

    base type

    bind

    byte

    class member

    compound type

    const

    const pointer

    const reference

    constant expression

    constexpr

    conversion

    data member

    declaration

    declarator

    decltype

    default initialization

    definition

    escape sequence

    global scope

    header  guard

    identifer

    in-class initializer

    in scope

    initialized

    inner scope

    integral types

    list initialization

    literal

    local scope

    low-level const

    member

    nonprintable character

    null pointer

    nullptr

    object

    outer scape

    pointer

    pointer to const

    preprocessor

    preprocessor variable

    reference

    reference to const

    scope

    global

    class

    namespace

    block

    separate compilation

    signed

    string

    strcut

    temporary

    top-level const

    type alias

    type checking

    type specifier

    typedef

    undefined

    uninitialized

    unsigned

    variable

    void* 

    void type

    word

    & operator 

    * operator

    #deifne

    #endif

    #ifdef

    #ifndef