본문 바로가기
  • fishing...
  • eating...
MISCELLANEOUSNESS

[C++] C++ 설명서

by 회색뿔 2007. 9. 16.
 
출처 블로그 > 령이의 공부방
원본 http://blog.naver.com/ourilove/100007557296
CLASS

1.1 class의 소개

① data type : 일반적으로 data type이라 하면 특정 data structure를 갖는 값과 그 값들에 대한 operation을 함께 말하는 것.

② data abstraction : 어떠한 data type을 사용할 때 data type의 data structure나 operation의 implementation에 대해서는 전혀 생각하지 않고 programming할 수 있어야 한다는 원칙을 의미.


③ class : class란 사용자가 정의한 data structure와 그 data structure를 사용하는 operation을 가리킴. C++에서는 이 class라는 개념을 통해서 data abstraction을 제공.


④ member

eg. class Complex {

double re, im;

}; → Complex라는 class가 double형의 두 변수 re, im을 갖고 있으며 이들을 가리켜 class의 member라 한다. (구체적으로 data member와 member fn이 있다.)


⑤ public member : class의 member중 외부에서도 사용할 수 있는 member. 'public:' 키워드 사용.

⑥ private member : class의 member중 외부에서는 사용이 불가능한 member. 'private:' 키워드 사용. → member fn을 통해서만 access가능


⑦ private member와 public member를 구분하는 이유 : 외부에서는 public member만을 사용함으로써 그 class를 사용하는 다른 program의 변경 없이도 public member의 수정이 가능하도록 하기위한 것. (→ "encapsulation" 즉, information hiding)


⑧ constructor를 사용하여 object를 생성할 때 특정 값의 할당.

→ constructor란 class와 같은 이름을 갖는 일종의 member fn으로서, 이 fn은 object가 생성될 때마다 호출. constructor에게 전달할 parameter는 object를 선언할 때 함께 할당.

eg. class Complex {
private :
double re, im;
public :
Complex(double x = 0, y = 0) { re = x; im = y };
// .....
};

Complex w; // Complex(,) 즉, 0 + 0i.
Complex v(3); // Complex(3,) 즉, 3 + 0i.
Complex u(3,4); // Complex(3,4) 즉, 3 + 4i.


 

1.2 class와 member
1.2.1 class definition
: class definition은 object의 상태를 표현하기 위한 data structure와 이것을 사용할 fn, 그리고 이들 member들의 access specifier(사용 범위를 제한하는 표시자)로 이루어진다.
⇒ ★ 중요 : class member의 사용을 제한하는 단위가 각 object가 아니라 class단위이다. 그러므로 같은 class에 속하는 object사이에는 서로의 private member들을 아무런 제한 없이 사용할 수.
eg. void Point :: operator+=(const Point& p) {
xp += p.xp; // 즉, xp += p.x()처럼 쓸 필요가 없다.
yp += p.yp;
}


 

1.2.2 inline function (확장 함수)

① fn을 호출하는 대신에 fn의 code를 직접 삽입함으로써 program의 수행을 최적화하기 위한 fn.

② 'inline'이라는 키워드를 사용하여 선언.

③ class내에서 정의된 member fn은 모두 inline fn으로 간주.


1.2.3 self reference (자기 참조)

① member fn은 자신을 호출한 object를 this라는 포인터 변수를 사용하여 가리킬 수.

eg. class Point {
private :

int xp, yp;

public :

int x() const { return xp; }

// int x() const { return this->xp; }

int x(int nx) { return xp = nx; }

// int x(int nx) { return this->xp = nx; }

};


② member fn에서 this는 항상 그 member fn을 호출한 object에 대한 포인터 변수로서, 다음과 같이 암시적으로 선언되어 있다.

class-name *const this;


③ this는 ,특히 object의 address를 필요로하는 lists 구조등에서 유용하게 사용.

eg. class list {
// .....

list* prev;

list* next;

public :

list* insert(list* l)

{

l->prev = prev;

prev->next = l;

l->next = this;

prev = l;

return this;

}

// .....

};


 

1.2.4 const member fn (상수 멤버 함수)

① fn 명칭과 fn code 사이에 const라는 키워드.

② const member fn이란 fn 내에서 그 fn을 호출한 object의 상태를 변경하지 않는 fn, 즉 그 object의 member에 새로운 값을 할당하지 않는 fn을 말함.

③ C++에서는 const object에 대한 member fn 호출은 const member fn에 대해서만 가능하도록 하여, programmer가 원하지 않는 곳에서의 object 상태 변화를 방지해 줌.

e.g., class blackbox {
private :

int x;

// .....

public :

int readx( ) const { return x; } // O.K.

int write(int n) const { return x = n; } // ERROR

};


1.3 constructor & destructor


<<< constructor >>>

① 사용자가 정의한 class object도 정수 기본 data type과 같은 방법으로 초기화할 수 있도록 constructor라는 개념을 두고 있음.

② constructor는 object가 생성될 때 마다 수행되는 일종의 초기화 함수로, class name과 동일한 name으로 선언.


③ 다른 일반 fn과 마찬가지로 overloaded fn name을 사용할 수 있으며, 이렇게 함으로써 다양한 형태의 initialize가 가능.

eg. class Point {
private :

int xp, yp;

public :

Point() { xp = 0; yp = 0; }

Point(int x, int y) { xp = x; yp = y; }

Point(int x) { xp = x; yp = 0; }

// .....

};


 

④ constructor의 argument로도 default argument를 사용할 수 있으므로, 위의 Point class의 constructor들은 다음과 같이 하나로 줄일 수.

eg. Point(int x = 0, int y = 0) { xp = x; yp = y; }


⑤ object의 initialize는 다른 object를 통해서도 가능.

eg. Point p(3,4);

Point q = p; // initialize


<<< destructor >>>

① class name 앞에 물결 표시(~).

② destructor를 갖고 있는 class의 object가 소멸될 때 마다 항상 호출.


1.4 friends


※ encapsulation : 한 object의 private member는 그 class에서 정의된 member fn에서만이 사용 가능. 이것은 외부에서 public member, 즉 class에서 제공한 외부 인터페이스만을 사용하도록 함으로써, 그 class를 사용하는 다른 program의 변경없이도 class의 구현 내용을 변경할 수 있도록 하기위한 것.

⇒ 이렇듯 S/W의 구현과 인터페이스를 분리하는 것은 reusable S/W를 만드는 열쇠가 되며, S/W의 evolution(진화)를 매우 용이하게 해줌.


① friend로 선언된 fn은 그 class의 private member를 마음대로 access할 수.


② 선언은 class의 private나 public 영역 어디에 있어도 상관 없음.


③ Complex class의 operator overloading(+=)에서 첫번째 인자가 double인 경우 ERROR이다. 그러므로 friend fn을 이용해서 다음과 같이 할 수있다.


eg. class Complex {
// .....

Complex(int r = 0, int i = 0) { re = r; im = i}

friend Complex& operator+=(Complex&, Complex&)

};


Complex& operator+=(Complex& z1, Complex& z2)

{

z1.re += z2.re;

z1.im += z2.im;

return z1;

}


Complex z(4, 5);

double d = 4.5;


z += d; // operator+=(z, Complex(d,0))

d += z; // operator+=(Complex(d,0), z)


→ operator+=() 함수를 friend로 선언하면 첫번째 인자에서도 형변환이 일어날 수 있으므로 'd+=z'식이 허용.


④ friend는 fn 뿐만 아니라 class에 대해서도 선언할 수.

eg. class Schedule {
// .....

};

class Queue {

// ... friend class Schedule;

};


→ friend로 선언된 class의 모든 member fn에게 그 class의 모든 사용 권한을 부여. 즉, 이 선언에서 friend로 선언된 Schedule class의 모든 member fn 내에서는 Queue class의 member들을 제한 없이 자유롭게 사용할 수있음을 의미.


1.5 nested classes (중첩된 클래스)

☆ IS_A_PART_OF의 관계

즉, car +- engine

+- tire

+- air-condition


① class의 정의는 중첩하여 정의할 수.

eg. class BlackBox {
private :

// .....

public :

class Sub_box {

public :

int size;

Sub_box* next;

// .....

};

// .....

};


→ class 외부에서 Sub_box class를 사용하고자 하는 경우 : scope resolution operator(::)를 사용하여 해결.

eg. Sub_box box1; // ERROR!!!

BlackBox::Sub_box box2; // OK!!!

⇒ !!! 주의 !!! 그러나 nested class가 private 영역에서 정의된 경우에는 외부에서 scope resolution operator(::)를 사용할 수 없다.


② constructor에서 parameter가 member와 같은 이름으로 선언되어 있을 경우, class의 member를 지정하기 위해서는 scope resolution operator를 사용. ← 모호성!!! 이렇게 하지마!!!

eg. class Queue {
int size;

// .....

public :

Queue(int size) {

head = new char[Queue::size = size]; }

}

// .....

};


 

③ scope resolution operator(::)의 왼쪽에는 항상 class name이 온다. 그러나 연산자 ::이 단독으로 사용되는 경우에는 global name을 가리킨다.


1.6 static member

※ 예를 들어 lists에서 head를 다룰 때 object들 사이에서 head의 값을 일관성있게 다루는 것은 매우 어렵다.


< head를 다루는 방법 >

i) 변수 head를 따로 정의하여 사용.

→ 올바른 방법이 아니다. class와 변수 head사이에 아무런 관련성을 표시할 수 없을 뿐만 아니라, head는 class의 다른 member들과 같은 사용 제한을 받지 않으므로 program의 어디에서나 head변수를 자유롭게 사용할 수 있어서 program을 복잡하고 어렵게 만든다.

ii) static member 사용.

→ GOOD!!!


<< static member >>

① class의 모든 object들 사이에서 공유되는 member.


② class에 따라서는 그 class에 속하는 모든 object들이 하나의 데이터 member를 공유하는 것이 꼭 필요한 경우 사용.

eg. OS나 simulation program에서 task의 관리를 위한 class 등이 이러한 종류에 속함.


③ 실제 example.

class Process {

// .....

public :

static Process* head;

// .....

};


Process* Process::head;


void f() {

Process p1(1), p2(2), p3(3);


p1.head = &p1;

p1.append(p2);

p2.append(p3);

// .....

}

→ object p1, p2, p3는 모두 동일한 head member를 사용.

→ 즉, head member는 Process class에 속하는 모든 object에 걸쳐 하나만 존재하므로 p1.head, p2.head, p3.head가 모두 같은 memeber를 가리키고 있음.

→ static data member는 object에 상관없이 동일한 member를 지칭하므로 p1.head나 P2.head와 같이 object를 지정하지 않고 다음과 같이 사용할 수도 있음 : eg. Process::head = p1;


④ class에서 static member의 선언은 단지 선언일 뿐. 따라서 program의 어디에선가 반드시 이 static member를 정의해 주어야.

→ static member의 정의는 일반 global variable의 선언과 마찬가지 이며, 이 경우에 한해서는 private member의 사용이 가능.

eg. class Spool {
static Spool* jobs;

public :

static int no_of_jobs;

// .....

};

Spool* Spool::jobs;

int Spool::no_of_jobs;


→ jobs는 Spool class의 private member이지만, 이 예에서와 같이 static member의 정의에서는 사용할 수.


⑤ static member의 선언은 data member뿐만 아니라 member fn 역시 static member로 선언할 수.

: static member fn의 의미는 data member와 같이 한 class 내에서 오직 하나만 존재한다는 의미 보다는, 특정한 object없이 호출할 수 있다는 데 그 의미가 있다.


eg. static member fn인 schedule()을 통해 프로세스들의 스케쥴을 하는 예

class Spool {
private :

int jid;

// .....

public :

static int no_of_jobs;

static void schedule();

// .....

};


void main()

{

while ( spool::no_of_jobs) {

Spool::schedule();

// .....

}

}


→ 이와 같이 static member fn의 경우 특정 object의 지정없이도 호출될 수 있기 때문에 static member fn의 내부에서는 자기 참조를 위한 this를 쓸 수 없다.

→ 함수 내부에서 지역적으로 선언된 class는 static member를 가질 수 없다.

(why? local class의 선언 외부에서 static member를 definition할 수 없기 때문.)

eg. void f() {
class X {

static int a; // ERROR!!!

// .....

};

// .....

}


 

1.7 structure & union

① C++에서는의 structure는 특별하게 지정하지 않는 한 모든 member가 public member인 class.


eg. struct X { class X {

// ..... == public :

}; // .....

};

- class와 마찬가지로 생성자, 소멸자를 가질 수 있다!!!


② union : 모든 data member들이 같은 기억장소에 위치하는 structure.

 

1.8 class object의 construction & destruction

(클래스 객체의 생성과 소멸)


1.8.1 멤버로서의 클래스 객체

: 예제를 통해서 살펴 보기로 하자!

class Person {
private :

int pid;

int age;

public :

Person(int pp, int aa) { pid = pp; age = aa; }

// .....

};


class Group {

private :

int gid;

Person p;

public :

Group(int = 0; int = 0; int = 0);

// .....

};


 

→ 만일 Group class의 object를 생성하는 경우 이 object의 member로 Person class의 object가 생성되어야 한다. 그런데 Person class의 object를 생성하기 위해서는 pid와 age에 해당하는 두 argument를 전달하여야 하는데, 이것은 Group class의 constructor 정의에서 다음과 같이 지정할 수.


Group::Group(int gg, int pp, int aa)
: Person(pp, aa), gid(gg)

{

// .....

}


 

→ 여러 member가 있는 경우 순서에 상관없이 콤마(,)로 구분하여 나열. 그러나 member의 초기화는 class 정의에서 member 정의 순서로 이루어짐 : 즉, Group class의 초기화는 member gid의 초기화가 먼저 이루어진 이후에 Person class의 초기화가 일어남.


→ 사용자가 정의한 class 뿐만 아니라, 기본 자료형에 대해서도 이와 같이 초기화 할 수.


1.8.2 array of class object (클래스 객체의 배열)

① Dictionary dic1[10];

Dictionary* dic2 = new Dictionary[20];

→ dic1 : 크기가 10인 Dictionary object array를 선언한 것.

dic2 : 크기가 20인 object array를 new 연산자를 사용하여 정의.


② 사용자가 선언한 class의 object array를 선언하는 경우에는 constructor에 대한 argument를 표시할 수 없음.

→ ∴ object array의 선언은 argument없이 constructor를 호출할 수 있는 class에 대해서만 가능.



i) constructor가 없거나 argument를 갖지 않는 constructor를 갖고 있는 class.

ii) default argument를 취하는 constructor를 갖고 있는 class.


③ C++에서는 object array만이 자신의 원소 갯수를 가지고 있도록 하고 이 array object의 소멸을 위해 delete[]연산을 따로 두고 있다.

(★ new[] 연산자에 대해서는 항상 delete[] 연산자를 사용하는 것이 좋다.)


1.9 좋은 class란?


① class는 잘 정의된 operation을 제공하여야. 이 때, 이 operation들은 그 class에 대한 모든 operation을 지원할 수 있어야하며, 그러면서도 적은 수의 operation들로 구성되어야.

② class는 잘 정의된 외부 interface를 통해서만 사용할 수 있는 일종의 black box와 같이 보여야. 외부 interface에 영향을 미치지 않고도 class를 유지 보수할 수 있어야.

③ ①,②와 같은 class를 잘 설계된 class라고 할 수 있으며, 이 class를 사용하였을 때 object-oriented programming의 장점인 data abstract, polymorphism, easy maintenance등을 최대로 얻을 수 있다.


------------------------------------------------------------------------------

유도된 클래스
(Derived Classes)

[ 이전으로 ]

지금까지 이 문서를 읽으신 분은  분 입니다. (Since 98. 1. 1)

2.1 Inheritance (상속)

① eg. 모든 Student는 Person이다 → 두 class사이의 관계를 다음과 같이 표현.

class Person {
// .....

};

class Student : public Person {

// .....

};


→ public Person이 'IS-A'관계를 표시하는 것. (즉, 위에서 "Student IS-A Person")

Student class는 Person class의 모든 member를 상속받는다.

+- Person class : Student class의 base class. → super class

+- Student class : Person class로부터 유도된 derived class. → subclass

→ 그래프로 표시하면...

Person



Student



② Student class는 Person class로부터 유도되었기 때문에 명시적인 type casting 없이도 다음과 같이 리스트 구조를 표현할 수...

void f()
{

Person p1, p2;

Student s1, s2;

Person* plist;


plist = &s1; // 리스트의 처음을 표시

s1.next = &p1; // s1과 p1을 연결

p1.next = &s2; // p1과 s2를 연결

s2.next = &p2; // s2와 p2를 연결

p2.next = 0; // p2가 끝임을 표시

}





③ ☆ class X가 class Y를 공용 베이스 클래스(public base class)로 할 때, class X에 대한 포인터는 명시적인 형 변환 없이도 class Y에 대한 포인터 변수에 할당할 수.

!!! 반대로 class Y에 대한 포인터를 class X에 대한 포인터 변수로는 할당하지 못한다. !!! → Why? 모든 Student가 Person이라고 할 수 있으나, 반대로 모든 Person이 Student라고는 말할 수 없기 때문...

e.g.,
class Y { //..... };

class X : public Y { //..... };


Y y;

X x;


① y = x; // O.K. (구겨져서 assign!!!)

② x = y; // ERROR.


④ class hierarchy : class사이의 상속 관계들의 집합.

2.2 member access control (멤버 사용의 제어)


① C++에서의 inheritance는 base class로 부터 모든 member들을 다 inheritance받는 완전 상속(full inheritance)의 입장을 취하고 있음. → 유도된 class에서 base class의 member를 다시 정의한 경우에도 base class의 member는 그대로 inheritance받음.


+- 유도된 class의 member가 base class의 member를 지배하므로 base class의

| member를 사용하기 위해서는 scope resolution operator ::를 명시해야.

+- 유도된 class의 member와 name conflict(충돌)을 일으키지 않는 base class의 member에 대해 서는 자신의 member와 같은 방법으로 사용.


eg. class X {
public :

int a;

int b;

// .....

};


class Y : public X {

public :

int a;

// .....

};


void f()

{ /* 여기서 member b는 class Y에서 정의되지 않았기 때문에

scope resolution operator없이도 바로 사용할 수 */

Y y;

y.a = 1; // class Y의 member a

y.X::a = 2; // class X의 member a

y.b = 3; // class X의 member b

}






② derived class에서는 base class의 private member를 사용할 수 없다.

→ base class가 derived class들의 변경 없이도 자신의 private member의 구현을 수정하기 위한 것.



③ protected : 상속관계를 갖는 두 class사이의 member 사용을 효율적으로 제어하기 위해. protected에 선언된 member들은 외부 함수에서는 사용할 수 없으며, 자신과 자신으로부터 유도된 class들의 member f'n 또는 friend에서만 사용할 수.


eg. class Shape {
private :

int x, y;

protected :

int color;

public :

int pen;

// .....

};


class Circle : public Shape {

int rad;

public :

void draw();

// .....

};


void Circle :: draw() {

/* base class의 color와 pen은 사용할 수. 그러나 private member인

x, y는 사용할 수 없음 */

x++; // ERROR.

color = BLUE; // OK.

pen = 10; // OK.

// .....

}


void Circle::draw() {

/* derived class에서는 그 class의 object 또는 그 class에 대한 pointer나 reference등을 통해서만 base class에서 정의된 protected member를 사용할 수 → derived class라도 base class object의 protected member나 그 base class로부터 유도된 다른 class object의 protected member는 사용 할 수 없다. */


Shape s;

color = RED; // OK

s.color = BLACK; // ERROR

}


 

2.3 base class의 초기화와 마무리


☆ base class에 argument를 요구하는 constructor가 정의되어 있는 경우에는 derived class에서도 반드시 이 constructor를 호출하여 초기화해야. → 이때, base class의 초기화는 member object의 초기화와 같은 방법으로 지정.

eg. class Person {
char* name;

short age;

public :

Person* next;


Person(char*, short); // Constructor

void print();

// .....

};


class Student : public Person {

int id;

public :

Student(char*, short, int); // Constructor

void print();

// .....

};


Student :: Student(char* n, short a, int sid)

: Person(n, a), id(sid)

{ ↘

// ..... (No ERROR!!! id = sid;의 의미)

}





→ derived class의 초기화는 base class, member 그리고 derived class 자신의 초기화 순으로 진행. (소멸할 때는 초기화와 반대의 순서로)



2.4 Virtual f'n (가상 함수)

① 예제로 설명

eg. class Person {
// .....

};

class Student : public Person {

// .....

};


void print_person(const Person* plist) {

// .....

}


void main(void) /* 문제점 : print_person의 인자는 Person class

{ 에 대한 pointer형이다. Person은 Student를 유

Student s; 도된 class로 가지고 있기 때문에, 실제로 p가

가리키는 object는 Person object 뿐만 아니라

print_person(&s); Student object일 수도. 그러므로 print_person

// ..... f'n에서는 object에 따른 정확한 정보를 출력

} 할 수 없다. */





→ 해결 방법 : Virtual f'n사용

(Virtual f'n는 derived class에서 재정의가 가능한 member f'n들을 base class에서 virtual f'n으로 선언함으로써 object에 따른 정확한 함수의 호출을 보장해 줌)





class Person {
// .....

public :

virtual void print(); // ①

};

class Student : public Person {

// .....

public :

void print(); // ②

};


void print(const Person* plist)

{

for ( ; plist; plist = plist->next)

plist->print();

}


→ virtual 키워드의 의미 : Person class로 부터 유도된 class가 자신에 맞는 print()함수를 재정의 할 수 있으며, object가 속한 class에 따라서 해당하는 print()함수가 호출되는 것을 가리킴.


☞ virtual f'n은 그 class로 부터 derived class가 없는 경우에도 선언할 수.


☞ derived class에서 base class의 virtual f'n을 반드시 재정의할 필요는 없음.


☞ !!! derived class에서 virtual f'n을 재정의 하는 경우에는 반드시 함수형이 일치하여야. → 만약 함수의 형이 다르면 base class에서 virtual선언은 무시됨.




class X {
// .....

public :

virtual void f1();

virtual void f2();

virtual void f3();

};

class Y : public X {

// .....

public :

void f1();

void f2(int);

int f3(); // Compile ERROR.

};

void g()

{

X *xp = new Y;


xp->f1(); // class Y의 f1()을 호출

xp->f2(); // class X의 f2()를 호출

}





→ f1()의 호출 : xp는 class X에 대한 포인터 변수지만, 실제로 변수가 가리키고 있는 object는 class Y의 object이므로 class Y의 함수 f1()이 호출.

→ f2()의 호출 : virtual f'n 선언이 무시되었으므로 변수 xp가 가리키고 있는 object의 class에 상관없이 class X의 f2()가 호출.

2.5 abstract classes (추상 클래스)


① object를 생성하거나 f'n을 정의하기에는 너무 일반적인 class일 경우에 이용




class Shape {
int x, y;

protected :

int color;

public :

int pen;

? virtual void draw() { error_handler("shape::draw"); }

? virtual void draw() = 0;

};


class Circle : public Shape {

int rad;

public :

void draw();

};


class Line : public Shape {

int length;

int direction;

public :

void draw();

};


→ ? 항상 오류를 발생하도록 draw()함수를 정의


→ ? pure virtual f'n(순수 가상 함수)

→ 이와 같이 하나 이상의 pure virtual f'n을 가지고 있는 class를 abstract class라고 함.


③ abstract class는 object의 생성이 허용되지 않음.


④ 단지, 다른 class의 base class로만 사용할 수.


⑤ !!! pointer나 reference variable은 선언할 수.

eg. Shape s; // ERROR.

Shape* s; // OK.



2.6 multiple inheritance (다중 상속)


① 의미 : 하나 이상의 class로 부터 inheritance받는 것.


② multiple inheritance하에서의 class 계층 구조는 DAG(Directed Acyclic Graph)구조를 갖음.




eg. class List { /*.....*/ };
class Outputs : public List { /*.....*/ };

class Inputs : public List { /*.....*/ };

class Jobs

: public Outputs, public Inputs {

// .....

};


→ Outputs에서 사용하는 List와 Inputs에서 사용하는 List구조가 서로 독립적으로 존재. 그러므로 Jobs class에서 두개의 List구조가 중복하여 존재함.

④ ③과 같이 base class들을 중복하여 상속받는 것이 적당하지 않은 경우.

→ 하나의 class만을 공통으로 상속받을 수 있도록 하기 위해 virtual base class(가상 베이스 클래스)라는 개념이 있다.

eg. class List { /*.....*/ };
class Outputs : public virtual List { /*.....*/ };

class Inputs : public virtual List { /*.....*/ };

class Jobs

: public Outputs, public Inputs {

// .....

};





2.7 ambiguity (모호성)


① 하나의 식이 두개 이상의 member f'n, object 또는 data type을 가리키는 경우, 이것을 base class member에 대한 사용이 모호하다고 함.


② C++에서의 모호성 검사는 그 명칭의 data type, 즉 명칭이 변수인지 함수인지를 따지기 전에 이루어진다.


③ member 사용 권한을 검사하기 이전에 일어난다.


④ !!! virtual base class로부터 상속받는 경우에는 여러 경로를 통해 지칭된 명칭이 하나의 member를 가리킬 수 있다. 이 경우에는 모호성이 발생하지 않는다.

 

2.8 access control (사용 권한의 제어)


① class X { /*.....*/ };

class Y : ______ X { /*.....*/ };



access specifier : public, protected, private등이 올 수 있으

며 base class의 사용 권한을 제한함.


② public access specifier : derived class는 base class의 public member는 public member로서, protected member는 protected member로서 상속. private member는 access 불능.


③ protected access specifier : derived class는 그 base class로부터 protected member와 public member 모두 자신의 protected member로서 상속.


④ private access specifier : 그 base class의 protected member와 public member를 모두 private member로서 상속.



2.9 Virtual destructor (가상 소멸자)


① class에 알맞는 memory의 할당과 소거 방법을 제공하기 위해 프로그래머는 class의 member f'n으로 operator new()와 operator delete()를 정의할 수.


② 문제점.

eg. class Person {
// .....

public :

void* operator new(size_t);

void operator delete(void*, size_t);

// .....

};

class Student : public Person {

// .....

};


void f() {

Person* pp = new Student;

// .....

delete pp; // 문제점 : delete(pp, sizeof(Person))을 호출.

}






③ 문제점의 해결 : base class의 destructor를 virtual f'n으로 선언.

eg. class Person {
// .....

public :

void* operator new(size_t);

void operator delete(void*, size_t);

virtual ~Person();

// .....

}; → 이렇게 하면 위의 문제가 delete(pp, sizeof(Student))로 해결.

→ 이때 destructor f'n은 내용이 없어도 됨.

즉, Person :: ~Person() { }





∴ derived class의 object에 대해서 정확한 memory의 할당과 소거를 제공하기 위해서는 base class에 virtual destructor를 두거나, operator delete()함수에서 size_t인자를 사용해야만 함.



2.10 Virtual constructor (가상 생성자)


① destructor와는 달리 constructor는 virtual f'n으로 선언할 수 없다.


→ 이유: virtual f'n 선언의 의미가 object의 type, 즉 object가 속한 class에 따라서 해당하는 f'n이 호출된다는 것인데, constructor를 호출할 때는 아직 object가 생성되기 전이므로 object의 type에 따라서 호출할 constructor를 결정한다는 것은 의미가 없기 때문.


② 만약 실제 응용에서 virtual constructor를 필요로 하는 경우?

→ virtual constructor의 역할을 하는 일반 member f'n을 정의하여 사용.


------------------------------------------------------------------------------


연산자 중복

(operator overloading)

[ 이전으로 ]

지금까지 이 문서를 읽으신 분은  분 입니다. (Since 98. 1. 1)

3.1 연산자 중복이란?

- C++가 제공하는 basic data type에 대해 적용되는 operator의 의미를 사용자가 정의한 data type에 대해서도 operation을 수행할 수 있도록 그 의미를 바꿀 수 있도록 하는 기법.


3.1.1 연산자 중복의 구문 및 특성

① operator f'n의 구문.

<반환값의 data type> operator <연산자>(argument 리스트) {/* operation을 구현 */}


② operator overloading이 허용되는 연산자.

+ - * / % ^ & | ~ !
= < > += -= *= /= %= ^= &=

|= << >> >>= <<= == != <= >= &&

|| ++ -- ->* , -> [] () new delete





- !!! operator overloading이 허용되지 않는 연산자

. .* :: ?: sizeof




→ 이유1( . .* :: ): 사용자가 정의한 어떤 class에 대해서도 그 operation들은 첫번째 operand로 그 object를 가진다는 미리 정의된 의미를 가지기 때문.


→ 이유2( ?: sizeof ) : 단순히 이 두 operator에 대해서는 operator overloading을 허용할 필요가 없다고 생각했기 때문.


③ operator overloading에서 유의해야할 사항. (☆중요☆)

i) 우선순위를 변경할 수 없다.


ii) 구문은 변경할 수 없다. 즉, unary operator는 unary operator로 binary operator는 binary operator로 정의되어야. (eg. ++ ---> binary op로 변환할 수 無)


iii) 임의로 새로운 operator를 정의할 수 없다.


iv) operator+()와 operator=()를 정의했다고 해서 operator+=()가 정의되지는 않음.


v) new나 delete를 제외한 나머지 operator들에 대해서 operator f'n은 반드시 member f'n이거나, 아니면 적어도 하나 이상의 class object를 argument로 가져야만. → 즉, basic data type(int, char, double ……)에 대해서는 operator overloading을 허용하지 않음.


vi) pointer 변수에 대한 operation는 재 정의될 수 없다.

eg. class x {
friend operator+(X*, X*); // ERROR: pointer에 대한 operation을 정의할 수 없음.

};


void f() {

X *a, *b;

a+b; // pointer 덧셈 : system이 제공하는 operator 사용.

}





vii) basic data type을 첫번째 argument로 가지는 operator f'n은 class의 member f'n이 될 수 없다.

eg. complex class의 object인 comp와 integer 2를 더하는 경우.

☞ complex complex::operator+(int)를 사용.

comp + 2; → comp.operator+(2)로 해석.

2 + comp; → 2.operator+(comp)와 같이 해석될 수 없다.

☞ 해결 : friend f'n 이용.

complex class에 friend complex operator+(int, complex);와 같이 선언.

 

④ operator f'n을 실제 호출하는 방법. (두 가지)

eg. void g(complex a, complex b)
{

complex c = a + b;


complex d = operator+(a, b);

}






3.2 연산자 중복의 사용


- operator overloading을 사용하는 이유 : 일관적으로 operator를 사용하기 때문에 훨씬 읽기 쉽고, 이해하기 쉽다. 또한, operator precedence가 그대로 유지되고 있다. → 결론적으로, operator overloading은 C++에 새로운 기능을 추가하는 것이 아니라 위와 같은 長點을 제공하여 좀더 일반적인 programming 환경을 제공하는 것이다.


3.2.1 이항 연산자와 단항 연산자

① binary operator를 정의하는 두 가지 방법.

+- ▷ argument를 하나만 가지는 class의 member f'n으로 정의.

+- ▶ argument를 두 개 가지는 전역(global) f'n으로 정의.

eg. 임의의 이항 연산자 @가 위의 두가지 방법에서 각각 어떻게 해석되나?

a@b

▷ a.operator@(b)로 해석.

▶ operator@(a, b)로 해석.


② unary operator도 두가지 방법.

+- ♤ argument를 가지지 않는 class의 member f'n으로 정의.

+- ♠ 하나의 argument를 가지는 global f'n으로 정의.

eg. 해석.

@a

♤ a.operator@()로 해석.

♠ operator@(a)로 해석.

a@

♤ a.operator@(int)로 해석. -+

♠ operator@(a, int)로 해석. -+

→ 이때, argument의 data type에 int형을 사용하는 것은 실제로 int data type을 전달받는 것이 아니라, postfix operator임을 나타내기 위한 일종의 모조 인자(dummy argument)이다.


③ member f'n을 사용하는 방법에서 argument수가 한개씩 적은 이유 : 어떤 object에 대한 member f'n은 암시적으로 그 object를 argument로 갖기 때문. → 이때, 그 object의 정보를 reference하는 경우에는 C++이 제공하는 this 포인터를 이용하여 접근할 수.


④ eg. of operator overloading.

class Type {

Type* operator&(); // 주소를 반환하는 prefix unary operator f'n.

Type operaotr-(Type); // 뺄셈 binary operator f'n.

Type operator--(int); // postfix decrement unary operator f'n.

Type operator*(Type, Type); // ERROR : 세개의 argument.

Type operator/(Type, Type); // ERROR : 세개의 argument.

};


// global f'n(보통 friend f'n)을 사용한 정의.

Type operator+(Type); // argument를 원래 부호로 변경하는 prefix unary operator f'n.

Type operator+(Type, Type); // prefix binary operator f'n.

Type operator++(Type&, int); // postfix increment unary operator f'n.

Type operator+(); // ERROR : argument 無.

Type operator+(Type,Type,Type); // ERROR : 세개의 argument.

Type operator%(Type); // ERROR : argument가 하나인 modulator operator f'n.


void g() {

Type a;

Type b = a;

Type* ptr = &a; // a.operator&() 호출. 임의의 data type에대한 pointer는 basic data type임을 명심!

Type c = ptr--; // basic operator -- 수행!

// Type c = ptr; ptr--; 와 같은 결과.

b = a - c; // a.operator-(c) 호출.

ptr++; // basic operator ++ 수행!

c = a + b; // operator+(a,b) 호출.

}


3.2.2 대입 연산자 : operator=()

eg. class X {

// .....

};


void h() {

X a, b;

a = b; // system이 default로 정의한 a.operator=(b)를 호출.

}


X& operator=(X&, const X&) { ..... }

// 문제 발생 : 이미 default 치환 operator f'n을 사용했음.


void g() {

X a, b;

a = b; // 사용자 정의 a.operator=(b)호출.

}




3.2.3 첨자 연산자 : operator[]()

① binary operator로서 class object에 대한 첨자를 정의하는데 사용.

eg. class Vec {

int data[100];

public :

int& operator[](unsigned i) {

if (i>99) cout << " out of range" << endl;

else return data[i];

}

};


void main(void) {

Vec v;

// .....

v[10] = 42; // v.operator[](10)과 같이 해석.

v[12] += v[10];

}


3.2.4 함수 호출 연산자 : operator()()

eg. class assoc_iterator {

assoc *cs;

int ce;

public :

assoc_iterator(const assoc& s) { cs = &s; ce = 0; }

pair* operator()()

{ return (ce < cs->free) ? &cs->[ce++] : 0; }

};


void main(void) {

const MAX = 256;

char buf[MAX];

assoc vec(16);

pair* p;


while (cin >> buf) vec[buf]++; // 입력을 받아들임.

assoc_iterator next(vec);

while(p = next()) // next.operator()()를 호출.

cout << p->name << " counts " << p->val << endl;

}

3.2.5 클래스 멤버 접근 연산자 : operator->()

eg. class Yptr {

Y* p;

public :

Y* operator->() { return p; }

Y& operator*() { return *p; }

Y& operator[](int i) { return p[i]; }

};



3.2.6 증가 연산자(operator++())와

감소 연산자(operator--())


① unary operator이지만, prefix operator와 postfix operator의 두가지 경우로 사용될 수 있으므로, 그 두 가지를 구분.


② postfix operator의 경우에는 dummy argument(모조 인자)를 두 번째 argument로 두어 구분하는 방법을 사용.


eg. class X {

public :

X operator++(); // prefix

X operator++(int); // postfix

X operator--(); // prefix

X operator--(int); // postfix

};



3.3 사용자 정의 자료형 변환


- complex와 double data type을 argument로 가질 때, 가능한 모든 argument들의 조합에 대한 f'n을 프로그래머가 일일이 작성해야하는 단점이 있겠지? 어떻게 해결할까?

→ C++에서는 constructor나 변환 연산자를 이용하여 사용자가 data type 변환을 할 수 지정할 수 있도록 함.


3.3.1 생성자를 사용한 사용자 정의 자료형 변환

① programmer가 일일이 모든 가능한 argument들의 data type 조합마다 따로 f'n을 정의해야 하는 것을 피하기 위한 방법으로 constructor를 이용.

eg. data type T를 class X로 변환하고자 할 경우 ⇒ class X 내에 data type T를 argument로 하는 constructor를 정의.

class X {

// .....

public :

X(T); // T를 argument로 갖는 constructor 선언.

};

⇒ data type X가 필요한 경우에 data type T가 사용되었더라도, system에 의하여 자동적으로 data type T가 data type X로 data type conversion이 일어남.


3.3.2 변환 연산자를 사용한 사용자 정의 자료형 변환

eg. class complex {

double re, im;

public :

operator double() {

if (im) {

cout << " Can't be converted to double type ! " << endl;

}

else return re;

}

};


3.3.3 사용자 정의 자료형 변환에서의 모호성

● 규칙 1 : 사용자 정의 자료형 변환은 그보다 높은 우선순위의 변환이 일어날 수 없는 상황에서만 수행됨.
● 규칙 2 : 사용자 정의 자료형 변환은 한 object에 대하여 오직 한 번만 시스템에 의해 자동적으로 수행됨.
● 규칙 3 : 생성자를 통한 자료형 변환과 변환 연산자를 통한 자료형 변환의 우선순위는 서로 같다.


<사용자 정의 자료형 변환>의 부연 설명


3.3.3 사용자 정의 자료형 변환에서의 모호성

● 규칙 1 : 사용자 정의 자료형 변환은 그보다 높은 우선 순위의 변환이 일어날 수 없는 상황에서만 수행됨.
→ system이 정의한 자료형 변환으로 해결할 수 없는 경우에만 사용자 정의 자료형 변환이 수행.


class complex {
private :
double re, im;
public :
complex(int r=0, int i=0) { re=r; im=i; }

// .....

};


double sqrt(double);
complex sqrt(complex);

void test() { sqrt{10); }


→ 위의 경우 sqrt(10)은 sqrt(complex(10))과 sqrt(double(10))중에서 system 제공 자료형 변환인 sqrt(double(10))가 수행되어 해결.


● 규칙 2 : 사용자 정의 자료형 변환은 한 object에 대하여 오직 한 번만 시스템에 의해 자동적으로 수행됨.


class Type1 {
// .....
Type1(int);
Type1(char*); // 두 가지 모두 constructor를 이용한 자료형 변환.
};


class Type2 {
// .....
Type2(Type1); // Type1을 Type2로 변환하는 기능을 갖는 constructor
};


Type2 test(Type2); // Type2 자료형을 인자로 받고, 반환하는 함수.

void sample {
① test("USUHAN");
② test(Type1("USUHAN"));
}

① ERROR : test(Type2(Type1("USUHAN")) - 두 단계의 암시적인 사용자 정의 자료형 변환.
② O.K. : 한 번의 암시적 사용자 정의 자료형 변환.

● 규칙 3 : 생성자를 통한 자료형 변환과 변환 연산자를 통한 자료형 변환의 우선순위는 서로 같다.
→ 어떤 class의 object에 적용할 수 있는 자료형 변환으로 constructor를 이용하는 방법과 변환 연산자를 이용하는 방법을 모두 적용할 수 있는 경우에는 ERROR 발생.

struct strX {
perator int(); // strX 자료형으로부터 int형으로의 변환 연산자.
};


struct strY {
strY(strX); // strX형으로부터 strY형으로의 constructor.
};

strY operator+(strY, strY) {
// 두 개의 strY 자료형의 인자를 받아 반환값으로 strY 자료형을 반환하는 f'n
// .....
}

void test(strX a, strX b) {
// .....
a+b; // ERROR : 어떤 변환 방법을 사용할 지 모호함.
} // operator+(strY(a), strY(b)) 또는 a.operator int()+b.operator int() 두 가지 모두 수행 가능.

3.4 멤버 함수와 프렌드 함수의 사용 선택 기준
① 함수 f()가 class X내의 member들을 사용하기 위해서는 class X의 member f'n이거나 class X내에서 friend f'n으로 선언된 외부 f'n이어야 함. → programmer는 어떤 방식을 선택할 것인지의 선택 기준은?

② 반드시 member f'n이어야 하는 경우.
: constructor, destructor, virtual f'n → global f'n으로는 사용할 수 無.

③ 일반적인 기준.
☞ member f'n
i) class object의 상태를 변경하는 operator에 사용해야.
ii) basic data type에 대한 operation에서 수식의 왼쪽값이 주소값을 갖는 object가 필요한 operator f'n을 구현할 때, 예를 들어 =, *=, +=등과 같은 operator f'n을 구현할 때.
☞ friend f'n
i) 암시적인 data type 변환이 필요한 operation을 구현할 때.
ii) basic data type에 대한 operation에서 수식의 왼쪽값으로 상수도 될 수 있는 operator f'n을 구현할 때, 예를 들어 +, -, &&등과 같은 operator f'n을 구현.

④ !!! 별다른 이유가 없으면 member f'n을 사용하는 것이 바람직. → Why? 어떤 f'n을 global f'n으로 정의하면 새로운 global name이 생성되어 그만큼 유지하는데 비용이 많이 들고, member f'n에서는 암시적으로 object를 가리키는 this라는 포인터를 사용할 수 있기 때문에 f'n을 보다 더 간단하게 작성할 수.

<<< Operator Overloading의 예 >>>
/* 다음 program은 무한대의 factorial 값을 구하는 program이다. 아무리 int, unsigned int, long int, unsigned long int, double, long double로 data를 선언해 놓아도 그 값에는 한계가 있다. strint(string을 이용한 숫자 계산)라는 class를 만들고 strint 형에 대한 각종 operator를 정의해 놓아 무한대의 숫자까지 factorial 계산이 가능하도록 만든 program이다. (p.39∼44) */


///////////////////////////////////////////////////////////////////

// Program : Factorial
//
// Dept. Computer Science 9301137(B)
// Programmer : USUHAN (Song CheolMin)
//
// last up date : 95. 5. 12
///////////////////////////////////////////////////////////////////

#include // use for I/O

#include // use for I/O
#include // use for 'clrscr()', 'getch()'
#include // use for 'strlen()', 'strrev()'
#include // use for 'itoa()', 'exit()'
#include // use for 'raise()'


#define MAX_INPUT 100

#define SWAP(x, y, t) ( (t)=(x), (x)=(y), (y)=(t) )


typedef enum { FALSE, TRUE } boolean;


class strint {

private :

char *num;

public :

strint(void); // constructor

strint(const int& n);

strint(const strint& n);


friend strint operator+(const strint& m, const strint& n);

friend strint operator+(const strint& m, const int& n);

friend char* add(char* m, char *n);

strint& operator+=(const strint& n);


friend strint operator-(const strint& m, const strint& n);

friend strint operator-(const strint& m, const int& n);

friend char* sub(char* m, char *n);


friend strint operator*(const strint &m, const strint& n);


strint& operator=(const strint& n);

strint& operator=(const char* n);

strint& operator=(const int& n);


friend boolean operator<=(const strint& m, const strint& n);

friend boolean operator<=(const strint& m, const int& n);

friend boolean operator<=(const int& m, const strint& n);


friend istream& operator>>(istream& stream, strint& x);

friend ostream& operator<<(ostream& stream, strint& x);

};


/********************* construtor ***********************/

strint :: strint(void)

{

num = NULL;

}


strint :: strint(const int& n)

{

int length;

char in[MAX_INPUT];


itoa(n, in, 10);


length = strlen(in);

num = new char[length+1];


num = in;

}


strint :: strint(const strint& n)

{

int length;


length = strlen(n.num);

num = new char[length+1];

strcpy(num, n.num);

}


/************ operator overloading (+, +=) ****************/

strint operator+(const strint& m, const strint& n)

{

strint res;


res.num = add(m.num, n.num);


return res;

}


strint operator+(const strint& m, const int& n)

{

strint res, parameter;

int length;

char number[MAX_INPUT];


itoa(n, number, 10);

length = strlen(number);

parameter.num = new char[length+1];

strcpy(parameter.num, number);


res.num = add(m.num, parameter.num);

delete[] parameter.num;


return res;

}


char* add(char* m, char *n)

{

char* tempchar;

int temp = 0;

int i, j, k;

int length, length1, length2;


length1 = strlen(m);

length2 = strlen(n);


if (length2 > length1) { // to modify (m > n)

SWAP(m, n, tempchar);

SWAP(length1, length2, length);

}


/* allocation & initialzation of character string

that will write the result */

tempchar = new char[length1 + 2];

tempchar[length1] = tempchar[length1+1] = '\0';


// calculate as second argument

for (i=length1-1, j=length2-1, k=0; j>=0; i--, j--, k++) {

temp += (m[i] - '0') + (n[j] - '0');

tempchar[k] = (temp%10) + '0';

temp /= 10;

}


for (; i>=0; i--, k++) {

temp += m[i] - '0';

tempchar[k] = (temp%10) + '0';

temp /= 10;

}


if (temp != 0)

tempchar[k] = temp + '0';


strrev(tempchar);


return tempchar;

}


strint& strint :: operator+=(const strint& n)

{

*this = *this + n;


return *this;

}


/************** operator overloading (-) ******************/

strint operator-(const strint& m, const strint& n)

{

strint res;


res.num = sub(m.num, n.num);


return res;

}


strint operator-(const strint& m, const int& n)

{

strint res, parameter;

int length;

char number[MAX_INPUT];


itoa(n, number, 10);

length = strlen(number);

parameter.num = new char[length+1];

strcpy(parameter.num, number);


res.num = sub(m.num, parameter.num);


return res;

}


char* sub(char* m, char *n)

{

char* tempchar;

int temp = 0;

int i, j, k;

int length, length1, length2;


length1 = strlen(m);

length2 = strlen(n);


if (length2 > length1) { // to modify (m > n)

SWAP(m, n, tempchar);

SWAP(length1, length2, length);

}


/* allocation & initialzation of character string

that will write the result */

tempchar = new char[length1 + 1];

tempchar[length1] = tempchar[length1-1] = '\0';


for (i=0; length2 < length1-i; i++)

tempchar[length1-1-i] = m[i];


// claculate subtraction

for (j=0; i
temp = m[i] - n[j];

if (temp >= 0)

tempchar[length1-1-i] = temp + '0';

else {

tempchar[length1-1-i] = 10 + temp + '0';

for (k=1; tempchar[length1-1-i+k] == '0'; k++)

tempchar[length1-1-i+k] = '9';

tempchar[length1-1-i+k] -= 1;

}

} // of for


if (tempchar[length1-1] == '0')

tempchar[length1-1] = '\0';


strrev(tempchar);


return tempchar;

}


/************** operator overloading (*) ******************/

strint operator*(const strint &m, const strint& n)

{

strint a;

char *tempChar;

int tempLen;

int temp;

int len1, len2, i, j, k;


len1 = strlen(m.num);

len2 = strlen(n.num);


tempLen = len1 + len2;

tempChar = new char[tempLen + 1];


for (k=0; k<=tempLen+1; k++)

tempChar[k] = '\0';


for (i=0; i
for (j=0; j
temp = (m.num[len1-i-1] - '0') * (n.num[len2-j-1] - '0');

for (k=i+j; ; temp/=10, k++) { // for3

if (tempChar[k] != '\0')

temp += tempChar[k] - '0';

tempChar[k] = (temp%10) + '0';


if (temp == 0) {

int index = k;

tempChar[index] = '0';

break;

} // of if

} // of for3

} // of for2

} // of for1


if (tempChar[tempLen-1] == '0')

tempChar[tempLen-1] = '\0';

else if (tempChar[tempLen] == '0')

tempChar[tempLen] = '\0';


strrev(tempChar);


a = tempChar;


return a;

}


/************** operator overloading (=) ******************/

strint& strint :: operator=(const strint& n)

{

int length;


length = strlen(n.num);

num = new char[length+1];

strcpy(num, n.num);


return *this;

}


strint& strint :: operator=(const char* n)

{

int length;


length = strlen(n);

num = new char[length+1];

strcpy(num, n);


return *this;

}


strint& strint :: operator=(const int& n)

{

int length;

char *number;


itoa(n, number, 10);

length = strlen(number);

num = new char[length+1];

strcpy(num, number);


return *this;

}


/************** operator overloading (<=) *****************/

boolean operator<=(const strint& m, const strint& n)

{

int len1, len2, cmp;


len1 = strlen(m.num);

len2 = strlen(n.num);


if (len1 < len2)

return TRUE;

else if (len1 > len2)

return FALSE;

else {

cmp = strcmp(m.num, n.num);


if (cmp<=0)

return TRUE;

else

return FALSE;

}

}


boolean operator<=(const strint& m, const int& n)

{

char* number;

int len1, len2, cmp;


itoa(n, number, 10);


len1 = strlen(m.num);

len2 = strlen(number);


if (len1 < len2)

return TRUE;

else if (len1 > len2)

return FALSE;

else {

cmp = strcmp(m.num, number);


if (cmp<=0)

return TRUE;

else

return FALSE;

}

}


boolean operator<=(const int& m, const strint& n)

{

char* number;

int len1, len2, cmp;


itoa(m, number, 10);


len1 = strlen(number);

len2 = strlen(n.num);


if (len1 < len2)

return TRUE;

else if (len1 > len2)

return FALSE;

else {

cmp = strcmp(number, n.num);


if (cmp<=0)

return TRUE;

else

return FALSE;

}

}


/*************** IO stream overloading *******************/

istream& operator>>(istream& stream, strint& x)

{

char buf[MAX_INPUT];


cin >> buf;


x = buf;


return stream;

}


ostream& operator<<(ostream& stream, strint& x)

{

stream << x.num;


return stream;

}


/***************** implement factorial ********************/

strint fact(strint& n)

{

if (n<=1)

return 1;

else

return (n*fact(n-1));

}


/************************ MAIN part ***********************/

void main(void)

{

clrscr();


strint total, i, n, temp;


total = 0;

cout << endl << " <<< 0! + 1! + 2! + .... + n! >>>" << endl;

cout << endl << " Please input n ( 1<= n <=35 ) : ";

cin >> n;


if (n <= 0) {

cout << "\n\n !!! Input # is more than 1 !!! ";

cout << "\n\n Press any key to EXIT \n";

getch();

exit(1);

}

else if (36 <= n) {

cout << "\n\n !!! Input # is less than 35 !!! ";

cout << "\n\n Press any key to EXIT \n";

getch();

exit(1);

}


for (i=1; i<=n; i+=1) {

cout << i << "! = ";

temp = fact(i);

cout << temp << endl;

total += temp;

}


cout << "\n\n Output : " << total;


cout << "\n\n Press any key to EXIT \n";

getch();


raise(SIGTERM);

}



------------------------------------------------------------------------------


템플릿 (Template)

[ 이전으로 ]

지금까지 이 문서를 읽으신 분은  분 입니다. (Since 98. 1. 1)

4.1 Template이란 무엇인가?

① 도대체 template이 왜 필요한 거야?

e.g., stack of integer, char, float, double.....

→ 오직 data type만 다를 뿐, 다른 요소는 모두 동일하다.

∴ data type에 관계없이 programming 가능하도록 → template

(※ C++에서의 template에 해당하는 기능이 지원되는 language로는 ADA가 있으며 Generic이라는 명칭을 갖고 있음)

(※ Generalization(일반화)과 유사. but, '상속'개념과는 다름)


② i) 정의 : 하나의 구조체의 정의로써 여러 종류의 class나 함수를 만들 수 있게 하는 기능.

ii) 용도 : 어떤 특정 data type(자료형)의 object들을 포함하는 class(예를 들어 lists, array, 집합을 위한 class)에 특히 유용하게 사용.


③ 종류

+- class를 생성하기 위한 class template

+- 함수를 생성하는 function template


④ 어떤 때 template이 편리할까?

Lists의 경우를 생각하면 integer lists, char lists, float lists, string lists 등과 같이 여러 가지로 사용될 수 있다. 실제로 이것들을 구현한 class들을 살펴본다면 일부 자료형을 제외하고는 거의 같으므로 다음과 같이 일일이 정의하는 것은 아주 불합리하다.

e.g., Lists를 위한 class
class IntList {

struct Link {

int data;

Link* link;

};

Link node;

public :

IntList(int);

void insert(int);

int delete();

};


class CharList { ..... };

class FloatList { ..... };

class StringList { ..... };


→ 위와 같을 때 data type을 인자로 받아서 그 data type에 대한 List를 시스템이 알아서 생성해 준다면 programmer에게는 막대한 이익일 것이다. 이와 같은 기능을 제공하는 것이 template이다.

→ 즉, template은 항상 어떤 인자를 입력으로 받아서, compiler가 compile을 수행할 때 그 인자를 사용하여 어떤 새로운 class나 함수를 생성한다.


⑤ 주로 여러 사람이 함께 큰 program을 작성할 경우나, 언어 자체내의 library를 작성할 경우에 사용. 즉, class나 function을 작성할 때 구체적인 자료형을 정하기보다는, user가 필요한 것들을 명시하기만 하면 system이 알아서 그것들을 생성할 수 있는 template을 제공하는 것이 더욱 융통성 있는 방법.


⑥ 장점 :

i) programmer가 비슷한 class를 일일이 정의해야하는 단순 노동에서 벗어날 수 있다.

ii) 여러 사람이 함께 작업을 하는 경우나 library를 작성하는 경우에 보다 많은 융통성 제공.

iii) 안전한 data type 검사 기법 제공.



4.2 Class Template


① class template이란?

: 각각의 class가 어떻게 선언될 것인지를 명시하는 것.


☞ class template을 선언하는 구문

template
class

{

/* 여기부터는 template argument가 선언에 사용된다는 것을

제외하고는 class선언부와 동일 */

};





→ argument list에 들어갈 수 있는 인자

i) data type이나 그에 대한 pointer

ii) 상수로 구성된 수식 iii) 함수

→ !!! argument list는 비어있을 수 없다는 것을 명심.

→ 주어진 class template의 이름은 class_name로 정해짐.

→ 주어진 class template으로 부터 생성된 class를 template class라 함.


② Circular queue(원형 큐)를 이용한 예제.

template // template의 선언부 : 반드시 전역 선언이어야 함.
class Queue {

T* base; // queue의 시작 주소를 가리키는 ptr

T* rear;

T* front;

int size; // queue의 크기

public :

Queue(int s) { base = rear = front = new T[size = s]; }

~Queue( ) { delete[] base; }

void add(T);

T delete( );

}; // 여기까지가 인자 T가 유효한 범위.


template void Queue :: add(T a) {

rear = (rear+1) % size; // ptr은 0에서 size-1까지 가리킴.

if (front == rear)

cout << endl << "Queue is already full !" << endl;

else

*rear = a; // 원소값을 반환.

}


template void Queue :: delete( ) {

if (front == rear)

cout << endl << Queue is empty !" << endl;

else {

front = (front + 1) % size; // ptr은 0에서 size-1까지 가리킴.

return *front; // 원소값을 반환.

}

}


Queue intqueue(50); // 크기가 50인 정수 queue의 정의

Queue charqueue(100); // 크기가 100인 문자 queue의 정의

Queue floatqueue(50); // 크기가 50인 실수 queue의 정의

→ __를 template name이라 함.


③ 앞 page 예제의 사용.

void f( ) {
Queue Q(10);

for (int i=0; i < 10; i++ )

Q.add(i);

for (int i=0; i < 10; i++ )

cout << Q.delete() << ' ';

cout << endl;

}






4.2.1 Template class에서의 자료형(data type) 비교


① 여러 개의 template class가 있을 때, 어떤 경우에 그것들이 같은 class(data type)이라고 할까?

답 : 같은 class template name을 가질 때만 data type이 같다고 한다.

즉, 두 개의 template class가 같은 data type을 갖기 위해서는,

+- 첫째 : 그것들의 template name이 같고

+- 둘째 : template에서의 인자들이 같은 값을 가져야 함.


e.g., template class buffer {
// ........

};


buffer buf1;

buffer buf2;

buffer buf3;

buffer buf4;


void f( ) {

buf1 = buf2; // O.K. : 2*512 = 1024

buf2 = buf3; // ERROR : 서로 다른 data type

buf3 = buf4; // ERROR : 서로 다른 data type

}






② template과 inheritance를 함께 사용한 경우.

e.g., class point {
// .....

};

class shape : public point {

// .....

};

class circle : public shape {

// .....

};

class rectangle : public shape {

// .....

};


template

class Vector {

// .....

};


point* pt;

shape shp;

circle circ;

rectangle rect;


Vector* vect1;

Vector vec2;

Vector vec3;

Vector vec4;


void f( ) {

pt = &shp; // O.K.

shp = circ; // O.K. : child class는 parent class로 자료형 변환.

shp = rect; // O.K. : child class는 parent class로 자료형 변환.


vec1 = &vec2; // ERROR : 서로 다른 data type.

vec2 = vec3; // ERROR : 서로 다른 data type.

vec2 = vec4; // ERROR : 서로 다른 data type.

}





→ 즉, 위의 예는 "상속 관계가 있는 인자들에 의해 각각 생성된 template class라 할지라도, 그들 사이에는 아무런 관계도 가지지 않는다"는 것을 보여준다. → template class에 사용되는 인자들 사이에는 어떠한 자료형 변환(data type conversion)도 일어나지 않는다.


☞ 위의 설명은 아래의 그림으로 쉽게 표현가능.

point template Vector



shape

↗ ↖ Vector* Vector

circle rectangle Vector Vector

→ : 상속 관계 : template class



4.2.2 Class template의 사용에 있어서 주의할 사항들

⊙ 규칙 1 : template의 선언은 (class template이든 f'n template이든) 항상 전역 선언이어야 한다.


⊙ 규칙 2 : class template 이름은 한 program에서 유일해야 한다.

→ 두 개 이상의 같은 class template이 존재하면 ambiguity error 발생!


⊙ 규칙 3 : class template의 argument로 다른 object로의 reference를 사용할 수 없다.

→ why? reference는 항상 객체에 대해서만 가능하므로.

→ 더 자세히 말하자면, reference가 인자로 사용되었다면 결국은 reference에 대한 reference를 하게 되는데, reference는 object가 아니라 object가 저장되어 있는 기억 장소의 주소값이므로 reference에 대한 reference는 수행할 수 없기 때문이다.

e.g., stack :: push(T&) {

// .....

};


stack :: push(Vector&&); // ERROR : reference에 대한 reference


⊙ 규칙 4 : template argument는 "template<"에서 부터 처음으로 나타나는 '>'사이에 나타난다.

→ why? 이것은 인자가 수식이 될 경우에 모호성을 방지하기 위한 것.

e.g., template

class X {

// .....

};


void test(int a, int b)

{

X < a > b >; // Syntax ERROR : 마지막 '>'가 삭제되어야 한다.

// .....

}

→ 이 경우에 programmer가 "Xb"를 입력하려고 하다가 잘못 입력하여 끝에 '>'를 붙인 것인지, "X<(a>b)>"와 같은 의미로 쓴 것인지 compiler가 알 수 없으므로.

∴ 첫 번째 만나는 > 안에 있는 것이 template 인자라는 규칙을 정한 것. 그러므로 a>b라는 수식을 인자로 사용하려고 할 때는 X<(a>b)>와 같이 수식을 괄호 안에 두어야 함.


⊙ 규칙 5 : class template의 어떤 argument가 template내에서 여러 가지로 사용되더라도, 그 argument는 argument list에 단 한 번만 나타나야 한다.

① template class sample1 {

T obj;

Vector vect;

// .....

};


② template > class sample2 {

// T가 두 번 사용되었으므로 오류.

// .....

};


위에서 ①에는 아무런 문제가 없었지만, 두 번째 template의 선언에서 T가 두 번 사용되었으므로 ERROR가 발생.

4.3 Function Template (함수 템플릿)

① 정의 : 각각의 f'n이 어떻게 생성되는가를 주어진 인자를 사용하여 명시한 것.

② 종류

+- member f'n template

+- global f'n template

→ template f'n : f'n template에 의해서 생성된 f'n


③ 구조 : template<인자 리스트>라는 template 선언을 한 뒤에, 일반함수의 정의와 같은 형식으로 f'n을 정의

e.g., Vector를 위한 template을 선언하고, 그 template을 오름차순으로 정렬하기 위한 global f'n template을 구현한 것.

template
class Vector {

private :

T* data; // data type T에 대한 ptr

int size; // 크기

public :

Vector(int n) {

data = new T[n];

size = n;

}

~Vector( ) {

delete[] data;

}

T& operator( ) (int i) {

return data[i];

}

};


template

void sort(Vector& v) { // sorting에 사용된 algorithm은 bubble sorting

unsigned n = v.size;


for (int i=0; i
for (int j=n-1; i
if (v[j] < v[j-1] { // v[j]와 v[j-1]을 교환

T temp = v[j];

v[j] = v[j-1];

v[j-1] = temp;

}

}


void f(Vector& vi, Vector& vs)

{

// .....

sort(vi); // sort(Vector&)를 호출

sort(vs); // sort(Vector&)를 호출

}





☞ 왜 f( )에서 template f'n sort( )를 호출할 때 template 인자를 명시적으로 전달하지 않을까?

→ 단지 어떤 객체(vi)를 전달하기만 하면 system이 알아서 필요한 함수(sort(Vector&)를 찾아 호출한다.


☞ !!! But, f( )를 수행하면 정수에 대한 정렬인 sort(vi)는 제대로 작동하지만, 문자열에 대한 sort(vs)는 생각대로 작동하지 않는다. 이런 경우 문자열에 대한 처리를 하고 싶다면 어떻게 해야할까?

→ 문자열에 대해서 특별히 template f'n을 따로 define하여 그 문제를 해결한다.

e.g., #include // use for 'strcmp( )'

void sort(Vector& v)

{

unsigned n = v.size;


for (int i=0; i
for (int j=n-1; i
if (strcmp(v[j], v[j-1]) < 0) { // v[j]와 v[j-1]을 교환

char* temp = v[j];

v[j] = v[j-1];

v[j-1] = temp;

}

}





☞ 사용자가 직접 정의한 template f'n은 f'n template을 이용하여 system이 생성하는 template f'n보다 높은 우선 순위를 가지기 때문에, compiler가 template f'n를 수행하는 부분을 처리할 때는 먼저 사용자가 정의한 template f'n가 있는지 검사하여 없을 경우에 system이 template f'n을 생성하여 실행하고, 만약 사용자가 정의한 f'n이 있을 경우에는 그 f'n을 수행하게 된다.


→ !!! 주의 : 사용자가 정의한 f'n이 제대로 수행되기 위해서는 반드시 수행될 행 이전에 f'n이 정의되어 있어야 한다. 그렇지 않으면 f'n template으로 부터 system이 template f'n를 생성하게 되어 같은 자료형의 f'n이 하나의 program에 두 개 생기게 되는 경우가 발생하므로 ERROR로 처리된다. (∴ 위의 예제에서 사용자가 정의한 template f'n인 sort(Vector)가 함수 f( )앞에 나타나야 한다.


4.3.1 Template f'n을 생성하는 규칙

⊙ 규칙 1 : template f'n의 모든 인자는 반드시 자료형이어야 한다. 즉, class template의 인자는 사용될 수 있었던 수식, f'n, 또는 문자열은 f'n template의 인자로는 사용될 수 없다.


⊙ 규칙 2 : template f'n이 같은 이름의 일반 f'n이나 같은 이름의 다른 template f'n에 의해 중복될 경우에는, 아래의 세단계에 의해 해결됨.


▷ 1단계 : 일반 f'n에 대하여 인자의 자료형이 정확하게 일치되는 것을 찾아 수행함.

▷ 2단계 : 인자의 자료형이 정확하게 일치되는 template f'n이 생성될 수 있는 f'n template을 찾아 template f'n을 생성하여 수행한다.

▷ 3단계 : 일반 f'n에 대해서만 중복 해결 방법을 적용하여 적절한 f'n을 찾아 수행한다.





e.g., template
T max(T a, T b) { return a>b ? a : b; }


void f(int a, int b, char c, char d) {

int m1 = max(a, b); // max(int, int) 호출, 2단계에서 처리

char m2 = max(c, d); // max(char, char) 호출, 2단계에서 처리

int m3 = max(a, c); // ERROR : max(int, char)를 생성할 수 없음.

}





→ 위의 ERROR를 간단히 해결하는 방법 : f'n f( )앞에 간단히 명시적으로 int max(int, int)라고 선언한다. 그러면, compiler는 자동적으로 int max(int, int) f'n을 생성하고, max(a, c)를 호출할 때 3단계에 의해서 max(a, int(c))로 변환되어 실행된다.


즉, template

T max(T a, T b) { return a>b ? a : b; }

int max(int, int); // 명시적 선언


void f(int a, int b, char c, char d) {

// .....

int m3 = max(a, c); // O.K.

}


☞ 결론 : template f'n을 명시적으로 선언하거나 정의한 것은 일반적인 함수와 같이 사용됨.


☞ !!! 주의 : template f'n의 인자에 대해서는 어떤 종류의 자료형 변환도 일어나지 않는다.

e.g., void g( ) {
vector vec1;

vector vec2;

vector vec3;


sort(vec1); // sort(vector) 호출

sort(vec2); // sort(vector) 호출

sort(vec3); // sort(vector) 호출

} → 즉, 기본 자료형에서는 int가 double로 변환되므로 sort(int)나 sort(double)가 같은 f'n sort(double)로 처리 가능하지만, template f'n에서는 인자에 대한 어떤 자료형 변환도 일어나지 않으므로 위의 함수 g( )에서는 세 개의 template f'n이 다 생성되어 수행됨.





⊙ 규칙 3 : f'n template의 인자들은 반드시 template f'n의 인자로 사용되어야 한다.

→ why? 각 f'n template에 주어진 인자를 검사하여 같은 인자들을 갖는 경우에 대해서는 template f'n을 하나만 생성하여 공통으로 사용하게 하기 위함. (!!! But, 이 규칙은 class template에는 적용되지 않는다. 왜냐하면, class template에 대해서는 각 class를 정의할 때마다 주어진 인자들이 같더라도 반드시 고유한 object를 생성해야 하기 때문. 즉, f'n은 공유될 수 있지만 object는 공유될 수 없기 때문.)



e.g., template void test1(T); // O.K.
template T test2(T*) // O.K.

template T test3(int); // ERROR.

template void test4(int[ ][i]); // O.K.

template void test5(int = i); // ERROR.

template int test6(T); // ERROR.

template void test7(Vector); // O.K.

template void test8( ) { // ERROR.

T a;

// .....

}





→ test 8의 경우, 내부에 template 인자 T를 사용하고 있지만, f'n 인자로는 사용되지 않았기 때문에 ERROR로 처리.


앞서의 예제 sort( )에서는 비교 연산자가 f'n 내에 고정되어 있었다. 따라서 비교 연산자를 사용하지 않는 어떤 특정한 자료형에 대하여 올바른 정렬을 하기 위해 적절한 비교 연산자를 사용한 별도의 f'n sort( )를 사용자가 직접 정의해야만 했다. 그러나 f'n sort( )는 비교 연산자를 제외한 나머지 부분은 모든 자료형에 대하여 동일하므로, 사용자가 특정한 sort( ) f'n 전체를 정의하기보다는 필요한 비교 연산자 부분만을 정의하도록 하는 것이 더 바람직하다. 앞으로는 f'n template sort( )를 작성하는 다른 기법에 대해 알아보자.




4.3.2 Derivation(유도)에 의한 operator(연산자)의 제공

☞ 정렬의 대상이 되는 class와 그들의 특성에 맞는 sorting f'n을 위한 class를 따로 정의하여, 그 두 class로 부터 유도된 class의 object를 sort( ) f'n의 인자로 이용하는 것.


e.g., SortableVector 자료형을 sort( )의 인자로 사용하고, sort( ) 내의 비교 연산자로는 인자로 받은 object의 member f'n(Comparator로 부터 물려받은 f'n)을 사용하는 방법

template class Vector {
// .....

};


template class Comparator {

public :

lessthan(T& a, T& b) { return a
};


class Comparator {

public :

lessthan(const char* a, const char* b) {

return strcmp(a, b) < 0;

}

};


template class SortableVector : public Vector, public Comparator {

public :

SortableVector(int s) : Vector(s) { }

};


template

void sort(SortableVector& v) { // bubble sorting

unsigned n = v.size;


for (int i=0; i
for (int j=n-1; i
if (v.lessthan(v[j], v[j-1])) { // v[j]와 v[j-1]을 교환.

T temp = v[j];

v[j] = v[j-1];

v[j-1] = temp;

}

}





4.3.3 Operator f'n(연산자 함수)를 f'n template의 parameter(인자)로 전달

☞ sorting하고자 하는 object 뿐만 아니라 sorting에 사용되는 비교 연산을 위한 f'n을 포함한 object를 f'n template sort( )의 인자로 제공하는 방법.


e.g., 앞의 class template의 정의를 사용하고, 이 방법으로 f'n template sort( )를 정의하면...

template
void sort(Vector& v, Comparator& cmp) {

unsigned n = v.size;


for(int i=0; i
for (int j=n-1; i
if (cmp.lessthan(v[j], v[j-1])) {

T temp = v[j];

v[j] = v[j-1];

v[j-1] = temp;

}

} → 이와 같은 방법은 연산 f'n을 포함한 object를 따로 인자로 제공하므로 하나의 자료형에 대해서도 여러 가지의 f'n을 제공할 수.


void f(Vector& vi, Vector& vc, Vector& vs)

{

Comparator ci;

Comparator cc;

Comparator cs;


sort(vi, ci); // sort(Vector&, Comparator&)

sort(vc, cc); // sort(Vector&, Comparator&)

sort(vs, cs); // sort(Vector&)

}



------------------------------------------------------------------------------


입출력 스트림
(I/O Stream)

[ 이전으로 ]

지금까지 이 문서를 읽으신 분은  분 입니다. (Since 98. 1. 1)

5.1 들어가기 전에

① 기존의 programming language에서는 소수의 기본 자료형(built-in type)을 제외하고는 사용자가 정의한 자료형(user-defined type)에 대해서는 복잡한 입출력 루틴을 programmer가 직접 작성해야 했다. 그러나 C++은 에 정의된 여러 가지 stream class를 사용하여 기본 자료형을 입출력하는 것처럼 사용자가 정의한 data type을 입출력하는 경우에도 편리하고 통합된 입출력 환경을 제공한다.

② C++는 file의 입출력에 대해서도 임의의 stream에 대한 연산으로 다룰 수 있도록 하는 기능을 함께 제공한다.

5.2 표준 입출력 장치로의 출력

5.2.1 기본 data type의 출력

☞ C++에서는 여러 형태의 basic data type에 대하여 operator overloading에 의한 operator를 이용하여 여러 형태의 basic data type을 출력한다.

e.g., #include

void mal(char c) {

cout << c << ' ' << "Mal has a big ..." << endl;

}

→ 즉, 출력하고자 하는 data의 type을 명시할 필요가 없다.

☞ <<와 >> 연산자는 다른 대부분의 operator에 비해서 우선 순위가 훨씬 낮으므로 다음과 같은 유용성

글쓴이:이종화

http://dbnet.hs.ac.kr/board/zboard.php?id=semina&page=2&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=headnum&desc=asc&no=64

'MISCELLANEOUSNESS' 카테고리의 다른 글

Turbo C 사용 설명서 입니다...  (0) 2007.09.16
ACM-ICPC 인터넷 예선 문제  (0) 2007.09.16
[JAVA] JFrame에서 이미지 등록/보이기  (0) 2007.09.16