ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C++ Primer CH2. Variables and Basic Types
    c++ 2024. 1. 21. 16:00

    c++는 정적 타입 언어이다.

    정적 타입 언어란, 컴파일러가 컴파일할 때, 모든 식별자의 타입을 알아야 한다는 것이다.

    c++은 class에 들어갈 data와 class가 수행할 연산도 커스텀할 수 있다.

     

    Type(타입)은 데이터와 연산자의 의미를 결정한다.

    char = same size as machine basic character set = single Byte

     

    char 8

    wchar_t 16

    char16_t 16

    char32_t 32

    short: 16bits

    int: 16bits(2Bytes)

    long 32

    long long 64

    float32

    double 64

    long double 96

     

    메모리는 워드 단위로 읽힌다.

    워드 = 32bit(4B) or 64bit(8)

    int main() {
        char c2 = 60;
        int a = c2 + 20;
        cout << "result: "<< c2 << endl;
        return 0;
    }
    // 결과: result: <
    // 60이 안나온 이유: c2의 타입이 char이고, 아스키코드상 60은 <와 매칭되기 때문이다.
    // c2대신 a 넣으면 80 나옴. 이유: c2가 더 큰 타입인 int로 바뀌기 때문.
    

     

    undefined behavior: 컴파일은 성공하는데 런타임시에 발생할 수도 있는 에러

    implementation-defined behavior: 머신A에서는 잘 돌아갔던 프로그램이 머신B로 갔을 때 에러 발생하는 것

    예: int 타입 크기를 고정시켜 놓은 경우.(변수할당을 통해)

     

    signed와 unsigned value를 섞어 사용하지 말라(표현식에서)

    signed → unsigned로 implicit type casting되므로 위험하다.(예기치 못한 동작)

    literal(값 그자체, self-evident자명함)

     

    literal 종류들

    - 숫자 literal

    - integer literal: 1,2,3…(decimal) 024(octal), 0x14(hexadecimal)

    - char

       - ‘a’

    - char string

       - “Hello World”,

       - H, e, l, l,… → 각각 const char이다.

       - 컴파일러가 string 맨끝에 ‘\0’ 붙임.

     

    escape sequence

       - ‘\n’ 이런식으로 표현하는데 문자 1개로 취급함

     

    literal 쓸 때 명시하는 법

    prefix(접두사, 앞에붙음)

    u → unicode 16 character, char16_t

    U → unicode 32 character, char32_t

    L → wide character, wchar_t

    u8 → utf-8(string literals only), char

     

    suffix(접미사, 뒤에붙음)

    u,U → unsigned

    l,L → long

    ll,LL → long long

     

    floating point literal에 붙이면,

    f,F → float

    l,L → long double

     

    unsigned를 나타내는 u,U는 number literals에 붙을 수 있다.

    ex)UL: unsigned long

     

    boolean and pointer literals

    boolean: true, false

    pointer: nullptr

     

    initialization과 assignment는 c++에서 동작과정이 다르다

    초기화(initialization): 변수가 생성될 때 변수 선언 및 값 할당 등의 과정

    할당(assignment): 변수의 현재 값을 지우고 다가올 값으로 교체하는 것

     

    List initialization

    {} 써서 초기화하는 것.

    ex)

    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
    

    여기서 a{ld} → 이게 direct-list-initialization인데, {}쓰려면 narrowing conversion 일어나면 안됨.

    narrowing conversion은 변환 후 precision이 감소하는 변환을 말함.

     

    여기서 long double → int 되면서 소수점은 날라가므로 precision이 줄어듦 → narrowing conversion → {}는 narrowing conversion 허용 안함 → error

     

    함수 밖에 선언된 변수들은 초기값이 0으로 initialize 된다.

    함수 안에 선언된 변수들은 uninitialize 된다 → 값이 undefined임.

     

    Objects 종류

    built-in type(int나 char 등)

    class type(우리가 만든것, stl 포함)

     

    built-in type에 대해 initialize 하는게 좋다. → 컴파일러는 warning을 띄우지만 안띄우는 경우도 있고 런타임시에도 됐다 안됐다 하는 경우 있어서 어디서 버그 발생하는지 찾기 어렵기 때문

     

    Declaration and Definition

    변수 선언, 정의, 초기화

    선언: 프로그램에게 해당 이름을 가진 변수가 있다 라고 알려주는 것

    정의: 실제 개체(entity)를 만들어 이름과 매칭시키는 것

    선언만: extern int i;

    선언+정의: int j;

     

    정의는 한번만 되어야 하지만, 선언은 여러곳에 할 수 있다.

     

    c++에서 scope → curly braces가 기준 → block scope

     

    변수가 사용되는 곳에다가 정의해라. 미리 다해놓지 말고

    → 초기화시 값 넣을 때 편함.

     

    전역 스코프에서 정의된 변수를 사용하고 싶을 때 → ::변수명

     

    Compound types

    References

    • lvalue reference(지금 배울것)
    • rvalue reference(이후에 배움)

    일반적인 변수 초기화(int a = 10)와 참조(int &b = a)의 다른 점

    전자: 초기자(initializer)를 복사해서 생성될 object에 넣음

    후자: 초기자와 참조를 bind(묶음) → 복사 과정 X

    int main()
    {
        int ival = 1024;
        int &refVal = ival;
        // int &refVal2; // reference는 무조건 initialized 되어야 함.
        ival = 10;
        cout << refVal << endl;
    		int &test = 10; // 안됨. object만 bind 가능
    }
    

    refVal과 ival binding 함. binding이란? → 초기자 object와 묶이는 것

    ival object의 값을 1024→10으로 바꿈

    출력결과 10

     

    같은 reference(참조) 변수에 대해 rebind 하는 법 없음.

     

    왜 rebind되지 않도록 만든 것인가?

    Reference는 기존 변수의 또다른 이름이라고 보면 된다.

    Reference는 object가 아니다.

    Pointers

    reference와의 차이점

    • pointer는 정의시 initialize 안해줘도 됨.
    • 재할당 가능
    • 복사 가능
    • 실제로 object임

    포인터에 포인터를 할당할 땐 타입이 같아야 함.

    포인터의 역참조*

    int *p = &val;

    cout << *p;

    or

    int *p;

    *p = 10;

    cout << *p // 10

     

    *p를 써서 할당할 때, p가 가리키고 있는 object에 할당하고 있다라고 해석하면 편하다.

    따라서,

    int main() {
        int val;
        int *p = &val;
    		int *q;
        *p = 10;
    		*q = 100;
        cout << *p;
    		cout << *q; // error -> q가 가리키는 object가 없으므로.
    }
    
    

     

    Null Pointers

    nullptr

    int *p1 = nullptr == int *p1 = 0 == int *p1 = NULL;

    NULL은 preprocessor에서 처리되어 컴파일러가 컴파일하기 전에 0으로 바꿔줌 → 0과 똑같음 → 안씀

    아무 object도 가리키고 있지 않다는 것을 명시해줌으로써 undefined behavior를 방지함(runtime crash)

    int zero = 0;

    pi = zero

    → 이거는 int타입을 int*타입 변수 p1에 할당해주는거라서 error

    void*

    object를 가리키고 있지만 그것이 어떤 타입인지는 정해지지 않았을 때

    아무 타입의 포인터든 할당될 수 있다.

    사용처: 함수 인자로, 반환타입으로. 메모리 주소 저장할 때.

    reference는 pointer를 가리킬 수도 있다.

    int *p;

    int *&r = p;

    *r = 10;

    cout << *p; // 10

    2.4 const Qualifier

    변하지 않을 변수를 정의하고 싶을 때 사용

    재할당시 error 발생하므로, 정의시점에 초기화까지 해줘야 한다.

    const 변수를 다른 변수에 할당하는건 아무런 특이사항이 없다.

     

    const 변수가 정의(정의는 곧 초기화)된 file에 대해 local성을 띈다.

    만약, 해당 const 변수를 사용하고 싶다면, 해당 파일 내에 initializer가 있어야 함.

    이러면 같은 const 변수를 여러개의 file에 동시에 정의해놓아야 함.

    → extern const

     

    extern이 붙으면 다른 파일에서 해당 변수를 접근할 수 있게 됨.

    extern이 const 변수에 붙고 해당 변수가 초기화되어있으면,

    이후, 해당 변수를 사용하고 싶은 파일에서 똑같이 extern const로 선언만 해주면, 컴파일러가 해당 변수의 초기자를 찾으려고 할 때, 초기자 탐색 범위를 파일 내로 한정하지 않고 해당 변수는 local 변수가 아니고 외부 어딘가에 있음을 명시함. extern 까지 확인함으로써 초기자를 찾을 수 있게됨.

    → 정의를 한번만 내려도 다른 파일에서 사용할 수 있게됨.

    extern은 선언(declaration)만 해주는 것임.

    선언 + 정의 + 초기화 까지 해줘야 변수를 사용 가능

    References to const

    const int ci = 1024;

    const int &r1 = ci; OK, not object

    r1 = 42; Error bcs r1 is const’s reference

    int &r2 = ci; Error bcs r2 is int’sreference

    근데,

    const int a = 10;

    int b = a; → 이건 됨. 값을 할당하는거라서.

    double dval = 3.14;
    1. const int &ri = dval; // 컴파일됨. ri = 3
    2. int &ri = dval; // 컴파일에러.
    
    1. int &ri = 42; // 컴파일에러.
    2. const int &ri = 42; // 컴파일됨.
    

    const 붙은 reference 변수는 초기자가 literal이든 다른 타입이든 다 컴파일됨

     

    int i = 42;
    int &r1 = i;
    const int &r2 = i;
    r1 = 0; // legal. 
    r2 = 10; // illegal because r2 is const.
    

    Pointer const

    const double pi = 3.14;
    // double *ptr = &pi; // ptr은 const object를 가리키지 않고 그냥 생짜 double을 가리켜서 컴파일 안됨.
    const double *cptr; // const 붙이면 됨.
    // *cptr = 33; // 당연히 const니까 재할당 불가.
    
    // double dval = 3.14;
    // cptr = &dval; // 근데 const포인터가 일반 포인터 할당받을 수 있음.
    // *cptr = 1111; // 당연히 const라서 컴파일 에러.
    

    const 변수를 가리키는 포인터는 변하지 않아야 하는게 가리키는 오브젝트다.

    const 변수처럼 const 포인터 변수도 정의될 때 초기화도 되어야 함. → const포인터 변수는 초기화 안해줘도 됨. 재할당 가능

    const 포인터에 일반변수주소를 할당할 수는 있는데 이유 → 어차피 변경하려고 시도할 때 const 포인터 변수 선에서 막히기 때문

    일반 포인터에 const 변수주소 컴파일 안됨 → 일반포인터라서 변경이 되기 때문에 const인데 변경되니까 막음.

     

    const Pointers(변하면 안되는 포인터)

    포인터가 가리키는 오브젝트(주소값)이 바뀌면 안될 때 사용

    int main()
    {
        int errNumb = 0;
        int *const p1 = &errNumb;
    
        const double pi = 3.14;
        const double *const cp1 = &pi;
    
        cout << *p1 << *cp1 << endl; 
    }
    

    Top-Level Const vs Low-Level Const

    top-level const: 나 자체가 const

    low-level const: 내가 가리키는 object가 const

    할당자가 top-level이면 컴파일 되는데 low-level이면 할당받는 변수도 low-level이어야 함.

    할당받는 변수가 const이면 허용 범위가 커짐. 일반변수면 const 할당 못받음(이건 참조인 경우에 해당. 바꾸려고 하므로)

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int i = 0;
        int *const p1 = &i; // top-level
        const int ci = 42; // top-level
        const int *p2 = &ci; // low-level
        const int *const p3 = p2; // top and low level
        const int &r = ci; // top-level
    
        i = ci; // ci is top-level so const in ci is ignored
        p2 = p3; // same
    
        // int *p = p3; // p3 has low-level so error
        p2 = p3; // p3 has low-level but p2 also has low-level so OK
        // p2 = &i; // converted int* to const int*
        // int &r = ci; // error. ci is const and r is ordinary
        // const int &r2 = i; // OK
        // p2 = &i;
        cout << *p2;
        
    }
    

    constexpr and Constant Expressions

    constexpr(const expression)

    • const 변수가 const한 expresison으로 초기화되는걸 보장하고 싶을 때 사용
    constexpr int mf = 20;// OK
    constexpr int limit = mf + 1;// OK
    constexpr int sz = size() -> // size()함수가 constexpr 함수일 때만 가능. define시 추가해줘야함.
    

    Pointers and constexpr

    constexpr int *p = &val; // expression을 const화 시키기 때문에 top-level const
    constexpr int *constp = &val; // object와 pointer object 둘다 const, top,low level const
    

    Type Aliases

    기존 타입을 좀 더 해석하기 쉬운 이름으로 바꾸는 것

    typedef double wages;
    typedef wages base, *p;
    
    p val = &val; // val은 double pointer 변수다.
    const p val = &val2; // const pointer 생성.
    

    const 붙으면 좀 어렵다.

    auto Type Specifier

    컴파일러가 initializer 결과를 보고 변수 타입을 추론하도록 하는 것.

    auto item = item1 + item2;
    auto i = 1, *p = &i; // OK 둘다 int
    auto sz = 1, df = 3.14 // error. 두 변수의 추론타입이 다름(int, float)
    auto &n = i, *p2 = &ci; // error. n:int, p2: const int
    
    const int ci = i, &cr = ci;
    auto b = ci; // ci: top-level -> auto=int
    auto c = cr; // cr = ci = top-level -> auto=int
    auto d = &i; // int*
    auto e = &ci; // const int * = low-level -> const int*
    
    명시적으로 const 넣어줄 수 있음.
    const auto f = ci; // const int
    

    헤더파일을 한번만 include 되도록 하는 법 → preprocessor 사용

    directives

    • #define: preprocessor variable
    • #ifdef: true if the variable has been defined
    • #ifndef: true if the variable has not been defined

    그래서, Sales.item.h 맨 위에

    #ifndef SALESITEM_H
    #define SALESITEM_H
    이렇게 있는데, 아직 해당 SALES_item.h 파일이 전처리기가 안 읽었으면 SALESITEM_H not define이므로
    #ifndef는 true라서 다음으로 넘어가고, 이후에 #define을 통해 SALESITEM_H가 define 된다.
    이후, 다른 파일에서 SALES_item.h가 include 되었을 때, #ifndef가 false가 되면서 전처리기가 해당 파일
    을 건너뛰게된다.
    true면 #endif 만날 때까지 코드 읽으면서 메모리에 로딩함.
    false면 #endif 사이 코드들 다 무시함.
    SALESITEM_H같은 변수를 header guard 라고 부름
    

    헤더 가드는 프로그램 내에서 유니크한 이름이어야 함. 따라서 클래스 이름을 대문자화_H 한 이름으로 주로함.

    헤더가드 설치하는걸 습관화해야함.

    'c++' 카테고리의 다른 글

    C++ Primer CH7. Classes  (0) 2024.01.21
    C++ Primer CH6. Functions  (0) 2024.01.21
    C++ Primer CH4. Expressions  (0) 2024.01.21
    C++ Primer CH3. Strings, Vectors and Arrays  (0) 2024.01.21
    C++ Primer CH1. Getting Started  (0) 2024.01.21
Designed by Tistory.