2018년 5월 7일 월요일

모던 C++ 입문, 1장-2장

모던 C++ 입문 엔지니어, 프로그래머를 위한 C++11/14 입문
피터 고츠슐링 저/옥찬호 역 | 길벗

http://www.yes24.com/24/goods/57615943


C++을 안쓴지 오래되서 그런지 C++11, C++14 문법들이 너무 생소하다. 옛날 생각하고 Effective Modern C++을 읽기 시작했는데 너무 어려워서 기본 문법책을 고르는 중 이책을 보게 됨.
설명이 친절하진 않지만 C++11, C++14 내용을 한글로 설명하고 있어 빠르게 읽고 다시 Effective Modern C++을 읽어야 할 것 같음.

책을 보다가 기억해야 할 것들을 정리함.
더 자세한 내용은 책에 있으니 책을 읽어 보는것이 가장 좋겠음.


1장 C++ 기초

- C++14는 2진수를 0b, 0B 접두사로 표현함.
int b1 = 0b11111010;

가독성을 위해 ' 도 사용함.

long d = 61'234'567'890;
int x = 1010'0011'0000;

- 데이터가 손실되지 않는 uniform initialization, braced initalization을 사용함.
long l = {1231234567890};
int pi = 3.14;  // 데이터 손실, 컴파일 가능
int pi = {3.14}; // 에러


* 값의 종류
Lvalue, Rvalue
Lvalue = 역사적으로, 대입 표현식의 왼쪽에 나타날 수 있기 때문에 붙여진 이름으로 함수나 개체를 말함.
Rvalue = 역사적으로, 대입 표현식의 오른쪽에 나타날 수 있기 때문에 붙여진 이름으로 만료된 값, 임시 개첸 그 하위 개체, 개ㅔ와 관련이 없는 값을 말함.

좀 말이 어려운데 아래 MSDN예시를 참조하면 더 이해하기 쉬움

https://msdn.microsoft.com/ko-kr/library/f90831hc.aspx

모든 C++ 식은 lvalue 또는 rvalue입니다. 
lvalue는 단일 식을 넘어 지속되는 개체를 참조합니다. lvalue를 이름이 있는 개체로 생각할 수 있습니다. 수정할 수 없는(const) 변수를 비롯한 모든 변수가 lvalue입니다. 
rvalue는 rvalue를 사용하는 식 외에서는 유지되지 않는 임시 값입니다. 

// lvalues_and_rvalues1.cpp  
// compile with: /EHsc  
#include <iostream>  
using namespace std;  
int main()  
{  
   int x = 3 + 4;  
   cout << x << endl;  
}  

이 예제에서 x는 이를 정의하는 식 외에도 지속되기 때문에 lvalue입니다. 3 + 4 식은 이 식을 정의하는 식 외에서는 유지되지 않는 임시 값으로 계산되기 때문에 rvalue입니다.

아래 링크도 참고.
http://jeremyko.blogspot.kr/2012/08/lvalue-rvalue.html


- 곱셈과 나눗셈은 덧셈과 뺄셈보다 먼저 계산하고, 연산은 왼쪽에서 오른쪽 순서로 결합한다. ... 여러분이 정말로 중요하게 기억해야 할 것은 인수들의 계산 순서는 정의되지 않았다는 점이다. 예를 들어

int i = 3, j = 7, k;
k = f(++i) + g(++i) +j;

이 예제는 첫번째 덧셈이 두번째 덧셈보다 먼저 수행됨을 보장한다.
그러나 f(++i)와 g(++i)중 어느 표현식이 먼저 계산될 것인지는 컴파일러 구현에 따라 달라진다.


- 범위 기반 for 문

int odd_nums[] = {1,3,5,7,9,11};
for( int i : odd_nums)
   std::cout << i << " ";


- 암수 오버로드는 signature가 서로 달라야 한다.
 : 함수 이름
 : 항(Arity)이라고 하는 인자의 개수
 : (해당 순서대로) 인자의 타입


- 배열의 바이트 크기를 단일 항목의 바이트 크기로 나누면 배열의 크기를 알 수 있다.
 : sizeof x / sizeof x[0]


- 포인터 초기화
 : int* num1 = nullptr;
 : int* num2{};
 : int* bad1 = 0;
 : int* bad2 = NULL:


- unique_ptr, shared_ptr, weak_ptr
https://msdn.microsoft.com/ko-kr/library/hh279674.aspx
  • unique_ptr
    기본 포인터로 한 명의 소유자만 허용합니다. shared_ptr이 필요하다는 점을 확실히 알 경우 POCO의 기본 선택으로 사용합니다. 새 소유자로 이동할 수 있지만 복사하거나 공유할 수 없습니다. 사용하지 않는 auto_ptr을 대체합니다. boost::scoped_ptr과 비교합니다. unique_ptr은 작고 효율적이며, 크기는 1 포인터이고 STL 컬렉션에서 빠른 삽입 및 검색을 위해 rvalue 참조를 지원합니다. 헤더 파일: <memory>. 자세한 내용은 방법: unique_ptr 인스턴스 만들기 및 사용 및 unique_ptr 클래스를 참조하십시오.
  • shared_ptr
    참조 횟수가 계산되는 스마트 포인터입니다. 원시 포인터 하나를 여러 소유자에게 할당하려고 할 경우 사용합니다(예: 컨테이너에서 포인터 복사본을 반환할 때 원본을 유지하고 싶을 경우). 원시 포인터는 모든 shared_ptr 소유자가 범위를 벗어나거나 소유권을 포기할 때까지 삭제되지 않습니다. 크기는 2개의 포인터입니다. 하나는 개체용이고, 다른 하나는 참조 횟수가 포함된 공유 제어 블록용입니다. 헤더 파일: <memory>. 자세한 내용은 방법: shared_ptr 인스턴스 만들기 및 사용 및 shared_ptr 클래스를 참조하십시오.
  • weak_ptr
    shared_ptr과 함께 사용할 수 있는 특별한 경우의 스마트 포인터입니다. weak_ptr은 하나 이상의 shared_ptr 인스턴스가 소유하는 개체에 대한 액세스를 제공하지만, 참조 수 계산에 참가하지 않습니다. 개체를 관찰하는 동시에 해당 개체를 활성 상태로 유지하지 않으려는 경우 사용합니다. shared_ptr 인스턴스 사이의 순환 참조를 차단하기 위해 필요한 경우도 있습니다. 헤더 파일: <memory>. 자세한 내용은 방법: weak_ptr 인스턴스 만들기 및 사용 및 weak_ptr 클래스를 참조하십시오.


2장 클래스

위임 생성자

: https://msdn.microsoft.com/ko-kr/library/dn387583.aspx

class class_c {  
public:  
    int max;  
    int min;  
    int middle;  
  
    class_c(int my_max) {   
        max = my_max > 0 ? my_max : 10;   
    }  
    class_c(int my_max, int my_min) : class_c(my_max) {   
        min = my_min > 0 && my_min < max ? my_min : 1;  
    }  
    class_c(int my_max, int my_min, int my_middle) : class_c (my_max, my_min){  
        middle = my_middle < max && my_middle > min ? my_middle : 5;  
}  
};  
int main() {  
  
    class_c c1{ 1, 3, 2 };  
}  
  


위 예제를 단계별로 수행했다면, class_c(int, int, int) 생성자가 class_c(int)을 다시 호출하는 class_c(int, int) 생성자를 호출하는 것을 주목합니다. 각 생성자는 다른 생성자에서 수행되지 않는 작업만을 수행합니다.
호출하는 첫번째 생성자는 해당 지점에서 해당 멤버 모두가 초기화되도록 개체를 초기화합니다. 다음과 같이 다른 생성자에 위임하는 생성자의 멤버를 초기화할 수 없습니다.
class class_a {  
public:  
    class_a() {}  
    // member initialization here, no delegate  
    class_a(string str) : m_string{ str } {}  
  
    //can’t do member initialization here  
    // error C3511: a call to a delegating constructor shall be the only member-initializer  
    class_a(string str, double dbl) : class_a(str) , m_double{ dbl } {}  
  
    // only member assignment  
    class_a(string str, double dbl) : class_a(str) { m_double = dbl; }  
    double m_double{ 1.0 };  
    string m_string;  
};  
  

멤버의 기본 값

class에서 멤버 변수의 기본값을 설정할 수 있음.

class complex
{
public:
   complex(double r, double i) : r{r}, i{i} { }
   complex(double r) : r{r} { }
   complex() { }

private:
   double r = 0.0, i = 0.0
}



유니폼 초기화, 중괄호 초기화
https://msdn.microsoft.com/ko-kr/library/dn387583.aspx

#include <string>  
using namespace std;  
  
class class_a {  
public:  
    class_a() {}  
    class_a(string str) : m_string{ str } {}  
    class_a(string str, double dbl) : m_string{ str }, m_double{ dbl } {}  
double m_double;  
string m_string;  
};  
  
int main()  
{  
    class_a c1{};  
    class_a c1_1;  
  
    class_a c2{ "ww" };  
    class_a c2_1("xx");  
  
    // order of parameters is the same as the constructor  
    class_a c3{ "yy", 4.4 };  
    class_a c3_1("zz", 5.5);  
}  
  

클래스에 기본이 아닌 생성자를 가질 경우, 중괄호 이니셜라이저에 클래스 멤버가 나타나는 순서는 멤버는 선언된 순서가 아니라 ( class_a 이전 예제에서와 마찬가지로 ) 생성자에서 해당 매개 변수가 나타나는 순서입니다. 그렇지 않고 해당 형식이 선언 된 생성자를 가지지 않을 경우, 중괄호 이니셜라이저에서 멤버가 나타나는 순서는 선언되어 있는 순서와 동일합니다; 이 경우, 대부분의 공용 멤버를 초기화할 수 있지만 모든 멤버를 건너뛸 수는 없습니다. 다음 예제에서는 선언 된 생성자가 없을 때 중괄호 초기화에서 사용되는 순서를 보여줍니다.
class class_d {  
public:  
    float m_float;  
    string m_string;  
    wchar_t m_char;  
};  
  
int main()  
{  
    class_d d1{};  
    class_d d1{ 4.5 };  
    class_d d2{ 4.5, "string" };  
    class_d d3{ 4.5, "string", 'c' };  
  
    class_d d4{ "string", 'c' }; // compiler error  
    class_d d5("string", 'c', 2.0 }; // compiler error  
}   

이동 문법

이동 생성자, 이동 할당 생성자
https://msdn.microsoft.com/ko-kr/library/dd293665.aspx


// Move constructor.  
MemoryBlock(MemoryBlock&& other)  
   : _data(nullptr)  
   , _length(0)  
{  
   std::cout << "In MemoryBlock(MemoryBlock&&). length = "   
             << other._length << ". Moving resource." << std::endl;  
  
   // Copy the data pointer and its length from the   
   // source object.  
   _data = other._data;  
   _length = other._length;  
  
   // Release the data pointer from the source object so that  
   // the destructor does not free the memory multiple times.  
   other._data = nullptr;  
   other._length = 0;  
}  
  
// Move assignment operator.  
MemoryBlock& operator=(MemoryBlock&& other)  
{  
   std::cout << "In operator=(MemoryBlock&&). length = "   
             << other._length << "." << std::endl;  
  
   if (this != &other)  
   {  
      // Free the existing resource.  
      delete[] _data;  
  
      // Copy the data pointer and its length from the   
      // source object.  
      _data = other._data;  
      _length = other._length;  
  
      // Release the data pointer from the source object so that  
      // the destructor does not free the memory multiple times.  
      other._data = nullptr;  
      other._length = 0;  
   }  
   return *this;  
}  






댓글 없음:

댓글 쓰기