970 likes | 1.2k Views
Ch15_ 객체를 정의하는 클래스. 01_C++ 의 클래스 02_ 클래스 상속 03_ 연산자 함수 04_ 프렌드 함수 및 클래스 05_ 가상 함수 06_ 템플릿 07_ 파일 입출력 08_ 예외 처리. 01_C++ 의 클래스. 객체지향 프로그래밍 객체를 중심으로 프로그래밍 객체 = 상태 ( 데이터 ) + 행위 ( 메서드 ) 클래스 : C++ 에서 지원하는 객체 정의 수단으로 IS-A 관계에 따라 계층 간 관계를 구성하여 특성을 공유함
E N D
01_C++의 클래스 • 02_클래스 상속 • 03_연산자 함수 • 04_프렌드 함수 및 클래스 • 05_가상 함수 • 06_템플릿 • 07_파일 입출력 • 08_예외 처리
01_C++의 클래스 • 객체지향 프로그래밍 • 객체를 중심으로 프로그래밍 • 객체 = 상태(데이터)+ 행위(메서드) • 클래스: C++에서 지원하는 객체 정의수단으로 IS-A 관계에 따라 계층 간 관계를 구성하여 특성을 공유함 • 클래스를 계층적으로 구성하여 기반 클래스(base class or superclass) 파생 클래스(derived class, subclass)에서 상속받아서 사용함
01_C++의 클래스 • C++의 클래스 정의 및 선언 • 클래스에 속한 변수와 함수를 각각 멤버 변수와 멤버 함수라고 함 • 클래스 멤버 • 멤버 변수: 객체의 속성을 정의함 • 멤버 함수: 객체의 행위를 정의함 • 인스턴스 • 클래스를 통해 정의한 데이터 항목(객체) • 생성자와 소멸자 • 생성자: 클래스의 객체를 초기화하는 역할 • 소멸자: 동적으로 할당된 메모리를 해제하는 역할 • 클래스는 생성자 함수를 한 개 이상 포함하며, 소멸자 한 개를 포함함
01_C++의 클래스 • 클래스 정의 • className • 클래스 이름으로, 생략 가능하며 이 클래스의 인스턴스를 선언하기 위한 자료형으로 사용함 • 일반 자료형과 같이 컴파일러가 형 검사(type checking)를 수행함 • memberList • 클래스의 멤버(variables, functions, nested classes, enums, bit fields 등)와 프렌드(friend)를 선언함 • 클래스 안에서는 명시적으로 데이터 초기화를 할 수 없으며, 멤버 변수의 초기화는 생성자 함수에서 수행함 • 비정적(nonstatic) 멤버로 클래스 자신은 포함할 수 없지만, 포인터 또는 레퍼런스를 통해 클래스 자신을 포함하는 것은 가능함 class className { ... memberList; ... };
01_C++의 클래스 • 클래스 멤버의 접근 권한 • 클래스의 멤버(변수 또는 함수)는 키워드 private, protected, public을 사용하여 접근 권한을 지정함 • private: 클래스의 멤버 함수에서만 접근 가능 • 기본 접근 지정자 • protected: 클래스의 멤버 함수와 해당 클래스로부터 파생된 클래스의 멤버 함수에서 접근 가능 • public: 어느 장소에서나 제한 없이 접근 가능 • 선언된 접근 지정자는 새로운 접근 지정자가 나올 때까지 유효함 • 일반적으로 클래스를 정의할 때 데이터 멤버는 private 또는 protected로 선언하여 외부로부터 보호함 • 멤버 함수는 클래스 외부에서 호 출할 필요가 없는 함수는 private로 선언하고, 클래스 외부에서 호출이 필요한 함수는 public으로 선언함 • 생성자와 소멸자는 반드시 public으로 선언함 • 클래스 인스턴스 생성 • 클래스에 의해 실제 메모리를 할당받은 구체화된 것 • 인스턴스와 객체를 혼용하여 쓰기도 함 • 클래스 이름과 명칭을 이용하여 선언함 • 각 인스턴스마다 멤버 변수에 대한 메모리가 별도로 할당되며, 멤버 함수는 한 번만 할당되고 인스턴스 사이에서 공유함
01_C++의 클래스 01 #include <iostream.h> 02 class CA 03 { 04 int m_nData; 05 public: 06 CA() { cout << "CA 클래스 생성자\n"; m_nData = 0; } 07 ~CA() { cout << "CA 클래스 소멸자\n"; } 08 void SetData(int data) { m_nData = data; } 09 int GetData() { return m_nData; } 10 void Display() { cout << m_nData << endl;} 11 }; 12 13 main() 14 { 15 CA a, b; // 생성자 호출 16 // a.m_nData = 10; // 오류: m_nData가 private 멤버임 17 a.SetData(10); 18 a.Display(); // cout << a.GetData() << "\n"; 19 b.Display(); // cout << b.GetData() << "\n"; 20 // 소멸자가 2번 호출됨 21 return 0; 22 }
01_C++의 클래스 • 멤버 변수 • 클래스의 멤버 변수(member variable)는 객체의 속성을 정의한다. 접근 지정자(private, protected, public)에 따른 접근 규칙이 있고, 인스턴스의 메모리 할당에 따라 정적(static) 멤버 변수와 비정적(nonstatic) 멤버 변수로 구분됨 CA 클래스의 private 멤버 변수인 m_nData는 main 함수에서 접근할 수 없다. 따라서 public 멤버 함수 SetData를 호출하여 m_nData의 값을 변경하고, Display 함수를 호출하여 m_nData 값을 출력한다. 15행: CA 클래스의 인스턴스 a, b를 위한 메모리가 할당되고, 생성자 CA( )가 호출되어 멤버 변수 m_nData를 0으로 초기화한다. 17행: SetData 함수를 통해 a 객체의 m_nData 값이 10이 된다. 18행: Display 함수를 통해 a 객체의 m_nData 값 10을 출력한다. 19행: Display 함수룰 통해 b 객체의 m_nData 값 0을 출력한다. 프로그램을 종료하기 전에 객체 a, b의 소멸자가 각각 호출된다. CA 클래스 생성자 CA 클래스 생성자 10 0 CA 클래스 소멸자 CA 클래스 소멸자
01_C++의 클래스 • 정적 멤버 변수(static member variable) • 클래스의 모든 객체(인스턴스)에 대해서 메모리가 단 하나만 할당됨 • 정적 멤버 변수의 메모리는 주어진 클래스의 객체 인스턴스에 존재하지 않음 • 정적 멤버 변수의 초기화는 클래스의 외부에서 수행함 • 정적 멤버 변수도 클래스 멤버 접근 규칙을 따름 • private 접근 정적 멤버 변수는 클래스 멤버 함수와 프렌드 함수에서만 접근할 수 있음 • 정적 멤버 변수는 주어진 클래스의 모든 객체 인스턴스에서 공통적으로 유지해야 할 데이터가 있을 때 사용함 class CA{ static int m_nCount; public: CA() { } ~CA() { } }; int CA::m_nCount = 0; // 정적 멤버 변수 초기화
01_C++의 클래스 • 비정적 멤버 변수(nonstatic member variable) • static 키워드가 없는 일반 멤버 변수 • 클래스 객체의 인스턴스마다 개별적으로 메모리가 할당됨 • 생성자에서 초기화를 수행함 • 멤버 함수 • 클래스의 멤버 함수(member function)는 객체의 행위를 정의함 • 멤버 함수 역시 비정적 멤버 함수와 정적 멤버 함수로 구분 • 기본으로 비정적 멤버 함수며, static 키워드를 통해 정적 멤버 함수로 정의함 • 비정적 멤버 함수는 멤버 선택 연산자(. 또는 ->)로 호출하며, 동일한 클래스의 멤버 함수에서 호출할 경우에는 멤버 선택 연산자를 생략할 수 있음 class CA{ int m_nData; public: CA() { m_nData = 0 ; } // 생성자에서 비정적 멤버 변수 초기화 ~CA() { } };
01_C++의 클래스 • 생성자(constructor) • 클래스와 이름이 동일한 함수로, 값을 반환하지 않으며클래스 객체의 인스턴스가 생성될 때 자동으로 호출됨 • 멤버 변수의 초기화, 메모리 할당 등을 수행함 • 클래스의 인스턴스는 클래스 외부에서 선언하므로 접근 지정자를 반드시 public으로 선언해야 함 • 함수 중복을 이용하여 생성자를 하나 이상 정의할 수 있음 • 소멸자(destructor) • 클래스와 이름이 동일한 함수로, 이름 앞에 틸드(~)를 붙여 생성자와 구별함 • 생성자와 마찬가지로 값을 반환하지 않음 • 정의된 클래스의 인스턴스가 메모리를 해제하고 생명(life time)을 잃을 때 자동으로 호출됨 • 주로 포인터 멤버 변수에 동적으로 할당된 메모리를 회수하는 일을 수행함 • 접근 지정자는 반드시 public으로 선언해야 함 • this 포인터(this pointer) • 비정적 멤버 함수가 실행되는 객체 인스턴스를 가리키는 포인터 • 정적 멤버 함수와 프렌드 함수에는 존재하지 않음 • 일반적으로 this 포인터는 멤버 함수를 호출한 함수에 해당 멤버 함수가 속한 객체의 포인터를 반환하는 데 사용함
01_C++의 클래스 • 정적 멤버 함수(static member function) • 정적 멤버 함수는 주로 정적 멤버 변수를 사용하며, 클래스의 객체를 생성하지 않고 클래스 이름과 범위 연산자( :: )를 사용하여 정적 멤버 함수를 호출할 수 있음 • 정적 멤버 함수는 this 포인터를 사용할 수 없으며, 가상 함수(virtual function)로 선언할 수 없음 • 상수 멤버 함수(constant member function) • const 키워드를 사용하여 정의함 • 상수 멤버 함수 안에서는 멤버 변수의 값을 변경할 수 없음 01 #include <iostream.h> 02 class CA 03 { 04 intm_nData; 05 public: 06 CA() { 07 m_nData = 0; 08 cout << "생성자\n"; 09 } 10 CA(int data) { 11 m_nData = data;
01_C++의 클래스 12 cout << "함수 중복 생성자\n"; 13 } 14 ~CA() { 15 cout << "소멸자\n"; 16 } 17 void Display() { 18 // this 포인터를 사용하여 멤버 변수 접근 19 cout << "m_nData = " << this->m_nData << endl; 20 // cout << "m_nData = " << m_nData << endl; 21 } 22 }; 23 24 main() 25 { 26 CA a; // CA() 호출 27 CA b(10); // CA(int data) 호출 28 a.Display(); 29 b.Display(); 30 // 소멸자가 2번 호출됨 31 return 0; 32 } 생성자 함수 중복 생성자 m_nData = 0
01_C++의 클래스 m_nData = 10 소멸자 소멸자 26행: CA a;는 생성자 CA( )를 호출한다. CA b(10);은 중복 생성자 CA(int data)를 호출한다. 9행: CA::Display 함수에서 CA 클래스의 멤버 m_nData를 접근하기 위해 this->m_nData를 사용한다. 멤버 함수에서 같은 클래스의 멤버를 사용할 때는 this 포인터를 생략할 수 있다(20행). 28-29행: a.Display( )를 호출하면 m_nData = 0이 출력된다. b.Display( )를 호출하면 m_nData = 10이 출력된다. 01 #include <iostream.h> 02 class CA 03 { 04 static int m_nCount; // 정적 멤버 변수 05 int m_nData; // 비정적 멤버 변수 06 public: 07 CA() 08 { 09 cout << "CA 클래스 생성자\n"; 10 m_nCount++; 11 m_nData = 1; 12 } 13 ~CA() { cout << "CA 클래스 소멸자\n"; }
01_C++의 클래스 14 static int GetCount() { return m_nCount; } // 정적 멤버 함수 15 int GetData() const { // 비정적 상수 멤버 함수 16 // m_nData = 2; // 오류 17 return m_nData; 18 } 19 }; 20 21 int CA::m_nCount = 0; // 정적 멤버 변수 초기화 22 main() 23 { 24 CA a, b; 25 cout << "Data of instance a = " << a.GetData() << endl; 26 cout << "# of instances = " << CA::GetCount() << endl; 27 cout << "# of instances = " << a.GetCount() << endl; 28 cout << "# of instances = " << b.GetCount() << endl; 29 // 소멸자가 2번 호출됨 30 return 0; 31 } CA 클래스 생성자 CA 클래스 생성자 Data of instance a = 1 # of instances = 2 # of instances = 2 # of instances = 2
01_C++의 클래스 • 멤버함수의 구현 • 멤버 함수는 클래스 정의 부분 안에서 구현할 수도 있지만, 일반적으로는 클래스 정의 안에서 멤버 함수 원형(prototype)만 정의하고, 클래스 정의 외부에서 멤버 함수를 구현함 • 클래스 정의는 헤더 파일(*.h)에 저장하고, 멤버 함수 구현은 CPP 파일(*.cpp)에 저장함 • 클래스 정의와 구현을 헤더 파일과 CPP 파일에 분리하여 작성할 때는 CPP 파일에서 #include 문을 사용하여 클래스를 정의하는 헤더 파일을 포함함 • CPP 파일에서 각 멤버 함수는 클래스 이름과 범위 연산자를 이용하여 멤버 함수가 속한 클래스를 명시함 CA 클래스 소멸자 CA 클래스 소멸자 4행: 정적 멤버 변수 m_nCount는 인스턴스 a, b에 대해 메모리가 공유된다. 21행: int CA:m_nCount = 0으로 정적 멤버 변수를 초기화한다. 24행: 객체 a에 대응하는 생성자가 호출될 때 m_nCount = 1이 되고, 다시 객체 b에 대응하는 생성자가 호출될 때 m_nCount = 2가 된다. 25-28행: a.GetData( )는 1을 반환하며, CA::GetCount( ), a.GetCount( ), b.GetCount( )는 모두 값 2를 반환한다.
01_C++의 클래스 • 클래스 CA의 정의는 myClass.h 파일, 멤버 함수 구현은 myClass.cpp 파일에 분리하여 작성한다. 또한 main 함수는 ex15_4.cpp 파일에 작성하고, CA 클래스를 사용하기 위하여 myClass.h 파일을 포함한다. • // myClass.h • 01 class CA • 02 { • 03 static int m_nCount; // 정적 멤버 변수 • 04 int m_nData; // 비정적 멤버 변수 • 05 public: • 06 CA(); • 07 ~CA(); • 08 static int GetCount(); • 09 int GetData() const; • }; // myClass.cpp 01 #include <iostream.h> 02 #include "myClass.h" 03 int CA::m_nCount = 0; // 정적 멤버 변수 초기화 04 05 CA::CA()
01_C++의 클래스 06 { 07 cout << "CA 클래스 생성자\n"; 08 m_nCount++; 09 m_nData = 1; 10 } 11 12 CA::~CA() 13 { 14 cout << "CA 클래스 소멸자\n"; 15 } 16 17 int CA::GetCount() 18 { 19 return m_nCount; 20 } 21 22 int CA::GetData() const 23 { 24 // m_nData = 2; // 오류 25 return m_nData; 26 } // ex15_4.cpp 01 #include <iostream.h>
01_C++의 클래스 02 #include "myClass.h" 03 main() 04 { 05 CA a, b; 06 cout << "data of instance a = " << a.GetData() << endl; 07 cout << "# of instances = " << CA::GetCount() << endl; 08 cout << "# of instances = " << a.GetCount() << endl; 09 cout << "# of instances = " << b.GetCount() << endl; 10 return 0; 11 } CA 클래스 생성자 CA 클래스 생성자 data of instance a = 1 # of instances = 2 # of instances = 2 # of instances = 2 CA 클래스 소멸자 CA 클래스 소멸자 [예제 15-4] 프로그램은 클래스 선언을 myClass.h 파일에, 멤버 함수 구현은 myClass.cpp 파일에, main( ) 함수는 ex15_4.cpp에 분리하여 구현한 예제이다. myClass.h 파일에 CA 클래스를 선언한다. myClass.cpp 파일에 CA 클래스의 멤버 함수를 구현한다. ex15_4.cpp에서 main( ) 함수를 작성하고 클래스 CA를 사용하기 위하여 myClass.h 파일을 포함한다.
01_C++의 클래스 • 객체 배열과 포인터 • 클래스 객체의 배열과 포인터를 선언할 수 있음 • 클래스 객체의 배열은 메모리가 연속으로 할당되므로, 배열의 크기만큼 생성자가 호출되고, 블록을 빠져나올 때 소멸자도 같은 횟수만큼 호출됨 • 클래스의 포인터는 해당 클래스 객체를 가리킬 수 있는 포인터임 • 클래스 포인터 선언으로 클래스 객체를 위한 메모리가 할당되는 것이 아니기 때문에 생성자가 호출되지는 않음 • new 연산자로 클래스 객체를 동적으로 할당할 때 생성자가 호출되고, delete 연산자로 메모리를 회수할 때 소멸자가 호출됨 01 #include <iostream.h> 02 class CA 03 { 04 int m_nData; 05 public: 06 CA(int data = 0) // 기본 인수를 이용한 생성자 07 { 08 m_nData = data; 09 cout << "생성자\n"; 10 }
01_C++의 클래스 11 ~CA() { cout << "소멸자\n"; } 12 void Display() { cout << " m_nData = " << m_nData << endl; } 13 }; 14 15 main() 16 { 17 CA a[3]; // 생성자 CA(0)이 3번 호출됨 18 for (int i = 0; i < 3; i++) 19 a[i].Display(); 20 CA b[3] = { CA(1), CA(2), CA(3) }; // 객체 배열 초기화 21 for (i = 0; i < 3; i++) 22 b[i].Display(); 23 // 객체 포인터 24 CA *pt1 = b; // 생성자가 호출되지 않음 25 for (i = 0; i < 3; i++, pt1++) 26 // 포인터를 이용하여 객체 배열 b의 m_nData 출력 27 pt1->Display(); 28 CA *pt2 = new CA(10); // 생성자가 호출됨 29 pt2->Display(); 30 delete pt2; // 소멸자 호출 31 // 객체 배열 a의 소멸자가 3번 호출됨 32 // 객체 배열 b의 소멸자가 3번 호출됨 33 return 0; 34 }
01_C++의 클래스 생성자 생성자 생성자 m_nData = 0 m_nData = 0 m_nData = 0 생성자 생성자 생성자 m_nData = 1 m_nData = 2 m_nData = 3 m_nData = 1 m_nData = 2 m_nData = 3 생성자 m_nData = 10 소멸자 소멸자 소멸자 소멸자 소멸자 소멸자 소멸자 6-10행: 생성자를 기본 인수를 사용하여 정의한다. 객체 선언에서 인수를 전달하지 않으면 클래스 생성자의 인수는 data=0이다. 20행: 클래스 CA 객체의 배열 b를 크기 3으로 선언하고, 생성자를 사용하여 배열 안에 있는 객체를 명시적으로 초기화한다.
01_C++의 클래스 • 클래스 자료형 변환 • 형 변환(type conversion)은 서로 다른 자료형 사이의 초기화, 함수 호출, 함수 반환, 서로 다른 자료형 사이의 연산 등에서 발생함 • C++는 기본적인 자료형(char, int, float, 포인터)의 형 변환 규칙을 제공함 • 클래스는 변환 생성자(conversion constructor)와 변환 함수(conversion function)를 통해 클래스 객체를 다른 자료형으로 변환할 수 있음 • 변환 생성자 • 인수를 한 개만 받는 생성자로, 인수의 자료형을 해당 클래스 형으로 변환할 수 있음 24행: CA 클래스 포인터 pt1을 선언하고 배열 이름 b로 초기화한다(배열 이름은 첫 번째 배열 요소의 주소이다). 새로운 객체가 생성되는 것이 아니기 때문에 생성자가 호출되지 않는다. 28행: new 연산자로 클래스 객체를 동적으로 생성할 때 생성자가 호출된다. 30행: delete로 메모리를 해제하면 소멸자가 호출된다. 프로그램을 실행하면 생성자와 소멸자가 각각 7번씩 호출됨을 알 수 있다.
01_C++의 클래스 01 #include <iostream.h> 02 class CA 03 { 04 int amount; 05 public: 06 CA() { cout << "생성자 호출\n"; } 07 CA(int i) // 정수 변환 생성자 08 { 09 amount = i; 10 cout << "변환 생성자 호출\n"; 11 } 12 ~CA() { cout << "소멸자 호출\n"; } 13 void Display() 14 { 15 cout << "amount = " << amount << "\n"; 16 } 17 }; 18 19 main() 20 { 21 CA a; 22 a = 1; // int에서 CA로 변환 23 // 임시 객체에 대한 소멸자 호출
01_C++의 클래스 • 변환 함수(conversion function) • 키워드 operator를 사용하여 정의하며, 명시적으로 형 변환을 수행함 24 a.Display(); 25 // a에 대한 소멸자 호출 26 return 0; 27 } 01 #include <iostream.h> 02 class CA 03 { 04 double amount; 생성자 호출 변환 생성자 호출 소멸자 호출 amount = 1 소멸자 호출 22행: a = 1은 정수 1을 CA 클래스 객체 a에 저장하려고 한다. 이때 CA(1) 생성자가 호출되어 정수 1을 CA 클래스의 임시 객체를 생성한 다음 객체 a에 저장한다. 프로그램이 종료되기 전에 임시로 생성된 객체와 a에 대한 소멸자가 호출된다.
01_C++의 클래스 05 public: 06 CA(double a = 0.0) // double 변환 생성자 07 { 08 cout << "생성자 호출\n"; 09 amount = a; 10 } 11 operator double() // 변환 함수 12 { 13 cout << "변환 함수 호출\n"; 14 return amount; 15 } 16 ~CA() { cout << "소멸자 호출\n"; } 17 }; 18 19 main() 20 { 21 CA a(1.0); 22 double b = a; // CA 형에서 double 형으로 변환 23 cout << (double) a << "\n"; // CA 형에서 double 형으로 변환 24 cout << "b = " << b << "\n"; 25 return 0; 26 } 생성자 호출 변환 함수 호출
01_C++의 클래스 • 복사 생성자 • 복사 생성자(copy constructor)는 생성자를 중복하여 정의함 • 복사 생성자는 선언문에서 객체 초기화, 인수로 객체를 함수에 전달, 함수 안에서 임시 객체를 반환할 때 사용됨 변환 함수 호출 1 b = 1 소멸자 호출 22행: 객체 a의 변환 함수 double( )이 호출된다. 23행: 객체 a를 명시적으로 형 변환할 때도 변환 함수 double( )이 호출된다. 01 #include <iostream.h> 02 #include <string.h> 03 class CMyString 04 { 05 public: 06 char *m_pStr; 07 public: 08 CMyString(char *str = NULL); // 생성자
01_C++의 클래스 09 CMyString(const CMyString& s); // 복사 생성자 10 ~CMyString(); 11 void Display(); 12 }; 13 14 CMyString::CMyString(char *str) // 생성자 15 { 16 cout << "생성자\n"; 17 if (str) 18 { 19 int len = strlen(str); // 문자열 길이 20 m_pStr = new char[len + 1]; // 메모리 할당 21 strcpy(m_pStr, str); // 문자열 복사 22 } 23 else 24 { 25 m_pStr = NULL; 26 } 27 } 28 29 CMyString::CMyString(const CMyString& s) // 복사 생성자 30 { 31 cout << "복사 생성자\n"; 32 int len = strlen(s.m_pStr); // 문자열 길이 33 m_pStr = new char[len + 1]; // 메모리 할당
01_C++의 클래스 34 strcpy(m_pStr, s.m_pStr); // 문자열 복사 35 } 36 37 CMyString::~CMyString() 38 { 39 cout << "소멸자\n"; 40 delete m_pStr; 41 } 42 43 void CMyString::Display() 44 { 45 if (m_pStr) 46 cout << m_pStr << "\n"; 47 } 48 49 main() 50 { 51 CMyString s1("abcd"); 52 CMyString s2 = s1; // 복사 생성자 호출 53 // s2 = s1; // 복사 생성자가 호출되지 않음 54 s1.Display(); 55 s2.Display(); 56 return 0; 57 }
01_C++의 클래스 52-53행: 객체 s2를 선언하고 s1로 초기화할 때 복사 생성자가 호출된다. s1의 주소가 복사 생성자 CMyString(const CMyString& s)의 인수 s의 주소와 같다. 그러므로 복사 생성자에서 문자열 포인터 s1.m_pStr이 가리키는 문자열의 길이를 계산하여, 현재 객체의 문자열 포인터 s2.m_pStr에 메모리를 할당하고, strcpy 함수로 복사한다. 54행: 실행문인 s2 = s1에서는 복사 생성자가 호출되지 않는다. 생성자 복사 생성자 abcd abcd 소멸자 소멸자
02_클래스 상속 • 파생 클래스 정의 • 클래스 상속(inheritance)을 통해 클래스를 계층적으로 정의할 수 있음 • 기반 클래스에 정의된 일반적인 특징은 상속받아 사용하고, 더 구체적인 특징은 파생 클래스에 추가하여 사용함 • 파생 클래스의 정의 • baseClassList : 기반 클래스(base class)를 명시함. • 기반 클래스 한 개 이상으로부터 파생 가능하며, 각 기반 클래스 이름 앞에는 접근 권한(public, private, protected)과 virtual 키워드를 명시할 수 있음 • public 상속 • public 상속에서는 기반 클래스의 각 멤버가 파생 클래스에서 동일한 접근 권한을 가진 멤버가 됨. 즉, 기반 클래스의 private 멤버는 파생 클래스의 private 멤버가 되고, 기반 클래스의 public 멤버는 파생 클래스의 public 멤버가 되며, 기반 클래스의 protected 멤버는 파생 클래스의 protected 멤버가 됨 class derivedClassName : [virtual,] [public, private, protected] baseClassList { // member_list };
02_클래스 상속 • private 상속 • private 상속에서는 기반 클래스의 각 멤버가 기반 클래스의 접근 권한과 관계없이 파생 클래스의 private 멤버가 됨. 즉, 기반 클래스의 private, public, protected 멤버 모두 파생 클래스의 private 멤버가 됨 • protected 상속 • protected 상속에서는 기반 클래스의 public, protected 멤버가 파생 클래스의 protected 멤버가 됨 • 기반 클래스의 private 멤버는 파생 클래스의 private 멤버가 됨 • 가상 기반 클래스 • 다중 상속을 사용하는 클래스 계층 구조에서 모호성을 피하고, 기억 공간을 절약하기 위해 사용함 • virtual 키워드가 없는 일반적인 비가상(nonvirtual) 객체는 각각 기반 클래스에 정의된 멤버 변수의 복사본(copy)을 포함함 • 반면 파생 클래스에서 baseClassList 부분에 virtual 키워드로 가상 기반 클래스를 명시하면, 기반 클래스의 멤버 변수에 대한 복사본을 포함하지 않음 • 기반 클래스의 멤버 함수 중복 • 기반 클래스에 있는 함수를 필요에 따라 파생 클래스에서 동일한 이름으로 중복(overriding)해서 사용함 • 기반 클래스 초기화 • 파생 클래스에서 기반 클래스의 생성자를 사용하여 기반 클래스를 초기화할 수 있음
02_클래스 상속 • 기반 클래스를 초기화하기 위한 파생 클래스의 생성자 구조 derivedClassName(): baseClassName(seClassConstructorParameters) { // derived class initialization } 01 #include <iostream.h> 02 class CA 03 { 04 protected: 05 int a_data; 06 public: 07 CA(int a = 1) // 기반 클래스 생성자 08 { 09 cout << "CA 생성자\n"; 10 a_data = a; 11 } 12 ~CA() { cout << "CA 소멸자\n"; } 13 }; 14 15 class CB: public CA // 파생 클래스 16 { 17 int b_data;
02_클래스 상속 18 public: 19 CB(int b = 1); // 파생 클래스 생성자 20 ~CB(); 21 void Display(); 22 }; 23 24 CB::~CB() 25 { 26 cout << "CB 소멸자\n"; 27 } 28 29 //CB::CB(int b) 30 CB::CB(int b) : 31 CA(b) // 파생 클래스에서 기반 클래스 초기화 32 { 33 cout << "CB 생성자\n"; 34 b_data = b; 35 } 36 37 void CB::Display() 38 { 39 cout << "a_data = " << a_data << "\n"; 40 cout << "b_data = " << b_data << "\n"; 41 } 42
02_클래스 상속 43 main() 44 { 45 CB a; 46 CB b(3); 47 a.Display(); 48 b.Display(); 49 return 0; 50 } CA 생성자 CB 생성자 CA 생성자 CB 생성자 a_data = 1 b_data = 1 a_data = 3 b_data = 3 CB 소멸자 CA 소멸자 CB 소멸자 CA 소멸자 45행: CB 클래스 객체 a를 선언한다. 이때 CB 클래스의 기반 클래스인 CA의 생성자가 먼저 호출되어 “CA 생성자”문자열을 출력하고, a_data = 1로 초기화한다. 다음으로 CB의 생성자가 호출되어“CB 생성자” 문자열을 출력하고, b_data = 1로 초기화한다. a.Display 함수를 호출하면 a_data = 1, b_data = 1로 출력한다. 프로그램이 종료하기 전에 CB 클래스의 소멸자가 먼저 호출되고, 그 다음 CA 클래스가 호출된다.
02_클래스 상속 • 다중 상속 • 기반 클래스를 콤마로 구분 나열하여 기반 클래스 한 개 이상으로부터 파생 클래스를 정의할 수 있음 • 기반 클래스를 나열하는 순서로 생성자가 호출되고 역순으로 소멸자가 호출됨 46행: CB 클래스의 객체를 생성할 때 이와 같이 생성자에 인수를 주면 기반 클래스의 생성자 CA(3)이 호출되어 a_data = 3으로 초기화하고, 다음으로 파생 클래스의 생성자 CB(3)이 호출되어 b_data = 3으로 초기화한다. b.Display 함수를 호출하면 a_data = 3, b_data = 3으로 출력한다. 01 #include <iostream.h> 02 class CA 03 { 04 int m_a1; 05 protected: 06 int m_a2; 07 public: 08 CA(int a1 = 0, int a2 = 1) 09 { 10 m_a1 = a1; 11 m_a2 = a2; 12 cout << "CA 생성자\n";
02_클래스 상속 13 cout << "m_a1 = " << m_a1 << "\n"; 14 } 15 ~CA() { cout << "CA 소멸자\n"; } 16 }; 17 18 class CB 19 { 20 int m_b1; 21 protected: 22 int m_b2; 23 public: 24 CB(int b1 = 2, int b2 = 3) 25 { 26 m_b1 = b1; 27 m_b2 = b2; 28 cout << "CB 생성자\n"; 29 cout << "m_b1 = " << m_b1 << "\n"; 30 } 31 ~CB() { cout << "CB 소멸자\n"; } 32 }; 33 34 class CD: public CA, public CB // 다중 상속 35 { 36 int m_d1; 37 int m_d2;
02_클래스 상속 38 public: 39 // CD(int d1 = 0, int d2 = 1); 40 CD(int d1 = 0, int d2 = 1) : CA(d1), CB(d1, d2) // 기반 클래스 초기화 41 { 42 m_d1 = d1; 43 m_d2 = d2; 44 cout << "CD 생성자\n"; 45 } 46 ~CD() { cout << "CD 소멸자\n"; } 47 void Display() 48 { 49 // cout << "m_a1 = " << m_a1 << "\n"; // 접근 오류 50 // cout << "m_b1 = " << m_b1 << "\n"; // 접근 오류 51 52 cout << "m_a2 = " << m_a2 << "\n"; 53 cout << "m_b2 = " << m_b2 << "\n"; 54 cout << "m_d1 = " << m_d1 << "\n"; 55 cout << "m_d2 = " << m_d2 << "\n"; 56 } 57 }; 58 59 main() 60 { 61 CD test1; 62 test1.Display();
02_클래스 상속 63 return 0; 64 } CA 생성자 m_a1 = 0 CB 생성자 m_b1 = 0 CD 생성자 m_a2 = 1 m_b2 = 1 m_d1 = 0 m_d2 = 1 CD 소멸자 CB 소멸자 CA 소멸자 61행: CD 클래스 객체 test1을 선언한다. 이때 CD 클래스의 기반 클래스 CA와 CB 클래스의 생성자가 차례로 호출되고, 다음으로 파생 클래스 CD의 생성자가 호출된다. 49-50행: CD::Display 멤버 함수에서 CA 클래스의 private 멤버 변수 m_a1과 CB 클래스의 private 멤버 변수 m_b1은 접근할 수 없다. 프로그램을 실행하면 프로그램을 종료하기 전에 CD, CB, CA 클래스의 소멸자가 차례로 호출됨을 알 수 있다.
02_클래스 상속 • 가상 기반 클래스 • 가상 기반 클래스(virtual base class)는 다중 상속에서 클래스 계층 구조의 모호성을 피하고, 메모리 사용을 줄이는 데 도움이 됨 • 각각의 비가상(nonvirtual) 객체는 기반 클래스에 정의된 데이터 멤버의 복사본(copy)을 포함하는반면, 가상 기반 클래스인 경우 기반 클래스의 데이터 멤버에 대한 복사본을 한 개만 포함함 • 예) [예제 15-11]의 클래스 상속 관계 • 파생 클래스 CB 또는 CC에서 virtual 키워드를 생략하면 CD::a_data 데이터 멤버가 모호(ambiguous)하다는 컴파일 오류가 발생함 • CD::a_data 데이터가 두 경로를 갖게 되어 발생함
02_클래스 상속 01 #include <iostream.h> 02 class CA 03 { 04 protected: 05 inta_data; 06 public: 07 CA(int a = 1) { cout << "CA 생성자\n"; a_data = a; } 08 ~CA() { cout << "CA 소멸자\n"; } 09 }; 10 11 class CB: virtual public CA // 가상 기반 클래스 CA 12 { 13 intb_data; 14 public: 15 CB(int b = 2); 16 ~CB() { cout << "CB 소멸자\n"; } 17 18 }; 19 20 CB::CB(int b) 21 { 22 cout << "CB 생성자\n";
02_클래스 상속 23 b_data = b; 24 } 25 26 class CC: virtual public CA // 가상 기반 클래스 CA 27 { 28 int c_data; 29 public: 30 CC(int c = 3); 31 ~CC() { cout << "CC 소멸자\n"; } 32 }; 33 34 CC::CC(int c) 35 { 36 cout << "CC 생성자\n"; 37 c_data = c; 38 } 39 40 class CD: public CB, public CC // 다중 상속 41 { 42 int d_data; 43 public: 44 CD(int d = 4); 45 void Display(); 46 ~CD() { cout << "CD 소멸자\n"; } 47 };
02_클래스 상속 48 49 CD::CD(int d) 50 { 51 cout << "CD 생성자\n"; 52 d_data = d; 53 } 54 55 void CD::Display() 56 { 57 cout << "a_data = " << a_data << "\n"; 58 cout << "d_data = " << d_data << "\n"; 59 } 60 61 main() 62 { 63 CD obj; 64 obj.Display(); 65 return 0; 66 } CA 생성자 CB 생성자 CC 생성자 CD 생성자 a_data = 1
02_클래스 상속 d_data = 4 CD 소멸자 CC 소멸자 CB 소멸자 CA 소멸자 63행: CD 클래스 객체 obj를 선언한다. CA, CB, CC, CD 생성자가 차례로 호출된다. 64행: obj.Display 함수 호출에 의해 a_data = 1, d_data = 4를 출력한다. 프로그램을 종료하기 전에 CD, CC, CB, CA 순서로 소멸자가 호출된다.
03_연산자 함수 • C++에서는 대부분의 연산자 기능을 연산자 함수(operator function)로 정의하여 연산자의 의미를 중복 정의 가능함 • 연산자 함수는 전역 함수 또는 클래스의 멤버 함수일 수 있음 • 키워드 operator로 연산자 함수와 다른 함수를 구별함 • 예) + 연산자를 연산자 함수로 정의하는 형식 • returnType: 연산자 +의 연산 결과 자료형 • className:연산자 + 왼쪽에 있는 객체의 클래스 이름 • parameters: 연산자 +의 오른쪽을 인수로 받음 • 연산자중복에 사용할 수 없는 연산자 returnType operator+(parameters) // 전역 연산자 함수 returnType className::operator+(parameters) // 클래스 멤버 연산자 함수
03_연산자 함수 • 연산자 함수 사용 예 • 덧셈 연산자 +는 s1.operator+(s2)와 같은 방식으로 호출됨. 즉, + 연산자 함수는 s1 객체에 의해 호출되고, s2 객체는 + 연산자 함수의 인수가 됨 • 연산자 함수 중복에서 지켜야 할 규칙 ① 피연산자(operand)의 수(unary, binary)는 변경할 수 없음 ② 본래의 연산자 우선순위(priority)와 결합 순서(associativity)가 유지됨 ③ 이미 정의되어 있는 자료형에서 연산자의 의미는 변경할 수 없음 CMyString s1("abcd"); CMyString s2("efg"); s3 = s1 + s2; // s3 = s1.operator+(s2); 01 #include <iostream.h> 02 #include <string.h> 03 class CMyString 04 { 05 public: 06 char *m_pStr; 07 public: 08 CMyString(char *str = NULL); // 생성자 09 CMyString(const CMyString& s); // 복사 생성자 10 ~CMyString();
03_연산자 함수 11 CMyString operator+(const CMyString& s); // 연산자 함수 12 CMyString& operator=(const CMyString& s); // 연산자 함수 13 void Display(); 14 }; 15 16 CMyString::CMyString(char *str) // 생성자 17 { 18 cout << "생성자\n"; 19 if (str) 20 { 21 int len = strlen(str); 22 m_pStr = new char[len + 1]; 23 strcpy(m_pStr, str); 24 } 25 else 26 { 27 m_pStr = NULL; 28 } 29 } 30 31 CMyString::CMyString(const CMyString& s) // 복사 생성자 32 { 33 cout << "복사 생성자\n"; 34 int len = strlen(s.m_pStr); 35 m_pStr = new char[len + 1];
03_연산자 함수 36 strcpy(m_pStr, s.m_pStr); 37 } 38 39 CMyString::~CMyString() 40 { 41 cout << "소멸자\n"; 42 delete m_pStr; 43 44 } 45 46 void CMyString::Display() 47 { 48 if (m_pStr) 49 cout << m_pStr << "\n"; 50 } 51 52 CMyStringCMyString::operator+(const CMyString& s) 53 { 54 cout << "+ 연산자 함수 \n"; 55 CMyStringtmpObj; 56 intlen = strlen(m_pStr) + strlen(s.m_pStr); 57 tmpObj.m_pStr = new char[len+1]; 58 59 strcpy(tmpObj.m_pStr, m_pStr); 60 strcat(tmpObj.m_pStr, s.m_pStr);
03_연산자 함수 61 return tmpObj; // 복사 생성자 호출 62 // tmpObj에 대한 소멸자와 복사 생성자에 대한 소멸자 호출 63 } 64 65 CMyString& CMyString::operator=(const CMyString& s) 66 { 67 cout << "= 연산자 함수 \n"; 68 delete m_pStr; 69 int len = strlen(s.m_pStr); 70 m_pStr = new char[len+1]; 71 strcpy(m_pStr, s.m_pStr); 72 return *this; 73 } 74 75 main() 76 { 77 CMyString s1("abcd"); 78 CMyString s2("efg"); 79 CMyString s3, s4, s5; 80 s3 = s1 + s2; // + 연산자 함수, = 연산자 함수 호출 81 s5 = s4 = s1; // = 연산자 함수 2번 호출 82 s1.Display(); // "abcd" 83 s2.Display(); // "efg" 84 s3.Display(); // "abcdefg" 85 s4.Display(); // "abcd"
03_연산자 함수 86 s5.Display(); // "abcd" 87 return 0; 88 } 생성자 생성자 생성자 생성자 생성자 + 연산자 함수 생성자 복사 생성자 소멸자 = 연산자 함수 소멸자 = 연산자 함수 = 연산자 함수 abcd efg abcdefg abcd abcd 소멸자 소멸자 소멸자 소멸자 소멸자