본문 바로가기
C++/문법 정리

Chapter 06. C++11/14/17

by GameStudy 2022. 5. 8.

6.1 새로운 키워드

  6.1-1 auto

    Def) auto 키워드

      자료형을 추론함.

      다만, C++은 컴파일 중에 자료형이 정해져야 하는

      stronged type이기 때문에, 실제 자료형은 컴파일 동안 추론됨.

      따라서 반드시 auto 변수는 초기화되어야 함.(참조랑 비슷)

auto x1;            // 컴파일 에러.
auto x2 = "Hello";  // 컴파일 성공. 다만, 좋은 예는 아님.
auto x3 = 3.141592; // 컴파일 성공.

 

    Note) Javascript와는 조금 다른 auto 키워드

// In JS.
var x;       // 컴파일 성공. x는 정의되지 않음.
x = "Hello"; // 컴파일 성공. x는 문자열
x = 100;     // 컴파일 성공. x는 정수

// In C++
auto x;          // 컴파일 에러. auto 변수가 컴파일 중에 추론 후 결정되게끔, 초기화는 필수.
auto x = "Hello" // 컴파일 성공. x는 문자열
x = 100;         // 컴파일 에러. x는 이미 컴파일 타임에 문자열로 결정되버림.

 

    Note) auto 변수로 포인터와 참조도 받을 수 있음.

      포인터: auto 또는 auto*

      참조:    auto&

Cat* myCat = new Cat(2, "Hello");
auto  pMyCat1 = myCat;
auto* pMyCat2 = myCat;  // pMyCat과 myCat은 동일한 포인터.

 

    Note) 왜 포인터를 받을 땐 auto와 auto* 둘 다 될까?

      컴파일 타임에 어느 자료형인지 알아 낼 수 있기 때문.

char ch = 'a';
char* p = &ch;

// ...

auto x = p; // auto x = &ch;

 

    Note) 그래도 꼭 auto*를 쓰자.

      ex. auto name = object.GetName();

      위의 경우, 포인터인지 string 클래스 개체인지 모름.

      가독성에 좋지 않아서 올바르지 않는 사용예.

      포인터를 받을 때는 가독성을 위해 auto*를 쓰자.

 

    Note) 왜 참조를 받을 땐 auto가 안될까?

      조금만 생각해보면 당연한거임. 

      참조를 auto로 받을 수 있어지면, 값복사와 전혀 분간 못함.

      그러니 꼭 auto&로 참조를 받자.

Cat myCat = Cat(2, "Hello");
Cat& refMyCat1 = myCat;
auto refMyCat2 = refMyCat // myCat에 대한 참조일까?
auto x = b;               // 참조일까 값복사일까?

 

    Note) auto 변수는 const를 이어 받음.

const int a = 100;
auto& b = a;       // a의 const attribute를 이어 받음.

 

    Note) 왜 const를 받을 땐 auto&와 const auto& 둘 다 될까?

      다시 말하지만 컴파일러가 추론해 낼 수 있기 때문.

      그러나, auto로 쓴다면 해당 변수가 const attribute를

      가졌는지 전혀 알 수 없음.

      따라서 const auto&로 적는 것이 올바른 방법.

 

    Note) 함수의 반환 값을 저장할 때 auto가 유용함.

      함수 반환 자료형이 변해도 auto는 그대로 추론 가능.

      허나 함수 반환 자료형 변경이 자주 발생할까?

 

    Ex06010101)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

template <typename T>
T add(T a, T b)
{
    return a + b;
}

int main()
{
    auto res1 = add(3.14f, 0.001592f);
    cout << res1 << endl;

    auto res2 = add(3.14, 0.001592);
    cout << res2 << endl;

    return 0;
}

 

    Note) auto 키워드가 매우 유용한 경우

      1. 반복자 추론

        ex. for (auto it = v.begin(); it != v.end(); ++it)

          for(std::map<std::string, std::string>::const_iterator it = ... 포기)

      2. 템플릿 매개변수가 두 개 이상인 경우 혹은 복잡한 경우

        ex. MyVector<int, ...>* vector = new MyVector<int, ...>();

          auto* copiedVector = new MyVectorM<int, ...>();

      3. 한 두줄 안에 무슨 자료형인지 알 수 있는 경우

 

    Note) 어찌 되었든 auto는 명시적이어야 함.

      auto 보다는 실제 자료형 사용을 권장.

      다만 위의 세 경우에는 예외적.

      그리고 반드시 아래와 같이 작성.

      auto* / auto& / const auto&

 

    Ex06010102)

<hide/>

// MyVector.h

#pragma once

template <typename T>
class MyVector
{
public:
    MyVector(T x, T y)
        : mX(x)
        , mY(y)
    {}

    T GetX() const { return mX; }
    T GetY() const { return mY; }

private:
    T mX;
    T mY;
};
<hide/>

// main.cpp

#include <iostream>
#include <vector>   // 추후에 C++ STL에서 배움.

#include "MyVector.h"

using namespace std;

int main()
{
    int* numPtr = new int(5);
    auto autoNumPtr = numPtr; // Bad Practice!!
    cout << "autoNumPtr: " << *autoNumPtr << endl;

    auto* autoNumPtr2 = numPtr;
    cout << "autoNumPtr2: " << *autoNumPtr2 << endl;

    autoNumPtr = nullptr;
    autoNumPtr2 = nullptr;
    delete numPtr;
    char character = 'a';
    char& characterRef = character;
    auto characterAutoCopy = characterRef; // Bad Practice!!
    character = 'b';
    cout << "characterAutoCopy: " << characterAutoCopy << endl;

    char anotherCharacter = 'c';
    char& anotherCharacterRef = anotherCharacter;
    auto& anotherCharacterAutoRef = anotherCharacterRef;
    anotherCharacter = 'd';
    cout << "anotherCharacterAutoRef: " << anotherCharacterAutoRef << endl;

    const float someFloat = 1.0f;
    auto& someFloatRef = someFloat; // Bad Practice!!
    // Compile Error
    // someFloatRef = 2.0f;
    const auto& betterSomeFloatRef = someFloat;
    cout << "betterSomeFloatRef: " << betterSomeFloatRef << endl;

    vector<int> intVector;
    intVector.reserve(3);
    intVector.push_back(1);
    intVector.push_back(2);
    intVector.push_back(3);
    for (auto it = intVector.begin(); it != intVector.end(); ++it)
    {
        cout << "Number in intVector: " << *it << endl;
    }

    auto* myVector = new MyVector<int>(10, 20);
    cout << "mX: " << myVector->GetX() << ", mY: " << myVector->GetY() << endl;
    delete myVector;

    return 0;
}

 

  6.1-2 static_assert

    Note) assert() 함수

      실행 중에 가정(assertion)이 맞는 지 평가하는 함수.

      오직 디버그 빌드에서만 작동함.

      릴리즈 빌드에서는 사라짐. 그래서 좋음.

 

    Note) 실패한 assert()를 보려면 반드시 실행해야만 함.

      근데, 모든 코드 경로가 실행되었다고 어떻게 장담하지?

      A 함수에 assert()를 호출해둠. 근데 A 함수 자체가 호출안되면?

      프로그래머는 '아, 잘 실행되는구나' 하고 넘어갈 수도 있음.

      또 어떤 assertion은 실행 전에도 평가 할 수 있음.

 

    Note) assert()를 모든 곳에 쓰자.

      앞에서 단점을 조금 이야기 했지만, 여전히 유효한 조언.

 

    Def) static_assert()

      컴파일 중에 assertion을 평가함.

      다만, assert 조건이 컴파일러가 판별할 수 있어야 함.

      판별 할 수 없거나, 거짓인 경우엔 컴파일 에러가 남.

      C++11에서 가장 유용한 기능 중 하나.

static_assert(74 == sizeof(Student), "Student structure size mismatch.");
static_assert(1 < Class::Version, "You need higher version than 1.0");
static_assert(MAX_SCORES == sizeof(mScores) / sizeof(mScores[0]));

 

    Note) static_assert() 베스트 프렉티스

      모든 곳에 assert()를 쓰되, 할 수 있다면 static_assert() 쓰자.

      즉, 컴파일 중에 잡을 수 있는건 컴파일 중에 잡자.

      디버그 빌드까지 기다리지 말자.

      이는 컴파일러처럼 생각하는데에도 도움이 됨.

 

    Ex06010201)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

struct StudentInfo1
{
    unsigned short age;
    float weight;
    float height;
    unsigned short id;
};

#pragma 
struct StudentInfo2
{
    float weight;
    float height;
    unsigned short age;
    unsigned short id;
};

int main()
{
    StudentInfo1 s1;
    StudentInfo2 s2;

    cout << sizeof(s1) << endl;
    cout << sizeof(s2) << endl;

    static_assert(sizeof(s1) <= 12, "Struct StudentInfo size must be smaller than 12.");
    static_assert(sizeof(s2) <= 12, "Struct StudentInfo size must be smaller than 12.");

    return 0;
}

 

  6.1-3 default / delete

    Note) 기본 생성자 / 복사 생성자 / 소멸자의 문제점

      1. 위 메서드들을 구현하지 않으면

        컴파일러가 기본 형태를 만듦.

        기본 생성자는 개체를 생성해야 하므로 그럴 수 있음.

        암시적 복사 생성자의 경우에는 shallow copy 문제가 있음.

      2. 근데 기본 생성자도 기분 나쁨.

        생략하자니 찝찝하고, 추가하자니 아무 의미없는 빈 메서드.

        

    Def) default 키워드

      컴파일러에게 특정 기본 생성자/복사 생성자/ 소멸자를 

      만들어 내라고 지정할 수 있음. 즉, 명시적인 효과.

      그래서 굳이 비어 있는 생성자나 복사 생성자, 소멸자를

      구체화할 필요가 없음. 명확하게 표현하는 것은 언제나 옳음.

 

    Ex06010301)

<hide/>

// MyVector.h

#pragma once

template <typename T>
class MyVector
{
public:
    MyVector() = default; // 이러면 알아서 cpp 파일에 구현됨.

    MyVector(T x, T y)
        : mX(x)
        , mY(y)
    {}

    T GetX() const { return mX; }
    T GetY() const { return mY; }

private:
    T mX;
    T mY;
};
<hide/>

// main.cpp

#include <iostream>
#include "MyVector.h"

using namespace std;

int main()
{
    auto* myVector = new MyVector<int>();
    cout << "mX: " << myVector->GetX() << ", mY: " << myVector->GetY() << endl;
    delete myVector;

    return 0;
}

 

    Def) delete 키워드

      컴파일러가 자동으로 기본생성자/복사생성자/소멸자를

      만드는게 원치 않다면 delete 키워드를 사용하면 됨.

 

    Note) private 접근 제어자로 생성자 제거? 트릭 안써도됨.

      올바른 에러 메세지도 나옴.

 

    Ex06010302)

<hide/>

// MyVector.h

#pragma once

template <typename T>
class MyVector
{
public:
    MyVector() = delete;

    MyVector(T x, T y)
        : mX(x)
        , mY(y)
    {}

    T GetX() const { return mX; }
    T GetY() const { return mY; }

private:
    T mX;
    T mY;
};
<hide/>

// main.cpp

#include <iostream>
#include "MyVector.h"

using namespace std;

int main()
{
    auto* myVector = new MyVector<int>();
    cout << "mX: " << myVector->GetX() << ", mY: " << myVector->GetY() << endl;
    delete myVector;

    return 0;
}

 

    Note) default / delete 키워드는 무조건 사용하는 것이 좋음.

 

  6.1-4 final / override

    Note) 상속은 아무 클래스나 받을 수 있음.

      그러니 모든 클래스의 가상 소멸자는 필수가 되버림.

      근데, virtual 키워드는 런타임 속도를 저하시킴.

 

    Def) final 키워드

      클래스나 가상 함수를 자식 클래스에서 

      오버라이딩 못하도록 막는 키워드.

      컴파일 중에 확인하기에 엄청 유용함.

      기본값으로 생각할 정도로 자주 사용됨.

 

    Ex06010401) 

<hide/>

// Animal.h

#pragma once

class Animal
{
public:
    Animal() = default;
    Animal(int age)
        : mAge(age)
    {}

    Animal(const Animal& other) = delete;

    virtual ~Animal() {}

    virtual int GetAge() const { return mAge; }

private:
    int mAge;

};
<hide/>

// Dog.h

#pragma once

#include "Animal.h"

class Dog final : public Animal
{
public:
    Dog() = default;
    Dog(const int& age)
        : Animal(age)
    {}

    Dog(const Dog& other) = delete;

    virtual ~Dog() {}

};
<hide/>

// Corgi.h

#pragma once

#include "Dog.h"

class Corgi : public Dog
{
public:
    Corgi() = default;

    Corgi(const Corgi& other) = delete;

    virtual ~Corgi() {};

};

 

    Note) 클래스의 가상 함수를 자식 클래스에서

      오버라이딩 못하도록 하기위해서도 final 키워드를 사용함.

      당연하지만, 가상 함수가 아니면 final이 별의미 없음. 

      재정의가 일어나지 않기 때문.

 

    Ex06010402)

<hide/>

// Animal.h

#pragma once

class Animal
{
public:
    Animal() = default;
    Animal(int age)
        : mAge(age)
    {}

    Animal(const Animal& other) = delete;

    virtual ~Animal() {}

    virtual int GetAge() const { return mAge; }

private:
    int mAge;

};
<hide/>

// Dog.h

#pragma once

#include "Animal.h"

class Dog : public Animal
{
public:
    Dog() = default;
    Dog(const int& age)
        : Animal(age)
    {}

    Dog(const Dog& other) = delete;

    virtual ~Dog() {}

    virtual int GetAge() final { return GetAge(); } // 내가 Animal 클래스의 GetAge() 재정의 했으니까, 내 밑으론 재정의 하지마!

};
<hide/>

// Corgi.h

#pragma once

#include "Dog.h"

class Corgi : public Dog
{
public:
    Corgi() = default;

    Corgi(const Corgi& other) = delete;

    virtual ~Corgi() {};

    virtual int GetAge() { return GetAge(); }

};

 

    Def) override 키워드

      잘못된 가상 함수 오버라이딩을 막기위한 키워드.

      당연히 가상 함수가 아니라면 쓸 수 없음.

      이것도 컴파일 도중에 검사함.

 

    Ex06010403)

<hide/>

// Animal.h

#pragma once

class Animal
{
public:
    Animal() = default;
    Animal(int age)
        : mAge(age)
    {}

    Animal(const Animal& other) = delete;

    virtual ~Animal() {}

    virtual int GetAge() const { return mAge; }
    virtual void SetAge(int age) { mAge = age; }

protected:
    int mAge;

};
<hide/>

// Dog.h

#pragma once

#include "Animal.h"

class Dog : public Animal
{
public:
    Dog() = default;
    Dog(const int& age)
        : Animal(age)
    {}

    Dog(const Dog& other) = delete;

    virtual ~Dog() {}

    virtual int GetAge() final { return mAge; } 
    virtual void SetAge(float age) { mAge = (int)age; } 
    
};
<hide/>

// main.cpp

#include <iostream>

using namespace std;

int main()
{
    
    return 0;
}

 

    Note) Ex06010403는 빌드는 잘되지만 문제가 있음. 뭘까?

      Dog::SetAge(float age) 메서드가 잘못됨. 왜 잘못되었을까?

      Animal::SetAge(int age)와 시그니처가 달라서, 오버라이드 되지않음.

      근데 왜 컴파일 되었을까? 컴파일러 입장에선, 그냥 새로운 Dog의 메서드.

 

    Note) override 메서드는 부모 클래스의 메서드를 재정의 하겠다는 명백한 의도.

...

class Dog : public Animal
{
public:
    ...

    virtual int GetAge() final { return mAge; } 
    virtual void SetAge(float age) override { mAge = (int)age; } // 컴파일 에러.

};

 

    Ex06010404) 아래 결과를 예측해 보자.

<hide/>

// Animal.h

#pragma once

class Animal
{
public:
    Animal() = default;
    Animal(int age)
        : mAge(age)
    {}

    Animal(const Animal& other) = delete;

    virtual ~Animal() {}

    int GetAge() const { return mAge; }
    virtual void SetInfo(int age) { mAge = age; }

protected:
    int mAge;

};
<hide/>

// Dog.h

#pragma once

#include "Animal.h"

class Dog : public Animal
{
public:
    Dog() = default;
    Dog(const int& age)
        : Animal(age)
    {}

    Dog(const Dog& other) = delete;

    virtual ~Dog() {}

    virtual int GetAge() override { return mAge; } // 컴파일 가능할까?

    void SetInfo(int age) override                 // 컴파일 가능할까?
    { 
        mAge = age;
        mID = mAge * 10;
    }

    void SetInfo(int age, int ID) override         // 컴파일 가능할까?
    { 
        mAge = age;
        mID = ID; 
    }

protected:
    int mID;

};

 

  6.1-5 offsetof

    Def) offsetof 매크로

      특정 멤버가 본인을 포함하여 

      자료구조의 시작점에서부터 몇 바이트만큼 

      떨어져 있는지 알려줌. 즉 오프셋(상대적 위치)을 반환함.

      #define offsetof(type, member) 

      /* implementation-defined */

      직렬화(serialize) / 역직렬화(deserialize)할 때 꽤나 유용함.

      즉, 파일에 데이터를 저장 혹은 읽기 할 때 유용함.

      키워드가 아님.

 

    Def) Serialize and Deserialize

      데이터를 파일에 쓰는 것을 Serialize

      파일에 쓰여 있는 것을 데이터로 읽어오는 것을 Deserialize

 

    Note) 쓸데 없는거 같은데?

      뇌내 망상으로는, 충분히 쉬워보임. 4바이트 정렬기준에 따라

      각 멤버 간의 상대적 위치는 쉽게 구할 수 있을 것 같음.

      그러나 컴파일러 별로 꼭 이렇다할 보장이 없음.

      또 32bit/64bit 플랫폼에 따라 다름. 

 

    Note) offsetof는 어떻게 그리 쉽게 구하지?

      컴파일 시에 메모리 구조를 결정하기 때문에 잘 알수밖에 없음.

 

    Ex06010501)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

struct StudentInfo {
    const char* ID;
    const char* name;
    int currentSemester;
};

int main(void)
{
    cout << "ID offset: " << offsetof(StudentInfo, ID) << endl;
    cout << "Name offset: " << offsetof(StudentInfo, name) << endl;
    cout << "Current semester offset: " << offsetof(StudentInfo, currentSemester) << endl;

    return 0;
}

 

    Note) 파일에 데이터를 읽고 쓸때, offsetof와 static_assert는 찰떡궁합.

      읽기 전에 static_assert()로 전처리 조건 걸고,

      offsetof()로 해당 데이터를 읽어냄.

      그리고 문제가 생기면 packing 하면 됨.

 

6.2 새로운 자료형

  6.2-1 nullptr

    Note) NULL 키워드를 쓰면 굉장히 이상한 일이 벌어짐.

      아래 예제에서 어떤 GetScore() 메서드가 호출될지 예측 해보자.

 

    Ex06020101)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

class Student
{
public:
    Student() = default;
    Student(int score)
        : mScore(score)
    {}
    
    Student(const Student& other) = delete;

    virtual ~Student() {};

    int GetScore(const char* name) const
    {
        return mScore; 
    }

    int GetScore(int studentNumber) const
    {
        return mScore; 
    }

private:
    int mScore;
    
};

int main(void)
{
    Student st1 = Student(99);

    cout << st1.GetScore("Fake!") << endl;
    cout << st1.GetScore(NULL) << endl;     // 중단점 걸고 F11 
    cout << st1.GetScore(20130659) << endl;

    return 0;
}

 

    Note) NULL Vs. nullptr

      NULL은 정수 0임. 즉, 함수 호출 인자로 넘기면 int형

      #define NULL (0)

      nullptr은 포인터 상수 0임. 

int num1 = NULL;      // 컴파일 성공.
int* pNum1 = NULL;    // 컴파일 성공.

int num2 = nullptr;   // 컴파일 실패.
int* pNum2 = nullptr; // 컴파일 성공.

 

    Note) 그래도 NULL 쓰면 코드가 컴파일 되긴 함.

      후방 호환성을 지키기 위해서임.

      그러나, 이제부터는 nullptr을 쓰자.

      그래야 프로그래머의 의도를 명확하게 밝힐수 있음.

 

  6.2-2 고정폭 정수형

    Note) 고정폭 정수형

      int8_t / uint8_t

      int16_t / uint16_t

      int32_t / uint32_t

      int64_t / uint64_t

      intptr_t / uintptr_t ...

      낡은 기존 자료형보다 고정폭 정수형을 사용함.

uint8_t score = st1->GetScore();

 

  6.2-3 enum class

    Note) 기존 C 스타일 enum 클래스는

      enum 멤버를 int형 변수에 대입하는 것도,

      다른 enum 멤버와의 비교도 가능했었음.

      즉, 엉망진창이 될 수 있음.

 

    Ex06020301)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

enum ePosition {
    TOP,
    JUNGLE,
    MID,
    SUPPORT,
    BOTTOM
};

enum eChamp {
    LEESIN,
    LULU,
    ZED,
    BLITZ,
    MISFORTUNE
};

int main()
{
    ePosition myPosition = SUPPORT;
    eChamp myChamp = BLITZ;
    int num = myPosition;
    if (myPosition == ZED) {
        /* ... */
    }

    return 0;
}

 

    Def) enum class

      C++11에서 제대로 지원함.

      독자적인 자료형으로 지원됨.

      정수형으로의 암시적 캐스팅 없음.

      명시적 캐스팅은 가능함.

      자료형 검사(Type-checking)를 진행함.

      또한, enum class에 할당할 바이트 크기도 정할 수 있음.

      원래는 '4바이트 겠지'라고 추측 했지만,

      파일에 저장 할 때 몇 바이트인지 아는게 중요하기 때문.

#include <cstdint> // 자료형때문.
enum class ePassInfo : uint8_t
{
    Assignment1,
    Assignment2,
    Assignment3,
    Assignment4 = 0x100, // 9번째 비트 초기화 불가. 컴파일 경고.
};

 

    Ex06020302)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

enum class ePosition {
    TOP,
    JUNGLE,
    MID,
    SUPPORT,
    BOTTOM
};

enum class eChamp {
    LEESIN,
    LULU,
    ZED,
    BLITZ,
    MISFORTUNE
};

int main()
{
    ePosition myPosition = ePosition::SUPPORT;
    eChamp myChamp = eChamp::BLITZ;
    int num = static_cast<int>(myPosition);
    if (myPosition == static_cast<ePosition>(eChamp::ZED)) {
        /* ... */
    }

    return 0;
}

 

    Note) 조금 불편해진 점도 있음.

      그래서 정적으로 배열 초기화할 땐 예전 enum을 예외적으로 씀.

// 예전 enum 
for (int i = ePosition::Top; i <= ePosition::Bottom; ++i) { /* ready check. */ }
  // 컴파일 성공
  
// enum class
for (int i = ePosition::Top; i <= ePosition::Bottom; ++i) { /* ready check. */ }
  // 컴파일 실패
for (int i = static_cast<int>(ePosition::Top); i <= static_cast<int>(ePosition::Bottom); ++i) { /* ready check. */ }
  // 컴파일 성공

 

  6.2-4 헤더 초기화

    Note) 헤더 초기화 사례

// MyVector.h
class MyVector
{
private:
    int mX = 0;                      // 헤더 파일 초기화. 
    int mY = 0;
    const static int mDimension = 2; // 이전 표준에선 const static만 헤더 파일 초기화 가능했음.
    static int mCount = 0;           // 컴파일 에러. 나중에 고칠지도.

};

// MyVector.cpp
int MyVector::mCount = 0; // const static이 아니기 때문에 cpp 파일에서 초기화해야 함.

 

    Note) 헤더 초기화 써도 될까?

      1. 선별적으로 써야 함.

        헤더 파일에 들어가는 것이기 때문에

        커다란 코드베이스에선 안쓰는게 맞음.

        1개만 헤더 초기화로 바꿔도

        컴파일 시간이 몇 시간으로 걸릴지도.

        header 파일 안건들면, cpp 파일은 아무리 건들어도

        컴파일 시간은 달라지지 않음.

      2. 모듈 시스템(C++20)이 도입된다면 다를지도.

        Java, C#같은 언어는 나름 최신언어기에 

        모듈 시스템이 내장되어 있음.

        C, C++은 고대 언어라서 순수하게 복붙해버림.

      결론적으로, 아직은 cpp 파일에서 초기화 하는게 맞음.

 

 

6.3 범위 기반 for

  6.3-1 범위 기반(Range-based) for 반복문

    Note) for 반복문을 쓰는 대부분의 경우가

      처음부터 끝까지 어떤 범위를 돌면서 쓰는 경우가

      대부분이었음. 그래서 좀 더 간단하게 쓰고자 나온 반복문.

      다만 역순회는 안됨.

 

    Note) 범위 기반 for 반복문의 종류

      1. for (range_declaration : range_expression) { ... }

      2. for_each()

 

    Note) 사용하기도 편하고, 가독성에서도 좋은 for(:) {}를 쓰자.

 

    Ex06030101)

<hide/>

// main.cpp

#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;

int main()
{
    int scores[3] = { 100, 88, 99 };

    for (int i = 0; i < 3; ++i) {
        cout << scores[i] << ' ' << endl;
    }

    for (int score : scores) {
        cout << score << ' ' << endl;
    }

    for (auto& score : scores) {
        cout << score << ' ' << endl;
    }

    unordered_map<string, int> scoreMap;
    scoreMap["Parkthy"] = 100;
    scoreMap["Bangthy"] = 88;
    scoreMap["Kimthy"] = 99;

    for (auto it = scoreMap.begin(); it != scoreMap.end(); ++it) {
        cout << it->first << ':' << it->second << endl;
    }

    for (auto& score : scoreMap) {
        cout << score.first << ':' << score.second << endl;
    }

    return 0;
}

 

    Note) 주의 할 점

      참조로 범위 순회 for문을 쓸 때는 꼭 const를 생활화 하자.

for (int score : scores) // score에 값복사가 이뤄짐
{
	score = 10;          // 원본에 영향없음.
} 

for (int& score : scores) // score에 참조로 저장됨.
{
    score = 10;          // 원본도 바뀜.
}

 

    Ex06030102)

<hide/>

// main.cpp

#include <iostream>
#include <string>
#include <map>

using namespace std;

int main()
{
    map<string, int> scoreMap;
    scoreMap["Parkthy"] = 100;
    scoreMap["Bangthy"] = 88;
    scoreMap["Kimthy"] = 99;

    for (auto score : scoreMap) {
        score.second -= 5;    // 값 복사이기 때문에 원본이 바뀌지 않음.
        cout << score.first << ':' << score.second << ' ';
    }
    cout << endl;

    for (auto& score : scoreMap) {
        cout << score.first << ':' << score.second << ' ';
    }
    cout << endl;

    for (auto& score : scoreMap) {
        score.second -= 5;    // 참조이기 때문에 원본이 바뀜. 
        cout << score.first << ':' << score.second << ' ';
    }
    cout << endl;

    for (const auto& score : scoreMap) { // 만약 정말 원본을 바꿀일 없다면 const 참조를 생활화하자. 
        cout << score.first << ':' << score.second << ' ';
    }
    cout << endl;

    return 0;
}

 

    Ex06030103)

<hide/>

// main.cpp

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> nums;
    nums.reserve(5);
    nums.push_back(1);
    nums.push_back(2);
    nums.push_back(3);
    nums.push_back(4);
    nums.push_back(5);

    for (int n : nums) { ++n; }
    cout << "Print nums:" << endl;
    for (auto it = nums.begin(); it != nums.end(); ++it) { cout << *it << endl; }

    for (int& n : nums) { --n; }
    cout << "Print nums again:" << endl;
    for (auto it = nums.begin(); it != nums.end(); ++it) { cout << *it << endl; }

    return 0;
}

 

    Ex06030104)

<hide/>

// main.cpp

#include <iostream>
#include <string>
#include <unordered_map>
#include <algorithm> // for_each()

using namespace std;

int main(void)
{
    unordered_map<string, int> scores;
    scores["Parkthy"] = 100;
    scores["Bangthy"] = 88;
    scores["Kimthy"] = 99;

    // lambda function. 뒤에 배우는 내용.
    auto printElement = [](unordered_map<string, int>::value_type& item)
    {
        cout << item.first << ": " << item.second << endl;
    };

    for_each(scores.begin(), scores.end(), printElement);

    return 0;
}

'C++ > 문법 정리' 카테고리의 다른 글

Chapter 08. 이동 문법  (0) 2022.05.09
Chapter 07. 스마트 포인터  (0) 2022.05.08
Chapter 05. 템플릿과 파일 시스템  (0) 2022.05.07
Chapter 04. 캐스팅과 인라인  (0) 2022.05.06
Chapter 03. 상속과 다형성  (0) 2022.05.05

댓글