[객체 지향 프로그래밍] 명품 C++ Programming

'명품 C++ Programming' 내용 정리 + '객체 지향 프로그래밍' 영상처리 프로젝트
조수환's avatar
Apr 24, 2024
[객체 지향 프로그래밍] 명품 C++ Programming
Contents
2장 C++ 프로그래밍의 기본3장 클래스와 객체4장 객체 포인터와 배열4-1장 객체의 동적 생성4-2장 this 포인터4-3장 String 객체5장 함수와 참조5-1장 복사 생성자6장 함수 오버로딩 / 디폴트 매개변수6-1장 static 멤버7장 friend와 연산자 오버로딩7-1장 연산자 오버로딩7-2장 다양한 연산자 오버로딩8장 상속9장 가상 함수와 추상 클래스10장 템플릿10-1장 auto와 람다식11장 C++ 입출력 시스템12장 C++ 파일 입출력13장 예외 처리MFC

2장 C++ 프로그래밍의 기본

  • main() 함수의 리턴 타입
    • main() 함수의 리턴 타입은 int이지만 생략하셔도 프로그램 종료 시 자동으로 return 0;이 실행됩니다. 리턴 타입을 void로 작성하셔도 대부분의 컴파일러에서는 문제가 없지만, 표준은 아닙니다.
  • 전처리기 지시문 #include
    • #include전처리기에 대한 지시문으로, 컴파일러가 컴파일 전에 헤더를 읽어 소스 파일에 삽입하도록 합니다.
  • 표준 출력 스트림 객체 cout
    • cout은 C++ 표준 출력 스트림 객체로, C++ 프로그램에서 출력한 데이터를 연결된 화면에 출력해 줍니다. 시프트 연산자 <<스트림 삽입 연산자로 오버로딩된 것입니다.
    • cout에서 줄바꿈
      • std::cout << '\n';에서는 '\n'cout 스트림 버퍼에 삽입되고, 나중에 버퍼가 꽉 차거나 강제 출력 지시가 있을 때 '\n'이 해석되어 커서를 다음 줄로 넘깁니다.
      • 반면 std::cout << std::endl;이 실행되면 endl() 함수가 호출되어 '\n'cout 스트림 버퍼에 넣고 현재 버퍼의 데이터를 바로 출력합니다.
  • 표준 입력 스트림 객체 cin
    • cin은 C++ 표준 입력 스트림 객체로, 키보드로 입력되는 값들을 cin 스트림 버퍼로 받고, 응용 프로그램에서 cin 객체로부터 입력된 키 값을 읽습니다.
    • 스트림 추출 연산자 >>는 스트림 객체로부터 데이터를 읽어 피연산자의 지정된 변수에 삽입합니다. 사용자가 입력한 키들은 cin 스트림 버퍼에 저장되며, Enter 키를 입력할 때 키 값을 변수에 저장합니다.
  • 네임스페이스
    • namespace같은 이름의 중복을 막기 위한 것으로, namespace 이름 { ... } 과 같이 생성하고, 이름::식별자 와 같이 :: 연산자를 이용하여 사용합니다.
    • std::는 C++ 표준 이름 공간으로 모든 C++ 표준 라이브러리가 std namespace에 만들어져 있습니다.
  • using 지시어
    • using 지시어를 사용하면 namespace 명시를 생략할 수 있습니다. using std::cout; 또는 using namespace std;와 같이 사용합니다.
  • C-string
    • C++에서는 C언어와의 호환성을 위해 C-string을 사용합니다.
    • C-string은 널 문자(\0)로 끝나는 char의 배열입니다.
    • 배열 크기가 문자 수보다 최소 1은 커야 합니다.
    • C-string을 다루기 위해서는 <cstring> 또는 <string.h> 헤더를 포함해야 합니다.
    • cin으로 입력받을 때 공백 문자를 만나면 그 이전까지 입력된 문자들을 하나의 문자열로 인식하므로 공백이 포함된 문자열을 입력 받으려면 cin.getline() 함수를 사용해야 합니다.
  • string 클래스
    • C++에서 문자열을 다루는 두 번째 방법으로 string 클래스가 있습니다.
      • 크기에 제약이 없고 다루기 쉽습니다.
      • string을 사용하기 위해서는 <string> 헤더를 포함해야 합니다.
  • 헤더 파일 포함 관계
    • #include<iostream>이 호출되면 <istream><ostream><ios> 헤더를 타고 들어가며 호출됩니다. 표준 C++ 헤더 파일은 확장자가 없습니다.
    • Visual C++에서는 C 라이브러리에 .h를 붙이는 것을 허용하지만 표준은 아닙니다.
    • C++ 표준 함수의 코드는 컴파일된 기계어 형태로 C 라이브러리에 있으며, 헤더 파일의 함수를 호출하는 것이 아닙니다.
    • 헤더 파일에는 함수의 선언(원형)만 들어있고 컴파일 할 때 함수 호출이 정확한지 판단하는 데 사용됩니다. cin, cout은 <iostream> 헤더 파일에 선언된 객체입니다.
  • 헤더 파일 include 구문
    • #include<헤더파일>컴파일러가 설치된 폴더에서 찾으라는 지시어이고, #include "헤더파일"프로젝트 폴더나 컴파일 옵션에서 지정한 include 폴더에서 찾으라는 지시어입니다.
#include <iostream> // #include "custom_header.h" // 프로젝트 폴더나 include 폴더에서 찾음 int main() { // main() 함수의 리턴 타입은 int이지만 생략 가능 std::cout << "Hello, World!" << std::endl; // endl은 버퍼를 비워 즉시 출력 std::cout << "Hello, " << "World!" << '\n'; // '\n'은 버퍼에 저장되어 나중에 출력 int num; std::cout << "Enter a number: "; std::cin >> num; // 사용자 입력을 num 변수에 저장 std::cout << "You entered: " << num << std::endl; // C-string 사용 char name[20]; std::cout << "Enter your name: "; std::cin >> name; // 공백 문자까지만 입력 받음 std::cout << "Hello, " << name << "!" << std::endl; // string 클래스 사용 std::string message = "Hello, World!"; std::cout << message << std::endl; return 0; // main() 함수 종료 시 자동으로 실행 }

3장 클래스와 객체

3.1 생성자(Constructor)

  • 객체가 생성될 때 자동으로 실행되는 멤버 함수
  • 코드 재사용을 위해 위임 생성자와 타겟 생성자를 사용할 수 있습니다.
class Circle { public: Circle() : Circle(1) {}// 위임 생성자 Circle(int r) { radius = r; }// 타겟 생성자 private: int radius; };

3.2 멤버 변수 초기화

  • 생성자 코드에서 초기화
  • 생성자 초기화 리스트에서 초기화
  • 멤버 변수 선언부에서 직접 초기화

3.3 기본 생성자와 기본 소멸자

  • 생성자가 없으면 컴파일러가 기본 생성자를 만들어 삽입합니다.
  • 소멸자가 선언되어있지 않으면 기본 소멸자가 자동으로 생성됩니다.

3.4 접근 지정자

  • private: 클래스 내부에서만 접근 가능(기본값)
  • public: 클래스 내부/외부에서 모두 접근 가능
  • protected: 클래스 내부와 상속받은 클래스에서 접근 가능

3.5 인라인 함수

  • 함수 선언 앞에 inline 키워드를 붙여 정의
  • 함수 호출 오버헤드를 줄여 실행 속도 향상
  • 클래스 선언부에 구현되면 자동으로 인라인 함수 처리

3.6 C++ 구조체

  • C 언어와의 호환성을 위해 지원
  • 클래스와 동일한 구조와 기능을 가지지만, 기본 접근 지정자가 public

3.7 헤더 파일과 소스 파일 분리

  • 클래스의 선언부와 구현부를 각각 헤더 파일과 소스 파일로 분리
  • 헤더 파일에 조건 컴파일 문을 작성하여 중복 선언 방지
// Circle.h #ifndef CIRCLE_H #define CIRCLE_H class Circle { public: Circle() : Circle(1) {} Circle(int r); // 멤버 함수 선언 private: int radius; }; #endif
// Circle.cpp #include "Circle.h" Circle::Circle(int r) { radius = r; } // 멤버 함수 구현

4장 객체 포인터와 배열

4.1 객체 포인터

  • 객체 포인터를 사용하여 객체의 멤버 함수와 멤버 변수에 접근할 수 있습니다.
  • 초기화되지 않은 객체 포인터를 사용하면 런타임 오류가 발생합니다.

4.2 객체 배열

  • 원소가 객체라는 점만 제외하면 기본 타입의 배열과 선언하고 활용하는 방법이 같습니다.
  • 배열 선언 시 각 객체의 기본 생성자가 호출됩니다. 기본 생성자가 없으면 컴파일 오류가 발생합니다.
  • 매개변수가 있는 생성자를 이용해 초기화하려면 별도의 방법이 필요합니다.
  • 객체 배열의 원소는 일반적인 배열 접근 방식으로 사용할 수 있습니다.
  • 포인터를 사용하여 객체 배열을 다룰 수 있습니다.
  • 객체가 소멸될 때는 높은 인덱스의 원소부터 순차적으로 소멸되며, 각 객체의 소멸자가 호출됩니다.

4.3 다차원 객체 배열

  • 다차원 배열의 선언, 초기화, 접근 방법은 일반적인 배열과 유사합니다.
  • 배열 선언 시 모든 객체의 생성자가 호출됩니다.
  • 생성자를 이용해 초기화할 수 있습니다
  • 이중 for문을 사용하여 다차원 배열의 원소에 접근할 수 있습니다.
// Circle.h #ifndef CIRCLE_H #define CIRCLE_H class Circle { public: Circle() : Circle(1) {}// 위임 생성자 Circle(int r) { radius = r; }// 타겟 생성자 ~Circle() {}// 소멸자 int GetRadius() { return radius; }// 인라인 함수 void SetRadius(int r) { radius = r; }// 인라인 함수 private: int radius; }; #endif
// main.cpp #include <iostream> #include "Circle.h" int main() { // 객체 생성 Circle c1; Circle c2(10); // 객체 포인터 Circle* pCircle = &c1; std::cout << "Radius of c1: " << pCircle->GetRadius() << std::endl; std::cout << "Radius of c1: " << (*pCircle).GetRadius() << std::endl; // 객체 배열 Circle circles[3]; for (int i = 0; i < 3; i++) { std::cout << "Radius of circle " << i << ": " << circles[i].GetRadius() << std::endl; } // 다차원 객체 배열 Circle circlesArray[2][3]; circlesArray[0][1].SetRadius(10); circlesArray[1][2].SetRadius(20); for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { std::cout << "Radius of circle at " << i << ", " << j << ": " << circlesArray[i][j].GetRadius() << std::endl; } } return 0; }

4-1장 객체의 동적 생성

4-1.1 new와 delete 연산자 사용

  • C++에서는 new와 delete 연산자를 사용하여 동적 메모리 할당 및 해제를 수행합니다.
    • new 연산자는 힙 영역에서 지정된 데이터 타입의 크기만큼 메모리를 할당하고 그 주소를 반환합니다.
    • delete 연산자는 new로 할당받은 메모리를 힙으로 반환합니다.

4-1.2 객체의 동적 생성

  • new 연산자를 사용하여 객체를 동적으로 생성할 수 있습니다.
  • 객체가 동적으로 생성될 때는 해당 클래스의 생성자가 자동으로 호출됩니다.
  • 매개변수가 있는 생성자를 호출하려면 생성자의 매개변수를 함께 전달해야 합니다.
#include <iostream> class Circle { public: Circle() { std::cout << "Circle default constructor called." << std::endl; } Circle(int radius) { std::cout << "Circle constructor with radius called." << std::endl; } ~Circle() { std::cout << "Circle destructor called." << std::endl; } }; int main() { // 동적 메모리 할당 및 객체 생성 int* number = new int; Circle* circle = new Circle; Circle* circle2 = new Circle(100); // 동적 메모리 해제 delete number; delete circle; delete circle2; return 0; }

4-2장 this 포인터

  • 컴파일러가 모든 멤버 함수에 자동으로 this 포인터를 추가합니다.
  • this 포인터는 객체 자신을 가리킵니다.
  • static 멤버 함수에서는 this 포인터를 사용할 수 없습니다.

4-2.1 this 포인터의 사용

  • 멤버 변수와 매개변수 구분
    • 매개변수와 멤버 변수의 이름이 같은 경우this-> 를 사용하여 멤버 변수를 명시적으로 지정해야 합니다.
    • 이를 통해 매개변수 값이 멤버 변수에 제대로 복사되도록 할 수 있습니다.
  • 객체 자신의 주소 반환
    • 멤버 함수에서 객체 자신의 주소를 반환할 때 this 포인터를 사용해야 합니다.
    • 특히 연산자 오버로딩 구현 시 이 패턴이 자주 사용됩니다.
class Circle { public: Circle(int radius) { this->radius = radius; } Circle* GetCircle() { return this; } int radius = 10; }; int main() { Circle circle(20); std::cout << circle.radius << std::endl; // Output: 20 Circle* pCircle = circle.GetCircle(); std::cout << pCircle->radius << std::endl; // Output: 20 return 0; }

4-3장 String 객체

  • <string> 헤더를 포함해야 합니다.
  • String 클래스는 다음과 같은 생성자를 가집니다
    • string(): 빈 문자열을 가진 string 객체 생성
    • string(const string& str)str을 복사한 새로운 string 객체 생성
    • string(const char* s): C-string의 s 문자열을 복사해 string 객체 생성
    • string(const char* s, int n)s에서 n개의 문자를 복사해 string 객체 생성
  • cout <<을 이용하여 string 객체를 쉽게 화면에 출력할 수 있습니다.
  • new와 delete 연산자를 이용하여 동적으로 string 객체를 생성하고 해제할 수 있습니다.

4-3.1 string 객체에 문자열 입력

  • string str;
  • cin >> str;
    • 공백 문자가 입력되면 그 앞까지를 하나의 문자열로 입력받습니다.
  • getline(cin, str, '\n');
    • 구분 문자(delimiter)를 만날 때까지 입력된 문자열을 str 객체에 저장합니다.

4-3.2 문자열 다루기

  • 문자열 치환: = 연산자
  • 문자열 비교: compare() 함수, 비교 연산자(==<>)
  • 문자열 연결: append() 함수, + / += 연산자
  • 문자열 삽입: insert() 함수, replace() 함수
  • 문자열 길이: length() 함수, size() 함수
  • 문자열 삭제: erase() 함수(일부분 삭제), clear() 함수(완전히 삭제)
  • 서브스트링: substr() 함수
  • 문자열 검색: find() 함수
  • 특정 위치의 문자: at() 함수, [] 연산자
  • 숫자 변환: stoi() 함수 (문자열을 숫자로 변환)
cppCopy codestring str = "gamza"; str.append("chips");// str = "gamzachips" str.replace(5, 3, "toran");// str = "gamzatoran" int length = str.length();// length = 10 string subStr = str.substr(5, 4);// subStr = "toran" int index = str.find("toran");// index = 5 char c = str[3];// c = 'z' int num = stoi("123");// num = 123

5장 함수와 참조

5.1 함수의 인자 전달 방식

  • Call by Value
    • 호출하는 코드에서 넘겨주는 원본 값이 함수의 매개변수에 복사되어 전달되는 방식입니다.
    • void incrementByValue(int num) { num++; } int main() { int value = 10; incrementByValue(value); // value is still 10 cout << "Value: " << value << endl; // Output: Value: 10 return 0; }
  • Call by Address
    • 주소를 직접 포인터 타입의 매개변수에 전달받는 방식입니다.
    • void incrementByAddress(int* num) { (*num)++; } int main() { int value = 10; incrementByAddress(&value); // value is now 11 cout << "Value: " << value << endl; // Output: Value: 11 return 0; }

5.2 객체 치환 및 객체 리턴

  • 객체 치환
    • 객체의 모든 데이터가 비트 단위로 복사됩니다.
  • 함수의 객체 리턴
    • 함수 내에서 생성된 객체의 복사본이 함수를 호출한 곳으로 전달됩니다.

5.3 참조

  • 참조 변수: 원본 변수에 대한 별명
    • int main() { int value = 10; int& ref = value; ref++; // value is now 11 cout << "Value: " << value << endl; // Output: Value: 11 return 0; }
  • Call by Reference
    • 참조 타입으로 선언된 함수의 매개변수를 통해 원본을 참조하여 메모리를 공유하는 인자 전달 방식입니다.
    • void swap(int& a, int& b) { int temp = a; a = b; b = temp; } int main() { int x = 5, y = 10; swap(x, y); // x is now 10 and y is now 5 cout << "x: " << x << ", y: " << y << endl; // Output: x: 10, y: 5 return 0; }
  • 참조 리턴
    • 현존하는 공간에 대한 참조를 리턴합니다.
    • 대표적으로 연산자 함수에서 참조 리턴이 중요하게 사용됩니다.
    • 참조는 l-value,  r-value가 모두 될 수 있으므로 아래와 같이 = 연산자의 왼쪽, 오른쪽에 모두 올 수 있습니다.
    • class Counter { public: Counter& increment() { count++; return *this; } int getCount() { return count; } private: int count = 0; }; int main() { Counter c; c.increment().increment().increment(); cout << "Count: " << c.getCount() << endl; // Output: Count: 3 return 0; }

5-1장 복사 생성자

5-1.1 얕은 복사(Shallow Copy)

  • 원본 객체의 멤버 변수가 그대로 사본 객체에 복사됩니다.
  • 포인터 타입의 멤버 변수인 경우, 포인터가 그대로 복사되어 원본과 사본이 같은 메모리를 가리키게 됩니다.
  • 사본 객체에서 포인터 변수를 변경하면 원본 객체에도 영향을 미치게 되어 오류가 발생할 수 있습니다.
class Point { public: Point(int x, int y) : x(x), y(y) {} int x, y; }; class Circle { public: Circle(int radius, Point* center) : radius(radius), center(center) {} int radius; Point* center; }; int main() { Point* point = new Point(10, 10); Circle circle1(5, point); // 얕은 복사 발생 Circle circle2(circle1); // circle1과 circle2의 center 포인터가 같은 메모리를 가리킴 std::cout << "circle1 center x: " << circle1.center->x << std::endl; std::cout << "circle2 center x: " << circle2.center->x << std::endl; delete point; // 소멸자 호출 시 크래시 발생 (중복 삭제) return 0; }

5-1.2 깊은 복사(Deep Copy)

  • 객체 멤버 중 포인터가 가리키는 메모리까지 복사합니다.
  • 원본과 사본의 포인터 변수가 별개의 메모리를 가리키게 됩니다.
  • 깊은 복사를 구현하기 위해서는 깊은 복사 생성자를 작성해야 합니다.

5-1.3 복사 생성자(Copy Constructor)

  • 복사 생성 시에만 실행되는 특별한 생성자입니다.
  • 복사 생성자를 직접 작성하지 않으면 컴파일러가 디폴트 복사 생성자를 삽입하여 얕은 복사를 수행합니다.
  • 깊은 복사를 구현하려면 사용자 정의 복사 생성자를 작성해야 합니다.
class Point { public: Point(int x, int y) : x(x), y(y) {} int x, y; }; class Circle { public: Circle(int radius, Point* center) : radius(radius), center(nullptr) { if (center) { this->center = new Point(center->x, center->y); } } // 복사 생성자 Circle(const Circle& other) : radius(other.radius), center(nullptr) { if (other.center) { this->center = new Point(other.center->x, other.center->y); } } ~Circle() { if (center) { delete center; } } int radius; Point* center; }; int main() { Point* point = new Point(10, 10); Circle circle1(5, point); // 깊은 복사 발생 Circle circle2(circle1); // circle1과 circle2의 center 포인터가 다른 메모리를 가리킴 std::cout << "circle1 center x: " << circle1.center->x << std::endl; std::cout << "circle2 center x: " << circle2.center->x << std::endl; delete point; // 소멸자 호출 시 문제 없음 return 0; }

5-1.4 복사 생성(Copy Construction)

  • 객체가 생성될 때 원본 객체를 복사하여 생성되는 경우를 말합니다.
  • 복사 생성이 일어나는 경우
      1. 객체로 초기화하여 객체가 생성될 때
      1. Call by value로 객체가 전달될 때
      1. 함수가 객체를 리턴할 때
      class Circle { public: Circle(int radius) : radius(radius) {} // 복사 생성자 Circle(const Circle& other) { std::cout << "Copy constructor called" << std::endl; radius = other.radius; } void printRadius() { std::cout << "Radius: " << radius << std::endl; } private: int radius; }; int main() { // 1. 객체 생성 시 복사 생성자 호출 Circle c1(5); Circle c2 = c1; c2.printRadius(); // Output: Radius: 5 // 2. 함수 매개변수로 전달 시 복사 생성자 호출 void printCircleRadius(Circle circle) { circle.printRadius(); } printCircleRadius(c1); // Output: Radius: 5 // 3. 함수 반환 시 복사 생성자 호출 Circle createCircle(int radius) { return Circle(radius); } Circle c3 = createCircle(10); c3.printRadius(); // Output: Radius: 10 return 0; }

6장 함수 오버로딩 / 디폴트 매개변수

6.1 함수 오버로딩(Function Overloading)

  • 같은 이름의 함수를 여러 개 만드는 것 입니다.
  • 함수의 매개변수 타입이나 개수가 달라야 합니다.
  • 리턴 타입은 고려하지 않습니다.
  • 함수 오버로딩을 통해 이름을 구분할 필요가 없어 실수를 줄일 수 있습니다.
  • 컴파일 시에 이루어지므로 실행 시간 저하가 없습니다.
  • 생성자 함수도 오버로딩하여 다양한 형태의 초기값으로 객체 생성이 가능합니다.
class Circle { public: int getArea() { return 3.14 * r * r; } int getArea(int r) { // 함수 오버로딩 1 return 3.14 * r * r; } int getArea(int r, int h) { // 함수 오버로딩 2 return 3.14 * r * r * h; } private: int r; int h; }; int main() { Circle c; int area1 = c.getArea(); int area2 = c.getArea(5); int area3 = c.getArea(5, 10); return 0; }

6.2 디폴트 매개변수(Default Parameter)

  • 함수 호출 시 매개변수에 값을 넣어주지 않은 경우 디폴트 값을 받도록 선언된 매개변수입니다.
  • 기본 매개변수라고도 하며, 끝 쪽에 몰려서 선언되어야 합니다.
  • 컴파일러가 함수 호출문에 나열된 실인자 값을 앞에서부터 순서대로 매개변수에 전달하고, 나머지를 디폴트 값으로 전달합니다.
  • 디폴트 매개변수 사용은 함수 오버로딩을 간소화할 수 있습니다.
  • 디폴트 매개변수 사용 여부에 따른 오버로딩은 되지 않습니다.
class Circle { public: int getArea(int r, int h = 1) { // h = 디폴트 매개변수 return 3.14 * r * r * h; } private: int r; int h; }; int main() { Circle c; int area1 = c.getArea(3); int area2 = c.getArea(3, 5); return 0; }

6.3 오버로딩 함수 호출 시 모호성

  1. 형 변환으로 인한 모호성
      • 자동 형 변환이 가능한 경우에도 모호성이 발생할 수 있습니다.
      class Circle { public: double getArea(double r) { return 3.14 * r * r; } double getArea(double r, double h) { return 3.14 * r * r * h; } private: double r; double h; }; int main() { Circle c; int area1 = c.getArea(1, 2); // 컴파일 에러: 모호성 // int area1 = c.getArea(r=1, h=2); // 명시적으로 매개변수 이름 지정으로 해결 return 0; }
  1. 참조 매개변수로 인한 모호성
      • 오버로딩된 함수 중 참조 매개변수를 가진 함수가 있을 때 모호성이 존재할 수 있습니다.
      class Circle { public: void draw(Circle& c1, Circle& c2); // 참조 매개변수로 인한 모호성 void draw(Circle c1, Circle c2); private: double r; double h; }; int main() { Circle c1, c2; c.draw(c1, c2); // 컴파일 에러: 모호성 return 0; }
  1. 디폴트 매개변수로 인한 모호성
      • 오버로딩된 함수 중 디폴트 매개변수를 가진 함수가 있을 때 모호성이 발생할 수 있습니다.
      class Circle { public: void func(int a); void func(int a, int b = 0); // 디폴트 매개변수로 인한 모호성 private: int r; int h; }; int main() { Circle c; c.func(1); // 컴파일 에러: 모호성 return 0; }

6-1장 static 멤버

6-1.1 static의 정의

  • 변수와 함수의 생명 주기와 사용 범위를 지정하는 방식입니다.
  • 프로그램 시작 시 생성되고 종료 시 소멸됩니다.
  • 변수나 함수가 선언된 범위 내에서 사용되며, 로컬로 구분됩니다.
  • non-static 멤버는 객체와 생명 주기가 같지만, static 멤버는 객체와 별도로 생성 및 소멸되며 모든 객체 간에 공유됩니다.

6-1.2 static 멤버 선언

  • 멤버 함수나 멤버 변수의 선언문 앞에 static 지정자를 붙입니다.
  • 클래스의 모든 멤버 함수와 변수가 static 지정자로 선언이 가능합니다.
  • static 멤버 변수는 전역 변수로 추가적으로 선언해야 합니다.

6-1.3 static 멤버 사용

  • 객체의 이름이나 포인터를 이용해 접근이 가능합니다.
  • 클래스의 이름과 범위 지정 연산자(::)를 사용해 접근이 가능합니다.
  • 객체 생성 전에도 사용이 가능합니다.
  • static 멤버 함수는 static 멤버에만 접근 가능하지만, non-static 멤버 함수는 static 멤버에 자유롭게 접근이 가능합니다.
  • static 멤버 함수는 this 포인터를 사용할 수 없습니다.

6-1.4 static의 활용

  • 전역 변수나 함수를 클래스에 캡슐화할 때 사용합니다.
  • 클래스의 모든 인스턴스들이 공유하는 변수나 함수를 만들 때 사용합니다.
class Circle { public: Circle(int r) : r(r) { count++; } ~Circle() { count--; } int getArea() { return 3.14 * r * r; } static int getCount() { return count; } private: int r; static int count; }; int Circle::count = 0; int main() { cout << Circle::getCount() << endl; // 0 Circle c1(10); cout << Circle::getCount() << endl; // 1 Circle c2(20); Circle c3(20); cout << Circle::getCount() << endl; // 3 -> 함수가 객체 간에 공유됨 return 0; }

7장 friend와 연산자 오버로딩

7.1 friend 함수

  • 클래스 외부에 작성된 함수클래스 내에서 friend 키워드로 선언하면 클래스의 멤버 함수와 동일한 접근 자격을 가지게 됩니다.
  • 클래스 내의 모든 함수와 변수에 접근할 수 있습니다.
  • 멤버 함수가 아니므로 상속되지 않습니다.

7.2 friend 함수의 선언 방법

  • 외부에 작성된 함수를 프렌드 함수로 선언하는 경우
  • 다른 클래스의 멤버 함수를 프렌드 함수로 선언하는 경우
  • 다른 클래스의 모든 멤버 함수를 프렌드 함수로 선언하는 경우
class Point { private: int x, y; public: Point(int x, int y) : x(x), y(y) {} friend class PointManager; // showPoint와 movePoint는 private인 x,y에 접근 가능 }; class PointManager { public: void showPoint(Point& p) { cout << "x: " << p.x << ", y: " << p.y << endl; } void movePoint(Point& p, int dx, int dy) { p.x += dx; p.y += dy; } }; int main() { Point p(1, 2); PointManager pm; pm.showPoint(p); // Output: x: 1, y: 2 pm.movePoint(p, 3, 4); pm.showPoint(p); // Output: x: 4, y: 6 return 0; }

7-1장 연산자 오버로딩

7-1.1 연산자 오버로딩의 정의

  • 동일한 연산자가 피연산자의 타입에 따라 서로 다른 연산을 수행하도록 정의하는 것입니다.
  • C++에서 언어에 정의된 연산자만 오버로딩 가능하며, 일부 연산자는 불가능합니다.

7-1.2 연산자 함수

  • 연산자 오버로딩은 함수를 작성하여 구현합니다.
  • 피연산자 중 하나는 반드시 객체여야 합니다.
  • 클래스의 멤버 함수로 구현하거나, 전역 함수로 구현하고 클래스에 friend 함수로 선언합니다.

7-1.3 연산자 함수 선언

  • 리턴타입 operator 연산자 (매개변수리스트);
  • 클래스의 멤버 함수로 선언된 경우, 왼쪽 피연산자는 자기 자신이 됩니다.
  • 외부 함수로 구현하고 클래스에 friend 함수로 선언된 경우, 두 개의 피연산자를 매개변수로 받습니다.

7-1.4 연산자 사용

  • 두 구현 방법 모두 연산자 사용 방식은 동일합니다.
  • a + b, a == b와 같이 사용합니다.
// 1. 클래스의 멤버 함수로 선언 class Vector { public: Vector operator+(Vector other) { // 구현 } bool operator==(Vector other) { // 구현 } }; // 2. 전역 함수로 구현, 클래스에 friend 함수로 선언 Vector operator+(Vector v1, Vector v2) { // 구현 } bool operator==(Vector v1, Vector v2) { // 구현 } class Vector { friend Vector operator+(Vector v1, Vector v2); friend bool operator==(Vector v1, Vector v2); }; // 연산자 사용 Vector a(1, 2, 3), b(2, 3, 4); Vector c = a + b; if (a == b) { // 코드 }

7-2장 다양한 연산자 오버로딩

7-2.1 이항 연산자 오버로딩

  • 클래스의 멤버 함수로 구현하거나, 전역 함수로 구현하고 클래스에 friend 함수로 선언합니다.
  • operator+operator==operator+= 등의 예시 코드를 제공합니다.
  • operator+=의 경우 참조를 리턴하도록 구현합니다.
class Vector { public: Vector(int x = 0, int y = 0) : x(x), y(y) {} Vector operator+(const Vector& other) { return Vector(x + other.x, y + other.y); } bool operator==(const Vector& other) { return (x == other.x && y == other.y); } Vector& operator+=(const Vector& other) { x += other.x; y += other.y; return *this; } private: int x, y; }; int main() { Vector a(1, 2), b(2, 3); Vector c = a + b; if (!(a == b)) { a += b; } return 0; }

7-2.2 단항 연산자 오버로딩 (전위/후위 연산자 오버로딩)

  • 전위 operator++는 자기 자신의 참조를 반환합니다.
  • 후위 operator++는 매개변수로 int 형을 받아 구분합니다.
  • 매개변수에 전달되는 값은 의미 없습니다.
class Vector { public: Vector(int x = 0, int y = 0) : x(x), y(y) {} Vector& operator++() { // 전위 ++ ++x; ++y; return *this; } Vector operator++(int) { // 후위 ++ Vector temp = *this; ++x; ++y; return temp; } private: int x, y; }; int main() { Vector a(1, 2); ++a; Vector b = a++; return 0; }

7-2.3 참조를 리턴하는 << 연산자 실습

  • operator<<를 멤버 함수로 구현합니다.
  • 참조를 리턴하여 연쇄적인 호출이 가능합니다.
class Vector { public: Vector(int x = 0, int y = 0) : x(x), y(y) {} Vector& operator<<(int a) { x += a; y += a; return *this; } private: int x, y; }; int main() { Vector a(1, 2); a << 2 << 3 << 1; return 0; }

8장 상속

8.1 상속의 개념

  • 자식 클래스(파생 클래스)가 부모 클래스(기본 클래스)의 멤버를 물려받는 것입니다.
  • 상속은 class 파생클래스 : 접근지정자 기본클래스 형태로 선언합니다.
class Character { public: Character(const std::string& name) : name(name) {} virtual void Speak() { std::cout << "Character says something." << std::endl; } protected: std::string name; }; class PlayerCharacter : public Character { // Character 클래스를 물려받은 PlayerCharacter public: PlayerCharacter(const std::string& name) : Character(name) {} void Speak() override { std::cout << "Player character says something." << std::endl; } };

8.2 기본 클래스 멤버의 접근 지정자에 따른 접근

  • private 멤버는 직접 접근이 불가능
  • protected 멤버는 파생 클래스에서 접근이 가능

8.3 상속 접근 지정자(상속의 종류)

  • public 상속: 접근 지정자 변경 없이 상속
  • protected 상속: public 멤버가 protected로 변경되어 상속
  • private 상속: 모두 private로 변경되어 상속
class Creature { public: void PublicFunction() {} protected: int protectedVariable; private: int privateVariable; }; class PublicDerived : public Creature { // PublicFunction()과 protectedVariable에 접근 가능 // privateVariable에는 접근 불가능 }; class ProtectedDerived : protected Creature { // PublicFunction()과 protectedVariable에 protected로 접근 가능 // privateVariable에는 접근 불가능 }; class PrivateDerived : private Creature { // PublicFunction(), protectedVariable, privateVariable 모두 private로 접근 가능 };

8.4 생성자와 소멸자 호출 순서

  • 파생 클래스 객체 생성 시 기본 클래스 생성자 먼저 호출, 파생 클래스 생성자 나중 호출
  • 파생 클래스 객체 소멸 시 파생 클래스 소멸자 먼저 호출, 기본 클래스 소멸자 나중 호출

8.5 캐스팅(Casting)

  • 업 캐스팅(up-casting)
    • 파생 클래스 객체를 기본 클래스 포인터로 가리키는 것입니다.
    • 명시적 타입 변환 불필요, 기본 클래스 멤버에만 접근이 가능합니다.
  • 다운 캐스팅(down-casting)
    • 기본 클래스 객체를 파생 클래스 포인터로 가리키는 것입니다.
    • 명시적 타입 변환 필요, 런타임 오류 주의가 필요합니다.
#include <iostream> #include <string> class Animal { public: // 생성자에서 이름을 받아 name 멤버 변수 초기화 Animal(const std::string& name) : name(name) {} // 가상 함수 Speak() // 자식 클래스에서 재정의할 수 있음 virtual void Speak() const { std::cout << "Animal speaks." << std::endl; } protected: std::string name; }; // Animal 클래스를 상속 class Dog : public Animal { public: // 생성자에서 이름을 부모 클래스 생성자에 전달 Dog(const std::string& name) : Animal(name) {} // Speak() 함수 재정의 (override) void Speak() const override { std::cout << "Dog barks." << std::endl; } // Fetch() 멤버 함수 추가 void Fetch() const { std::cout << "Dog fetches." << std::endl; } }; int main() { // 개 객체 생성 Dog* dog = new Dog("Buddy"); // 업캐스팅: 개 객체를 동물 포인터에 대입 (암시적 형변환) Animal* animal = dog; // 재정의된 Speak() 함수 호출 (다형성) animal->Speak(); // 출력: Dog barks. // 다운캐스팅 (명시적 형변환) - dynamic_cast 사용 Dog* anotherDog = dynamic_cast<Dog*>(animal); if (anotherDog) { // 다운캐스팅 성공 anotherDog->Fetch(); // 출력: Dog fetches. } else { // 다운캐스팅 실패 std::cout << "Failed to downcast." << std::endl; } delete dog; return 0; }

8.6 다중 상속

  • 하나의 파생 클래스가 여러 개의 기본 클래스를 상속받는 것입니다.
  • 다이아몬드 형태의 상속 구조 발생 시 모호성 문제가 존재합니다.

8.7 가상 상속

  • 다중 상속의 모호성 문제를 해결하기 위해 virtual 키워드를 사용합니다.
  • 기본 클래스의 멤버 공간이 한 번만 할당되고 공유됩니다.
class Creature { public: Creature() { std::cout << "Creature constructor called." << std::endl; } virtual void Speak() { std::cout << "Creature speaks." << std::endl; } }; class Player : virtual public Creature { public: Player() { std::cout << "Player constructor called." << std::endl; } void Speak() override { std::cout << "Player speaks." << std::endl; } }; class NPC : virtual public Creature { public: NPC() { std::cout << "NPC constructor called." << std::endl; } void Speak() override { std::cout << "NPC speaks." << std::endl; } }; class PlayableNPC : public Player, public NPC { public: PlayableNPC() { std::cout << "PlayableNPC constructor called." << std::endl; } }; int main() { PlayableNPC pnpc; pnpc.Speak(); // Output: Player speaks. return 0; }

9장 가상 함수와 추상 클래스

9.1 순수 가상 함수

  • 코드 구현이 없는 가상 함수를 말합니다.
  • 클래스에서 파생 클래스가 반드시 구현해야 하는 함수를 정의하는 데 사용됩니다.
class Animal { public: virtual void makeSound() = 0;// 순수 가상 함수 };

9.2 추상 클래스

  • 하나 이상의 순수 가상 함수를 가진 클래스를 말합니다.
  • 인스턴스를 생성할 수 없으며, 파생 클래스에서 모든 순수 가상 함수를 오버라이딩하여 구현해야 온전한 클래스가 됩니다.
class Animal { public: virtual void makeSound() = 0; // 순수 가상 함수 }; class Dog : public Animal { public: // 파생 클래스에서 모든 순수 가상 함수를 오버라이딩 virtual void makeSound() override { std::cout << "멍멍" << std::endl; } };

9.3 override 지시어

  • 컴파일러에게 오버라이딩이 의도적으로 수행되었음을 알려줍니다.
  • 오버라이딩이 잘못 되었을 경우 컴파일 오류가 발생하여 실수를 쉽게 발견할 수 있습니다.
class Animal { public: virtual void makeSound(); }; class Dog : public Animal { public: virtual void makeSound() override { // 올바른 오버라이딩 std::cout << "멍멍" << std::endl; } virtual void move() override; // 컴파일 오류: Animal::move()를 오버라이딩하지 않음 };

9.4 final 지시어

  • 가상 함수의 오버라이딩을 금지하거나 클래스의 상속을 금지할 때 사용됩니다.
class Animal { public: virtual void makeSound() final;// 이 함수는 더 이상 오버라이딩할 수 없음 }; class Dog final : public Animal {// 이 클래스는 더 이상 상속할 수 없음 public: virtual void makeSound() override;// 컴파일 오류: Animal::makeSound()는 final임 };

10장 템플릿

  • 함수나 클래스의 타입을 일반화하여 재사용성을 높이는 기능입니다.
  • 템플릿을 사용하면 매개 변수 타입이 다르지만 같은 기능을 하는 함수들을 하나의 템플릿 함수로 표현할 수 있습니다.
template <typename T> // 템플릿 선언 T Add(T a, T b) { return a + b; } int main() { // 정수, 실수 모두 가능 int result1 = Add(1, 2); // 3 double result2 = Add(1.5, 2.0); // 3.5 return 0; }

10.1 구체화(specialization)

  • 컴파일러는 템플릿 함수 호출 시 매개 변수 타입에 맞는 구체화된 함수를 생성합니다. 이 과정을 구체화라고 합니다.
template <typename T> T Add(T a, T b) { return a + b; } template <> int Add<int>(int a, int b) { // 구체화 return a + b + 1;// 특수화된 버전 } int main() { int result1 = Add(1, 2); // 3 int result2 = Add<int>(1, 2); // 4 return 0; }

10.2 오버로딩 vs 템플릿

  • 오버로딩된 함수가 템플릿 함수보다 우선적으로 호출됩니다.
cppCopy code// C++ template <typename T> T Sum(T a, T b) { std::cout << "Template Sum" << std::endl; return a + b; } int Sum(int a, int b) { std::cout << "Overloaded Sum" << std::endl; return a + b; } int main() { int result = Sum(1, 2);// "Overloaded Sum" 출력 return 0; }

10.3 제네릭 클래스

  • 템플릿을 사용하여 데이터 타입이 다르더라도 동일한 알고리즘을 사용하는 클래스를 만들 수 있습니다.
#include <iostream> template <typename T> class Pair { private: T first; // 첫 번째 값 T second; // 두 번째 값 public: // 생성자: 두 값을 초기화 Pair(const T& f, const T& s) : first(f), second(s) {} // 첫 번째 값 가져오기 T getFirst() const { return first; } // 두 번째 값 가져오기 T getSecond() const { return second; } // 첫 번째 값 설정하기 void setFirst(const T& f) { first = f; } // 두 번째 값 설정하기 void setSecond(const T& s) { second = s; } }; int main() { // int 타입의 Pair 객체 생성 및 사용 Pair<int> intPair(10, 20); std::cout << "첫 번째 값: " << intPair.getFirst() << std::endl; std::cout << "두 번째 값: " << intPair.getSecond() << std::endl; // double 타입의 Pair 객체 생성 및 사용 Pair<double> doublePair(3.14, 6.28); std::cout << "첫 번째 값: " << doublePair.getFirst() << std::endl; std::cout << "두 번째 값: " << doublePair.getSecond() << std::endl; // std::string 타입의 Pair 객체 생성 및 사용 Pair<std::string> stringPair("안녕", "세상"); std::cout << "첫 번째 값: " << stringPair.getFirst() << std::endl; std::cout << "두 번째 값: " << stringPair.getSecond() << std::endl; return 0; }

10-1장 auto와 람다식

10-1.1 auto

  • 변수의 타입을 자동으로 추론하여 선언할 수 있게 해줍니다.
    • auto n = 3;// int auto p = &n;// int* auto& r = n;// int& auto r2 = r;// int
    • r2int로 선언됩니다. auto는 참조 타입을 자동으로 선언하지 않으므로, 참조를 사용하려면 &를 명시적으로 작성해야 합니다.

10-1.2 람다식

  • 이름이 없는 함수 객체를 만들 수 있게 해주는 C++11 기능입니다.
  • 캡처 리스트, 매개변수 리스트, 리턴 타입(생략 가능), 함수 본문으로 구성됩니다.
double pi = 3.14; // 캡처 리스트: [pi], 매개변수 리스트: (int r), 리턴 타입: -> double auto calc = [pi](int r) -> double { return r * r * pi; }; std::cout << calc(3); int sum = 0; // 캡처 리스트: [&sum], 매개변수 리스트: (int x, int y), 리턴 타입: 없음 [&sum](int x, int y) { sum = x + y; }(3, 4); std::cout << sum; std::vector<int> v = {1, 2, 3, 4, 5}; // 캡처 리스트: 없음, 매개변수 리스트: (int n), 리턴 타입: 없음 std::for_each(v.begin(), v.end(), [](int n) { std::cout << n << " "; });
  • 익명 함수 객체를 만들 수 있어 유용합니다.
    • 한 번만 사용하고 재사용하지 않을 때
    • STL 알고리즘의 매개변수로 작은 연산 코드를 전달할 때

11장 C++ 입출력 시스템

  • 스트림연속적인 데이터 흐름 또는 데이터를 전송하는 소프트웨어 모듈을 의미합니다.
  • 입력 스트림은 입력 장치(키보드, 파일 등)로부터 데이터를 프로그램으로 전달하고, 출력 스트림은 프로그램의 출력을 출력 장치(화면, 파일 등)로 전달합니다.
  • C++의 표준 입력 스트림 객체는 cin, 표준 출력 스트림 객체는 cout입니다.

11.1 입출력 스트림 버퍼

  • 입출력 스트림은 데이터를 보내기 전에 일시적으로 저장하는 버퍼를 가지고 있습니다.
  • 버퍼를 사용하면 데이터를 모아두었다가 한 번에 전송하므로 효율이 향상됩니다.
  • 입력 버퍼에 저장된 데이터는 프로그램에 전달되기 전에 수정할 수 있습니다.

11.2 출력 스트림 멤버 함수

cout.put('A');// A 출력 cout.put(65);// A 출력 (ASCII 코드) cout.put('H').put('E').put('L').put('L').put('O');// HELLO 출력// 문자열 출력 char str[] = "C++ is awesome"; cout.write(str, strlen(str));// C++ is awesome 출력// 버퍼 강제 출력 cout << "Hello" << flush;// Hello 즉시 출력

11.3 입력 스트림 멤버 함수

int c; while ((c = cin.get()) != EOF) { cout.put(static_cast<char>(c));// 입력받은 문자 출력 if (c == '\n') break;// 엔터키 입력 시 반복 종료 } // 문자열 입력 char buf[100]; cin.get(buf, 100);// 최대 99자까지 입력받음 cout << "입력된 문자열: " << buf << endl; // 한 줄 입력 char line[100]; cin.getline(line, 100);// 엔터키 전까지 입력받음 cout << "입력된 한 줄: " << line << endl; // 입력 스트림 비우기 cin.ignore(1, '\n');// 엔터키 입력을 제거

11.4 포맷 입출력

1. 포맷 플래그
  • C++ 입출력 스트림은 32개의 포맷 플래그를 저장하는 멤버 변수를 가지고 있습니다.
  • 포맷 플래그는 ios 클래스에 정의된 정수형 상수로 표현됩니다.
  • setf(flags) 함수로 플래그를 설정하고, unsetf(flags) 함수로 플래그를 해제할 수 있습니다.
    • cout.unsetf(ios::dec);// 10진수 출력 해제 cout.setf(ios::hex);// 16진수 출력 설정 cout << 30 << endl;// 출력: 1e cout.setf(ios::dec | ios::showpoint);// 10진수, 소수점 표시 설정 cout << 23.5 << endl;// 출력: 23.5000
2. 포맷 함수
  • width()fill()precision() 함수를 통해 출력 포맷을 조절할 수 있습니다.
  • width(int): 최소 필드 너비 설정
  • fill(char): 필드 너비가 부족할 때 채울 문자 지정
  • precision(int): 실수 출력 시 유효 자리수 설정
    • cout.width(10); cout << "Hello" << endl;// 출력: Hello cout.fill('-'); cout.width(5); cout << 12 << endl;// 출력: --12 cout.precision(5); cout << 11./3. << endl;// 출력: 3.6667
3. 조작자
  • 특수한 함수 형태의 조작자를 통해 포맷을 설정할 수 있습니다.
  • <iostream> 헤더에 정의된 조작자는 매개변수가 없고, <iomanip> 헤더에 정의된 조작자는 매개변수를 가집니다.
    • cout << hex << showbase << 30 << endl;// 출력: 0x1e cout << dec << showpos << 100 << endl;// 출력: +100 cout << setw(10) << setfill('-') << "hello" << endl;// 출력: -----hello

11.5 삽입 연산자

  • 삽입 연산자 <<는 출력 스트림에 데이터를 출력하는 연산자입니다.
  • ostream 클래스에 오버로딩된 << 연산자는 ostream&을 반환합니다.
    • cout << 'a' << 123 << endl;
  • 컴파일러가 cout << 'a'를 cout << ('a') 로 변환하여 연산자 함수를 호출합니다.
  • 'a'가 cout의 버퍼에 삽입되고, cout의 참조가 리턴되어 이어서 cout << 123을 실행할 수 있습니다.
  • 사용자 정의 삽입 연산자
    • ostream& operator<<(ostream& outs, UserClass obj); ostream& operator<<(ostream& outs, UserClass& obj);
      class Point { friend ostream& operator<<(ostream& stream, Point // ... }; ostream& operator<<(ostream& stream, Point p) { stream << "(" << p._x << ", " << p._y << ")" << endl; return stream; }

11.6 추출 연산자

  • 추출 연산자 >>는 입력 스트림으로부터 데이터를 읽어들이는 연산자입니다.
  • 사용자 정의 추출 연산자 함수의 원형
    • istream& operator>>(istream& ins, UserClass& obj);
      class Point { friend istream& operator>>(istream& stream, Point& p); // ... }; istream& operator>>(istream& stream, Point& p) { cout << "x 좌표>> "; stream >> p._x; cout << "y 좌표>> "; stream >> p._y; return stream; }

11.7 조작자

  • 조작자는 특별한 형태의 함수로, 입출력 스트림의 동작을 조작할 수 있습니다.
  • 예시: cout << endl;
    • 컴파일러가 cout << endl;을 cout.operator<<(endl);로 변환하여 연산자 함수를 호출합니다.
    • endl 함수의 주소가 매개변수로 전달됩니다.
  • 새로운 조작자 만들기
    • ostream& manipulatorFunction(ostream& outs); istream& manipulatorFunction(istream& ins);
      ostream& fivestar(ostream& outs) { outs << "*****"; return outs; } cout << fivestar << "C++";// 출력: *****C++

12장 C++ 파일 입출력

12.1 텍스트 파일 읽고 쓰기

  • ofstream 객체를 생성하여 파일을 쓸 준비를 합니다.
  • fout.open("file1.txt") 또는 ofstream fout("file1.txt")로 파일을 엽니다.
  • << 연산자를 사용하여 데이터를 파일에 기록합니다.
  • fout.close()로 파일을 닫습니다.
  • ifstream 객체를 생성하여 파일을 읽을 준비를 합니다.
  • fin.open("file1.txt") 또는 ifstream fin("file1.txt")로 파일을 엽니다.
  • >> 연산자를 사용하여 데이터를 파일에서 읽습니다.
  • fin.close()로 파일을 닫습니다.

12.2 파일 모드

  • ios::in: 읽기 모드
  • ios::out: 쓰기 모드
  • ios::ate: 쓰기 모드에서 파일 포인터를 파일 끝에 둠
  • ios::app: 쓰기 모드에서 자동으로 파일 포인터를 끝으로 이동
  • ios::trunc: 파일을 열 때 기존 내용 삭제

12.3 입출력 함수

  • get(), getline(), put()
  • read(), write(), gcount()

12.4 스트림 상태 검사

  • eof(), fail(), bad(), good(), clear()

12.5 임의 접근

  • seekg(), seekp(): 파일 포인터 이동
  • tellg() tellp(): 파일 포인터 위치 확인
// 텍스트 파일 읽고 쓰기 ofstream fout("example.txt"); fout << "Hello, world!" << endl; fout.close(); ifstream fin("example.txt"); string line; while (getline(fin, line)) { cout << line << endl; } fin.close(); // 바이너리 파일 읽고 쓰기 ofstream fout_binary("example.bin", ios::binary); int num = 42; fout_binary.write(reinterpret_cast<char*>(&num), sizeof(int)); fout_binary.close(); ifstream fin_binary("example.bin", ios::binary); int read_num; fin_binary.read(reinterpret_cast<char*>(&read_num), sizeof(int)); cout << "Read value: " << read_num << endl; fin_binary.close(); // 임의 접근 ifstream fin_access("example.txt"); fin_access.seekg(7, ios::beg);// 파일의 7번째 문자로 이동 char c; fin_access.get(c); cout << "Character at position 7: " << c << endl; fin_access.close();

13장 예외 처리

13.1 try-throw-catch 구조

  • try 블록에 예외가 발생할 수 있는 코드를 작성
  • 예외 발생 시 throw문을 통해 예외를 던짐
  • catch 블록에서 예외를 처리

13.2 처리 과정

  • try 블록에서 예외가 발생하면 catch 블록으로 제어가 이동
  • catch 블록의 예외 파라미터 타입과 throw된 예외 타입이 일치하면 해당 catch 블록에서 예외가 처리됨
  • 마지막 catch 블록에 ...을 사용하면 모든 타입의 예외를 처리할 수 있음

13.3 함수를 포함하는 try 블록

  • 함수 내에서 throw된 예외는 해당 try 블록의 catch 블록에서 처리됨

13.4 예외를 발생시키는 함수 선언

  • 함수 선언 시 throw() 안에 발생시킬 수 있는 예외 타입을 명시할 수 있음

13.5 중첩 try 블록

  • try 블록 안에 다른 try 블록을 중첩할 수 있음
  • 내부 try 블록의 예외는 먼저 내부에서 처리되고, 처리되지 않으면 외부 try 블록의 catch 블록으로 전달됨

13.6 throw 사용 시 주의사항

  • throw문은 항상 try 블록 안에서 실행되어야 함
  • 예외를 처리할 catch 블록이 없으면 프로그램이 종료됨
  • catch 블록 내에도 try-catch 블록을 선언할 수 있음
try { if (n == 0) throw n; else { average = sum / n; } } catch (int x) { cout << "예외 발생" << endl; cout << x << "으로 나눌 수 없음" << endl; average = 0; }

MFC

  • MFC(Microsoft Foundation Classes)는 마이크로소프트에서 제공하는 C++ 라이브러리입니다. 윈도우 응용 프로그램을 개발할 때 사용되며, GUI(Graphic User Interface) 프로그래밍을 쉽게 할 수 있게 해줍니다.
    • 주요 특징
        1. 윈도우 메시징과 이벤트 처리를 간단히 할 수 있습니다.
        1. 다양한 UI 컨트롤(버튼, 메뉴, 툴바 등)을 쉽게 추가할 수 있습니다.
        1. 문서/뷰 아키텍처를 통해 데이터와 UI를 분리하여 개발할 수 있습니다.
        1. OLE, 데이터베이스 프로그래밍 등 다양한 기능을 지원합니다.
        1. Visual C++ 통합 개발 환경을 지원하여 GUI 기반 개발이 가능합니다.

  • MFC 표기법
    • 헝가리안 표기법
      • 변수나 함수 이름에 데이터 형식이나 유형에 대한 정보를 접두사로 붙이는 방식
        • iCount - 정수형 변수 Count
    • 클래스는 모두 C로 시작한다.
    • 여러 단어가 하나의 클래스 이름일 경우 각 단어별로 첫 글자를 대문자로 표기한다.
    • 맴버 변수는 m_으로 시작하고, 맴버 변수는 대문자로 시작한다.
    • 전역 함수는 Afx라는 접두어가 붙는다.

  • 최근에는 .NET이나 더 가벼운 라이브러리를 사용하는 추세라고 합니다.
💡
이번 미니 프로젝트 Version 2에서는 MFC를 사용하여 UI를 구현하였습니다.

MFC 시작하기

먼저 Visual Studio을 실행한 후, 새 프로젝트를 생성해 줍니다. Visual Studio를 설치 시 MFC를 선택해야만 아래와 같이 ‘MFC 앱’이 활성화됩니다.
notion image
  • 프로젝트 이름을 입력한 후, 애플리케이션 종류와 프로젝트, 비주얼 스타일을 선택합니다.
  • 문서 템플릿 속성에서, 파일 확장명을 입력합니다. 저는 Gray scale의 경우 .raw로, Color scale의 경우 .jpg로 설정했습니다.

프로젝트가 생성되면 먼저 이미지 처리에 필요한 유틸리티 함수들이 필요합니다. MFC에서는 함수의 재정의를 통해 문서 처리 및 그래픽 렌더링 기능을 사용자가 정의할 수 있습니다.
notion image
  • 표시된 버튼을 클릭한 뒤, 원하는 기능의 함수를 선택해 Add 하고 기능을 구현합니다.
  • 이러한 방식으로 Doc 클래스의 OnOpenDocument()OnSaveDocument(), View 클래스의 OnDraw() 함수를 재정의하여 사용했습니다.

  • 프로젝트가 생성되면, 각 기능에 해당하는 함수와 변수를 추가해주면 됩니다.
    • 참고로 변수나 함수의 이름, 매개변수 등 잘못 설정할 경우 다시 선언하거나 사용 하지 않는 방법을 사용해야 합니다.
    • 보이지 않는 기능은 Doc.cpp에, 보이는 기능은 View.cpp에 구현합니다.
이제 Doc 클래스에 영상 처리 알고리즘을 수행하는 함수를 추가해야 합니다.
notion image
  • 함수 이름과 반환 형식, 매개 변수 등을 입력한 뒤 영상처리 기능을 구현해 줍니다.

영상 처리 알고리즘을 구현해 기능들을 만들었다면, 그러한 기능들과 여러 옵션을 선택하는 화면이 필요하겠죠?
  • 리소스 파일의 Menu에서 Main Menu를 찾아 선택하고, 기능을 선택할 수 있는 화면을 구성해줍니다.
    • notion image
  • 각 기능 메뉴의 속성에서 ID를 설정한 후, 이벤트 처리기를 View 클래스에 추가합니다.
    • notion image
  • 그러면 View.cpp에 함수가 추가되는데, 아래의 코드를 통해 기능을 수행합니다.
    • // 영상을 반전시키는 함수 void CColorImageProcessingView::OnReverseImage() { // 문서 클래스로부터 문서 포인터를 가져옵니다. CColorImageProcessingDoc* pDoc = GetDocument(); // 문서 포인터가 유효한지 확인합니다. ASSERT_VALID(pDoc); // 문서의 OnReverseImage() 함수를 호출하여 실제 영상 반전 작업을 수행합니다. pDoc->OnReverseImage(); // 뷰 영역을 무효화하여 반전된 영상을 다시 그립니다. Invalidate(TRUE); }
    • 이러한 방식으로 View에서 Doc을 호출해 수행하는 방식은 MFC의 문서/뷰 아키텍처에 따라 객체 지향 원칙을 지키기 위한 것입니다.

  • 입력 대화 상자
    • MFC에서는 입력 대화 상자를 통해 영상처리 기능들을 수행할 때, 사용자에게 입력을 받아 처리할 수 있습니다.
    • 먼저 리소스 파일의 Dialog에서, 삽입을 눌러줍니다.
      notion image

      Edit Control
      • Edit Control은 사용자에게 값을 입력받을 수 있습니다.
      도구 상자에서 텍스트와 Edit Control를 가져와 Dialog에 놓습니다. 그리고 Dialog 상자 클래스를 추가해줍니다.
      notion image

      Dialog 상자 클래스를 생성했다면, Edit Control의 변수를 추가해줍니다.
      notion image
      • 범주를 ‘값’으로 설정한 후 이름과 형식을 입력합니다. ‘기타’에서 최대값과 최소값 또한 지정할 수 있습니다.
        • notion image

      Dialog 클래스와 변수 생성이 완료되면, Doc.cpp에 해당 Dialog 클래스를 include 해서 입력받은 값을 처리할 수 있습니다.
      CConstantF dlg; if (dlg.DoModal() != IDOK) return; double sigma = (double)dlg.m_constant_f;

      Radio Button
      • Radio Button은 값을 입력받는 것이 아닌, 선택지를 제공할 수 있습니다.
      • 도구 상자에서 여러 개의 Radio Button을 가져와 Dialog에 놓습니다. 그리고 Dialog 상자 클래스를 추가 해줍니다.
      Dialog 상자 클래스를 생성했다면, 첫 번째 라디오 버튼의 ‘그룹’을 True로 설정해줍니다.
      notion image
      • Ctrl + D를 입력해 첫 번째 라디오 버튼을 확인할 수 있습니다.

      첫 번째 라디오 버튼에 UINT형의 인덱스 변수를 추가해 줍니다.
      • Dialog 상자.cpp 파일에서, 생성자에 인덱스 변수를 0으로 초기화 합니다.
        • CConstantConv ::CConstantConv (CWnd* pParent /*=nullptr*/) : CDialog(IDD_CONSTANT_CONV, pParent) { m_radio_index = 0; // 인덱스 변수 }
      • 밑의 DoDataExchange() 함수에 아래의 코드를 추가해 줍니다.
        • void CConstantConv ::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); // 라디오 버튼의 선택 상태를 m_radio_index 멤버 변수와 연결합니다. DDX_Radio(pDX, 첫번째라디오버튼ID, (int&)m_radio_index); // 이 부분 추가 }

      이제 Doc.cpp에 해당 Dialog 클래스를 include 해서 경우에 따라 처리할 수 있습니다.
      CConstantConv btn; if (btn.DoModal() != IDOK) return; if (btn.m_radio_index == 0) // 첫 번째 라디오 버튼을 선택했을 경우 size = 3; else if (btn.m_radio_index == 1) // 두 번째 라디오 버튼을 선택했을 경우 size = 5; else if (btn.m_radio_index == 2) // 세 번째 라디오 버튼을 선택했을 경우 size = 7; else if (btn.m_radio_index == 3) // 네 번째 라디오 버튼을 선택했을 경우 size = 9;
       
       
 

강의 소스 코드 및 미니 프로젝트 Version 1(spin-off), Version 2 발표 자료

Share article
RSSPowered by inblog