번 호 : 2632
게시자 : 김상형 (mituri )
등록일 : 1998-05-24 04:13
제 목 : [C++] OOP 기초 강좌 #0
이 강좌는 C++ 입문용 강좌입니다. C는 어느 정도 개념을 알고 있지만
C++이나 OOP에 관해서는 도통 개념이 없는 분들을 위하여 작성되었으
며 C++을 전혀 모른다는 가정하에 강좌를 진행합니다. 강좌 곳곳에 삽
입되어 있는 예제들은 C++의 특성을 가장 잘 설명할 수 있도록 의도적
으로 작성되었으므로 쉽게 C++에 익숙해지리라 생각합니다.
전체적인 목차는 다음과 같습니다.강좌 내용이 무척 많으므로 아예 온
라인 상에서 읽을 생각은 하지 않는 것이 좋으며 캡처하신 후 읽어보
시기 바랍니다.
제 1 장 C++로의 확장
1-1 I/Ostream 소개
1-2 문법적 엄격성
가. 함수의 원형 반드시 정의
나. 매개변수 type 생략 불가능
1-3 확장된 기능
가. 함수 중간에서 변수 선언
나. 동일 이름의 함수 존재 가능
다. default parameter
라. inline함수
마. 주석
바. 예약어
사. 구조체 tag가 data type이 된다.
아. 이름없는 공용체
사. 명시적 캐스트 연산자
1-4 첨가된 기능
가. 참조호출
나. scope 연산자
다. new,delete
라. linkage 지정
제 2 장 클래스
2-1 구조체의 확장
2-2 class
가. class의 정의
나. instance
다. 멤버 Access
2-3 생성자, 파괴자
가. 생성자
나. 파괴자
다. 생성자, 파괴자의 특징
2-4 Access 권한 지정자
2-5 friend 함수의 개념
2-6 class 배열 및 pointer
2-7 shtet2 분석
제 3 장 특성 계승
3-1 C++의 부품
3-2 상속
가. 기반 class, 파생 class
나. Access 지정자
다. 생성자 호출
라. class정의 관행
3-3 가상 함수
가. 이차상속
나. 가상함수
다. 가상함수 재정의
라. 상속과 pointer
3-4 다중 상속
가. Message class
나. Messcir class
다. 가상 기반 class
라. 추상 class
마. 상속의 방향성
제 4 장 연산자 오버로딩
4-1 연산자 함수
가. 기존 type의 연산자
나. 멤버 연산자 함수
다. 인수와 피연산자의 개수
라. 복소수 class연산
마. 일반 함수와 연산자 함수 비교
4-2 friend 연산자 함수
가. class와 기존 data의 연산
나. friend 함수
4-3 연산자 오버로딩 규칙
4-4 오버로딩 예
가. 관계 연산자
나. 문자열 연결 연산자
다. 문자열 출력 연산자
라. [ ] 연산자
제 5 장 OOP
5-1 정적 member
가. 정적 data member
나. 정적 member함수
5-2 const member
5-3 this 포인터
5-4 data형 변환
가. 생성자 변환
나. 일반 data type으로의 변환
다. class끼리의 변환
5-5 동적 객체 생성
5-6 Template
가. 함수 template
나. class template
5-7 OOP 일반 이론
가. 둥장 배경
나. 캡슐화
다. 추상성
라. 다형성
마. 상속성
바. 재사용성 문제
제 6 장 I/O 스트림
6-1 출력
가. cout과 <<연산자
나. data 출력 함수
다. 사용자 정의 data 출력
6-2 출력 형식 지정
가. 폭지정, fill지정
나. 진법지정
다. manipulator
라. 좌우 정렬
6-3 입력
가. cin과 >> 연산자
나. 사용자정의 데이터 입력
6-4 파일 입출력
6-5 iostream.h의 분석
이 강좌는 가남사 발행 "C++을 내것으로" 문법편의 일부입니다. 강좌
중에 삽입된 그림은가급적 보기 쉽도록 다시 그리고자 했으나 너무
복잡하거나 텍스트 환경에서 그리고 어려운 부분은 생략하였습니다.
이 강좌에서 사용된 모든 예제는 하이텔 공개 자료실 CPDS-6번의
1393번에 있습니다. 번호가 언제 바뀔지 모르므로 li mituri로 검색
하시기 바랍니다.강좌 본문에서는 표현의 간결을 위하여 존칭을 사용
하지 않으니 양해 바랍니다.
만약 강좌 내용중 틀린 부분이 있으시면 반드시 하이텔 아이디 mituri
로 메일을 보내 주셔서 우매한 저에게 가르쳐 주시기 바라며 의문 사
항이 있으실 경우에도 메일을 보내 주시기 바랍니다. 질문 사항에 대
해서는 반드시 답장을 드릴 수 있도록 노력하겠습니다.
감 사 합 니 다.
인간 사랑의 과학 실천
경희대학교 컴퓨터 연구회 COM.COM
학술부장 김 상 형
추가:원래 두루물 강좌란에 있던 강좌인데 두루물 시삽이란 사람이
게시판을 정리한답시고(이해할 수 없음) 강좌를 몽땅 지워 버렸습니다.
원하시는 분들이 많아 다시 올립니다.
번 호 : 2633
게시자 : 김상형 (mituri )
등록일 : 1998-05-24 04:15
제 목 : [C++] OOP 기초 강좌 #1
1장.C++로의 확장
이 장에서는 C와 C++과의 문법상의 차이점을 알아본다. Borland C++
3.1은 Turbo C 2.0의소스 파일을 그대로 사용할 수도 있고 확장된 C++
도 사용할 수 있다. 어느 때 어떠한 문법을 적용시켜 컴파일하는가는
소스 파일의 확장자에 따라 달라지는데 확장자가 .C면 C문법대로 컴파
일하고 확장자가 .CPP이면 C++ 문법대로 컴파일하게 된다.
C에 OOP 개념이 추가된 것이 C++이지만 , 단순히 OOP 개념이 추가된
것 뿐만 아니라 다른 면으로도 많은 변화가 보인다. OOP를 빼고라도
기본 문법이 변한 부분을잘 모르면C++을 사용하는 데 걸림돌이 된다.
C를 잘 아는 사람이 C++을 알고자 할 때는 이 차이를먼저 보는 편이
좋다. C에서 용납되는 것이 C++에서는 문법의 엄격성으로 그렇지 못한
것들도 있고 그 반대도 있다.
이 장에서 다루고자 하는 것은 C 문법과 C++의 차이점에 관한 것이지
C++ 이론에 관한전반적인 내용을 다루고자 하는 것은 아니다. 추가된
새로운 것들, 특히 그 중에서도 class라든가 상속, 가상 함수 등등은
이 장 이후에 계속 소개해 나간다. 보통 C 하면 C 언어를의미한다. 넓
게는 C++이 여기에 포함되기도 하지만 이 장에서 C는 구체적으로
Turbo C2.0에서 사용되는 C 문법을 말하는 것이고 C++은 Borland C++
3.1에서 사용하는 문법을 말하는 것임을 고려하기 바란다.
**********************************************
**********************************************
1-1 I/O 스트림 소개
C와 C++이 표면적으로 제일 먼저 차이가 나는 것은 기본적인 입출력
방법이다. C에서 가장 자주 사용하고 C를 배우는 사람이 제일 먼저 배
우는 함수가 printf 함수이다. 그러나C++에서는 printf를 잘 사용하지
않으며 이보다 더발전된 형태의 cout 객체를 사용한다.
우선 C와 C++의 출력 형태를 비교해주는 두 개의 예제를 보자. C에서
는 printf를 사용하여 다음과 같이 데이터를 출력한다.
#include
void main()
{
printf("Hello World !\\n");
}
이 예제는 C를 배우는 사람들이 제일 먼저 구경하는, C의 진가를 보여
주는 전설적인 예제이다. 다음은 C++ 문법을 사용하여 똑같은 출력을
하도록 하는 예제를 보자.
#include
void main()
{
cout << "Hello World !\\n";
}
이러한 출력 방법을 입출력 스트림을 사용하는 방법이라고 한다. 두
예제는 다음과 같은문자열을 출력하고 프로그램을 종료하는 똑같은 동
작을 한다.
Hello World !
똑같은 동작을 하지만 구현 원리는 상당히 다르다. 어떻게 다른지 구
체적인 차이점들을보자.
1. 우선 C에서는 함수를 사용하였지만 C++에서는 객체를 사용하였다.
C++을 처음 배우는 사람은 객체가 뭔지 잘 모르겠지만 어쨌든 함수를
사용하는 것보다는 더 융통성이많고 확장이 용이한 방법이다.
2. 헤더 파일이 달라졌다. C 프로그램에는 으례 #include <stdio.h>가
제일 먼저 들어가는것이 보통이지만 C++에서는 stdio.h 대신에
iostream.h가 쓰인다. iostream.h에 정의되어있는 class가 stdio.h에
원형이 선언되어 있는 함수의 기능들을 대부분 다 수행할 수 있기 때
문이다. C++ 프로그램의 선두에는 으례 #include 가 들어
간다.
3. 사용법이 더욱 간단해지고 직관적이다. printf 함수는 C 함수 중에
서도 제일 먼저 배우는 함수지만 제일 복잡한 함수이다. 세세하게 출
력 형식을 지정하려면 복잡한 형식 지정에 대해 알아야 한다. 하지만
입출력 스트림을 사용하는 방법은 비교적 쉽게 배울 수있고 쓰기도 간
편하다.
C++을 배우기 위해서는 여러 가지 예제를 보아야 하는데 각 예제마다
기본적인 입출력을 위해 입출력 스트림을 사용하므로 먼저 입출력 스
트림을 사용하는 법을 제일 먼저 배우고 난 뒤 C++을 공부하는 것이
순서이다. 구체적인 구현 원리는 C++을 공부하면서 천천히 알더라도
여기서는 당장 입출력 스트림을 어떻게 사용하는가만 알고 넘어 가도
록하자. 입출력 스트림을 사용해 데이터를 출력하는 기본 구문은 다음
과 같다.
cout << 출력할 데이터 << 출력할 데이터 .....;
cout란 iostream.h에 정의되어 있는 입출력 객체의 이름인데 이 안에
는 데이터 출력을 위한 모든 처리가 다 되어 있다. 그래서 데이터가
정수형이건, 실수형이건, 문자열이건 어떠한 형태이더라도 전부 출력
해 낼 수 있다. printf같이 귀찮게 %d, %s, %f 등을 구분할 필요가 없
어졌다. "cout <<" 다음에 출력하기를 원하는 데이터를 써주면 된다.
다음 예제는여러 가지 타입의 변수들을 출력하는 예제이다.
#include <iostream.h>
void main()
{
int i=5;
char ch='S';
double pie=3.1415;
cout << i << endl;
cout << ch << endl;
cout << pie << endl;
}
출력 결과는 다음과 같다.
5
S
3.1415
보다시피 정수형, 실수형, 문자형을 각각 출력하더라도 그 데이터 타
입을 별도로 밝히지않는다. cout 객체 내부에서 출력할 데이터의 타입
을 자동으로 검사하여 적절한 처리를하기 때문이다. 위 예제에서 쓰인
endl은 printf의 "\\n"에 해당하는데 개행을 하고자 할 때사용된다.
cout는 여러 개의 데이터를 한꺼번에 출력하는 것도 가능하다. << 표
시(일종의연산자이다) 뒤에 출력할 데이터를 계속해서 써 주기만 하면
된다. 다음 예제를 보자.
#include <iostream.h>
void main()
{
int i=5;
double pie=3.14;
long l=12345678;
cout << "integer is " << i << ", pie is " << pie
<< ",long number is " << l << endl;
}
출력 결과는 다음과 같다.
integer is 5, pie is 3.14,long number is 12345678
printf문으로도 물론 이런 것은 가능하지만 좀 더 직관적이고 명확한
형태를 가짐으로써사용의 편리를 추구한다. 다음은 cin 객체를 이용한
입력 방법에 대해 간단하게 알아보자.
기본적인 구문은 다음과 같다.
cin >> 입력받을 변수;
cin이 키보드로부터 값을 입력받아 변수에 대입해준다. cin도 물론 여
러 개의 변수에 한꺼번에 입력받을 수 있지만 대개 입력은 한 변수만
단독으로 받는 경우가 많다. 다음 예제는 키보드로부터 정수값을 입력
받아 i에 대입해준다.
#include
void main()
{
int i;
cout << "Input a number :";
cin >> i;
cout << "result is " << i << endl;
}
cin도 cout과 마찬가지로 입력받을 데이터형에 상관없이 사용할 수 있
다. 여기서 알아본입출력 스트림에 관한 사항은 어디까지나 C++ 공부
를 위한 준비 단계로서 필요한 내용만 알아본 것이다. 아무리 간단하
고 쓰기 편하게 만들었다고 해도 내부적으로는 굉장히복잡하고 나름대
로 주의 사항도 많이 있다. 이후 C++을 설명하는 예제에서 사용하는
입출력 스트림 구문만 이해할 수 있을 정도로 대충 알아두고 자세한
사항은 다음 기회에 다시 거론하기로 한다.
**********************************************
**********************************************
1-2 문법적 엄격성
가. 함수의 원형 반드시 정의
함수의 원형이란 컴파일러에게 함수의 형태에 관한 정보를 알려주는
것이며 다음과 같은 형태로 C에서 함수의 원형을 밝혀주었었다.
*표준 함수 : 그 함수의 원형이 포함되어 있는 헤더 파일을 include
하여 원형을 선언한다.
* 사용자정의 함수 : main 파일의 선두에 함수의 원형을 직접 밝혀 준
다. 다중 모듈의 프로젝트인 경우는 별도의 헤더 파일을 만들어 두고
헤더 파일에 사용자정의 함수의 원형을 모두 밝힌 후 main 파일에서는
헤더 파일을 include하기도 한다.
C를 제대로 공부했다면 이 정도는 잘 알고 있을 것이다. 그런데 C에서
는 예외 사항이 존재하는데 그 예외 사항이란 함수의 원형이 밝혀지지
않은 경우에 디폴트 원형을 적용한다는 것이다.함수의 디폴트 원형은
리턴값이 int형이며 인수에 관한 규정은 없다. 즉 선언되지 않은 함수
nodecl() 함수가 호출되면 이 함수의 리턴값을 정수형으로 취급하고
인수는 호출시의 인수를 사용하도록 되어 있다. 그래서 이러한 디폴트
원형과 일치하는 원형을 가지는 함수는 원형을 선언해주지 않더라도
제대로 컴파일되었다.
printf, putch, scanf 등등 우리가 항상 애용했던 이러한 함수들이
모두 이 부류에 속하는함수들이다. 대표적으로 printf를 보면 특별히
리턴할 값도 없는 주제에 int형을 리턴하고있는데 아마도 디폴트 원형
과 맞추기 위한 것이 아닌가 하고 생각된다. 그래서 C에서 이런 함수
를 쓸 때는 헤더 파일을 습관적으로 생략하곤 한다. 하지만 C++에서는
그렇지 못하다. 디폴트 원형 따위를 적용해주는 일은 없으며 사용한
모든 함수에 대해 원형을 일일이 밝혀주거나 헤더 파일을 포함시켜야
한다. 문법적으로 좀더 엄격한 구조를 가짐으로써 실수를 미연에 방지
하기 위해서이기도 하지만 C++의 특성인 다형성(polymorphism)이실현
되기 위해서는 모든 함수의 원형이 반드시 필요하기 때문이다. 다음
예제를 실행시켜 보자.
void main()
{
printf("prototype\\n");
}
더 이상 설명을 할 필요가 없는 간단한 프로그램이다. 이 프로그램을
test.c라는 이름을 써서 디스크에 보관 후 실행시키면 C 문법을 따르
기 때문에 무리없이 실행된다. 하지만 이름을 test.cpp라는 이름으로
확장자를 바꾸어 다시 저장하고 컴파일시키면 C++ 문법을 따르므로 다
음과 같은 에러 메시지가 나고 컴파일은 중단된다.
Function 'printf' should have a prototype.
함수의 원형이 정의되지 않아 컴파일할 수가 없다는 뜻이다. #include
<stdio.h>를 포함시켜 주면 문제가 해결된다. 정리하자면 C++에서는
모든 함수의 원형을 밝혀주어야 하며절대로 생략할 수 없다는 것이다.
위와 같은 메시지를 만나는 족족 레퍼런스나 help를 뒤져 그 함수의
원형이 선언되어 있는 헤더 파일을 포함시켜 주도록 하자.
나. 매개 변수 타입 생략 불가능
C에서 함수의 원형을 밝혀주는 방법에는 3가지 방법이 있다.
1 함수의 리턴값만 명시한다.
int func();
2 함수의 리턴값과 인수의 타입만 명시한다.
int func(int,char);
3 함수의 리턴값과 인수의 타입, 인수의 이름까지 선언한다.
intfunc(int a,char c)
이 세 가지 방법이 모두 가능하며 이중 2번 방법이 제일 권장되는 방
법이다.
그런데 사람이 원래 간편한 것을 좋아하는 속성이 있어 1번 형식을 애
용하는 사람들이 많이 있는데 이 형식을 사용하더라도 C가 인수 타입
을 체크해주지않는다는 것뿐 별 무리는 없었다. 그러나 C++에서는 이
러한 간편한 방법이 허용되지 않으며 반드시 인수의 데이터 타입을 밝
혀주는 2번 형식이나 3번 형식을 사용해야 한다.
그렇지 않으면 다음과 같은 에러가 난다.
Extra parameter in call to func()
원형에는 인수가 없는데 호출시에 인수가 발견되었으므로 에러로 간주
된다. 인수의 데이터 타입을 반드시 밝혀주어야 하는 것도 문법이 엄
격한 쪽으로 변한 것이며 이름은 같고 인수가 다른 함수들을 구별해내
기 위한 다형성을 위한 것이다. 다음 예제를 보자.
#include
int add();
void main()
{
printf("3+2=%d",add(3,2));
}
int add(int a,int b)
{
return a+b;
}
이 예제를 C 형식으로(확장자가 C) 컴파일하면 문제가 없지만 C++ 형
식으로(확장자가cpp) 컴파일하면 에러가 발생한다. 함수의 원형은 반
드시 밝혀주되 2번 형식이나 3번형식, 그 중에도 특히 2번 형식이 권
장할 만하다.
**********************************************
**********************************************
1-3 확장된 점
가. 함수 중간에서 변수 선언
C에서 변수는 반드시 함수 바깥에서 선언(전역 변수)되거나 또는 함수
내부의 선두(지역변수)에서 선언되어야 한다. 그러나 C++에서는 함수
내부의 중간쯤에도 변수가 선언될수 있다. 다음 예제가 그 대표적인
예이다.
#include
void main()
{
int i;
i=3;
int j; // 새로운 변수를 선언
j=5;
for (i=1;i<10;i++)
printf("%d\\n",j);
}
5행에서 i에 3을 대입하는 대입문이 오고 난 후에도 변수 j를 선언하
는 것이 가능하다. C에서는 함수 호출, 연산식 등의 코드를 생성시키
는 문장 이후에 변수를 선언하는 것은 불가능한 일이다. 그러나 이 방
법이 비록 가능은 하다고 해도 별로 권장할 만한 방법은 아니다. 변수
의 선언은 가급적 한 곳에 모아서 하는 것이 시각상, 효율상 훨씬 유
리하다.
변수의 선언이 함수 내부의 이곳 저곳에서 중구난방으로 이루어진다면
나중에 수정하기가 곤란해지고 변수가 숨바꼭질을 하는 사태가 발생하
게 된다. 다음과 같은 형태로 변수를 선언해서 사용하는 것은 꽤 편리
한 방법이며 실용적으로도 배워둘만한 방법이다.
#include <stdio.h>
void main()
{
int j;
j=588;
for (int i=0;i<10;i++) // 루프 내부에서 변수 선언
printf("%d\\n",i);
}
C++에서는 for, while 등의 순환문 선두에 변수를 선언하는 것이 가능
하다. for 루프 내부에서만 잠시 쓰이고 말 루프 제어 변수라면 굳이
함수 선두에서 만들 필요없이 for 루프에서 만들어 쓰는 것이 좋다.
이렇게 for 루프의 선두에서 선언된 변수는 for 루프에서만 사용되며
for 루프가 종료되는 즉시 기억장소가 반납되고 생명이 다하는 지역
변수이다. C에서 이러한 국부적인 변수를 선언할 수 없기 때문에 제어
변수로 잠시 쓰이는 변수라도반드시 함수의 선두에 선언해주어야 하는
불편함이 있었다. 참고로 다음과 같은 변수 선언도 가능하다.
#include <stdio.h>
void main()
{
int i;
for (i=1;i<5;i++)
{
int j; // 여기서 새로운 변수를 선언
j=i*2;
printf ("%d\\n",j);
}
}
함수의 선두가 아닌 for 루프의 블럭 시작 부분에서 변수가 선언되어
사용된다. C를 오랫동안 사용한 사람도 이 방법은 잘 모르고 있는데
C++에서 새로 추가된 것이 아니라 원래C에서 이 방법이 가능하다. C는
모든 종류의 블럭 선두에서({ }의 선두) 변수 선언이 가능하도록 되어
있다.
번 호 : 2634
게시자 : 김상형 (mituri )
등록일 : 1998-05-24 04:16
제 목 : [C++] OOP 기초 강좌 #2
나. 동일 이름의 함수 존재 가능
C에서 변수, 함수, 태그, 레이블 등은 모두 이름을 가지고 있기 때문
에 다른 것들과 서로구분이 된다. 구분되지 않고서는 모호함
(ambiguity)이 발생하기 때문에 반드시 하나의 변수, 함수에 유일한
이름을 주어야 하며 이름끼리 중복되어서는 안된다.
마치 쌍둥이가 모양이 같은데 이름까지 같아 버리면 서로 구분하기가
곤란하기 때문에다른 이름을 붙여주는 것과 같은 이치이다. 하지만
C++에서는 함수끼리 이름이 중복되어도 상관없는 경우가 있다. 비록
이름은 같더라도 인수의 개수가 다르거나 인수의 데이터 타입이 다르
면 서로 구분이 가능하기 때문이다.
쌍둥이더라도 하나가 여자고 하나가 남자면 또는 키 차이가 현격하다
면 이름이 같아도구분이 되지 않는가. 이렇게 함수가 같은 이름으로
여러 개 중복되어 존재하는 것이 가능한 것을 다형성(polymorphism)이
라 한다. 다음 예제를 보자.
#include <iostream.h>
int add(int,int);
double add(double,double);
void main()
{
int a,b;
double c,d;
a=3;
b=4;
c=3.14;
d=2.25;
cout << "add integer is " << add(a,b) << endl; // 정수의 add 호출
cout << "add real is " << add(c,d) << endl; // 실수의 add 호
출
}
int add(int a,int b) // 정수를 인수로 취하는
add 함수
{
return a+b;
}
double add(double c,double d) // 실수를 인수로 취하는 add 함수
{
return c+d;
}
add라는 이름의 함수가 두 개 존재하고 있다. 둘 다 인수의 개수는 2
개이지만 인수의 데이터 타입이 하나는 정수이고 하나는 실수이기 때
문이다. main 함수에서 처음 add(a,b)를호출했을 때 인수 a,b가 정수
이므로 이때는 정수를 인수로 취하는 함수 add를 호출하게되고 두 번
째 add(c,d)가 호출될 때는 c,d가 실수임을 근거로 실수를 인수로 취
하는 함수add를 호출하게 된다.
똑같은 동작을 하되 인수의 형이 다른 함수를 이렇게 같은 이름으로
만들어 두면 인수의형에 신경쓰지 않고 함수를 호출할 수 있어서 좋
다. 만약 위와 똑같은 예제를 C 문법을 이용해서 짠다면 다음과 같이
된다.
#include <stdio.h>
int addint(int,int); // 인수가 다른 두 함수의
이름이
double adddbl(double,double); // 서로 다르다.
void main()
{
int a,b;
doublec,d;
a=3;
b=4;
c=3.14;
d=2.25;
printf("add integer is %d\\n",addint(a,b)); // 정수의 add 호출
printf("add real is %f\\n",adddbl(c,d)); // 실수의 add 호출
}
int addint(int a,int b) // 정수를 인수로 취하는
add 함수
{
return a+b;
}
double adddbl(double c,double d) // 실수를 인수로 취하는
add 함수
{
return c+d;
}
똑같은 기능의 함수이지만 취하는 인수가 다르기 때문에 addint,
adddbl 등과 같은 별도의함수 이름을 주어야 한다. 똑같은 동작을 하
되 인수만 다른 함수를 이런 식으로 구별해서정의해야 한다는 것은 뭔
가 불합리하다. 물론 이 경우는 매크로 함수를 사용하면 간단하게 해
결되지만 매크로 함수는 간단한 연산만을 할 수 있으므로 일반적인 해
결책이 되지못한다. 같은 이름을 사용하는 두 개(또는 그 이상)의 함
수는 다음과 같은 조건 중 최소한한 개 이상을 만족해야 한다. 아무
함수나 이름을 같이 공유할 수 있는 것은 아니다.
* 전달되는 인수의 데이터 타입이 달라야 한다. 컴파일러는 함수의 이
름뿐만 아니라 함수 호출시에 전달되는 인수의 데이터 타입을 보고 호
출할 함수를 정확하게 찾아낸다.
int add(int,int);
double add(double,double);
char *add(char *,char *);
long add(int double);
등의 함수는 같은 이름을 사용하지만 모두 다른 함수로 취급된다.
* 인수의 데이터 타입이 설사 같더라도 인수의 수가 다르면 서로 다른
함수로 취급된다.
int func(int);
int func(int,int);
int func(int,int,int);
등은 같은 이름의 함수명을 사용하고 모두 정수형의 인수를 취하지만
호출시에 인수의 개수가 명확히 다르므로 구분이 가능하다.
* 통용 범위가 다른 함수는 이름이 같아도 상관없다. 마치 두 개의 다
른 함수 내에서 같은 이름의 변수를 사용할 수 있는 것과도 같다. 그
런데 변수는 지역 변수로 선언될 수있기 때문에 이런 것이 가능하지만
C의 함수는 지역 함수라는 것이 없으며(사실은 아주 드물지만 static
함수라는 것이 있다) 일단 선언되면 외부 모듈에 모두 알려지므로통용
범위가 다른 함수가 존재하지 않는다. 그러나 C++에서는 class의 멤버
함수가 외부로 알려지지 않는 특성이 있으므로 두 개의 다른 class에
서 정의된 멤버 함수가 통용범위가 다른 관계로 이름이 중복될 수 있
다. 다음에 class를 공부할 때 자세히 알아보아라.
사람이 눈으로 구별할 수 있는 것은 컴퓨터(=Compiler)도 구분할 수
있다. 여기까지의 설명은 크게 상식의 범위를 벗어나지 않으므로 비교
적 쉽게 이해했으리라 생각한다. 그런데 혹시 "리턴값의 데이터 타입
이 다른 함수"에 대해서도 똑같이 이름이 중복될 수 있지않을까 라고
생각할 수도 있을 것이다. 즉 다음과 같은 함수들은 서로 구분이 가능
하므로서로 다른 함수로 취급될 수 있을 것이다.
int func(int,char *);
char *func(int,char *);
void func(int,char *);
그러나 미안하게도 이 세 개의 함수는 같은 이름을 가질 수 없다. 컴
파일러는 리턴값의데이터 타입에 의해 함수를 구분하지 않으며 구분하
기가 불가능하다. 왜냐하면 리턴값에 관한 처리는 함수 호출 과정에서
이루어지는 것이 아니라 함수가 호출을 마치고 돌아왔을 때 이루어지
기 때문에 호출되는 시점에서는 함수의 리턴값을 알 수 없기 때문이
다.
int i=func(3,"boy");
outtextxy(100,100,func(10,"girl"));
이런 경우라면 혹시 구분이 가능할지도 모르지만 함수가 단독으로 쓰
이는 다음과 같은경우에는 리턴값이 어떤 타입을가지는지 애매해지게
되고 어떤 함수가 사용되어야 할지 컴파일러가 결정하지 못하게 된다.
cout << func(5,"bed scene");
func(8,"kiss scene");
요약하자면 같은 이름의 함수가 여러 개 존재하기 위해서는 인수의 수
가 다르거나 데이터 타입이 달라야 한다. 다음 예제는 함수의 다형성
이 어떤 편리함을 주는지를 보여준다.
#include <iostream.h>
#include
void outtime(time atime) // 시간을 출력해주는 함수 1
{
cout << "Now time is ";
cout << (int)atime.ti_hour << ':'
<< (int)atime.ti_min << ':'
<< (int)atime.ti_sec << endl;
}
void outtime(int h,int m,int s) // 시간을 출력해주는 함수 2
{
cout << "Now time is ";
cout << h << ':' << m << ':' << s << endl;
}
void main()
{
time nowtime;
gettime(&nowtime);
outtime(nowtime);
outtime(nowtime.ti_hour,nowtime.ti_min,nowtime.ti_sec);
}
똑같이 시간을 나타내는 함수가 두 개 있는데 취하는 인수가 서로 다
르다. 하나는 시간을담고 있는 구조체 하나를 원하고 또 다른 함수는
시, 분, 초의 시간을 이루는 개별적 요소를 원한다.
이 외에도 C에서 시간을 나타내는 형태가 여러 가지가 있는데 outtime
함수를 그 각각의자료형에 대해 정의해 두면 어떠한 형태로 저장되어
있든지 시간을 출력할 수 있게 된다.
위 예제의 출력 결과는 다음과 같다.
Now time is 11:36:55
Now time is 11:36:55
다형성은 C++ 문법의 특성을 결정하는 중요한 속성이므로 잘 이해해
두도록 하자.
다. 디폴트 파라미터
디폴트란 다 알다시피 내정치, 또는 기정치라는 뜻으로 아무것도 지정
하지 않을 경우에취하는 값을 말한다. 예를 들어 DOS의 가장 간단한
명령인 dir 명령 뒤에는 "현재 디렉토리의 모든 파일" 이라는 디폴트
가 적용이 되고 있다. 디폴트란 어디까지나 따로 지정하지않을 경우의
값이므로 따로 값을 지정하면 디폴트 대신 지정한 값이 쓰여지게 된
다.
dir a:*.txt란 명령은 "a 드라이브의, 확장자가 txt인 모든 파일"을
지정해주었기 때문에 디폴트가 이때는 무시된다. C에서 함수를 호출할
때는 함수 호출시에 넘겨주기로 한 인수의 개수에 맞게 인수를 넘겨주
어야 하며 인수가 모자라거나 남아서는 안된다. 그러나C++에서는 인수
에도 디폴트값을 지정하여 생략시의 값을 미리 정해줄 수 있도록 되어
있다. 다음 예제를 보자.
#include <stdio.h>
#include
void outchar(int x,int y,char c='S',int num=3);
main()
{
clrscr();
outchar(10,10,'D',5);
outchar(20,20,'T');
outchar(10,20);
}
void outchar(int x,int y,char c,int num)
{
int i;
for (i=0;i__EXPRESSION__ systax 에러를 발생시킨다.
라. inline 함수
inline이란 말은 그때 그때 즉시 처리한다는 뜻인데 함수 호출 부분을
아예 함수 그 자체의 코드로 대체시켜 버리는 것을 말한다. 일반적인
함수는 함수가 호출되면 함수가 정의되어 있는 부분으로 가서 함수 부
분을 실행하고 다시 돌아오는 방식으로 되어 있다.
따라서 함수 호출이 몇번이건간에 프로그램에서 함수가 정의되어 있는
부분은 단 한곳밖에 없으며 호출시마다 호출원에서 함수로 제어권을
넘겨주게 된다. 반면 inline 함수는 함수가 호출되는 부분에 아예 함
수 코드 자체를 집어넣어 버린다. 따라서 함수를호출한 횟수 만큼 함
수 정의 부분이 반복되어 나타나게 되며 제어권이 넘겨지는 일은발생
하지 않는다.
이런 inline 함수를 사용하는 이유는 제어권의 이동이 발생하지 않으
므로 속도가 더 빠르기 때문이다. 귀찮게 인수를 주고 리턴값을 받고
하는 일이 발생하지 않는다.
하지만 다 좋으란 법은 없는 만큼 inline 함수는 실행 파일의 크기를
크게 만드는 주범이되기도 한다. 프로그래밍에서 size와 speed는 이런
식으로 거의 항상 대립적인 관계에 있으며 그래서 필요에 따라 골라서
선택할 수 있도록 되어 있다. 속도에 더 가치를 둔다면inline 함수를
사용해야 할 것이고 size가 되도록이면 작기를 원한다면 inline 함수
의 사용을 삼가해야 한다. 다음 inline 함수의 예제를 보자.
#include <iostream.h>
inline int add(int a,int b)
{
return a+b;
}
void main()
{
cout << add(3,7) << endl;
cout << add(9,8) << endl;
}
위 예제에서 두 정수 값을 더하는 add 함수가 inline 함수로 정의되어
있는데 자신이 호출( =실행)되기 전에 먼저 정의되어야 하므로 프로그
램 선두에 나타나게 된다. 기본 형식은일반 함수와 같되 다만 앞에
inline이라는 키워드만 추가시켜 주면 된다.
inline 함수는 그 자체가 선언이므로 프로그램 선두에 위치하기만 한
다면 별도의 원형 선언은 필요가 없다. inline 함수는 그 속성을 잘
알고 써야 그 진가를 제대로 발휘할 수 있다.
이미 언급했듯이 속도는 좋은 반면 실행 파일 크기에 불리하므로 구조
가 극히 간단한 함수만 inline 함수로 사용하는 것이 좋다. inline 함
수는 여러 가지 면에서 매크로 함수와 비슷한 성질을 가지고 있으며
실제로 조금 형태가 복잡한 매크로 함수이다.
뒤에 배우겠지만 class 내부에서 정의된 멤버 함수는 자동으로 inline
함수가 되므로 일부러 inline 함수를 만들어 쓰지 않더라도 inline 함
수의 특성에 대해서는 알고 있어야 한다.
마. 주석
C에서는 주석문이 로 끝이 나도록 되어 있다. 시작과
끝을 따로 분리하여 표시하므로 여러 행에 걸쳐서 주석을 기입할 수
있어 편리한 면도 있지만 짧은 주석이행 단위로 삽입될 때는 일일이
처음과 끝을 로 싸 주어야 하기 때문에 불편하였다.
그래서 C++에서는 한 행만 주석으로 처리할 수 있는 //를 제공한다.
컴파일러가 소스 파일을 읽던 중에 //를 만나게 되면 그 행의 끝까지
를 주석으로 처리하게 된다.
주석의 시작 부분에만 //을 기입해주면 되므로 주석을 넣기가 한결 더
편리해졌다. 그러나 //를 주석 기호로 한다고 해서 C++에서 를
주석으로 사용하지 못하는 것은 아니며경우에 따라 적절한 주석 기호
를 사용하면 된다. 주로 길고 양이 많은 주석을 표시할 때는 를
사용하는 것이 유리하고 간단하고 짧은 주석은 //로 표시하는 것이 좋
다. 주석문은 이 책의 예제 곳곳에서 볼 수 있으므로 따로 예제를 싣
지는 않는다.
바. 예약어
C++의 기능이 확장됨으로써 새로운 예약어(keyword)가 생겨나게 되었
다. 다음에 보인C++에서 추가된 예약어는 함수명이나 변수명 등의 사
용자가 정의해서 사용하는 명칭이될 수 없다. 물론 C 문법을 따른다면
사용할 수 있지만 별로 바람직한 일은 아니다.
class 관련
class, friend,virtual, this
access 지정자
private, protected, public
연산자
operator, new, delete
예외 처리
try, catch, throw
기타
template
이 중에서 예외 처리에 사용되는 키워드와 템플릿은 아직까지 그 기능
이 시험적인 단계이므로 별로 사용할 기회가 없을 것이다. C++을 공부
하다 보면 여기서 보인 예약어들이 for나 return, include, define 등
과 같이 이때까지 자주 써오던 예약어처럼 친숙해질것이다.
사. 구조체 태그가 데이터 타입이 된다.
C에서는 구조체 태그를 사용하여 구조체의 형틀을 먼저 기억시킨 후
태그를 사용해 구조체 변수를 선언할 때 다음과 같이 한다.
struct strutag {
int a;
char b;
double c;
};
struct strutag strvar;
먼저 구조체의 모양과 형태를 strutag란 이름으로 기억시킨 후 struct
strutag형의 변수를선언한다. 이때 구조체 변수 선언시 struct 키워드
와 태그가 동시에 있어야 하는데 사실strutag가 구조체의 태그임이 구
별이 되는 이상 struct 키워드가 불필요하다. 그래서 C++에서는 이 불
필요한 struct 키워드를 빼 버리고 태그 이름만 가지고 구조체 변수를
선언할수 있도록 하였다.
struct strutag {
int a;
char b;
double c;
};
strutag strvar;
이 문제는 단순히 struct라는 단어 하나가 있고 없고가 중요한 것이
아니라 C++에서는 구조체 자체가 하나의 데이터 타입으로 간주된다는
것이다. 이것은 비단 구조체에만 국한되는 사항이 아니며 태그를 가지
는 공용체(union)와 열거형(enum) 및 C++에서 확장된 구조체인 클래스
(class)에 모두 적용되는 사항이다.
C는 다른 언어와는 달리 진위형(boolean)의 데이터 타입을 별도로 지
원하지 않는다. 그래서 진위형이 꼭 필요한 경우에는 대체로 열거형을
사용하여 진위형의 데이터 타입을 만들어서 사용한다. C++에서는 열거
형의 태그도 독립적인 데이터 타입으로 인정되므로 다음과 같이 간단
하게 진위형을 만들 수 있다.
enum boolean {FALSE, TRUE };
boolean isdie;
if (isdie==TRUE)
cout << " 잘 죽었다" << endl;
실제로 windows 프로그래밍에서는 windows.h에 이런 진위형을 선언해
두고 사용한다.
번 호 : 2635
게시자 : 김상형 (mituri )
등록일 : 1998-05-24 04:17
제 목 : [C++] OOP 기초 강좌 #3
아. 이름없는 공용체
한 회사에서 고객 관리 프로그램을 짠다고 하자. 고객의 신상을 기억
시키기 위해 구조체를 정의하여 사용하기로 했는데 고객의 이름을 아
는 경우가 있고 접수 번호를 알고 있는경우가 있어서 고객의 이름과
접수 번호 두 가지 중 알고 있는 한 가지를 사용하기로 했다. 이런 경
우는 구조체 속에 공용체가 다음과 같이 삽입되어야 한다.
struct ctag {
union {
char name[10];
int num;
} ID;
int age;
int duration;
}custom[100];
공용체 ID가 구조체 custom 내부에 위치해 있는 모양이다.
┌─────────────────────────────┐
│ num │ name │
├──────────┼──────────────────┘
│ age │
├──────────┤
│ duration │
└──────────┘
이때 구조체 내의 공용체 내의 멤버를 참조하고자 할 때는 다음과 같
이 dot 연산자를 이중으로 사용한다.
custom[10].ID.name
custom[10].ID.num
이 방법이 C에서 전통적으로 사용하는 방법이다. 그런데 위의 경우 공
용체 ID가 굳이 이름을 가질 필요가 없으며 name이나 num이 참조될 경
우는 당연히 먼저 ID가 참조되어야한다. 그래서 C++에서는 구조체 안
의 공용체는 이름을 주지 않고도 곧바로 사용할 수 있도록 되어 있다.
다음과 같이.
struct ctag {
union {
char name[10];
int num;
};
int age;
int duration;
}custom[100];
이렇게 하더라도 name이나 num이 하나의 공용체 소속임은 분명하게 구
분되므로 아무런무리가 없다. 이렇게 선언된 공용체를 이름없는 공용
체(anonymous union)라 한다. 이제공용체의 이름을 경유하지 않고 구
조체와 공용체의 멤버만 가지고 곧바로 참조가 가능해진다.
custom[10].name
custom.num
등과 같이 말이다. 다음은 좀더 간단한 형태로 이름없는 공용체를 구
현해본 것이다.
#include <iostream.h>
void main()
{
union {
int i;
double f;
};
i=3;
cout << "integer is " << i << endl;
f=3.14;
cout << "real number is "<< f << endl;
}
선언된 공용체의 이름이 주어져 있지 않을 뿐만 아니라 공용체 내의
멤버를 참조할 때 소속 공용체를 명시하지 않고 곧바로 멤버의 이름으
로 참조를 하였다. i와 f는 물론 같은 기억장소를 공유하고 있다.
자. 명시적 캐스트 연산자
C는 다른 종류의 데이터 타입끼리 혼용할 수 있는 융통성을 제공하며
주로 데이터 타입끼리의 형 변환을 통하여 같은 타입으로 맞추어 사용
하도록 한다. 여기에 대해서는 5장11절에 자세히 언급되어 있다. 그런
데 데이터 타입끼리 자동 변환이 이루어질 수 없는 상황이거나 아니면
강제적으로 데이터 타입을 변환시켜야 할 필요가 있을 때는 다음과 같
은 형식으로 캐스트 연산자를 사용하였다.
int i;
float f;
f=(float)i;
이때캐스트 연산자는 연산자 함수의 형태로 실행되므로 다음과 같이
함수 형태로도 캐스트 연산자를 사용할 수 있다. 이런 캐스트 연산자
의 사용 방법은 C++에서 확장된 내용이며 기존의 C에서는 이 방법을
쓸 수 없다.
int i;
float f;
f=float(i);
캐스트 연산자를 종전대로 연산자의 형태로 사용하는 것은 암시적 캐
스트 연산자라 하며 함수의 형태로 사용하는 것을 명시적 캐스트 연산
자라고 한다. 다음 예제에서 두 캐스트 연산자의 사용 방법을 보인다.
두 방법 중 어느 방법을 사용하거나 결과는 동일하다.
#include<iostream.h>
void main()
{
int i=3;
double d;
d=(double)i; // 암시적 캐스트 연산자.
d=double(i); // 명시적 캐스트 연산자.
cout << d << endl;
}
**********************************************
**********************************************
1-4 첨가된 기능
가. 참조호출
참조자는 다른 변수에 대한 또 다른 이름이며 별칭(alias)이다. 똑같
은 메모리 번지를 두개의 변수가 동시에 가지게 된다. 만약 참조자가
가리키는 변수의 값이 변하면 참조자도변하게 되고 반대의 경우도 성
립한다. 참조자의 선언은 다음과 같이 한다.
type &참조자=초기값;
여기서 사용된 &를 참조 선언자(reference declarator)라고 하며 변수
의 번지를 구하는 &연산자나, 비트 AND 연산을 하는 & 연산자와는 다
른 것이다. 다음 예제를 보고 참조자의기본 문법을 익히도록 하자.
#include <iostream.h>
#include
void main()
{
int i;
int &j=i; // 참조자의 선언
i=3;
cout << "i is " << i << ", j is " << j << endl;
j=5;
cout << "i is " << i << ", j is " << j << endl;
printf("%p\\n",&i); // 주소를 출력
printf("%p\\n",&j);
}
j를 i의 참조자로 선언하고 있다. 출력 결과는 다음과 같다.
i is 3, j is 3
i is 5, j is 5
8FA9:0FFE
8FA9:0FFE
i값이 변하면 j값도 변하고 j값이 변하면 i값도 변한다. 이름이 다를
뿐이지 같은 대상을가리키고 있으므로 어떤 한 변수에 조작을 가하면
나머지 한 변수도 영향을 받게 된다.
그리고 두 변수가 같은 메모리를 가지고 있는 같은 변수임을 보이기
위해 두 변수의 메모리 번지를 조사해보았다. 역시 같은 메모리 번지
를 가진다.
참조자는 생성이 됨과 더불어 어떠한 변수의 별명으로 사용되므로 생
성시에 반드시 초기화되어야 한다. 일반 변수처럼 선언과 정의를 분리
하여 실행할 수가 없다. 만약 위의예제에서 int &j;라고 먼저 선언을
한 후 j=i라고 한다면 j가 생성될 때 가리키는 대상이없으므로 다음과
같은 에러 메시지가 출력된다. int &j;라는 문장 자체가 성립되지 않
는다.
reference value 'j' must be initialized
참조자를 초기화시켜 주는 것이 곧 참조자를 정의하는 것이다. 참조자
는 초기화식과 대입식의 의미가 일반 변수와는 달리 아주 특별나다.
초기화 : 어떤 변수를 가리키게 될지를 지정한다.
대입식 : 참조자가 가리키는 변수의 값을 변경시킨다.
다음 구문을 보자.
int i=4;
int k=5;
int &j=i; // 초기화
j=k; // 대입
참조자 j를 선언하면서 i의 번지를 가리키도록 초기화하였으며 이후부
터 j와 i는 같은 변수이다. 프로그램 중간에 j를 i가 아닌 k를 가리키
도록 바꾸고 싶다고 하여 j=k와 같이 쓸수는 없다. 왜냐하면 참조자는
한번 초기화되면 다른 대상을 가리킬 수 없기 때문이다.
j=k라는 대입식은 j가 가리키는 대상을 k가 가진 값으로 바꾸는 대입
식이다. 즉 j=k에 의해 j는 여전히 i를 가리키고 있으며 i값이 k가 가
진 5로 변경된다. 참조자는 선언과 동시에반드시 초기화되어야 하지만
언제나 그렇듯이 예외는 있다.
* class의 멤버로서 선언될 때는 선언시에 초기화되지 않고 생성자에
서 초기화된다.
* 함수의 인수로 사용될 때는 함수의 호출 시점에서 초기화된다.
* 함수의 리턴값으로 사용될 때는 리턴될 때 초기화된다.
아직까지 class에 관해서는 모를 것이므로 지금은 그냥 보아두고 지나
가도 된다. 다음에다시 보면 알겠지만 전혀 상식의 범위를 벗어나지
않는 평범한 예외 사항이며 예외의 경우에도 초기화 시점이 표현상 다
를 뿐 사실 거의 선언과 동시에 초기화된다고 보아도 무방하다.
참조자를 선언할 때 또 한 가지 주목할 것은 참조자의 형과 초기화 변
수의 형이 일치해야한다는 것이다. 정수형의 참조자를 실수형의 변수
로 초기화하는 것은 올바른 동작을 하지 않게 된다. 그렇다고 에러가
발생하지는 않으며 참조자는 타입이 다른 별도의 변수가된다. 이런 실
수는 잘 하지 않겠지만 어쨌든 어떤 현상이 일어나는지는 보고 넘어가
도록하자.
#include <iostream.h>
void main()
{
int i=3;
unsigned &j=i;
cout << " i= " << i << " j= " << j << endl;
j=4;
cout << " i= " << i << " j= " << j << endl;
}
부호없는 정수형의 참조자 j의 초기값으로 정수형 변수 i를 주었다.
두 변수의 타입이 맞지 않으므로 j는 i의 별칭으로 제대로 동작할 수
없게 되며 컴파일러는 j를 별도의 변수로만들어 버린다. i와 j는 같은
메모리 공간을 가지지도 않으며 논리적으로 아무런 연관이없다. 그래
서 한 변수에 조작을 가한다하여 다른 변수가 변하는 일은 발생하지
않는다.
다음이 실행 결과이다.
i= 3 j= 3
i= 3 j= 4
참조자를 위에서 보였듯이 다른 변수의 별칭으로 사용하는 경우는 별
특별한 의미가 없으며 참조자가 제몫을 하는 좋은 예는 함수의 인수로
사용되거나 리턴값으로 사용될 때이다.
C에서는 인수 전달 방식에 따라 함수의 호출 방법이 두 가지가 있다.
인수의 값을 전달하는 방식인 값호출 방식과 번지를 전달하는 참조호
출 방식이 있다. 값호출 방법은 실인수의 복사본을 넘겨주고 함수 내
부에서는 이 복사본을 형식 인수로 받아 사용하기 때문에함수 내부에
서 절대로 실인수의 값을 변경시킬 수 없다. 반면 참조호출은 실인수
자체를넘겨주기 때문에 함수 내부에서 실인수를 직접 건드릴 수 있다.
C는 포인터를 사용해서 간접적으로 참조호출을 지원한다. 그러나 C의
참조호출 방식은결과는 맞지만 번지 그 자체도 값(번지값)의 형태로
넘겨주므로 완전한 참조호출이라고보기가 조금 애매하다. C++에서는
참조호출 방식을 원칙적으로 제공한다. 다음 예제를보면 그 방식을 대
충이나마 짐작할 수 있을 것이다.
#include <stdio.h>
void dubae(int &i); // 함수의 인수에 &가 쓰였다.
main()
{
int j;
j=2;
dubae(j);
printf("%d\\n",j);
}
void dubae(int &i) // 참조자를 인수로 받아들인다.
{
i*=2; // 참조자(=실인수)를 직접 변경한다.
}
위에서 정의된 함수 dubae가 참조호출 방식의 함수이다. 참조 방식으
로 전달하고자 하는인수 앞에 참조 선언자인 & 기호를 붙여주면 된다.
그리고 호출할 때는 변수를 그대로 넘겨주기만 하면 그 변수와 완전히
똑같은 변수를 만들어 함수 내부에서 직접 변수를 변경시키게 된다.
함수 호출시의 dubae(j)의 실인수 j와 dubae 함수 정의부의 형식 인수
i는 이때 같은 메모리 위치를 가지는 완전히 같은 변수이다. i에 대해
어떤 조작을 가하면j에 그효과가 직접적으로 나타나게 된다.
C에서 포인터를 통해 참조호출을 구현하는 방법과는 근본적으로 다른
방법이다. 포인터를 이용한 방법은 번지값이 포인터로 넘겨져 포인터
에 의해 실인수값이 변경되지만 C++에서 도입된 참조호출은 포인터라
는 중간 과정을 거치지 않고 곧바로 변수를 조작한다.
참조자를 사용한 참조호출은 포인터라는 새로운 변수를 만드는 것이
아니므로 아무래도속도가 더 빠를 수밖에 없다. 만약 C에서 참조호출
을 구현하려면 다음과 같이 포인터를사용할 것이다.
void dubae(int *i);
dubae(&j);
포인터를 사용하는 방법은 중간 과정을 거치게 되며 포인터 변수가 실
제로 생성되지만참조자는 이미 있는 변수와 같은 메모리를 가리키는
같은 변수이다. 새로운 변수가 생성되는 것이 아니다.
┌─────┐
│ j ├───┐
└─────┘ │
│
┌──────┐ ┌───────┐
│ i │ │ i,j │
└──────┘ └───────┘
포인터를 이용한 참조호출 참조형을 이용한 참조호출
참조자는 함수의 인수로 사용될 뿐만 아니라 함수의 리턴값으로도 사
용된다. 참조자가리턴될 때 호출원으로 리턴되어 오는 것은 값이 아니
라 메모리 번지를 가지고 있는 실체(=변수 또는 객체)이다. 참조자는
최초 초기화될 때부터 다른 변수의 번지를 공유하면서만들어지므로 메
모리 번지를 가지고 있으며 따라서 무조건 좌변값(lvalue)이며 대입식
의좌변에 놓을 수 있다.
참조자를 리턴하는 함수 또한 마찬가지로 함수 호출 구문 자체가 좌변
값이 되며 함수가대입식의 좌변값에 놓이는 것도 가능하다. C에서는
절대로 함수가 대입식의 좌변에 놓이는 일이 없으므로 처음 보는 사람
들에게는 무척이나 생소한 문법일 것이다. 다음 예제에참조자를 리턴
하는 함수를 보인다.
#include
int ar[5]={1,2,3,4,5};
int &getar(int n)
{
return ar[n];
}
void main()
{
getar(3)=100; // 함수가 식의 좌변에 놓여 있다.
for (int i=0;i<5;i++)
cout << i << " : " << ar[i] << endl;
}
전역 배열 ar이 선언되어 있고 첨자 n을 인수로 주면 ar[n]을 리턴하
는 함수 getar이 선언되어 있다. main 함수의 첫 행에서 ar[3]에 100
을 대입하기 위해 getar(3)=100; 이라는대입식을 사용하였는데 뭔가
불안해보이고 어색해보이지만 전혀 문제가 없는 정상적인 대입식이다.
이 대입식에 의해 ar[3]은 100으로 바뀌게 되며 ar 배열은 다음과 같
이변하게 된다.
0 : 1
1 : 2
2 : 3
3 : 100
4 : 5
getar(3)이 리턴하는 것은 ar[3]의 값도 아니고, 그렇다고 ar[3]의 번
지도 아니며 ar[3] 변수그 자체를 리턴한다. 리턴되는 참조자는 ar[3]
의 별칭이며 참조자에 어떤 조작을 가하는것은 곧 ar[3]에 어떤 조작
을 가하는 것과 동일한 효과를 가져오게 된다.
┌────┬────┬────┬────┬────┐
│ ar[0] │ ar[1] │ ar[2] │ ar[3] │ ar[4] │
└────┴────┴────┴─┬──┴────┘
│
getar(3):변수 자체를 ─────┘
리턴해 준다.
단 참조자는 지역 변수를 리턴할 수는 없다.
int & func(void)
{
int i=3;
return i; // 여기서 에러 발생
}
지역 변수를 리턴할 수 없는 이유는 참조자가 변수를 리턴하는 시점에
서 함수가 종료되고 함수 내부에서 선언된 지역 변수의 생명이 끝나기
때문에 참조자가 리턴하는 값이 호출원에서는 아무런 의미가 없기 때
문이다.
아직도 참조자가 잘 이해가 안 가는 사람은 머리 속으로 상상력을 총
동원하여 컴파일러의 동작과 예제의 동작을 총체적으로 분석해보는 수
밖에 없다. 나중에 class를 공부할 때참조자는 밥먹듯이 자주 나오므
로 확실하게 알아두는 것이 좋다. C를 처음 공부하는 사람이 제일 애
를 많이 먹는 부분이 포인터이다. C++을 처음 공부하는 사람이제일
먼저 통과해야 할 관문은 참조자이다. 좀더 장기간의 시간을 투자하여
공부할 필요가 있는 부분이므로 너무 조급한 마음을 가질 필요는 없
다. 무조건 외워 버리고 나면 어느날 프로그래밍하던 도중에 완전히
이해하게 되는 날이 있을 것이다.
나. scope 연산자
C에서는 scope rule이라는 것이 있어 변수의 통용되는 범위에 관한 규
정을 하고 있다(6장참조). 원칙적으로 같은 통용 범위에 같은 이름의
변수가 두 개 같이 존재할 수 없지만 범위가 다른 경우에는 범위가 좁
은 변수에 우선 순위를 준다. 즉 지역 변수와 전역 변수가이름이 같을
때 함수 내부에서는 전역 변수를 잠시 인식하지 못하며 지역 변수만을
인식한다. C에서 이런 문제를 해결하기 위해서는 전역 변수든 지역 변
수든 둘 중 한 변수의 이름을 다른 이름으로 바꾸어 주어야만 한다.
C++에서는 여기에 새로운 연산자를 첨가하여 함수 내부에서 전역 변수
를 참조할 수 있도록 하고 있는데 이 연산자가 scope 연산자 (범위 연
산자)이며 colon을 두 개 연속적으로붙여서 표기한다(:: 요렇게) 일단
예제를 통해 scope 연산자의 활약을 보자.
#include <iostream.h>
void func(void);
int i;
void main()
{
i=3;
func();
cout << "here main i= " << i << endl; // 전역 변수 i 참조}
void func(void)
{
int i;
i=4;
cout << "func's i=" << i << endl; // 지역 변수 i 참조cout
<< "global i=" << ::i << endl; // 전역 변수 i 참조}
전역 변수로 i가 선언되어 있고 func 함수 내부에서 또 지역 변수로 i
가 선언되어 있다.
func 함수 밖에서 i를 참조하면 전역 변수 i가 참조되고 func 함수 내
부에서는 지역 변수 i가 참조된다.
func 함수 내부에서 전역 변수 i를 참조하고자 할 때 scope연산자를
변수 앞에 붙여주면된다. func 함수 내부의 두 번째 printf문에서 사
용한 ::i는 전역 변수 i를 의미하며 첫번째printf문에서 사용된 i는
지역 변수 i를 의미한다. 또한 이 scope 연산자는 class와 변수 사이
에 위치해 변수 또는 함수가 어느 class 소속인지를 밝혀주는 용도로
도 사용된다.
번 호 : 2636
게시자 : 김상형 (mituri )
등록일 : 1998-05-24 04:18
제 목 : [C++] OOP 기초 강좌 #4
다. new, delete
기존의 C 문법에서는 동적 메모리의 할당과 해제에 malloc, free 등의
함수를 사용하였다.
이러한 함수는 구조적 프로그래밍 방식에는 적합하지만 OOP적인 방식
에서는 다소 한계가 있기 때문에 C가 C++로 확장되면서 새로운 형태의
동적 메모리 관리 방법이 제기되었다.
C++에서는 동적 메모리 관리에 함수를 사용하지 않고 연산자의 형태로
제공하는데 이연산자가 바로 new, delete 연산자이다. 이 연산자들은
malloc, free 등의 함수들보다 진보된 방식으로 동적 메모리를 관리한
다. 이 연산자들이 가지는 여러 가지 장점들을 살펴보면 OOP적인 프로
그램 방식에 적합한 기능을 가지고 있음을 알 수 있다. 일단 이 연산
자들을 사용하여 동적 메모리를 관리하게 되면 사용하기가 간편할 뿐
만 아니라 소스를 읽기가 더 쉬워진다. 또한 내부적으로 처리되는 속
도도 malloc 등의 함수들보다 더 빠르다.
그리고 함수가 아닌 연산자이기 때문에 별도의 헤더 파일을 포함시킬
필요가 없으며 연산자 오버로딩을 통해 자신이 원하는 형태로 변경시
키는 것도 가능하다. new 연산자를사용하여 동적 메모리를 할당하는
기본 문법을 보자.
new 할당 대상;
아주 간단하다. 할당 대상에 들어갈 수 있는 것은 int, char, double
등의 데이터 타입은 물론이고 구조체, 배열 등도 가능하다. new 연산
자는 sizeof(할당 대상) 만큼의 메모리를heap으로부터 할당하여 그 번
지값을 리턴한다. 이때 리턴되는 번지값은 할당 대상을point하는 포인
터형이기 때문에 곧바로 포인터 변수에 대입이 가능하다. malloc의 경
우에는 무조건 void *형을 리턴하기 때문에 별도의 캐스트 연산자를
사용해야 한다.
다음이 new 연산자를 이용한 동적 메모리 할당의 예이다.
int *pi=newint;
double *pd=new double;
long *pl=new long;
delete 연산자는 new 연산자와 하는 일이 반대인 연산자이다. new 연
산자가 할당한 동적메모리를 할당 해제시키는 역할을 하는데 delete
뒤에 할당 해제의 대상이 될 포인터를 기입해주면 된다. 이 포인터는
대개 new 연산자가 리턴한 번지값을 대입받은 포인터이다.
위에서 할당의 예로 보였던 동적 메모리를 delete 연산자를 사용하여
할당을 해제하는 구문은 다음과 같이 간단하다.
delete pi;
delete pd;
delete pl;
new 연산자에 의해 할당된 메모리는 delete 연산자가 호출되어 할당을
능동적으로 해제하기 전에는 그대로 할당된 채로 남게 된다. 따라서
설사 함수 내부에서 동적 메모리를할당하여 변수를 생성하였다고 하더
라도 그 변수는 함수를 벗어나도 그대로 존재하게된다. 즉 scope rule
의 영향을 받지 않는 변수가 된다.
만약 new 연산자로 할당한 메모리를 할당 해제를 하지 않고 프로그램
을 끝내게 되면 프로그램이 끝나는 시점에서 미 회수된 메모리가 자동
으로 할당이 해제된다. 하지만 프로그램이 실행되는 동안에는 계속 할
당된 채로 남아 있으므로 할당한 메모리는 사용이 끝난 후 반드시 할
당 해제해주는 것이 바람직하다. 다음에 new, delete 연산자를 사용한
극단적으로 간단한 동적 메모리의 할당 예를 보자.
#include <iostream.h>
void main()
{
int *pi=new int; // 여기서 동적 메모리를 할당
*pi=3;
cout << *pi <<endl;
delete pi; // 할당을 해제한다.
}
정수형 변수 하나를 담을 수 있는 2바이트의 메모리를 할당하여 이 번
지값을 정수형 포인터인 pi에 대입하였다. 이렇게 생성된 정수형 변수
를 프로그램의 내부에서 사용하다가프로그램이 종료되기 직전에
delete 연산자를 통해 할당을 해제하였다. 사실 이런 식으로기본 데이
터 타입에 대해 new 연산자를 사용하는 일은 드문 일이며 new 연산자
가 제대로쓰이는 때는 구조체나 배열, 또는 class의 객체를 생성할 때
이다.
new 연산자 뒤에 구조체의 태그를 할당 대상으로 기입해주면 구조체가
차지하는 메모리의 크기 만큼 메모리를 할당해주게 된다. 다음에 구조
체를 할당하는 new 연산자를보자.
#include <iostream.h>
#include
struct person // 구조체 person을 선언
{
char name[15];
int age;
double hakjum;
};
void main()
{
person *pp=new person; // 구조체를 위한 메모리 할당
strcpy(pp->name,"kim nyun hang");
pp->age=21;
pp->hakjum=3.99;
cout << pp->name << "'s hakjum is "
<< pp->hakjum << endl;
delete pp; // 할당 해제
person *pa=new person; // 또 새로운 메모리를 할당
strcpy(pa->name,"kim hyun a");
pa->age=19;
pa->hakjum=3.28;
cout << pa->name << "'s hakjum is "
<< pa->hakjum << endl;
delete pa; // 할당 해제
}
실제로 할당되는 메모리는 sizeof(person)의 크기가 되며 할당 직후에
person형의 포인터를 리턴하므로 이 포인터값을 person *형 변수에 곧
바로 대입하여 두었다가 '->' 연산자와함께 사용하면 person형의 구조
체를 곧바로 사용할 수 있다.
여기서 보다시피 new 연산자는 메모리 할당 뿐만 아니라 마치 변수를
생성해내는 역할을하는 것처럼 보인다. new 연산자 뒤에 생성하고자
하는 변수의 타입만 적어주고 new 연산자가 리턴하는 포인터를 받아두
면 포인터를 사용하여 생성한 변수를 일반 정적 변수와 마찬가지로 사
용할 수 있다. 더구나 class의 객체를 생성할 때는 객체를 초기화하는
함수까지도 호출해주므로 과연 OOP에 적합한 메모리 관리 연산자이다.
다음은 new 연산자를 사용하여 배열을 생성해보도록 하자.
#include
void main()
{
int *pi=new int[5]; // 배열을 생성
int i;
for (i=0;i<5;i++)
pi[i]=i;
for (i=0;i<5;i++)
cout << "p[" << i << "] = " << pi[i] << endl;
delete pi;
}
new 연산자 뒤에 배열을 그대로 기입만 해주면 된다. 이때 할당되는
실제 메모리는sizeof(int[5]) 즉, 10 바이트이다. new 연산자의 에러
처리는 malloc 함수와 동일한 방법을사용하는데 할당할 메모리가 부족
하면 NULL을 리턴한다. 그래서 malloc 함수와 마찬가지로 메모리 할당
후 포인터값을 검사해보면 제대로 할당되었는지를 알 수 있다. 다음
예제는 일부러 많은 양의 메모리를 요구하여 에러를 유발시켜 어떻게
에러를 처리하는가를 보여준다.
#include <iostream.h>
#include
void main()
{
char *pc=new char[65535u];
if (pc==NULL) // 에러 검사
{
cout << "\\a\\n No more free heap memory !\\n";
exit(1);
}
// else some operation here
delete pc;
}
그러나 이런 에러 처리 방법은 실제 프로그래밍에서는 잘 사용되지 않
으며 에러 handler함수를 사용하는 방법을 사용하거나 아니면 new 연
산자의 기능을 사용자가 변경하여 에러 처리를 하도록 한다. new 연산
자는 메모리 할당에 실패할 경우 _new_handler라는 함수를 호출하도록
되어 있다. 이 함수는 단순히 프로그램을 끝내기만 하도록 되어 있는
데 사용자가 set_new_handler 함수를 사용하여 얼마든지 수정 가능하
다. 다음에 _new_handler함수의 기능을 바꾸어 본 예제를 보인다.
#include <iostream.h>
#include
#include
void new_error() // 이 함수가 에러 handler 함수이다.
{
cout << "\\a\\n No more free heap memory !\\n";
exit(1);
}
void main()
{
set_new_handler(new_error);
char *pc=new char[65535u]; // 여기서 에러 발생
// some operation here
delete pc;
}
new 연산 실행 도중 메모리 부족으로 에러가 발생하면 _new_handler
함수를 호출하되new 연산 이전에 set_new_handler 함수에 의해
new_error 함수가 에러 handler 함수로 지정이 되었으므로 에러 발생
시는 무조건 new_error 함수가 호출된다. 여기서 정의한new_error 함
수에서는 단순히 에러가 났다는 사실을 알리기만 하고 프로그램을 종
료하고있으나 좀 더 섬세한 처리를 하는 것도 가능하다.
라. linkage 지정
C 소스 파일로부터 실행 파일을 만드는 과정은 소스 파일을 오브젝트
파일로 만드는 컴파일 과정과 오브젝트 파일을 실행 파일로 만드는 링
크 과정으로 나누어진다. 컴파일러는 컴파일 과정에서 실행 파일의 모
든 것을 결정할 수 없다. 왜냐하면 C 소스 파일에서 사용되는 함수들
은 사용자가 정의한 함수가 아닌 한은 표준 함수든, 라이브러리에 있
는 함수든 그 실제의 번지를 알 수 없기 때문이다. 컴파일러는 이런
실제 번지값을 알 수 없는함수의 정보를 오브젝트 파일에 기록해두고
나머지는 링커에게 맡긴다.
이때 컴파일러가 링커에게 함수에 관한 정보를 오브젝트 파일에 남기
게 되는데 이 정보를 linkage라고 한다. 링커는 오브젝트 파일에 있는
linkage 정보를 보고 어떤 함수가 결합되어야 할지를 판단하게 된다.
그런데 C와 C++에서 linkage를 작성하는 방법이 각각 다르다. 기존의
C에서는 함수끼리의 구분을 위해서는 함수의 이름만으로도 구분이 가
능했다.
그래서 linkage 정보로 오브젝트 파일에 기입되는 것은 함수의 이름이
었다. 하지만 C++에서는 기능이 확장됨(다형성)으로 해서 함수의 이름
만으로는 실제의 어떤 함수를 지칭하는지 구별할 수 없게 되었다.
C++은 오브젝트 파일에 함수에 관한 정보를 함수의 이름뿐만 아니라
함수가 취하는 인수의 개수와 데이터 타입까지 한꺼번에 집어넣는다.
이렇게 C++이 작성하는 linkage를mangled name이라고 한다. 두 언어가
linkage를 작성하는 방법이 다름에 따라 링크 과정에서 문제가 발생한
다. C 형식으로 짜여진 함수는 linkage가 mangled name으로 기입되지
않음으로 해서 C++ 링커(TLINK.EXE)가 함수를 인식해내지 못하게 된
다.
C 소스 파일이 있다면 C++ 컴파일러로 다시 컴파일하면 되지만 그렇지
못한 라이브러리나 오브젝트 파일은 C++ 컴파일러에 그대로 사용할 수
없다. 그렇다고 해서 C로 짜여진함수를 다시 C++로 짠다는 것은 엄청
난 시간적,
</XMP>