Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

2013 C++ Study For Students #1

3,979 views

Published on

2013 C++ Study For Students #1

Published in: Technology
  • Be the first to comment

2013 C++ Study For Students #1

  1. 1. 2013 C++ STUDY FOR STUDENTS #1 옥찬호
  2. 2. 진행자 소개 • 옥찬호 • 현) KAIST 전산학과 석사과정 • 현) 에이콘출판사 역자 • 현) 인디 게임 개발팀 Game Drunker 팀장 • 전) 경북대학교 컴퓨터학부 졸업 • 전) 스마트비아 인턴 – 안드로이드 프로그래머 • 전) 경북대학교 컴퓨터학부 시스템 관리위원장 • 전) 순심고등학교 졸업
  3. 3. 참고한 책들
  4. 4. 이번 스터디 주제 • C++ 언어의 문법과 중요 요소 둘러보기
  5. 5. C++도 시작은 “HELLO, WORLD” • // helloworld.cpp #include <iostream> int main() { std::cout << “Hello, World!” << std::endl; return 0; }
  6. 6. 주석 • 주석을 표시하는 두 가지 방법 • // helloworld.cpp : 슬래시 두 개를 겹쳐 써서 그 뒤에 따라오는 문자들은 그 종류와 관계없이 (줄 바꿈 문자를 제외하고) 모두 주석으로 처리하는 방법. • C 언어의 주석 : /*로 시작하고 */로 끝나는 구간을 줄 바꿈을 포함해서 모두 주석 처리. • /* 컴파일러에 의해 무시되는 * 여러 줄에 걸친 * C 스타일 주석 */
  7. 7. 전처리 지시자 • C++ 프로그램은 세 단계를 거쳐서 빌드(Build)된다. • 전처리(Preprocess) 작업 : 코드 내에서 지시된 메타 정보를 인식하여 자동으로 코드를 수정. • 컴파일(Compile) 작업 : 소스 코드를 기계어로 번역. • 링크(Link) 작업 : 기계어로 된 여러 오브젝트 파일을 묶어서 하나의 실행 파일로 만듦. • 전처리 지시자는 전처리 단계에서 사용되는 것. # 문자로 시작. • #include <iostream> 구문은 전처리 지시자 #include를 통해 <iostream> 헤더 파일의 코드르 모두 가져와 현재 소스에 삽입하도록 지시. • 헤더 파일은 보통 다른 파일에 구현(Define)된 함수들의 함수 선언(Declare)문을 담고 있어서 그 함수들을 현재 소스에서 참조하여 호출할 수 있도록 컴파일러에 정보를 알려주는 용도로 활용.
  8. 8. 전처리 지시자 • 자주 사용하는 전처리 지시자 • #include [파일명] : 지정된 파일의 내용을 현재 소스 파일의 해당 지시자가 위치한 곳에 삽입. • #define [key] [value] : 지시자 이후에 뒤따르는 모든 key 키워드를 value로 치환. • #ifdef [key], #ifndef [key], #endif : 특정 키워드가 #define 문이나 컴파일러 옵션으로 정의되어 있는지 여부에 따라 해당 조건 블록의 코드를 활성화 / 비활성화시킴. • #pragma [xyz] : [xyz] 부분은 컴파일러마다 다른데, 언어 차원에서 표준화되지 않고 특정 종류의 컴파일러에서만 지원되는 전처리 기능을 이용할 수 있음.
  9. 9. MAIN() 함수 • main() 함수는 프로그램을 실행할 때 가장 먼저 호출되는 시작점. • int 타입 리턴값은 프로그램의 상태를 알려주는 목적으로 사용됨. • main() 함수는 파라미터 없이 호출될 수도 있고 다음과 같이 두 개의 파라미터를 받을 수도 있음. • int main(int argc, char* argv[]) • argc는 프로그램을 실행할 때 명령줄에서 넘겨진 파라미터의 개수. • argv는 각 파라미터의 문자열 값이 들어 있음. • 프로그램의 이름도 파라미터에 포함되어 제일 첫 번째 파라미터로 취급.
  10. 10. I/O 스트림 • 스트림 출력 기능 : std::cout • 출력할 데이터를 컨베이어 벨트에 밀어 넣는다고 생각. • << 연산자는 데이터를 컨베이어 벨트에 밀어 넣는 역할. • 다양한 타입의 데이터를 << 연산자로 밀어 넣을 수 있음. • std::cout << “There are “ << 219 << “ways I love you.” << std::endl; • std::endl은 출력한 메시지의 한 줄이 끝났음을 알려줌. • 줄 바꿈을 하기 위해 n 문자(줄 바꿈을 의미하는 특수 문자(Escape Character))를 사용할 수도 있음. • n : 줄 바꿈, r : 캐리지 리턴(출력 커서를 처음으로 옮김), t : 탭 문자, : 역슬래시 문자, ” : 따옴표
  11. 11. I/O 스트림 • 스트림 입력 기능 : std::cin • >> 연산자를 입력 스트림에 적용. • 사용자의 키보드 입력을 받는 데 이용할 수 있음. • 사용자 입력을 받을 때 입력받는 데이터가 어떤 타입인지 알 수 없으므로 까다로운 면이 있음.
  12. 12. 네임스페이스 • 코드 내에서 이름이 같은 변수명이나 함수명이 서로 충돌하는 문제를 해결하기 위해 고안됨. • 예를 들어 foo() 함수를 만들어 놓고, 나중에 외부 라이브러리를 사용해야만 하는 상황이 되어서 이용하려고 봤더니 외부 라이브러리에도 foo() 함수가 있을 때, 컴파일러 입장에서는 어느 foo() 함수를 사용해야 하는지 알아낼 방법이 없음. • 네임스페이스는 특정 코드 영역에서 참조하는 이름을 어느 이름 집합에서 찾아야 하는지 범위를 제안하는 수단. • 특정 함수를 특정 네임스페이스에 종속시키려면 다음과 같이 네임스페이스 블록으로 둘러쌈. • • namespace mycode { void foo(); } 함수 선언뿐 아니라 함수 구현 본체도 네임스페이스 아래에 둘 수 있음.
  13. 13. 네임스페이스 • 네임스페이스 아래에 선언된 foo() 함수를 호출할 때는 네임스페이스와 ::를 앞 첨자로 덧붙임. • ::와 같은 첨자를 스코프 설정 연산자(Scope Resolution Operator)라고 함. • • mycode::foo(); // “mycode” 네임스페이스에 속한 “foo” 함수의 호출 네임스페이스 사용을 명시적으로 선언하면 그 아래에 있는 코드에서는 스코프 설정 연산자 mycode::를 붙이지 않아도 mycode 네임스페이스에 속한 이름(함수명, 데이터 타입 / 변수명 등)을 불러다 쓸 수 있음. • using namespace mycode; int main() { foo(); // mycode::foo();와 같은 효과를 가짐 return 0; }
  14. 14. 네임스페이스 • Hello, World 예제에서도 네임스페이스가 이용됨. • cout과 endl은 네임스페이스 std에 속한 것들이기 때문에 다음과 같이 using 지시자로 네임스페이스를 이용하면 개별적으로 스코프 설정 연산자 std::를 사용하지 않아도 cout과 endl을 이용할 수 있음. • #include <iostream> using namespace std; int main() { cout << “Hello, World!” << endl; return 0; } • using 지시자는 네임스페이스에 속한 이름을 특정해서 지정할 수도 있음. • 예를 들어 네임스페이스 std의 cout만을 스코프 설정 연산자 없이 사용하고 싶다면 다음과 같이 선언. • using std::cout;
  15. 15. 변수 • C++에서 변수는 코드 내 어디서나 선언할 수 있고, 선언한 이후에는 해당 선언이 속한 블록 내의 선언 이후 코드에서 그 변수에 접근할 수 있음. • 초깃값을 설정하지 않고도 가능하며, 그렇게 하면 변수가 할당된 메모리 영역에 원래 들어 있던 값이 사용됨. • 명시적인 초깃값 설정이 없는 변수 선언은 버그를 유발하기 쉽기 때문에 피하는 것이 좋음. • int uninitializedInt; int initializedInt = 7; cout << uninitializedInt << “ is a random value” << endl; cout << initializedInt << “ was assigned an initial value” << endl;
  16. 16. 변수 • C++에서 흔하게 이용하는 변수 타입. • int : 음수 / 양수 정수. 표현 가능한 크기는 컴파일러에 따라 다름. → int i = 7; • short (int) : 작은 음수 / 양수 정수(보통 2바이트 크기). → short s = 13; • long (int) : long 정수(보통 4바이트 크기). → long l = -7; • [C++ 11] long long (int) : long long 정수. 표현 가능한 크기는 컴파일러에 따라 다르나 최소 long 정수 이상으로 보통 8바이트 크기임. → long long ll = 14; • unsigned int / unsigned short (int) / [C++ 11] unsigned long (int) / [C++ 11] unsigned long long (int) : 0을 포함한 양수만 표현 가능한 정수. → unsigned int i = 2; unsigned short s = 23;
  17. 17. 변수 • C++에서 흔하게 이용하는 변수 타입. • float : 부동소수점 숫자. → float f = 7.2f; • double : 배정도 정밀도 부동소수점 숫자. 최소한 부동소수점 이상의 정밀도 표현 가능. → double d = 7.2; • long double : long 배정도 정밀도 부동소수점 숫자. 최소한 double 이상의 정밀도 표현 가능. → long double d = 16.98L; • char : 8비트 문자. → char ch = ‘m’; • [C++ 11] char16_t : 16비트 문자. → char16_t c16 = u’m’; • [C++ 11] char32_t : 32비트 문자. → char32_t c32 = U’m’; • wchar_t : 와이드 문자로 컴파일러에 따라 다름. → wchar_t w = L’m’;
  18. 18. 변수 • C++에서 흔하게 이용하는 변수 타입. • bool : 참(true) 또는 거짓(false)를 나타내는 논리 타입. → bool b = true; • [C++ 11] auto : 컴파일러에 의해 자동으로 타입이 정해지는 타입. → auto i = 7; // i는 자동으로 int형이 됨 • [C++ 11] decltype(expr) : “expr”이 나타내는 타입과 같은 타입을 따르는 타입. → int i = 7; decltype(i) j = 8; // j는 i의 타입을 따라 int가 됨
  19. 19. 변수 • C++에서는 명시적인 변수 타입 캐스팅을 위해 세 가지 방법을 지원. • 1. 변수 앞에 캐스팅할 타입을 괄호로 명시. • • 2. 타입 생성자 함수를 이용. • • bool someBool = (bool)someInt; bool someBool = bool(someInt); 3. 캐스팅 연산자를 이용. • bool someBool = static_cast<bool>(someInt); • int 변수의 값이 0이면 false로, 0이 아니면 true로 변환.
  20. 20. 변수 • Coerced 캐스팅 : 어떤 상황에서는 변수의 타입 캐스팅이 자동으로 일어날 수도 있음. • 예를 들어 short 타입 변수는 long 타입 변수로 명시적인 타입 캐스팅 없이도 자동으로 캐스팅 될 수도 있음. • 왜냐하면 long 타입은 short 타입과 같이 정수를 표현하면서도 그 표현 범위가 더 넓기 때문. • long someLong = someShort; // 명시적인 타입 캐스팅이 없어도 된다. • 변수가 자동으로 타입 캐스팅될 때 데이터에 손실이 없는지 고려해야 함. • 예를 들어 float 변수가 int 변수로 캐스팅된다면 소수점 이하 값들은 잃어버리게 됨.
  21. 21. 연산자 • C++ 연산자에는 이항 연산자(두 개의 변수를 대상으로 하는 연산자)와 단항 연산자(하나의 변수를 대상으로 하는 연산자) 그리고 삼항 연산자(세 개의 변수를 대상으로 하는 연산자)가 있음. • C++ 연산자의 종류 • = : 이항 연산자로 오른쪽에 있는 값을 왼쪽에 있는 변수로 대입. → int i; i = 3; int j; j = i; • ! : 단항 연산자로, true / false 논리값을 반전. → bool b = !true; bool b2 = !b; • + : 덧셈을 위한 이항 연산자. → int i = 3 + 2; int j = i + 5; int k = i + j; • -, *, / : 각각 뺄셈, 곱셈, 나눗셈을 위한 이항 연산자. → int i = 5 – 1; int j = 5 * 2; int k = j / i; • % : 정수 나눗셈에서의 나머지 값을 구함. → int remainder = 5 % 2;
  22. 22. 연산자 • C++ 연산자의 종류 • ++ : 변숫값 1을 증가시키는 단항 연산자. 연산자가 변수 오른쪽에 있으면 “사후 증가”가 적용되어 해당 코드 라인에서는 증가하기 전의 값을 가지나 그 라인을 벗어나면 1 증가. 연산자가 변수 왼쪽에 있으면 “사전 증가”가 적용되어 바로 해당 코드 라인에서 1 증가한 값을 가짐. → i++; ++i; • -- : 변숫값을 1 감소시키는 단항 연산자. ++와 마찬가지로 연산자 위치에 따라 사후 감소 또는 사전 감소가 적용. → i--; --i; • +=, -=, *=, /=, %= : 각각 i = i + j; i = i – j; i = i * j; i = i / j; i = i % j;의 축약형. • &, &= : 두 변수의 각 비트 값에 AND 연산을 함. → i = j & k; j &= k; • I, I= : 두 변수의 각 비트 값에 OR 연산을 함. → i = j | k; j |= k;
  23. 23. 연산자 • C++ 연산자의 종류 • <<, >>, <<=, >>= : 왼쪽 변수의 비트 열을 오른쪽 값만큼 왼쪽(<<) 또는 오른쪽(>>)으로 시프트(Shift). → i = i << 1; i = i >> 4; i <<= 1; i >>= 4; • ^, ^= : 두 변수의 각 비트 값에 배타적 논리합(Exclusive OR) 연산을 함. → i = i ^ j, i ^= j; • 연산 수식을 이해하기 쉬운 조각으로 쪼개거나 괄호를 이용하여 명시적으로 순서를 지정하는 것이 좋음. • int i = 34 + 8 * 2 + 21 / 7 % 2; • int i = 34 + (8 * 2) + ((21 / 7) % 2); • int i = 8 * 2; int j = 21 / 7; j %= 2; i = 34 + i + j;
  24. 24. 연산자 • #include <iostream> using namespace std; int main() { int someInteger = 256; short someShort; long someLong; float someFloat; double someDouble; someInteger++; someInteger *= 2; someShort = (short)someInteger; someLong = someShort * 10000; someFloat = someLong + 0.785; someDouble = (double)someFloat / 100000; cout << someDouble << endl; return 0; }
  25. 25. 열거 타입 • 열거(Enumeration) 타입은 어떤 숫자들을 나열할지 프로그래머가 마음대로 정할 수 있게 해줌. • 예를 들어 체스 프로그램이라면 각각의 말에 특정 int 값을 할당하여 말의 종류를 나타내는 고유 번호로 이용. • • const int kPieceTypeKing = 0; const int kPieceTypeQueen = 1; const int kPieceTypeRook = 2; const int kPieceTypePawn = 3; // etc int myPiece = kPieceTypeKing; 이와 같이 값을 정의해도 되지만, 잘못 사용될 가능성이 있음. • 만약 다른 프로그래머가 각 말의 값에 대해 실수로 1씩 더해서 사용한다면 왕이 왕비가 되어 버림. • 최악의 상황으로 -1과 같이 어느 말의 고윳값과도 매칭이 안되는 엉뚱한 값을 이용해 버릴 수도 있음.
  26. 26. 열거 타입 • 열거 타입은 이러한 문제를 나열하는 값 목록과 범위를 엄격하게 관리함으로써 방지. • typedef enum { kPieceTypeKing, kPieceTypeQueen, kPieceTypeRook, kPieceTypePawn } PieceT; • 실제로 열거 타입은 컴파일러 안에서 정수 타입으로 처리. • 예를 들어 kPieceTypeKing의 실제 값은 정수 0. • 하지만 PieceT의 가능한 값 목록을 정함으로써 PieceT 타입 변수가 정해진 값 목록 이름 이외의 값으로 설정될 때 컴파일러가 에러 메시지를 출력할 수 있게 해줌. • PieceT myPiece; myPiece = 0;
  27. 27. 열거 타입 • 열거 타입의 각 항목에 대해 개별적으로 특정 정수 값을 할당하는 것도 가능. • typedef enum { kPieceTypeKing = 1, kPieceTypeQueen, kPieceTypeRook = 10, kPieceTypePawn } PieceT; • kPieceTypeKing이 값 1을 명시적으로 할당받았기에 바로 뒤따르는 kPieceTypeQueen은 컴파일러에 의해 1 증가한 값 2를 자동으로 할당받음. • 명시적으로 10을 할당받은 kPieceTypeRook의 바로 뒤에 있는 kPieceTypePawn도 1 증가한 11을 자동으로 할당받음.
  28. 28. [C++ 11] 엄격한 열거 타입 • 앞서 설명한 열거 타입은 타입 세이프하지 않음. • 기본적으로 정수 타입 취급을 받기 때문에 서로 완전 별개인 열거 타입 간에 비교 연산이 가능. • C++ 11에서는 이런 문제를 해결하기 위해 enum class를 도입. • • enum class MyEnum { EnumValue1, EnumValue2 = 10, EnumValue3 }; MyEnum은 타입 세이프하기 때문에 열거 값이 다른 정수 타입 변수로 자동으로 캐스팅되지 않고 class 이름으로 스코프 설정이 되지 않으면 참조할 수도 없음.
  29. 29. [C++ 11] 엄격한 열거 타입 • 일반 정수 값과 MyEnum 값의 비교는 C++ 11에서 문법 위반. • if (MyEnum::EnumValue3 == 11) { … } • 열거 타입은 기본적으로 int 타입과 연계되지만 enum class에서는 값의 타입을 바꿀 수도 있음. • enum class MyEnumLong : unsigned long { EnumValueLong1, EnumValueLong2 = 10, EnumValueLong3 };
  30. 30. 구조체 • 구조체는 기본 타입 또는 다른 구조체를 조합하여 새로운 타입을 만들어냄. • 가장 흔한 예는 데이터베이스의 레코드 하나를 나타내는 것. • 예를 들어 직원의 신상명세를 기록하는 데이터베이스라면 직원별로 이름의 첫 글자와 직원 등록 번호, 급여 정보가 있다면 구조체로 정의하여 헤더 파일에 둘 수 있음. • • typedef struct { char firstInitial; char middleInitial; char lastInitial; int employeeNumber; int salary; } EmployeeT; EmployeeT 타입으로 변수를 선언하면 위의 각 항목을 모두 가진 데이터가 됨.
  31. 31. 구조체 • #include <iostream> using namespace std; int main() { EmployeeT e; e.firstInitial = ‘M’; e.middleInitial = ‘R’; e.lastInitial = ‘G’; e.employeeNumber = 42; e.salary = 80000; cout << “Employee: “ << e.firstInitial << e.middleInitial << e.lastInitial << endl; cout << “Number: “ << e.employeeNumber << endl; cout << “Salary: “ << e.salary << endl; return 0; }
  32. 32. IF/ELSE 구문 • if 구문은 조건이 참이면 if 문 아래 블록 또는 블록이 없을 경우 바로 후속되는 라인의 코드를 실행. • 조건이 거짓이면 else 블록의 코드를 실행. else 블록이 없으면 if 문 블록의 후 속 코드가 실행. • if (i > 4) { // 작업 수행 } else if (i > 2) { // 다른 작업 수행 } else { // 또 다른 작업 수행 } • if 문의 괄호 영역에 들어갈 구문은 반드시 불(Boolean) 변수이거나 불 타입의 결과 값을 가져야 함.
  33. 33. SWITCH 구문 • switch 구문은 변숫값에 따른 코드 분기를 편리하게 처리할 수 있게 해줌. • switch 구문에서는 변수를 상숫값과 비교해야 하므로 if 구문에서처럼 “~보다 크거나 작은”과 같은 조건은 switch 구문으로 처리할 수 없음. • 각 상숫값은 case 구문에 대응되어 해당 변수가 특정 상숫값과 같으면 해당 case 구문의 코드가 break 구문을 만날 때까지 실행됨. • 어느 상숫값에도 해당하지 않을 때 실행할 코드가 있다면 default 구문으로 처리할 수 있음. • break 구문을 빠뜨리면 의도하지 않게 다음 case(또는 default)에 해당하는 코드까지 수행되어 버리므로 주의해야 함.
  34. 34. SWITCH 구문 • switch (menuItem) { case kOpenMenuItem: // 파일 열기 코드 break; case kSaveMenuItem: // 파일 저장 코드 break; default: // 에러 메시지 출력 코드 break; }
  35. 35. 삼항 연산자 • C++의 유일한 삼항 연산자 • “만약 ~이면 ~하고 아니면 ~한다”를 축약적으로 표현하는 문법으로 ?과 :로 이루어짐. • std::cout << ((i > 2) ? “yes” : “no”); • i가 2보다 크면 yes를 출력하고 아니면 no를 출력하는 코드.
  36. 36. 조건 연산자 • 일반적인 조건 연산자에 대한 설명. • <, <=, >, >= : 왼쪽 값을 오른쪽 값과 비교하여, 작거나, 작거나 / 같거나, 크거나, 크거나 / 같거나에 따라 참, 거짓을 리턴함. → if (i < 0) { std::cout << “i는 음수이다.”; } • == : 왼쪽 / 오른쪽 값이 같으면 참. 대입 연산자 =과 혼동하지 않도록 주의. → if (i == 3) { std::cout << “i는 3이다.”; } • != : 왼쪽 / 오른쪽 값이 다르면 참. → if (i != 3) { std::cout << “i는 3이 아니다.”; } • ! : 단항 연산자로, 논리 기호 NOT과 같음. 참 / 거짓의 결과를 반전시킴. → if (!someBoolean) { std::cout << “someBoolean은 거짓이다.”; } • && : 논리 기호 AND와 같음. 왼쪽과 오른쪽 변수 또는 표현식의 결과가 모두 참이면 참을 리턴. → if (someBoolean && someOtherBoolean) { std::cout << “양쪽 다 참이다.”; } • || : 논리 기호 OR과 같음. 왼쪽 / 오른쪽 어느 쪽이든 참이면 참을 리턴. → if (someBoolean || someOtherBoolean) { std::cout << “최소한 둘 중 하나는 참이다.”; }
  37. 37. 조건 연산자 • C++는 표현식을 실행할 때 단락 논리(Short-Circuit Logic)를 적용. • 조건문의 최종 결과가 남아있는 표현식의 결과에 관계없이 확정될 수 있다면 표현식들은 실행하지 않음. • 예를 들어 여러 표현식 항목에 대해 OR 연산을 할 때 각 표현식의 항목을 실행하던 중에 결과가 참인 것을 만나면 그 이후의 항목의 결과에 관계없이 전체 조건 구문의 값이 참이 되므로 이후 항목은 실행해보지 않음. • bool result = bool1 || bool2 || (i > 7) || (27 / 13 % i + 1) < 2; • bool1이 참으로 판명되었다면 전체 결과는 무조건 참이 되므로 나머지 부분의 계산은 생략. • 불필요한 작업이 실행되지 않도록 언어 차원에서 최적화됨. • 하지만 특정 표현식이 함수 호출을 포함하는 등, 후속 코드에 영향을 미치는 상태 변화를 야기한다면 조건식의 자동화된 실행 생략이 찾기 어려운 버그를 만들어 낼 수도 있음.
  38. 38. WHILE 루프 • while 루프는 주어진 표현식이 참인 동안 while 블록의 코드가 실행되게 함. • int i = 0; while (i < 5) { std::cout << “이건 쉬운데” << std::endl; i++; } • break 키워드를 이용하면 루프에서 바로 벗어나 while 블록 이후의 코드로 연속해서 실행하게 함. • continue 키워드는 루프 블록 내에서 해당 키워드 아래쪽 코드를 실행하지 않고 바로 루프의 처음으로 돌아가서 다시 실행하게 함.
  39. 39. DO/WHILE 루프 • do / while 루프는 while 루프의 파생형으로 while 루프와 비슷함. • 단, while 블록의 코드가 처음 한 번은 무조건 실행되고 나서 루프 반복 여부를 체크한다는 점이 다름. • 첫 번째 실행이 무조건 발생해야 하는 상황에서 첫 번째 비교문의 불필요한 실행을 제거해주는 효과. • int i = 100; do { std::cout << “이건 쉬운데” << std::endl; i++; } while (i < 5);
  40. 40. FOR 루프 • for 루프와 while 루프는 상호 변환이 가능. • for 루프는 시작 조건, 중단 조건 그리고 루프 마지막에 반복 수행할 코드를 지정할 수 있음. • for (int i = 0; i < 5; i++) { std::cout << “이건 쉬운데” << std::endl; } • while 루프와 하는 일은 같지만 시작 조건, 중단 조건, 루프 반복 코드가 한 줄에 일목요연하게 표시되므로 어떤 개발자에게는 가독성이 훨씬 더 좋을 수 있음.
  41. 41. [C++ 11] 구간 지정 루프 • 구간 지정 for 루프는 C++ 11에서 새로 도입된 반복문. • 리스트를 순회하며 작업할 때 편리함. • 배열과 초기화 리스트, 반복자를 리턴하는 begin() / end() 함수를 멤버로 가진 데이터 타입이 주 대상. • int arr[] = {1, 2, 3, 4}; for (auto& i : arr) { i += 2; }
  42. 42. 배열 • 배열은 같은 데이터 타입의 값들을 일렬로 나열한 것. • C++에서 배열을 선언할 때는 배열의 크기를 명시적으로 설정해야 함. • 변수를 이용하더라도 const 변수여야 함. • C++ 11에서는 이러한 조건을 약간 완화하여 상수 표현식(constexpr)을 배열 크기 선언에 이용할 수 있음. • int myArray[10]; for (int i = 0; i < 10; i++) { myArray[i] = 0; } • int myArray[10] = {0}; • C++ 배열의 첫 번째 항목은 언제나 0번째에 위치. • 마지막 항목의 위치도 배열 크기의 – 1임에 주의해야 함.
  43. 43. [C++ 11] STD::ARRAY • C++ 11에서는 std::array라는 새로운 형태의 컨테이너를 도입. • <array> 헤더에 정의되어 있음. • std::array는 C언어 배열을 대체하는 안전한(범위를 벗어난 잘못된 메모리 작업의 위험이 없는) 배열 타입. • std::array는 std::vector와 달리 크기가 고정되어 있어 새로 항목을 추가하거나 삭제할 수 없지만 오버헤드가 적은 장점. • 배열의 크기를 항상 정확하게 알 수 있고, 포인터가 자동으로 잘못 타입 캐스팅되는 것도 피할 수 있으며 C++ STL의 반복자를 활용하여 항목을 순회하거나 STL 알고리즘을 이용할 수도 있음.
  44. 44. [C++ 11] STD::ARRAY • #include <iostream> #include <array> using namespace std; int main() { array<int, 3> arr = {9, 8, 7}; cout << “Array size = “ << arr.size() << endl; for (auto i : arr) { cout << i << endl; } return 0; }
  45. 45. 함수 • 프로그램의 크기가 커지면 main() 함수에 모든 코드를 담을 수 없음. • 효과적으로 관리하려면 이해하기 쉬운 의미 단위로 쪼개어 함수들로 정리해야 함. • 다른 코드에서 함수를 이용할 수 있게 하기 위해서는 함수를 선언해야 함. • 함수가 특정 소스 파일 안에서만 이용된다면 보통 그 파일 안에서 선언과 정의를 모두 하게 됨. • 다른 모듈의 소스 파일에서 이용된다면 함수 선언은 헤더 파일에 위치시키고 함수 정의는 소스 파일에 두게 됨. • 함수 선언의 예 : void myFunction(int i, char c); • 리턴 타입이 void이므로 호출자에게 아무런 결과도 돌려주지 않음. • 함수 파라미터로 int와 char가 선언되어 있기 때문에 함수를 호출할 때는 두 값을 꼭 입력해야 함.
  46. 46. 함수 • 함수 선언부와 함수 정의부가 같지 않으면 해당 함수를 찾지 못하여 컴파일러의 링크 단계에서 에러 발생. • void myFunction(int i, char c) { std::cout << “the value of i is “ << i << std::endl; std::cout << “the value of c is “ << c << std::endl; } • 위 함수를 호출할 때는 int와 char 타입의 두 파라미터를 입력함. • myFunction(8, ‘a’); • myFunction(someInt, ‘b’); • myFunction(5, someChar);
  47. 47. 함수 • 호출자에게 리턴값을 넘겨 줄 때는 리턴할 데이터 타입을 선언해 주어야 함. • int addNumbers(int number1, int number2); int addNumbers(int number1, int number2) { int result = number1 + number2; return result; } • C++ 11에서는 자동으로 함수 이름을 담고 있는 __func__ 변수가 함수의 로컬 변수로써 정의됨. • static const char __func__[] = “function-name”; • std::cout << “Entering Function “ << __func__ << std::endl;
  48. 48. 스택과 힙 • C++ 애플리케이션이 사용하는 메모리는 스택(Stack)과 힙(Heap) 두 가지로 나누어짐. • 만약 현재 실행 중인 함수 foo()가 또 다른 함수 bar()를 호출한다면 새로운 스택 프레임이 생성되고 bar() 함수를 호출할 때 넘겨진 파라미터들이 foo()의 스택 프레임에서 복제되어 새로 생성된 bar()의 스택 프레임에 옮겨짐. • 스택 프레임은 각 함수 간 메모리 공간을 격리시키는 중요한 역할을 함. • • • 예를 들어 foo() 함수의 로컬 변수들은 bar() 함수가 호출되는 동안 바뀌지 않음(단, 명시적으로 bar() 함수에 해당 변수의 메모리 위치를 넘겨주는 경우는 예외). 그리고 bar() 함수의 실행이 끝나고 함수가 리턴하면 bar() 함수의 로컬 변수들은 bar()의 스택 프레임과 함께 사라지기 때문에 더는 메모리를 차지하지 않음. 힙 메모리는 현재 실행 중인 함수나 스택 프레임과는 완전히 독립적인 메모리 영역. • 함수의 호출과 리턴에 관계없이 항상 존재해야 하는 변수라면 힙에 위치시키면 됨.
  49. 49. 동적 할당 배열 • 스택은 동작 방식 때문에 컴파일 시점에 각 스택 프레임의 크기가 결정되어야 함. • 스택 프레임의 크기가 사전에 결정되기 때문에 가변 크기의 배열을 함수의 로컬 변수로 선언할 수 없음. • int arraySize = 8; int myVariableSizedArray[arraySize]; // 컴파일 에러 발생 • 로컬 변수는 모두 스택에 들어가기 때문에 정확한 크기를 컴파일러가 알 수 있어야 함. • 배열의 크기를 실행 시점에 동적으로 결정해야 한다면 동적 메모리를 이용해 스택 대신 힙에 배열을 위치시킴.
  50. 50. 동적 할당 배열 • 동적 배열을 할당할 때는 포인터 변수를 선언. • int* myVariableSizedArray; • int 키워드 뒤에 붙은 * 표시는 해당 변수가 int 타입 데이터에 대한 힙 메모리 주소 값임을 나타냄. • 포인터는 할당된 힙 메모리를 가리키는 화살표로 이해할 수 있음. • 포인터를 초기화하려면 다음과 같이 new 명령을 이용함. • myVariableSizedArray = new int[arraySize]; • new 명령은 파라미터 arraySize만큼 힙 메모리를 확보하여 그 주소 값을 포인터 변수에 대입해줌. • 동적으로 할당된 배열을 더는 사용하지 않는다면 명시적으로 힙 메모리에서 해제해야 함. • delete [] myVariableSizedArray;
  51. 51. 동적 할당 배열 • C++의 new와 delete 명령은 C에서의 malloc(), free()와 유사. • new, new []와 delete, delete []는 각각 짝을 맞춰서 사용해야 함. • new []를 했다면 반드시 delete []를 수행해야 메모리 릭(Memory Leak)을 막을 수 있음. • 메모리 문제를 피하고 싶다면 스마트 포인터를 사용. • 스마트 포인터는 해당 객체에 대한 참조 스코프가 벗어나면 자동으로 메모리 해제가 수행됨. • <memory> 헤더에 정의된 C++ 11의 unique_ptr을 사용. • unique_ptr<int[]> myVariableSizedArray(new int[arraySize]); • unique_ptr는 해당 메모리가 스코프에서 벗어날 때 자동으로 메모리 해제를 수행.
  52. 52. 포인터의 이용 • 어떠한 변수든 힙 메모리에 할당할 수 있음. • int* myIntegerPointer = new int; • 여기서 포인터는 정수 변수 단 하나가 위치한 주소를 가리킴. • 변숫값에 접근하기 위해서는 포인터를 역참조(Dereference)해야 함. • *myIntegerPointer = 8; • 포인터 자체는 바뀌지 않고 그 포인터가 가리키는 메모리의 값이 바뀌는 것. • 포인터가 항상 힙 메모리만 가리키는 것은 아님. • 스택을 가리킬 수도 있고 심지어 또 다른 포인터를 가리킬 수도 있음. • 변수로부터 포인터를 얻을 때는 주소 참조 연산자 &를 이용. • int i = 9; int* myIntegerPointer = &i; // 숫자 8을 값으로 가진 변수의 포인터 변수
  53. 53. 포인터의 이용 • 포인터로 구조체를 참조할 때는 특별한 문법을 사용. • 값에 접근하기 위해 새 역참조 연산자 *를 포인터에 붙이고, 이렇게 역참조된 변수에 .을 붙여서 구조체 내 각 필드를 선택. • • EmployeeT* anEmployee = getEmployee(); cout << (*anEmployee).salary << endl; 구조체 역참조 연산자 ->를 이용하면 역참조와 필드 접근을 한 번에 할 수 있음. • EmployeeT* anEmployee = getEmployee(); cout << anEmployee->salary << endl;
  54. 54. 포인터의 이용 • 값에 의한 전달(Passing by Value) : 함수를 호출하면서 파라미터 전달을 위해 변수를 인자로 이용할 때 해당 변수의 주소가 아닌 값을 넘기게 됨. • 예를 들어 어떤 함수가 정수 값을 파라미터로 받고, 함수 호출 시 정수 변수를 인자로 넘겼다면 해당 변수의 값을 복사하여 함수 스택 프레임 안의 파라미터 영역에 위치시킴. • 참조에 의한 전달(Passing by Reference) : 포인터를 인자로 전달하면 스택 프레임 안에 복사되어 저장되는 것이 변수의 값이 아닌 변수의 주소가 되기 때문에 그 주소를 역참조 함으로서 스택 프레임 밖에 있는 변수의 값을 변경하는 것이 가능. • C++에서는 참조형 변수라는 더 좋은 방식을 지원하기 때문에 포인터를 통한 우회적인 방법을 사용하지 않아도 됨.
  55. 55. C 언어의 문자열 • C++에서 문자열을 다루는 두 가지 방법. • 1. 기존 C 언어에서와 같이 문자(char)의 배열로 이용하는 것. • 2. C++에서 제공하는 string 타입을 이용하는 것. • “Hello, World”와 같은 문자열은 내부적으로 널 문자(Null Character) ‘0’이 마지막에 추가된 문자(char) 타입의 배열로 처리됨. • char arrayString[20] = “Hello, World”; const char* pointerString = “Hello, World”;
  56. 56. C++ 언어의 문자열 • C++에서는 string 타입을 지원하여 훨씬 더 유연하게 문자열을 이용할 수 있음. • <string> 헤더에 정의되어 있고 기본 타입으로써 이용되며 std 네임스페이스 아래에 정의되어 있음. • string myString = “Hello, World”; cout << “The value of myString is “ << myString << endl; • C++ string 타입을 이용하면 일반적인 연산자로 문자열을 조작할 수 있음. • 예를 들어 문자열을 합칠 때 C에서는 strcat() 함수를 이용했지만, string 타입 문자열은 + 연산자를 이용. • 그리고 C에서는 문자열 비교에 ==를 이용하면 문자열 자체가 아닌 주소 값을 비교하게 되어 의도한 결과를 얻을 수 없지만 string 타입 문자는 == 연산자를 이용하여 문자열 비교를 할 수 있음.
  57. 57. C++ 언어의 문자열 • string str1 = “Hello”; string str2 = “World”; string str3 = str1 + “ “ + str2; cout << “str1 is “ << str1 << endl; cout << “str2 is “ << str2 << endl; cout << “str3 is “ << str3 << endl; if (str3 == “Hello World”) { cout << “str3 is what it should be.” << endl; } else { cout << “Hmmm . . . str3 isn’t what it should be.” << endl; }
  58. 58. 참조형 • 함수의 전형적인 패턴은 0개나 1개 이상의 파라미터를 받아 계산하고 하나의 결과 값을 리턴하는 것. • 하지만 리턴하고 싶은 결과 값이 2개 이상일 수도 있고 파라미터로 넘겨진 변수를 함수에서 변경해주길 원할 수도 있음. • 이런 경우 C에서는 변수 대신 변수의 포인터를 전달하여 해결. • C++에서는 포인터 없이도 명시적으로 참조에 의한 전달을 지원. • 함수 파라미터의 정의부에서 각 파라미터의 변수 이름 앞에 & 연산자를 붙이는 것. • 사용법은 보통 변수와 같지만, 내부적으로는 함수 안에서도 함수 호출 시 사용된 변수의 포인터가 사용됨.
  59. 59. 참조형 • void addOne(int i) { i++; } // 파라미터 변수의 복제본이기 때문에 원래 변수에는 아무런 영향이 없음 void addOne(int& i) { i++; } // 원본 파라미터 변수의 값이 1 증가함 • 위의 함수를 호출할 때는 정수 변수를 사용하기만 한다면 보통 파라미터 함수든 참조형 파라미터 함수든 차이가 없음. • int myInt = 7; addOne(myInt); • 파라미터가 값으로 전달되는 함수는 상수 파라미터를 넘겨 주어도 아무런 문제가 없지만 참조형 변수를 받는 함수는 상수 파라미터를 넘길 경우 컴파일 에러가 발생. • 이러한 문제는 rvalue 참조로 해결할 수 있음.
  60. 60. 예외 처리 • C++은 유연한 언어이기는 하지만 딱히 안전한 언어는 아님. • 작성한 코드가 엉뚱한 메모리 영역을 헤집거나 0으로 나누기를 시도해서 컴파일러가 걸러주지 않음. • C++의 exception은 코드의 안전성을 한 단계 높이기 위해 언어 차원에서 제공되는 기능. • exception에서는 몇 가지 새로운 용어가 도입됨. • 특정 코드가 예외적인 상황을 인지하면 exception을 던짐(throw). • 그리고 또 다른 코드가 던져진 exception을 받아서(catch) 필요한 뒤처리를 함.
  61. 61. 예외 처리 • #include <stdexcept> double divideNumbers(double inNumerator, double inDenomiator) { if (inDenominator == 0) { throw std::exception(); } return inNumerator / inDenomiator; } • throw 구문에 이르면 리턴값도 없이 함수의 실행이 즉시 중단됨. • 호출한 측에서 이 함수를 try / catch 블록으로 감쌌다면 던져진 exception을 받아서 뒤처리를 하게 됨.
  62. 62. 예외 처리 • #include <iostream> #include <stdexcept> int main() { try { std::cout << divideNumbers(2.5, 0.5) << std::endl; std::cout << divideNumbers(2.3, 0) << std::endl; } catch (const std::exception& exception) { std::cout << “An exception was caught!” << std::endl; } return 0; }
  63. 63. CONST 상수 • C에서는 버전 넘버 같이 실행되는 동안 절대 변하지 않는 상숫값에 이름을 부여하여 활용할 때 #define 전처리 매크로를 이용했음. • C++에서는 #define 대신 const 키워드를 이용하는 것을 권장. • const로 선언된 변수는 컴파일러에 의해 변숫값이 바뀌지 않도록 보증됨. • const float kVersionNumber = 2.0f; const string kProductName = “Super Hyper Net Modular”;
  64. 64. 파라미터 보호를 위한 CONST • C++에서는 const가 아닌 변수를 const 변수로 캐스팅할 수 있음. • 특정 코드가 실행되는 동안 특정 변수를 상수화하면 의도하지 않게 값이 바뀌는 오류 상황을 컴파일 시점에 알아낼 수 있음. • 예를 들어 여러분이 작성한 함수를 다른 동료 개발자가 사용할 때 해당 함수가 전달된 파라미터의 값을 무단으로 변경하지 않는다는 것을 보증해야 한다면 const 변수가 해결책이 됨.
  65. 65. 파라미터 보호를 위한 CONST • void mysteryFunction(const char* myString); int main() { char* myString = new char[2]; myString[0] = ‘a’; myString[1] = ‘0’; mysteryFunction(myString); return 0; } void mysteryFunction(const char* myString) { myString[0] = ‘b’; // const 위반으로 컴파일 오류가 발생함 } • mysteryFunction() 함수를 호출하면서 char* 변수를 const char* 타입으로 자동으로 캐스팅.
  66. 66. 참조형 CONST • 참조형 타입은 파라미터 변수를 변경할 수 있게 하려고 이용하기에 const의 사용이 일견 모순되어 보임. • 사실 참조형 파라미터 변수는 변숫값을 변경하는 목적 외에도 성능 관점에서 불필요한 값의 복제를 피하려는 의도로 이용되는 경우도 많음. • const이면서 참조형인 파라미터를 이용함으로써 값 복제 오버헤드도 피하고 참조형의 위협 요소인 원본의 변조도 막는, 어떻게 보면 일거양득의 효과를 얻음. • 참조형 const 변수는 객체를 이용할 때 더 중요함. • 객체는 정수 기본 타입보다 덩치도 크고 복제 작업 자체가 의도하지 않는 부작용을 낳기 쉽기 때문.
  67. 67. 클래스의 선언 • 클래스(Class)는 객체의 특성을 정의. • C++에서 class는 보통 헤더 파일에서 선언되고 상세 정의는 관련 소스 파일에서 작성됨. • 클래스 정의는 클래스 이름을 선언하는 것으로 시작. • 중괄호 {…} 안에서는 속성 데이터를 저장할 멤버 변수(Data Member)들과 클래스의 행동을 조정할 수 있는 메서드(Method)들이 선언됨. • • 클래스명과 같은 이름이면서 리턴값이 없는 메서드는 생성자(Constructor)라고 불림. • • 각 데이터 멤버와 메서드들은 public, protected, private 세 가지 접근 권한에 매핑됨. 객체가 생성될 때 자동으로 호출됨. 물결 표시(~)로 시작하고 클래스명과 같은 메서드는 소멸자(Destructor)라고 함. • 소멸자는 객체가 메모리에서 해제될 때 자동으로 호출됨. • 상수 불변의 원칙이 프로그램 전체적으로 전파되고 유지되게 하려면 멤버 변수의 변경이 없는 메서드는 명시적으로 const 키워드로 선언하는 것이 좋음. • const 메서드를 “inspector”라고 하고 멤버 변경이 있는 메서드는 “mutator”라고 함.
  68. 68. 표준 라이브러리 • C++에서는 유용한 클래스를 표준 라이브러리로 제공. • 중복해서 같은 기능의 클래스를 개발하는 부담을 피할 수 있음. • 오랜 시간 많은 사람으로부터 검증되었기 때문에 오류의 가능성도 적음. • 성능 최적화 또한 충분히 되어 있어서 개발자가 직접 작성하는 것보다 더 뛰어남.

×