시작하면서

본 강좌는 C language를 어느 정도 아는 사람을 위한 C++ 기초 강좌입니다. 따라서 C를 전혀 모르는 분은 C기초부터 공부한 다음 이 강좌를 보시면 되겠습니다.

 

 

 

OOP(Object-Oriented Programming)란

우리가 살고있는 세상은 거의 모든 사물이 객체(Object)로 이루어져 있다.

사람, 나무, 자동차 등등이 객체가 된다.  예를 들어 자동차는 엔진, 차체, 타이어 등으로 이루어져있고, 또 엔진은 자동차를 움직이게 한다.

이처럼 실세계를 아주 가깝게 반영하는 프로그래밍이 OOP이다.

 

객체지향 프로그래밍은 프로그래머들이 절차형 방식보다 빠르게 목적(응용 프로그램을 완성하고 디버깅하는 것)을 달성할 수 있도록 해주는 좋은 프로그래밍 수단이다.

객체지향 프로그래밍은 변수를 다루기 위해 그 주변에서 기다리기보다는 껍질속의 데이터를 동작시키며, 프로그램의 변수들은 그것을 어떻게 처리할지를 알고 있는 것이다. 변수의 내용을 출력할 때가 되면 변수를 출력하는 것이 아니라 스스로 출력하도록 변수에게 알려주면 된다. 이러한 신기한 프로그래밍 방법이 실제로는 아주 자연스런 프로그래밍 방법이다.

 

 

cout  ( iostream.h )

printf()은 C에서 사용하는 출력함수이고 cout은 C++에서 사용하는 출력객체이다.

cout은 printf() 보다 사용하기 더 간편하다.

아래의 문장을 보자.

cout<< "Hello C++";

이 문장은 화면에 "Hello C++"이라고 출력한다.

<<은 좌측-쉬프트의 비트 연산자이지만 출력을 위해서도 사용된다.

얼마간, cout를 화면(출력 장치)으로 생각하자. 위의 문장은 "Hello C++"를 화면으로 보내라는 명령이라고 생각하면 된다.

 

 

화면이나

다른 출력장치

 << " Hello C++ "

 

예제

   cout으로 출력

#include<iostream.h>

#include<conio.h>

void main(){

    clrscr();

    int i=10;

    cout<< "Hello C++";

    cout<< "\n";

    cout<< i;

    cout<< "\n";

    cout<< "End";

}

 


 

 

 

예제

   cout으로 출력2

#include<iostream.h>

#include<conio.h>

void main(){

    clrscr();

    int i=65;

    float f=1.2345;

    double d=-1234.56789;

    cout<< "int i is:" << i << "\n";

    cout<< "float f is:"<< f << "\n";

    cout<< "double d is:"<< d << "\n";

    getch();

}

 


 

cin ( iostream.h )

scanf()함수를 대체한 객체이다.

cout을 하면으로 생각할 수 있듯이 cin은 키보드라고 할수 있다. 키보드로부터 입력을 받을 때 사용한다. 아래의 문장을 보자.

cin >> a;

키보드로부터 변수 a에 값을 입력받는다.

 

키보드

>>  a    

 

 

예제

   cin으로 입력

#include<iostream.h>

#include<conio.h>

void main(){

    clrscr();

    int a;

    cout <<"What is the number: ";

    cin >> a;

    cout << "\n\nThe number is " << a;

    getch();

}


 

 

cerr ( iostream.h )

cerr은 에러 메시지를 보낼 때 사용된다. cerr은 버퍼를 사용하지 않고 바로 출력하여 사용자가 에러 메시지를 바로 볼 수 있게 해준다.

 

 

예제

   cerr으로 에러메시지 보내기

// 나눗셈 예제

#include<iostream.h>

#include<conio.h>

void main(){

    clrscr();

    int a=10;

    int b;     

    cout <<"What is the number: ";

    cin >> b;

    if (b==0)

       cerr << "Wrong number";  // 0으로 나눌 수 없으므로 에러 메시지를 출력

    else

       cout << "a / b is  " << a/b;

    getch();

}


 

 

혼자 해보기1

 

국어, 영어, 수학 점수를 입력받아 평균을 구하여 출력하는 프로그램을 작성하라.

 

 

혼자 해보기2

 

혼자 해보기1에서 에러 메시지를 출력하는 코드를 추가해 보자.

 

 

출력 형식 조작 ( iomanip.h )

입력하거나 출력하는 데이터를 스트림(stream)이라고 하고 이런 조작기들을 스트림 조작기(stream manipulators)라고 한다.

 

 [표]  I/O 스트림 조작기

조작기

설명

dec

hex

oct

endl

ends

flush

setbase(int n)

resetiosflags(long f)

setiosflags(long f)

setfill(int c)

setprecision(int n)

setw(int n)

10진수 전환 베이스를 설정한다.

16진수 전환 베이스를 설정한다.

8진수 전환 베이스를 설정한다.

개행문자('\n')를 삽입하고 스트림 내용을 지운다.

문자열에 널 문자를 삽입한다.

출력 스트림의 내용을 지운다.

n진수로 전환 설정한다.

형식 플래그인 f.f에 의해 지정된 형식을 지운다.

형식 플래그인 f.f에 의해 지정된 형식을 설정한다.

c로 채우기 문자를 설정한다.

n으로 부동 소수점 유효자리를 설정한다.

n으로 필드 폭을 설정한다.

 

 

 [표]  resetiosflags()와 setiosflags()를 위한 형식 플래그 값.

형식 플래그 이름

설명

ios::left

ios::right

ios::scientific

ios::fixed

ios::dec

ios::hex

ios::oct

ios::uppercase

 

ios::showbase

ios::showpos

ios::showpoint

setw() 폭 안에 출력을 좌측 정돈한다.

setw() 폭 안에 출력을 우측 정돈한다.

과학용 표기로 출력을 형식 지정한다.

10진수 형식으로 숫자를 형식 지정한다.

10진수로 숫자를 형식 지정한다.

16진수로 숫자를 형식 지정한다.

8진수로 숫자를 형식 지정한다.

16진수와 과학용 표기의 문자를 대문자로 형식 지정한다.

( 0x123을 0X123으로, 2.34e+05를 2.34E+05로 )

수치 베이스 접두 문자를 출력한다.( 16진수의 0x나 8진수의 0 )

양수를 출력할 때 플러스 부호, +를 출력한다.

정확도를 위해 필요하다면 끝의 0들을 표시한다.

 

형식 플래그의 값들은 상수이다. 영역 지정 연산자(::)는 나중에 설명한다.

형식 플래그는 두 함수 resetiosflags()와 setiosflags()에서만 작동한다.

 

  

 

예제

   조작기 사용

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    int num=220;

    clrscr();

    cout << "The decimal num is " << num << "\n";  // The decimal num is 220

    cout << "The hexadecimal num is " << hex << num << "\n";

        // The hexadecimal num is dc

    cout << "The octal num is " << setbase(8) << num << "\n";

        // The octal num is 334

    cout << setbase(10);

    cout << 12345 << "\n";     //  12345

    cout << setw(20) << 12345 << "\n";    // '               12345'

    cout << setw(20) << setfill('*')<< 12345 << "\n";  // ***************12345

    cout << setiosflags(ios::left);

    cout << setw(10) << 12345 << "\n";                        // 12345*****

    cout << setiosflags(ios::hex) << 45 <<"\n";                  // 2d

    cout << setiosflags(ios::hex | ios::uppercase) << 45 <<"\n";   // 2D

    getch();

}


 

 

입력 조작

cin은 공백을 무시한다. 공백을 포함하려면 cin.get()을 사용한다.

get(char *string, int length [, char terminator])

[]부분은 생략할 수 있다. 함수의 매개변수 생략은 오버로드함수에 가서 배우기로하고 그냥 넘어가자. terminator는 생략하면 '\n'이 디폴트가 된다.

 변수 string에 최대 length까지 받아들일 수 있으며 종료자를 만나면 중지한다.

 

 

예제

   get()으로 문자열 받아들이기

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    char name[80];

    clrscr();

    cout << "What is your name? ";

    cin.get(name,30);

    cout << "your name is :" << name << endl;

    getch();

}


 

get()은 입력버퍼에 개행문자를 남겨둔다. 따라서 get()을 두 번 사용하면 문제가 발생한다.

  cout << "What is your name? ";

  cin.get(name,30);

  cout << "your name is :" << name <<endl ;

  cout << "What is your city? ";

  cin.get(city,30);

  cout << "your city is :" << city << endl;

city에는 아무것도 입력 받지 못한다. 사용자의 입력을 기다리지 않고 끝나버린다. 이유는 첫 번째 get()이 '\n'을 남겨두고 두 번째 get()이 '\n'을 읽어드린다. 따라서 city="" 이다.

이를 해결하는 방법은 getline()을 써는 것이다.

 

 

예제

   getline()으로 두 번 읽기

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    char name[80], city[30];

    clrscr();

    cout << "What is your name? ";

    cin.getline(name,80);

    cout << "your name is :" << name <<endl ;

    cout << "What is your city? ";

    cin.getline(city,30);

    cout << "your city is :" << city << endl;

    getch();

}


 

 

영역 지정 연산자( :: )

::(scope resolution operator)는 지역변수와 전역변수의 이름이 같을 때 전역변수를 사용한다고 C++에게 알려준다. 지역변수에 우선권을 갖는 전역변수를 원하면 변수 앞에 ::를 두면 된다. ::의 중요한 OOP적 사용은 나중에 보게 될 것이다.

 

 

예제

   cout으로 출력

#include<iostream.h>

#include<conio.h>

int a=10;            //전역변수

void main(){

    int a=20;        //지역변수

    clrscr();

    cout << a<<endl;     // 지역변수 a=20

    cout << ::a<<endl;   // 전역 변수 a=10

    getch();

}

 


 

const 변수

const를 이용하여 프로그램 내에서 값을 변경하지 않도록 할 수 있다.

const float PI=3.141592;

const int NUM=123;

const는 만들면서 바로 초기화하여야 한다. 나중에 값을 바꾸려고 하면 에러가 발생한다.

NUM=100;  <-- 에러 발생

 

함수에서 const 매개변수

int func( const int x){

  명령

}

위의 함수에서 const int x를 주목하자. 매개변수가 const int 형이다.

그럼 매개변수로서 const가 와야 하는가?... 그런 의미가 아니라 func()내에서 x의 값을 변경할 수 없다는 의미이다.

아래의 문장은 오류가 없다.

int a=10;

func(a);

그러나 매개변수의 값을 바꿀려고 하면 에러가 난다.

int func2(const int x){

   x=10;     // 에러 발생

   return 1;

}

 

inline 함수

inline 결과형 func( 매개변수 리스트 ){

   명령

}

인라인 함수는 C에서 #define과 비슷한 역할을 한다.

인라인 함수는 실제로 함수 호출이 발생하는 않지만 함수가 호출되는 곳에서 라인속(inline)으로 코드가 복사된다. 따라서 호출시간이 절약되고 그만큼 빨라지게 되는 것이다.

Turbo c++은 인라인을 무시할 수도 있다. 인라인 함수가 8라인 이상이거나 for문이나 while 문 등의 반복문을 포함하면 인라인화 되지 않는다. 인라인 함수는 호출되는 곳에 코드가 복사되므로 inline이 많으면 실행파일의 크기가 크지는 단점이 있다.

 

void 포인트

void 포인트는 어떤 데이터 형도 가리킬 수 있는 포인트 변수이다.

void 포인트

##########0*

int

float

double

char

 

void *vp;

int i=10;

char c='A';

vp=&i;

vp=&c;

void 포인트 변수 vp는 int형 변수를 가리킬 수 있고 char형 변수도 가리키고 있다.

 

그러나 역 참조할 때 void형은 형변환을 해주어야 한다.

int *b;

b= (int *)vp;  // vp를 int형 포인트로 변환한다.

 

 

예제

   void 포인트 사용

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    void *vp;

    int i=20;

    char c='A';

    clrscr();

    vp=&i;

    cout << *(int *)vp << endl;

    vp=&c;

    cout << *(char *)vp << endl;

    getch();

}


 

레퍼런스(References)

레퍼런스는 자동적으로 역참조되는 포인터라고 할 수 있다. 자동적 역참조(automatically dereferenced)라는 것은 레퍼런스의 값을 얻기 위해 역참조 연산자 *를 사용할 필요가 없음을 의미한다. 레퍼런스는 다른 변수를 위한 별명(aliases)을 만든다.

 

int i = 20;  //일반 정수형 변수

int *ip = &i;  // 정수형에 대한 포인터

int & ir = i;  // 정수형에 대한 레퍼런스

 

레퍼런스를 정의 하기 위해 주소 연산자, &를 사용한다.

레퍼런스를 초기화하지 않고 레퍼런스를 정의해서는 않된다. ir은 현재 i의 별명이고 ir에 행해지는 것은 역으로 i에 행해진다.

 

ir = 10; // i의 값이 10이 된다.

 

ip

 

.....

 ir-i-

10

 

 

예제

  레퍼런스 변수의 정의

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    void *vp;

    int i = 20;

    int & ir = i;

    clrscr();

    cout << ir << endl;

    ir=10;

    cout << i << endl;

    i=50;

    cout << ir << endl;

    getch();

}


레퍼런스는 함수간 매개변수 전달 시 강력한 힘을 발휘한다.

 

 

상수에 대한 포인트

int i=10;

const int *ip = &i;

 

ip는 const int에 대한 포인트이다. 그러므로 ip의 내용은 수정할 수가 없다.

아래의 예제를 보자.

 

 

예제

   상수에 대한 포인트

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    int i = 20;

    const int * ip= &i ;

    clrscr();

    cout << *ip << endl;

    *ip=10;                     // 에러발생 (cannot modify a const object)

    cout << *ip << endl;

    getch();

}


상수 포인트는 일반변수를 상수처럼 다루도록 요구하여 접근을 제한할 때 사용한다.

 

 

포인트 상수

 int i = 20;

int * const ip = &i;

ip 가 const형임을 의미한다. 즉 ip를 바꿀 수 없다. 그러나 ip의 내용은 바꿀 수 있다.

 

 

예제

   포인트 상수

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    int i = 20, j =10 ;

    int * const ip= &i ;

    clrscr();

    cout << *ip << endl;

    *ip=j;                      // ip의 내용은 바꿀 수 있다.

    cout << *ip << endl;

    ip = &j;                    // 에러발생 (cannot modify a const object)

    getch();

}


 

 

읽기 전용 레퍼런스

 

int i = 25;

const int & r = i;

 

r은 상수에 대한 레퍼런스이다. 따라서 r로써 i의 값을 변경할 수 었다.

 

r=30;   // 에러 발생

 

하지만 i에 직접 값을 바꿀 수는 있다.

i=30;

 

r은 읽기 전용이다.

 

 

혼자 해보기

 

배열의 각 요소에 12개월의 날짜를 가진 정수 배열을 초기화하는 프로그램을 작성해 보자. 배열을 정의하고 초기화한 다음, 배열의 첫 번째 요소를 가리키는 포인터 상수를 생성하자. 배열의 이름을 사용하지 말고 포인터 상수를 사용해서 배열의 요소를 출력하는 루프를 작성해 보자.

 

 

메모리 동적 할당

동적(dynamic)이란 말은 변화를 의미하는데, 동적 메모리 할당(dynamic memory allocation)은 메모리를 할당하고 해제할 때 힙의 크기가 변하는 것을 말한다.

 

 

메모리

MS-DOS

 

Turbo C++

 

프로그램

 

프로그램 변수 힙

 

힙  

남겨진

메모리

 

힙은 남겨진 메모리이다.

 

 

메모리 힙을 위한 new

힙에 정수를 하나 할당하자.

int * a = new int;

new라는 키워드는 힙을 사용하라는 의미이다. 힙에 int 만큼 메모리를 잡아서 변수 a가 가리키도록 한다.

물론 하나의 변수를 힙에 할당하는 것은 그렇게 좋은 것은 아니다. 다음과 같이 보통 배열을 할당할 때 사용한다.

int *a = new int[100];

 

 

예제

   Heap에 배열할당하고 초기화하기

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    int *arr = new int[10];     // 메모리 할당

    clrscr();

    for(int i = 0; i < 10; i++){      //초기화

     *(arr+i) = i*2;

    }

    for(i = 0; i < 10; i++){             // 출력

     cout << *(arr+i) << endl;

    }

    getch();

}


 

 

delete으로 메모리 해제하기

new로 메모리를 할당하면 delete으로 해제한다.

메모리를 반납하여 다른 자원이 이 메모리를 쓸 수 있도록 한다.

delete a;  // 메모리를 힙에 돌려줌

다음 문장은 new로 할당된 메모리의 배열을 해제한다.

delete [] arr;      // 힙으로부터 배열을 해제한다.

 

 

예제

   delete으로 메모리 해제

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    int *arr=new int[10];

    clrscr();

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

     *(arr+i)=i*2;

    }

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

     cout << *(arr+i)<<endl;

    }

    delete [] arr;   // 메모리 해제

    getch();

}


detete arr; 으로 하면 배열의 첫 번째 요소만 해제하므로 주의해야 한다.

 

 

다차원 배열

int (*arr) [6] = new int[5][6];      // (* arr) arr이 포인트라는 것이다.

int (*arr2)[5][6]= new int[4][5][6];

첫 번째 차원은 명시하지 않으며 두 번째 차원은 배열의 첫 번째 차원의 각 요소들을 위해 6개의 요소를 갖는다.

delete [] arr;       // []가 하나 뿐임을 주시하자.

delete [] arr2;

메모리를 해제한다.

 

 

예제

   2차원 배열 메모리 할당 및 해제

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void main(){

    int (*arr)[6] = new int[5][6];      // 할당

    clrscr();

    for(int i = 0;i < 5;i++)            // 초기화

        for(int j = 0;j < 6;j++)

        *(*(arr + i) + j) = i + j;

    for(i = 0;i < 5;i++){             // 출력

        for(j = 0;j < 6;j++)

           cout << setw(5) << arr[i][j];

        cout << endl;

    }

    delete [] arr;          // 해제

    getch();

}


 

 

함수간의 매개변수 전달

함수에 매개변수를 전달하는 방법은 값에 의한 매개변수 전달과 주소에 의한 매개변수 전달이 있다. 또는 값에 의한 호출, 주소에 의한 호출이라고 한다.

값에 의한 호출은 매개 변수 전달 시 매개변수의 값을 복사하여 사용한다. 따라서 원변수의 내용은 바뀌지 않는다. 그러나 주소에 의한 호출은 변수의 주소를 참조하므로 변수의 내용이 바뀌게 된다. 앞에서 우리는 포인트를 배웠다. 이 포인트를 이용하는 방법이 주소에 의한 함수 호출이 되겠다. C++에서 추가된 전달방법이 레퍼런스이다.

 

 

레퍼런스에 의한 호출

레퍼런스도 포인트와 비슷하다. 아니 포인트라고 해도 타당하다.

아래의 예제는 값, 포인트, 레퍼런스에 의한 호출을 비교한 프로그램이다.

 

 

예제

   값, 포인트, 레퍼런스에 의한 호출 비교

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

void call_by_value(int x){    // 값에 의한 호출 함수

   x=x * 2;

}

void call_by_point(int * x){  // 포인트에 의한 호출 함수

   *x=*x * 2;

}

void call_by_reference(int & x){      // 레퍼런스에 의한 호출 함수

   x=x * 2;                        // 자동 역참조된다. 역참조 연산자 '*'이 필요 없다.

}

void main(){

    int v=10,p=20,r=30;

    clrscr();

    call_by_value(v);                   // 함수 호출 후 v의 값이 바뀌지 않음

    cout <<"v= " << v << endl ;

    call_by_point(&p);                  // 함수 호출 후 p의 값이 바뀜

    cout <<"p= " << p << endl ;

    call_by_reference(r);                // 함수 호출 후 r의 값이 바뀜

    cout <<"r= " << r << endl ;

    getch();

}


 

위 예제에서 보듯이 레퍼런스는 자동 역참조되는 포인트이다. 포인트를 사용하는 것보다 훨씬 간결하고 역참조 연산자를 사용하지 않으므로 이해하기도 쉽다.

 

 

레퍼런스 전달의 효율성과 안전성

레퍼런스 매개변수를 부주의하게 변경하지 못하도록 하기 위해 받아들인 레퍼런스 매개변수 앞에 const를 둘 수 있다.

먼저, 받는 함수가 호출 함수의 값을 변경하지 못하게 할 때 왜 레퍼런스에 의해 매개변수를 전달하는지 궁금할 것이다. 값에 의해 매개변수를 전달하는 것이 더 수월하고 받는 함수가 보내는 함수의 값을 건드리지 못할 것 같다.

값에 의해 매개변수를 전달하는 것은 프로그램의 몇 가지 비효율성을 만든다. 값에 의해 변수를 전달할 때 특히 커다란 구조체 변수일 경우, Turbo C++는 변수의 지역 복사본을 만들기 위해 상당한 실행시간을 요구한다. (그것은 by value라는 용어를 흔히 by copy라고도 하는 이유이다).

그러나, 주소나 레퍼런스에 의해 변수를 전달할 때 데이터 그 자체보다는 데이터에 대한 포인터가 전달된다.(레퍼런스에 의한 전달 때 포인터는 자동적으로 역참조된다). 그러므로, 효율성이 중요하면, 레퍼런스에 의한 전달이 좋은데, 받는 함수가 매개변수를 변경하지 못하도록 하려면 const를 받는 값 앞에 두는 것이다.

 

 

레퍼런스 Return(반환)

아래의 max( x , y)는 x, y 중 큰 수를 반환하다. 정확히 말하자면 큰 수의 레퍼런스를 반환한다. 즉 값이 아닌 변수를 반환하는 것이다.

 

int & max(int & x, int & y){

  return (x > y)? x: y;

}

 

따라서 아래의 식이 성립된다.

 

max(a, b)=0;    // a, b중 큰 수의 값이 0이 된다.

 

물론 z= max(a, b)의 형태도 된다.

아래의 예제는 레퍼런스 반환을 보여주고 있다.

 

 

예제

   레퍼런스 반환

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

int & max(int & x, int & y){

  return (x > y)? x: y;

}

void main(){

    int a=10,b=20,z;

    clrscr();

    z=max(a, b);                      // z=20이 된다.

    cout << "z= " << z << endl;

    max(a, b)=0;                     // b=0이 된다. a의 값(10)은 변화 없다.

    cout << "a= " << a << endl;

    cout << "b= " << b << endl;

    getch();

}


 

 

 디폴트 매개변수

두 수의 합을 반환화는 sum(x, y)함수를 만들어 보자.

 

int sum (int x, int y){

  return (x+ y);

}

 

세 수의 합을 반환하는 함수 sum2(x, y, z)를 만들어 보자.

 

int sum (int x, int y, int z){

  return (x+ y+ z);

}

 

4, 5, 6,...가지 수의 합을 구하는 함수가 필요하면 별 수 없이 모두 만들어야 했다. 하지만 C++는 디폴트 인수를 사용하여 여러분의 타이핑 수고를 많이 들어준다.

 

int sum (int a, int b, int c = 0, int d = 0, int e = 0){

  return (a+ b+ c+ d+ e);

}

 

매개변수에 값이 주어진 것들이 있다.  c, d, e가 디폴트 매개변수(인수)이다.

디폴트 인수는 생략할 수 있다. 생략하면 디폴트 값(c = 0, d = 0, e = 0)이 들어간다.

 

x=sum(1, 2);   // x = 3   :  a = 1, b = 2, c = 0, d = 0, e = 0

x=sum(1, 2, 3);   // x = 6   :  a = 1, b = 2, c = 3, d = 0, e = 0

x=sum(1, 2, 3, 4);   // x = 10   :  a = 1, b = 2, c = 3, d = 4, e = 0

x=sum(1, 2, 3, 4, 5);   // x = 15   :  a = 1, b = 2, c = 3, d = 4, e = 5

 

마술 같은 일이 아닐 수 없다.(C++ 제작팀에게 감사하는 마음을 가지자....^^)

하나의 함수로 위와 같이 여러 가지 경우를 처리할 수가 있다.

 

매개변수에 디폴트 값을 주는 것을 '매개변수 초기화' 라고 한다.

초기화된 매개변수는 생략이 가능하고 반대로 초기화되지 않은 매개변수는 생략이 불가하다. x=sum(1)은 불가능하다.

또 초기화된 매개변수는 매개변수 리스트에서 맨 뒤로 보내야한다. 아래의 함수는 에러다.

 

int func(int x, int y = 0, int z){...}

 

int func(int x, int z, int y = 0){...}  // 에러 없음

 

조금만 생각해보면 당연하다는 것을 알 수 있을 것이다.

 

 

 cin.getline(city,25),  cin.getline(city,25,'#')에서 마지막 매개변수가 디폴트 인수이다.

 마지막 인수를 생략하면 cin.getline(city, 25, '\n')이 된다.

 

 

혼자 해보기

 

평균을 구하는 avg()함수를 만들어 보자. 매개변수는 4개이고 디폴트 매개변수는 2개가 되어야 한다.

 

 

Overloaded Function(오버로드 함수)

 

a = 5 * 3 ;   // (1)

b = * p;      // (2)

 

연산자 '*'는 (1)에서는 곱셈을 의미하고, (2)에서는 포인트임을 의미한다. 같은 연산자지만 형태에 따라 Turbo C++는 정확히 구별하고 있다. 즉 연산자 '*'가 다중 정의되어 있음을 의미한다. 연산자 다중정의를 '연산자 오버로드'라고 한다.

( 비트연산자 '<<' 와 cout의 '<<'는 오버로드 연산자이다.)

 

그렇다면 오버로드 함수는 무엇인가?

다중 정의된 함수를 오버로드 함수라 하며, 오버로드 함수를 작성하는 행위를 함수 오버로딩이라고 한다.

 

int func(int i){ return i; }    // (1)

char func(char c){ return c; }  // (2)

float func(float f){ return f; }  // (3)

 

함수 func()는 단순히 매개 변수의 값을 반환하고 있다. func()는 다중정의 되어 있음을 볼 수 있다.

 

a=func(10);

 

위의 문장에서 (1),(2),(3) 중 어느 것이 호출될까?

생각할 것도 없이 (1)이 호출됨을 알 수 있다.

매개변수의 자료형에 따라 그에 맞는 함수가 호출된다.

 

"오버로드 함수는 함수의 이름은 같고 매개변수의 개수나 매개변수의 자료형이 달라야한다."

 

void good(int a){...}

int good(int a, int c){...}

 

매개변수의 개수가 다르므로 good()는 타당한 오버로드 함수이다.

 

char bad(int a){...}            // (1)

int bad(int a, int b = 1){...} // (2)

 

위의 경우는 어떤가?

bad(1)을 호출하면 어느 것이 호출될까?  (2)에서 매개 변수 b는 생략이 가능하다.

그러므로 어느 함수를 호출할 지 애매하다.

Turbo C++ 는 어느 함수를 호출해야 할지 애매하다는 에러 메시지를 보내고 컴파일을 중단한다. 따라서 위의 문장은 잘못된 것이므로 다음과 같이 수정해야한다.

 

char bad(int a){...}            

int bad(int a, int b){...}

 

 

혼자 해보기

 

같은 이름의 두 함수를 가진 프로그램을 작성해 보자. 첫 번째 함수는 그것에 전달되는 정수형 배열의 평균을 반환하고, 두 번째 함수는 부동-소수형 배열의 평균을 반환한다.

 

operator...() 함수에 의한 연산자 오버로딩

 

연산자 '+'를 오버로딩해 보자.

 

int operator + (const struct buza & x, const struct buza & y){

 return x.money + y.money;

}

a = x.money + y.money;   // 이렇게 하기보다는 아래의 문장이 코드 량을 줄여준다.

a= x + y;                // 또는 a = operator + ( x , y );

 

 

예제

   '+' 오버로딩

#include<iostream.h>

#include<iomanip.h>

#include<conio.h>

struct buza{

 int money;

};

int operator + (const struct buza & x, const struct buza & y){

 return x.money + y.money;

}

void main(){

    buza a, b;

    a.money = 10;

    b.money = 20;

    clrscr();

    cout << a + b;

    getch();

}


 

Class를 내 것으로 만들자..!!

 

class 만들기

 

class Myclass{   // Myclass를 정의한다.

  int a;

  int b;

};

 

Myclass my;   // Myclass변수 my를 정의한다.

 

위의 예에서 Myclass 는 class이고 my는 Myclass형 변수가 된다.

클래스 이름은 대문자로 시작하도록 하자. ( Myclass )

a, b는 클래스의 멤버 변수라고 한다.

my는 멤버 변수 a, b를 가지게 된다. 즉 my.a , my.b를 가진다.

 

class와 struct의 비교

 

 

구조체

클래스

 struct mystruct{

   int a;

   int b;

 };

 class myclass{

   int a;

   int b;

 };

 struct mystruct mys;

     또는,

 mystruct mys;

 class myclass myc;

     또는,

 myclass myc;

필드

멤버

 mys. a 와 mys. b

 myc. a 와 myc. b

 

표에서도 알 수 있듯이 구체와 클래스는 아주 비슷하다. 그러나 구조체는 단순한 자료의 집합이고 클래스는 동작(함수)을 포함한다.

 

접근 지정자(공용과 전용)

구조체는 멤버가 디폴트로 공용(public)이다. 그러나 class는 디폴트로 전용(private)이다.

위의 구조체, 클래스 비교 표에서 mys. a 와 mys. b는 공용이고, myc. a 와 myc. b는 전용으로 엑세스 보호된다. 다시말하면 클래스 외부에서 멤버를 엑세스할 수 없다.

그러나 다음과 같이 하면 멤버가 공용이 되어 클래스 외부에서 접근 가능하다.

 

class myclass{

public:         // 멤버를 공용으로 지정

  int a;

  int b;

};

public 키워드 뒤에 ':'이 있음을 유의하자. private도 역시 콜론으로 마친다.

 

 

예제

  클래스 공용 멤버 엑세스

#include<iostream.h>

#include<conio.h>

class Time{

public:          // 멤버를 공용으로 사용 가능하게 함

    int hours;

    int minutes;

    int seconds;

};

void main(){

    Time clock;

    clock.hours=10;

    clock.minutes=30;

    clock.seconds=25;

    clrscr();

    cout << clock.hours << ":" << clock.minutes << ":" << clock.seconds <<endl ;

    getch();

}


 

위의 예제에서 public:를 private으로 고치면 몇가지 에러 가 뜬다.

 

'Time::hours' is not accessible

'Time::minutes' is not accessible

'Time::seconds' is not accessible

 

여기서 영역지정자(::)가 나왔다. Time::hours 는 'Time의 멤버 변수 hours'라고 해석하면 된다. 즉 멤버 변수에 접근할 수 없다는 에러 메시지이다.

그렇다면 어떻게 전용변수에 접근할 수 있을까? 이 물음에 대한 해답은 다음 절에서 배울 맴버 함수에서 다룰 것이다.

아래의 예제는 어떻게 될까? 생각해보자.

 

 

예제

  

#include<iostream.h>

#include<conio.h>

class Time{

    int hours;

public:

    int minutes;

    int seconds;

};

void main(){

    Time clock;

    clock.hours=10;

    clock.minutes=30;

    clock.seconds=25;

    clrscr();

    cout << clock.hours << ":" << clock.minutes << ":" << clock.seconds <<endl ;

    getch();

}


 

 

변수가 스스로 동작하게 하는 멤버 함수

멤버 함수(member functions)를 통해서, 프로그램의 모든 전용 데이타를 엑세스할 수 있다.

 

변수를 출력하고자 할 때 여러분이 그 변수를 출력하지 않아도 그 변수는 스스로 출력할 것이다. 이런 마술같은 데이터 동작은 객체의 멤버 함수를 통해 모두 수행된다.

 

class Man {

private:           // 멤버 변수는 private로 지정( 생략해도 되나 넣는 것이 좋음)

  char *name;

  int age;

  int height;

public:             // 공용 멤버 함수로 멤버 변수에 접근

  void getname(char *s){

     name=s;

   }

}

getname()은 매개 변수 s를 받아 멤버 변수 name에 대입한다.

 

Man man1 ;

man1.getname("김철수");

 

멤버 변수 name이 private이므로 접근 불가하지만 멤버 함수 getname()을 이용하여 간접적으로 접근할 수 있게 된다.

 

 

예제

  공용 멤버 함수

#include<iostream.h>

#include<conio.h>

class Man{

private:

    char *name;

    int age;

    int height;

public:

    void getname(){

       cout << "What's your name? ";

       cin >> name;

    }

    void putname(){

       cout << "your name is " << name << endl;

    }

};

void main(){

    Man man1;

    man1.getname();

    man1.putname();

    getch();

}


 

위의 예제에서 getage(), getheight(), putage(), putheight()를 추가해 보자.

 

다음과 같이 클래스 데이터 멤버를 초기화할 수는 없다.

 

class Abc{

private:

   char a='g';        // 클래스 변수가 아직 정의되지 않았기 때문에

   int b=33;          // 초기화할 수 없다.

   float c=89.12;

public:  

   //생략

}

 

멤버 함수의 외부 정의

공용 멤버 함수 예제를 다음과 같이 할 수도 있다.

 

 

예제

  class 간결화

#include<iostream.h>

#include<conio.h>

class Man{

private:

    char *name;

    int age;

    int height;

public:

    void getname();           // 멤버 함수 프로토타입

    void putname();

};  // 클래스 정의 끝

void Man::getname(){           // 멤버 함수 getname() 정의 부분

     cout << "What's your name? ";

     cin >> name;

}

void Man::putname(){            // 멤버 함수 putname() 정의 부분

     cout << "your name is " << name << endl;

}

void main(){

    Man man1;

    man1.getname();

    man1.putname();

    getch();

}


 

클래스를 내부에 정의하면 클래스가 너무 길어진다. 따라서 클래스를 간결화 하기 위해 멤버 함수를 클래스 외부에 정의한다.

영역지정 연산자 (::)를 사용하여 클래스의 멤버 변수임을 밝힌다.

 

class에서 inline 함수 사용

클래스의 효율성을 개선할 수 있게 하는 또 한가지 사항이 있다. 클래스 멤버 함수에 inline을 추가하자. inline이 함수 호출을 함수 코드 자체로 확장하는 것이라고 앞에서 배웠다.

Turbo C++가 멤버 함수 코드를 성공적으로 인라인할 때 클래스 멤버 함수 실행이 보다 효율적이 된다.

아래의 예제는 위의 예제(class의 간결화)에서 inline 키워드를 추가했다.

x1행과 x2행을 유심히 보기 바란다.

 

 

예제

  class의 인라인 사용

#include<iostream.h>

#include<conio.h>

class Man{

private:

    char *name;

    int age;

    int height;

public:

    void getname();           // 멤버 함수 프로토타입

    void putname();

};  // 클래스 정의 끝

inline void Man::getname(){           // x1행

     cout << "What's your name? ";

     cin >> name;

}

inline void Man::putname(){            // x2행

     cout << "your name is " << name << endl;

}

void main(){

    Man man1;

    man1.getname();

    man1.putname();

    getch();

}


 

 

 

멤버 함수를 클래스 안에 직접 넣었다면 inlin을 명시할 필요는 없다. Turbo C++는 자동적으로 그 함수들을 인라인한다.

 

this 포인트 변수

this 포인프 변수란 오브젝트 자신의 시작 주소를 가리키는 포인트 변수이다.

위의 예제에서

 

inline void Man::getname(){           

     cout << "What's your name? ";

     cin >> name;

}

는 아래의 구문과 동일하다.

inline void Man::getname( ){           // x1행

     cout << "What's your name? ";

     cin >> this->name;

}

'Development > C/C++' 카테고리의 다른 글

[펌] Observer pattern (C++)  (0) 2011.08.13
[펌] Effective C++  (0) 2011.08.13
[펌] 펌) C++ Programming HOW-TO  (0) 2011.08.13
[펌] 메모리 할당 메소드 비교  (0) 2011.08.13
[펌] [譯] CStdString - 표준 C++ 을 사용한 CString  (0) 2011.08.13
안정적인 DNS서비스 DNSEver DNS server, DNS service
Posted by 키르히아이스
,