태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

'Programming'에 해당되는 글 4건

  1. 2008/08/04 [TCPL] C++ 타입과 선언 - 연습 문제
  2. 2008/08/03 [TCPL] C++ 타입과 선언
  3. 2008/08/02 [TCPL] C++ 둘러보기
My Work/C++ (TCPL)2008/08/04 17:55

The C++ Programming Language




4장 타입과 선언

4.11 연습 문제


4.11.1 "Hello,world!" 프로그램을 실행시킨다. 프로그램이 컴파일되지 않으면 B3.1을 참고하자.

<TEXTAREA class=c name=code row="10" col="60">#include<iostream> //#include 문,헤더파일,전처리 지시자로 호칭 using namespace std; //using 키워드를 사용하여 std 네임스페이스를 사용 선언 int main() { cout<<"Hello, world!"<<endl; //Hello, world를 출력한다. return 0; // 리턴 반환하면서 프로그램을 끝낸다. } </TEXTAREA>



4.11.2 4.9에 나온 선언문 전부에 대해 다음과 같이 해 보자. 선언문이 정의문이 아니면, 그에 대한 정의문을 작성한다. 선언문이 정의문이면 그에 대해 정의문이 아닌 선언문을 작성한다.


Char ch;

선언,정의

String s;

선언,정의

Int count = 1;

선언,정의

Const double pi = 3.1415926535897932385;

선언,정의

Extern int error_number;

int error_number = 1;

Const char*name = "Njal";

선언, 정의

Const char*season[] = {"spring","summer","fall","winter"};

선언, 정의

Struct Date{int d, m, y;};

선언, 정의

Int day(date*p){return p -> d ;}

선언, 정의

Double sqrt(double);

Sqrt 에 대한 또 다른 선언

Template<class T> T abs (T a) {return a<0? –a : a;}

선언, 정의

Typedef complex<short> Point;

선언, 정의

Struct User;

선언,정의

Enum Beer{Carlsberg, Tuborg, Thor};

선언, 정의

Namespace NS{int a;}

선언,정의




4.11.3 기본타입, 포인터 타입, 나열자 타입을 몇 개 골라 그것들의 크기를 출력하는 프로그램을 작성하자. sizeof 연산자를 사용 하면 된다.

<TEXTAREA class=c name=code row="10" col="60">#include <iostream> using namespace::std; int main( ) { int a; //int 타입 char b; //char 타입 double c; //double 타입 int * p; //int 포인터 타입 enum color {red, blue, green}; //나열자 타입 cout<<"size of int : "<<sizeof(a)<<"endl"; cout<<"size of char : "<<sizeof(b)<<"endl"; cout<<"size of double : "<<sizeof(c)<<"endl"; cout<<"size of pointer : "<<sizeof(p)<<"endl"; cout<<"size of color : "<<sizeof(color)<<"endl"; /* 각타입의 크기를 sizeof로 알아 본다. */ } </TEXTAREA>

결과









4.11.4 글자 a..z와 0..9를 출력하고, 각각의 정수값도 출력하는 프로그램을 작성한다. 출력할 수 있는 다른 문자에 대해서도 똑같은 프로그램을 작성하자. 16진수만을 사용해서 똑같은 프로그램을 작성해 보자.


1. a에서 z까지, 0에서 9까지

<TEXTAREA class=c name=code row="10" col="60">#include <iostream> using namespace::std; int main( ) { for (char a = 'a' ; a<('z'+1);a++) //문자형 변수를 'a'로 초기화하면서 선언 { //'z'까지 증가 시킨다. cout<<a<<" - "<<(int)a<<" "; } //char형 그대로 출력하고 int형으로 형 변환하여 출력 한다. for (char a = '0' ; a<('9'+1);a++) { cout<<a<<" - "<<(int)a<<" "; } //a..z와 동일한 방법 return 0; } </TEXTAREA>

결과



2. 출력할 수 있는 다른 문자에 대해서

<TEXTAREA class=c name=code row="10" col="60">#include <iostream> using namespace::std; int main( ) { char StartChar; //시작 문자 char EndChar; //끝 문자 while (1) //무한 루프 시작 { cout<<"Enter Start Character : "; cin>>StartChar; //시작 문자 입력 받기 cout<<"Enter End Character : "; cin>>EndChar; //끝 문자 입력 받기 if ((int)StartChar < (int)EndChar) //시작 문자와 끝 문자 크기 비교 { //끝 문자가 시작 문자 보다 클때(정상) for (StartChar;StartChar<EndChar+1 ; StartChar++ ) { //끝 문자까지 시작 문자를 증가 시킨다. cout<<StartChar<<" - "<<(int)StartChar<<" "; } //char 타입으로 보여주고 int타입으로 형 변환하여 보여준다. return 0; //프로그램 종료 } else { //끝문자가 시작 문자보다 작을때(비정상) cout<<"End Character must bigger than Start Character"<<endl; cout<<"Try again..."<<endl; //잘못되었다는 경고 메시지와 함께 if문을 빠져 나오고 //while의 무한 루프에 의해 다시 처음으로 돌아간다. } } return 0; } </TEXTAREA>

결과




4.11.5 여러분이 사용하는 구현한경에서는 char, short, int, long, float, double, long double, unsigned 중 어떤 것이 가장 크고 가장 작은지 조사해 보자.

<TEXTAREA class=c name=code row="10" col="60">#include<iostream> #include<limits> //numeric_limits를 사용하기 위한 헤더 파일 using namespace std; /* c++ 에서의 객체의 크기는 1바이트를 기준으로 표현된다 */ int main(){ int intmax = INT_MAX; int intmin = INT_MIN; char charmax = CHAR_MAX; char charmin = CHAR_MIN; long longmax = LONG_MAX; long longmin = LONG_MIN; double doublemax = DOUBLE_MAX; double doublemin = DOUBLE_MIN; cout <<"char size:" << sizeof(char)<<"byte"<<endl; cout <<"char max:" << int(charmax) <<endl; //char의 최대값을 숫자화 해서 출력 cout <<"char min:" << int(charmin) <<endl; /* 캐릭터의 크기를 cout을 통해 출력 */ cout <<"bool size:" << sizeof(bool)<<"byte"<<endl; cout <<"void size:" << sizeof(void)<<"byte"<<endl; //에러 cout <<"short short:" << sizeof(short)<<"byte"<<endl; cout <<"int size:" <<sizeof(int)<<"byte"<<endl; cout <<"int max:" << intmax <<endl; cout <<"int min:" << intmin <<endl; cout <<"long size:" << sizeof(long)<<"byte"<<endl; cout <<"long max:" << int(longmax) <<endl; cout <<"long min:" << int(longmin) <<endl; cout <<"float size:" << sizeof(float)<<"byte"<<endl; cout <<"double size:" << sizeof(double)<<"byte"<<endl; cout <<"double max:" << int(doublemax) <<endl; //에러 cout <<"double min:" << doublemin <<endl; //에러 cout <<"long double size:" << sizeof(long double)<<"byte"<<endl; cout <<"unsigned size:" << sizeof(unsigned)<<"byte"<<endl; cout <<"double max:" <<numeric_limits<double>::max() <<"byte"<<endl; /* double의 최대값을 템플릿에서 제공되는 numeric_limits를 통해 표현 */ return 0; } </TEXTAREA>

결과






4.11.6 여러분이 사용하는 구현환경에서 c++ 프로그램에 쓸 수 있는 가장 긴 내부 이름(local name)은 무엇인가? 또 가장 긴 외부 이름(external name)은 무엇인가? 이름에 넣을 수 있는 문자에 대해 어떤 제약이 있는지 조사해 보자.


풀이) 길이에는 제약이 없으나 링커에 의해서 가끔씩 제한되기도 한다.

이름 제약

  • 숫자로 시작하면 안된다
  • 스페이스(빈 공간)이 있어서는 안된다.
  • 예약어는 이름으로 사용 못한다.
  • 특수문자는 사용 못한다.

런타임 환경은 문자 집합을 늘리거나 제한하여 특수문자의 사용이 가능하게끔 하기도 한다

<TEXTAREA class=c name=code row="10" col="60">#include <stdio.h> int this_is_a_global_variable_identifier_of_extrenal_area=10, b=20; /* 전역변수(약 열배로도 쳐봄) */ void func1(){ int temp, this_is_a_global_variable_identifier_of_extrenal_area=10, b=20; //지역변수 temp = this_is_a_global_variable_identifier_of_extrenal_area; this_is_a_global_variable_identifier_of_extrenal_area=b; b=temp; } void func2(){ int temp; temp = this_is_a_global_variable_identifier_of_extrenal_area; this_is_a_global_variable_identifier_of_extrenal_area=b; b=temp; } void main(void){ func1(); printf("this_is_a_global_variable_identifier_of_extrenal_area=%d, b=%d \n", this_is_a_global_variable_identifier_of_extrenal_area,b); func2(); printf("this_is_a_global_variable_identifier_of_extrenal_area=%d, b=%d \n", this_is_a_global_variable_identifier_of_extrenal_area,b); } </TEXTAREA>


4.11.7 표준을 따르는 모든 구현환경에서 어떤 A타입의 값이 B타입으로 전부 표현될 수 있으면 A타입이 B타입 을 가리키는 그래프를 그려 보자. 일단 정수와 기본 타입에 대해서 그리면 된다. 여러분이 자주 사용하는 구현환경에서는 이 그래프가 어떻게 바뀌는지도 조사해보자.





1 ≡ sizeof (char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(float) ≤ sizeof(long double)

char

short

int

float

long

long double

1바이트

2바이트

4or2바이트

4바이트

4바이트

8바이트

8비트

16비트

32or16비트

32비트

32비트

64비트





<flat 과 double 비트 간격 표현 범위>


<TEXTAREA class=c name=code row="10" col="60">#include<iostream> using namespace::std; int main() { char a; // char 타입 short b; // short 타입 int c; // int 타입 long d; // long 타입 float e; // float 타입 double f; // double 타입 long double g; // long double 타입 cout<<"size of char : "<<sizeof (a)<<"endl"; // 각 타입의 크기를 출력한다. cout<<"size of short : "<<sizeof (b)<<"endl"; cout<<"size of int : "<<sizeof (c)<<"endl"; cout<<"size of long : "<<sizeof (d)<<"endl"; cout<<"size of float : "<<sizeof (e)<<"endl"; cout<<"size of double : "<<sizeof (f)<<"endl"; cout<<"size of long double : "<<sizeof (g)<<"endl"; } </TEXTAREA>


크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 탈수
My Work/C++ (TCPL)2008/08/03 17:54

The C++ Programming Language


4장 타입과 선언

4.1 타입

4.1.1 C++의 기본 타입

C++는 컴퓨터가 사용하는 기본적인 저장 단위에 대응되는 기본 타입들 그리고 이들 타입을 사용해서 데이터를 담는 방법을 언어 수준에서 지원한다.

타입 이름

형태

bool

True / False

char

문자

'a', 'b'

int

정수

123

Double

부동소수점 실수

123.123

Enum

특정한 집합을 나열

Red, Blue

void

전하는 데이터 없음

 

4.8 나열자 타입

나열자란, 프로그래머가 지정한 값들만 가질 수 있는 타입을 말한다.

<TEXTAREA class=c name=code col="60" row="10">enum { RED, GREEN, BLUE };</TEXTAREA>

나열자의 원소로 정의된 것들은 정수 상수에 이름을 붙인 꼴이다. (RED = 0, GREEN = 1)


그리고 나열자 타입은 이름도 가질 수 있다.

<TEXTAREA class=c name=code col="60" row="10">enum color { RED, GREEN, BLUE }; /* RED 의 타입은 color이다.*/ </TEXTAREA>

4.9 선언

C++ 에서 어떤 이름을 이용 하려면 반드시 선언 해야 한다.

<TEXTAREA class=c name=code col="60" row="10">int a; // int형 변수 a를 선언 </TEXTAREA>

선언문에 있는 이름에 대한 실체들은 어딘가에 정의 해야 한다.

<TEXTAREA class=c name=code col="60" row="10">int add(int); //int형을 반환하는 add라는 함수를 선언 int add(int c) { return ++c; } //1을 더해 리턴하는 정의 </TEXTAREA>

어떤 정의 중에서는 값이 지정 되는 것들이 있다. 타입/템플릿/함수/상수 이것들은 지정된 후에는 변하지가 않는다. 그러나 비 상수 데이터 타입의 경우에는 초기에 넣어준 값을 바꿀수있다.

<TEXTAREA class=c name=code col="60" row="10">#include <iostream.h> int main() { int count = 1; //선언과 동시에 1로 정의 const char* name = "apple"; //name은 어떤 문자열을 가리키는 변수 cout<<"\n Before -----------------------"; cout<< "\ncount : "<<count<<"\n"<<"name : "<<name; //출력 count=2; //값을 2로 바꾼다. name = "cherry"; //name이 가리키는 곳을 바꾼다. cout<<"\n\n After -------------------------"; cout<< "\n"<<"count : "<<count<<"\n"<<"name : "<<name<<"\n"; //출력 /* 바뀐 값들이 출력 된다.*/ } </TEXTAREA>

4.9.1 선언문의 구조

선언문은 크게 네 부분으로 나뉜다.

지정자 Ex) virtual, extern….

기본 타입 Ex) int, char, double…..

선언자 , 선택적 초기치


4.9.2 이름 여러 개를 선언하는 방법

선언문 한 줄에 여러 개의 이름을 선언하는 것도 가능하다.

<TEXTAREA class=c name=code col="60" row="10">Int a, b, c, d; //int a, int b, int c, int d </TEXTAREA>


4.9.3 이름에 대하여

이름(식별자)은 글자와 숫자를 모아서 만든다. 다음은 이름을 만들 때 주의 해야 할 점이다

  • 글자로 시작할 것
  • _는 글자로 간주
  • 구별이 잘 가지 않는 형태는 피한다. (0o, l1..)
  • 넒은 유효 범위에 걸쳐 사용 하는 이름의 경우, 뜻이 명확히 전달 되도록 한다
  • 일관성 있는 스타일을 유지 한다.

4.9.4 유효 범위

선언된 이름의 유효 범위에는 지역과 전역이 있다.

지역 이름의 유효 범위는 그 이름이 선언된 위치부터 그 선언을 품고 있는 블록{ }이 끝나는 시점 까지 이다.

전역 이름의 유효 범위는 그 이름이 선언된 위치부터 그 선언을 품고 있는 파일 끝까지 이다.


블록 안에 선언된 이름은 그 블록을 감싸고 있는 블록 혹은 전역 이름을 가린다.

<TEXTAREA class=c name=code col="60" row="10">#include <iostream.h> int a = 1; //전역 으로 선언 int main ( ) { cout<<" a = "<<a<<"\n\n"; //전역 a ( 1 ) 출력 int a = 123; //지역 a 선언 cout<<" a = "<<a<<"\n\n"; //전역 a가 지역a에 가려저 123 출력 { int a =321; //블록안의 지역 a선언 cout<<" a = "<<a<<"\n\n"; //지역 a가 내부 블록 a에 가려져 321출력 } } </TEXTAREA>

가려진 전역 이름은 범위 지정 연산자 ::로 끌어올 수 있지만 지역 이름은 가려지면 끌어 올 수 있는 방법은 없다.


4.9.5 초기화

어떤 객체에 초기식이 지정되면, 그 객체가 처음 가지게 되는 값은 초기치로 결정 된다.

초기치가 주어지지 않는 경우 전역 객체, 네임스페이스 안의 객체, 지역 정적 객체 (통틀어 정적 객체)들은 그 타입의 0에 해당 하는 값으로 초기화 된다.

<TEXTAREA class=c name=code col="60" row="10">int b; //b = 0으로 초기화 되었음 </TEXTAREA>

배열 원소 및 구조체의 맴버는 초기화되지 않는다. (정적이냐 아니냐에 상관 없음)


4.9.6 객체와 좌변값

객체(object)란 데이터를 담는 기억장치의 특정한 구역을 가리키며, 객체를 참조하는 표현식이 바로 좌변값(lvalue)이다.

프로그래머가 객체의 특성을 지정해주지 않은 경우, 함수에 선언된 객체는 정의문에서 생성되고 객채의 이름이 유효 범위를 벗어날 때 소멸 된다. 이런 객체를 가리켜 자동 객체라 한다.


4.9.7 Typedef

Typedef는 타입에 대한 새로운 이름을 선언한다.

<TEXTAREA class=c name=code col="60" row="10">Typedef unsigned short int sint; /*이제부터 sint로 선언하면 unsigned short int 형으로 선언된다. */ </TEXTAREA>


4.11 연습 문제

1. Hello world! 를 출력시켜라

<TEXTAREA class=c name=code col="60" row="10">#include <iostream.h> int main ( ) { cout<<"Hello, world!"; } </TEXTAREA>

2.

3.
<TEXTAREA class=c name=code col="60" row="10">#include <iostream.h> int main( ) { int a; //정수형 char b; //문자형 double c; //부동소수점 실수형 int * p; //포인터형 enum color {red, blue, green}; //나열형 cout<<"size of int : "<<sizeof(a)<<"\n"; cout<<"size of char : "<<sizeof(b)<<"\n"; cout<<"size of double : "<<sizeof(c)<<"\n"; cout<<"size of pointer : "<<sizeof(p)<<"\n"; cout<<"size of color : "<<sizeof(color)<<"\n"; } //각각의 사이즈를 판단해서 출력함 </TEXTAREA>

4.
<TEXTAREA class=c name=code col="60" row="10">#include <iostream.h> int main( ) { for (int i='a'; i<'z'+1 ;++i ) //a 에서 z까지 증가 { cout<<char(i)<<" "<<i<<" "; //i를 char형과 int형으로 출력 } for (int i='0'; i<'9'+1 ;++i ) { cout<<char(i)<<" "<<i<<" "; //i를 char형과 int형으로 출력 } }</TEXTAREA>

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 탈수
My Work/C++ (TCPL)2008/08/02 13:46

The C++ Programming Language



2장 워밍업 : C++ 둘러보기


2.1 C++ 이란 무엇인가?


모든 용도에 쓸수 있으며 시스템 프로그래밍에 강한 프로그래밍 언어


C++의 기본 성격

  • C를 기본으로 발전시킨 언어
  • 데이터 추상화를 지원
  • 객체 지향 프로그래밍을 지원
  • 일반화 프로그래밍을 지원


2.2 C++는 프로그래밍 패러다임이 한두 개가 아니다


객채 지향 프로그래밍은 '훌륭한' 프로그램을 짜게 해 주는 패러다임(paradigm)중 하나 이다.

어떤 프로그래밍 언어가 어떤 프로그래밍 스타일을 지원한다고 말할 수 있으려면 그 스타일을 편하게(쉽고, 안전하고, 효율적으로) 쓸 수 있도록 하는 언어적 장치가 뒷받침 되어야 한다.


프로그래밍 스타일을 지원하기 위한 조건

  • 모든 기능은 깔끔하고 세련되게 언어에 녹아 있어야 함.
  • 별도의 기능이 필요할 것 같은 문제에도 기존의 기능들을 조합해서 해결할 수 있어야 함.
  • 이름만 번지르르 하고 특수한 용도로 마련된 기능은 최대한 적어야 함.
  • 기능의 구현은 그 기능을 쓰지 않은 프로그램엔 큰 오버헤드를 주지 않도록 해야 함.
  • 언어에서 제공되는 기능 중 확실한 몇 개만 알아도 프로그램을 작성할 수 있어야 함.

C++의 프로그래밍 스타일

절차적 프로그래밍 - 객체 지향 프로그래밍 - 일반화 프로그래밍



2.3 절차적 프로그래밍


절차적 프로그래밍의 패러다임

- 문제 해결에 적합한 처리 절차를 정하고 찾을 수 있는 최선의 알고리즘을 구사한다.


대게 함수에 인자를 전달하고 함수로부터 값을 반환 하는 메커니즘이 제공되는 것이 보통이다.

다음은 제곱근을 계산하는 함수 이다.

<TEXTAREA class=c name=code row="10" col="60">#include <iostream.h> double square_root(double num) //square_root함수에 double값을 넣으면 제곱근이 double값으로 반환 된다. { //제곱근 계산을 위한 코드 } void main() { double number; //제곱근을 구하기 위한 double변수 cout<<" Enter the number : "; cin>>number; //제곱근을 구하고자 하는 값을 입력. cout<<"\n"<<square_root(number); //함수에 값을 넣어 반환한다. } </TEXTAREA>


2.3.1 변수의 산술 연산


모든 이름과 표현식에는 어떤 연산이 수행될지 정해주는 타입이란 것이 붙는다.


    C++의 다양한 타입들 중 몇 가지

타입

크기

사용법

bool

불(Boolean)

1 byte

True, false

char

문자(character)

1 byte

'k', 'h', 'j', '4'

int

정수(integer)

2 or 4 bytes

1, 342, 6342

double

배정밀도 부동소수점실수

8 bytes

3.14, 29348.0

*컴퓨터나 컴파일러에 따라 크기가 다를 수 있음


이런 타입들에 대해 계산을 처리할 때 다음과 같은 산술 연산자와 비교 연산자가 사용된다.

산술 연산자

비교 연산자

+

덧셈

==

같은가?

-

뺄셈

!=

다른가?

*

곱셈

<

작은가

/

나눗셈

>

큰가?

%

나머지

<=

작거나 같은가?

  

>=

크거나 같은가


대입 연산과 산술 연산을 기본 제공 타입을 가지고 수행 할 경우에는, 다른 타입을 쓰더라고 자유롭게 섞어 쓸 수 있도록 적절한 타입 변환이 내부 적으로 이루어진다.

다음은 타입 변환이 이루어지는 예제이다.
 <TEXTAREA class=c name=code row="10" col="60">#include <iostream.h> void main () { double a = 2.2; //a는 double형으로 소수점 까지 표시 가능 int b = 7; //b는 int형 cout<<"a : "<<a; //a의 초기값 cout<<"\nb : "<<b; //b의 초기값 cout<<"\n\n\n"; a = a + b; //a 에 a와 b를 더한 값을 넣는다.(2.2+7) cout<<"\nafter ----- a = a + b :"; cout<<"\na : "<<a; //a 는 9.2가 되어 있음 cout<<"\nb : "<<b; //b 는 7 cout<<"\n\n\n"; b = a * b; //b 에 a와 b를 곱한 값을 넣는다.(9.2 * 7) cout<<"\nafter ----- b = a * b :"; cout<<"\na : "<<a; //a 는 9.2 cout<<"\nb : "<<b; //b 는 64.4가 되어야 하지만 int타입이므로 타입 변환이 이루어져 64가 출력 된다. } </TEXTAREA>

b의 int 타입에 맞게 변환 되었다.


2.3.2 조건검사 및 루프


C++ 에서는 분기(selection)와 루프처리(looping)를 나타낼 수 있는 구문들이 있다.

다음은 간단한 입력을 받아 그 응답을 나타내는 불(bool)값을 표시하는 예제이다.

<TEXTAREA class=c name=code row="10" col="60">#include <iostream.h> bool accept( ) { char answer = 0; //문자 변수 cout<<"Do you want to stop (y or n) ?\n"; //질문 출력 cin>>answer; //대답을 읽는다. if(answer == 'y') return true; //'y' 이면 1 (true)을 반환 return false; //'y'가 아니면 0 (false)을 반환 } void main( ) { bool true_false = accept(); //bool변수에 accept 함수값을 저장 cout<<true_false; //true, false에 따라 1, 0 을 출력 } </TEXTAREA>

위의 예제 에서 accept 함수를 n을 받아들였을 경우까지 고려하면 조금 더 짜임새 있게 된다.
 <TEXTAREA class=c name=code row="10" col="60">bool accept2() { char answer =0; cout<<"Do you want to stop (y or n) ?\n"; cin>>answer; switch(answer) { case 'y': return true; //'y' 이면 1 (true)을 반환 case 'n': return false; //'n' 이면 0 (false)을 반환 default: cout<<"I'll take that for a no.\n"; return false; //'y'. 'n'둘 다 아닐 때는 메시지와 함께 0 (false)을 반환 } } </TEXTAREA>

위 예제의 switch문은 상수의 집합에 대해 어떤 값을 검사하는 조건문이다. 'case 상수'는 서로 겹치지 않아야 하고 검사하는 갑이 어떤 상수에도 해당 하지 않으면 default쪽으로 처리 흐름이 오게 된다.


위 예제에 루프를 적용한다면 사용자가 몇 번의 시도를 할수록 고칠 수 있다.
 <TEXTAREA class=c name=code row="10" col="60">bool accept3( ) { int tries = 1; //시도 횟수 카운트를 위한 변수 while (tries<4) //tries 값이 4보다 작을때 아래를 처리 { cout<<"Do you want to stop (y or n) ?\n"; char answer = 0; cin >> answer; switch(answer) { case 'y': return true; //'y'일때 true값을 반환하고 나온다. case 'n': return false; //'n'일때 false값을 반환하고 나온다. default: cout<<"Sorry, only 'y' or 'n'\n"; //'y' , 'n' 둘다 아닐때 보내는 메세지 tries = tries++; //tries변수에 1을 더한다음 다시 처음으로 돌아간다. } } cout<<"I'll take that for a no.\n"; return false; //1~3까지 3번이 반복되어 tries변수가 4가 되면 while문을 빠져나와 메시지를 보낸 후 false를 반환한다. }</TEXTAREA>

위 예제에서 while문은 조건이 거짓(false)이 될 때까지 실행하게 만드는 구문이다.


2.3.3 포인터와 배열


배열의 선언

<TEXTAREA class=c name=code row="10" col="60">char k[4]; //문자 4개의 배열 </TEXTAREA>   
        - char타입 4개가 선언 되었다. ( k[0], k[1], k[2], k[3] )

포인터의 선언

<TEXTAREA class=c name=code row="10" col="60">char *p; //문자를 가리키는 포인터 </TEXTAREA>   
        - char 타입의 객체가 저장된 메모리 주소를 가진다.

<TEXTAREA class=c name=code row="10" col="60"> p = &k[2]; // p는 k의 3번째 원소를 가리키는 포인터 </TEXTAREA>   
        - 단항 연산자인 &은 주소 ( address-of ) 연산자이다.


아래는 한 배열에서 다른 배열로 4개의 원소를 복사 하는 예제이다.

<TEXTAREA class=c name=code row="10" col="60">#include <iostream.h> void main( ) { int k1[4] = {1,2,3,4}; //복사할 변수 선언과 함께 데이터 입력 int k2[4] = {0,0,0,0}; //복사 받을 변수 선언과 함께 0으로 초기화 int i; cout <<"Before copy------------"; for (i = 0; i<3; i++) //복사 하기전의 배열을 보여주는 for문 { cout<<"\n\nk1["<<i<<"] : "<<k1[i]; cout<<"\nk2["<<i<<"] : "<<k2[i]; } for (i=0; i<4; i++) k2[i] = k1[i]; //k1 배열을 k2배열로 복사 하는 for문 cout <<"\n\n\nAfter copy------------"; for (i = 0; i<3; i++) //복사한 후의 배열을 보여주는 for문 { cout<<"\n\nk1["<<i<<"] : "<<k1[i]; cout<<"\nk2["<<i<<"] : "<<k2[i]; } } </TEXTAREA>

위 예제에 쓰인 for문은 i를 0으로 설정하고 이 값이 4보다 작은 동안 i번째의 원소를 복사하고 i를 증가시킨다



2.4 모듈화 프로그래밍


문제 해결에 사용하는 데이터를 만들어 두면, 그 데이터를 조작하는 프로시저가 자연스럽게 하나 둘 생기게 되는데 이것들이 모인 것을 가리켜 모듈(module)이라 한다.


모듈화 프로그래밍의 패러다임

- 문제 해결에 적합한 모듈을 정한다.

- 프로그램을 쪼개어 데이터가 모듈 속에 숨겨지도록 한다.


이런 패러다임을 가리켜 데이터 은닉 원칙(data-hiding principle)이라고도 한다.


모듈화 프로그래밍을 잘 보여주는 대표적인 예로 스택이 있는데, 스택의 구현은 다음과 같이 이루어지는 것이 보통이다.

  1. 스택 조작을 위한 사용자(프로그래밍) 인터페이스를 제공한다 [push, pop]
  2. 스택을 나타내는 데이터 (배열로 나타낼수도 있다)는 1.에서 제공한 인터페이스로만 접근(access) 할 수 있도록 한다.
  3. 반드시 사용 전에 스택의 초기화가 이루어지도록 한다.

다음은 namespace를 이용 하여 Stack이란 이름의 모듈에 대한 사용자 인터페이스를 선언한 결과와 이 인터페이스의 사용 예제이다.

<TEXTAREA class=c name=code row="10" col="60">namespace stack //인터페이스 (모듈 선언부) { void push(char); char pop( ); } void main( ) { stack::push('c'); if(stack::pop( ) != 'c') error("impossible"); } </TEXTAREA>

stack:: 이란 한정표시(qualification)은 그 뒤의 push( )pop( )이 stack namespace의 것이라는 뜻이다. 이로써 똑같은 push( )pop( )을 쓰더라도 이름 혼동이 일어나지 않는다

stack 모듈의 정의(구현)코드는 (선언부와) 별도로 컴파일된 파일에서 받아 올 수 있다.
 <TEXTAREA class=c name=code row="10" col="60">namespace stack { const int max_size = 200; char v[max_size]; int top = 0; void push(char c) { /* overflow를 점검하고 c를 push한다.*/ } char pop( ) { /* underflow를 점검하고 pop한다. */ } } </TEXTAREA>

이 모듈에서 사용자 코드가 데이터표현부와 분리 되어 있음에 주목하자. 이는 stack::push( )stack pop::( )이 있기 때문에 가능하다.

C++의 namespace 에는 어떤 선언이든 넣을 수 있다. 이 모듈은 스택을 나타내는 수단중 하나일뿐이다.



2.4.1 분할 컴파일


여러 개의 하위 구성 요소를 모아서 하나의 프로그램을 만들 때 분할 컴파일 (separate compilation)을 사용 한다.


모듈 인터페이스를 지정하는 선언부는 '이것은 인터페이스'라는 것을 잘 알려 주는 이름을 가진 파일에 넣어 둔다.

<TEXTAREA class=c name=code row="10" col="60">namespace stack //인터페이스 { void push(char); char pop( ); } </TEXTAREA>

스택을 조작하는 함수를 선언해 둔 인터페이스를 stack.h라는 헤더파일(header file)에 넣는다. 사용자는 이 파일을 include 해서 사용자 코드로 가져 온다.

<TEXTAREA class=c name=code row="10" col="60">#include "stack.h" //인터페이스를 가져 온다. void main( ) { stack::push('c'); if(stack::pop( ) != 'c') error("impossible"); } </TEXTAREA>

일관성을 유지할 수 있도록, stack 모듈의 구현부를 가지고 있는 파일 쪽에서도 인터페이스를 include한다.

<TEXTAREA class=c name=code row="10" col="60">#include "stack.h" //인터페이스를 가져온다. namespace stack // 스택의 데이터 표현 { const int max_size = 200; char v[max_size]; int top = 0; } void push(char c) { /* overflow를 점검하고 c를 push한다.*/ } char pop( ) { /* underflow를 점검하고 pop한다. */ } </TEXTAREA>

이 모듈을 사용 하는 코드는 제 3의 파일 에 둔다. stack.h안에 만들어 놓은 인터페이스 정보를 여러 군데서 끌어오고 있지만, 그렇다고 해서 파일들 사이에 어떤 종속 관계가 있는 것은 전혀 아니다. stack.h를 불러 오는 모든 파일을 각각 따로 컴파일 된다.


프로그램을 잘 작성 하려면

  1. 모듈화의 정도를 극대화 한다.
  2. 언어에서 제공되는 특징을 반영하여 모듈관계를 논리적으로 표현한다.
  3. 효과적인 분할 컴파일을 위해 모듈들을 별도의 파일에 분산 시켜 구성한다.


2.4.2 예외 처리


모듈의 집합으로 설계 되어 있는 프로그램 에서는 오류 처리도 모듈 중심으로 해 주어야 한다. stack 모듈을 예를 들어 stack을 초과하는 문자를 push( ) 할 때 어떻게 대처 해야 할까? 해결책은 Stack모듈 개발자 쪽에서 overflow가 나는 경우를 찾아 사용자에게 알려주는 것이다.

<TEXTAREA class=c name=code row="10" col="60"> namespace stack //인터페이스 { void push(char); char pop( ); class overflow { }; //overflow 라는 예외 상황을 나타내는 타입 } </TEXTAREA>

stack::push( ) 는 오버플로가 발생 했다 판단되면 적절한 예외 처리 코드를 호출 한다.
 <TEXTAREA class=c name=code row="10" col="60"> void stack::push(char c) { if ( top == max_size ) throw overflow( ); // c를 push한다. } </TEXTAREA>

throwstack::overflow 타입을 가진 예외를 처리 하는 블록으로 제어권을 넘긴다. 이 블록은 stack::push( )를 직간접적으로 호출한 함수 어딘가에 있어야 한다. 이 '제어권 넘기기'를 위해 C++환경은 호출부의 문맥을 제대로 찾아 갈수 있도록 호출 스택에 대한 풀기 동작을 적절히 수행한다. 쉽게 말해 여러 군데에 return을 하는 것과 마찬가지이다.

<TEXTAREA class=c name=code row="10" col="60"> void main( ) { //.. try { //여기서 일어난 예외의 처리는 아래에 정의된 처리자가 맡는다. while(true) stack::push('c'); } catch (stack::overflow) { //스택 overflow에 대해 적절한 조치를 취한다. } //... } </TEXTAREA>

이 코드에서 while 루프는 무한정 돌게 된다. 따라서 stack::overflow에 대한 처리자가 있는 catch 절은 스택이 넘쳐서 stack::push( )에서 throw가 실행 된 후에야 움직인다.

예외 처리 메커니즘은 오류 처리 코드를 좀더 조직적이고 읽기 좋게 하기 위해 마련된 것이다.



2.5 데이터 추상화


규모가 큰 프로그램 중에 제대로 된 것은 모두 '모듈화가 잘 되었다' 라는 특징을 기본적으로 가지고 있다. 모듈을 통해 사용자 정의 타입의 '모양새'를 제공하는 방법과, 이 방법을 썼을 때 생기는 문제를 해결 하기 위해 사용자 정의 타입을 직접 정의 하는 방법을 알아보자.



2.5.1 모듈로 타입을 정의하는 방법


모듈 방식으로 프로그래밍을 하다 보면, 한 가지 타입을 가진 데이터는 모두 타입 관리자 모듈(type manager module) 같은 것을 써서 모아 두는 경우가 생길 수 밖에 없게 된다. 앞에서 본 stack 모듈을 여러 개 쓰고 싶은 경우, 필요한 인터페이스가 달린 스택 관리자를 정의해서 쓰는 것이다.

<TEXTAREA class=c name=code row="10" col="60">namespace stack { struct Rep; //스택 데이터를 나타내는 Rep. 이것의 정의는 어디에 있을 것이다. typedef Rep& stack; stack create( ); //스택하나를 새로 만든다. void destroy(stack s); //s를 삭제한다. void push(stack s, char c); //c를 s에 push한다. char pop(stack s); //s를 pop한다. } </TEXTAREA>

선언문 struct Rep; 에서 Rep은 타입 이름에 해당 하지만, 타입 자체는 나중에 정의되도록 내버려 둔 상태이다.

두 번째로 나온 선언문 typedef Rep & stack; 은 'Rep에 대한 참조자(reference)'에 stack이란 이름을 붙인다. 이 stack::stack은 기본 제공 타입의 변수와 똑같이 쓸 수 있다.

<TEXTAREA class=c name=code row="10" col="60"> struct Bad_pop { }; void f( ) { stack::stack s1 = stack::create( ); //스택 하나를 새로 만든다. stack::stack s2 = stack::create( ); //스택을 하나 더 만든다. stack::push(s1,'c'); stack::push(s2,'k'); if (stack::pop(s1) != 'c') throw Bad_pop( ); if (stack::pop(s2) != 'k') throw Bad_pop( ); stack::destroy(s1); stack::destroy(s2); }</TEXTAREA>

Stack의 인터페이스를 구현 하는 방법은 여러 가지가 있을 수 있겠지만, 어떤 방법을 쓰든 사용자는 신경 쓰지 않아도 된다.

구현방법의 한 가지 예 : 스택 몇 개를 미리 만들어 할당해 놓고, stack::create( )는 미사용 스택에 대한 참조자를 끄집어 내는 일만 하게 한다. stack::destroy( )는 사용 되는 것들은 다시 '미사용' 이라고 표시해 두어, 나중에 stack::create( )가 재사용할 수 있도록 하는 것이다.

<TEXTAREA class=c name=code row="10" col="60"> namespace stack { //실제 스택 데이터의 표현부 const int max_size = 200; struct Rep { char v [max_size]; int top; }; const int max = 16; Rep stacks[max]; bool used[max]; typedef Rep& stack; } void stack::push(stack s, char c) { /* 오버플로를 점검하고 c를 push한다. */ } char stack::pop(stack s) { /* s가 언더플로가 났는지 점검하고 pop한다. */ } stack::stack stack::create( ) { //미사용 중인 Rep을 꺼내고 이것을 '사용 중'이라고 표시한 후, 초기화하고 참조자를 반환한다. } void stack::destroy(stack s) { /* s를 "미사용" 상태로 바꾼다. */ } </TEXTAREA>

지금까지의 코드를 보면, 스택을 표현하는 타입이 인터페이스 함수들로 둘러싸인 형태이다. 이렇게 에둘러 만들어진 '스택 타입'의 동작 방식을 좌우하는 요인은 여러 가지로서, 조금씩 달라진다. 그러나 이 방법은 이상적인 설계에서 약간 어긋나 있다. 유사 타입을 사용하는 방법이 실제 데이터의 내부사항에 따라 판이하게 달라질 수 있고, 사용자는 이로 인해 혼란스러워 한다.



2.5.2 사용자 정의 타입


이 문제에 대한 해결책으로 C++에서는 기본 제공 타입과 똑같은 동작 방식을 가지는 타입을 사용자가 직접 정의할 수 있도록 하였다. 이런 타입을 가리켜 추상 데이터 타입 (abstract data type)이라고들 한다.


사용자 정의 타입을 사용한 프로그래밍의 이론적인 틀

  • 문제 해결에 필요한 타임들을 정한다.
  • 각타입에 대해 동작 연산을 완벽하게 제공한다.

사용자 정의 타입의 예는 유리수나 복소수 따위를 나타내는 산술 타입이 대표적이다. 다음 예제를 보자.

<TEXTAREA class=c name=code row="10" col="60"> class complex { double re, im; public: complex(double r, double i) { re=r; im=i; } //두 개의 스칼라로부터 복소수를 만든다 complex(double r) { re=r; im=0; } //하나의 스칼라로부터 복소수를 만든다. complex( ) { re = im = 0; } friend complex operator + (complex, complex); friend complex operator - (complex, complex); //이항 연산 friend complex operator - (complex, complex); //단항 연산 friend complex operator * (complex, complex); friend complex operator / (complex, complex); friend complex operator == (complex, complex); //같음 비교 friend complex operator != (complex, complex); //다름 비교 //... }; </TEXTAREA>

complex란 이름의 클래스(사용자 정의 타입)는 복소수 데이터의 표현부와 조작에 필요한 연산들을 모두 가지고 있다. 여기서 데이터표현부는 외부에 가려져 있다. (re와 im) 이 두 데이터는 complex 클래스의 선언부에서 지정된 함수들만 접근할 수 있다. 이들 '지정된' 함수는 다음과 같이 정의 될 수 있다.

<TEXTAREA class=c name=code row="10" col="60"> complex operator + ( complex a1, complex a2) { return complex (a1.re + a2.re, a1.im + a2.im); } </TEXTAREA>

 클래스와 똑같은 이름을 가진 멤버 함수를 볼 수 있는데, 이것을 생성자(constructor)라고 한다. complex 클래스에 정의된 생성자는 double하나를 받아 complex객체를 초기화하는 것, double 두 개를 받아 초기화하는 것, 그리고 기본 값으로 초기화하는 것, 이렇게 세 가지이다.


complex 클래스의 사용법은 다음과 같다.

<TEXTAREA class=c name=code row="10" col="60"> void f(complex z) { complex a = 2.3; complex b = 1/a; complex c = a+b*complex(1,2.3) //... if (c != b) c = -(b/a) + 2*b; } </TEXTAREA>

 C++ 컴파일러는 complex 타입의 복소수를 피연사자로 사용하는 연산자를 읽어, 그 클래스에서 정의된 함수 호출 코드로 바꾸어 준다.



2.5.3 구체 타입


앞서 보았던 stack 타입이 complex 타입처럼 만들어졌다고 생각 해보자. 약간 쓸만하게 다듬으려면 스택 내부의 원소 개수를 인자로 받아들이도록 다음과 같이 정의 할 수 있을 것이다.

<TEXTAREA class=c name=code row="10" col="60"> class stack { char*v; int top; int max_size; public: class underflow { }; // 예외로 사용하기 위함 class overflow { }; // 예외로 사용하기 위함 class bad_size { }; // 예외로 사용하기 위함 stack(int s); // 생성자 stack( ); // 소멸자 void push(char c); char pop( ); }; </TEXTAREA>


'생성자'라는 이름이 붙은 stack(int)는 이 클래스의 객체가 메모리에 만들어질 때마다 호출되는 함수 이다. 이 함수가 맡은 일은 객체의 초기화이다. 이 클래스의 객체가 유호 범위를 벗어날 경우에 끝 마무리 작업이 필요 하다면 소멸자(destructor)라는 함수를 준비해 두면 된다.

<TEXTAREA class=c name=code row="10" col="60"> stack::stack(int s) // 생성자 { top = 0; if (s0 || 10000<s) throw bad_size( ); // '||'의 뜻은 '또는'이다. max_size = s; v = new char[s]; // 자유 저장공간에 원소 할당 } stack::~stack( ) // 소멸자 { delete[ ] v; // 공간을 다시 쓸 수 있도록 할당한 원소를 해제한다. } </TEXTAREA>


여기서 생성자가 할 일을 새로 생성된 stack 변수를 초기화하는 것이다. 사용자는 기본제공 타입의 변수를 대하듯이 stack 객체를 만들어 쓰면 되는 것이다.

<TEXTAREA class=c name=code row="10" col="60"> stack s_varl(10); //10개의 원소를 가진 전역 스택 void f(stack& s_ref; inti) //stack 객체에 대한 참조자 { stack s_var2(i); // i 개의 원소를 가진 지역 스펙 stack* s_ptr = new stack(20); //자유 저장공간에 할당된 stack객체 포인터 s_var1.push('a'); //변수를 통해 접근하는 방법 s_var2.push('b'); s_ref:push('c'); //참조자를 통해 접근하는 방법 s_ptr -> push('d'); //포인터를 통해 접근 하는 방법 //... }</TEXTAREA>


이 stack 타입은 이제 동작 방식이 int나 char 등의 기본 제공 타입과 똑같아졌다.

앞에서 본 complex와 여기서 본 stack과 같은 타입을 가리켜 구체 타입(concrete type) 이라고 한다. 추상 타입(abstract type)과 반대되는 의미인데, 이 추상 타입은 인터페이스 밖에 없기 때문에 사용자 코드가 구현코드로부터 더 분리 된다.



2.5.4 추상 타입


stack 이란 타입을 나타내는 문제를 모듈 형태의 '유사 타입'에서 진짜 타입으로 옮기면 좋은 점도 있지만 안타깝게도 한 가지 성질을 잃게 된다. 바로 데이터표현부가 사용자 인터페이스와 떨어지지 않은 상태로 남는다는 점이다.

사용하는 타입이 자주 바뀌지 않는다든지 지역 변수를 사용해서 깔끔하고 효율적으로 작업할 수 있는 경우에는 구체 타입이 이상적일 때가 많다. 하지만 스택을 사용하는 프로그래머와 스택 구현부와의 관계를 완전히 끊어 버려야 할 때도 있는데, 이 경우에는 stack 클래스로도 어림없다. 해결책은 단 하나, 인터페이스와 데이터표현부를 떼어 놓고 지역 변수를 포기하는 것이다.

우선, 인터페이스를 이렇게 정의한다.

<TEXTAREA class=c name=code row="10" col="60"> class stack { public: class underflow { }; class overflow { }; virtual void ush(char c) = 0; virtual char pop( ) = 0; }; </TEXTAREA>

virtual 이란 키워드가 붙은 함수는 이 클래스로부터 파생된 클래스에서 나중에 재정의될 수 있다는 것을 의미 한다. 정리하면, 이 stack은 push( )pop( ) 함수를 '구현' 하게 될 미지의 클래스에게 인터페이스, 즉 틀만을 제공하는 것이다.

이 stack은 어떻게 사용 될까? 다음을 보자.

<TEXTAREA class=c name=code row="10" col="60"> void f(stack& s_ref) { s_ref.push('c'); if(s_ref:pop( ) != 'c') trow bad_pop( ); } </TEXTAREA>

f( )는 실제 구현부를 전혀 모르고도 stack 인터페이스를 사용 하고 있다. 이렇게 다른 클래스가 쓸 수 있는 인터페이스를 제공하는 클래스를 가리켜 다형성 타입(polymorphic type)이라고 한다.

stack이 인터페이스가 됐으므로, stack이 구체 타입이었을 때 가지고 있던 것들을 전부 떼어 내면 구현부를 구성할 수 있을 것이다.


-----


2.5.5 가상 함수


f( )에 들어있는 s_ref:pop( )은 어떻게 해당 클래스의 함수 정의부로 정확히 해석(resolve)되는 것일까? h( )에서 호출되는 f( )에서는 list_stack::pop( )이 호출 되어야 하고 g( )에서 f( )가 호출되는 경우에는 array_stack::pop( )이 호출되어야 한다. 적절한 함수 정의를 찾아내려면, 런타임에 호출될 함수를 가리키는 정보가 stack객체 어딘가에 들어 있어야 한다. 이 매커니즘은 일반적으로 컴파일러 쪽에서 virtual 키워드가 붙은 함수의 이름을 함수 포인터 테이블의 색인번호로 바꾸는 식으로 만들어져 있다. 이 테이블을 가리켜 가상 함수 테이블(virtual function table) 이라고 하며, 짧게 vtbl이라고 한다.



2.6 객체 지향 프로그래밍


사용자 정의 타입만 썼을 때 생기는 문제 그리고 이 문제를 뛰어넘는 해결책인 클래스 계통이란 것을 살펴보자


2.6.1 구체 타입의 결정적 약점


구체타입은 일단 한번 만들어지고 나면 외부에서 통할 수 있는 길이 극히 한정되어 있다. 예를 들어 그래픽 시스템에 사용 하려고 shape란 타입을 하나 정의 한다고 가정해 보자. 그리고 다음과 같은 타입도 있다고 가정하자.

<TEXTAREA class=c name=code row="10" col="60"> class Point{/*...*/}; class Color{/*...*/}; </TEXTAREA>

도형(shape)을 나타내는 타입을 정의해 보자

<TEXTAREA class=c name=code row="10" col="60"> enum kind {circle, triangle, square}; //나열자 타입 class shape { kind k; //도형 타입 필드 point center; color col; // ... public: void draw( ); void rotate(int); //... }; </TEXTAREA>

'도형 타입 필드'란 이름이 붙은 k라는 변수가 있어야 하는 이유는 draw( )와 rotate( )에서 자신이 처리하는 도형이 어떤 것인지를 구분할 수 있어야 하기 때문이다.


....


2.6.2 클래스 계통


문제의 핵심은, 도형마다 가지고 있는 일반적인 성질과 그 도형만의 고유한 성질이 명확하게 갈라지지 않았다는 데 있다. 이 차이를 언어로 나타내고 이용하는 것이 바로 객체지향 프로그래밍이다.

일반성과 특수성의 구분을 이루어 내는 답은 바로 상속 메커니즘이다. 우선 도형마다 공통적으로 가진 성질을 정의하는 클래스를 만든다.

<TEXTAREA class=c name=code row="10" col="60"> class Shape { point center; color col; //... public: point where( ) {return center;} void move(Point to) { center = to; /*...*/ draw( ); } virtual void draw( ) = 0; virtual void rotate(int angle) = 0; //... }; </TEXTAREA>

호출 인터페이스는 정의할 수 있지만 구현코드는 바로 제공하지 않아도 되는 함수를 가상 함수로 선언된다. (draw( ), rotate( ) )

이렇게 클래스를 정의해 두었으면 도형에 대한 포인터를 담은 벡터를 조작하는 일반함수를 만들어서 일괄적으로 처리할 수 있다.

<TEXTAREA class=c name=code row="10" col="60">void rotate-all(vetor<Shape*>& v, int angle) //v의 원소가 되는 각 도형을 angle도 만큼 회전 { for (int i = 0; i<v.size(); ++i) v[i] -> rotate(angle); }</TEXTAREA>

이제 특정한 도형을 정의한다. '이것은 도형이다'라고 알려 줌과 동시에 개별적인 성질을 지정해 주는 것이다.

<TEXTAREA class=c name=code row="10" col="60"> class circle : public shape { int radious; public: void draw( ) {/*...*/} void rotate(int) { } //아무것도 안 하는 함수 }; </TEXTAREA>

파생 클래스는 기본 클래스가 가진 모든 것을 물려 받기 때문에, 이런 클래스의 관계를 이용하는 것을 상속 이라고 한다.


객체 지향 프로그래밍의 이론적 틀은 다음과 같다.

  • 문제 해결에 필요한 클래스가 무엇인지 정한다.
  • 클래스에 대해 모든 함수 집합을 만들어 준다.
  • 클래스 사이의 공통적인 부분을 상속관계를 써서 드러낸다.


2.7 일반화 프로그래밍


일반화 프로그램의 이론적인 틀은 다음과 같다.

  • 문제 해결에 필요한 알고리즘을 정한다.
  • 이들 알고리즘이 여러 가지의 타입과 자료 구조에 대해 동작 할 수 있도록 알고리즘을 매개변수화한다.

2.7.1 컨테이너


C++에서는 앞에서 만들던 '문자만 담는 스택' 타입을 '어느 것이든 담는 스택' 타입으로 바꿀수 있는 방법이 있는데, 타입을 템플릿(template)으로 만들고 char라는 특정한 타입을 탬플릿 매개변수로 바꾸는 것이다.

<TEXTAREA class=c name=code row="10" col="60"> template <class T> class stack { T*v; int max_size; int top; public: class underflow{ }; class overflow{ }; stack(int s); //생성자 ~stack( ); //소멸자 void push(T); T pop( ); }; </TEXTAREA>


클래스 이름의 앞에 붙은 template<class T> 라는 접두사가 바로 T를 이 타입의 매개 변수로 만들어 주는 부분이다.

멤버 함수의 코드 정의는 이전의 것과 크게 다르지 않으나 T를 고려해서 이루어진다.

<TEXTAREA class=c name=code row="10" col="60"> template<class T> void stack<T>:: push(T c) { if (top == max_size) throw overflow( ); v[top] = c; top = top + 1; } template<class T>T stack<T>:: pop( ) { if ( top == 0 ) throw underflow( ); top = top -1; return v[top]; } </TEXTAREA>

이렇게 만든 스택(템플릿)은 다음과 같이 사용 한다.

<TEXTAREA class=c name=code row="10" col="60"> stack<shar>sc(200); //200개의 문자를 담는 스택 stack<complex>scplx(30); //30개의 복소수를 담는 스택 stack<list<int> > sli(45); //45개의 정수 리스트를 담는 스택 void f( ) { sc.push('c'); if(sc.pop( ) != 'c') throw bad_pop( ); scplx.push(complex)(1.2)); if (scplx.pop( ) != complex(1,2)) throw bad_pop( ); } </TEXTAREA>

여기서 보인 스택처럼 리스트, 벡터, 맵 같은 것들도 템플릿으로 정의할 수 있다. 이렇게 어떤 타입의 원소를 모아서 담을 수 있는 클래스를 가리켜 컨테이너 클래스라고 한다. 편하게 컨테이너(container)라고 불러 주기 바란다.



2.7.2 일반화 알고리즘


C++ 표준 라이브러리에는 위에서 이야기한 컨테이너라고 불리는 것들이 들어 있으며, 없는 컨테이너는 직접 만들 수 있다.

컨테이너가 정확히 어떻게 생겼는지 몰라도 그 컨테이너를 조작할 수 있는 일반화된 방법을 찾아야 한다. 여기에는 여러 가지 방법이 있을 수 있겠으나 표준 C++ 라이브러리에서 제공하는 컨테이너와 비수치형 알고리즘에서 취한 방법은 원소들이 순서대로 연결된 시퀸스라는 개념으로 컨테이너를 모델링하고 이 시퀸스를 반복자라는 것으로 조작하게 만드는 것이었다.

하나의 시퀸스엔 반드시 시작과 끝이 존재한다. 반복자는 시퀸스 안의 우너소를 가리키고 자신이 참조하는 원소의 다름 것으로 옮겨갈 수 있는 연산을 제공한다. 일반화 프로그래밍에서 시퀸스의 끝은 시퀸스의 마지막 원소의 바로 다음을 가리키는 반복자를 뜻한다. 또한 '반복자가 가리키는 원소에 접근하기'와 '반복자를 다음 원소로 옮기기' 같은 기본적인 연산에 대한 표준 방식도 이미 정해져 있다. 우리들이 쉽게 접근하고, 증가 연산자인 ++를 써서 다음 원소로 옮겨 가게 한것이다. 그렇기 때문에 이런 식의 코딩이 가능하다.

<TEXTAREA class=c name=code row="10" col="60">template<class in, class out> void copy(in from, in too_far, out to) { while (from != too_far) { *to = *from; //반복자가 지금 참조하고 있는 원소를 복사한다. ++to; //대상이 되는 곳의 반복자를 증가시킨다. ++from; //바탕이 되는 곳의 반복자를 증가시킨다. } } </TEXTAREA>

C++에서 기본적으로 제공되는 배열과 포인터 타입은 반복자 연산에 정확히 맞아 떨어지기 때문에 다음과 같이 작성할 수 있다.

<TEXTAREA class=c name=code row="10" col="60"> char vc1[200]; //200개 문자를 담는 배열 char vc2[500]; //500개 문자를 담는 배열 void f( ) { copy(&vc1{0}, &vc1[200], &vc2[0]); } </TEXTAREA>

vc1의 첫 원소부터 끝 원소까지를 vc2에 복사하는 코드로, 대상위치는 vc2의 처음부터 시작한다.

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 탈수