C++ Programming Language
Contents Chapter 01 C++시작하기 Chapter 02 C++기본문법 Chapter 03 객체지향 개념(Object-Oriented Concepts) Chapter 04 클래스(Class) Chapter 05 상속(Inheritance) Chapter 06 연산자 오버로딩(Operator Overloading) Chapter 07 템플리트(Template) Chapter 08 예외처리(Exception Handling) Chapter 09 스트림(Stream) Chapter 10 이름공간(Namespace)
Chapter 01 C++시작하기
C++언어의 소개 C++언어란? C++ 언어 절차적 언어 객체지향 언어 가장 대중적 언어인 C와 객체지향 패러다임의 만남 C언어의 특성인 저수준의 하드웨어 접근과 OOP특성인 고수준의 추상화 제공 C언어의 절차적 프로그래밍 기법과 객체 지향 프로그래밍 기법, 그리고 템플릿을 이용한 일반화 프로그래밍 기법 모두 사용 가능 순수한 의미에서의 객체지향 언어는 아니지만 가장 대중적으로 성공한 객체지향 언어 C++ 언어 절차적 언어 객체지향 언어 절차적 프로그래밍 기법 프로그래밍에서 데이터보다 알고리즘에 더 중점을 둔다. 컴퓨터가 수행할 기능들을 우선적으로 명확히 정의하고, 그 기능을 프로그래밍 언어를 사용하여 구현한다. FORTRAN, BASIC, C 객체지향 프로그래밍 기법 프로그래밍에서 알고리즘보다 데이터 자체에 더 중점을 둔다. 해결해야 할 문제를 언어의 절차적 접근방식에 끼워 맞추지 않고, 해결해야 할 문제의 특성에 맞 게 데이터형 자체를 설계한다. C++
프로그래밍 과정 1. 프로그램 목적 정의 2. 프로그램 설계 3. 소스 코드 작성 4. 컴파일 & 링크 5. 프로그램 실행 6. 프로그램 검사와 디버그 hello.h hello.exe 7. 프로그램 유지와 보수 hello.obj Compiler Linker hello.cpp 목적 파일(*.obj) 소스 파일과 헤더 파일 (*.cpp & *.h) 라이브러리 파일
첫 만남, Hello, World~! /* Hello.cpp */ #include <iostream.h> int main(void) { cout << “Hello, World~!” << endl; return 0; } /* Hello.cpp */ //프로그램에 대한 설명(주석)입니다. 2. include <iosteam.h> //<>내의 파일을 포함합니다. 3. 4. int main(void) //main()함수는 프로그램의 시작과 끝! 5. { //함수의 시작을 알립니다. 6. cout << “Hello, World~!” << endl; //문자열을 화면에 출력합니다. 7. return 0; //프로그램을 종료하고 0을 반환합니다. 8. } //함수의 끝을 알립니다. 프로그램의 실행 결과 : “Hello, World~!” 라는 문자열의 화면 출력
Chapter 02 C++기본문법
Contents 데이터 타입(Data Types) 연산자(Operators) 프로그램 제어문(Control Flow) 함수(Function) 프로젝트 구성(Project Configuration)
데이터 타입(Data Type) 구분 타입 설명 기본형 Bool형 참과 거짓의 두가지 값을 갖는 데이터 타입 정수형 소수점 이하를 가지지 못하는 숫자 타입 실수형 소수점 이하를 가지는 숫자 타입 문자형 문자 하나를 표현하는 데이터 타입 void형 타입이 정해지지 않은 자료형 유도형 열거형 변수가 가질 수 있는 가능한 값의 목록을 나타내는 타입 배열 같은 타입을 갖는 변수들의 집합 구조체 다른 타입을 갖는 변수들의 집합 공용체 다른 타입의 데이터를 하나의 메모리 공간으로 관리하는 타입 포인터 대상체의 주소를 가리키는 타입 함수형 함수의 주소를 가리키는 타입
bool형 특징 참, 거짓 중 어느 한 가지 값만 가질 수 있다. 정수값으로 변환될 수 있다.(true = 1, false = 0) 1 이상의 값은 항상 true가 된다. bool b = 7; // b is true. int i = true; // i is 1. [예제] Bool형 변수를 선언하고 초기화된 값을 출력하는 프로그램 #include <iostream.h> int main(void) { bool a = false; bool b = true; bool p = a + b; bool q = a * b; bool r = a / b; bool x = !a; bool y = a&b; bool z = a|b; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "p = " << p << endl; cout << "q = " << q << endl; cout << "x = " << x << endl; cout << "y = " << y << endl; cout << "z = " << z << endl; return 0; }
정수형 특징 각 정수 타입은 세가지 형태를 띤다 : int, unsigned int, signed int 각 정수 타입은 세가지 크기를 지닌다 : short, int, long int 정수 리터럴은 네 가지 값의 형태를 지닐 수 있다 : decimal, octal, hexadecimal, character int num; int a, b, c; //한 문장에서 3개의 변수 선언 int age = 30; //선언과 동시에 초기화 [예제] 정수형 변수를 선언하고 값을 출력하는 프로그램 #include <iostream.h> int main(void) { int age_10 = 30; int age_8 = 030; int age_16 = 0x30; cout << "I am " << age_10 << " years old." << endl; cout << "I an " << age_8 << " years old." << endl; cout << "I am " << age_16 << " years old." << endl; return 0; }
실수형 특징 소수점 이하를 가지는 실수를 표현하고 저장하는 데이터 타입 실수형 데이터 타입은 세가지 크기를 가진다 : float, double, long double (기본 타입 : double) float a = 10, b = 20; double percent = a/100; double c = 0.3; [예제] 실수형 변수를 선언하고 값을 출력하는 프로그램 #include <iostream.h> int main(void) { float a = 10; double b = a/100; double c = a / 3; cout << "a is " << a << endl; cout << "b is " << b << endl; cout << "c is " << c << endl; return 0; }
문자형 특징 하나의 문자 혹은 하나의 기호를 표현하고 저장하는 데이터 타입 표준 ASCII 코드(0 ~ 127) 사용(정수값 표현 가능) 문자의 표현시 반드시 작은 따옴표(‘ ’)로 묶어준다 출력되지 않는 문자(Escape Sequences)를 사용한다 char grade; char gradeA = ‘A’, gradeB = 66; //선언과 동시에 초기화 [예제] 문자형 변수를 선언하고 값을 출력하는 프로그램 #include <iostream.h> int main(void) { char ch; cout << "한 문자를 입력하십시오 : "; cin >> ch; cout << "당신은 " << ch << "를 입력하셨습니다." << endl; return 0; }
void형 특징 타입이 정해지지 않은 데이터를 위한 자료형 객체를 정의하기 위해 사용될 수 없다. 함수의 리턴 값 혹은 인자가 없다는 의미로 사용될 수 있다. void x; // 오류 발생 : there are no void objects. void f (void); //함수 f는 인자와 리턴 값을 갖지 않는다.
데이터 타입의 크기 특징 데이터 타입의 크기는 시스템이나 운영체제, 컴파일러마다 달라질 수 있다. : <limits.h> 참조 타입간의 크기 순서는 정해져 있다. 1 sizeof(char) sizeof(short) sizeof(int) sizeof(long) sizeof(float) sizeof(double) sizeof(long double) sizeof 연산자를 이용하여 자료형의 크기를 확인할 수 있다. [예제] sizeof연산자를 이용하여 각 데이터 타입의 크기를 알아보는 프로그램 #include <iostream.h> int main(void) { int *ip; long *lp; double *dp; char *cp; cout << endl << "#### Primitive type size ####" << endl << endl; cout << "sizeof(bool) = " << sizeof(bool) << endl; cout << "sizeof(char) = " << sizeof(char) << endl; cout << "sizeof(int) = " << sizeof(int) << endl; cout << "sizeof(unsigned) = " << sizeof(unsigned) << endl; cout << "sizeof(signed) = " << sizeof(signed) << endl; cout << "sizeof(short) = " << sizeof(short) << endl; cout << "sizeof(unsigned short) = " << sizeof(unsigned short) << endl; cout << "sizeof(signed short) = " << sizeof(signed short) << endl;
[예제] sizeof연산자를 이용하여 각 데이터 타입의 크기를 알아보는 프로그램(cont’d) cout << "sizeof(long) = " << sizeof(long) << endl; cout << "sizeof(unsigned long) = " << sizeof(unsigned long) << endl; cout << "sizeof(signed long) = " << sizeof(signed long) << endl; cout << "sizeof(float) = " << sizeof(float) << endl; cout << "sizeof(double) = " << sizeof(double) << endl; cout << "sizeof(long double) = " << sizeof(long double) << endl; cout << endl << "#### Pointer type size ####" << endl << endl; cout << "int pointer size = " << sizeof(ip) << endl; cout << "long pointer size = " << sizeof(lp) << endl; cout << "double pointer size = " << sizeof(dp) << endl; cout << "char pointer size = " << sizeof(cp) << endl; return 0; }
Enum(1) 특징1 사용자가 정수형 값에 이름을 주어 의미를 표현하고자 할 때 사용 정의된 후에는 정수형 타입으로 사용됨 값을 정하지 않는 경우, 디폴트로 0부터 증가하는 값을 가짐 프로그램의 가독성을 향상시키기 위해 사용 enum colors {RED, YELLOW, BLUE, GREEN}; //RED=0,YELLOW=1,BLUE=2,GREEN=3 enum font {ARIAL, CENTURY=10, GOTHIC, TIMESROMAN=20}; // ARIAL=0,CENTURY=10,GOTHIC =11,TIMESROMAN=20
Enum(2) 특징2 값의 범위는 0부터 가장 가까운 2진 자릿수의 최대값까지이다. 정수형 값을 명시적으로 형변환하여 대입할 수 있다. enum e1 {open, close}; //range : 0 ~ 1 enum e2 {a = 3, b = 10} //range : 0 ~ 15 enum flag {a = 1, b = 2, c = 4, d = 8}; //range : 0 ~ 15 flag f1 = 5; //type error flag f2 = flag(5); //OK flag f3 = flag(c|d); //OK [예제] 열거형 변수를 선언하고 각각의 요소의 값을 알아보는 프로그램 #include <iostream.h> int main(void) { enum colors {RED=1, YELLOW=2, BLUE=4, GREEN=8}; colors c1 = 5; //warning or error colors c2 = colors(5); colors c3 = colors(BLUE|GREEN); colors c4 = colors(99); colors c5 = RED; colors c6 = YELLOW; colors c7 = RED + YELLOW; //warning or error cout << "c1 = " << c1 << endl; cout << "c2 = " << c2 << endl; cout << "c3 = " << c3 << endl; cout << "c4 = " << c4 << endl; cout << "c5 = " << c5 << endl; cout << "c6 = " << c6 << endl; cout << "c7 = " << c7 << endl; return 0; }
typedef 특징 어떤 타입에 대한 새로운 이름을 부여하는 것 새로운 타입이 생성되는 것이 아니라 별명이 생긴다고 할 수 있음 typedef char* PCHAR; typedef unsigned long ulong; PCHAR p1; ulong var1; [예제] typedef 를 이용해 자료형에 새로운 별명을 부여하고 변수를 선언한 프로그램 #include <iostream.h> int main(void) { typedef int EmpNo; EmpNo no1 = 1000; EmpNo no2 = no1 + 100; cout << "홍길동의 사원 번호는 " << no1 << " 입니다." << endl; cout << "원균의 사원 번호는 " << no2 << " 입니다." << endl; return 0; }
구조체(1) 특징1 임의 타입의 변수들의 집합체 구성하는 모든 멤버 변수들의 데이터 타입이 같아도 되고, 달라도 된다. 포인터 변수나 배열도 멤버 변수로 사용할 수 있다. 멤버 변수의 접근을 위해서 dot(.)을 사용한다. struct phone { //phone 이라는 이름(태그)의 구조체 선언 char name[20]; //첫번째 구조체 멤버 char phoneno[20]; //두번째 구조체 멤버 } struct phone myphone; cout << myphone.name << endl; cout << myphone.phoneno << endl; [예제] 이름과 전화번호를 가지는 구조체를 선언하고 멤버로 접근하는 프로그램 #include <iostream.h> #include <string.h> int main(void) { struct phone { //phone 이라는 이름(태그)의 구조체 선언 char name[20]; //첫번째 구조체 멤버 char phoneno[20]; //두번째 구조체 멤버 }; struct phone myphone; strcpy(myphone.name, "Hong, Gil-Dong"); strcpy(myphone.phoneno, "02-111-1234"); cout << myphone.name << endl; cout << myphone.phoneno << endl; return 0; }
구조체(2) 특징 struct로 정의된 타입은 typedef를 이용하여 독립적인 타입으로서 사용될 수 있다. 동일한 타입일 경우, 대입과 복사 연산이 가능하다. typedef struct { char name[20]; //첫번째 구조체 멤버 char phoneno[20]; //두번째 구조체 멤버 } Phone; Phone myphone; strcpy(myphone.name, “Hong, Gil-Dong”); strcpy(myphone.phoneno, “02-111-1234”); Phone yourphone = myphone; [예제] 구조체를 선언하고 복사와 대입 연산이 수행됨을 확인하는 프로그램 #include <iostream.h> #include <string.h> int main(void) { typedef struct { char name[20]; //첫번째 구조체 멤버 char phoneno[20]; //두번째 구조체 멤버 } Phone; Phone myphone; strcpy(myphone.name, "Hong, Gil-Dong"); strcpy(myphone.phoneno, "02-111-1234"); Phone yourphone = myphone; //복사 연산 수행 Phone hisphone; hisphone = myphone; //대입 연산 수행 cout << "YourName : " << yourphone.name << endl; cout << "YourPhone : " << yourphone.phoneno << endl; cout << "HisName : " << hisphone.name << endl; cout << "HisPhone : " << hisphone.phoneno << endl; return 0; }
구조체(3) 특징 변수를 정의할 때 초기화 블록을 사용하여 초기화할 수 있다. 중첩 구조체를 선언할 수 있다. (중첩 구조체 : 다른 struct 타입의 변수를 포함하는 구조체) 중첩 초기화를 통해 모두 한꺼번에 초기화할 수 있다. typedef struct { char name[20]; //첫번째 구조체 멤버 char phoneno[20]; //두번째 구조체 멤버 } Phone; Phone myphone = {“Hong, Gil-Dong”, “02-111-1234”}; [예제] 중첩 구조체를 선언하고 한꺼번에 초기화하는 프로그램 #include <iostream.h> #include <string.h> int main(void) { typedef struct { int day; int month; int year; } Date; typedef struct { char name[20]; char phoneno[20]; Date birthday; } Person; Person p1 = {"Hong, Gil-Dong", "02-111-1234", {1,1,2000}}; cout << "이름 : " << p1.name << endl; cout << "전화 : " << p1.phoneno << endl; cout << "생일 : " << p1.birthday.year <<"년 " << p1.birthday.month << "월 " << p1.birthday.day << "일" << endl; return 0; }
구조체(4) 특징 Strict Type Checking : 두 구조체 타입이 같은 타입의 동일한 이름을 가진 변수들을 똑같이 가지고 있더라도, 구조체 이름이 다르다면, 다른 타입으로 인식한다. struct phone{ char name[20]; char phoneno[20]; }; struct person { char name[20]; struct phone ph = {“Hong, Gil-Dong”, “02-111-1234”}; person pe = ph; //type mismatch error
구조체(5) 특징 비트 단위로 타입 내의 변수들을 구성할 수 있다. 메모리의 효율성을 제공한다. 구조체 선언에서 각 비트 필드에 레이블을 붙이고 크기를 지정한다. 각 비트마다 가질 수 있는 값은 0과 1 뿐이다. struct sreg { unsigned int enable : 1; unsigned int page : 3; unsigned int : 1; //unused unsigned int mode : 2; unsigned int access : 1; unsigned int length : 1; }; struct sreg s1; s1.page = 5;
공용체(Union) 특징 struct와 유사하나, 타입 내의 모든 멤버 변수들 중 가장 큰 크기를 가진 변수만큼만 메모리가 할당된다. 타입내의 여러 멤버 변수들 중 한가지로만 사용될 수 있다. union MyType { int num1; double num2; char ch; }; //이 때, mytype 타입의 변수들은 8bytes(=>double의 크기)의 저장공간을 사용하게 된다. [예제] int, double, char 셋 중 하나의 값만 가질 수 있는 공용체를 사용하는 프로그램 #include <iostream.h> union MyType { int num1; double num2; char ch; }; int main(void) { union MyType type1 = {100}; //초기화 union MyType type2 = type1; //같은 타입의 공용체 변수로 초기화 union MyType type3 = {100, 1.2}; //오류발생 - 한 시점에는 하나의 값만. cout << type1.num1 << endl; cout << type2.num1 << endl; type1.num2 = 11.11; type2.ch = 'A'; //num1과 num2, ch의 값이 어떻게 될까?????? cout << "[type1]" << endl; cout << "num1 = " << type1.num1 << ", num2 = " << type1.num2 << ", ch = " << type1.ch << endl; cout << "[type2]" << endl; cout << "num1 = " << type2.num1 << ", num2 = " << type2.num2 << ", ch = " << type2.ch << endl; return 0; }
포인터(1) 특징 메모리의 주소값을 저장하는 변수 타입T*의 변수는 T타입 객체의 주소를 가질 수 있다. 배열이나 함수를 가리키는 포인터를 정의할 수 있다. 간접연산자(*) : 뒤에 포인터 이름이나 주소가 따라올 경우, *는 가리켜진 주소의 메모리 공간에 저장된 값을 반환한다. 주소연산자(&) : 뒤에 변수 이름이 따라올 경우, 그 변수가 위치한 메모리 공간의 주소를 반환한다. int num1 = 20; int num2 = 30; int *pi; pi = &num1; cout << *pi << endl; 0x1000 0x1004 0x1008 0x100c num1 = 20 num2 = 30 pi = 0x1000
function code for int f(char) 포인터(2) int * pi; //pointer to int char ** ppc; //pointer to pointer to char int (*ap)[4]; //pointer to array of 4 ints int (*fp) (char*); //pointer to function taking a char* arguments; //returns an int int * f(char *); //function taking a char* argument; //returns a pointer to int* pi 123 function code for int f(char) ppc ‘a’ ap [예제] 포인터를 사용하여 배열을 다루는 프로그램 #include <iostream.h> int main(void) { int arr[6] = {1,2,3,4,5,6}; int *parr = arr; int i; cout << "arr의 값 = " << arr << endl; cout << "parr의 값 = " << parr << endl; cout << "&arr[0]의 값 = " << &arr[0] << endl; for (i=0; i<6; i++) cout << "arr[" << i << "] = " << arr[i] << "\t"; cout << "*(arr+" << i << ") = " << *(arr+i) << "\t"; cout << "*(parr+" << i << ") = " << *(parr+i) << "\t"; cout << "parr[" << i << "] = " << parr[i] << endl; } return 0; 123 234 456 678
포인터(3)-void* 특징 선언시에 포인터의 타입이 정해지지 않는다. 그 외의 연산은 불가능하다. void f(int * pi) { void* pv = pi; //ok : implicit conversion *pv; //error : can’t dereference void* pv++; //error : can’t dereference void* int* pi2 = (int*) pv; //ok : explicit conversion double* pdl = pv; //error } [예제] void 포인터 테스트 프로그램 #include <iostream.h> int main(void) { char str[] = "Pointer to void test"; void* ps = str; cout << "ps = " << ps << endl; cout << "ps = " << (char*)ps << endl; int* pi1 = ps; //error int* pi2 = (int*)ps; cout << "pi2 = " << static_cast<char*>(pi2) << endl; //error cout << "pi2 = " << static_cast<int*>(pi2) << endl; return 0; }
배열(1) 특징 한가지 데이터 타입으로 이루어진 일련의 요소의 모임 배열은 생성과 동시에 초기화할 수 있다. 콤마로 구분된 값의 목록을 중괄호로 둘러싸는 방법으로 초기화한다 초기화 값의 목록이 배열의 크기보다 모자랄 경우 컴파일러는 나머지 요소의 값을 0으로 설정한다. 초기화 값의 목록이 배열의 크기보다 클 경우 컴파일러는 오류로 처리한다. int v1[4] = { 1, 2, 3, 4 }; int v2[] = { 5, 6, 7 }; int v3[3] = { 1 }; int v4[4] = { 2, 3, 4, 5, 6, 7 }; //error [예제] int형 배열을 선언하고 초기화한 값을 출력하는 프로그램 #include <iostream.h> int main(void) { int i; int arr1[10]; //초기화하지 않음 int arr2[10] = {1,2,3,4,5,6,7,8}; //10개 중 8개의 값을 초기화함 for (i=0; i<10; i++) cout << "arr1[" << i << "] = " << arr1[i] << endl; cout << "-----------------------------------" << endl; cout << "arr2[" << i << "] = " << arr2[i] << endl; return 0; } v1 1 2 3 4 v2 5 6 7 v3
배열(2) 특징 다차원 배열을 구성할 수 있으며, 초기화 또한 가능하다. 2차원 배열 : 배열의 배열(행과 열로 구성되는 표 형태) 3차원 이상의 배열 : 행과 열로 쌓여 있는 표가 여러개 쌓여 있는 형태 int v1[2][3] = { {1,2,3}, {4,5,6} }; int v2[][3] = { {1,2,3}, {4,5,6}, {7,8,9} }; v1 1 2 3 => 논리적 구조 4 5 6 [예제] 3X5배열을 선언하고 모든 요소가 아닌 일부 요소들만 초기화한 후 전체 요소들의 값의 상태를 출력해보는 프로그램 #include <iostream.h> int main(void) { int i, j; int jumsu[3][5] = { {90,80,75}, {92,70,75,85}, {95,88} }; //위와 아래의(중괄호가 한 겹일 경우와 두 겹일 경우의 초기상태를 비교해보자. //int jumsu[3][5] = {90, 80, 75, 92, 70, 75, 85, 95, 88}; for (i=0; i<3; i++){ cout << "[" << i << "번째] 학생의 점수 -----------------" << endl; for (j=0; j<5; j++) cout << "jumsu[" << i << "][" << j << "] = " << jumsu[i][j] << endl; } return 0; v1 1 2 3 4 5 6 => 물리적 구조
Reference(1) 특징 이미 존재하는 객체에 붙이는 별명 항상 초기화되어야만 사용 가능하다. 레퍼런스로 수행하는 연산은 레퍼런스가 참조하는 변수를 가지고 수행하는 연산과 같은 효과를 나타낸다. void f() { int num 1 = 10; int &ref = num1; int num2 = ref; ref = 20; } [예제] 변수를 선언한 후 레퍼런스로 변수의 값을 바꾸는 프로그램 1 #include <iostream.h> int main(void) { int num1 = 10; int &ref = num1; int num2 = ref; ref = 20; cout << "num1 = " << num1 << endl; cout << "ref = " << ref << endl; cout << "num2 = " << num2 << endl; return 0; }
[예제] 변수를 선언한 후 레퍼런스로 변수의 값을 바꾸는 프로그램 2 #include <iostream.h> extern int& r3; int main(void) { int i = 1; int& r1 = i; int& r2; //error cout << "i = " << i << " " << "r1 = " << r1 << endl; r1++; int *p = &r1; *p = 100; cout << "i = " << i << " " << "r1 = " << r1 << " " << "*p = " << *p << endl; return 0; }
Reference(2) 특징 레퍼런스는 인자 전달(argument passing)에서 call-by-reference를 구현한다. void increment(int& aa) {aa++;} void g() { int x = 1; increment(x); } [예제] 레퍼런스와 포인터를 이용한 Call-by-reference 프로그램 #include <iostream.h> void increment(int& aa) {aa++;} void incr(int *p) {(*p)++;} int main(void) { int x = 1; increment(x); cout << "x = " << x << endl; incr(&x); return 0; }
Constant(1) 특징 상수는 선언된 값을 변경할 수 없으며, 항상 초기화하여 사용해야 한다. const int model = 90; const int v[] = {1, 2, 3, 4}; const int x; //error : no initialization void f() { model = 200; //error v[2]++; //error }
Constant(2) 특징 값과 포인터를 상수화할 수 있다. void f(char* p) { char s[] = “Gorm”; const char* pc = s; //constant pc[3] = ‘g’; //error pc = p; //OK } [문제] 다음의 프로그램에서 오류가 나는 곳을 찾아보고 그 이유를 말해보자. #include <iostream.h> int main(void) { char s[] = "tiger"; char p[] = "lion"; const char* pc = s; pc[3] = 'g'; pc = p; char *const cp = s; cp[3] = 'a'; cp = p; const char* const cpc = s; cpc[3] = 'a'; cpc = p; return 0; }
[예제] 문자열 상수 테스트 프로그램 #include <iostream.h> int main(void) { char p[] = "TIGERS"; //p는 내용은 바꿀 수 있지만 주소를 바꿀 수 없는 상수 포인터~!! p[0] = 'L'; //OK!! cout << "p = " << p << endl; char* q = "TIGERS"; //q는 내용은 바꿀 수 없고 주소는 바꿀 수 없는 포인터~!! q[0] = 'L'; //error!! cout << "q = " << q << endl; q = p; //OK!! const char* x = "Animals"; const char* y = "Animals"; if (x == y) cout << "x = y" << endl; else cout << "x != y" << endl; return 0; } [예제] 인자를 상수로 받는 함수의 예제 프로그램 void cf(const char p[]) p[0] = 'L'; //error!! void f(char p[]) p[0] = 'L'; char str[] = "TIGERS"; cf(str); f(str); cout << "string = " << str << endl;
Storage Class(1) 유효범위(Scope) 연결성 저장기간 어떤 변수에 접근할 수 있는 구역 혹은 프로그램의 구역 블록 범위 : 서로 짝이 되는 { 와 } 사이의 영역 내에서 유효한 범위 파일 범위 : 변수가 정의된 지점으로부터 그 파일의 끝까지 유효한 범위 연결성 외부 연결성 : 여러 파일로 구성된 프로그램의 어디서든 사용할 수 있는 것 내부 연결성 : 그 변수가 선언된 파일 안에서만 사용할 수 있는 것 연결성 없음 : 자신이 정의된 블록 내에서만 사용할 수 있는 것 저장기간 정적 저장 기간 : 변수가 프로그램이 실행되는 기간 내내 존재하는 것 자동 저장 기간 : 변수가 정의된 블록에 프로그램 제어권이 들어왔을 때부터 블록을 빠져나갈 때 까지 존재하는 것 다섯 가지 기억부류에 대한 분류표 기억부류 저장 기간 유효범위 연결성 선언위치 자동 블록 없음 블록 내 시작위치 레지스터 (restrict 키워드를 동반) 연결성이 없는 정적 부류 정적 (static 키워드를 동반) 외부 연결성을 갖는 정적 부류 파일 외부 모든 함수의 외부 내부 연결성을 갖는 정적 부류 내부
Storage Class(2) 자동 변수 레지스터 변수 블록범위 정적변수(Static) 보통 특정 블록, 혹은 함수의 시작 부분에 선언되며, 그 블록 혹은 함수 내에서만 사용된다. 레지스터 변수 CPU와 같은 빠른 메모리에 저장되며, 그 성격은 자동 변수와 같다. 블록범위 정적변수(Static) 자동변수와 같이 특정 블록 혹은 함수 내에서만 사용될 수 있으나, 해당 블록 혹은 함수 종료 후에도 존재하게 되며 프로그램이 완전히 종료되어야 소멸된다. void StaticFunction(int val) { static int count = 0; count = count + val; cout << count << endl; } [예제] 블록 범위를 갖는 정적 변수를 선언하여 그 값을 출력해보는 프로그램 #include <iostream.h> void subfunc(int count); int main(void) { int i = 1; while (i <= 10) subfunc(i++); return 0; } void subfunc(int count) int autovar = 1; static int staticvar = 1; cout << count << " : autovar = " << autovar++ << “, staticvar = " << staticvar++ << endl; //함수가 10번 불리므로 10번의 출력이 이루어질 것이다. 이 때 staticvar의 값은???? 이 함수가 처음 호출되었을 때만 초기화되고, 그 이후에 함수가 호출될 때는 이전에 계산된 값을 그냥 사용하게 된다.
Storage Class(3) 외부 연결 정적 변수(전역변수) 내부 연결 정적 변수(전역변수) 함수의 외부에 선언하며 프로그램의 시작부터 종료까지 프로그램 전체(모든 파일)에서 사용한다. 내부 연결 정적 변수(전역변수) 함수의 외부에 선언하며 프로그램의 시작부터 종료까지 살아있지만, 변수의 선언이 포함된 파일 내에서만 사용된다. [예제] 외부연결 정적변수(전역변수)를 선언하고 subfunc이라는 함수에서 참조 사용하는 프로그램 #include <iostream.h> int total; //전역변수 선언 void subfunc(int count); int main(void) { int i = 1; total = 0; while (i <= 10) subfunc(i++); cout << "total = " << total << endl; return 0; } void subfunc(int count) extern int total; //전역변수 total에 대한 참조 선언 - 생략가능하다. for (int i=0; i<count; i++) total += i;
데이터 타입의 변환(형 변환) 암시적 형 변환(자동 형 변환) 명시적 형 변환(강제 형 변환) 연산자를 기준으로 하여 왼쪽의 데이터와 오른쪽의 데이터의 타입이 서로 다른 경우에 연산을 수행하기 위하여 발생하는 형 변환 Type Promotion(형 승격) 동일 종류의 데이터 타입에 대해 타입의 표현 범위가 큰 쪽으로 타입을 변환한다. (예) bool, char, short -> int Standard Type Conversion(표준 형 변환) 기본 데이터 타입간의 가능한 형 변환을 이용하여 찾는다. (예) int <- -> double 명시적 형 변환(강제 형 변환) 프로그래머의 명시적인 선언에 의해서 발생하는 형 변환 (예) float f = (float)100; [예제] 정수형과 실수형 사이의 암시적, 명시적 형변환 결과를 출력하는 프로그램 #include <iostream.h> int main(void) { int a=1, b=10; int d1 = a/b; //암시적 형변환 double d2 = (float)a / (float)b; //명시적 형변환 cout << "value of d1 : " << d1 << endl; cout << "value of d2 : " << d2 << endl; return 0; }
<, <=, >, >=, ==,!= 연산자(Operators) 연산자 종류 연산 기호 특징 범위지정연산자 :: 변수에 대한 접근 범위를 지정하기 위한 연산자 산술 연산자 +, - , *, / , % %(나머지) 연산자는 피연산자로 정수형이 온다. 증감연산자 ++, -- 단항 연산자로 피연산자의 위치에 따라 차이가 있다. 대입연산자 =, -=, +=, … 우항의 연산결과를 좌항의 변수에 대입한다. 비교 연산자 <, <=, >, >=, ==,!= 연산 결과가 참이면 1을 거짓이면 0 논리 연산자 &&, ||, ! 비트 연산자 &, |, ~, ^ 변수 자체의 논리가 아닌 각 비트별 논리 연산 쉬프트 연산 <<, >> 비트 단위의 쉬프트로 기본적으로 빈 곳은 0을 채움 간접 연산자 * 피 연산자의 값이 메모리 주소일 때 유효하다. 주소 연산자 & 해당 변수가 할당된 주소를 확인 형변환 연산자 (변환할 type) 명시적 형변환에 사용된다. 구분 연산자 . , -> 구조체나 공용체 처럼 변수와 멤버사이의 구분자 조건 연산자 조건?처리1:처리2 조건이 참이면 처리1을 거짓이면 처리2를 수행 기타 (),[],{} 괄호 연산자는 우선순위가 가장 높다. 좌변값 연산자의 왼쪽에 위치한 값 우변값 연산자의 오른쪽에 위치한 값 피연산자(operand) 좌변값, 우변값과 같이 연산에 참여하지만 연산자가 아닌 항목들
범위 지정 연산자(Scope Resolution) 특징 접근하려고 하는 변수에 대한 범위를 지정할 때 사용하는 연산자 전역 변수에 대한 접근을 위해 사용하기도 하므로, 전역 변수 연산자라고도 한다. int amount = 123; void main() { ::amount = 100; cout << “::amount = “ << ::amount << endl; } [예제] 지역 변수와 전역 변수의 이름이 같은 경우 각각을 참조하는 프로그램 #include <iostream.h> int amount = 123; int main(void) { int amount = 100; ::amount = 200; cout << "amount = " << amount << endl; cout << "::amount = " << ::amount << endl; return 0; }
산술연산자 특징 덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산을 수행하는 가장 기본적이며 자주 사용하는 연산자 연산자 우선순위 의미 결합방식 () 1 왼쪽 -> 오른쪽 +,-(단항) 2 양수, 음수 오른쪽 -> 왼쪽 *,/,% 3 곱셈, 나눗셈,나머지 +,-(이항) 4 덧셈, 뺄셈
증감연산자 특징 피연산자를 1씩 증가 혹은 감소 시키는 단항 연산자 연산자의 위치에 따라 증감의 시점이 달라지게 되어(전위/후위 연산자), 수식 내에서 사용시에 주의해야 한다 연산자 의미 ++a 값을 1 증가 후 연산 수행 a++ 연산을 수행 후 값을 1 증가 --a 값을 1 감소 후 연산 수행 a-- 연산을 수행 후 값을 1 감소 [예제] 증감 연산자를 많이 사용하면 결과를 예측하기가 힘들어짐을 확인하는 프로그램 #include <iostream.h> int main(void) { int a = 10; cout << a++ << endl; cout << ++a << endl; cout << a-- << endl; cout << --a << endl; int b = a++ + ++a; cout << "b = " << b << endl; int c = 10; c = c++ + c++ + c++ + c++; cout << "c = " << c << endl; return 0; }
대입연산자 특징 우변값을 좌변에 대입하는 연산자 연산자 의미 = Simple assignment *= Multiplication assignment /= Division assignment %= Remainder assignment += Addition assignment -= Subtraction assignment <<= Left-shift assignment >>= Right-shift assignment &= Bitwise-AND assignment |= Bitwise-OR assignment ^= Bitwise-Exclusive-OR assignment
비교연산자(관계연산자) 특징 좌변값과 우변값을 비교하여 누가 큰지, 작은지, 같은지, 같지 않은지 등의 관계를 알려주는 연산자 비교 조건이 만족되면 true(=1), 만족하지 못하면 false(=0)를 반환 연산자 의미 < 좌변값이 우변값보다 작은가? <= 좌변값이 우변값과 같거나 작은가? == 좌변값과 우변값이 같은가? >= 좌변값이 우변값과 같거나 큰가? > 좌변값이 우변값보다 큰가? != 좌변값과 우변값이 같지 않은가? [예제 사용자가 입력한 숫자가 100이라면 1을, 그렇지 않으면 0을 출력하는 프로그램 #include <iostream.h> int main(void) { int num1, num2; num1 = 100; cout << "Enter one number : "; cin >> num2; cout << (num1 == num2) << endl; return 0; }
논리연산자 특징 둘 이상의 비교(관계)식을 결합하는 연산자 연산자 의미 && 논리곱 : AND 피연산자가 모두 true일 경우 true를 반환 || 논리합 : OR 피연산자 중 하나라도 true이면 true를 반환 ! 부정 : NOT 피연산자가 true면 false를, 피연산자가 false면 true를 반환 [예제] 논리 연산을 사용하여 둘 이상의 비교조건들이 결합될 수 있음을 보여주는 프로그램 #include <iostream.h> int main(void) { int num1, num2, num3, num4; num1 = 100; cout << "Enter 1st number : "; cin >> num2; cout << "Enter 2nd number : "; cin >> num3; num4 = num1<num2 && num1<num3; cout << "논리곱 : " << num4 << endl; num4 = num1<num2 || num1<num3; cout << "논리합 : " << num4 << endl; return 0; }
비트 논리 연산자 특징 비트 단위(0 또는 1)로 연산을 할 때 사용하는 연산자 정수 타입 데이터들만 사용할 수 있다 (char타입 포함) 연산자 의미 ~ 비트단위 부정(NOT) 1은 0으로, 0은 1로 반전시켜 반환 & 비트단위 논리곱(AND) 두개의 비트가 모두 1일때 1을 반환 | 비트단위 논리합(OR) 두개의 비트 중 하나라도 1이면 1을 반환 ^ 비트단위 배타적 논리합(Exclusive OR : XOR) 두개의 비트가 서로 다를 경우에 1을 반환 [예제] 각각의 비트 연산 결과를 2진 표현으로 나타내는 프로그램 #include <iostream.h> int main(void) { int a = 15; // 15의 2진표현 : 00000000 00000000 00000000 00001111 int b = 25; // 25의 2진표현 : 00000000 00000000 00000000 00011001 int c; c = a|b; cout << "15와 25의 비트단위 논리합(|) : " << c << endl; c = a&b; cout << "15와 25의 비트단위 논리곱(&) : " << c << endl; c = a^b; cout << "15와 25의 비트단위 배타적 논리합(^) : " << c << endl; c = ~a; cout << "15의 비트단위 부정(~) : " << c << endl; return 0; }
비트 시프트 연산자 특징 비트를 왼쪽에서 오른쪽으로, 혹은 오른쪽에서 왼쪽으로 이동시키는 연산자 연산자 의미 << 왼쪽 시프트 연산자 왼쪽 피연산자의 값의비트들을 오른쪽 피연산자가 나타내는 자리 개수만큼 왼쪽으로 이동 >> 오른쪽 시프트 연산자 왼쪽 피연산자의 값의비트들을 오른쪽 피연산자가 나타내는 자리 개수만큼 오른쪽으로 이동 [예제] 비트단위 시프트 연산자를 이용하여 2의 2제곱,2의 3제곱을 수행하여 출력하는 프로그램 #include <iostream.h> int main(void) { cout << "15 << 2 : " << (15 << 2) << endl; cout << "15 >> 3 : " << (15 >> 3) << endl; return 0; }
변환연산자(캐스트연산자) 특징 수식내에서 변수의 데이터 타입을 강제로 변환시키는 연산자(명시적 형변환) int a; double b = 10.25; a = (int)b; //b의 실수값이 정수형으로 변환된 후 a에 대입된다. [예제] 형변환 연산자를 이용하여 명시적 형변환이 일어남을 확인하는 프로그램 #include <iostream.h> int main(void) { int a = 3; cout << "a/10 = " << a/10 << endl; cout << "((double)a)/10 = " << ((double)a)/10 << endl; return 0; }
조건연산자 특징 if~else문의 효과를 내는 연산자 피연산자를 3개 취하기 때문에 삼항연산자라고도 불린다 int a = 10, b; b = (a > 0) ? 1 : 0; //a의 값이 0보다 크면 1을, 그렇지 않으면 0을 대입 a = (b == 1) ? 1 : 0; //b의 값이 1이면 1을, 그렇지 않으면 0을 대입 [예제] if 제어문 대신 조건연산자를 이용하여 a, b에 값을 대입하는 프로그램 #include <iostream.h> int main(void) { int a, b; cout << "Please, Enter one number : "; cin >> a; b = (a > 0) ? 1 : 0; a = (b == 1) ? 1 : 0; cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
new & delete 특징 new : 동적으로 메모리를 할당하는 연산자 두 연산자 모두 힙(heap)영역의 메모리를 관리하는데 사용된다. void* operator new(size_t); void operator delete(void*); void* operator new[] (size_t); void operator delete[] (void*); int *pi = new int; delete pi; [예제] new와 delete를 이용하여 배열의 크기를 동적으로 조절하는 프로그램 #include <iostream.h> int main(void) { int* intarr; int arrsize, i=0; cout << "몇개의 숫자를 입력하시겠습니까? " ; cin >> arrsize; intarr = new int[arrsize]; cout << "------------------------------------------" << endl; while (i < arrsize) { cout << i << "st : 숫자를 입력하여 주십시오 : "; cin >> intarr[i]; i++; } cout << "당신이 입력하신 숫자는 다음과 같습니다. " << endl; for(i=0; i<arrsize; i++){ cout << intarr[i] << "\t"; if ((i+1)%5==0) cout << endl; cout << endl; delete[] intarr; return 0;
프로그램 제어문(Control Flow) 조건에 따른 분기와 점프 : 주어진 조건에 따라 다음에 오는 명령의 실행 여부를 결정하여 상황에 따라 다르게 동작시키는 문장 if문 switch문 goto문 반복문 : 비슷한 형태의 문장들을 여러 번 반복해서 실행하는 문장 while문 do while문 for문 continue & break
if문 if (condition1) statement1; else if (condition2) statement2; else true false condition1이 true면 statement1을 false면 condition2를 검사한다 condition2가 true면 statement2를 false이면 statement3을 수행한다. [예제] if문을 이용한 사칙연산 프로그램 #include <iostream.h> int main(void) { int num1, num2; char op; cout << "첫번째 숫자를 입력하세요. : "; cin >> num1; cout << "두번째 숫자를 입력하세요. : "; cin >> num2; cout << "연산자를 입력하세요.[+, -, *, /] : "; cin >> op; if (op == '+') cout << num1 << " + " << num2 << " = " << num1+num2 << endl; else if (op == '-') cout << num1 << " - " << num2 << " = " << num1-num2 << endl; else if (op == '*') cout << num1 << " * " << num2 << " = " << num1*num2 << endl; else if (op == '/') cout << num1 << " / " << num2 << " = " << num1/num2 << endl; else cout << "연산자를 잘못입력하셨습니다." << endl; return 0; }
switch문 switch (n) { case value1 : statement1; break; default : statement3; } n =? statement1 statement2 statement3 value1 value2 other value n의 값이 value1이면 statement1을 수행한다. value2이면 statement2을 수행한다. other value면 statement3를 수행한다. [예제] switch문을 이용한 사칙연산 프로그램 #include <iostream.h> int main(void) { int num1, num2; char op; cout << "첫번째 숫자를 입력하세요. : "; cin >> num1; cout << "두번째 숫자를 입력하세요. : "; cin >> num2; cout << "연산자를 입력하세요.[+, -, *, /] : "; cin >> op; switch (op) case '+' : cout << num1 << " + " << num2 << " = " << num1+num2 << endl; break; case '-' : cout << num1 << " - " << num2 << " = " << num1-num2 << endl; case '*' : cout << num1 << " * " << num2 << " = " << num1*num2 << endl; case '/' : cout << num1 << " / " << num2 << " = " << num1/num2 << endl; default : cout << "연산자를 잘못입력하셨습니다." << endl; } return 0;
while문 조건을 먼저 검사하여 true 인 동안만 문장들을 실행하는 진입 조건형 루프 condition false true statement1 true false statement2 statement3 statement4 while (condition) { statement1; statement2; statement3; } statement4; [예제] while문을 이용하여 1부터 10까지의 함을 구하는 프로그램 #include <iostream.h> int main(void) { int num = 1; int sum = 0; while (num <= 10) sum += num++; cout << "1부터 10까지의 합 : " << sum << endl; return 0; } condition이 true인 동안 { 과 } 사이의 블럭내의 statement1, statement2, statement3을 반복하여 수행한다.
do-while문 일단 루프내의 문장들을 한번 실행한 후 조건을 검사하여 false가 될때까지 루프내의 문장들을 반복 실행하는 탈출 조건형 루프 condition statement1 true false statement2 statement3 statement4 do { statement1; statement2; statement3; } while (condition) statement4; [예제] do-while문을 이용하여 1부터 10까지의 함을 구하는 프로그램 #include <iostream.h> int main(void) { int num = 1; int sum = 0; do sum += num++; } while (num <= 10); cout << "1부터 10까지의 합 : " << sum << endl; return 0; } statement1, statement2, statement3를 한번 실행한 후 condition을 검사하여 true인 동안 statement1,2,3을 반복해서 수행한다.
for문 제어변수를 사용하며, 조건식 외에 초기문과 증감문을 기본적으로 포함하여 반복 횟수가 미리 정해지게 되는 반복문 condition 초기문 false true 증감문 statement1 statement2 statement3 for (초기문; 조건식; 증감문) { statement1; statement2; } statement3; [예제] for문을 이용하여 1부터 10까지의 함을 구하는 프로그램 #include <iostream.h> int main(void) { int sum = 0; for (int num=1; num <= 10; num++) sum += num; cout << "1부터 10까지의 합 : " << sum << endl; return 0; } 루프에 진입하기 전에 초기문을 수행하고, 조건식의 true/false를 조사한 후 true인 경우에 statement1, statement2를 수행한다. 한번의 반복루프 수행마다 증감문을 한번씩 수행하고 다시 조건식을 검사하여 다음 번 반복루프의 수행여부를 결정하게 된다.
continue & break continue break 반복하고 있는 루프의 나머지 문장은 건너뛰고, 그 다음의 반복주기를 시작한다. switch문 내에 break문장이 있으면 switch문을 빠져나간다. continue문이 중첩된 반복루프 안에 있을 경우에는 가장 안쪽의 반복루프에만 영향을 미친다. break 자신을 둘러싸고 있는 반복루프를 빠져나와 루프 밖의 문장을 수행시킨다. break문이 중첩된 반복루프 안에 있을 경우에는 가장 안쪽의 반복루프에만 영향을 미친다. [예제] 0부터 시작해서 1씩 증가시켜 가면서 짝수만 더하는 프로그램으로, 그 합이 100보다 크게 되면 덧셈을 멈추고 결과를 출력하는 프로그램 #include <iostream.h> int main(void) { int sum=0; int i; for (i=0; i<100; i++) if (i%2) continue; else if (sum > 100) break; sum += i; } cout << "i : " << i << "\t summary : " << sum << endl; return 0;
함수(Functions) 함수란 함수를 사용하는 이유 함수의 종류 특정 기능을 위해 설계된 프로그램의 독립적 단위이다. 적당한 인자(매개변수)를 주면 그에 따른 출력(반환 값)이 존재한다. 함수를 사용하는 이유 재사용성(reusability) 분할 정복 접근법(divide-and-conquer approach) 모듈화가 이루어져, 읽기와 수정 작업이 쉬워진다. 프로그램의 세부사항보다 프로그램의 전반적인 설계에 집중할 수 있다. 함수의 종류 표준 함수 사용자 정의 함수
함수의 선언과 정의 함수의 선언 함수의 몸체는 없이 함수의 이름과 입력 매개변수(인자), 리턴 데이터 타입으로 구성된다. 함수의 호출 전에 함수의 선언이 먼저 와야 한다. 함수의 정의 함수의 선언과 동시에 기능에 해당하는 프로그램모듈(몸체)을 가진다. [예제] 두 수를 더하는 함수를 만들어 사용하였다. #include <iostream.h> int add(int num1, int num2); //함수의 선언방법1 //int add(int, int); //함수의 선언방법2 int main(void) { int i = 10, j = 20; int sum; sum = add(i, j); cout << "i와 j의 합 : " << sum << endl; return 0; } int add(int num1, int num2) //함수의 정의 return num1 + num2;
매개변수 전달방법(I) 매개변수 매개변수 전달방법(함수의 호출 방식) 함수를 호출하는 외부에서 함수 내부로 넘겨주고 싶은 데이터가 있는 경우 매개변수를 사용한다. 경우에 따라서는 매개변수가 없을 수도 있다. 매개변수 전달방법(함수의 호출 방식) 값에 의한 전달방식(call by value) : 변수의 값을 복사해서 전달하는 방법(C에서의 기본방법) 함수 내부에서 외부로부터 매개변수로 전달되는 변수의 값을 직접 바꿀 수 없다. [예제] call by value에 의하여 값을 바꾸는 swap프로그램 #include <iostream.h> void swap(int, int); int main(void) { int i = 10, j = 20; cout << "before swap : i = " << i << ", j = " << j << endl; swap(i, j); cout << "after swap : i = " << i << ", j = " << j << endl; return 0; } void swap(int n1, int n2) int temp = n1; n1 = n2; n2 = temp; cout << "in swap() : n1 = " << n1 << ", n2 = " << n2 << endl;
매개변수 전달방법(II) 매개변수 전달방법(함수의 호출 방식) – cont’d 참조에 의한 전달방식(call by reference) : 함수 내부에서 외부로부터 매개변수로 전달되는 변수의 값을 직접 바꿀 수 있다. 변수의 메모리 주소를 전달하는 방식(포인터를 이용하여 참조 전달 방식 구현)과 레퍼런스를 이용한 전달방식이 있다. : 리터럴, 상수, 변환을 요청하는 인자는 const &형으로 받아야 한다. [예제] call by reference에 의하여 값을 바꾸는 swap프로그램 #include <iostream.h> void swap(int&, int&); int main(void) { int i = 10, j = 20; cout << "before swap : i = " << i << ", j = " << j << endl; swap(i, j); cout << "after swap : i = " << i << ", j = " << j << endl; return 0; } void swap(int& n1, int& n2) int temp = n1; n1 = n2; n2 = temp; cout << "in swap() : n1 = " << n1 << ", n2 = " << n2 << endl;
반환 값(Value Return) 값의 반환 방식 일반적인 형 변환 규칙을 따른다. 리턴 타입이 참조형이나 포인터형인 경우에는 함수 종료 후에도 지속되는 객체(static변수, 전역 변수 등)를 전달하여야 한다. int* f() { int local = 1; ............ return &local; //bad } int& f() { int local = 1; ............ return local; //bad } [문제] 다양한 반환 값의 형태를 갖는 함수들에 대한 프로그램을 실행시켜보고 오류가 발생하는 부분을 수정하시오. #include <iostream.h> int local_val_return() { int i=1; return i; } int* local_addr_return() { int i=2; return &i; } int& local_ref_return() { int i=3; return i; } int* local_constptr_return() {return 4;} int& local_constref_return() {return 4;} int main(void) { int retval; int* pretval; retval = local_val_return(); cout << "retval = " << retval << endl; pretval = local_addr_return(); cout << "retval = " << pretval << endl; retval = local_ref_return(); pretval = local_constptr_return(); retval = local_constref_return(); return 0; }
재귀함수(Recursive Function) 특징 함수 내에서 자기 자신을 호출하는 함수 재귀를 종료시키는 조건 검사 코드를 넣지 않으면 무한히 자신을 호출하게 된다. long factorial(int n) { long res; for (res=1; n>1; n--) res *= n; return res; } long factorial(int n) { long res; if (n > 0) res = n * factorial(n-1); else res = 1; return res; } [예제] 재귀 함수를 이용하여 사용자가 입력한 수의 factorial(n!)을 구하는 프로그램 #include <iostream.h> long factorial(int n); int main(void) { int n, fact; cout << "숫자를 입력하여주십시오. : "; cin >> n; fact = factorial(n); cout << n << "!의 결과 : " << fact << endl; return 0; } long factorial(int n) long res; if (n > 0) //재귀를 멈출 수 있도록 변수(매개변수)의 값의 변화와 종료 조건이 있어야 한다. res = n * factorial(n-1); else res = 1; return res;
함수 오버로딩(1) 함수 오버로딩이란? 특징 동일한 이름의 함수를 중복해서 여러 개 정의하는 것 함수의 인자 개수와 인자의 데이터 타입에 따라 서로 다른 함수로 인식한다. void print(double); void print(long); void f() { print(1L); //call print(long) print(1.0); //call print(double) } [예제] 타입별 두개의 숫자를 더하는, 같은 이름의 함수들을 사용하는 프로그램 #include <iostream.h> int add(int n1, int n2); double add(double d1, double d2); long add(long l1, long l2); int main(void) { int a=10, b=20; double c=10.1, d=20.2; long e = 10L, f=20L; cout << "a + b = " << add(a, b) << endl; cout << "c + d = " << add(c, d) << endl; cout << "e + f = " << add(e, f) << endl; return 0; } int add(int n1, int n2) { cout << "addition for integer" << endl; return n1 + n2; double add(double d1, double d2) { cout << "addition for double" << endl; return d1 + d2; long add(long l1, long l2) {cout << "addition for long" << endl; return l1 + l2;
함수 오버로딩(2) 실행할 함수 찾는 순서 Exact match : 동일한 타입을 가진 함수를 찾는다. (e.g.) 배열이름 -> 포인터, 함수이름-> 함수에 대한 포인터, T -> const T Type promotion : 동일 종류의 타입에 대해 타입의 표현 범위가 큰 쪽으로 타입을 변환하여 찾는다. (e.g.) bool, char, short -> int, float->double, double->long double Standard type conversion (e.g.) int <-> double, derived* -> base*, T* -> void* int -> unsigned int User-defined type conversion :사용자가 정의한 타입 형 변환 함수를 적용하여 찾는다. Find ellipsis : 생략 인자를 표현한 함수가 있는지를 찾는다. [문제] 다음중 오버로딩된 여러개의 print() 함수 중 각각 어떤 함수가 수행될지 생각해보고 이유를 말해보자. void print(int); void print(const char*); void print(double); void print(long); void print(char); void h(char c, int i, short s, float f) { print(c); //? print(i); //? print(s); //? print(f); //? }
함수 오버로딩(3) 특징 리턴 타입은 함수 오버로딩에 영향을 주지 않는다. 함수 오버로딩은 함수 선택에 있어서 모호성을 유발시킬 수 있다. void f1(char); void f1(long); void f2(char*); void f2(int*); void k(int i) { f1 (i); //ambiguous: f1(char) or f1(long) f2(0); //ambiguous: f2(char*) or f2(int*) } [문제] 다음중 오버로딩된 여러개의 pow() 함수 중 각각 어떤 함수가 수행될지 생각해보고 이유를 말해보자. (오류가 발생할 수도 있다.) int pow(int, int); double pow(double, double); complex pow(double, complex); complex pow(complex, int); complex pow(complex, double); complex pow(complex, complex); void k(complex z) { int i = pow(2,2); double d = pow(2.0, 2.0); complex z2 = pow(2,z); complex z3 = pow(z,2); complex z4 = pow(z,z); double d = pow(2.0, 2); }
[예제] 함수 오버로딩 테스트 프로그램 #include <iostream.h> void print(char* cp) {cout << "CHAR* = " << cp << endl;} void print(int i) {cout << "INT = " << i << endl;} void print(float f) {cout << "FLOAT = " << f << endl;} int pow(int x, int y) { int res = x; for (int i=0; i<y; i++) res *= x; cout << "int pow(int x, int y) 실행 : " << endl;; return res; } int pow(int x, double y) for (int i=0; i<(int)y; i++) res *= x; cout << "int pow(int x, double y) 실행 : " << endl; double pow(double x, double y) double res = x; cout << "double pow(double x, double y) 실행 : " << endl; int main(void) short i = -1; double d = 123.344; print("Overloading Test"); print(1); print(i); print(124.123f); print(d); //error : multiple candidates cout << "\tpow(2,3) = " << pow(2,3) << endl; cout << "\tpow(2.0,3.0) = " << pow(2.0,3.0) << endl; cout << "\tpow(2,3.0) = " << pow(2,3.0) << endl; cout << "pow(2.0,3) = " << pow(2.0,3) << endl; //error : multiple candidates return 0;
기본 매개변수(Default Arguments) 특징 기본 값을 제공함으로써, 함수의 중복 선언을 막고, 함수 정의를 간단하게 할 수 있다. 함수 선언에서 기본 값은 매개변수 순서상 맨 뒤에서부터 명시할 수 있다. void print (int value, int base = 10); void f() { print (31); //결과 : 31 print (31, 10); //결과 : 31 print (31, 16); //결과 : 1f print (31, 2); //결과 : 11111 } [예제] 기본 매개변수를 가지는 print함수의 출력 테스트 프로그램 #include <iostream.h> void print(int first, char* second="NO VALUE", double third=0) { cout << "first = " << first << ", " << "second = " << second << ", " << "third = " << third << endl; } int main(void) print(10); print(10, "VALUE"); print(10, 99.9); //error : skip second parameter print(10, "VALUE", 99.9); return 0;
매개변수가 정해지지 않은 함수 특징 매개변수의 수를 결정할 수 없을 경우, 생략인자(...)를 가진 함수를 정의할 수 있다. void printf (const char* ...); printf (“Hello World~!\n”); printf (“Myname is %s\n“, “Hong”); printf(“%d + %d = %d\n”, 10, 20, 10+20);
함수 포인터(1) 특징 함수는 함수의 이름 뿐만 아니라 함수의 주소를 이용하여 부를 수 있다. 함수 이름이 포인터가 된다. 함수 포인터 타입은 함수의 반환하는 데이터 타입과 매개변수로 결정된다. 함수 포인터를 사용하면 실행 시에 함수를 매개변수로 입력 받을 수 있다. typedef를 이용하여 함수 포인터 타입의 변수를 선언할 수 있다. 함수 호출 시, *(dereferencing)의 사용은 선택적이다. void (*f1) (string) = &error; //OK void (*f2) (string) = error; //OK void g() { f1 (“Vasa”); //OK (*f1) (“Mary Rose”); //OK } [예제] 같은 형의 매개변수와 반환값을 갖는 Add, Display함수를 선언하고 포인터를 이용하여 함수를 실행하는 프로그램 include <iostream.h> void Add(int a, int b); void Display(int a, int b); int main(void) { int num1 = 10, num2 = 20; void (*fct)(int, int); //int형 매개변수 2개를 갖고 반환값이 없는 함수 포인터 선언 fct = Add; fct(num1, num2); fct = Display; return 0; } void Add(int a, int b) cout << "sum = " << a + b << endl; void Display(int a, int b) cout << "a = " << a << ", b = " << b << endl;
함수 포인터(2) 특징 동일한 형식의 함수를 필요에 따라 선택적으로 부르고자 하는 경우 매우 유용하다. 함수 포인터는 자신과 동일한 함수 선언을 가진 함수만을 가리킬 수 있다. typedef void (*PF) (); PF edit_ops[] = { ©, &cut, &paste, &search, &replace}; PF file_ops[] = { &open, &append, &close, &write }; PF* button2 = edit_ops; PF* button3 = file_ops; button2[3] (); //call button’s 3rd function [예제] 함수 포인터를 이용한 정렬 프로그램 #include <iostream.h> #include <string.h> typedef int (*CFT) (const void*, const void*); //비교 함수 포인터 선언 void ssort(void* base, size_t n, size_t sz, CFT cmp) { for (int gap=n/2; 0<gap; gap/=2) for (int i=gap; i<n; i++) for (int j=i-gap; 0<=j; j-=gap) char* b = static_cast<char*> (base); char* pj = b + j * sz; //&base[j] char* pjg = b + (j + gap)*sz; //&base[j+gap] if (cmp(pj, pjg) < 0){ for (int k=0; k<sz; k++) { char temp = pj[k]; pj[k] = pjg[k]; pjg[k] = temp; }
[예제] 함수 포인터를 이용한 정렬 프로그램 – (cont’d) struct User {char* name; char* id; int dept;}; User persons[] = { "Hong, Gil-Dong", "gdhong", 1000, "Jang, Gil-San", "gsjang", 2000, "Lee, Sun-Shin", "sslee", 3000, "Jang, Bo-Go", "bgjang", 4000, "Choi, Young", "ychoi", 5000 }; int cmp1(const void*p, const void*q) { return strcmp(static_cast<const User*>(p)->name, static_cast<const User*>(q)->name); } int cmp2(const void*p, const void*q) User* up = (User*)p; User* uq = (User*)q; if (up->dept < uq->dept) return -1; else if (up->dept > uq->dept) return 1; else return 0; int main(void) cout << "### sort by NAME ###" << endl; ssort(persons,5,sizeof(User),cmp1); for(int i=0; i<5; i++) cout << persons[i].name << ",\t" << persons[i].dept << endl; cout << endl << "### sort by DEPT ###" << endl; ssort(persons,5,sizeof(User),cmp2); for(i=0; i<5; i++)
[예제] 함수 포인터를 이용한 swap 프로그램 #include <iostream.h> #include <string.h> void int_swap(void* x, void* y) {int p=*(int*)x; *(int*)x=*(int*)y; *(int*)y=p;} void int_reset(void* x, void* y) {*(int*)x = *(int*)y = 0;} void char_swap(void* x, void* y) { char *p = new char[strlen((char*)x)+1]; strcpy(p,(char*)x); strcpy((char*)x,(char*)y); strcpy((char*)y,(char*)p); } void (*fp) (void*, void*); int main(void) int i=1, j=2; char p[] = "first"; char q[] = "second"; fp = int_swap; cout << "Before Swap : " << "i = " << i << ", j = " << j << endl; fp(&i, &j); cout << "After Swap : " << "i = " << i << ", j = " << j << endl; cout << "------------------------------------------" << endl; fp = &int_reset;//&는 붙여도 되고 붙이지 않아도 된다. cout << "Before Reset : " << "i = " << i << ", j = " << j << endl; cout << "After Reset : " << "i = " << i << ", j = " << j << endl; fp = char_swap; cout << "Before Swap : " << "p = " << p << ", q = " << q << endl; fp(p, q); cout << "After Swap : " << "p = " << p << ", q = " << q << endl; return 0;
인라인 함수(Inline Function) 특징 인라인 함수는 그것이 사용되는 위치에 코드 형태로 포함된다. 함수 호출 과정이 생략되므로 성능 향상을 기대할 수 있다. 컴파일러에 따라서 성능 향상에 오히려 해가 된다면 무시되기도 한다. inline int fac(int n) { return ( n < 2 ) ? 1 : n*fac(n-1);} int main() { fac(100); fac(200); } [예제] 반지름이 주어졌을 때 원의 넓이를 구하는 함수를 인라인으로 구현한 프로그램 include <iostream.h> inline double circle(double r) { return 3.14*r*r; } int main(void) { double radius = 5; cout << circle(radius) << endl; radius = 10; return 0; }
매크로 함수(Macro) 특징 매크로는 컴파일 전단계(precompile phase)에서 코드로 치환된다. 매크로 이름은 오버로딩 될 수 없다. 재귀 형태로 정의할 수 없다. 매크로 함수는 가능한 인라인 함수로 정의하는 것이 바람직하다. [예제] 반지름이 주어졌을 때 원의 넓이를 구하는 함수를 매크로로 구현한 프로그램 #include <iostream.h> #define CIRCLE1(r) (3.14 * r * r) #define CIRCLE2(r) (3.14 * (r) * (r)) #define CIRCLE2(r,m) (3.14 * (r) * (m)) //error - 오버로딩 불가능 int main(void) { double radius = 5.0; cout << CIRCLE1(radius) << endl; cout << CIRCLE2(radius) << endl; cout << CIRCLE1(radius+5) << endl; cout << CIRCLE2(radius+5) << endl; //CIRCLE1과 CIRCLE2의 값이 다르다. 왜?? return 0; }
프로젝트 구성(Project Configuration) 조건부 컴파일(Conditional compilation) 분할 컴파일(Separate compilation) 헤더파일(Header files)
조건부 컴파일(1) 특징 소스 파일의 일부분을 선택적으로 컴파일 할 수 있도록 지원한다. 조건에 맞추어 특정 정보 또는 코드 블럭을 수행하거나 무시할 것을 컴파일러에게 지시한다. 헤더파일에 대한 중복 정의를 막는다거나 프로그램 디버깅 등의 작업 시에 효과적으로 사용할 수 있다. #if constant-expression #ifdef identifier #ifndef identifier #elif constant-expression #else #endif defined(identifier) defined identifier
조건부 컴파일(2) #if defined(PHONE) phone(); #elif defined(FAX) fax(); #else error(); #endif #if GRADE > 3 #define FLAG 1 #if MAX == 1 #define MAXLEN 200 #else #define MAXLEN 100 #endif #define FLAG 0 #define MAXLEN 50 #if !defined(MYEXAM_H) #define MYEXAM_H struct MyExam{...}; #endif //!defined(MYEXAM_H) [예제] 헤더 파일을 여러번 포함시켜도 중복 정의 되지 않음을 보여주는 프로그램 /* file name : main.c */ #include <iostream.h> #include "phones.h" #include "phones.h“ //같은 헤더 파일을 두번 포함시켰다. int main(void) { struct phone myphone = {"Hong, Gil-Dong", "111-1234", 20}; printphone(myphone); return 0; } /* file name : phones.c */ void printphone(struct phone ph) cout << "name = " << ph.name << ", phoneno = " << ph.phoneno << ", age = " << ph.age << endl; /* file name : phones.h */ #ifndef PHONES_H_ #define PHONES_H_ struct phone { char name[20]; char phoneno[20]; int age; }; void printphone(struct phone); #endif
separately compiled parts 분할 컴파일 특징 한 파일에 모든 코드를 구성하는 것은 불가능 여러 파일에 분할하여 코드를 구성함으로써 프로그램을 이해하기 쉽게하고 재컴파일 시 수정된 파일만을 컴파일함으로써 시간을 절약한다. source file compiler linker [문제] 여러 파일로 구성된 프로그램에서 하나의 소스 파일이 수정되면 수정된 파일만 분할 컴파일 할 수 있 다. 그럼 헤더 파일만 수정된 경우에도 그 파일만 분할 컴파일 될지, 아니면 전체 프로그램의 재 컴파 일이 될지 생각해보자. separately compiled parts
Header File 헤더 파일에 포함될 수 있는 것들 named namespaces namespace SEC { /*...*/} type definitions struct Point {int x, y;}; template declarations template<class T>class Z; template definitions template<class T> class V{/*.......*/}; function declaration extern int strlen(const char*) inline function definitions inline char get() {return *p--;} data declaration extern int gnum; constant definitions const float pi = 3.141592; enumerations enum Color {red, blue, green}; name declarations class Person; Include directives #include “phone.h” macro definitions #define LEVEL 4 conditional compilation directives #ifdef_cplusplus comments /* this file is sample. */ 헤더 파일에 포함될 수 없는 것들 ordinary function definitions char getdata() {return *ptr;} data definitions int num; aggregate definitions short arr[] = {1,2,3}; unnamed namespaces namespace {/* ...... */}
Chapter 03 객체지향 개념
Contents Preliminary : 객체지향 개념(Object-Oriented Concepts) 클래스(Class) 파생 클래스(Derived Class) : Inheritance 객체지향 프로그래밍을 위한 기본 개념과 프로그래밍 기법에 대한 소개는 다음과 같이 구성된다. Preliminary: 객체지향 개념의 기본 요소들과 핵심 개념에 대해 소개한다. Class: 객체와 클래스의 개념을 어떻게 C++에서 정의, 사용하는 지에 대해 소개한다. Derived Class: 클래스간의 상속 개념과 이 개념이 어떻게 프로그래밍에 적용되는 지에 대해 소개한다.
객체지향 개념 강의 상황 추상화된 현실 세계 현실 세계 현실 세계에 대한 추상적 모델링 강의 상황에 대한 추상적 표현 객체지향 모델은 현실 세계를 추상화된 현실 세계로 표현하기 위한 것이다. 정형화되지 않은 현실 세계를 객체를 중심으로 객체 간의 관계로서 표현한 것을 의미한다.
Key Elements of OO Concepts Method Object ENCAPSULATION Message Attribute Class INHERITANCE Instance 객체지향 개념은 다음과 같은 요소들로 이루어진다. 핵심 요소: Object(객체), Attribute(속성), Method(메소드), Message(메시지), Class(클래스), Instance(인스턴스), Class Hierarchy(클래스 계층구조) 핵심 개념: Encapsulation(캡슐화), Inheritance(상속), Polymorphism(다형성) Class Hierarchy POLYMORPHISM
객체(Object) 실세계에 존재하는 모든 것 자체적으로 유일성을 보장할 수 있는 것 상황에 따라 동일한 존재가 객체가 될 수도 되지 않을 수도 있다 독립적으로 존재하여, 스스로 행위와 상태를 변경할 수 있는 것 객체(Object)란? 정의 - 실 세계에 존재하는 모든 것(thing) 특징 - 유일성: 다른 객체와 구별될 수 있어야 한다. 유형/무형, 추상적인 것/구체적인 것, 무생물/생물 등 모든 것들이 객체의 대상이 된다. 독립적으로 존재해야 한다. 스스로 상태를 변경하고, 행위를 할 수 있어야 한다. 상황에 따라 동일한 어떤 것(thing)은 객체가 될 수도 되지 않을 수도 있다. 예 - 모든 사람은 객체가 될 수 있다. 그러나, 객체지향 수업이라는 상황에서는 객체지향 수 업을 수강하는 사람들만이 객체로서 의미가 있다.
속성(Attribute) 객체를 내부 상태를 표현하기 위한 것 한 객체를 다른 객체와 구분해 주기 위한 값 유일성을 보장해 주기 위한 값을 식별자라 한다 대상1 : 홍길동 대상2 : 황진이 깊이 : 천년 깨질 가능성 : 없음 대상1 : 군대간 남 대상2 : 남은 여 깊이 : 몇 년 깨질 가능성 : 상당히 많음 속성(Attribute)이란? 정의 - 어떤 객체를 설명하기 위한 특징. 특징 - 각 속성은 특정 범위의 값을 가진다. 속성들 중에는 객체를 다른 객체와 식별할 수 있는 속성이 있다. 이를 객체의 식별자라 한다. 예 - 그림에서 두 하트 모양의 객체는 네 개의 속성들로 구성되어 있으며, 각 속성은 임의의 값으로 표현되어 있다. 두 객체를 구분하기 위한 식별자는 “대상1”과 “대상2”를 결합 한 속성값이다.
메시지(Message) 객체의 내/외부에서 객체로 전달되는 이벤트 객체간의 전달 신호 (객체가 자신에게 전달하는 신호) 실세계의 동적인 상황을 기술하기 위한 수단 객체간에 서로 노출되며, 받은 메시지를 무시할 수 있다 관계 여 Message : 복부를 향해 주먹을 날림 메시지(Message)란? 정의 - 어떤 객체로부터 발생되는 이벤트(Event) 특징 - 자기자신에게 전달되는 메시지와 다른 객체에 전달되는 메시지로 구분된다. 객체 간의 상호 작용에 대한 규약이다. 실 세계의 동적 상황을 기술할 수 있는 수단이다. 예 - 객체지향 수업에서 강사 객체는 수강생 객체들에게 내용 전달, 질문, 답변메시지 등을 전달한다. Message : 머리를 향해 2단 돌려 차기를 함 군대 남 새로운 남
메소드(Method)(1) 메시지를 처리하기 위해, 객체에서 내부적으로 정의한 행위 수행되는 작업은 외부에서 볼 수 없다 수행 중에 객체의 속성을 바꿀 수 있다. Message : 복부를 향해 주먹을 날림 군대 남 메소드(Method) 란? 정의 - 어떤 메시지를 받아서 처리하기 위해 객체 내부에 정의된 행위 또는 메카니즘 특징 - 수행되는 메소드 작업은 외부 즉 다른 객체에서 볼 수 없다. 수행 중에 객체는 자신의 속성 값들을 수정할 수 있다. 예 - 객체지향 수업에서, 강사 객체가 질문 메시지를 어떤 수강생 객체에게 전달하였을 때, 수강생 객체는 받은 메시지를 처리하기 위해 자신이 가진 메소드(질문 메시지를 처리하 기 위한)를 수행한다. 그리고, 이 메소드가 어떻게 동작하는 지 강사 객체는 알 수 없다. 강사 객체가 알 수 있는 것은 메소드를 수행한 결과에 대해 수강생 객체가 강사 객체에 게 전달하는 메시지뿐이다. Method : 날아오는 위치 계산 후 위치 이동하고, 발차기로 대응함 관계 여 새로운 남
메소드(Method)(2) Object [attribute, method] 수강생1 수강생2 객체지향 프로그래밍 수업 message 강사1 수강생5 이와 같은 몇 가지 객체지향 개념들을 이용하였을 때, 객체지향 프로그래밍 수업은 다음과 같이 표현된다. 객체지향 프로그래밍 수업을 구성하는 중요 객체들인 수강생 객체들과 한 개의 강사 객체가 추 출된다. 각 수강생과 강사 객체는 자신의 속성들을 소유한다. 객체지향 프로그래밍 수업의 동적인 상황에 대한 설명을 위해, 객체 간에 전달되는 메시지들과 이를 처리하기 위한 메소드들이 정의된다. 수강생3 수강생4
클래스(Class) 유사한 특성(행위/구조)을 가지는 일련의 객체들에 대해 그들의 공통적인 구조와 행위를 표현한 개념(Concept) 수강생1 수강생2 수강생 개념 B A 강사1 강사 개념 B A 수강생5 클래스(Class) 란? 정의: 동일한 특징(속성/메시지/메소드)를 가진 일련의 객체들을 표현한 개념 예) 그림에서, 객체지향 프로그래밍 수업은 여러 수강생 객체들과 한 개의 강사 객체로 표현된다. 이 때, 모든 수강생 객체들은 동일한 속성(속성 값은 서로 다를 수 있음), 메시지, 메소드(동 일한 처리 방식)를 가진다. 이러한 객체들은 동일한 개념의 범주로 표현할 수 있다. 이 개념 을 “수강생” 이라 하고, “수강생” 개념에 이 객체들이 가진 속성들/메시지들/메소드들을 정 의하였을 때, 이를 “수강생” 클래스라 한다. 강사 객체의 경우, 한 개만 존재하지만, 역시 한 개념을 표현하므로, 이 객체가 가진 속성들/ 메시지들/메소드들에 기반하여 “강사” 클래스가 정의될 수 있다. 수강생3 수강생4
인스턴스(Instance) 클래스에 표현된 특성에 임의의 값을 가지고 새롭게 만들어진 객체 수강생1 수강생2 B A 강사1 수강생 개념 B A 수강생5 인스턴스(Instance) 란? 정의 - 어떤 클래스의 속성들에 대해 특정 값을 부여 받고, 이 클래스의 메시지들과 메소드들 을 소유한 어떤 객체. 특징 - 인스턴스는 어떤 클래스로에 대해 무수히 많이 생성될 수 있다. 예 - 객체지향 프로그래밍 수업에 n 명의 수강생 객체들이 있다고 하자. 이 때, 새로운 수강 생이 추가되면, 새로운 수강생 객체(새로운 속성 값을 가진)가 정의되고 추가되어야 한 다. 이 때 생성된 객체를 “수강생” 클래스의 “인스턴스”라고 한다. 수강생3 수강생4 신규 수강생
클래스 계층(Class Hierarchy) (1) 유사한 구조와 행위를 가진 클래스들 간의 개념적 포함 관계를 계층적으로 나타낸 것 상위 클래스는 하위 클래스들에 대한 공통적인 구조와 행위를 소유함 하위 클래스는 자신만의 특별한 특징만을 가진다 온전한 객체를 만들기 위해서는 상위 클래스로부터 공통된 특징을 상속받아야 한다 계층적으로 구성되므로, 하위 클래스는 다시 그 하위 클래스의 상위 클래스로서의 역할을 갖는다 체계적인 개념 관리와 새로운 개념의 추가, 기존 개념의 변경이 쉽다 클래스 계층구조(Class Hierarchy) 란? 정의 - 클래스들 간의 개념적 포함 관계. 특징 - 동일한 속성들과 메시지/메소드를 가진 클래스들 간의 개념적 포함 관계를 계층적으로 표현함 어떤 상위 클래스는 하위 클래스들이 가진 공통된 속성, 메시지/메소드를 소유한다. 하위 클래스들은 상위 클래스에 포함되지 않는 자신만의 속성, 메시지/메소드 만을 소 유한다. 클래스 간의 관계는 계층적으로 표현되므로, 어떤 상위 클래스를 가진 하위 클래스 C 가 자신의 하위 클래스를 가지면, C는 자신의 하위 클래스의 상위 클래스가 된다. 객체 생성: 상위 클래스를 가진 어떤 하위 클래스는 새로운 객체를 생성하기 위해 필요 한 모든 속성들, 메시지들, 메소드들을 소유하지 못한다. 따라서, 온전한 객체가 만들어 지기 위해서는 상위 클래스가 소유한 모든 속성들, 메시지들, 메소드들이 함께 생성되고 사용 가능해야 한다. 클래스들을 계층적으로 관리함으로써, 우리는 새로운 개념의 클래스를 추가하거나, 기 존 클래스에 대한 수정, 삭제 작업이 매우 쉽게 된다. 인간의 사고 방식과 동일한 형태로 현실 세계의 개념을 표현
클래스 계층(Class Hierarchy) (2) 수강생1 (회사:P) 수강생2 (학교: A) 수강생 얼굴표정 이해도 B A 강사1 B 직장인 수강생 학생 수강생 A 수강생5 (학교:B) 회사 학교 예) 앞서, 모든 수강생들은 동일한 클래스 “수강생”으로 표현하였다. 하지만, 수강생들이 모두 동일한 속성들만을 가진 것이 아니라, 일부분의 동일한 속성들과 일부분의 서 로 다른 속성들을 소유할 수 있다. 이 때, 동일한 속성들만을 가진 클래스들을 정의해보니, “직장인 수강 생”과 “학생 수강생”으로 표현되었다고 하자. 이 두 클래스가 공통적으로 “얼굴표정”과 “이해도”라는 속성을 가지고 있다면, 이 두 속성을 분리하여 새로운 상위 클래스 “수강생”을 정의하고, 이 클래스의 속 성으로 두 속성을 정의할 수 있다. 그 결과, “직장인 수강생”과 “학생 수강생”이 가진 동일한 속성들을 가진 새로운 상위 개념이 정의되고, 그 외에 각 클래스만이 가진 속성들만 해당 클래스에 유지된다. 속성 들과 마찬가지로, 동일한 메시지나 메소드는 상위 클래스로 전달되며, 하위 클래스들은 자신만이 소유한 속성들을 포함한다. 이렇게 함으로써, 새로운 수강생 개념이 추가될 때, 정의된 “수강생” 클래스 계층 구조를 확장하여 새로 운 클래스를 쉽고 빠르게 정의할 수 있다. 수강생3 (회사:Q) 수강생4
캡슐화(Encapsulation) 객체 내부에 정의된 것은 외부로부터 감춰진다 메시지를 받은 객체는 내부적으로 메소드를 이용하여 자신의 상태를 변경하지만, 외부에는 수행된 결과를 노출시키지 않을 수 있다 객체 내부의 변경은 다른 객체에 영향을 끼지지 않는다 정보 은닉을 보장해 준다 ME1 : 강의속도 속성 변경 강의 속도 : 빠름/중간/느림 M1 : 강의 내용 캡슐화(Encapsulation) 란? 객체는 메시지를 통해서만 상호 교류하며, 객체 내의 모든 것은 감춰진다. 캡슐화를 통해 객체 내의 정보를 외부로부터 감출 수 있다. 따라서, 객체 내의 정보 변경은 다른 객체에 영향을 끼치지 않는다. 예) 그림에서, 강사 객체는 수강생 객체에 영향을 끼치지 않으면서, 자신의 메소드(ME1)을 자유롭 게 변경할 수 있다. M2: 이해도 이미지 강사1 ME1’ : 강의속도와 예제 난이도 속성 변경 강의 속도 : 빠름/느림 예제 난이도 : 높음/낮음 수강생1
다형성(Polymorphism) 외부로부터 전달된 메시지에 대해, 관련 객체들이 서로 다르게 반응하는 것 강사 수강생 M : 머릿속에 그냥 저장한다 직장인 수강생 학생 수강생 다형성(Polymorphism) 이란? 어떤 개념에 속한 객체들은 동일한 메시지를 받더라도 서로 다르게 반응할 수 있다. 예) 그림에서, 두 종류의 수강생 객체들이 있으며, 크게는 “수강생”의 개념에 포함된다. 모든 수강생 객체들은 M1 메시지를 받는다. 하지만, “직작인” 과 “학생” 수강생은 M1에 대해 서로 다른 메소드를 수행할 수 있다. 객체지향 개념에서는 이렇게 동일한 메시지가 주어졌을 때, 한 개념에 속한 객체들이 서로 다른 메소드를 수행할 수 있도록 보장해야 한다는 메시지 다형성 을 보장해야 한다. M : 학교에서 배운 내용과 결합하여 가공 저장한다
상속(Inheritance) (1) 상위 클래스의 특성이 하위 클래스로 전달되는 행위 온전한 하위 클래스의 표현을 보장하기 위한 메카니즘이다 클래스 계층 구조의 형태에 따라 상속 형태가 달라질 수 있다. 수강생 상속(Inheritance) 이란? 클래스 계층구조에서 상위 클래스의 특성(속성/메소드)을 하위 클래스로 전달하는 행위 상위 클래스는 하위 클래스들의 공통된 특성들을 소유하고 있다. 따라서, 하위 클래스들은 자신 이 원래 소유해야 하는 모든 특성들을 가지고 있지 않다. 하위 클래스에 속하는 어떤 객체를 생성하기 위해서는 상위 클래스의 모든 특성이 하위 클래스 로 전달되도록 보장해야 한다. 직장인 수강생 학생 수강생
상속(Inheritance) (2) 단일 상속 하위 클래스는 한 개의 상위 클래스로부터 개념을 상속 받는다 계층 구조가 깊어질수록 개념이 중복되는 경우가 발생한다 - 식별자 - 생산자 - 매체 - 원동력 - 마력 - 연료 용량 - 연료 종류 - 이륙 거리 - 최대 고도 탈것 모터가 있는 비행기 모터가 없는 상속은 단일 상속과 다중 상속으로 구분된다. 단일 상속은 어떤 클래스의 바로 위 수준의 상위 클래스가 한 개만 있는 경우를 말한다. 반면, 다 중 상속은 어떤 클래스의 바로 위 상위 클래스가 두 개 이상인 경우를 말한다. 단일 상속 모델은 클래스 계층구조의 깊이가 길어질수록 클래스들에 중복된 특성들이 나타날 수 있는 단점이 있다. 하지만, 다중 상속에 비해 단순한 표현이 가능하다. 예) 그림에서, 모터가 있는 비행기와 모터가 없는 비행기는 중복된 비행기의 속성들을 가진다.
상속(Inheritance) (3) 다중 상속 - 하위 클래스는 여러 상위 클래스로부터 개념을 상속 받는다 계층 구조가 깊어져도 개념의 중복은 적으나, 상속된 개념 간의 충돌이 발생할 수 있다. - 식별자 - 생산자 - 매체 탈것 비행기 - 마력 - 연료 용량 - 연료 종류 모터가 있는 비행기 탈것 모터가 없는 비행기 탈것 - 이륙 거리 - 최대 고도 - 원동력 상속은 단일 상속과 다중 상속으로 구분된다. (cont’d) 다중 상속 모델은 클래스들 간에 중복되는 부분을 최소화할 수 있다는 장점이 있다. 하지만, 이 름 충돌(name conflict)가 발생되며, 이를 해결하기 위해 복잡한 처리 작업이 필요하며, 단일 상 속에 비해 복잡한 형태로 계층구조가 표현된다는 단점이 있다. 예) 다음 페이지의 다중 상속 그림에서, “비행기” 클래스가 추가로 정의되어 있으며 모터가 있는 비행기와 모터가 없는 비행기는 두 개의 상위 클래스를 상속받는다. 따라서 중복되는 부분을 제 거할 수 있다. - 이륙 거리 - 최대 고도
Problems of Structural Analysis & Design Function : A v1 v5 Function : B Function : D v4 v3 Function : E Function : C v2 구조적 분석/설계 기반 프로그래밍의 문제점. C와 같이 함수 중심으로 프로그램을 분할하여 프로그램을 구성하는 방식은 “높은 결합도”와 “낮 은 응집도”를 가진 프로그램을 생산한다. “높은 결합도”를 가진 프로그램은 프로그램의 일부가 수정되면 프로그램의 많은 부분을 수정해야 하며, “낮은 응집도”를 가진 프로그램은 데이터가 오용될 수 있는 가능성이 높아서 프로그램의 논리 에러율을 높이게 된다. 그림에서, 함수 F의 변경은 다른 함수 전체를 검토/수정하도록 만들 수 있다.(높은 결합도). 한편, 데이터 v2는 함수 F와 C에서만 사용되어야 하지만, 모든 함수들이 사용할 수 있는 전역 변수로 정의되어 있어 자유로운 접근이 가능하다. 따라서 v2의 오용에 따른 논리 에러를 발생시킬 확률 이 높아진다.(낮은 응집도). Function : F [cohesion & coupling]
Object-Oriented Methodology (1) OOA(Object-Oriented Analysis) 도메인 전문가, 사용자, 관리자, 개발자들 간에 만들고자 하는 프로그램에 대한 공통적인 생각을 끄집어 내는 과정 OOD(Object-Oriented Design) 프로그램에 대한 공통적인 생각을 어떻게 구현할 것인지를 계획하는 과정 OOP(Object-Oriented Programming) 설계된 결과에 따라 객체지향 언어를 이용하여 프로그램을 완성하는 과정 객체지향 기반의 방법론 어떤 문제를 객체지향 개념을 이용하여 분석, 설계, 구현하는 소프트웨어 개발 방식을 말한다. 객체지향 방식으로 프로그램을 구성함으로써, 우리는 다음 그림과 같은 형태의 프로그램을 만들 수 있다. 앞 페이지의 그림에서, 구조적 프로그래밍 방식에서 문제가 되었던 데이터와 함수간의 응집도는 객체 개념을 이용하여 높일 수 있으며, 함수 간의 결합도는 메시지를 이용하여 줄일 수 있다.
Object-Oriented Methodology (2) v1 v5 v3 v4 msg : C, F v2 Function : A Function : F Function : B Function : C Function : D Function : E
Chapter 04 클래스
Contents 클래스 구조(Class Structure) 멤버/멤버함수(Member/Member Function) 객체 생성과 소멸(Object Creation & Destruction) 내포 클래스(Nested Class) 클래스 객체(Class Object) 프렌드(Friend) 구조체/공용체(Structure/Union)
클래스 구조(1) class class_name { public: type member_name; return_type member_function_name(argument_list); private: }; 클래스를 정의하기 위한 기본 구조는 다음과 같다. 클래스를 정의하기 위해, 키워드 class와 클래스 이름이 지정되어야 한다. 새로운 클래스의 내용은 { 와 }; 내에 포함되어야 한다. 클래스로부터 생성되는 모든 객체들은 캡슐화의 특성을 가지므로, 클래스 내에 정의된 속성과 메소드들은 외부로부터 감춰질 수 있도록 보장해야 한다. 이를 위해, class 내의 영역은 private 과 public 이라는 두 부분으로 구분된다. private : 외부로부터 접근할 수 없는 영역 public : 외부에서도 접근할 수 있는 영역 만약 private 와 public 을 명시하지 않으면, 자동으로 private 으로 간주된다. 클래스에 정의되는 모든 속성은 어떤 값을 표현하므로, C++에서는 변수로 매핑되며, 모든 메소 드는 어떤 기능을 수행하므로 함수로 매핑된다. 또한, 클래스는 C++에서 새로운 타입으로서의 의미를 가진다.(클래스 ≡ 타입) 예) class A {}; //라고 정의하였을 때, A는 새로운 타입으로서, A obj; //와 같이 바로 타입으로 사용된다.
클래스 구조 (2) class Date { int day, month, year; public: Date() {} Date(int d =1, int m=1, int y=1999); void set(int d, int m, int y) { day = d; month =m; year = y; } bool cmp(const Date&); void print(); }; Date::Date(int d =1, int m=1, int y=1999) : day(d), month(m), year(y) {} bool Date::cmp(const Date& d) { return (year == d.year && month == d.month && day == d.day ) ? true : false; } void Date::print() { cout << “( ” << day << “ , ” << month << “ , ” << year << “ ) ” << endl; } 이 클래스는 세 개의 속성 day, month, year 로 표현된다. 그리고 몇 개의 메소드들, set, cmp 등을 소유 한다. 정의되는 메소드들의 코드는 클래스 내에 정의되거나 클래스 밖에 정의될 수 있다. 클래스 밖에 정의되 는 경우에는 메소드이름 앞에 클래스 이름과 영역 연산자를 붙여 주어야 한다. 예) Date::print Date 클래스의 메소드 내에서는 Data 클래스의 속성들을 자유롭게 접근, 수정할 수 있다. 예) Date::print() { cout << … << day << … } 에서 day 는 Date 클래스의 속성이다.
Member(1) 속성(Attribute) 클래스의 구조를 임의의 객체를 멤버로 표현한다 정의 가능하다 가능한 외부로 속성(attribute) ≡ 멤버(member) 멤버는 임의의 타입의 변수로서 표현된다. 가능한 외부로 노출시키지 않는다. 따라서, 대개의 경우 private 영역에 정의된다. 가능한 외부로 노출시키지 않는다
Member(2) object 31 12 2001 this Data* const this; #include <iostream.h> class Date { int day, month, year; // private members public: void set(int day, int month, int year) this ->day = day; Date::month =month; this->year = year; } }; object 31 12 2001 this 객체 자신을 가리키는 포인터 특징 : 자신만을 가리킬 수 있다. 용도 : 1. 이름 충돌을 방지. 2. 자기자신을 외부로 전달 위 클래스 정의는 세 개의 멤버들, day, month, year, 를 정의하고 있다. 각 멤버는 int 타입이며, 멤버 함 수들에서 사용된다. 모든 클래스는 명시적으로 정의하는 멤버들 외에 한 개의 암시적 멤버를 자동으로 정의한다. 이를 this 라 한다. this 는 자기자신을 가리키는 포인터로서, Date 클래스에 대해 Date* const this; 와 같이 정의된다. 모든 객체들은 자신의 this 멤버를 가진다고 할 수 있다. 하지만, this 는 실제로 존재하는 멤버, 즉 메모리를 따로 가진 멤버가 아니라, 클래스 내의 멤버 함수 내 에서 객체 자신을 참조하기 위한 용도로 사용하기 위한 멤버이다. 따라서, 가상의(메모리 할당이 없는) 멤버이다. Data* const this; 멤버의 범위를 정해줌으로서, 이름충돌을 막아준다.
[예제] Date 클래스와 이를 이용한 간단한 테스트 프로그램 #include <iostream.h> class Date { int d, m, y; public : void init(int, int, int); void add_year(int n); void add_month(int n); void add_day(int n); void print(); }; void Date::init(int d, int m, int y) { Date::d = d; Date::m = m; Date::y = y; } void Date::add_year(int n) { this->y += n; } void Date::add_month(int n) { this->m += n; } void Date::add_day(int n) { this->d += n; } void Date::print() { cout << "Day : " << d << "/" << m << "/" << y << endl; } Date my_birthday; main() Date today; today.init(9,11,1998); my_birthday.init(28,9,1989); Date tomorrow = today; tomorrow.add_day(1); tomorrow.print(); } 클래스 Date 내에는 세 개의 멤버들이 정의되어 있으며, 모두 private 영역에 위치한다. 클래스 Date 는 5개의 메소드들을 가지며, 모두 클래스 밖에 정의되어 있다. 따라서, 모두 Date:: 를 함수 앞에 표현한다. 이렇게 생성된 Date 클래스는 프로그램에서 타입으로 사용된다. 예) Date my_birthday; … Date today; … Date tomorrow; 각 Date 객체들에 대해 정의된 멤버 함수들을 적용하여 객체들의 값을 참조/수정할 수 있다. 예) today.init(9,11,1998); … tomorrow.add_day(1); …
Member Function (1) 메시지와 메소드에 대한 구체화된 표현 객체 내/외부에 대한 서비스 제공 구현 내용은 외부로부터 감춰짐 메소드(method) ≡ 멤버 함수(member function) 멤버 함수는 외부 또는 내부로부터 전달된 메시지를 처리하는 메소드를 의미한다. 외부로부터 전달되는 메시지를 처리하기 위한 메소드는 public 영역에, 내부에서 전달되는 메시지를 처리하기 위한 메소드는 private 영역에 정의된다. 멤버 함수 내에서는 클래스가 소유한 멤버들을 자유롭게 접근, 수정할 수 있다.
Member Function (2) 멤버 함수의 종류 인라인 멤버 함수 일반 멤버 함수 상수 멤버 함수 컴파일 후 생성된 기계 코드가 다른 함수에 내포되는 함수 프로그램 크기는 커지지만, 속도는 증가 짧은 라인 수를 가진 함수만이 인라인으로 정의함 클래스 내부에 정의되었거나, inline 키워드가 함수 선언부에 붙어있는 함수 일반 멤버 함수 분기를 통해서 수행될 수 있는 함수. 프로그램의 크기는 작지만, 잦은 호출은 속도를 저하시킬 수 있음 상수 멤버 함수 함수의 몸체에서 멤버 값을 변경할 수 없는 함수. 함수가 멤버 값을 수정해서는 안될 때 유용하게 사용할 수 있음. 함수 선언부 끝에 const 가 붙어 있는 함수 (예: void print() const) 상수 객체는 상수 멤버 함수만을 수행시킬 수 있음. (예 : const Date obj; 와 같이 객체가 정의되었을 때, obj 에 대해서는 상수 멤버 함수만을 사용할 수 있다.)
Member Function (3) #include <iostream.h> class Date { int day, month, year; public: void set(int d, int m, int y) // inline member function { day = d; month =m; year = y; } bool cmp(const Date&) const; void print() const; // general constant member function }; inline bool Date::cmp(const Date& d) const { /* … */ } // inline constant member function void Date::print() const { /* … */ } 위 코드에서, 멤버함수 set 과 cmp 는 인라인 멤버 함수이며, print 는 일반 멤버 함수이다. 멤버 함수 cmp와 print는 상수 멤버 함수이다. 따라서 멤버들을 참조만 가능하며, 값을 수정할 수는 없 다.
[예제] 멤버 함수에 대한 간단한 테스트 프로그램 // Class : Member Function Practice // #include <iostream.h> class Date { public : // public member function Date(int dd=1, int mm=1, int yy=1990) : d(dd), m(mm), y(yy) {} void print(); private: // private member function int day() const { return d; } //inline & constant member function int month() const {return m; } //inline & constant member function int year() const; private: int d, m, y; }; inline int Date::year() const { return y; } //inline & constant member function void Date::print() // General member function cout << day() << '/' << month() << '/' << year() << endl; } main() Date ddate(9,11,1998); ddate.print(); 클래스 Date 는 인라인, 일반, 상수 멤버함수들을 정의하고 있다. 예) void print(); int day() const { return d; } 이 클래스에서 print() 는 일반 멤버 함수로 정의되었으며, day() 라는 함수는 상수 멤버 함수로 멤버 값 을 참조만 하는 함수이다.
객체 생성 (1) Date void f() { Date obj; create createMethod() {…} } obj 새로운 인스턴스를 만들어 내는 것 생성자 (Constructor) 인스턴스를 만들어내는 메소드 클래스가 소유하는 메소드 객체를 초기화함 객체 생성(object creation) 어떤 클래스로부터 새로운 인스턴스를 만드는 것. 어떤 타입에 대응되는 새로운 변수를 만드는 것. 객체를 생성하기 위해, 클래스는 생성자(constructor)라는 함수를 소유함. 생성자는 클래스가 소유하는 메소드로서 객체가 부를 수 없다. 객체를 생성하기 위해, 클래스는 멤버의 생성자로 멤버 객체를 생성하고, 자신의 생성자 몸체를 수행하여 객체를 초기화한다. 세 종류의 생성자가 있다. 기본 생성자(default constructor) 복사 생성자(copy constructor) 인자를 받는 생성자 객체 생성 과정 멤버의 생성자로 멤버 객체 생성 생성자의 몸체를 수행하여 객체 초기화
객체 생성 (2) 디폴트 생성자 ( Default Constructor ) Date void f() { Date obj; } create Date() { /* 2001년 1월1일로 항상 초기화 */ } obj 2001 1 인자를 받지 않으며, 객체를 위한 공간만을 할당해 주는 생성자. 클래스에 생성자가 하나도 없는 경우, 자동으로 만들어진다 객체 생성시 특별한 값을 주고 싶은 경우, 해당 몸체를 가진 함수를 정의할 수 있다 기본 생성자(default constructor) 클래스 이름과 동일한 함수 이름을 가지며, 인자는 없는 함수. 예) Date() { /* … */ } 클래스에 생성자가 없는 경우, 자동으로 생성됨. 이 때, 함수는 어떠한 일도 수행하지 않음. 예) Date() { } 객체가 생성될 때 특정한 값으로 초기화 하고 싶은 경우, 기본 생성자의 몸체를 정의한다. 예) Date() { day = 1; month = 1, year = 2001; }
객체 생성 (3) 인자를 받는 생성자 create Date void f() { Date obj(2005,4,5); } Date(int y, int m, int d) { /* 인자값으로 날짜 설정 */ } obj 2005 4 5 객체 공간과 함께, 인자로 받은 값을 이용하여 객체를 초기화하는 생성자 인자의 종류와 개수에 따라 여러 생성자를 생성할 수 있다. 인자를 받는 생성자 클래스 이름과 동일한 함수 이름을 가지며, 임의의 인자를 받는 함수. 예) Date(int d, int m, int y) { /* … */ } Date(const char* str) { /* … */ } Date(double y) { /* … */ } 다양한 형태의 인자 값들을 통해 객체를 초기화하고 싶은 경우 사용. 인자의 종류와 개수에 따라 무수히 많은 생성자들을 정의할 수 있다.
객체 생성 (4) 복사 생성자 Date void f() { Date obj(2005,4,5); Date today = obj; } Date(const Date& arg) { /* arg 객체를 원하는 형태로 복사 */ } create today 2005 4 5 동일한 타입의 다른 객체를 인자로 받아, 생성되는 객체를 초기화하는 생성자 따로 생성하지 않으면, “디폴트 복사 생성자”를 컴파일러가 자동으로 생성해 줌. “디폴트 복사 생성자”가 문제가 발생될 경우만 따로 정의하여 사용함. obj 2005 4 5 복사 생성자(copy constructor) 클래스 이름과 동일한 함수 이름을 가지며, 동일한 타입의 인자를 받는 함수 예) Date(const Date&) { /* … */ } 클래스에 복사 생성자가 정의되어 있지 않는 경우, 자동으로 생성됨. (디폴트 복사 생성자) 복사의 의미를 바꾸고 싶은 경우에만 정의하고, 그 외의 경우는 디폴트 복사 생성자를 사용함. 예) Date(const Date& arg) { day = arg.day+1; month = arg.month+1; year = arg.year+1; }
객체 생성 (5) class Person { char* name; int age; public: person(char* n, int a) { name = new char[strlen(n)+1]; strcpy(name,n); age = a; }; person(const Person& p); } main() Person hong(“Hong, Gil-Dong”, 23); Person jang = hong; 얕은 복사 생성자 Person::Person(const Person& p) { name = p.name; age = p.age; } 깊은 복사 생성자 Person::Person(const Person& p) { name = new char[strlen(p.name)+1]; strcpy(name, p.name); age = p.age; } 얕은 복사 생성자 깊은 복사 생성자 hong jang name Hong, Gil-Dong name age 23 23 age hong name Hong, Gil-Dong age 23 jang name Hong, Gil-Dong age 23
객체 생성 (6) 객체의 배열 생성 Date() {… } 0 1 2 9 Date void f() { Date arr[10]; create => 10번 수행 arr 2001 1 2001 1 2001 1 ... 2001 1 배열 객체는 디폴트 생성자를 객체 수만큼 호출하여 만듦 초기화하고자 하는 경우, 초기화 블록을 설정해야 함 배열 객체 초기화. 배열 객체를 생성하는 경우에는 각 객체에 대한 기본 생성자가 수행된다. 만약 각 객체를 서로 다른 값으로 초기화하고자 한다면, 초기화 블록을 사용해야 한다. 예) Date darr[10] = { Date(1,1,1999), Date(2,2,1999), }; 초기화 블록에서는 Date 생성자를 직접 호출하여 임시 객체를 만들고, 이 객체의 값을 복사하여 darr 에 저장한다. 0 1 2 9
[예제] 다양한 개수의 인자를 갖는 생성자 예제 #include <iostream.h> class Date { int d, m, y; public: Date(int dd, int mm, int yy) : d(dd), m(mm), y(yy) {}; Date(int dd, int mm) : d(dd), m(mm), y(2005) {}; Date(int dd) : d(dd), m(5), y(2005) {}; Date() : d(5), m(5), y(2005) {}; void print(); }; void Date::print() cout << "Date : " << y << "/" << m << "/" << d << endl; } int main(void) Date day1(1, 1, 2003); Date day2(1, 1); Date day3(1); Date day4; day1.print(); day2.print(); day3.print(); day4.print(); return 0;
[예제] 기본값이 있는 인자를 갖는 생성자 예제 (생성자의 형태는 달라보여도 앞의 예제와 결국은 같은 내용) #include <iostream.h> class Date { int d, m, y; public: Date(int dd=5, int mm=5, int yy=2005) : d(dd), m(mm), y(yy) {}; void print(); }; void Date::print() cout << "Date : " << y << "/" << m << "/" << d << endl; } int main(void) Date day1(1, 1, 2003); Date day2(1, 1); Date day3(1); Date day4; day1.print(); day2.print(); day3.print(); day4.print(); return 0;
[예제] 복사 생성자 예제 프로그램 #include <iostream.h> class Date { int d, m, y; public: Date(int dd=5, int mm=5, int yy=2005) : d(dd), m(mm), y(yy) {}; Date(const Date& _date) d = _date.d; m = _date.m; y = _date.y; } void print(); }; void Date::print() cout << "Date : " << y << "/" << m << "/" << d << endl; int main(void) Date day1(1, 1, 2003); Date day2 = day1; // 복사 생성자 사용됨 cout << "day1 :"; day1.print(); cout << "day2 :"; day2.print(); return 0;
[예제] 객체 멤버를 초기화하는 생성자 프로그램 #include <iostream.h> #include <string.h> class Date { int d, m, y; public: Date(int dd=5, int mm=5, int yy=2005) : d(dd), m(mm), y(yy) { cout << "Date Object Created ~!" << endl;} Date(const Date& _date) d = _date.d; m = _date.m; y = _date.y; cout << "Date Object Created with Copy Constructor " << endl; } void print(); }; void Date::print() cout << "Date : " << y << "/" << m << "/" << d << endl; class Person char name[20]; Date birthday; Person(char* n, Date d) :birthday(d) strcpy(name, n); cout << "Person Object Created ~!" << endl; void Person::print() cout << "Name : " << name << endl; cout << "Birthday : "; birthday.print(); cout << endl; int main(void) Date day(1, 1, 2003); Person hong("Hong, Gil-Dong", day); hong.print(); return 0;
[예제] 객체 배열을 초기화하는 프로그램 #include <iostream.h> class Date { int d, m, y; public: Date(int dd=5, int mm=5, int yy=2005) : d(dd), m(mm), y(yy) { cout << "Date Object Created ~!" << endl;} void print(); }; void Date::print() cout << "Date : " << y << "/" << m << "/" << d << endl; } int main(void) Date arr[10] = {Date(1,1,2001), Date(2,2,2001), Date(3,3,2001)}; for (int i=0;i<10;i++) cout << "arr[" << i << "] = "; arr[i].print(); return 0;
객체 소멸 (1) ~Date() {… } Date void f() { Date obj; destroy destroyMethod() {…} ~Date() {… } obj 생성되었던 객체를 소멸시키는 것 소멸자 (Destructor) 객체를 소멸시키는 메소드 클래스가 소유하는 메소드 객체를 메모리를 반환함 객체 소멸(object destruction) 객체(인스턴스) 소멸시 깨끗하게 정리하는 작업 수행 객체를 소멸하기 위해, 클래스는 소멸자(destructor)라는 함수를 소유함. 일반적으로 소멸자는 객체 소멸시 자동적으로 호출되지만 경우에 따라 직접 호출할 수 도 있다. 객체를 소멸하기 위해, 클래스는 멤버의 소멸자를 호출하고 사용하던 메모리를 반환한다. 소멸자를 정의하지 않으면 디폴트 소멸자(default destructor)가 자동 삽입된다. 소멸자는 인자를 가질 수 없다. 객체 소멸 과정 소멸자의 몸체를 수행하여 추가 공간 반환 멤버 객체 소멸자를 통해 객체 소멸
객체 소멸 (2) class Date { int d, m, y; public: Date(int dd=5, int mm=5, int yy=2005) : d(dd), m(mm), y(yy) {} ~Date() {} //destructor }; main() Date arr[10]; //main()함수 종료시 ~Date() 가 10번 수행된다. } 유의사항 객체의 멤버 중 포인터 타입이 있는 경우, 만약 객체가 존재하는 동안에 해당 포인터 타입에 동적으로 메모리를 할당했다면, 이를 반환하기 위한 작업을 소멸자에서 반드시 해주어야 한다.
[문제] 소멸자를 이용하여 메모리를 정리하는 프로그램이다. 프로그램을 그대로 수행하여 보고 오류가 발생 하는 곳의 원인을 찾아 수정하시오. #include <iostream.h> #include <string.h> class Date { int d, m, y; public: Date(int dd=5, int mm=5, int yy=2005) : d(dd), m(mm), y(yy) { cout << "Date Object Created ~!" << endl;} ~Date() { cout << "Date Object Destructed ~!" << endl; } void print(); }; void Date::print() { cout << "Date : " << y << "/" << m << "/" << d << endl; } class Person char* name; Date *birthday; public : Person(char* n,Date *d); ~Person() {delete[] name; cout << "Person Object Destructed ~!" << endl;} Person::Person(char* n, Date* d):birthday(d) name = new char[strlen(n) + 1]; strcpy(name,n); cout << "Person Object Created ~!" << endl; } void Person::print() cout << "Name : " << name << endl; cout << "Birthday : "; birthday->print(); cout << endl; int main(void) Date day(1,1,1973); Person hong("Hong, Gil-Dong", &day); hong.print(); Person jang = hong; jang.print(); return 0;
내포 클래스 내포 클래스란? 특징 다른 클래스 내부에 정의된 클래스 클래스의 사용 범위가 포함하는 클래스 내부로 한정된다. 정의가 다른 동일한 이름의 클래스를 서로 다른 클래스에 한정함으로써 이름 충돌을 막는다. Person Animal Date Date class Person { char* name; class Date { int d, m, y; /* ...... */ }; } class Dog { int h, d, m, y; main() Person::Date pd; Dog::Date dd; //...... Person::Date 속성: 년/월/일 Animal::Date 속성 : 년/월/일/시 클래스 내에 선언된 내포 클래스는 ::연산자를 이용해서 접근할 수 있다.
[예제] Person 클래스의 내부에 Date클래스를 선언한 프로그램 #include <iostream.h> #include <string.h> class Person { public: class Date //nested class int d, m, y; Date(int dd=5, int mm=5, int yy=2005) : d(dd), m(mm), y(yy){} ~Date() {} void print() {cout << "Date : " << y << "/" << m << "/" << d << endl;} }; Person(char* n, Date d):birthday(d){strcpy(name,n);} void print(); private: char name[20]; Date birthday; void Person::print() cout << "Name : " << name << endl; cout << "Birthday : "; birthday.print(); cout << endl; } int main(void) Person::Date day(5,5,2005); Person hong("Hong, Gil-Dong", day); hong.print(); return 0;
클래스 객체 (1) 클래스 객체란? 특징 클래스로부터 생성되는 객체(인스턴스)가 아닌 클래스 자신 클래스의 모든 객체가 공유할 멤버와 멤버 함수를 갖는다. 클래스 자체가 개체로 의미를 가질 때 사용한다. static으로 표현한다.
클래스 객체 (2) 클래스 멤버 클래스 멤버 함수 클래스 선언 바깥에서 독립적으로 초기화 문장을 작성한다. main() 함수 호출 전에 메모리 공간(전역 데이터 공간)에 자리가 마련되고 초기화된다. 클래스 멤버가 public으로 선언된 경우에는 객체(인스턴스)가 생성되기 이전에도 클래스 멤버를 사용할 수 있다. 클래스 멤버 함수 객체에 의해 호출될 필요가 없다. 어떤 특정 객체와도 결합하지 않기 때문에, 클래스 멤버(static member)만 사용할 수 있다. class Person { static int serial_no; char name[20]; int age; public: person(char* n, int a) name = new char[strlen(n)+1]; strcpy(name,n); age = a; serial_no++; }; int Person::serial_no = 1000; main() { Person hong(“Hong, Gil-Dong”, 23); Person jang(“Jang, Gil-San”, 25); } hong name Hong, Gil-Dong age 23 serial_no 1002 jang name Jang, Gil-San age 25
[예제] 클래스 멤버와 멤버 함수를 갖는 Date 클래스 예제 프로그램 #include <iostream.h> #include <string.h> class Date { int d, m, y; static Date default_date; //클래스 멤버 public: Date(int dd=0, int mm=0, int yy=0); void print(); static void set_default(int, int, int); //클래스 멤버 함수 }; void Date::print() cout << "Date : " << y << "/" << m << "/" << d << endl; } Date::Date(int dd, int mm, int yy) d = dd? dd : default_date.d; m = mm? mm : default_date.m; y = yy? yy : default_date.y; Date Date::default_date(1,1,2005); //클래스 멤버의 초기화 void Date::set_default(int dd, int mm, int yy) Date::default_date = Date(dd,mm,yy); int main(void) Date day = Date(); day.print(); //출력 결과는? Date::set_default(5, 5, 2005); day = Date(); //출력 결과는? day.print(); return 0;
프렌드 프렌드 함수/클래스 특징 어떤 클래스의 Private 멤버를 자유롭게 접근할 수 있는 함수 또는 클래스 일방적인 관계일 수 있다. 코드가 간결해 지고, 효율적이다. 클래스의 캡슐화에 위배되기 때문에 자주 사용하지 않는다. [예제] Vector와 Matrix에서의 Friend 선언 예 class Matrix; class Vector { float v[4]; // … friend Vector add(const Matrix&, const Vector&); //프렌드 함수 선언 }; class Matrix { Vector v[4]; // … friend Vector multiply(const Matrix&, const Vector&); //프렌드 함수 선언 Vector multiply(const Matrix& m, const Vector& v) { Vector v; for ( int i=0; i < 4; i++ ) { r.v[i] = 0; for ( int j=0; j < 4; j++ ) r.v[i] +=m.v[i].v[j]*v.v[j]; } return r;
[예제] Person과 Dog 클래스 사이에 프렌드를 선언한 예제 프로그램 #include <iostream.h> #include <string.h> class Person; class Dog { friend class Person; char name[20]; int weight; public: Dog(char* n, int w) : weight(w) { strcpy(name,n); } void print() { cout << name << " / " << weight << endl; } }; class Person int age; Person(char* n, int a) : age(a) void dogNaming(Dog& d) { strcat(d.name, name); } void print() { cout << name << " / " << age << endl; } int main(void) Person p1("Hong, Gil-Dong",23); Dog dog1("Nabiya",12); p1.dogNaming(dog1); p1.print(); dog1.print(); return 0; }
클래스와 구조체 class Date { int day, month, year; public: void set(int d, int m, int y); bool cmp(const Date&); void print() const; }; struct Date { void set(int d, int m, int y); bool cmp(const Date&); void print() const; private: int day, month, year; }; [예제] 구조체도 일종의 클래스다. #include <iostream.h> #include <string.h> struct Date { void set(int d, int m, int y) { day = d; month = m; year = y; } void print() const { cout << "Today : " << day << "/" << month << "/" << year << endl; } private: int day, month, year; }; int main(void) Date today, tomorrow; today.set(5,5,2005); tomorrow.set(5,6,2005); today.print(); tomorrow.print(); return 0; }
연습문제 정수의 배열을 관리할 수 있는 Array 클래스를 생성하시오. 생성자 : 배열의 크기를 정수 값으로 받아서 배열을 생성한다. 소멸자 특정 위치의 데이터를 가져오는 함수 특정 위치의 데이터를 변경하는 함수 데이터를 마지막 요소로 추가하는 함수 특정 위치의 데이터를 삭제하는 함수 배열 데이터 전체를 출력하는 함수 배열이 현재 꽉 찼는지 비어있는지를 검사하기 위해 내부적으로 사용하는 함수 Array 클래스의 선언 class Array { int arr*; int size; //현재 배열의 크기 int idx; //현재 데이터 갯수 public: Array(int s=5); ~Array(); int GetData(int); //i번째 요소의 데이터 반환 int SetData(int i, int data); //i번째 요소를 data 값으로 변경 int AddData(int); //마지막 요소로 인자의 값을 추가 int DelData(int); //i번째 요소를 삭제 void Print(); //배열 전체 출력 private: bool isfull(); //배열이 꽉 찼는지 검사 bool isempty(); //배열이 비어있는지 검사 };
Chapter 05 상속
클래스 계층구조(Class Hierarchy) 개념(Concept)의 계층 구조 Person Club School Friend Professor 기본 클래스 super class, base class, parent class sub class, derived class, child class 파생 클래스(유도 클래스) 클래스 계층 구조 이미 정의된 클래스를 재활용하기 위한 필수적인 기능이다. 파생클래스는 기본 클래스의 멤버와 멤버함수를 상속 받는다. 파생클래스는 기본 클래스에 멤버를 추가할 수 있다. 파생클래스는 기본 클래스에 멤버 함수를 추가할 수 있다. 기본 클래스에 정의된 멤버 함수의 내용을 파생클래스에서 변경할 수 있다. (다형성 : Polymorphism)
Why Class Hierarchy? (1) [예제] 회사를 구성하는 사원들에 대한 클래스 표현 일반사원 직원번호, 이름, 나이, 전공, 근무년수, 소속 부서, 월급 직원번호, 이름, 나이, 전공, 근무년수, 소속 부서, 월급, 담당 임원 이름, 담당 임원 직책 비서 [예제] 회사를 구성하는 사원들에 대한 클래스 표현 관리사원 class 일반사원 { int 직원번호; char 이름[20]; int 나이; char 전공[30]; int 근무년수; char 소속부서[20]; float 월급; public: 일반사원(); ~일반사원(); // 기타 멤버 함수들 }; class 관리사원 { int 직원번호; char 이름[20]; int 나이; char 전공[30]; int 근무년수; char 소속부서[20]; float 월급; int 등급; 일반사원* 내사원들; public: 관리사원(); ~관리사원(); // 기타 멤버 함수들 }; 직원번호, 이름, 나이, 전공, 근무년수, 소속 부서, 월급, 관리직 등급, 관리하는 사원들 class 비서 { int 직원번호; char 이름[20]; int 나이; char 전공[30]; int 근무년수; char 소속부서[20]; float 월급; char 임원이름[20]; char 임원직책[20]; public: 비서(); ~비서(); // 기타 멤버 함수들 };
Why Class Hierarchy? (2) [예제] 회사를 구성하는 사원들에 대한 클래스 표현 (cont’d) 직원 번호가 0보다 큰 수가 되도록 제한하고, 배우자 이름도 같이 넣어보자. => 모든 클래스의 수정이 필요하다~!!! //새로 정의한 클래스 Class NN { /* 자연수 */ }; Char 배우자[20]; class 일반사원 { NN 직원번호; char 배우자[20]; //... }; class 관리사원 { NN 직원번호; char 배우자[20]; //... }; class 비서 { NN 직원번호; char 배우자[20]; //... };
Why Class Hierarchy? (3) [예제] 회사를 구성하는 사원들에 대한 클래스 표현을 수정해보자. 공통된 속성과 메소드를 모두 포함하는 상위개념 클래스를 하나 생성 사원 관리사원 일반사원 비서 직원번호, 이름, 나이, 전공, 근무년수, 소속 부서, 월급 관리직 등급, 관리하는 사원들 담당 임원 이름, 담당 임원 직책 [예제] 회사를 구성하는 사원들에 대한 클래스 표현 class 사원 { int 직원번호; char 이름[20]; int 나이; char 전공[30]; int 근무년수; char 소속부서[20]; float 월급; public: 사원(); ~사원(); // 기타 멤버 함수들 }; class 일반사원:public 사원 { public: 일반사원(); ~일반사원(); // 기타 멤버 함수들 }; class 관리사원:public 사원 { int 등급; 일반사원* 내사원들; public: 관리사원(); ~관리사원(); // 기타 멤버 함수들 }; class 비서:public 사원 { char 임원이름[20]; char 임원직책[20]; public: 비서(); ~비서(); // 기타 멤버 함수들 };
Why Class Hierarchy? (4) [예제] 회사를 구성하는 사원들에 대한 클래스 표현 (cont’d) 직원 번호가 0보다 큰 수가 되도록 제한하고, 배우자 이름도 같이 넣어보자. => 이젠 상위의 기본 클래스만 수정하면 된다~!!! //새로 정의한 클래스 Class NN { /* 자연수 */ }; Char 배우자[20]; class 사원 { NN 직원번호; char 배우자[20]; //... }; 가장 상위의 기본 클래스만 수정해주면 하위에 존재하는 모든 파생 클래스들에는 상속되어 자동적으로 적용된다. => 따라서 위의 경우, 기본 클래스인 사원 클래스만 수정해주면 일반사원, 관리사원, 비서 클래스들이 모두 자연수 데이터 타입의 직원번호와 문자열(길이 20) 타입의 배우자 멤버를 갖게 된다.
파생 클래스 객체 생성 특징 파생 클래스 객체 생성 과정 파생 클래스는 자신이 상속받은 기본 클래스의 private멤버에 직접 접근할 수 없다. 기본 클래스의 멤버 함수 혹은 생성자를 이용해 접근한다. 파생 클래스의 생성자는 기초 클래스의 생성자를 사용해야 한다. 파생 클래스 객체 생성 과정 파생 클래스 생성시 기본 클래스의 생성자를 먼저 실행한다. 파생 클래스의 생성자에서 기본 클래스의 생성자가 호출되도록 프로그램한다. 기본 클래스의 생성자 호출이 생략되었다면 디폴트 기초 클래스 생성자를 사용한다. 파생 클래스의 생성자가 실행된다. 파생 클래스의 생성자에서는 새로 추가된 멤버들을 초기화해야 한다. [예제] 관리 사원 타입의 객체 생성해보자. int main(void) { 관리사원 manager(“Hong”); //... return 0; } 생성 과정 call 관리사원(char* name) : 관리사원 생성자의 실행이 아닌 호출 call 사원(char* name) : 사원 생성자의 호출 execute 사원(char* name) : 사원 생성자의 실행 -> 사원 객체 생성 execute 관리사원(char* name) : 관리사원 생성자의 실행 -> 관리사원 객체 생성 class 사원 { int 직원번호; char 이름[20]; int 나이; char 전공[30]; int 근무년수; char 소속부서[20]; float 월급; public: 사원(char*); ~사원(); // 기타 멤버 함수들 }; class 관리사원:public 사원 { int 등급; 일반사원* 내사원들; public: 관리사원(char*); ~관리사원(); // 기타 멤버 함수들 };
파생 클래스 객체 소멸 파생 클래스 객체 소멸 과정 파생 클래스 소멸자가 먼저 실행된다. 파생 클래스 생성자에 의해서 동적으로 할당된 메모리가 있다거나 정리작업이 필요한 경우에 반드시 파생 클래스 소멸자가 명시적으로 정의되어야 한다. 기본 클래스의 소멸자가 나중에 실행된다. 파생 클래스 객체 중 기본 클래스와 관련된 부분은 기본 클래스 소멸자를 통해서 정리작업이 이루어진다. [예제] 관리 사원 타입의 객체 생성해보자. int main(void) { 관리사원 manager(“Hong”); //... return 0; } 생성 과정 call and execute ~관리사원(char* name) : 관리사원 소멸자 실행 call and execute ~사원(char* name) : 사원 소멸자 실행 class 사원 { int 직원번호; char 이름[20]; int 나이; char 전공[30]; int 근무년수; char 소속부서[20]; float 월급; public: 사원(char*); ~사원(); // 기타 멤버 함수들 }; class 관리사원:public 사원 { int 등급; 일반사원* 내사원들; public: 관리사원(char*); ~관리사원(); // 기타 멤버 함수들 };
protected 접근제한 (1) 클래스 계층 구조에서의 접근 제한 class 사원 { private: int 직원번호; char 이름[20]; int 나이; //... public: 사원(); ~사원(); getEmpNo(); // 기타 멤버 함수들 }; 직원번호 멤버 : 사원 클래스의 private영역에 소속 관리사원 클래스의 직원번호 : 직원번호 멤버에 직접 접근할 수 없으며, getEmpNo()과 같은 멤버 함수를 통해야 한다. → 불편하다. 새로운 접근 모드의 필요하다!! → “protected” 접근 제한 클래스 계층 구조에서의 접근 제한 private : 클래스 밖에서는 절대로 접근할 수 없다. public : 클래스 밖에서 접근하여 사용할 수 있다. protected : 클래스 밖에서는 private과 같이 접근할 수 없고, 자신의 파생클래스에서는 public 같이 접근할 수 있다. [예제] 사원 클래스를 재정의하자. class 사원 { protected: int 직원번호; char 이름[20]; int 나이; //... public: 사원(); ~사원(); getEmpNo(); //파생 클래스를 위해서 만들었다면 이제 더이상 필요없다. // 기타 멤버 함수들 };
protected 접근제한 (2) 클래스(X)로의 접근 가능 영역 클래스(X) 자신의 프렌드에서 접근 가능 private section 멤버 함수 파생 클래스(Y)의 멤버 함수와 프렌드에서 접근 가능 protected section 임의의 다른 함수 (클래스 외부)에서 접근 가능 public section
[예제] 멤버 접근 제한 테스트 프로그램 #include <iostream.h> #include <string.h> class Ancestor { private: int back_money; protected: int hidden_money; public: int known_money; char personality[20]; Ancestor(int b, int h, int k, char* p) :back_money(b), hidden_money(h), known_money(k) strcpy(personality, p); } }; class Descendent:public Ancestor int his_back_money; int his_hidden_money; int his_known_money; Descendent(int ab,int ah,int ak,char* ap, int hb, int hh, int hk) : Ancestor(ab, ah, ak, ap), his_back_money(hb), his_hidden_money(hh), his_known_money(hk) {} void inheritance() his_back_money += back_money; //error : access denied ~!!!! his_hidden_money += hidden_money; his_known_money += known_money; void print() cout << "Back_Money : " << his_back_money << endl; cout << "Hidden_Money : " << his_hidden_money << endl; cout << "Known_Money : " << his_known_money << endl; //다음 페이지 계속...
[예제] 멤버 접근 제한 테스트 프로그램 (cont’d) class Final:public Descendent { private: int final_back_money; protected: int final_hidden_money; public: int final_known_money; Final(int ab, int ah, int ak, char* ap, int hb, int hh, int hk) : Descendent(ab, ah, ak, ap, hb, hh, hk), final_back_money(0), final_hidden_money(0), final_known_money(0) {} void inheritance() Descendent::inheritance(); final_back_money += his_back_money; //error : access denied~!! final_hidden_money += his_hidden_money; final_known_money += his_known_money; } void print() cout << "Back_Money : " << final_back_money << endl; cout << "Hidden_Money : " << final_hidden_money << endl; cout << "Known_Money : " << final_known_money << endl; }; int main(void) Descendent desc(1000, 2000, 3000, "kind", 100, 200, 300); Final desc_final(1000, 2000, 3000, "kind", 100, 200, 300); desc.inheritance(); desc.print(); desc_final.inheritance(); desc_final.print(); return 0;
상속 유형(Inheritance Type) 파생 클래스 정의시 상속의 유형 Class B1: public A private protected public Class A private protected public Class B2 : protected A Class C : public B2 private protected public private protected public 파생 클래스 정의시 상속의 유형 public 상속 : 기본 클래스의 protected영역은 파생 클래스의 protected영역으로, 기본 클래스의 public영역은 파생 클래스의 public영역으로 상속된다. protected 상속 : 기본 클래스의 protected영역과 public영역이 모두 파생 클래스의 protected영역으로 상속된 다. private 상속 : 기본 클래스의 protected영역과 public영역이 모두 파생 클래스의 private영역으로 상속된다. 상속 유형에 상관없이 기본 클래스의 private영역은 무조건 직접 접근 불가능하다. [문제] 다음의 질문에 답하시오 class A의 private 영역 멤버를 직접 접근 할 수 있는 클래스를 말해보자. class A의 protected 영역 멤버를 직접 접근 할 수 있는 클래스를 말해보자. class A의 public 영역 멤버를 직접 접근 할 수 있는 클래스를 말해보자. Class B3 : private A Class D : public B3 private protected public private protected public
기본클래스와 파생클래스의 관계 파생 클래스 객체는 기본 클래스 메서드를 사용할 수 있다. 기본 클래스를 가리키는 포인터와 기본 클래스에 대한 참조 타입은 명시적 데 이터형 변환 없이도 파생클래스 객체를 가리키거나 참조할 수 있다. 파생 클래스를 가리키는 포인터와 파생 클래스에 대한 참조 타입은 기본 클래 스 객체를 가리키거나 참조할 수 없다. [예제] 관리 사원 타입의 객체 생성해보자. class 사원 { int 직원번호; //... }; class 관리사원:public 사원 int 등급; 일반사원* 내사원들; // ... int main(void) 관리사원 manager(“Hong”); 사원* ptr1 = &manager; //수행이 될까? 사원& ref1 = manager; 사원 employee(“Jang”); 관리사원* ptr2 = &employee; //수행이 될까? 관리사원& ref2 = employee; return 0; }
가상 함수(Virtual Function) (1) [예제] 우리 회사 사원들에 대한 정보를 배열로 관리해보자. 일반사원과 관리사원, 비서 서로 다른 클래스 유형이 세가지가 있으므로 배열을 세가지로 나누어 관리해야 하는가? 일반사원 emp[100]; 관리사원 mgr[100]; 비서 sec[100]; //모든 사원들에 대한 정보를 출력하라 void printall() { for (int i=0; i<empind; i++) emp[i].print(); for (int j=0; j<mgrind; j++) mgr[j].print(); for (int k=0; k<secind; k++) sec[k].print(); } 문제점 동일한 패턴의 코드에 대한 반복 출력 형태의 변경에 따라 새로운 코드의 중복이 발생됨 사원 계층 구조에서 클래스들의 추가/변경/삭제가 코드에 영향을 미치게 됨. 해결책 클래스 개수와 종류에 무관하게 한개의 데이터 저장소를 이용하게 한다.
가상 함수(Virtual Function) (2) [예제] 우리 회사 사원들에 대한 정보를 배열로 관리해보자. (cont’d) 일반사원, 관리사원, 비서 세가지 클래스의 기본 클래스인 사원 클래스 객체를 가리킬 수 있는 배열을 만들자. 사원* emps[300]; //모든 사원들에 대한 정보를 출력하라 void printall() { for (int i=0; i<ind; i++) emps[i]->.print(); } 문제점 하나의 저장소에서 관리는 되었지만, 실제 데이터의 종류에 무관하게 일반적인 사원 정보만을 출력한다. 사원::print() => 직원번호, 이름, 소속부서, 월급 내용 출력 일반사원::print() => 사원::print() 내용과 같다. 관리사원::print() => 사원::print() + 관리직등급, 관리하는 사원들 비서::print() => 사원::print() + 담당임원이름, 담당임원직책 => 실제 사원의 종류에 관계없이 사원::print()가 실행된다. 해결책 타입을 구분하여 출력할 수 있도록 하기 위해서 각 클래스에 type field를 둔다.
가상 함수(Virtual Function) (3) [예제] 우리 회사 사원들에 대한 정보를 배열로 관리해보자. (cont’d) 사원 클래스에 각 타입을 구분할 수 있는 멤버를 추가하여 각 객체의 종류가 일반사원, 관리사원, 비서 세가지 클래스 중 어디에 해당하는지 찾을 수 있도록 한다. 사원* emps[300]; //사원 클래스에 emptype멤버 추가 // enum Etype {EMP, MGR, SEC}; // Etype emptype; void printall() { for (int i=0; i<ind; i++) { switch(emps[i]->emptype){ case EMP: (일반사원*)emps[i]->print(); break; case MGR: (관리사원*)emps[i]->print(); break; case SEC: (비서*)emps[i]->print(); break; } 문제점 사용자가 명시적으로 type을 구분하여야 하는 번거로움이 있다. 단지 출력 함수에서 뿐만 아니라 프로그램 전반에 걸쳐서 같은 형태의 코드가 중복된다. 클래스 변경이 발생할 때마다 코드 수정이 필요하다. 해결책 컴파일러가 자동으로 실제 객체의 타입을 찾아서 해당 클래스의 멤버 함수가 불리도록 지원해야 한다. => 가상 함수(Virtual Function)을 사용한다.
가상 함수(Virtual Function) (4) 가상함수란? 메시지 다형성 보장을 위한 것으로, 가상함수가 정의된 클래스의 파생 클래스에 이를 재정의한 함수가 있을 수 있다는 것을 알려줌. 실행 시점에 동적으로 자신의 타입에정의된 함수를 부르도록 함. 함수의 선언은 동일해야 함(함수이름, 인자 타입과 개수, 리턴 타입) 사원 관리사원 일반사원 비서 virtual void print() {/* ....... */ }; void print() { /* ....... */ }; [예제] 가상 함수의 실행 과정을 알아보자. class 사원 { protected: //멤버들 public: virtual void print() { /* 사원 멤버 출력 */ } //기타 멤버 함수들 }; class 관리사원:public 사원 void print() { /* 관리사원 멤버 출력 */ } int main(void) 사원* mgr = new 관리사원(“Hong”); mgr->print(); return 0; } 1. 사원 객체로 메세지 전달 2. 사원 객체는 자신의 실제 객체 타입 검사 3. 관리사원 객체임을 알아내고 관리사원 객체의 print()함수를 호출
[예제] 객체 타입을 추가하여 관리하는 프로그램 #include <iostream.h> #include <string.h> class Employee { protected: char name[20]; char hire_date[20]; int dept_id; public: enum Empl_type { M, E }; Empl_type type; Employee(char* n, char* h, int d, Empl_type t) strcpy(name,n); strcpy(hire_date,h); dept_id = d; type = t; } void print() cout << "[Name : " << name << "]\n" << "Hire_Date : " << hire_date << ", Dept_ID : " << dept_id << ", " << "Employee_Type : " << type << "(0:M,1:E)" << endl; }; class Manager : public Employee int level; Manager(char* n, char* h, int d, Empl_type t, int l) : Employee(n,h,d,t), level(l) {} cout << "[Name : " << name << "]\n" << "Hire_Date : “ << hire_date << ", " << "Dept_ID : " << dept_id << ", " << "Employee_Type : " << type << "(0:M,1:E), " << "Level : " << level << endl; //다음 페이지에 계속...
[예제] 객체 타입을 추가하여 관리하는 프로그램(cont’d) int main(void) { Manager m1("Hong","980101",1,Employee::M,1); Employee e1("Jang","970101",1,Employee::E); Employee* elist[2]; elist[0] = &m1; elist[1] = &e1; for ( int i=0; i < 2; i++ ) switch (elist[i]->type) case Employee::E : elist[i]->print(); break; case Employee::M : (static_cast<Manager*>(elist[i]))->print(); default : continue; } return 0;
[예제] 가상 함수를 이용하여 관리하는 프로그램 #include <iostream.h> #include <string.h> class Employee { protected: char name[20]; char hire_date[20]; int dept_id; public: Employee(char* n, char* h, int d) strcpy(name,n); strcpy(hire_date,h); dept_id = d; } virtual void print() // virtual function cout << "[Name : " << name << "]\n" << "Hire_Date : " << hire_date << ", " << "Dept_ID : " << dept_id << endl; }; class Manager : public Employee int level; Manager(char* n, char* h, int d, int l) : Employee(n,h,d), level(l) {} void print() cout << "[Name : " << name << "]\n" << "Hire_Date : " << hire_date << ", “ << "Dept_ID : “ << dept_id << ", “ << "Level : " << level << endl; int main(void) Manager m1("Hong","980101",1,1); Employee e1("Jang","970101",1); Employee* elist[2]; elist[0] = &m1; elist[1] = &e1; for ( int i=0; i < 2; i++ ) elist[i]->print(); return 0;
가상 함수(Virtual Function) (5) 가상함수 사용시 주의사항 기본 클래스의 멤버 함수가 파생 클래스에서 다시 정의되는 경우에는 가상함수로 선언하는 것이 일반적이다. 멤버 함수가 객체에 의해 호출되지 않고 참조나 포인터에 의해 호출되는 경우가 있다면 해당 멤버 함수를 가상함수로 선언하는 것이 좋다. 기본 클래스에서 가상 함수로 정의하려는 함수가 오버로딩 되어있다면, 오버로딩 된 모든 버전에 대하여 파생클래스에서 재정의하여야 한다. (한가지 버전만 재정의하게 되면, 나머지 버전들은 은닉되어 사용할 수 없게 된다.) 프렌드 함수는 가상함수로 선언할 수 없다.
가상 소멸자(Virtual Destructor) 가상 함수를 써야 하는 경우라면, 각 개체들이 소멸될 때에도 올바르게 정리되기 위해서는 소멸자를 가상 소멸자로 선언해야 한다. (명시적인 소멸자가 필요없는 기본 클래스일지라도 가상 소멸자를 제공해야 한다.) 관리사원 객체에서 파생시에 추가적으로 사용된 메모리가 있었다면 소멸시 반드시 정리 작업이 이루어져야 한다. 사원 클래스의 소멸자가 가상이 아니라면 : delete 작업시에 사원 클래스의 소멸자가 불 리므로 올바른 정리가 이루어지지 못한다. 반드시 사원 클래스의 소멸자는 가상으로 선언되어야 한다. void f() { 사원* emp = new 관리사원(“홍길동”); // … ... delete emp; } 생성자는 가상으로 선언할 수 없다. [예제] 가상 소멸자를 선언한 프로그램 class 사원 { protected: //멤버들 public: virtual void print() { /* 사원 멤버 출력 */ } virtual ~사원(); //가상 소멸자로 선언하였다. //기타 멤버 함수들 }; class 관리사원:public 사원 void print() { /* 관리사원 멤버 출력 */ } ~관리사원(); int main(void) 사원* mgr = new 관리사원(“Hong”); mgr->print(); delete mgr; //이때 호출되는 소멸자는? return 0; }
추상화 클래스(Abstract Class) (1) 추상화 클래스(Abstract Class or Abstract Base Class) 구체화된 객체를 생성할 필요가 없는, 추상적인 개념만을 표현하는 클래스 사원 관리사원 일반사원 비서 사원 클래스는 회사내의 사원들에 대한 공통된 멤버와 멤버 함수를 표현하지만, 객체를 생성할 필요가 없다. => 추상화 클래스로 표현~! 추상 클래스의 역할 파생 클래스로의 창구 역할을 한다. 모든 파생 클래스 타입의 객체들에 대한 관리를 할 수 있다. 기본 클래스를 추상화 시킴으로써, 기본 클래스가 정한 인터페이스 규칙이 잘 준수되도록 할 수 있다. 추상화 클래스로는 절대로 객체를 생성할 수 없다.
추상화 클래스(Abstract Class) (2) 추상화 클래스 생성 방법 하나 이상의 가상함수를 몸체가 없는 순수 가상 함수(pure virtual function)로 선언한다. 파생 클래스에서는 기본클래스의 순수 가상 함수를 반드시 재정의해야 객체를 생성할 수 있다. 순수 가상 함수 : 함수의 몸체가 0인 가상 함수 (예) virtual void print() = 0 ; 이 경우, print()함수는 클래스 선언부에 몸체를 가질 수 없다. 그러나 .cpp파일에 몸체를 정의할 수는 있다. [예제] 사원 클래스를 추상화 클래스로 선언하였다. class 사원 { protected: //멤버들 public: virtual void print() = 0; //순수 가상 함수로 선언하였다. virtual ~사원(); //기타 멤버 함수들 }; class 관리사원:public 사원 void print() { /* 관리사원 출력 */ } //반드시 재정의 되어야 한다. ~관리사원(); int main(void) 사원 emp; //error~!! emp.print(); //error~!! 사원* mgr = new 관리사원(“Hong”); //OK~! mgr->print(); delete mgr; return 0; }
[예제] 추상화 클래스를 기본 클래스로 하는 클래스 계층 프로그램으로 실행시켜보고 오류가 발생하는 곳을 찾고 이유를 말해보자. #include <iostream.h> #include <string.h> class Mammal { protected: char name[20]; public: Mammal() { strcpy(name,"Mammal"); } virtual void move() =0; //순수가상함수 virtual void setname(char* n) =0; //순수가상함수 }; class Person : public Mammal int number; void move() { cout << name << " Move" << endl; } void setnum(int i) { number = i; } void setname(char* n) { strcpy(name, n); } class Horse : public Mammal char owner[20]; void setowner(char* o) { strcpy(owner, o); } int main(void) Person hong; hong.setname("Hong"); hong.move(); Horse jongma; jongma.setname("Cheolian"); jongma.move(); return 0; }
다중 상속(Multiple Inheritance) (1) 한 클래스가 여러 다른 클래스를 상속받는 것 다중 상속의 문제 별개의 두 기본 클래스로부터 이름은 같지만 서로 다른 멤버함수를 상속 받을 수 있다. 서로 관련된 클래스들로부터 상속받음으로써 특정 클래스가 다중 상속 될 수 있다. 다중 상속의 유형 서로 다른 기본 클래스를 가지는 클래스들을 상속하는 것 중복되는 기본 클래스의 다중 생성 중복되는 기본 클래스의 단일 생성
다중 상속(Multiple Inheritance) (2) 상속유형 1 - 서로 다른 기본 클래스를 가진 클래스들의 상속 사원 임시직 일반사원 관리사원 비서 상속 유형 1 - 서로 다른 기본 클래스를 가진 클래스들의 상속 class 사원 {......}; class 임시직 {......}; class 일반사원: public 사원{......}; class 관리사원: public 사원{......}; class 비서: public 사원{......}; class 컨설턴트 : public 임시직, public 관리사원{......}; class 임시직비서 : public 임시직, public 비서{...... }; => 이름은 같지만 서로 다른 동작을 수행하는 멤버 함수들을 2이상 갖게 되는 문제가 발생할 수 있다. 컨설턴트 임시직 비서
다중 상속(Multiple Inheritance) (3) 상속유형 2 - 중복되는 기본 클래스의 다중 생성 운동선수 축구선수 야구선수 상속 유형 2 - 중복되는 기본 클래스의 다중 생성 class 운동선수 {......}; class 축구선수: public 운동선수{......}; class 야구선수: public 운동선수{......}; class 축구야구선수 : public 축구선수, public 야구선수{......}; => 축구야구선수 클래스 내에 운동선수 클래스가 2개 존재하는 문제가 발생한다. 축구야구선수
다중 상속(Multiple Inheritance) (4) 상속유형 3 – 가상상속 : 중복되는 기본 클래스의 단일 생성 식별자 생산자 매체 탈것 비행기 모터 있는 탈것 마력 연료용량 연료종류 모터 없는 탈것 원동력 이륙거리 최대고도 상속 유형 3 - 중복되는 기본 클래스의 단일 생성 반복되는 기본 클래스 객체가 하나만 나타나도록 하기 위해 기본 클래스를 가상으로 상속받는다. 모터 있는 비행기 모터 없는 비행기 class 탈것 {......}; class 모터있는탈것: virtual public 탈것{......}; class 모터없는탈것: virtual public 탈것{......}; class 비행기: virtual public 탈것{......}; class 모터있는비행기: public 모터있는탈것, public 비행기{......}; class 모터없는비행기: public 모터없는탈것, public 비행기{......};
다중 상속(Multiple Inheritance) (5) 가상 기본 클래스(virtual base class)와 생성자 가상 기본 클래스 : 파생 클래스들에게 가상(virtual)으로 상속되는 기본 클래스 가상 기본 클래스가 존재하면 파생 클래스들에게 생성자 작성시 주의해야 한다. class 운동선수 { 운동선수(char* name); }; class 축구선수: virtual public 운동선수 축구선수(char* name, int goal):운동선수(name) {......} class 야구선수: virtual public 운동선수 야구선수(char* name, int homerun):운동선수(name) {......} class 축구야구선수: public 축구선수 축구야구선수(char* name, int goal, int homerun) : 축구선수(name, goal),야구선수(name,homerun) {......} 가상상속 void f() { 축구야구선수 park(“ChanHo”,10,20); } 가상 상속이 아닌 경우 축구야구선수 생성자 축구선수 생성자 운동선수 생성자 호출 야구선수 생성자 운동선수 생성자 호출 가상 상속인 경우
다중 상속(Multiple Inheritance) (6) 가상 기본 클래스(virtual base class)와 생성자 (cont’d) 잠재적인 충돌을 피하기 위하여 기본 클래스가 가상일 경우에는 중간 단계에서 가상 기본 클래스로 정보를 자동으로 전달하여 생성자를 수행시키는 기능을 정지시킨다. 다중 상속에 의한 파생 클래스 생성자에서 가상 기본 클래스의 생성자를 수행시키고 싶을때에는 명시적으로 생성자를 호출하여야 한다. //앞의 축구야구선수 클래스 생성자의 수정 class 축구야구선수: public 축구선수 { 축구야구선수(char* name, int goal, int homerun) : 운동선수(name), 축구선수(name, goal),야구선수(name,homerun) {......} } void f() { 축구야구선수 park(“ChanHo”,10,20); } 축구야구선수 생성자 운동선수 생성자 명시적 호출 야구선수 생성자 명시적 호출
다중 상속(Multiple Inheritance) (7) 다중 상속된 클래스에서의 멤버 함수 호출 방법1 : 다중 상속으로 인하여 같은 멤버 함수가 둘 이상 존재할 경우, 범위 지정 연산자(::)를 사용하여 모호성을 피한다. 방법2 : 다중 상속으로 인하여 같은 멤버 함수가 둘 이상 존재할 경우, 파생 클래스에서 재정의한다. void 축구선수::print() {......} void 야구선수::print() {......} int main(void) { 축구야구선수 park (“ChanHo”, 10, 20); park.print(); //ambiguous ~!! park.축구선수::print(); //ok 방법1.소속클래스를 밝힌다. park.야구선수::print(); //ok } 다중 상속된 클래스에서의 멤버 함수 호출 void 축구선수::print() {......} void 야구선수::print() {......} void 축구야구선수::print() {......} //방법2. 멤버 함수를 재정의한다. int main(void) { 축구야구선수 park (“ChanHo”, 10, 20); park.print(); //ok return 0; }
RTTI (1) RTTI(RunTime Type Information) 목적 주요 구성 요소 실시간 데이터 타입 정보 실행시간에 각 객체들이 가진 타입 정보 목적 프로그램이 실행 도중에 객체의 데이터 타입을 결정하는 표준 방법을 제공하는 것 동작 중인 객체의 실제 타입 정보를 즉각적으로 얻어, 필요한 연산을 수행하도록 할 수 있다. 동종의 타입으로의 실시간 타입 변환을 가능하게 하여 유연성있는 프로그램을 구성할 수 있다. 주요 구성 요소 type_info 클래스와 typeid 연산자 dynamic_cast 타입 변환 연산자 주요 구성 요소 type_info 클래스 : 어떤 특별한 데이터 타입에 대한 정보를 저장한다. typeid 연산자 : 어떤 객체의 정확한 데이터 타입을 식별하는 하나의 값을 반환한다. dynamic_cast 연산자 : 기본 클래스형을 지시하는 포인터로부터 파생 클래스 타입을 지시하는 포인터를 생성한다. 주의사항 RTTI는 가상 함수들을 가지고 있는 클래스들에 대해서만 사용할 수 있다. 최근에 추가된 기능으로 오래된 컴파일러에서는 지워하지 않는다. 컴파일러가 RTTI를 지원하더라도 그 기능이 꺼져있을 수 있다.
RTTI (2) type_info 클래스 타입에 기반한 기본적인 정보를 제공하는 클래스로서, 구현 환경에 따라 달라질 수 있다. typeinfo 헤더파일에 정의되어 있다. class type_info { public: virtual ~type_info(); int operator == (const type_info& rhs) const; //== 연산자 오버로딩 int operator != (const type_info& rhs) const; // != 연산자 오버로딩 int before(const type_info& rhs) const; const char* name() const; //타입의 이름을 반환하는 멤버 함수 const char* raw_name() const; private: ... };
RTTI (3) typeid 연산자 전달인자의 종류 : 클래스의 이름 or 객체로 평가되는 식 인자로 주어지는 객체의 타입 이름이나 수식의 결과 타입에 대한 type_info 정보를 제공한다. const type_info& typeid(type_name) throw(bad_typeid); const type_info& typeid(expression); [예제] typeid를 사용하여 현재의 객체의 유형(클래스이름)을 출력해보자. class AAA { ...... }; class BBB : public AAA { ...... }; int main(void) { BBB b; AAA *pa = &b; cout << “나의 객체 유형은 무엇일까요? “ << endl; cout << typeid(*pa).name() << “ 입니다.” << endl; return 0; }
RTTI (4) dynamic_cast 연산자 특징 인자로 주어진 객체의 실제 타입을 참조하여 원하는 타입으로 타입변환 하는 함수 dynamic_cast<T*>(pointer) dynamic_cast<T&>(object) 특징 대응되는 타입으로의 변환이 불가능한 경우 포인터 타입이면 ->null을 반환 참조 타입이면 -> bad_cast 에외를 발생시킴 downcast, upcast, crosscast 모두 허용함 [예제] dynamic_cast를 사용하여 데이터 타입을 변환해보자. class AAA { public: virtual void print(); }; class BBB : public AAA { void print(); virtual void printall(); class CCC : public BBB { void printall(); int main(void) { AAA a; BBB b; CCC c; AAA* arr[3] = {&a, &b, &c}; for (int i=0; i<3; i++){ arr[i]-> print(); //OK arr[i]-> printall(); //error } return 0; BBB* pb; if (pb = dynamic_cast<BBB *>(arr[i])) pb->printall();
[예제] RTTI를 사용하는 예제 프로그램 #include <iostream.h> #include <typeinfo.h> class AAA { public: virtual void print() {cout << "AAA 클래스의 print()함수 입니다." << endl;} }; class BBB : public AAA { void print() {cout << "BBB 클래스의 print()함수 입니다." << endl;} virtual void printall() {cout << "BBB 클래그의 printall() 함수입니다." << endl;} class CCC : public BBB { {cout << "CCC 클래스의 print()함수 입니다." << endl;} void printall() {cout << "CCC 클래그의 printall() 함수입니다." << endl;} int main(void) { AAA a; BBB b; CCC c; AAA* arr[3] = {&a, &b, &c}; for (int i=0; i<3; i++){ cout << "------------------------------------" << endl; cout << "[" << typeid(*arr[i]).name() << "]" << endl; arr[i]-> print(); //OK BBB *pb; if (pb = dynamic_cast<BBB *>(arr[i])) pb-> printall(); } return 0;
데이터 타입 변환 연산자 추가적인 데이터 타입 변환 연산자 dynamic_cast const_cast : 어떤 값을 const나 volatile로 또는 그 반대로 변환하는 연산자 static_cast : 기본 클래스를 파생클래스로 변환하여 사용하기 위한 연산자로 언제나 유효한 것은 아니다. reinterpret_cast : 위험한 데이터 타입 변환을 하기 위한 연산자 : 시스템 의존적인 low level 프로그래밍에서 주로 사용 dynamic_cast <type_id> (expression) const_cast <type_id> (expression) static_cast <type_id> (expression) reinterpret_cast <type_id> (expression)
Chapter 06 연산자 오버로딩과 자동 변환
연산자 오버로딩 목적 Built-in 타입에 대해 사용할 수 있었던 연산자들을 사용자 정의 데이터 타입에 대해서도 동일한 이름으로 동일한 연산을 수행하도록 하여, 프로그램을 이해하기 쉽고 간결하게 만들자는 것 void func() { int i=10, j=20, k; k = i + j * i; } A set of operators int Built-in type void func() { 복소수 i=10, j=20, k; k.assign(i.add(j.multiply(i))); } A set of member functions 복소수 오버로딩 방법 멤버 함수에 의한 연산자 오버로딩 전역 함수에 의한 연산자 오버로딩 User-defined type 이해하기 어렵고, 오류 발생 가능성 높다.
오버로딩 가능 연산자들 오버로딩 가능 연산자들 오버로딩 불가능 연산자들 + - * / % ^ & | ~= ! = < > <= >= == != += -= *= /= %= ^= &= |= <<= >>= << >> && || ++ -- , ->* -> () [] new delete 멤버 연산자 . 멤버 지시 포인터 연산자 .* 범위 지정 연산자 :: 조건 연산자 ?: RTTI 연산자 typeid 데이터형 변환연산자 const_cast, dynamic_cast, static_cast 오버로딩 제약사항 오버로딩 된 연산자는 적어도 피연산자가 사용자 정의 데이터 타입이어야 한다. 예) int와 int를 더하는 +연산자를 오버로딩 할 수 없다. 오버로딩 된 연산자는 원래의 연산자에 적용되는 문법 규칙과 맞지 않게 사용할 수 없다. 예) 두개의 연산자를 갖는 나눗셈 연산자(/)가 하나의 연산자만 갖도록 오버로딩 할 수 없다. 연산자 우선순위를 변경할 수 없다. 새로운 기호를 사용하여 연산자를 만들 수 없다.
예제를 위한 복소수 클래스 선언 Complex class using member functions class Complex { double re, im; public: Complex(double r, double i) : re(r), im(i) {} Complex add (const Complex&) const; Complex multiply (const Complex&) const; Complex& post_pp(); // 후위 ++ Complex& pre_pp(); // 전위 ++ Complex& assign(const Complex&) const; void print() { cout << "Real : " << re << " , " << "Imaginary : " << im << endl; } // .… }; 복소수 클래스(Complex class) 실수부와 허수부를 나타내는 두개의 멤버(re, im)가 있다. double타입의 인자 두개를 받는 생성자가 존재한다. add() : 복소수를 하나 받아들여서 덧셈하는 멤버 함수 multiply() : 복소수를 하나 받아들여서 곱하는 멤버 함수 post_pp() : 자신이 포함된 연산을 수행한 후, 1을 증가시키는 멤버함수 pre_pp() : 1을 먼저 증가시킨 후 자신이 포함된 연산을 수행하게 하는 멤버함수 assign() : 복소수 하나를 인자로 받아서 그 값들로 자신의 멤버 값을 바꾸는 멤버함수 print() : 실수부와 허수부를 출력하는 멤버 함수
Binary Operator : + 복소수의 합을 구하는 함수를 작성하자. Complex Comoplex::add (const Complex &c) const { Complex sum; sum.re = re + c.re; sum.im = im + c.im; return sum; } Complex Comoplex::operator+ (const Complex &c) const { Complex sum; sum.re = re + c.re; sum.im = im + c.im; return sum; } [예제] 위의 함수를 사용하는 프로그램의 main() 부분 int main(void) { Complex c1(1,2), c2(3,4), c3, c4; c3 = c1.operator+(c2); c4 = c1 + c2; c3.print(); c4.print(); return 0; } [예제] 위의 멤버 함수를 전역 함수로 바꿔보았다. Complex operator+ (const Complex &c1 , const Complex &c2) const Complex sum; sum.re = c1.re + c2.re; sum.im = c1.im + c2.im; return sum; 단, 전역함수의 경우 사용자 정의 데이터 타입, 이 경우엔 Complex 클래스의 friend함수로 선언되어야 한다.
Binary Operator : * 복소수의 곱을 구하는 함수를 작성하자. Complex Comoplex:: multiply (const Complex &c) const { Complex mul; mul.re = re * c.re; mul.im = im * c.im; return mul; } Complex Comoplex::operator* (const Complex &c) const { Complex mul; mul.re = re * c.re; mul.im = im * c.im; return mul; } [예제] 위의 함수를 사용하는 프로그램의 main() 부분 int main(void) { Complex c1(1,2), c2(3,4), c3, c4; c3 = c1.operator*(c2); c4 = c1 * c2; c3.print(); c4.print(); return 0; } [문제] 위의 멤버함수 operator*를 전역함수로 바꾸어보자.
Unary Operator : ++(전위) 실수부와 허수부 각각을 1 증가시키는 증가 연산자를 작성하자. 전위 증가 연산자 : (예)++a; Complex& Comoplex:: pre_pp() { ++re; ++im; return *this; } Complex Comoplex::operator++ () { ++re; ++im; return *this; } [예제] 위의 함수를 사용하는 프로그램의 main() 부분 int main(void) { Complex c1(1,2), c2(3,4); c1.operator ++(); ++c1; ++c2; c2.operator ++(); c1.print(); c2.print(); return 0; } [문제] 위의 멤버함수 operator++을 전역함수로 바꾸어보자.
Unary Operator : ++(후위) 실수부와 허수부 각각을 1 증가시키는 증가 연산자를 작성하자. 후위 증가 연산자 : (예) a++; Complex Comoplex:: post_pp() { Complex tmp; tmp.re = re; tmp.im = im; re++; im++; return tmp; } Complex Comoplex:: operator++(int a) { Complex tmp; tmp.re = re; tmp.im = im; re++; im++; return tmp; } 전위 증감 연산자와 후위 증감 연산자를 구별하기 위하여 후위 연산자 오버로딩시에 dummy argument 를 하나 갖도록 한다. [예제] 위의 함수를 사용하는 프로그램의 main() 부분 int main(void) { Complex c1(1,2), c2; c2 = c1++; c1.print(); c2.print(); return 0; } [문제] 위의 멤버함수 operator++을 전역함수로 바꾸어보자. [문제] 전위 감소 연산자와 후위 감소 연산자에 해당하는 멤버 함수를 작성하여 보자. dummy argument
<< operator 복소수가 print() 함수를 통해서가 아니라 바로 출력될 수 있도록 오버로딩 하자. 출력 시에 osteam객체가 연산자의 왼쪽에 오고, 출력 대상인 사용자 정의 데이터 타입이 오른쪽에 와야 하므로 전역 함수로 오버로딩 한다. 단일 대상 출력인 경우 ostream객체를 반환할 필요가 없지만, 연속 출력의 경우에는 반드시 ostream객체를 반환하여야 한다. 예) 단일 출력 : cout << c1; 연속 출력 : cout << c1 << “, “ << c2 << endl; ostream& operator<< (ostream& os, Complex& c) const { os << "Real : " << c.re << " , " << "Imaginary : " << c.im; return os; } [예제] 위의 함수를 사용하는 프로그램의 main() 부분 int main(void) { Complex c1(1,2), c2; c2 = c1++; operator<<(cout, c1) << endl; cout << c2 << endl; return 0; }
Subscripting Operater : [] 사용자 정의 데이터형의 배열 요소에 접근할 때 사용하는 []연산자를 작성하자. 연관 매핑[Associative Mapping)에 사용한다. class Student { Jumsu myjumsu[5]; int ind; char name[20]; public: Student(char* nm):ind(0){strcpy(name, nm);}; void savegrade(const Jumsu& j); void print() const; Jumsu& operator[] (int); }; [예제] 위의 연산자를 오버로딩 하여 프로그램을 작성하자. #include <iostream.h> #include <string.h> class Jumsu{ int lectureno; int grade; public: Jumsu(int no=0, int val=0):lectureno(no),grade(val){}; friend ostream& operator<< (ostream&, const Jumsu&); }; ostream& operator<< (ostream& os, const Jumsu& j) { os << "[ " << j.lectureno << " : " << j.grade << " ] "; return os; } //================================================================ // 다음 페이지 계속 ...
[예제] 위의 연산자를 오버로딩 하여 프로그램을 작성하자. – (cont’d) class Student { Jumsu myjumsu[5]; int ind; char name[20]; public: Student(char* nm):ind(0){strcpy(name, nm);}; void savegrade(const Jumsu& j); void print() const; Jumsu& operator[] (int); }; void Student::savegrade(const Jumsu& j) if (ind <= 5) myjumsu[ind++]=j; } void Student::print() const cout << "이름 : " << name << endl; cout << "점수 : " << endl; for (int i=0; i<5; i++) cout << '\t' << myjumsu[i] << endl; Jumsu& Student::operator [] (int n) return myjumsu[n]; //================================================================ int main(void) Student iam("Hong"); for (int i=1; i<6; i++) iam.savegrade(Jumsu(i*10, i*100)); iam.print(); iam[0] = Jumsu(10, 90); iam[1] = Jumsu(20, 80); return 0;
Assignment Operator (1) 복소수를 복소수에 대입하는 연산자를 오버로딩 해보자. Complex Comoplex::assign (const Complex &c) const { re = c.re; im = c.im; return *this; } Complex Comoplex::operator= (const Complex &c) const { re = c.re; im = cim; return *this; } 한번의 대입으로 끝난다면 대입 연산자의 반환값은 필요없으나, 이후에 연속적인 대입이 발생한다면 같은 사용자 정의 데이터 타입으로 반환해야 한다. [예제] 위의 연산자를 오버로딩 하여 프로그램을 작성하자. int main(void) { Complex c1(1,2), c2(3,4), c3; c2 = c1 = c3; //이때, 오버로딩 된 대입연산이 사용된다. cout << c1 << endl; cout << c2 << endl; cout << c3 << endl; //각각의 출력값이 어떻게 될까? return 0; }
Assignment Operator (2) 디폴트 대입 연산자 사용자 정의 데이터 타입에 대해 대입 연산자가 오버로딩 되지 않은 자동으로 삽입되어 실행된다. 디폴트 복사 생성자와 마찬가지로 멤버 변수 대 멤버 변수를 복사하는 기능을 지닌다. 메모리 유출과 얕은 복사라는 문제점이 있으므로 메모리를 동적으로 할당하여 사용하는 사용자 정의 데이터 타입인 경우에는 깊은 복사가 이루어지도록 명시적으로 대입 연산자를 오버로딩 하여야 한다. [예제] 반드시 대입연산자가 지정되어야 하는 경우 class Student { Jumsu myjumsu[5]; int ind; char* name; public: Student(char* nm); Student& operator=(const Student& s); }; Student::Student(char* nm) name = new char[strlen(nm)+1]; strcpy(name, nm); ind = 0; } Student& Student::operator=(const Student& s) delete[] name; name = new char0strlen(s.name)+1]; strcpy(name, s.name); //깊은 복사가 이루어진다. return *this;
De-referencing Operator 단항, 후위 연산자 포인터의 역할과 추가적인 작업을 수행하는 용도로 사용된다. ‘Smart Pointer’구현에 적합하다. class Ptr { X* operator->(); //언제나 반환값이 포인터가 된다. }; void f(Ptr p) { p->m = 7; // 위의 내용은 (p.operator->())->m = 7 과 같다. }
Conversion Operator (1) 데이터 타입 변환 int Complex 두 타입간의 형 변환이 필요할 때, 어떻게 지원해야 하는가? 기존 Built-in 타입 간에는 자동 타입 변환이 가능하다. 사용자 정의 데이터 타입도 자동 변환이 되도록 하기 위해서는 변환 연산자를 재정의 해야한다. !!! 데이터 타입 변환 C++에서는 기본 데이터 타입에 대해 호환되는 타입끼리 자동으로 형변환 한다. 기본 데이터 타입 뿐 아니라 사용자 정의 타입에 대해서도 서로간에 변환할 수 있는 클래스를 정의할 수 있다. 자동 변환과 강제 변환 두 가지 방법이 있다
Conversion Operator (2) 규칙 1. C++에서 하나의 전달인자를 취하는 생성자는 그 전달인자 데이터 타입의 값을 클래스 타입으로 변환하는 길을 제공한다. class Complex { double re, im; public: Complex(double r, double i) : re(r), im(i) {}; // 2개의 전달인자를 받는 생성자 Complex(double r) : re(r), im(0) {}; //double타입 1개의 전달인자를 받는 생성자 Complex(int r) : re(r), im(0) {}; // int타입 1개의 전달인자를 받는 생성자 }; 2개의 전달인자를 받는 생성자 => 타입 변환에 사용되지 못한다. double 타입 1개의 전달인자를 받는 생성자 => double 타입을 받아서 Complex타입으로 변환하는데 사용된다. int 타입 1개의 전달인자를 받는 생성자 => int 타입을 받아서 Complex 타입으로 변환하는데 사용된다. [예제] 위의 클래스를 사용하여 자동 형 변환이 이루어지는 함수 int main(void) { Complex c(10, 20); int i = 10; double d = 11.5; c = i; //c = Complex(i)가 수행된 것과 같다. c = d; //c = Complex(d)가 수행된 것과 같다. i = c; //이 문장도 생성자에 의해 수행이 될까? d = c; //이 문장도 생성자에 의해 수행이 될까? return 0; } int -> Complex 자동 타입 변환 가능 double -> Complex 자동 타입 변환 가능
Conversion Operator (3) 규칙 2. 단 하나의 전달인자를 갖는 생성자가 있을지라도 해당하는 생성자가 explicit 키워드로 제한된 경우에는 명시적인 데이터형 변환만 할 수 있다. class Complex { double re, im; public: Complex(double r, double i) : re(r), im(i) {}; // 2개의 전달인자를 받는 생성자 Complex(double r) : re(r), im(0) {}; //double타입 1개의 전달인자를 받는 생성자 explicit Complex(int r) : re(r), im(0) {}; // int타입 1개의 전달인자를 받는 생성자 }; [예제] 위의 클래스를 사용하여 자동 형 변환이 이루어지는 함수 int main(void) { Complex c(10, 20); int i = 10; double d = 11.5; c = i; //수행이 될까? error~!! c = Complex(i); //이렇게 명시적인 변환이 필요하다. c = (Complex) i; c = d; //수행이 될까? explicit키워드가 없으므로 수행된다. return 0; } int -> Complex 자동 타입 변환 불가능 double -> Complex 자동 타입 변환 가능
Conversion Operator (4) 규칙 3. Conversion Function을 정의하여 사용자 정의 데이터 타입을 다른 타입으로 변환하도록 할 수 있다. class Complex { double re, im; public: Complex(double r) : re(r), im(0) {}; //double타입 1개의 전달인자를 받는 생성자 Complex(int r) : re(r), im(0) {}; // int타입 1개의 전달인자를 받는 생성자 operator int() { return (int)re; }; //데이터 타입 이름이 함수의 이름이 된다. operator double() {return re; }; }; [예제] 위의 클래스를 사용하여 자동 형 변환이 이루어지는 함수 int main(void) { Complex c1(10, 20), c2(11.5, 22.4); int i; double d; d = c1; //Complex->double 변환 i = c2; //Complex->int 변환 cout << "d : " << d << endl; cout << "i : " << i << endl; return 0; } Complex -> int 타입 변환 가능 Complex -> double 타입 변환 가능
Conversion Operator (5) 주의할 점 변환 함수는 클래스의 메서드이어야 한다. 변환 함수는 전달인자를 가질 수 없다. 변환 함수는 반환 데이터 타입을 가지지 않는다. 변환 과정에서 데이터의 손실이 발생할 수 있다. 생성자에 의한 자동 변환이나 변환 함수를 사용하는 경우에나 모두 함수 선택에 있어서 모호한 상황이 발생하면 오류가 발생한다. [문제] 위의 클래스를 사용하였을 때 함수의 선택에 모호한 상황이 발생하는 경우를 알아보고 수정하자. class Complex { double re, im; public: Complex() : re(0), im(0) {}; Complex(double r) : re(r), im(0) {}; Complex(int r) : re(r), im(0) {}; operator int() { return (int)re; }; operator double() {return re; }; }; int main(void) { Complex c; long l = 10; c = l; l = c; //잘 수행이 되는가? 수행이 되지 않는다면 수정하여 보자. return 0; }
사용 금지를 위한 오버로딩 class X { private: void operator=(const X&); }; void f(X a, X b) { a=b; // error:operator=private &a; // error: operator& private a,b; // error:operator,private } 클래스를 만들면 모든 클래스에 자동으로 생성되는 연산자들이 있다. 이 때 이것을 막고 싶은 경우 그 연산자에 대해 오버로딩을 하면서 private 영역에 선언하면 외부에서 사 용하는 것이 금지되므로 결과적으로는 사용할 수 없는 연산자가 되는 효과가 나타난다.
[문제] 연산자 오버로딩과 관련된 다음의 예제를 수행하여 보고 오류가 나는 부분을 해결하자. // complex.h 헤더파일 #ifndef _COMPLEX_H_ #define _COMPLEX_H_ #include <iostream.h> class Complex { double re, im; public: Complex() : re(0), im(0) { cout << "default contructor called" << endl; } Complex(int r) : re(r), im(0) { cout << "int argument constructor called" << endl;} Complex(double r) : re(r), im(0) { cout << "double argument constructor call" << endl;} Complex(double r, double i) : re(r), im(i) {} Complex operator+(const Complex&) const; Complex operator-(const Complex&) const; Complex operator*(const Complex&) const; Complex& operator+=(Complex&); Complex& operator-=(Complex&); bool operator==(Complex&); bool operator!=(Complex&); bool operator>(Complex&); bool operator<(Complex&); Complex& operator++(); Complex operator++(int); Complex* operator()(); Complex operator=(const Complex&); void print() { cout << "Real : " << re << " , " << "Imaginary : " << im << endl; } friend ostream& operator<< (ostream&, const Complex&); operator int() { return (int)re; }; //데이터 타입 이름이 함수의 이름이 된다. operator double() {return re; }; }; #endif //다음 페이지 계속...
[문제] 연산자 오버로딩과 관련된 다음의 예제를 수행하여 보고 오류가 나는 부분을 해결하자. (cont’d) // complex.cpp 소스파일 #include "complex.h" Complex Complex::operator+(const Complex& c) const { Complex sum; sum.re = re + c.re; sum.im = im + c.im; return sum; } Complex Complex::operator-(const Complex& c) const Complex min; min.re = re - c.re; min.im = im - c.im; return min; Complex Complex::operator*(const Complex& c) const Complex mul; mul.re = re * c.re; mul.im = im * c.im; return mul; Complex& Complex::operator+=(Complex& c) re += c.re; im += c.im; return *this; }; Complex& Complex::operator-=(Complex& c) re -= c.re; im -= c.im; bool Complex::operator==(Complex& c) { return (re == c.re && im == c.im) ? true : false; }; bool Complex::operator!=(Complex& c) { return !(operator==(c)); }; bool Complex::operator>(Complex& c) { return (re > c.re || ( re == c.re && im > c.im)) ? true : false; }; bool Complex::operator<(Complex& c) { return ( !(operator==(c)) && !(operator>(c)) ) ? true : false; }; //다음 페이지 계속...
[문제] 연산자 오버로딩과 관련된 다음의 예제를 수행하여 보고 오류가 나는 부분을 해결하자. (cont’d) // complex.cpp 소스파일 – 계속 Complex& Complex::operator++() //전위 증가 연산 { ++re; ++im; return *this; } Complex Complex::operator++(int a) //후위 증가 연산 Complex tmp; tmp.re = re; tmp.im = im; re++; im++; return tmp; Complex* Complex::operator()() Complex* n = new Complex(re, im); return n; }; ostream& operator<< (ostream& os, const Complex& c) os << "Real : " << c.re << " , " << "Imaginary : " << c.im; return os; Complex Complex::operator=(const Complex& c) re = c.re; im = c.im; //다음 페이지 계속...
[문제] 연산자 오버로딩과 관련된 다음의 예제를 수행하여 보고 오류가 나는 부분을 해결하자. (cont’d) // main.cpp 소스파일 #include <iostream.h> #include "complex.h" int main(void) { Complex a = Complex(1, 3.1); Complex b = Complex(1.2, 2); Complex c; Complex d; // operator call check c = a.operator+(b); // c = a+b cout << c << endl; c = b.operator+(a); // c = b+a d.operator+=(b); // a += b cout << d << endl; // implicit conversion and constructor call check Complex e = a + 2; // e = a + complex(2) cout << e << endl; Complex g = a + 2.0; cout << g << endl; // global function call check : no 2.operator+(complex) Complex f = 2 + a; cout << f << endl; // comparator function check bool b1 = a.operator==(b); bool b2 = c == d; if (b1) cout << "b1 true (a == b)" << endl; else cout << "b1 false(a != b)" << endl; if (b2) cout << "b2 true(c == d)" << endl; else cout << "b2 false(c != d)" << endl; Complex *clone = a(); cout << clone << endl; d = a++; cout << "[d] " << d << ", [a] " << a << endl; d = ++a; return 0; }
연습문제(1) (2) 문자열 인자를 하나 받는 생성자 문자열을 다룰 수 있는 String 클래스를 생성하시오. 생성자 : (1) 인자가 전혀 없는 생성자 (2) 문자열 인자를 하나 받는 생성자 (3) 복사 생성자 소멸자 대입연산자 오버로딩(=) 비교 연산자 오버로딩(<, >, ==, !=) 배열 연산자 오버로딩([]) : 문자열 내 특정 위치의 문자 한개를 반환하는 연산자와 대입시키는 연산자 입출력 연산자 오버로딩(<<, >>) 문자열의 길이를 반환하는 함수 String 클래스의 선언 class String { char* str; int len; public: String(); String(const char*); String(const String&); ~String(); String& operator= (const String&); //대입연산자 String& operator< (const String&); //비교연산자 String& operator> (const String&); String& operator== (const String&); String& operator!= (const String&); char& operator[] (int i); //i번째의 위치에 문자 대입 const char& operator[] (int i) const; //i번째 문자 반환 friend ostream& operator<< (ostream&, const String&); //출력연산자 friend istream& operator>> (istream&, String&); //입력연산자 int length(); //문자열의 길이를 반환하는 함수 };
연습문제(2) 정수의 배열을 관리하는 Array 클래스를 “4장. 클래스”장의 연습문제에서 작성 하였다. 그 중 연산자와 관련된 기능을 수행하는 멤버 함수를 연산자 오버로딩으 로 수정하시오. 특정 위치의 데이터를 가져오는 함수 특정 위치의 데이터를 변경하는 함수 배열 데이터 전체를 출력하는 함수 Array 클래스의 선언 class Array { int arr*; int size; //현재 배열의 크기 int idx; //현재 데이터 갯수 public: Array(int s=5); ~Array(); int& operator[] (int i); //int SetData(int i, int data); const int& operator[] (int i) const; // int GetData(int) int AddData(int); //마지막 요소로 인자의 값을 추가 int DelData(int); //i번째 요소를 삭제 friend ostream& operator<< (ostream&, const Array&); // void Print(); private: bool isfull(); //배열이 꽉 찼는지 검사 bool isempty(); //배열이 비어있는지 검사 };
Chapter 07 템플릿
클래스템플릿(Class Template) (1) int 타입 데이터를 관리하는 스택(stack) 클래스를 만들어보자. 스택 : LIFO(Last Input, First Out)특징을 지니는 자료구조. 마지막에 들어간(push) 데이터가 가장 먼저 나오게되는(pop) 형식의 저장구조를 말한다. class Stack { private : int data_arr[10]; //데이터 갯수 최대 10개인 스택 정의 int top; bool is_empty() const {return top<=0;}; bool is_full() const {return top>=10;}; public : Stack():top(0){}; ~Stack(){} bool push(const int&); //스택에 데이터를 저장한다. bool pop(int&); //스택에서 최상위 데이터를 꺼내온다. }; [예제] 위에서 선언된 Stack클래스의 멤버 함수와 이를 사용하는 main()함수 bool Stack::push(const int& data) { if (is_full()){ cout << "PUSH Failure : The Stack Is Full" << endl; return false; } else { data_arr[top++] = data; return true; bool Stack::pop(int& data) if (is_empty()){ cout << "POP Failure : The Stack Is Empty." << endl; data = data_arr[--top]; return true; int main(void) Stack mydata; int n; for (int i=0; i<10; i++) if (mydata.push(i*10)) cout << "i: " << i << ", data: " << i*10 << ", Pushed!!" << endl; for (i=9; i>=0; i--) if (mydata.pop(n)) cout << "i : " << i << ", data : " << n << endl; return 0;
클래스템플릿(Class Template)(2) 앞의 int 타입 스택구조를 보고 char타입 스택 구조 클래스를 정의하여 보자. 방법1 : int 타입 스택 클래스 코드를 전부 복사한 후 char 타입 자료를 처리할 수 있도 코드를 수정한다. : 단점 - 데이터 타입을 변경해야 할 때마다 계속해서 헤더 파일을 바꾸어 주어야 한다. - 같은 종류의 멤버와 멤버 함수를 갖고, 데이터 타입만 다른 클래스에 대해 중복 코딩 되어야 하며, 이는 유지보수에 어려움을 낳는다. 방법2 : int 타입 스택 클래스를 보고 실제 사용시 필요한 데이터 타입만 인자로 넘겨서 처리할 수 있는 템플릿을 생성시킨다. [예제] 앞위에서 선언된 int타입 Stack클래스선언을 복사하여 char타입으로 바꾸어보았다. class Stack { private : char data_arr[10]; //데이터 갯수 최대 10개인 스택 정의 int top; bool is_empty() const {return top<=0;}; bool is_full() const {return top>=10;}; public : Stack():top(0){}; ~Stack(){} bool push(const char&); //스택에 데이터를 저장한다. bool pop(char&); //스택에서 최상위 데이터를 꺼내온다. };
클래스템플릿(Class Template)(3) 인자를 받아서 새로운 클래스를 생성해 주는 메타 클래스 클래스 템플릿 인자 : 데이터 타입, 포인터, 상수 값, 함수 등일 올 수 있다. 템플릿 인자 클래스 생성된 클래스들
클래스템플릿(Class Template)(4) 클래스 템플릿의 기본 구조 template <typename T> class ClassName { private : T member1; T member2; //클래스 템플릿에서 사용할 멤버 선언 public : ClassName(); ~ClassName(){} void MFunction1(T&, T&); T MFunction2(); //외부에서 접근 가능한 멤버함수들에 대한 선언 및 정의 }; template < typename T > 이 선언 아래에 나오는 클래스가 일반 클래스가 아닌 템플릿 임을 알려준다. 해당 템플릿과 관계된 모든 멤버 함수들에 대해 정의할 때에는 클래스 제한자로서 클래스 이름만 이 아니라 이 선언이 항상 함께 사용되어야 한다. T는 아직 결정되지 않은 데이터 타입을 의미하며 다른 문자 혹은 문자열(예: “Type”)이 사용되어 도 좋다.. typename은 class로 바꾸어 사용해도 된다. 그러나 typename은 추가된 키워드로서, 먄악 typename으로 작성했을 때 오류가 발생하면 class로 바꾸어 사용해야 한다. template <typename T> == template <class Type>
클래스템플릿(Class Template)(5) 이제 앞의 int 타입 스택클래스를 템플릿으로 바꾸어보자. template <typename T> class Stack { private : T data_arr[10]; //데이터 갯수 최대 10개인 스택 정의 int top; bool is_empty() const {return top<=0;}; bool is_full() const {return top>=10;}; public : Stack():top(0){}; ~Stack(){} bool push(const T&); //스택에 데이터를 저장한다. bool pop(T&); //스택에서 최상위 데이터를 꺼내온다. }; [예제] 위에서 선언된 Stack 클래스 템플릿의 두개의 멤버 함수(push, pop)을 정의하였다. template <typename T> bool Stack<T>::push(const T& data) { if (is_full()){ cout << "PUSH Failure : The Stack Is Full" << endl; return false; } else { data_arr[top++] = data; return true; bool Stack<T>::pop(T& data) if (is_empty()){ cout << "POP Failure : The Stack Is Empty." << endl; data = data_arr[--top];
클래스템플릿(Class Template)(6) 클래스 템플릿의 사용 템플릿을 사용할 때에는 인자로 넘겨주어야 할 데이터 타입 혹은 상수 값 등이 명시 되어야 한다. 사용 예 Stack 클래스의 사용 : Stack mystack; Stack 클래스 템플리의 사용 : //템플릿에서 처리하는 데이터가 각각 int, char 타입임을 인자로 전달하였다. Stack<char> mystack1; Stack<int> mystack2; Stack<int> mystack1; 컴파일러가 위의 코드를 만나는 순간 템플릿을 바탕으로 모든 T타입을 int로 바꾸어 하나의 클래 스 정의를 생성하게 된다. 이것을 “암시적 구체화”라 한다.
클래스템플릿(Class Template)(7) 상수 값을 템플릿 인자로 받는 클래스 템플릿 Stack의 최대 크기를 실행 시 동적으로 결정될 수 있도록 데이터 타입과 상수 값을 인자로 가지는 템플릿으로 수정하자. template <typename T, int s> class Stack { private : T* data_arr; int size; int top; bool is_empty() const {return top<=0;}; bool is_full() const {return top>=10;}; public : Stack(); ~Stack(); bool push(const T&); bool pop(T&); }; [예제] 데이터 타입과 상수값을 인자로 가지는 Stack 클래스 템플릿을 정의하고 사용한 프로그램 #include <iostream.h> //클래스 선언은 //위의 것과 같아서 생략하였으므로 이 자리에 클래스 선언을 그대로 복사한다. template <typename T, int s> Stack<T,s>::Stack():size(s),top(0) { data_arr = new T[size]; }; Stack<T,s>::~Stack() delete[] data_arr; bool Stack<T,s>::push(const T& data) if (is_full()){ cout << "PUSH Failure : The Stack Is Full" << endl; return false; } else { data_arr[top++] = data; return true;
[예제] 데이터 타입과 상수값을 인자로 가지는 Stack 클래스 템플릿을 정의하고 사용한 프로그램 (cont’d) template <typename T, int s> bool Stack<T,s>::pop(T& data) { if (is_empty()){ cout << "POP Failure : The Stack Is Empty." << endl; return false; } else { data = data_arr[--top]; return true; int main(void) Stack<int,10> mydata; //데이터 형이 int이고, 스택 사이즈가 10 int n; cout << "[PUSH] ==================================\n"; for (int i=0; i<10; i++) if (mydata.push(i*10)) cout << "i : " << i << ", data : " << i*10 << ", Success" << endl; cout << "[POP] ==================================\n"; for (i=9; i>=0; i--) if (mydata.pop(n)) cout << "i : " << i << ", data : " << n << endl; Stack<char,5> mydata2; //데이터 형이 char이고, 스택 사이즈가 5 char ch = 'A'; for (i=0; i<5; i++) if (mydata2.push(ch+i)) cout << "i : " << i << ", data : " << char(ch+i) << ", Success" << endl; for (i=4; i>=0; i--) if (mydata2.pop(ch)) cout << "i : " << i << ", data : " << ch << endl; return 0;
클래스템플릿(Class Template)(8) 클래스 템플릿의 특수화(Specialization) 특정 데이터 타입이 전달되었을 때에만 호출이 되도록 클래스 템플릿 특수화 시켜 정의할 수 있다. template <typename T> class Stack {}; : 어느 데이터 타입에나 상관없이 부르게 되는 포괄적인 클래스 템플릿 template <> class Stack<char*> {}; : 특별히 char* 타입의 데이터들이 Stack 클래스 템플릿 인자로 전달되었을 때에만 사용되는 특수화된 템플릿 : 이 때, 특별한 타입에만 적용되어야 하는 기능 들이 재정의 되거나 추가될 수 있다. [예제] Stack 클래스 템플릿에 char* 타입에 대한 특수화를 정의하여 사용한 프로그램 #include <iostream.h> #include <string.h> //포괄적으로 정의된 템플릿 template <typename T> class Stack { private : T data_arr[10]; //데이터 갯수 최대 10개인 스택 정의 int top; bool is_empty() const {return top<=0;}; bool is_full() const {return top>=10;}; public : Stack():top(0){}; ~Stack(){} bool push(const T&); //스택에 데이터를 저장한다. bool pop(T&); //스택에서 최상위 데이터를 꺼내온다. }; bool Stack<T>::push(const T& data) cout << "call : General Template PUSH" << endl; if (is_full()){ cout << "PUSH Failure : The Stack Is Full" << endl; return false; } else { data_arr[top++] = data; return true; } //다음 페이지에 계속
[예제] Stack 클래스 템플릿에 char* 타입에 대한 특수화를 정의하여 사용한 프로그램 (cont’d) template <typename T> bool Stack<T>::pop(T& data) { cout << "call : General Template POP" << endl; if (is_empty()){ cout << "POP Failure : The Stack Is Empty." << endl; return false; } else { data = data_arr[--top]; return true; //=========================================================================== // char* 데이터 타입에 대해 특수화된 템플릿 template <> class Stack<char*> private : char* data_arr[10]; // char* 데이터 갯수 최대 10개인 스택 정의 int top; bool is_empty() const {return top<=0;}; bool is_full() const {return top>=10;}; public : Stack():top(0){}; ~Stack(){} bool push(const char*); //스택에 char*데이터를 저장한다. bool pop(char*); //스택에서 char*최상위 데이터를 꺼내온다. }; bool Stack<char*>::push(const char* data) cout << "call : Specialization Template PUSH" << endl; if (is_full()){ cout << "PUSH Failure : The Stack Is Full" << endl; char* temp; temp = new char[strlen(data)+1]; strcpy(temp, data); data_arr[top++] = temp; //다음 페이지에 계속
[예제] Stack 클래스 템플릿에 char* 타입에 대한 특수화를 정의하여 사용한 프로그램 (cont’d) template <> bool Stack<char*>::pop(char* data) { cout << "call : Specialization Template POP" << endl; if (is_empty()){ cout << "POP Failure : The Stack Is Empty." << endl; return false; } else { top -= 1; strcpy(data, data_arr[top]); return true; int main(void) Stack<int> mydata1; int n; mydata1.push(10); mydata1.push(20); mydata1.push(30); mydata1.pop(n); cout << "==============================================\n"; Stack<char*> mydata2; char data[20]; mydata2.push("AAA"); mydata2.push("BBB"); mydata2.push("CCC"); mydata2.push("DDD"); mydata2.push("EEE"); mydata2.pop(data); return 0;
클래스템플릿(Class Template)(9) 클래스 템플릿의 상속 (cont’d) 특징 1. : 일반 클래스로부터 클래스 템플릿으로의 상속이 가능하다. 특징2. : 클래스 템플릿 간의 상속이 가능하다. class Slink {......}; template <typename T> class Tnode : public Slink { T val; }; main() {Tnode<int> mynode;} template <typename T> class vector {......}; template <typename T> class Vec: private vector<T> {......};
클래스템플릿(Class Template)(10) 클래스 템플릿의 상속 특징 3. : 일반 클래스나 클래스 템플릿은 템플릿을 멤버로 가질 수 있다. template <typename Scalar> class Complex { Scalar re, im; public: template<typename T> Complex(const Complex<T>& c) : re(c.re), im(c.im) {} //...... }; Complex<float> cf(0,0); Complex<double> cd = cf; 이 경우에는 scalar : double, T: float가 된 것
클래스템플릿(Class Template)(11) 클래스 템플릿의 상속 특징 4. : 템플릿 인자간의 계층 관계는 템플릿 정의 시 무의미해진다. 특징 5. : 타입 변환 연산자를 정의하여 서로 다른 템플릿 인자에 대한 타입 변환 작업을 가능하게 할 수 있다. 타입 변환 연산자를 이용한 예제 프로그램 template <typename T> //pointer to T class Ptr{ T* p; public: Ptr(T*); template <typename T2> operator Ptr<T2>(); //convert Ptr<T> to Ptr<T2> //...... }; template <typename T> template <typename T2> Ptr<T>::operator Ptr<T2> () { return Ptr<T2>(p); } void f(Ptr<Circle> pc) { Ptr<Shape> ps = pc; //OK: can convert Circle* to Shape* Ptr<Circle> pc2 = ps; //error: cannot convert Shape* to Circle* }
함수 템플릿(Function Template)(1) int 타입 두개의 인자를 서로 바꾸는 Swap함수를 정의해보자. 앞의 int 타입 Swap함수를 보고 char타입 Swap함수를 정의하자. 복사를 하여 int라는 글자들을 모두 char로 수정할 것인가? 클래스 템플릿과 마찬가지로 실제 함수 실행시 필요한 데이터 타입만 인자로 넘겨주어서 처리할 수 있는 템플릿을 생성하여 사용하자. void Swap(int &n, int &m) { int temp; temp = n; n = m; m = temp; }
함수 템플릿(Function Template)(2) 인자를 받아 동일한 기능의 새로운 함수를 생성해 주는 함수 함수 템플릿 인자 : 데이터 타입만 올 수 있다. 템플릿 인자 함수 템플릿 생성된 함수들 다양한 데이터 타입에 대하여 동일한 기능을 하는 함수를 적용해야 한다면 각각의 데이터 타입마다 별개 로 정의하지 말고 템플릿을 사용하는 것이 좋다. 함수 템플릿은 함수를 만드는 것이 아니라 함수를 정의하는 방법을 컴파일러에게 알려주는 것이다.
함수 템플릿(Function Template)(3) 이제 앞의 int 타입 Swap함수를 템플릿으로 바꾸어보자. Swap 함수 템플릿의 사용 template <typename T> void Swap(T &n, T &m) { T temp; temp = n; n = m; m = temp; } [예제] 위에서 선언된 Swap 함수 템플릿을 사용하는 프로그램 #include <iostream.h> template <typename T> void Swap(T& n, T& m); int main(void) { int i=50, j=10; cout << "[Before Swap]\t"; cout << "i = " << i << ", j = " << j << endl; Swap<int>(i, j); //저절로 int를 처리하는 Swap함수가 생성된다. cout << "[After Swap]\t"; char ch1='A', ch2='Z'; cout << "ch1 = " << ch1 << ", ch2 = " << ch2 << endl; Swap(ch1, ch2); //저절로 char를 처리하는 Swap함수가 생성된다. return 0; } Swap<int>(n1, n2); //명시적으로 처리하려는 데이터 타입을 인자로 넘김 Swap(ch1, ch2); //명시적으로 데이터 타입을 주지 않으면 함수의 매개변수의 //타입으로 유추하여 생성한다.
함수 템플릿(Function Template)(4) Swap함수 템플릿의 데이터 타입으로 사용자 정의 데이터 타입 객체를 사용하자. 위의 Person 데이터 타입 객체를 Swap함수의 데이터로 넘겨서 실행하여보자. 아무런 문제 없이 수행이 잘 될까? Class Person { private : char* name; char* phoneno; int age; public : Person(); ~Person(); void print() const; }; 사용자 정의 데이터 타입을 템플릿에 적용하기 위해 고려할 점 템플릿 내에서는 T 타입의 객체에 대해 비교연산(<, >, == 등)과 대입연산(=), 그리고 복사 생성 연산을 수행할 경우가 빈번하게 발생한다. 따라서 사용하고자 하는 T 타입에 이들 연산자들에 대 한 정의가 있어야 한다.
[문제] Person 클래스 타입 객체를 두 개 선언하여 Swap함수에 넣어 서로 바꾸는 작업을 하는 예제이다. 오류가 발생하는 곳을 찾아보고 수정하여 보자. #include <iostream.h> #include <string.h> class Person { private : char* name; char* phoneno; int age; public : Person(); Person(char* n, char* p, int a); ~Person(); void print() const; }; Person::Person() name = new char[strlen("NONE")+1]; strcpy(name, "NONE"); phoneno = new char[strlen("NONE")+1]; strcpy(phoneno, "NONE"); age = 0; } Person::Person(char* n, char* p, int a) name = new char[strlen(n)+1]; strcpy(name, n); phoneno = new char[strlen(p)+1]; strcpy(phoneno, p); age = a; Person::~Person() delete[] name; delete[] phoneno; //다음 페이지에 계속
[문제] Person 클래스 타입 객체를 두 개 선언하여 Swap함수에 넣어 서로 바꾸는 작업을 하는 예제이다. 오류가 발생하는 곳을 찾아보고 수정하여 보자. (cont’d) void Person::print() const { cout << "Name : " << name << ", Phone : " << phoneno << ", Age : " << age << endl; } template <typename T> void Swap(T& n, T& m) T temp; temp = n; n = m; m = temp; int main(void) Person hong("Hong", "111-1234", 23), jang("Jang", "222-3333", 30); cout << "[Before Swap]\n"; hong.print(); jang.print(); Swap<Person>(hong, jang); cout << "[After Swap]\n"; return 0;
[문제] Person 클래스 타입 객체를 두 개 선언하여 Swap함수에 넣어 서로 바꾸는 작업을 하는 예제에서 오류가 발생하는 곳을 수정하였다. class Person { private : char* name; char* phoneno; int age; public : Person(); Person(char* n, char* p, int a); ~Person(); void print() const; Person& operator= (const Person &p); //대입 연산자 오버로딩 추가~!!! }; Person& Person::operator= (const Person &p) delete[] name; name = new char[strlen(p.name)+1]; strcpy(name,p.name); delete[] phoneno; phoneno = new char[strlen(p.phoneno)+1]; strcpy(phoneno, p.phoneno); age = p.age; return *this; }
함수 템플릿(Function Template)(5) 함수 템플릿의 특수화(Specialization) template <typename T> void Swap(T& a, T& b) : 어느 데이터 타입이나 관계없이 부르도록 하는 포괄적인 템플릿 template <char> void Swap(char& a, char& b) : 특별히 char 타입의 데이터들이 Swap함수의 인자로 전달되었을 때에만 사용 되도록 하는 특수화된 템플릿 : 이 때, 템플릿 인자의 데이터 타입과 Swap함수의 매개변수의 데이터 타입이 같은 경우에는 템플릿 인자의 데이터 타입을 생략할 수 있다. 즉, template <> void Swap(char& a, char& b) 혹은 template <> void Swap<char>(char& a, char& b) 도 가능한 표기법이다. [예제] Swap함수에 char 타입에 대한 특수화를 정의하여 사용한 프로그램 #include <iostream.h> #include <string.h> //포괄적인 템플릿 template <typename T> void Swap(T& n, T& m) { T temp; temp = n; n = m; m = temp; cout << "General Template." << endl; } //특수화된 템플릿 template <> void Swap(char& n, char& m) char temp; cout << "Swap Specialization for CHAR ~!!" << endl;
[예제] Swap함수에 char 타입에 대한 특수화를 정의하여 사용한 프로그램 int main(void) { int i=10, j=50; cout << "[Before Swap]\t"; cout << "i = " << i << ", j = " << j << endl; Swap(i, j); cout << "[After Swap]\t"; double d1=1.11, d2=2.22; cout << "d1 = " << d1 << ", d2 = " << d2 << endl; Swap(d1,d2); char ch1 = 'A', ch2 = 'Z'; cout << "ch1 = " << ch1 << ", ch2 = " << ch2 << endl; Swap(ch1,ch2); return 0; }
함수 템플릿(Function Template)(6) 특징1 템플릿 인자가 템플릿 함수 인자에서 유추될 수 없는 경우에는, 어떤 템플릿 인자를 받을 것인지 명시적으로 표현해야 한다. 유추 가능한 경우에는 명시적으로 데이터 타입을 표현하지 않아도 된다. 특징2 동일한 이름을 가진 여러 함수 템플릿과 일반 함수들이 정의될 수 있다. : 일반적인 함수와 마찬가지로 함수 템플릿도 오버로딩 되며, 이 때 매개변수로 확 실하게 구분이 되어야 한다. 동일한 이름을 갖는 함수가 여러개일 때 우선순위 규칙에 의해 올바른 함수를 선정하여 부르게 된다. 특징3 경우에 따라서는 함수 템플릿 인자를 명시적으로 기술하여야 모호성을 제거하고 실행 가능하게 된다. 함수 호출의 우선순위 전달 인자가 정확하게 대응되며, 일반 함수가 함수 템플릿보다 우선 순위가 높다. 포괄적으로 정의된 함수 템플릿 보다는 특정 매개변수가 지정이된 특수화된 템플릿이 우선 순위 가 높다. 함수 템플릿 인자를 명시적으로 기술하여 모호성을 제거한 예 template<typename T> max(T,T); const int s = 7; void k() { max(1, 2); //max<int>(1,2) max(‘a’, ‘b’); //max<char>(‘a’,’b’) max(2.7, 4.9); //max<double>(2.7,4.9) max(s,7); //max<int>(int(s),7) (trivial conversion used) max(‘a’, 1); //error : ambiguous(no standard conversion) max(2.7, 4); //error : ambiguous(no standard conversion) } void f() max<int>(‘a’, 1); //OK. max<int>(int(‘a’),1) max<double>(2.7, 4); //OK. max<double>(2.7, double(4))
연습문제 배열 관리 템플릿(Tarray)을 작성하시오. 정수의 배열을 관리하는 Array 클래스를 “6장. 연산자 오버로딩”장의 연습문제에서 작성하였다. 그 Array클래스를 int타입 뿐 아니라 다른 데이터 타입으로도 사용할 수 있도록 템플릿으로 수정하시오.
Chapter 08 예외(Exceptions)
전통적인 방식의 예외 처리 방식 전통적인 예외(에러)의 처리 방식 문제점 프로그램의 종료(abort()호출) 에러 코드 값의 반환 정상적인 값을 반환토록 하여 정상적으로 프로그램을 진행하거나 비정상적으로 동작시킴 에러 처리 함수 호출 문제점 정상적인 프로그램 수행 코드와 에러 처리 코드의 경계가 불분명함 에러 상황에 대한 정보를 얻기 어려움 에러에 대한 체계적인 관리 방법 부재 [예제] 나눗셈에서 0으로 나누는 예외에 대한 전통적인 처리 방식 #include <iostream.h> #include <stdlib.h> double divide(double, double); int main(void) { double d1 = 10, d2 = 20; cout << d1 << " / " << d2 << " = " << divide(d1,d2) << endl; d2 = 0; return 0; } double divide(double n, double m) if (m == 0) abort(); return n/m;
예외 처리 방식 (1) C++의 예외 처리 방식 예외 처리 문법 구조 step1. 예외를 정의(?)하고 void f() { try { EGC(); //error generation (function call) ...... } catch (E1) {......} catch (E2) {......} void EGC() { if (...) { //예외 상황 발생 throw E1(...); } else { //정상 로직 ......
예외 처리 방식 (2) 예외 처리 관련 키워드 try 예외 발생에 대해 검사를 수행할 범위를 설정한다. catch 발생한 예외를 포착하고 처리하는 블럭을 선언할 때 사용한다. catch 키워드로 시작하고 중괄호로 이루어지는 블록내부에 처리 내용을 코딩 한다. catch 블록은 try블록의 바로 뒤에 위치시킨다. throw 특정 예외 상황이 발생하였음을 알린다. throw 키워드로 시작하며, 바로 뒤에 그 예외의 특징을 나타내는 문자열이나 객체와 같은 하나의 값을 반환한다. 이 값의 타입으로 여러 개의 catch 블록 중 실제 실행될 블록을 찾아낸다. [예제] 나눗셈에서 0으로 나누는 예외에 대한 C++ 의 에러 처리 방식 #include <iostream.h> #include <stdlib.h> double divide(double, double); int main(void) { try{ double d1 = 10, d2 = 20; cout << d1 << " / " << d2 << " = " << divide(d1,d2) << endl; d2 = 0; } catch (const char* msg){ cout << "## Error~!! : " << msg <<" ##" << endl; return 0; double divide(double n, double m) if (m == 0) throw "0으로 나눌 수 없습니다."; return n/m;
예외 처리 방식 (3) try 블록 설정 catch 블록 설정 하나의 함수 내에 둘 이상의 try블록이 설정될 수 있다. 하나의 try 블록 내부에 다른 try블록이 내포될 수 있다. try 블록도 명시적인 블록이므로 해당 블록 내에서 선언된 변수들은 그 블록이 끝나면 사용할 수 없다. catch 블록 설정 catch 블록 설정 시 해당 블록에서 처리할 예외의 타입를 명시한다. 명확한 타입 대신 생략 기호(...)를 사용하면 모든 종류의 예외를 포착할 수 있다. 하나의 try블록 뒤에 둘 이상의 catch블록들이 올 수 있으며, 앞에서부터 차례대로 예외의 타입이 맞는지 검사 하게 된다. 경우에 따라 catch블록에서 해당 예외에 대한 처리를 직접 하지 않고 다시 throw할 수 있다. (예) [예제] 앞의 예제에 여러 개의 try블록과 catch블록을 설정해 보았다. #include <iostream.h> #include <stdlib.h> double divide(double, double); int main(void) { try{ double d1 = 10, d2 = 20; cout << d1 << " / " << d2 << " = " << divide(d1,d2) << endl; d2 = 0; } catch (char* msg){ //문자열 예외 처리 cout << "## Error~!! : " << msg <<" ##" << endl; double d1 = 10; //위의 try블록과 똑같은 d1 이 다시 선언 되어야 한다. if (d1 < 100) throw -1; catch (...){ //모든 종류의 예외 처리 cout << "## Error~!! ##" << endl; return 0; double divide(double n, double m) if (m == 0) throw "0으로 나눌 수 없습니다."; return n/m; catch (int num) { if (num ==0) cout << “This Number is Zero” << endl; else throw; }
예외 명세 함수 예외 명세(exception specification) 예외 명세를 함으로써, 해당 함수가 발생시킬 수 있는 예외들의 종류를 지정할 수 있다. 인터페이스 사용자들에게 어떤 예외가 발생되는 지 알려주기 위한 것이다. void f (int a) throw (x1, x2) {......} double g (double n, double m) throw ( ){......} - f() 함수에서는 x1, x2 타입의 예외나 혹은 그로부터 파생된객체 타입의 예외가 발생할 수 있다. - g() 함수에서는 예외를 발생시키지 않는다. [예제] 앞의 나눗셈 예제에서 divide()함수에 예외 명세를 해보았다. #include <iostream.h> #include <stdlib.h> double divide(double, double); int main(void) { try{ double d1 = 10, d2 = 20; cout << d1 << " / " << d2 << " = " << divide(d1,d2) << endl; d2 = 0; } catch (char* msg){ cout << "## Error~!! : " << msg <<" ##" << endl; return 0; double divide(double n, double m) throw (char*) //문자열 예외의 발생을 의미하는 명세 if (m == 0) throw "0으로 나눌 수 없습니다."; return n/m;
스택 풀기(Stack Unwinding) 발생한 예외에 대하여 처리할 catch 블록이 없는 경우, 자동으로 상위 함수로 예외 객체를 전달한다. 만약 main()함수까지 전달되었을 때에도 처리할 catch() 블록이 없으면 프로그램이 종료된다. step7. throw step6. throw step5. throw f1() f2() f3() f4() step4. E1 예외 발생 : E1처리 catch 블록이 f4(), f3(), f2()에는 없고, f1()에만 있다. [예제] 스택 풀기의 예제 프로그램 #include <iostream.h> void f3(double n); void f2(double n); void f1(double n); int main(void) { try{ double num = -100; f1(num); } catch (char* msg) {cout << "[main()] ##Error!! : " << msg << endl;} return 0; void f1(double n) try{ f2(n); } catch (int no) {cout << "[f1()] ##Error!! : " << no << endl;}; void f2(double n) try{ f3(n); } catch (int no) {cout << "[f2()] ##Error!! : " << no << endl;}; void f3(double n) try{ if (n < 0) throw "The number is too small."; } catch (int no) {cout << "[f3()] ##Error!! : " << no << endl;}; step1. f2() call step2. f3() call step3. f4() call step8. E1 예외 처리
예외와 클래스 (1) 다른 클래스의 멤버로 내포되는 예외 클래스 정의 어떤 함수들이 예외를 발생시킨다면, 발생되는 예외의 타입으로 사용할 클래스를 public 멤버로 정의하는 것이 좋다. class Vector{ int size; public: class ERange {......}; //out of Range Exception class ESize {......}; //bad Size Exception //...... }; void f() { try { use_vector(); } catch (Vector::ERange) {......} catch (Vector::ESize) {......} } [예제] 배열(Array) 클래스의 멤버로 내포되어 배열 첨자 범위에 어긋날 때 사용되는 예외 클래스를 정의한 프로그램 #include <iostream.h> class Array{ int size; int* arr; public: class ESize { int index; ESize(int i):index(i){} void print() const; }; Array(){arr=NULL; size=0;}; Array(int n); ~Array(){ delete[] arr; } int getSize() { return size; } int summary(); int& operator[](int i); const int& operator[](int i) const; //다음 페이지 계속
[예제] 배열(Array) 클래스의 멤버로 내포되어 배열 첨자 범위에 어긋날 때 사용되는 예외 클래스를 정의한 프로그램 (cont’d) void Array::ESize::print() const { cout << "Out of Range : " << index << endl; } Array::Array(int n):size(n) arr = new int[size]; for(int i=0; i<size; i++) arr[i]=0; int Array::summary() int sum=0; for (int i=0; i<size; i++) sum += arr[i]; return sum; int& Array::operator[](int i) if (i < 0 || i >= size) throw ESize(i); //ESize 예외 발생 return arr[i]; const int& Array::operator[](int i) const int main(void) try{ Array mydata(3); //배열 사이즈가 3으로 설정됨 for (int i=0; i<3; i++) mydata[i] = i*10; for (i=0; i<=3; i++) //i=3이면 범위를 초과하게 된다. cout << mydata[i] << endl; catch(Array::ESize &err) //ESize 예외 처리 cout << "## Exception ##\t" ; err.print(); return 0;
예외와 클래스 (2) 전역으로 예외 클래스 정의 특정 개체와 관련되어서만 발생하는 예외가 아니면 전역으로 정의하여도 좋다. class ERange {......}; //out of Range Exception class ESize {......}; //bad Size Exception class Vector{ int size; public: //...... }; void f() { try { use_vector(); } catch (ERange) {......} catch (ESize) {......} } [문제] 바로 앞의 예제에서 Array 클래스의 멤버로 정의되었던 ESize 클래스를 전역으로 정의하여 프로그램 을 수행하여 보자.
예외의 상속 예외 클래스의 상속 어떤 클래스가 public 멤버로 예외 클래스를 가지고 있다면, 그것을 기본으로 하여 파생되는 클래스들은 그 예외 클래스들을 상속한다. 기존에 정의되어 있는 예외 클래스들로부터 새로운 예외 클래스를 파생시킬 수 있다. 동일한 범주의 예외 상황들을 클래스 계층 구조로 표현함으로써 관리가 용이해진다. class MathErr {......}; class Overflow : public MathErr {......}; class Underflow : public MathErr {......}; class ZeroDivide : public MathErr {......}; catch블록 구성시 주의사항 catch 블록은 선언된 순서대로 예외를 검사한다. catch 블록은 가장 구체화된 예외 클래스에 대한 처리가 먼저 나오고, 가장 일반화된 예외 클래스에 대한 처리가 나중에 오도록 구성해야 한다. 만약 일반화된 예외 클래스가 먼저 온다면 구체화된 예외 처리 구문은 결코 수행되지 않는다. catch블록을 다음처럼 구성하면 ZeroDivide에 대한 catch블록은 결코 실행되지 않는다. try { ...... } catch (Overflow) { ...... } catch (MathErr) { ...... } catch (ZeroDivide) { ...... }
[예제] 배열 첨자 범위에 어긋날 때 사용되는 ESize 예외 클래스를 전역으로 정의하고, 그것으로 부터 파생시킨 ChildESize를 정의한 후 에러를 발생시켰다. #include <iostream.h> class ESize { protected: int index; public: ESize(int i):index(i){} virtual void print() const; }; void ESize::print() const cout << "Out of Range : " << index << endl; } class ChildESize : public ESize int lbound; int ubound; ChildESize(int i,int lidx, int uidx) : ESize(i), lbound(lidx), bound(uidx) {}; void print() const; void ChildESize::print() const cout << "Out of Range [" << lbound << " to " << ubound << "]" ; cout << ", Current Index : " << index << endl; class Array{ int size; int* arr; Array(){arr=NULL; size=0;}; Array(int n); ~Array(){ delete[] arr; } int getSize() { return size; } int summary(); int& operator[](int i); const int& operator[](int i) const; //다음 페이지에 계속
[예제] 배열 첨자 범위에 어긋날 때 사용되는 ESize 예외 클래스를 전역으로 정의하고, 그것으로 부터 파생시킨 ChildESize를 정의한 후 에러를 발생시켰다. (cont’d) Array::Array(int n):size(n) { arr = new int[size]; for(int i=0; i<size; i++) arr[i]=0; } int Array::summary() int sum=0; for (int i=0; i<size; i++) sum += arr[i]; return sum; int& Array::operator[](int i) if (i < 0 || i >= size) throw ChildESize(i,0,size); return arr[i]; const int& Array::operator[](int i) const int main(void) Array mydata(3); try{ for (int i=0; i<=3; i++) //예외발생을 위해 일부러 (i <= 3)으로 비교함 mydata[i] = i*10; catch(ESize &err) cout << "## Exception ##\t" ; err.print(); cout << mydata[i] << endl; catch(ChildESize &err) return 0;
예외 주의 사항 예외 사용시 주의 사항 예외 명세를 사용하는 함수에서 예외가 발생하면, 그 예외는 명세 목록에 있는 데이터 타입 중에 하나여야 한다. 그렇지 않으면 Unexpected Exception이 발생하고 프로그램이 중단된다. 발생된 예외에 대하여 catch블록에서 포착되어 처리되어야 한다. 만약 해당하는 catch블록이 없다면 Uncaught Exception이 발생하고 프로그램이 중단된다. 예외를 많이 사용하면 프로그램의 크기가 커지고, 실행 속도가 떨어질 수 있다. 예외가 발생하는 경우, 사용 중이던 리소스에 대한 반환이 정확하게 이루어지도록 catch 블록을 신중하게 구성해야 한다. 사용 중이던 리소스에 대한 반환이 이루어지도록 하는 예 void use_file (const char* fn) { FILE* f = open (fn, “r”); ....... fclose(f); } void use_file (const char* fn) { FILE* f = open (fn, “r”); try { ......... } catch(...) { fclose(f); ......
exception 클래스 exception 클래스 exception 헤더 파일에 선언되어 있다. 다른 예외 클래스들의 기본 클래스로 사용된다. what() 이라는 가상(virtual function) 함수가 주어진다. 이 함수를 이용하여 반환할 문자열을 지정해 줄 수 있다. [예제] 앞에서 본 ESize 클래스를 exception 클래스의 파생 클래스로 정의하고 사용하였다. #include <iostream.h> #include <exception> //exception 헤더파일 포함 class Array{ int size; int* arr; public: class ESize : public exception { int index; ESize(int i):index(i){} const char* what() {return "[Out of Range]" ;}; //가상 함수 what()을 재정의 하여 문자열을 지정하였다. }; Array(){arr=NULL; size=0;}; Array(int n); ~Array(){ delete[] arr; } int getSize() { return size; } int summary(); int& operator[](int i); const int& operator[](int i) const; //다음 페이지에 계속
[예제] 앞에서 본 ESize 클래스를 exception 클래스의 파생 클래스로 정의하고 사용하였다. (cont’d) Array::Array(int n):size(n) { arr = new int[size]; for(int i=0; i<size; i++) arr[i]=0; } int Array::summary() int sum=0; for (int i=0; i<size; i++) sum += arr[i]; return sum; int& Array::operator[](int i) if (i < 0 || i >= size) throw ESize(i); return arr[i]; const int& Array::operator[](int i) const int main(void) try{ Array mydata(3); for (int i=0; i<3; i++) mydata[i] = i*10; for (i=0; i<4; i++) cout << mydata[i] << endl; catch(Array::ESize &err) cout << "## Exception ##\t" << err.what() << endl; return 0;
[예제] Vector 클래스에 예외를 처리할 멤버 함수를 두는 예제 프로그램 #include <iostream.h> class Vector { int* p; int sz; public: enum {MAX = 100}; class Erange //exception class int index; ERange(int i):index(i) {} }; class ESize //exception class int size; ESize(int s):size(s) {} Vector(int s); ~Vector() {delete[] p;} int& operator [] (int i); inline Vector::Vector(int s) { if (s < 0 || s > MAX) throw ESize(s); p = new int [sz = s]; } inline int& Vector::operator[](int i) if (i >= 0 && i < sz) return p[i]; throw ERange(i); void use_vector() try { Vector vec(10000); } catch(Vector::ESize s) {throw Vector::ESize(s.size);} int main(void) Vector vec(10); try{ use_vector(); cout << vec[11] << endl; catch(Vector::ESize s) cout << "Size Error : " << s.size << endl; try { cout << vec[11] << endl; } catch (Vector::ERange r){cout << "Range Error : " << r.index << endl;} catch (Vector::ERange r) { cout << "Range Error : " << r.index << endl; } catch (...) { cout << "Another Error" << endl; } return 0;
[예제] MathErr, Overflow, Underflow, ZeroDivide 간의 상속관계를 나타내어 산술 연산과 관련된 에러를 처 리하였다. #include <iostream.h> const int MAXINT = 32767; const int MININT = -32768; class MathErr { int first, second; public: MathErr(int f=0, int s=0):first(f), second(s) {} int getfirst() { return first; } int getsecond() { return second; } virtual void print() { cout << "This is Math Error " << endl; } }; class Overflow : public MathErr Overflow(int a, int b) : MathErr(a,b) {} void print() { cout << "This is Overflow Error" << endl; } class Underflow : public MathErr Underflow(int a, int b):MathErr(a,b) {} void print() {cout << "This is Underflow Error" << endl; } class ZeroDivide : public MathErr ZeroDivide(int a, int b) : MathErr(a,b) {} void print() {cout << "This is Zero Division Error" << endl; } class MathClass int add(int, int); int subtract(int, int); int multiply(int, int); int divide(int, int); int MathClass::add(int a, int b) int tmp = a + b; if (tmp > MAXINT) throw Overflow(a,b); return tmp; }
[예제] MathErr, Overflow, Underflow, ZeroDivide 간의 상속관계를 나타내어 산술 연산과 관련된 에러를 처 리하였다. (cont’d) int MathClass::subtract(int a, int b) { int tmp = a - b; if (tmp < MININT) throw Underflow(a,b); return tmp; } int MathClass::multiply(int a, int b) int tmp = a * b; if (tmp > MAXINT) throw Overflow(a,b); int MathClass::divide(int a, int b) if (b==0) throw ZeroDivide(a,b); return (int)(a/b); int main(void) MathClass mc; cout << "Maxint = " << MAXINT << endl; cout << "Minint = " << MININT << endl; try{ cout << "Add : " << mc.add(10000,20000) << endl; cout << "Subract : " << mc.subtract(10000,20000) << endl; cout << "Multiply : " << mc.multiply(10, 100) << endl; cout <<"Divide : " << mc.divide(10000,0) << endl; catch (Overflow o) {cout << "Overflow : " << o.getfirst() << " + " << o.getsecond() << endl;} catch (Underflow u) {cout << "Under flow : " << u.getfirst() << " - " << u.getsecond() << endl;} catch (ZeroDivide z) {cout << "ZeroDivide : " << z.getfirst() << " + " << z.getsecond() << endl;} return 0;
[예제] Vector 클래스 템플릿의 멤버로 예외 클래스가 들어간 예제 프로그램 #include <iostream.h> template <typename T> class Vector { T* p; int sz; public: enum { max = 100 }; class ERange // exception class int index; ERange(int i) : index(i) {} }; class ESize // exception class int size; ESize(int s) : size(s) {} Vector(int s); ~Vector() { delete[] p; } T& operator[](int i); template <typename T> inline Vector<T>::Vector(int s) if ( s < 0 || s > max ) throw ESize(s); p = new T[sz =s]; template <typename T> inline T& Vector<T>::operator[](int i) if ( 0 <= i && i < sz ) return p[i]; throw ERange(i); int main(void) try Vector<char> cvec(10); Vector<int> ivec(1000); cout << cvec[11] << endl; } catch (Vector<int>::ESize s) { cout << "Size Error : " << s.size << endl; } catch (Vector<int>::ERange r) { cout << "Range Error : " << r.index << endl; } catch (Vector<char>::ESize s) catch (Vector<char>::ERange r) catch (...) { cout << "Another Error" << endl; } return 0;
Chapter 09 스트림(Stream)
스트림 스트림이란? Program 입력과 출력을 바이트들의 흐름 프로그램과 입력장치, 혹은 프로그램과 출력장치 사이의 매개자 역할을 한다. 표준화된 인터페이스를 제공함으로써, 입력 및 출력 장치의 종류에 상관없이 동일한 처리 방법을 제공한다. Program Keyboard Camera File & etc. Monitor Printer 입력 스트림 출력 스트림 char* Complex Person String int 임의 타입의 객체들 버퍼 입출력 장치에서 프로그램으로 또는 프로그램에서 입출력장치로 정보를 전송하기 위해 사용하 는 임시 저장 장소의 메모리 블록 디스크 혹은 다른 저장 장치에서 매번 직접 한 문자씩을 꺼내와서 프로그램에서 처리하도록 하 는 것 보다는 한꺼번에 읽어서 메모리(버퍼)로 저장한 후 메모리에서 한 문자씩을 가져와 처리하 는 것이 훨씬 효율적이다. 버퍼를 사용한 키보드 입력은 입력한 문자들을 프로그램으로 전송하기 전에 잠시 보관하고 수정 할 수 있다.
스트림 클래스 계층구조 ios fstreambase istream ostream iostream ifstream ofstream 표준 입출력 스트림 객체 fstream 객체 버퍼사용 스트림 cin O 표준 입력 스트림 cout 표준 출력 스트림 cerr X 표준 에러 스트림 clog
output stream : cout (1) cout(ostream) 객체의 기본 출력 연산자 (멤버함수) 모든 Built-in 타입에 대해 “<<“ 연산자 오버로딩 되어 있다. class ostream : public virtual ios { public: // … ostream& operator<<(const char*); // strings ostream& operator<<(char); ostream& operator<<(int); ostream& operator<<(long); ostream& operator<<(float); ostream& operator<<(double); ostream& operator<<(const void*); // pointers //… }; 사용자 정의 클래스에 대한 << 연산자 오버로딩 출력 스트림 클래스를 변경하지 않고 전역함수 형태로 << 연산자를 오버로딩 한다. [예제] 앞에서 등장했던 복소수 클래스를 출력하도록 전역 함수 형태로 <<연산자를 오버로딩하였다. class complex { … friend ostream& operator<<(ostream&,complex); }; ostream& operator<< (ostream& os, Complex& c) const { return os << "Real : " << c.re << " , " << "Imaginary : " << c.im; } int main(void) Complex c1(1,2), c2; c2 = c1++; operator<<(cout, c1) << endl; cout << c2 << endl; return 0; ostream& operator<<(ostream&, UserDefinedClass);
output stream : cout (2) cout 객체의 다른 출력 함수(멤버함수) write() : 전체 문자열을 출력한다. 출력할 문자열과 출력 길이를 인자로 전달하며, 널 문자에 상관없이 무조건 해당 길이만큼의 문자열을 출력한다. ostream& put (char); basic_ostream<charT,traits>& write(const char_type*s, streamsize n); [예제] put()과 write() 함수를 이용한 문자열 출력 예제 #include <iostream.h> #include <string.h> int main(void) { char* name = "My name is Hong."; for (int i=0; i< (int)strlen(name); i++) cout.put(name[i]).put('.'); cout << endl; cout.write(name,strlen(name)).write("\n",1); return 0; }
output stream : cout (3) 출력 형식 지정 출력 시 필드 폭을 지정할 수 있다 : width() 멤버 함수 -> 바로 다음에 출력되는 항목에만 영향을 주고, 원래 기본 설정으로 되돌아간다. 정수 출력 시에 16, 10, 8진법을 선택할 수 있다 : hex(), dec(), oct() 부동 소수점수의 출력 시에 정밀도를 설정할 수 있다. : precision() -> 컴파일러 버전에 따라 precision(n)의 인자로 전달되는 수를 전체 자릿수로 해석할 수도 있고, 소수점 이하의 자릿수로 해석할 수도 있다. 출력 형식을 지정하기 위해 제공되는 멤버 함수들 class ios { // … public : ostream* tie(ostream* s); // tie input to output ostream* tie(); // return “tie” int width(int w); // set field width int width() const; char fill(char); // set fill character char fill() const; // return fill character long flags(long f); long flags() const; long setf(long setbits, long field); long setf(long); long unsetf(long); int precision(int); // set floating point precision int precision() const; // ... };
[예제] int 타입 배열과 double 타입 배열에 숫자를 넣고, 출력 형식을 지정하여 출력하였다. #include <iostream.h> int main(void) { int iarr[5] = {10, 5, 105, 32, 6547}; double darr[5] = {10.1, 10.525, 10.23456, 10, 10.32}; int i; cout << "[정수의 출력]" << endl; cout.width(15); cout << "10진수"; cout.width(15); cout << "16진수"; cout.width(15); cout << "8진수" << endl; for (i=0; i<5; i++){ cout.width(15); cout << dec << iarr[i] << " "; cout.width(15); cout << hex << iarr[i] << " "; cout.width(15); cout << oct << iarr[i] << endl; } cout << "\n[부동소수점수의 출력]" << endl; cout.width(15); cout << "precision(2)"; cout.width(15); cout << "precision(5)" << endl; cout.precision(2); cout.width(15); cout << darr[i]; cout.precision(5); cout.width(15); cout << darr[i] << endl; cout << endl; return 0;
output stream : cout (4) 출력 형태 플래그 설정 setf()와 flags()에 플래그를 사용하여 출력 형태를 지정할 수 있다. const int my_ioflag = ios::left|ios::oct|ios::showpoint|ios::fixed; old_flag = cout.flags(my_ioflag); //이전 플래그 값을 저장함 cout.setf(ios::showpos); //새로운 플래그를 지정 : explicit '+' for positive int 출력 형태를 결정하기 위한 값들 class ios { public: // flags for controlling format: enum { skipw =01, // skip whitespace on input // field adjustment: left =02, // padding after value right =04, // padding before value internal=010, // padding between sign and value // integer base: octal =020, // octal dec =040, // decimal hex =0100, // hexadecimal showbase=0200, // show integer base showpoint=0400, // print tailing zeros uppercase=01000, // 'E', 'X' rather than 'e', 'x' showpos =02000, // explicit '+' for positive ints //floating point notation scientific=04000, // .dddddd Edd fixed =010000, // dddd.dd // flush output: unitbuf =020000, // after each output operation stdio =040000, // after each character }; // ... };
output stream : cout (5) 출력 형태 플래그 설정(cont’d) 정수 출력 시 진법을 선택하는 플래그 정렬방법(right, left, internal)을 설정하는 플래그 cout.setf ( ios::oct, ios::basefield ); // octal cout.setf ( ios::dec, ios::basefield ); // decimal cout.setf ( ios::hex, ios::basefield ); // hexadecimal cout.setf ( ios::left, ios::adjustfield ); // left cout.setf ( ios::right, ios::adjustfield ); // right cout.setf ( ios::internal, ios::adjustfield ); // internal 정수 출력 시 진법 선택 플래그의 사용 예 정렬 방법 선택 플래그의 사용 예 cout.setf(ios::oct,ios::basefield); // octal cout << 1234; // output: 2322 cout.setf(ios::hex,ios::basefield); // hexadecimal cout << 1234; // output: 4d2 cout.setf(ios::showbase); cout << 1234; // output : 0x4d2 cout.width(4); cout << '(' << -12 << ")\n"; // output: ( -12) cout.setf(ios::left, ios::adjustfield); cout << '(' << -12 << ")\n"; // output: (-12 ) cout.setf(ios::internal, ios::adjustfield); cout << '(' << -12 << ")\n"; // output: (- 12)
output stream : cout (6) 출력 형태 플래그 설정(cont’d) 부동 소수점 출력 형태 설정 플래그 cout.setf ( ios::scientific, ios::floatfield ); // scientific number cout.setf ( ios::fixed, ios:: floatfield ); // fixed point number cout.setf (0, ios:: floatfield ); // reset to default 부동 소수점 출력 시 형태 설정 플래그 사용 예 cout << 1234.56789 << '\n'; // 1234.57 cout.setf(ios::scientific,ios::floatfield); cout << 1234.56789 << '\n'; // 1.234568e+03 cout.setf(ios::fixed,ios::floatfield); cout << 1234.56789 << '\n'; // 1234.567890 cout.precision(8); cout << 1234.56789 << '\n'; // 1234.5679 cout.precision(4); cout << 1234.56789 << '\n'; // 1235
[예제] 앞에서 cout 객체의 멤버 함수로 출력 형태를 설정했던 프로그램에서 멤버 함수 대신 setf() 함수의 다양한 플래그 설정으로 바꾸었다. #include <iostream.h> int main(void) { int iarr[5] = {10, 5, 105, 32, 6547}; double darr[5] = {10.1, 10.525, 10.23456, 10, 10.32}; int i; cout << "\n[정수의 출력]" << endl; cout.width(15); cout << "10진수"; cout.width(15); cout << "16진수"; cout.width(15); cout << "8진수" << endl; for (i=0; i<5; i++){ cout.width(15); cout.setf(ios::dec,ios::basefield); //10진수 cout << iarr[i] << " "; cout.setf(ios::hex,ios::basefield); //16진수 cout.setf(ios::oct,ios::basefield); //8진수 cout << iarr[i] << endl; } cout.setf(ios::left, ios::adjustfield); //왼쪽 정렬이 되도록 설정한다. cout << "\n[부동소수점수의 출력]" << endl; cout.width(15); cout << "fixed"; cout.width(15); cout << "Scientific" << endl; cout.precision(5); cout.setf(ios::fixed, ios::floatfield); cout.width(15); cout << darr[i]; cout.setf(ios::scientific, ios::floatfield); cout.width(15); cout << darr[i] << endl; cout << endl; return 0;
output stream : cout (7) 출력 버퍼 비우기 출력 작업 시에 사용하는 버퍼는 다음과 같은 경우에 비우기 작업이 일어난다. 버퍼가 꽉 차게 되면 비운다. 개행 문자를 보내면 버퍼가 비워진다. 입력이 발생했을 때, 입력에 앞서 버퍼가 비워진다. tie() 함수 실행 시 버퍼가 비워진다. 명시적으로 직접 호출하여 버퍼를 비울 수 있다. : flush(), endl()
input stream : cin (1) cin 객체의 기본 입력 연산자 (멤버함수) 모든 Built-in 타입에 대해 “>>“ 연산자 오버로딩 되어 있다. class istream : public virtual ios { // ... public: istream& operator>>(char*); // string istream& operator>>(char&); // character istream& operator>>(short&); istream& operator>>(int&); istream& operator>>(long&); istream& operator>>(float&); istream& operator>>(double&); istream& get(char& c); istream& get(char* p, int n, char ='\n'); istream& putback(char& c); }; 사용자 정의 클래스에 대한 >> 연산자 오버로딩 입력 스트림 클래스를 변경하지 않고 전역함수 형태로 >> 연산자를 오버로딩 한다. 대상이 되는 객체로 값을 전달해야 하므로, 인자의 타입은 참조형으로 받는다. [예제] 앞에서 등장했던 복소수 클래스의 값을 입력받도록 전역 함수 형태로 >>연산자를 오버로딩하였다. istream& operator>>(istream& s, complex& a) { double re =0, im = 0; char c = 0; s >> c; if (c == '(') { s >> re >> c; if (c == ',') s >> im >> c; if (c != ')') s.clear(ios::badbit); // set state } else { s.putback(c); s >> re; if (s) a = complex(re,im); return s; ostream& operator>>(istream&, UserDefinedClass&);
input stream : cin (2) 스트림 상태를 나타내는 값 class ios { // ... public: enum io_state { goodbit =0, eofbit =1, failbit =2, badbit =4, }; int eof() const; // end of file seen int fail() const; // next operation will fail int bad() const; // stream corrupted int good() const; // next operation might succeed}; 스트림 상태를 나타내는 값 멤버 & 멤버함수 내용 goodbit eofbit, failbit, badbit가 설정되지 않으면 설정된다. eofbit 파일 끝에 도달하였을 때 1로 설정된다. failbit 입력이 기대하는 문자를 읽지 못했거나, 출력 연산이 기대하는 문자를 쓰지 못했을 때 1로 설정된다. 일반적으로 복구가 가능한 오류일 때 발생한다. badbit 데이터 스트림이 손상되었을 때 1로 설정된다. 일반적으로 이런 오류는 복구가 불가능하다. good() bad(), fail(), eof() 함수가 false를 반환할 때 true를 반환한다. eof() eofbit = 1일 때 true를 반환한다. fail() failbit = 1일 때 true를 반환한다. bad() badbit = 1 일 때 true를 반환한다. rdstate() 스트림의 오류 상태를 반환한다. setstate (iostate s) clear (rdstate() | s)를 호출하여 s의 상태대로 설정한다. exceptions() 예외를 발생시킨 플래그를 식별하는 비트마스크를 리턴한다. exceptions( iostate ex) clear()를 통해 예외를 발생시킬 상태를 설정한다. clear (iostate s) 스트림 상태를 s로 설정한다. (default : goodbit)
input stream : cin (3) cin(istream) 객체의 다른 입력 함수(멤버함수) get() : 단일 문자 입력 함수이며, 연속적인 호출이 가능하다. 여백을 건너뛰지 않고 데이터형 변환도 수행하지 않으며, 문자를 받아들인다. getline() : 문자열 입력 함수. 1번째 전달인자는 입력 문자열을 저장할 공간이며, 2번째 전달인자는 읽어들일 최대 문자수, 3번째 전달인자는 종료문자로, 이 종료문자를 만나면 지정된 개수의 문자를 읽기 전이라도 입력을 멈춘다. ignore() : 문자열을 읽고 나서, 읽은 문자열을 버리는 함수 istream& get (char& ch) : 입력 문자를 전달인자에 대입한다. get (void) : 입력 문자를 정수형, 보통 int형으로 변환하여 반환한다. istream& getline (char*, int, char = ‘\n’); istream& get (char*, int, char = ‘\n’); [예제] getline()과 get() 함수를 이용하여 주소와 전화번호를 입력받는 프로그램 #include <iostream.h> const int MAXLEN = 255; int main(void) { char input[MAXLEN]; cout << "[getline() 함수]" << endl; cout << "주소를 입력하여 주십시오 : "; cin.getline(input, MAXLEN, '\n'); cout << "ECHO : " << input << endl; cout << "[get() 함수]" << endl; cout << "전화번호를 입력하여 주십시오 : "; char ch; cin.get(ch); cout << "ECHO : "; while (ch != '\n'){ cout << ch; } cout << endl; return 0; istream& ignore (int=1, int=EOF);
input stream : cin (4) cin(istream) 객체의 다른 입력 함수(멤버함수) (cont’d) read() : 주어진 수의 바이트들을 읽어서 지정된 위치에 저장하며, 입력에 널문자를 붙이지 않는다. 보통 키보드 입출력보다는 write()과 함께 파일 입출력에 사 용된다. peek() : 입력 스트림으로부터 추출하는 일 없이 입력에 있는 다음 문자를 반환한다. gcount() : 마지막 추출 멤버함수가 읽은 문자 수를 반환한다. putback() : 어떤 문자를 입력 스트림에 다시 넣는다. (조금 전에 읽은 문자가 아닌 문자도 입력 스트림에 넣을 수 있다.) [예제] read(), peek(), putback()을 넣어서 주소와 전화번호를 입력받았다. #include <iostream.h> int main(void) { cout << "[peek() 함수]" << endl; cout << "주소를 입력하여 주십시오 : "; //주소입력: 방학동 동아@ 102동 307호 => 아파트를 의미하는 @문자를 '아파트'로 출력한다. char ch; while (cin.peek() != '\n'){ cin.get(ch); if (ch == '@'){ cin.putback(' '); cout << "아파트"; } else cout << ch; cout << "\n[read() 함수]" << endl; cout << "전화번호를 입력하여 주십시오 : "; cout.flush(); char input[5]; cin.read(input, 5); input[5] = '\0'; cout << input << endl; return 0;
파일 I/O (1) 파일 입출력의 기본 단계 필요한 헤더 파일을 포함한다. : fstream.h or fstream, iostream.h or iostream 스트림을 관리하기 위해 객체를 생성한다. : ofstream 또는 ifstream 타입의 객체 생성된 객체를 하나의 파일에 연결시킨다. : open() 함수 : 경우에 따라서는 객체 생성과 동시에 파일에 연결시킬 수도 있다. 표준 입출력을 사용하는 것과 동일한 방법으로 그 객체를 사용한다. 사용이 끝난 객체에 대해 파일과의 연결을 닫는다. : close() 함수 파일 입출력의 기본 단계 #include <fstream.h> // 1. 헤더파일 포함 int main(void) { char filename[20] = "fileIO.txt"; ofstream fout; // 2. 파일스트림 객체 생성 fout.open(filename); // 3. 파일과 스트림 객체 연결 fout << "새로운 파일 생성을 축하합니다." << endl; // 4. 파일로 출력 fout.close(); // 5. 파일과 스트림 객체 연결 해제 return 0; }
파일 I/O (2) 파일 열기와 닫기 open() : 파일 입출력을 위해 파일을 열어주는 멤버 함수로, 파일의 이름과 열기 모드, 접근 모드의 세가지 정보를 인자로 받는다. : 파일 열기 모드 void open (const char* filename, int mode, int access); 열기 모드 상수 내용 ios::app 파일의 끝에 추가한다. ios::ate 파일이 열릴 때, 파일의 끝을 찾는다. ios::binary 이진 형식으로 파일을 연다. ios::nocreate 파일이 존재하지 않을 경우 열기에 실패한다. ios::noreplace 파일이 이미 존재하는 경우에 열기에 실패한다. ios::trunc 파일이 존재하는 경우에 그 파일의 크기를 0로 만든다. 파일의 접근 모드 접근모드 내용 정상적인 파일 1 읽기만을 위한 파일 2 숨겨진 파일 4 시스템 파일 8 저장용 비트 설정
파일 I/O (3) 파일 열기와 닫기 (cont’d) is_open() : open()의 실행이 실패하였을 경우 false를 반환하는 멤버 함수 close() : 열린 파일을 닫는 멤버 함수 eof() : 파일의 끝에 도달했는지 검사하여, 파일 끝이라면 0이 아닌 값을 반환하고, 파일 끝이 아니라면 0을 반환한다.
파일 I/O (4) 텍스트 파일(text file)의 입출력 기본적으로 파일 입력과 출력에 사용되는 멤버 함수들은 표준 입출력 함수들과 같은 함수들이다. istream& get (char& ch); //파일에서 단일 문자 입력 ostream& put (char& ch); //파일로 단일 문자 출력 istream& get (char* buf, int num, char delim=‘\n’); //파일로부터 최대 num개의 문자를 ‘\n’이 입력되기 전까지 buf로 입력 istream& getline (char* buf, int num, char delim=‘\n’); int peek(); //파일의 다음 문자를 추출하지 않고 엿보기 istream& putback (char c); //파일로부터 읽어들인 마지막 문자 파일로 되돌리기 ostream& flush(); // 출력 버퍼가 가득 차기 전에 버터의 내용 파일로 출력 [예제] “source.txt” 파일의 내용을 “dest.txt” 파일로 복사한다. #include <fstream.h> const int MAXLEN = 255; int main(void) { char infile[20] = "source.txt"; char outfile[20] = "dest.txt"; char input[MAXLEN]; ifstream fin; ofstream fout; fin.open(infile); fout.open(outfile); while (!fin.eof()){ fin.getline(input, MAXLEN, '\n'); fout << input << endl; } cout << "입력 끝~!" << endl; fin.close(); fout.close(); return 0;
파일 I/O (5) 이진 파일(binary file)의 입출력 이진 파일 : 데이터가 컴퓨터의 내부적인 표현 그대로 저장되는 파일 숫자를 저장하는 데 있어서 반올림 등의 변화 없이 데이터가 그대로 저장되므로 더 정확하고, 처리 속도가 빠르다. 2진 파일의 데이터를 다른 시스템으로 옮기는 것은 표현 방법의 차이로 인해 문제가 발생할 수 있다. 파일을 열 때, 이진 모드(ios_base::binary)로 열어야 한다. istream& read (unsigned char* buf, int num); //num크기 만큼의 이진 데이터 블록을 파일로부터 buf로 입력 ostream& write (const unsigned char* buf, int num); //buf에 있는 num크기 만큼의 이진 데이터를 파일로 출력
[예제] 5명의 친구들의 이름과 나이를 사용자로부터 입력 받아 이진 파일에 저장한 후 다시 화면출력으로 확인해본다. #include <iostream> using namespace std; #include <fstream> typedef struct { char name[20]; int age; } Person; const char* file = "person.dat"; ostream& operator << (ostream& os, Person& p) { os << "이름 : " << p.name << "\t나이 : " << p.age; return os; } int main(void) ofstream fout; fout.open(file, ios_base::out | ios_base::binary); Person myfriend; cout << "[5명의 친구들의 이름과 나이를 입력하여 주십시오.]" << endl; for (int i=0; i<5; i++){ cout << i+1 << ". "; cout << "\t이름 : "; cin >> myfriend.name; cout << "\t나이 : "; cin >> myfriend.age; fout.write((char*)&myfriend, sizeof(myfriend)); cout << "----------------------------" << endl; fout.close(); cout << "[이진 파일에서 데이터를 꺼내옵니다.]" << endl; ifstream fin; fin.open(file, ios_base::in | ios_base::binary); if (fin.is_open()) { for (i=0; i<5; i++){ fin.read((char*)&myfriend, sizeof(myfriend)); cout << myfriend << endl; fin.close(); return 0;
파일 I/O (6) 임의 접근 파일(random file)의 입출력 임의 접근 파일 : 파일 데이터에 처음부터 차례로 접근하는 것이 아니라, 원하는 곳으로 직접 이동하여 처리하는 파일 이진 파일과 마찬가지로 ios::binary로 접근해야 한다. 원하는 위치로 이동하여 데이터를 입력받거나 출력하기 위해서 seekg()과 seekp() 함수를 사용한다. istream& seekg (streampos offset, seek_dir origin); ostream& seekp (streaampos offset, seek_dir origin); //파일의 현재 입력 위치를 origin의 위치에서 offset만큼 떨어진 위치로 이동 streampos tellg(); //파일 포인터의 현재 위치 제공 streampos tellp(); seekg(), seekp() 두 함수의 전달인자 1번째 인자(streampos) : 이동할 값으로, +/- 모두 가능 2번째 인자 (seek_dir) : 임의접근의 시작 위치를 나타냄 ios::beg 파일의 처음 위치 ios::cur 파일 포인터의 현재 위치 ios::end 파일의 끝 위치 istream& seekg (streampos offset, seek_dir origin); ostream& seekp (streaampos offset, seek_dir origin);
[예제] 5명의 친구들의 이름과 나이를 입력 받아 이진 파일에 저장한 파일을 사용하여 파일의 처음이나 중간, 끝에 데이터를 추가하는 임의 파일 작업을 해보자. #include <iostream> using namespace std; #include <fstream> typedef struct { char name[20]; int age; } Person; const char* file = "person.dat"; ostream& operator << (ostream& os, Person& p) { return os << "이름 : " << p.name << "\t나이 : " << p.age; } int main(void) { fstream fio; fio.open(file, ios_base::in | ios_base::out | ios_base::binary); Person myfriend; if (fio.is_open()){ //처음으로 가서 현재의 전체 데이터 화면 출력 cout << "[현재 전체 데이터 출력]" << endl; fio.seekg(0, ios::beg); while( fio.read((char*)&myfriend, sizeof(myfriend)) && !fio.eof() ) cout << myfriend << endl; if (fio.eof()) fio.clear(); //마지막 친구의 데이터를 읽어온다. fio.seekg(-sizeof(myfriend), ios::end); fio.read((char*)&myfriend, sizeof(myfriend)); cout << "수정 중 ... " << myfriend << endl; //나이를 바꾸어 현재의 위치에 데이터를 저장함 - 현재의 위치가 어딜까? myfriend.age += 10; fio.seekp(0, ios::cur); fio.write((char*)&myfriend, sizeof(myfriend)) << flush; if (fio.fail()) cout << "ERROR~!!!" << endl; //다시 처음으로 가서 전체 데이터 화면 출력 cout << "[수정된 전체 데이터 다시 출력출력]" << endl; } else cout << "File Oper Error ~!!!" << endl; fio.close(); return 0;
Chapter 10 이름공간(Namespace)
이름 공간 Namespace A Namespace B c12 c1 v1 v2 c3 v1 c2 func1 c11 c8 func7 이름을 지니는 공간 프로그램의 논리적 그룹을 표현하기 위한 메커니즘 c7 func5 func4 func3 c10 c16 c10
이름 공간의 선언 이름 공간의 선언과 정의 이름 공간의 기본 구성 namespace 라는 키워드를 사용하여 새로운 이름 공간을 생성한다. 이름 공간은 전역 위치에 혹은 다른 이름 공간에 선언할 수 있다. 블록 내부에는 이름 공간을 선언할 수 없다. 변수, 객체, 함수의 이름으로 동일한 이름이 서로 다른 이름 공간에 정의될 수 있다. 이름 공간의 기본 구성 namespace namespace-name { // declaration and definitions } 또는 // declarations namespace-name::member-name // definition 이름 공간의 기본 구성의 예 //선언방법1 namespace Bank1 { class Account public: char CustomerName[20]; char AccountNo[20]; double balance; //...... }; //선언방법2 namespace Bank2 class Account; class Bank2::Account
이름 공간 내부 객체 사용 (1) 이름 공간 내부 객체 사용 이름 공간 안에 선언되어 있는 객체나 변수, 함수에 접근하기 위해서는 범위 지정 연산자(::)를 사용하여 소속을 밝힌다. namespace Bank { class Account private: char CustomerName[20]; char AccountNo[20]; double balance; public: Bank::Account::Account(char*, char*, double); void print(); }; double todaycount = 10; double totalbalance = 100000; [예제] 위의 Bank 이름 공간 내부의 클래스와 변수를 출력하는 프로그램 #include <iostream.h> #include <string.h> //이곳에 위의 Bank 이름 공간의 선언이 들어온다. Bank::Account::Account(char* name, char* no, double balance) { strcpy(CustomerName,name); strcpy(AccountNo, no); balance = balance; totalbalance += balance; } void Bank::Account::print() cout << "Name : " << CustomerName << ", AccountNo : " << AccountNo << ", Balance : " << balance << endl; int main(void) Bank::Account myacc("Hong","134-28-927",1000); myacc.print(); cout << "Today Count : " << todaycount << endl; //Error:소속(이름공간)을 밝히지 않음. cout << "Total Balance : " << Bank::totalbalance << endl; //OK. return 0;
이름 공간 내부 객체 사용 (2) using 선언 이름 공간 안에 소속된 객체들에 대해 접근할 때마다 언제나 범위 지정 연산자를 사용하여 소속(이름공간)을 밝히는 것은 귀찮은 일. using 선언을 이용하여 쉽고 편하게 접근할 수 있다. using Bank::Account; //프로그램의 전역에 위의 코드를 넣으면, 프로그램 전역에서 //Bank::Account라고 소속을 밝히지 않고 간단히 Account 로 사용할 수 있다. //특정 함수 내에 위의 코드를 넣으면, 그 함수 내에서만 Account로 간단히 //부를 수 있다. [예제] 앞의 예제에서 Bank::Account를 using 선언으로 수정하였다. //Bank 이름 공간과 관련된 클래스 선언과 함수 정의가 이곳에 포함된다. using Bank::Account; int main(void) { Account myacc("Hong","134-28-927",1000); //Bank::Account대신 간편하게 사용 myacc.print(); cout << "Today Count : " << Bank::todaycount << endl; cout << "Total Balance : " << Bank::totalbalance << endl; return 0; } //Bank 이름 공간 내부의 변수들에 대해서는 using선언되지 않았으므로 //todaycount와 totalbalance는 그 소속을 밝혀야 한다.
이름 공간 내부 객체 사용 (3) using namespace 지시자 이름 공간 내부의 객체 하나에 대해서만 소속을 미리 밝혀주는 using선언과 달리 using 지시자는 하나의 이름 공간 내부의 모든 변수, 객체, 함수에 대해 범위 지정 연산자 없이 사용할 수 있게 한다. 이 지시자를 이용하게 되면 내부 객체나 함수 등을 사용하기에는 편리하겠지만 이름 충돌이 발생할 확률이 높아지게 되므로 권장하지 않는다. 이름 공간 내부에서 필요한 것들만을 선택하여 using 선언을 하는 것을 권장한다. using namespace Bank; //프로그램의 전역에 위의 코드를 넣으면, 프로그램 전역에서 //Bank 이름 공간에 관련된 모든 변수나 객체, 함수에 그 이름 만으로 간편하게 //사용할 수 있다. [예제] 앞의 예제에서 Bank 이름 공간을 using namespace를 이용하여 지시되도록 수정하였다. //Bank 이름 공간과 관련된 클래스 선언과 함수 정의가 이곳에 포함된다. int main(void) { using namespace Bank; Account myacc("Hong","134-28-927",1000); myacc.print(); cout << "Today Count : " << todaycount << endl; cout << "Total Balance : " << totalbalance << endl; return 0; } //main()함수 내에 using namespace Bank;가 선언되었으므로 main() 함수 내부에서만 이름 공 간에 대한 소속을 밝히지 않고 사용할 수 있으며, 다른 함수나 전역 공간에서는 그 소속을 분명 하게 밝혀야 한다.
이름 공간 내부 객체 사용 (4) 이름 공간의 중첩 이름 공간의 내부에 다시 다른 이름의 공간을 중첩할 수 있다. 내부에 중첩된 이름 공간에 선언된 이름(함수, 변수 등)에 접근할 때에는 외부의 이름 공간에 대한 소속도 함께 밝혀야 한다. namespace Bank { class Account { ...... }; namespace Status double todaycount = 10; double totalbalance = 100000; ;} }; Bank::Status::todaycount = 0; //계층적으로 이름 공간을 밝힌다. Bank::Status::totalbalance = 0; [예제] 앞의 예제에서 Bank 이름 공간내부에 todaycount와 totalbalance를 Status라는 이름의 이름 공간으 로 묶어서 선언하면 앞의 프로그램예제는 다음처럼 두개의 함수가 바뀌게 된다. Bank::Account::Account(char* name, char* no, double balance) { strcpy(CustomerName,name); strcpy(AccountNo, no); balance = balance; Status::totalbalance += balance; } int main(void) Bank::Account myacc("Hong","134-28-927",1000); myacc.print(); cout << "Today Count : " << Bank::Status::todaycount << endl; cout << "Total Balance : " << Bank::Status::totalbalance << endl; return 0;
이름 공간 내부 객체 사용 (5) 이름이 지정되지 않은 이름 공간 이름이 없는 이름 공간을 선언할 수 있다. 이름 없는 공간에 선언된 멤버들은 항상 자유롭게 접근 가능하다. 이름이 없으므로, using선언이나 using namespace 지시는 할 수 없다. 보통 내부 링크를 가지는 정적 변수(static 변수) 대신 이 기능을 이용하는 경우가 많다. namespace { int a; void f() { …. } int g() { …. } } namespace $$$ using namespace $$$; 자동인식
이름 공간 내부 객체 사용 (6) 함수 오버로딩(function overloading) 서로 다른 이름 공간에 정의된 동일한 이름의 함수가 서로 다른 전달 인자를 가지면 이들에게는 함수 오버로딩의 규칙이 적용된다. namespace A { void f (int); } namespace B { void f (char); } using namespace A; using namespace B; void g() { f(‘a’); //calls the f() from B }
이름 공간 내부 객체 사용 (7) 이름 공간의 다중 정의 이름 공간은 여러 번 정의하여 멤버들을 추가할 수 있다. namespace A{ int f(); //now A has member f() } int g(); //now A has two members, f() and g() [예제] 위의 Bank 이름 공간도 두번에 나누어 정의할 수 있다. namespace Bank { class Account private: char CustomerName[20]; char AccountNo[20]; double balance; public: Bank::Account::Account(char*, char*, double); void print(); }; //현재는 Bank 이름 공간에는 Account 클래스 하나만 소속되어 있다. //................................... double todaycount = 10; //같은 이름의 이름 공간 변수 double totalbalance = 100000; //이렇게 되면 Bank 이름 공간에는 이제 Account 클래스와 todaycount, totalbalance 두개의 변수가 소속된다.
이름 공간 내부 객체 사용 (8) using 선언과 using namespace 지시자 사용시 주의할 점 int main(void) { int todaycount = 99; //같은 이름의 지역변수 using Bank::todaycount; //Error~!! : 이름충돌 Account myacc("Hong","134-28-927",1000); myacc.print(); cout << "Today Count : " << todaycount << endl; return 0; } [예제] Bank 이름 공간과 같은 이름의 지역 변수(totalcount)가 main()함수 내부에 존재하는 경우에 출력되는 값은 무엇인지 확인하자. //Bank 이름 공간과 관련된 클래스 선언과 함수 정의가 이곳에 포함된다. //Bank 이름 공간에는 todaycount, totalbalance가 선언되어 있다. int main(void) { //같은 이름의 지역변수 선언 int todaycount = 99; int totalbalance = 9999999; using namespace Bank; Account myacc("Hong","134-28-927",1000); myacc.print(); //지역변수의 값? or 이름 공간 변수의 값? or ERROR? cout << "Today Count : " << todaycount << endl; cout << "Total Balance : " << totalbalance << endl; return 0; }
표준 이름 공간 (1) 특징 : 이전 표준 라이브러리들과 템플릿 라이브러리들을 표준 이름 공간에 통합 하여 관리함. 특징 : 이전 표준 라이브러리들과 템플릿 라이브러리들을 표준 이름 공간에 통합 하여 관리함. C 라이브러리 관련 파일들 cassert, cctype, cerrno, cfloat, climits, clocale, cmath, csetjmp, csignal, cstdarg, cstddef, cstdio, cstdlib, cstring, ctime, cwchar, cwctype Old C++ 라이브러리 관련 파일들 exception, complex, fstream, iomanip, ios, iosfwd, iostream, istream, limits, locale, ostream, sstream, stdexcept, streambuf, strstream, valarray C++ 표준 템플릿 라이브러리들 algorithm, deque, functional, iterator, list, map, memory, numeric, queue, set, stack, utility, vector 이름 공간을 사용하는 라이브러리의 예 // <iostream> namespace std { extern istream cin; extern ostream cout; extern ostream cerr; extern ostream clog; // … }; // <exception> namespace std { class exception; class bad_exception; typedef void (*terminate_handler)(); typedef void (*unexpected_handler)(); terminate_handler set_terminate(terminate_handler ph) throw(); unexpected_handler set_unexpected(unexpected_handler ph) throw(); void terminate(); void unexpected(); bool uncaught_exception(); }; // <cstdlib> namespace std { #include <stdlib.h> //... };
표준 이름 공간 (2) // <iostream> namespace std { extern istream cin; [예제] 앞의 Bank 이름 공간에서 포함하고 있는 헤더파일(iostream.h)을 이름 공간을 사용하는 같은 기능의 헤더파일(iostream)로 바꾸어 사용하였다. #include <iostream> #include <cstring> using std::cout; using std::endl; namespace Bank { class Account private: char CustomerName[20]; char AccountNo[20]; double balance; public: Bank::Account::Account(char*, char*, double); void print(); }; namespace Status double todaycount = 10; //같은 이름의 이름 공간 변수 double totalbalance = 100000; Bank::Account::Account(char* name, char* no, double balance) strcpy(CustomerName,name); strcpy(AccountNo, no); balance = balance; Status::totalbalance += balance; } void Bank::Account::print() cout << "Name : " << CustomerName << ", AccountNo : " << AccountNo << ", Balance : " << balance << endl; using Bank::Status::todaycount; using Bank::Status::totalbalance; int main(void) Bank::Account myacc("Hong","134-28-927",1000); myacc.print(); std::cout << "Today Count : " << todaycount << std::endl; cout << "Total Balance : " << totalbalance << endl; return 0; // <iostream> namespace std { extern istream cin; extern ostream cout; extern ostream cerr; extern ostream clog; // … }; // <exception> namespace std { class exception; class bad_exception; typedef void (*terminate_handler)(); typedef void (*unexpected_handler)(); terminate_handler set_terminate(terminate_handler ph) throw(); unexpected_handler set_unexpected(unexpected_handler ph) throw(); void terminate(); void unexpected(); bool uncaught_exception(); }; // <cstdlib> namespace std { #include <stdlib.h> //... };