2023. 11. 26. 22:09ㆍ프로그래밍 공부/OOP
11.1 분할 컴파일
C++에는 프로그램을 별도의 파일에 보관된 부분으로 나누어 따로 컴파일한 후
프로그램을 실행할 때(또는 직전) 함께 링크할 수 있는 기능이 있다.
클래스에 대한 정의(및 관련 함수 정의)를 클래스를 사용하는 프로그램과 별도의 파일에 넣을 수 있다.
이렇게 하면 클래스의 라이브러리를 구축하여 많은 프로그램이 동일한 클래스를 사용할 수 있다.
헤더 파일 iostream 및 cstdlib와 같이 미리 정의된 라이브러리를 사용하는 것처럼
클래스를 한 번 컴파일한 다음 여러 다른 프로그램에서 사용할 수 있다.
또한 클래스 자체를 두 개의 파일로 정의하여 클래스가 수행하는 작업의 사양을 클래스가 구현되는 방식과 분리할 수 있다.
클래스 구현만 변경한 경우에는 클래스 구현이 포함된 파일만 다시 컴파일하면 된다.
클래스를 사용하는 프로그램이 포함된 파일을 포함한 다른 파일은 변경하거나 다시 컴파일할 필요가 없다.
이 섹션에서는 클래스를 별도로 컴파일하는 방법에 대해 설명한다.
캡슐화 리뷰
캡슐화의 원리는 당신이 프로그래머가 클래스를 어떻게 사용하는지에 대한 명세와
클래스를 구현되는 세부 사항을 분리해야 한다는 것이다.
분리는 당신이 없이 구현을 변경할 수 있을 정도로 완전해야 한다
클래스를 사용하는 모든 프로그램을 변경해야 한다.
이러한 분리를 보장하는 방법은 세 가지 규칙으로 요약될 수 있다:
1. 모든 멤버 변수를 클래스의 private 멤버로 만든다.
2. 클래스에 대한 각 기본 연산을 클래스의 public 멤버 함수, friend 함수, 정규 함수 또는 오버로딩 연산자로 만든다.
클래스 정의와 함수 및 연산자 선언(원형)을 함께 그룹화한다.
클래스에 대한 인터페이스(interface): 클래스의 정의, 함수 및 연산자 선언(원형), 그에 수반되는 코멘트
클래스 또는 함수 또는 연산자 선언과 함께 주어진 코멘트에서 각 함수 또는 연산자를 사용하는 방법을 완전히 지정하라.
3. 클래스를 사용하는 프로그래머는 기본 연산의 구현을 사용할 수 없도록 한다.
구현(implementation): 함수 정의와 오버로드된 연산자 정의로 구성된다
(이러한 정의에 필요한 도움말 함수 또는 기타 추가 항목과 함께).
인터페이스 파일과 구현 파일
C++에서 이 규칙들을 확실히 지키는 가장 좋은 방법은
인터페이스와 클래스의 구현을 별도의 파일에 넣는 것이다.
인터페이스 파일(interface file): 인터페이스를 포함하는 파일
구현 파일(implementation file): 구현을 포함하는 파일
이 파일들을 어떻게 설정하고, 컴파일하고, 어떻게 사용하는지에 대한
기본적인 방식은 C++ 버전의 모든 파일에서 동일하다.
특히 파일에 들어가는 것의 세부 사항은 모든 시스템에서 동일하다.
오직 달라지는 것은 이 파일들을 컴파일하고 링크하는 데 사용되는 명령어들뿐이다.
이 파일들에 들어가는 것에 대한 자세한 내용은 다음 서브섹션에 나와 있다.
private 멤버는 구현의 일부이다.
전형적인 클래스는 private 멤버 변수를 가지고 있다.
private 멤버 변수(및 private 멤버 함수)는
인터페이스와 클래스 구현을 별도의 파일에 배치하는 우리의 기본 철학에 문제를 제시한다.
클래스 정의의 public 부분은 클래스 인터페이스의 일부이지만, private 부분은 구현의 일부이다.
이것은 C++가 클래스 정의를 두 파일에 걸쳐 분할하는 것을 허용하지 않기 때문에 문제가 된다.
따라서 일종의 타협이 필요하다.
유일하게 합리적인 타협안은 전체 클래스 정의를 인터페이스 파일에 배치하는 것이다.
클래스를 사용하는 프로그래머는 클래스의 private 멤버 중 어떤 것도 사용할 수 없으므로,
사실상 private 멤버는 프로그래머로부터 숨긴다.
헤더 파일 및 구현 파일
디스플레이 11.1에는 DigitalTime이라는 클래스의 인터페이스 파일이 포함되어 있다.
DigitalTime은 9시 30분과 같이 값이 하루의 시간인 클래스이다.
클래스의 public 멤버만 인터페이스의 일부이다.
public 멤버는 인터페이스 파일에 있지만 구현의 일부이다.
public 멤버는 이 public 인터페이스의 일부가 아님을 경고한다.
DigitalTime 클래스를 사용하기 위해 프로그래머가 알아야 할 모든 것은
파일 시작 부분의 주석과 클래스 정의의 공용 섹션에 있는 주석에 설명되어 있다.
인터페이스 파일 맨 위에 있는 주석에서 언급했듯이
이 클래스는 24시간 표기법을 사용하므로
예를 들어 오후 1시 30분이 13시 30분으로 입력 및 출력된다.
DigitalTime 클래스를 효과적으로 사용하기 위해 알아야 할 기타 자세한 내용은
멤버 함수와 함께 제공되는 주석에 포함되어 있다.
헤더 파일(header file)
인터페이스를 dtime.h라는 이름의 파일에 넣었다.
접미사 .h는 이것이 헤더 파일임을 나타낸다.
인터페이스 파일은 항상 헤더 파일이므로 항상 접미사 .h로 끝난다.
DigitalTime 클래스를 사용하는 모든 프로그램은
이 파일의 이름을 지정하는 다음과 같은 포함 지시문을 포함해야 한다:
#include "dtime.h"
include 지시문
include 지시문을 작성할 때
헤더 파일이 사용자에게 제공되는 미리 정의된 헤더 파일인지 아니면
사용자가 작성한 헤더 파일인지를 표시해야 한다.
헤더 파일이 미리 정의되어 있으면,
헤더 파일 이름을 <iostream>과 같이 각괄호로 쓰자.
헤더 파일이 당신이 쓴 것이라면,
헤더 파일 이름을 "dtime.h"와 같이 따옴표로 쓰자.
이 구분은 헤더 파일을 어디서 찾을지 컴파일러에게 알려준다.
헤더 파일 이름이 각괄호로 되어 있으면,
컴파일러는 C++ 구현에서 미리 정의된 헤더 파일이 있는 곳을 찾는다.
헤더 파일 이름이 따옴표로 되어 있으면,
컴파일러는 현재 디렉터리나 프로그래머가 있는 곳을 모두 찾아본다.
정의된 헤더 파일은 시스템에 보관되어 있다.
파일 이름
DigitalTime 클래스를 사용하는 모든 프로그램은 다음을 포함해야 한다.
헤더 파일의 이름을 dtime.h로 하는 명령어는
당신이 프로그램을 컴파일하는 것에는 충분하지만
프로그램을 실행하는 것에는 충분하지 않다.
프로그램을 실행하려면 멤버 함수와 오버로딩된 연산자에 대한 정의를 작성하고 컴파일해야 한다.
구현 파일(implementation file): 멤버 함수와 연산자 정의를 작성한 파일
대부분의 컴파일러가 필요로 하는 것은 아니지만
인터페이스 파일과 구현 파일에 같은 이름을 붙이는 것이 전통적인 방법이다.
그러나 두 파일은 다른 접미사로 끝난다.
우리는 클래스의 인터페이스를 dtime.h라는 파일에,
그리고 클래스의 구현을 dtime.cpp라는 파일에 각각 배치했다.
구현 파일에 사용하는 접미사는 C++ 버전에 따라 다르다.
C++ 프로그램을 포함하는 파일에서 일반적으로 사용하는 것과 같은 접미사를 구현 파일에 사용한다.
디스플레이 11.1 DigitalTime 클래스 인터페이스 파일
//This is the header file dtime.h. This is the interface for the class
//DigitalTime. Values of this type are times of day. The values are
//input and output in 24-hour notation, as in 9:30 for 9:30 AM and
//14:45 for 2:45 PM.
#include <iostream>
using namespace std;
class DigitalTime
{
public:
DigitalTime( int theHour, int theMinute);
DigitalTime( );
//Initializes the time value to 0:00 (which is midnight).
int getHour( ) const;
int getMinute( ) const;
void advance( int minutesAdded);
//Changes the time to minutesAdded minutes later.
void advance( int hoursAdded, int minutesAdded);
//Changes the time to hoursAdded hours plus minutesAdded minutes
//later.
friend bool operator ==( const DigitalTime& time1,
const DigitalTime& time2);
friend istream& operator >>(istream& ins, DigitalTime& theObject);
friend ostream& operator <<(ostream& outs, const DigitalTime&theObject);
private:
//These member variables and helping functions are part of the implementation.
//They are not part of the interface. The word
//private indicates that they are not part
//of the public interface.
int hour;
int minute;
static void readHour( int& theHour);
//Precondition: Next input to be read from the keyboard is
//a time in notation, like 9:45 or 14:45.
//Postcondition: theHour has been set to the hour part of the time.
//The colon has been discarded and the next input to be read is the
//minute.
static void readMinute( int& theMinute);
//Reads the minute from the keyboard after readHour has read the
//hour.
static int digitToInt( char c);
//Precondition: c is one of the digits '0' through '9'.
//Returns the integer for the digit; for example, digitToInt('3')
//returns 3.
};
이러한 멤버 변수와 멤버 함수는 구현의 일부이다.
이들은 인터페이스의 일부가 아니다.
private라는 단어는 이들이 public 인터페이스의 일부가 아님을 나타낸다.
디스플레이 11.2 구현 파일
//This is the implementation file dtime.cpp of the class DigitalTime.
//The interface for the class DigitalTime is in the header file dtime.h.
#include <iostream>
#include <cctype>
#include <cstdlib>
using namespace std;
#include "dtime.h"
//Uses iostream and cstdlib:
DigitalTime::DigitalTime( int theHour, int theMinute)
{
if (theHour < 0 || theHour > 24 || theMinute < 0 || theMinute > 59)
{
cout << "Illegal argument to DigitalTime constructor.";
exit(1);
}
else
{
hour = theHour;
minute = theMinute;
}
if (hour == 24)
hour = 0; //Standardize midnight as 0:00
}
DigitalTime::DigitalTime( )
{
hour = 0;
minute = 0;
}
int DigitalTime::getHour( ) const
{
return hour;
}
int DigitalTime::getMinute( ) const
{
return minute;
}
void DigitalTime::advance( int minutesAdded)
{
int grossMinutes = minute + minutesAdded;
minute = grossMinutes % 60;
int hourAdjustment = grossMinutes / 60;
hour = (hour + hourAdjustment)%24;
}
void DigitalTime::advance( int hoursAdded, int minutesAdded)
{
hour = (hour + hoursAdded) % 24;
advance(minutesAdded);
}
bool operator ==( const DigitalTime& time1, const DigitalTime& time2)
{
return (time1.hour == time2.hour && time1.minute == time2.minute);
}
//Uses iostream:
ostream& operator <<(ostream& outs, const DigitalTime& theObject)
{
outs << theObject.hour << ':';
if (theObject.minute < 10)
outs << '0';
outs << theObject.minute;
return outs;
}
//Uses iostream:
istream& operator >>(istream& ins, DigitalTime& theObject)
{
DigitalTime::readHour(theObject.hour);
DigitalTime::readMinute(theObject.minute);
return ins;
}
int DigitalTime::digitToInt( char c)
{
return ( static_cast< int>(c) - static_cast< int>('0') );
}
//Uses iostream, cctype, and cstdlib:
void DigitalTime::readMinute(int& theMinute)
{
char c1, c2;
cin >> c1 >> c2;
if (!(isdigit(c1) && isdigit(c2)))
{
cout << "Error: illegal input to readMinute\n";
exit(1);
}
theMinute = digitToInt(c1)*10 + digitToInt(c2);
if (theMinute < 0 || theMinute > 59)
{
cout << "Error: illegal input to readMinute\n";
exit(1);
}
}
//Uses iostream, cctype, and cstdlib:
void DigitalTime::readHour( int& theHour)
{
char c1, c2;
cin >> c1 >> c2;
if ( !( isdigit(c1) && (isdigit(c2) || c2 == ':' ) ) )
{
cout << "Error: illegal input to readHour\n";
exit(1);
}
if (isdigit(c1) && c2 == ':')
{
theHour = DigitalTime::digitToInt(c1);
}
else //(isdigit(c1) && isdigit(c2))
{
theHour = DigitalTime::digitToInt(c1)*10
+ DigitalTime::digitToInt(c2);
cin >> c2; //discard ':'
if (c2 != ':')
{
cout << "Error: illegal input to readHour\n";
exit(1);
}
}
if (theHour == 24)
theHour = 0; //Standardize midnight as 0:00
if ( theHour < 0 || theHour > 23 )
{
cout << "Error: illegal input to readHour\n";
exit(1);
}
}
DigitalTime 클래스를 사용하는 모든 파일에는 include 지시문이 포함되어야 한다
#include "dtime.h"
응용프로그램 파일 혹은 드라이버 파일
따라서 구현 파일과 프로그램 파일 모두 인터페이스 파일의 이름을 지정하는 include 지시문이 포함해야 한다.
응용프로그램 파일(application file) 또는 드라이버 파일(driver file): 프로그램이 포함된 파일(즉, main 함수가 포함된 파일)
Display 11.3에는 DigitalTime 클래스를 사용하고 시연하는 매우 간단한 프로그램이 있는 응용프로그램 파일이 포함되어 있다.
프로그램 컴파일 및 실행
세 개의 파일에 들어 있는 이 완전한 프로그램을 어떻게 실행하느냐의 정확한 내용은 사용하는 시스템에 따라 달라진다.
하지만 기본적인 내용은 모든 시스템에서 동일하다.
구현 파일을 컴파일해야 하고
main 함수가 들어 있는 응용 프로그램 파일을 컴파일해야 한다.
인터페이스 파일은 컴파일하지 않는다.
예시)
dtime.h는 디스플레이 11.1에 주어진 파일이다.
컴파일러는 인터페이스 파일의 내용이 이미 다른 두 파일 각각에 포함되어 있다고 생각하기 때문에
인터페이스 파일을 컴파일할 필요가 없다.
구현 파일과 응용 프로그램 파일 모두 지시문을 포함하고 있음을 기억하라.
#include "dtime.h"
프로그램을 컴파일하면
include 지시문을 읽어오는 전처리기가 자동으로 호출되어
파일 dtime.h의 텍스트로 대체된다.
따라서 컴파일러는 dtime.h의 내용을 볼 수 있으므로
파일 dtime.h를 따로 컴파일할 필요가 없다.
(실제로 컴파일러는 dtime.h의 내용을 두 번 본다.
한 번은 구현 파일을 컴파일할 때, 한 번은 응용 파일을 컴파일할 때.)
이 dtime.h 파일을 복사하는 것은 개념 복사일 뿐이다.
컴파일러는 마치 dtime.h의 내용을 복사하는 것처럼 행동한다
dtime.h는 include 지시문이 있는 각 파일에 복사되었다.
그러나 파일이 컴파일된 후 해당 파일을 검색하면
include 명령어만 찾을 수 있고 파일 dtime.h의 내용은 찾을 수 없다.
링커
일단 구현 파일과 응용 파일이 컴파일되면 이 파일들이 함께 작동할 수 있도록 연결해야 한다.
이것을 파일을 연결하는 것(linking)이라고 하고 링커(linker)라고 하는 별도의 유틸리티로 수행한다.
링커를 호출하는 방법에 대한 세부 사항은 다르다.
사용 중인 시스템에서 프로그램을 실행하는 명령이 자동으로 링커를 호출하는 경우가 많다.
파일이 연결된 후에는 프로그램을 실행할 수 있다.
make 기능
복잡한 과정처럼 들리지만, 많은 시스템에는 이 세부 정보의 대부분을 자동 또는 반자동으로 관리하는 기능이 있다.
어떤 시스템에서든 세부 정보는 빠르게 일상화된다.
UNIX 시스템에서는, 이러한 세부 사항들은 make 기능에 의해 처리된다.
대부분의 IDE(통합 개발 환경)에서는 이러한 다양한 파일이 하나의 프로젝트(project)로 결합된다.
왜 파일을 분할할까?
디스플레이 11.1, 11.2, 11.3에는 하나의 완전한 프로그램이 조각으로 나누어져 세 개의 다른 파일에 들어 있다.
대신 이 세 파일의 내용을 하나의 파일로 결합한 다음, 이 하나의 파일을 컴파일하고 실행할 수 있다.
지시문이나 별도의 파일을 연결하는 등의 번거로움 없이 세 개의 다른 파일을 처리해야 하는 이유는 무엇인가?
프로그램을 별도의 파일로 분할하면 여러 가지 장점이 있다.
1. 응용프로그램 파일과 별도의 파일에 클래스 DigitalTime의 정의와 구현이 있으므로
코드를 다시 작성할 필요 없이 다양한 프로그램에서 이 클래스를 사용할 수 있다.
2. 각 프로그램에 클래스 코드를 작성하지 않고도 많은 프로그램에서 클래스를 사용할 수 있다.
3. 클래스의 구현을 변경할 수 있으며 클래스를 사용하는 프로그램의 어떤 부분도 다시 작성할 필요가 없다.
DigitalTime 클래스 구현에 대한 자세한 내용은 다음 예제 섹션에서 설명한다.
개별 파일에서 클래스 정의: 요약
클래스를 정의하고 클래스의 정의와 그 멤버 함수의 구현을 별도의 파일에 넣을 수 있다.
그런 다음 클래스를 사용하는 프로그램과는 별도로 클래스를 컴파일할 수 있으며
이 클래스를 여러 다른 프로그램에서 사용할 수 있다.
클래스는 다음과 같이 파일에 저장된다:
1. 클래스의 정의를 인터페이스 파일이라는 헤더 파일에 넣는다.
이 헤더 파일의 이름은 .h로 끝난다.
인터페이스 파일에는 기본 클래스 연산을 정의하지만
클래스 정의에 나열되지 않은 모든 함수와 오버로드 연산자에 대한 선언(prototype)도 포함되어 있다.
이 모든 함수와 연산자가 어떻게 사용되는지 설명하는 주석을 포함한다.
2. 앞에서 언급한 모든 함수와 오버로드 연산자의 정의는 구현 파일이라는 또 다른 파일에 저장된다.
이 파일은 앞에서 설명한 인터페이스 파일의 이름을 지정하는 include 명령어를 포함해야 한다.
여기에는 다음 예제와 같이 파일 이름 주변에 따옴표를 사용한다:
#include "dtime.h"
인터페이스 파일과 구현 파일은 전통적으로 이름은 같지만 접미사는 다르다.
인터페이스 파일은 .h로 끝난다.
구현 파일은 완전한 C++ 프로그램을 포함하는 파일에 사용하는 것과 같은 접미사로 끝난다.
구현 파일은 프로그램에서 사용하기 전에 별도로 컴파일된다.
3. 프로그램에서 클래스를 사용하려면
프로그램의 주요 부분(및 추가 함수 정의, 상수 선언 등)을
응용 프로그램 파일 또는 드라이버 파일이라고 하는 다른 파일에 넣는다.
이 파일에는 다음 예제와 같이 인터페이스 파일의 이름을 지정하는 include 지시문도 포함되어 있어야 한다:
#include "dtime.h"
응용프로그램 파일은 구현 파일과 별도로 컴파일된다.
인터페이스와 구현 파일 한 쌍과 함께 사용할 이러한 응용프로그램 파일을 얼마든지 작성할 수 있다.
전체 프로그램을 실행하려면
먼저 응용프로그램 파일을 컴파일하여 생성한 객체 코드와
구현 파일을 컴파일하여 생성한 객체 코드를 링크해야 한다.
(일부 시스템에서는 링크가 자동 또는 반자동으로 수행될 수 있다.)
프로그램에서 여러 클래스를 사용하는 경우
단순히 여러 인터페이스 파일과 여러 구현 파일이 있으며,
각 파일은 개별적으로 컴파일된다.
디스플레이 11.3 DigitalTime 클래스를 이용한 애플리케이션 파일
//This is the application file timedemo.cpp, which demonstrates use of
//DigitalTime.
#include <iostream>
using namespace std;
#include "dtime.h"
int main( )
{
DigitalTime clock, oldClock;
cout << "You may write midnight as either 0:00 or 24:00,\n"
<< "but I will always write it as 0:00.\n"
<< "Enter the time in 24-hour notation: ";
cin >> clock;
oldClock = clock;
clock.advance(15);
if (clock == oldClock)
cout << "Something is wrong.";
cout << "You entered " << oldClock << endl;
cout << "15 minutes later the time will be "
<< clock << endl;
clock.advance(2, 15);
cout << "2 hours and 15 minutes after that\n"
<< "the time will be "
<< clock << endl;
return 0;
}
샘플 대화 상자
You may write midnight as either 0:00 or 24:00,
but I will always write it as 0:00.
Enter the time in 24-hour notation: 11:15
You entered 11:15
15 minutes later the time will be 11:30
2 hours and 15 minutes after that
the time will be 13:45
예: DigitalTime 클래스
앞에서 Displays 11.1, 11.2, 11.3의 파일이
클래스 DigitalTime을 위한 인터페이스,
클래스 DigitalTime 구현,
클래스를 사용하는 응용 프로그램의
세 가지 파일로 프로그램을 분할하는 방법을 설명했다.
여기에서는 클래스 구현의 세부 사항에 대해 설명한다.
이 예제 섹션에는 새로운 자료가 없지만
구현의 일부 세부 사항(Display 11.2)이 완전히 명확하지 않은 경우
이 섹션에서 혼란을 설명할 수 있다.
대부분의 구현 세부사항은 간단하지만, 코멘트를 달 가치가 있는 두 가지가 있다.
멤버 함수 이름 advance는 두 개의 함수 정의가 있을 정도로 오버로딩 상태임을 주목하라.
또한 오버로딩된 추출(입력) 연산자 >>에 대한 정의는 readHour와 readMinute라는 두 개의 도움 함수를 사용하며,
이 두 도움 함수 자체가 digitToInt라는 세 번째 도움 함수를 사용한다는 점도 주목하자.
이러한 점에 대해 논의해 보겠다.
클래스 DigitalTime(Displays 11.1과 11.2)에는 advance라고 불리는 두 개의 멤버 함수가 있다.
하나의 버전은 단일 인수, 즉 시간을 앞당기는 데 분수를 부여하는 정수를 사용한다.
다른 버전은 두 개의 인수, 하나는 시간 수를 위한 인수, 하나는 분수를 더한 인수를 사용하며 시간을 진전시킨다.
두 개의 인수 버전 advance의 정의에는 한 개의 인수 버전에 대한 호출이 포함되어 있음을 주목하자.
11.2에 나와 있는 두 개의 인수 버전의 정의를 보자.
먼저 시간을 HoursAdded 시간만큼 앞당긴 다음
단일 인수 버전의 advance을 사용하여 추가적인 minutesAdded 분만큼 시간을 진전시킨다.
처음에는 이상하게 보일지 모르지만, 완벽하게 합법적이다.
advance라는 이름의 두 함수는 컴파일러에 관한 한 우연히 같은 이름을 갖게 된 두 개의 다른 함수이다.
이제 도움말 함수(helping function)에 대해 설명하겠다.
도움말 함수는 readHour와 readMinute를 통해 입력된 문자를 한 글자씩 읽은 다음,
입력값을 멤버 변수인 시와 분에 입력된 정수 값으로 변환한다.
readHour 함수과 readMinute 함수는
시와 분을 한 자리씩 읽으므로 char 타입의 값을 읽는다.
입력값을 int 값으로 읽는 것보다 더 복잡하지만,
입력이 제대로 형성되지 않은 경우
오류 검사를 수행하여 입력이 제대로 형성되었는지 확인하고
오류 메시지를 발행할 수 있다.
readHour와 read Minute 도움말 함수는 digitToInt라는 다른 도움말 함수를 사용한다.
digitToInt 함수는 '3'과 같은 숫자를 3과 같은 숫자로 변환한다.
이 함수는 이전에 이 책에서 7장의 Self-Test Practice 3에 대한 답으로 주어졌다.
팁: 재사용 가능한 구성 요소
클래스가 개발되어 별도의 파일로 코드화된 소프트웨어 구성 요소는 여러 프로그램에서 반복적으로 사용될 수 있다.
재사용 가능한 구성 요소는 모든 응용 프로그램에 대해 재설계, 재코딩, 재테스트를 할 필요가 없으므로 작업을 절약할 수 있다.
재사용 가능한 구성 요소는 또한 한 번만 사용하는 구성 요소보다 더 신뢰할 수 있다.
두 가지 이유로 인해 더 신뢰할 수 있다.
1. 구성 요소가 여러 번 사용될 경우 더 많은 시간과 노력을 들일 수 있다.
2. 구성 요소가 반복적으로 사용될 경우 반복적으로 테스트된다.
소프트웨어 구성 요소의 모든 사용은 해당 구성 요소의 테스트이다.
소프트웨어 구성 요소를 다양한 맥락에서 여러 번 사용하는 것은
소프트웨어에 남아 있는 버그를 발견하는 가장 좋은 방법 중 하나이다.
#ifndef 사용
우리는 프로그램을 세 개(또는 그 이상의) 파일에 배치하는 방법을 제시했는데,
각 클래스의 인터페이스와 구현을 위한 것과 프로그램의 응용 부분을 위한 것이다.
하나의 프로그램은 세 개 이상의 파일에 저장될 수 있다.
예시)
프로그램은 여러 클래스를 사용할 수 있고
각 클래스는 별도의 파일 쌍에 저장될 수 있다.
여러 파일에 걸쳐 프로그램이 퍼져 있고
두 개 이상의 파일에
다음과 같은 클래스 인터페이스 파일에 대한
include 지시문이 있다고 가정한다:
#include "dtime.h"
이런 상황에서는 다른 파일을 포함하는 파일을 가질 수 있고,
다른 파일은 다른 파일을 포함할 수도 있다.
그러면 파일에 dtime.h 단위의 정의가 두 번 이상 포함되는 상황이 쉽게 발생할 수 있다.
C++는 반복되는 정의가 동일하더라도 클래스를 두 번 이상 정의할 수 없다.
또한 여러 프로젝트에서 동일한 헤더 파일을 사용하는 경우
클래스 정의를 두 번 이상 포함했는지 여부를 추적하는 것이 불가능에 가깝다.
이 문제를 방지하기 위해 C++는 코드 섹션을 다음과 같이 표시하는 방법을 제공한다
"만약 여러분이 이미 전에 이것을 한 번이라도 포함시킨 적이 있다면,
그것을 다시 포함시키지 마세요"라고 말하라.
비록 여러분이 그것에 익숙해질 때까지 그 표기법이 약간 이상하게 보일 수 있지만,
이것이 행해지는 방법은 매우 직관적이다.
우리는 가면서 세부사항들을 설명하면서, 예를 살펴볼 것이다.
DTIME_H를 정의하는 지침은 다음과 같다:
#Defined DTIME_H
이것이 의미하는 것은
컴파일러의 전처리기가 DTIME_H를 목록에 올려놓아 DTIME_H를 보았다는 것을 의미한다.
DTIME_H는 단순히 목록에 올려놓는 것을 의미하는 것이 아니라
정의된 것으로 정의되어 있기 때문에
정의된 것은 이에 가장 적합한 단어는 아니다.
중요한 점은 다른 명령어를 사용하여 DTIME_H가 정의되었는지 여부를 테스트하고
코드 섹션이 이미 처리되었는지 여부를 테스트할 수 있다는 것이다.
DTIME_H 대신에 임의의 (키워드가 아닌) 식별자를 사용할 수 있지만
어떤 식별자를 사용해야 하는지에 대한 표준 규칙이 있다는 것을 알게 될 것이다.
DTIME_H가 정의되었는지 여부를 확인하기 위한 다음 지침 테스트:
#ifndef DTIME_H
디스플레이 11.4 클래스의 다중 정의 피하기
//This is the header file dtime.h. This is the interface for the class
//DigitalTime. Values of this type are times of day. The values are
//input and output in 24-hour notation, as in 9:30 for 9:30 AM and
//14:45 for 2:45 PM.
#ifndef DTIME_H
#define DTIME_H
#include <iostream>
using namespace std;
class DigitalTime
{
<The definition of the class DigitalTime is the same as in Display 11.1.>
};
#endif //DTIME_H
팁: 다른 라이브러리 정의하기
별도의 컴파일을 사용하기 위해서는 클래스를 정의할 필요가 없다.
자신이 설계한 라이브러리로 만들고자 하는 관련 함수 모음이 있으면,
클래스에 대해 설명한 것과 같이 함수 선언(원형)과 그에 수반되는 주석을 헤더 파일에,
함수 정의를 구현 파일에 배치할 수 있다.
그 후 이 라이브러리를 사용하면 별도의 파일에 클래스를 배치한 것과 동일한 방식으로 프로그램에서 사용할 수 있다.
11.2 네임스페이스
프로그램이 다른 프로그래머들에 의해 쓰여진 다른 클래스와 함수들을 사용할 때
두 프로그래머가 다른 두 가지 일에 같은 이름을 사용할 가능성이 있다.
네임스페이스는 이 문제를 해결하기 위한 하나의 방법이다.
네임스페이스: 클래스 정의나 변수 선언과 같은 이름 정의들의 집합
어떤 의미에서는 네임스페이스의 일부가 다른 네임스페이스의 이름과 충돌할 때 그것을 끄도록 설정할 수 있다.
네임스페이스 및 using 지시어
std라는 이름의 네임스페이스는 이미 사용하고 있다.
std 네임스페이스에는 표준 C++ 라이브러리 파일(iostream 등)에 정의된 모든 이름이 포함되어 있다.
예를 들어, 파일의 시작 부분에 다음 행을 배치할 때,
#include <iostream>
모든 이름 정의(cin 및 cout과 같은 이름의 경우)를 std 네임스페이스에 배치한다.
프로그램이 std 네임스페이스를 사용하고 있음을 지정하지 않는 한
std 네임스페이스의 이름에 대해 알 수 없다.
std 네임스페이스의 모든 정의를 코드에서 사용할 수 있도록 하려면
다음 using 지시문을 삽입하라:
using namespace std;
using 지시문을 포함하려는 이유를 확인하는 좋은 방법은 포함하지 않으려는 이유를 생각해 보는 것이다.
네임스페이스 std에 대한 using 지시문을 포함하지 않는 경우,
표준 의미가 아닌 다른 의미를 갖도록 cin 및 cout을 정의할 수 있다.
(아마도 표준 버전과 약간 다르게 동작하기를 원하기 때문에 cin 및 cout을 재정의하고 싶을 것이다.)
표준 의미는 std 네임스페이스에 있다.
using 지시문(또는 이와 같은 것)이 없으면 표준 의미는 std 네임스페이스에 있다
코드는 std 네임스페이스에 대해 아무것도 알지 못하기 때문에,
코드에 관한 한, cin과 cout의 정의는 당신이 제공하는 모든 정의뿐이다.
전역 네임스페이스
당신이 작성하는 코드의 모든 비트는 일부 네임스페이스에 있다.
만약 당신이 특정 네임스페이스에 코드를 두지 않는다면,
그 코드는 전역 네임스페이스라고 알려진 네임스페이스에 있다.
지금까지 우리는 우리가 작성한 코드를 어떤 네임스페이스에도 두지 않았으므로
우리의 모든 코드는 전역 네임스페이스에 있었다.
당신은 항상 전역 네임스페이스를 사용하기 때문에 전역 네임스페이스에는 using 지시문이 없다.
당신은 전역 네임스페이스를 사용하고 있다는 암시적인 자동 using 지시문이 항상 존재한다고 말할 수 있다.
동일한 프로그램에서 둘 이상의 네임스페이스를 사용할 수 있다.
예를 들어, 우리는 항상 전역 네임스페이스를 사용하고 있고
보통 std 네임스페이스를 사용하고 있다.
이름이 두 개의 네임스페이스로 정의되어 있고
두 개의 네임스페이스를 모두 사용하고 있다면 어떻게 될까?
이렇게 되면 오류(정확한 세부 정보에 따라 컴파일러 오류 또는 런타임 오류)가 발생한다.
두 개의 다른 네임스페이스에 같은 이름이 정의될 수 있지만,
만약 그렇다면 한 번에 한 개의 네임스페이스만 사용할 수 있다.
하지만, 그렇다고 해서 두 개의 네임스페이스를 같은 프로그램에서 사용할 수 있는 것은 아니다.
같은 프로그램에서 각각 다른 시간에 사용할 수 있다.
예를 들어 NS1과 NS2를 두 개의 네임스페이스라고 가정하고
myFunction이 두 네임스페이스에 정의되어 있지만
두 네임스페이스에 다른 방식으로 정의된 인수가 없는 void 함수라고 가정한다.
그러면 다음은 합법적이다:
{
using namespace NS1;
myFunction( );
}
{
using namespace NS2;
myFunction( );
}
첫 번째 호출은 네임스페이스 NS1에 주어진 myFunction의 정의를 사용하고
두 번째 호출은 네임스페이스 NS2에 주어진 myFunction의 정의를 사용한다.
범위
블록은 괄호 안에 포함된 문장, 선언 및 기타 코드의 목록임을 기억하자.
블록을 시작할 때 using 지시문은 해당 블록에만 적용된다.
따라서 첫 번째 using 지시문은 첫 번째 블록에서만 적용되고
두 번째 using 지시문은 두 번째 블록에서만 적용된다.
일반적인 표현 방법은 using 지시문은 NS1의 범위가 첫 번째 블록인 반면,
using 지시문은 NS2의 범위는 두 번째 블록인 것이다.
이 범위(scope) 규칙 때문에 동일한 프로그램에서 충돌하는 두 개의 네임스페이스를 사용할 수 있다
(예: 이전 단락에서 설명한 두 블록을 포함하는 프로그램).
일반적으로 블록의 시작 부분에 using 지시문을 배치한다.
그러나 블록에서 더 아래로 내려간다면 using 지시문의 정확한 범위를 알아야 한다.
using 지시문의 범위는 발생한 위치부터 블록의 끝까지 진행된다.
둘 이상의 블록에서 동일한 네임스페이스에 대한 using 지시문을 가질 수 있으므로
네임스페이스의 전체 범위는 연결되지 않은 여러 블록을 포함할 수 있다.
블록에서 using 지시문을 사용할 때는 일반적으로 함수 정의의 본문으로 구성된 블록이다.
파일의 시작 부분에 (지금까지 일반적으로 그래왔던 것처럼) using 지시문을 놓으면
using 지시문은 파일 전체에 적용된다.
using 지시문은 일반적으로 파일의 시작 부분(또는 블록의 시작 부분) 근처에 놓여야 하지만,
정확한 범위 규칙은 모든 블록 밖에 있는 using 지시문의 범위는 using 지시문의 발생부터 파일의 끝까지라는 것이다.
using 지시문에 대한 범위 규칙
using 지시문의 범위는 표시되는 블록(더 정확하게는 using 지시문의 위치에서 블록의 끝까지)이다.
using 지시문가 모든 블록 밖에 있는 경우 using 지시문를 따르는 모든 파일에 적용된다.
네임스페이스 만들기
네임스페이스 그룹화
네임스페이스에 일부 코드를 배치하려면
단순히 다음 양식의 네임스페이스 그룹(namespace grouping)에 코드를 배치하면 된다:
namespace Name_Space_Name
{
Some_Code
}
이러한 그룹화 중 하나를 코드에 포함시키면
Some_Code에 정의된 이름을 namespace Name_Space_Name에 넣게 된다고 한다.
이러한 이름(실제로 이러한 이름의 정의)은 using 지시문으로 사용할 수 있다
using namespace Name_Space_Name;
예시)
Display 11.5에서 가져온 다음은 네임스페이스 Space1에 함수 선언을 배치한다:
namespace Space1
{
void greeting( );
}
Display 11.5에서 다시 살펴보면 함수 greeting의 정의가 네임스페이스 Space1에 있는 것을 볼 수 있다.
이 작업은 다음과 같은 추가 네임스페이스 그룹화를 통해 수행된다:
namespace Space1
{
void greeting( )
{
cout << "Hello from namespace Space1.\n";
}
}
단일 네임스페이스에 대해 이러한 네임스페이스 그룹을 얼마든지 가질 수 있다.
디스플레이 11.5에서는
네임스페이스 Space1에 두 개의 네임스페이스 그룹화를 사용하고
네임스페이스 Space2에 두 개의 다른 네임스페이스 그룹화를 사용했다.
네임스페이스에 정의된 모든 이름은 네임스페이스 그룹 내부에서 사용할 수 있지만
이름을 네임스페이스 그룹 외부에서 코드화할 수도 있다.
예를 들어, 네임스페이스 1의 함수 선언 및 함수 정의는
디스플레이 11.5와 같이
using 지시문
using namespace Space1을 이용할 수 있다.
디스플레이 11.5 네임스페이스 구현
#include <iostream>
using namespace std;
namespace Space1
{
void greeting( );
}
namespace Space2
{
void greeting( );
}
void bigGreeting( );
int main( )
{
{
//Names in this block use definitions in
//namespaces Space2, std, and the
//global namespace.
using namespace Space2;
greeting( );
}
{
//Names in this block use definitions in
//namespaces Space1,std, and the
//globalnamespace.
using namespace Space1;
greeting( );
}
//Names out here only use definitions in the
//namespace std and the global namespace.
bigGreeting( );
return 0;
}
namespace Space1
{
void greeting( )
{
cout << "Hello from namespace Space1.\n";
}
}
namespace Space2
{
void greeting( )
{
cout << "Greetings from namespace Space2.\n";
}
}
void bigGreeting( )
{
cout << "A Big Global Hello!\n";
}
이 블록의 이름은 이름공간 Space2, std 및 전역 네임스페이스의 정의를 사용한다.
이 블록의 이름은 이름공간 Space1, std 및 전역 네임스페이스의 정의를 사용한다.
여기에 있는 이름은 네임스페이스 std 및 전역 네임스페이스의 정의만 사용한다.
샘플 대화 상자
Greetings from namespace Space2.
Hello from namespace Space1.
A Big Global Hello!
네임스페이스에 정의 입력
네임스페이스 그룹에 이름 정의를 배치하여
네임스페이스에 이름 정의를 배치한다는 다음과 같은 구문을 가지고 있다:
namespace Namespace_Name
{
Definition_1
Definition_2
.
.
.
Definition_Last
}
여러 네임스페이스 그룹(여러 파일에서도) 및 의 모든 정의를 사용할 수 있다
모든 그룹은 동일한 네임스페이스에 있다.
using 선언
이 하위 섹션에서는 네임스페이스에 있는 모든 이름을 사용할 수 있도록 하는 대신
네임스페이스에서 하나의 이름만 프로그램에 사용할 수 있도록 자격을 부여하는 방법을 설명한다.
다음과 같은 상황이 발생했다고 가정해보자.
NS1과 NS2라는 두 개의 네임스페이스가 있다.
NS1에 정의된 함수 fun1과 네임스페이스 NS2에 정의된 함수 fun2를 사용하려고 한다.
문제는 NS1과 NS2가 모두 myFunction을 정의한다는 것이다.
(이 논의의 모든 함수는 인수를 사용하지 않으므로 오버로드는 적용되지 않는다.)
당신은 아래 구문을 사용할 수 없다
using namespace NS1;
using namespace NS2;
이것은 잠재적으로 myFunction에 대한 상반된 정의를 제공할 수 있다.
(만약 myFunction이라는 이름이 사용되지 않는다면,
컴파일러는 문제를 감지하지 못하고 프로그램을 컴파일하고 실행할 수 있게 된다.)
당신이 필요한 것은 당신이 네임스페이스 NS1에서 fun1을 사용하고 있고,
네임스페이스 NS2에서 fun2를 사용하고 있고,
그 외에는 namespace NS1과 NS2에서 fun2를 사용하고 있다고 말하는 방법이다.
우리는 이미 이 상황을 처리할 수 있는 기술을 사용하고 있다.
다음은 당신의 해결책이다:
using NS1::fun1;
using NS2::fun2;
Name_Space::One_Name을 사용하는 양식의 자격은
네임스페이스 Name_Space의 (정의) One_Name을 사용할 수 있게 해주지만
Name_Space의 다른 이름은 사용할 수 없게 해준다.
이것을 using 선언(declaration)이라고 한다.
여기에서 선언을 사용하여 사용하는 범위 지정 연산자 ::는
멤버 함수를 정의할 때 사용하는 범위 지정 연산자와 동일하다.
범위 지정 연산자의 이 두 가지 용도는 유사점이 있다.
예를 들어 디스플레이 11.2는 다음과 같은 기능 정의를 가지고 있었다:
void DigitalTime::advance( int hoursAdded, int minutesAdded)
{
hour = (hour + hoursAdded)%24;
advance(minutesAdded);
}
이 경우 ::는 DigitalTime 클래스에 대한 함수를 정의하고 있음을 의미하며,
다른 클래스의 고급이라는 이름의 다른 함수와는 대조적이다.
유사하게,
using NS1::fun1;
즉, 다른 네임스페이스에서 fun1에 대한 다른 정의와 달리
네임스페이스 NS1에 정의된 fun1이라는 이름의 함수를 사용하고 있다.
using 선언 간에는 다음과 같은 두 가지 차이점이 있다
using std::cout;
그리고 다음과 같은 using 지시문
using namespace std;
차이점은 다음과 같다:
1. using 선언은 네임스페이스에 있는 하나의 이름만 코드에 사용할 수 있는 반면
using 지시문은 네임스페이스에 있는 모든 이름을 사용할 수 있다.
2. using 선언은 코드에 이름(cout 같은)이 추가되므로 이름을 다른 용도로 사용할 수 없다.
그러나 using 지시문은 네임스페이스의 이름을 잠재적으로 도입할 뿐이다.
1번은 매우 분명하다. 2번 점은 약간의 미묘한 점들을 가지고 있다.
예를 들어,
이름공간 NS1과 NS2가 모두 myFunction에 대한 정의를 제공하지만
다른 이름 충돌은 없다고 가정한다.
그러면 (이 명령의 범위 내에서) 충돌하는 이름이 코드에서 사용되지 않는다면
다음은 아무런 문제를 일으키지 않을 것이다.
using namespace NS1;
using namespace NS2;
반면, myFunction 기능을 사용하지 않더라도 다음은 불법이다:
using NS1::myFunction;
using NS2::myFunction;
때때로 이 미묘한 점은 중요할 수 있지만 대부분의 일상적인 코드에는 영향을 미치지 않는다.
그래서 우리는 종종 using 지시문 또는 using 선언을 의미하기 위해
용어 using 지시문을 느슨하게 사용할 것이다.
자격을 갖춘 이름
이 섹션에서는 이전에 논의하지 않은 이름을 지정하는 방법을 소개한다.
네임스페이스 NS1에 정의된 대로 fun1이라는 이름을 사용하려고 하고,
fun1을 한 번(또는 적은 횟수)만 사용하려고 한다고 가정한다.
네임스페이스의 이름과 범위 지정 연산자를 사용하여
다음과 같이 함수(또는 다른 항목)의 이름을 지정할 수 있다:
NS1::fun1( );
이 양식은 매개변수 유형을 지정할 때 자주 사용된다.
예를 들어
int getInput(std::istream inputStream)
. . .
getInput 함수에서 inputStream의 매개변수는 isstream이며,
istream은 std 네임스페이스에 정의되어 있다.
이 타입 이름 istream의 사용이 std 네임스페이스에서 필요한 유일한 이름인 경우
(또는 필요한 모든 이름이 std::로 유사하게 자격이 부여된 경우)에는 필요하지 않다
using namespace std;
or
using std::istream;
std::istream은
이름도 istream으로 정의하는 다른 네임스페이스에 대한
using 지시문의 범위 내에서도 사용할 수 있다.
이 경우 std::istream과 istream의 정의는 서로 다르다.
예를 들어
using namespace MySpace;
void someFunction(istream p1, std::istream p2);
istream이 네임스페이스 MySpace에 정의된 유형이라고 가정하면
p1은 MySpace에 정의된 대로 istream 유형을 가지고
p2는 std 네임스페이스에 정의된 대로 istream 유형을 갖는다.
팁: 네임스페이스의 이름 선택
다른 사람이 당신과 같은 네임스페이스 이름을 사용할 가능성을 줄이기 위해
네임스페이스의 이름에 당신의 성 또는 다른 고유 문자열을 포함시키는 것이 좋다.
여러 프로그래머가 동일한 프로젝트에 대한 코드를 작성할 때,
서로 구별되어야 하는 네임스페이스가 실제로 서로 구별되는 이름을 갖는다는 것이 중요하다.
그렇지 않으면, 당신은 쉽게 같은 범위에 있는 동일한 이름을 여러 정의할 수 있다.
그래서 우리는 디스플레이 11.9의 네임스페이스 DtimeSavitch에 Savitch라는 이름을 포함시켰다.
예: 네임스페이스의 클래스 정의
디스플레이 11.6 및 11.7에서
클래스 DigitalTime의 헤더 파일 dtime.h와
클래스 DigitalTime의 구현 파일을 다시 작성했다.
이번에는 DTimeSavitch라는 네임스페이스에 정의를 넣었다.
네임스페이스 DTimeSavitch는 dtime.h 및 dtime.cpp 두 파일에 걸쳐 있다.
네임스페이스는 여러 파일에 걸쳐 있을 수 있다.
DigitalTime 클래스의 정의를 Display 11.6 및 11.7과 같이 다시 작성하는 경우
Display 11.3의 응용 프로그램 파일은
다음과 같은 방식으로 네임스페이스 DTimeSavitch를 지정해야 한다:
using namespace DTimeSavitch;
or
using DTimeSavitch::DigitalTime;
디스플레이 11.6 네임스페이스에 클래스 배치(헤더 파일)
//This is the header file dtime.h.
#ifndef DTIME_H
#define DTIME_H
#include <iostream>
using std::istream;
using std::ostream;
namespace DTimeSavitch
{
class DigitalTime
{
< The definition of the class DigitalTime is the same as in Display 11.1 >.
};
} // DTimeSavitch
#endif //DTIME_H
네임스페이스 DTimeSavitch는 두 개의 파일에 걸쳐 있으며 나머지 하나는 디스플레이 11.7에 나와 있다.
이 클래스 정의의 더 나은 버전은 디스플레이 11.8 및 11.9에 제공된다.
이름없는 네임스페이스
컴파일 단위(compilation unit): 클래스 구현 파일과 같이 클래스에 대한 인터페이스 헤더 파일과 같이 파일에 #include된 모든 파일과 함께 하는 파일이다.
모든 컴파일 단위에는 이름 없는 네임스페이스가 있다.
이름 없는 네임스페이스에 대한 네임스페이스 그룹핑은
다른 네임스페이스와 동일한 방식으로 작성되지만
다음 예제와 같이 이름이 지정되지는 않다:
namespace
{
void sampleFunction( )
.
.
.
} //unnamed namespace
디스플레이 11.7 네임스페이스에 클래스 배치(구현 파일)
//This is the implementation file dtime.cpp.
#include <iostream>
#include <cctype>
#include <cstdlib>
using std::istream;
using std::ostream;
using std::cout;
using std::cin;
#include "dtime.h"
namespace DTimeSavitch
{
< All the function definitions from Display 11.2 go here. >
} // DTimeSavitch
namespace std를 사용하여 지시문을 사용하여 단일을 사용할 수 있으며 선언을 사용하여 이 4개 대신 사용할 수 있다.
그러나 선언문을 사용하는 4개는 바람직한 스타일이다.
이름 없는 네임스페이스에 정의된 모든 이름은 컴파일 단위에 로컬이므로
컴파일 단위 외부의 다른 용도로 이름이 재사용될 수 있다.
예를 들어 디스플레이 11.8과 11.9에는
클래스 DigitalTime의 인터페이스 및 구현 파일이 다시 작성(그리고 최종)된 버전으로 표시된다.
readHour, readMinute 및 digitToInt 도움 함수는 모두 이름 없는 네임스페이스에 있으므로 컴파일 단위에 로컬이다.
디스플레이 11.10에 표시된 것처럼 이름 없는 네임스페이스의 이름은 컴파일 단위 외부의 다른 용도로 재사용될 수 있다.
디스플레이 11.10에서는 readHour라는 함수 이름이 응용 프로그램의 다른 기능에 재사용된다.
디스플레이 11.9의 구현 파일을 다시 보면 이름 없는 네임스페이스 밖에서
이름 없는 네임스페이스 수식자 없이 digitToInt, readHour, readMinute 함수를 사용하고 있다.
이름 없는 네임스페이스에 정의된 이름은 컴파일 단위 어디에서도 자격 없이 사용할 수 있다.
(이름 없는 네임스페이스에는 이름을 따는데 사용할 이름이 없으므로 당연히 그래야 한다.)
디스플레이 11.8 네임스페이스에 도움말 기능 숨기기(인터페이스 파일)
//This is the header file dtime.h. This is the interface for the class
//DigitalTime. Values of this type are times of day. The values are
//input and output in 24-hour notation, as in 9:30 for 9:30 AM and
//14:45 for 2:45 PM.
#ifndef DTIME_H
#define DTIME_H
#include <iostream>
using std::istream;
using std::ostream;
namespace DTimeSavitch
{
class DigitalTime
{
public:
DigitalTime( int theHour, int theMinute);
DigitalTime( );
//Initializes the time value to 0:00 (which is midnight).
getHour( ) const;
getMinute( ) const;
void advance( int minutesAdded);
//Changes the time to minutesAdded minutes later.
void advance( int hoursAdded, int minutesAdded);
//Changes the time to hoursAdded hours plus minutesAdded minutes later.
friend bool operator ==( const DigitalTime& time1,
const DigitalTime& time2);
friend istream& operator >>(istream& ins, DigitalTime& theObject);
friend ostream& operator <<(ostream& outs,
const DigitalTime& theObject);
private:
int hour;
int minute;
};
} //DTimeSavitch
#endif //DTIME_H
이것은 DigitalTime 클래스의 마지막 버전이다.
이것이 가장 좋은 버전이고 당신이 사용해야 할 버전이다.
이 인터페이스와 함께 사용하기 위한 구현은 디스플레이 11.9에 나와 있다.
인터페이스 파일에는 도움말 기능이 언급되어 있지 않다.
디스플레이 11.9 네임스페이스에 도움말 기능 숨기기(구현 파일)
//This is the implementation file dtime.cpp of the class DigitalTime.
//The interface for the class DigitalTime is in the header file dtime.h.
#include <iostream>
#include <cctype>
#include <cstdlib>
using std::istream;
using std::ostream;
using std::cout;
using std::cin;
#include "dtime.h"
namespace //Specifies the unnamed namespace
{
//Names defined in the unnamed namespace
//are local to the compilation unit. So, these
//helping functions are local to the file
//dtime.cpp.
int digitToInt( char c)
{
return ( int(c) - int('0') );
}
//Uses iostream, cctype, and cstdlib:
void readMinute(int& theMinute)
{
char c1, c2;
cin >> c1 >> c2;
if (!(isdigit(c1) && isdigit(c2)))
{
cout << "Error: illegal input to readMinute\n";
exit(1);
}
theMinute = digitToInt(c1)*10 + digitToInt(c2);
if (theMinute < 0 || theMinute > 59)
{
cout << "Error: illegal input to readMinute\n";
exit(1);
}
}
//Uses iostream, cctype, and cstdlib:
void readHour( int& theHour)
{
char c1, c2;
cin >> c1 >> c2;
if ( !( isdigit(c1) && (isdigit(c2) || c2 == ':' ) ) )
{
cout << "Error: illegal input to readHour\n";
exit(1);
}
if (isdigit(c1) && c2 == ':')
{
theHour = digitToInt(c1);
}
else//(isdigit(c1) && isdigit(c2))
{
theHour = digitToInt(c1)*10 + digitToInt(c2);
cin >> c2; //discard ':'
if (c2 != ':')
{
cout << "Error: illegal input to readHour\n";
exit(1);
}
}
if (theHour == 24)
theHour = 0; //Standardize midnight as 0:00.
if (theHour < 0 || theHour > 23)
{
cout << "Error: illegal input to readHour\n";
exit(1);
}
}
} //unnamed namespace
namespace DTimeSavitch
{
//Uses iostream:
istream& operator >>(istream& ins, DigitalTime& theObject)
{
//Within the compilation unit (in this case
//dtime.cpp), you can use names in the
//unnamed namespace without qualification.
readHour(theObject.hour);
readMinute(theObject.minute);
return ins;
}
ostream& operator <<(ostream& outs, const DigitalTime& theObject)
<The body of the function definition is the same as in Display 11.2.>
bool operator ==( const DigitalTime& time1, const DigitalTime& time2)
<The body of the function definition is the same as in Display 11.2.>
DigitalTime::DigitalTime( int theHour, int theMinute)
<The body of the function definition is the same as in Display 11.2.>
DigitalTime::DigitalTime( )
<The body of the function definition is the same as in Display 11.2.>
int DigitalTime::getHour( ) const
<The body of the function definition is the same as in Display 11.2.>
int DigitalTime::getMinute( ) const
<The body of the function definition is the same as in Display 11.2.>
void DigitalTime::advance( int minutesAdded)
<The body of the function definition is the same as in Display 11.2.>
void DigitalTime::advance( int hoursAdded, int minutesAdded)
<The body of the function definition is the same as in Display 11.2.>
} //DTimeSavitch
디스플레이 11.10 네임스페이스에 도움말 기능 숨기기(응용프로그램)
//This is the application file timedemo.cpp. This program
//demonstrates hiding the helping functions in an unnamed namespace.
#include <iostream>
#include "dtime.h"
void readHour(int& theHour);
int main( )
{
using std::cout;
using std::cin;
using std::endl;
using DTimeSavitch::DigitalTime;
int theHour;
readHour(theHour);
DigitalTime clock(theHour, 0), oldClock;
oldClock = clock;
clock.advance(15);
if (clock == oldClock)
cout << "Something is wrong.";
cout << "You entered " << oldClock << endl;
cout << "15 minutes later the time will be "
<< clock << endl;
clock.advance(2, 15);
cout << "2 hours and 15 minutes after that\n"
<< "the time will be "
<< clock << endl;
return 0;
}
void readHour( int& theHour)
{
using std::cout;
using std::cin;
cout << "Let's play a time game.\n"
<< "Let's pretend the hour has just changed.\n"
<< "You may write midnight as either 0 or 24,\n"
<< "but, I will always write it as 0.\n"
<< "Enter the hour as a number (0 to 24): ";
cin >> theHour;
}
using 선언을 여기(#include<iostream>이 있는 부분)에 배치하면 프로그램 동작이 동일하다.
그러나 많은 권한들은 using 선언문이나 using 지시문 각각의 범위를 합리적인 범위로 만들어야 한다고 말하고 있으며,
우리는 그 기법의 예를 들어 보고자 했다.
이 함수(응용프로그램의 readHour)는 구현 파일의 readHour와 다르다(디스플레이 11.9에 표시됨)
우리가 예전에 이 using 선언문들을 주었을 때는 그것들이 main에 있어서 그들의 범위가 main이었다.
따라서 readHour에서 cin과 cout을 사용하려면 여기서(readHour 함수 내에서 다시) 반복해야 한다.
샘플 대화 상자
Let's play a time game.
Let's pretend the hour has just changed.
You may write midnight as either 0 or 24,
but, I will always write it as 0.
Enter the hour as a number (0 to 24): 11
You entered 11:00
15 minutes later the time will be 11:15
2 hours and 15 minutes after that
the time will be 13:30
이름 없는 네임스페이스
이름 없는 네임스페이스를 사용하여 컴파일 단위에 로컬로 정의를 만들 수 있다.
각 컴파일 단위에는 이름 없는 네임스페이스가 하나씩 있다.
이름 없는 네임스페이스에서 정의된 모든 식별자는 컴파일 단위에 로컬이다.
다음과 같이 네임스페이스 이름이 없는 네임스페이스 그룹에 정의를 배치하여
이름 없는 네임스페이스에 정의를 배치한다:
namespace
{
Definition_1
Definition_2
.
.
.
Definition_Last
}
이름 없는 네임스페이스에서 한정자 없이 컴파일 단위의 어느 곳에서도 임의의 이름을 사용할 수 있다.
전체 예는 디스플레이 11.8, 11.9 및 11.10을 참조하라.
함정: 전역 네임스페이스와 이름 없는 네임스페이스를 혼동하다
전역 네임스페이스를 이름 없는 네임스페이스와 혼동하지 말자.
네임스페이스 그룹화에 이름 정의를 넣지 않으면 전역 네임스페이스에 있다.
이름 없는 네임스페이스에 이름 정의를 넣으려면
이름 없이 다음과 같이 시작하는 네임스페이스 그룹에 이름 정의를 넣어야 한다:
namespace
{
전역 네임스페이스의 이름과 이름 없는 네임스페이스의 이름은 모두 한정자 없이 접근할 수 있다.
그러나 전역 네임스페이스의 이름은 전역 범위(모든 프로그램 파일)를 갖지만
이름 없는 네임스페이스의 이름은 컴파일 장치에 로컬이다.
전역 네임스페이스와 이름 없는 네임스페이스 사이의 이러한 혼동은
코드를 작성할 때 그다지 발생하지 않는다.
전역 네임스페이스의 이름은 기술적으로 정확하지 않지만
"이름 없는" 것으로 생각하는 경향이 있기 때문이다.
그러나 코드를 논의할 때 혼동이 쉽게 발생할 수 있다.
팁: 이름 없는 네임스페이스가 static 한정자를 대체하다
C++의 이전 버전에서는 이름을 파일에 로컬로 만들기 위해 수식어 static을 사용했다.
static의 사용은 단계적으로 폐지되고 있으므로,
대신 이름이 없는 네임스페이스를 사용하여 컴파일 단위에 로컬로 이름을 만들어야 한다.
static의 사용은 클래스의 모든 개체가 공유하는 클래스 멤버를 만들기 위해
static을 사용하는 것과는 아무런 관련이 없다
(7장의 "Static 밈부" 하위 섹션에서 설명함).
따라서 static은 두 가지 이상의 의미로 사용되므로
단어의 한 가지 사용이 단계적으로 폐지되는 것이 좋을 것이다.
팁: 도움말 함수 숨기기
클래스에 대한 도움말 함수를 숨기는 데는 두 가지 좋은 방법이 있다.
함수를 클래스의 public 멤버 함수로 만들거나
클래스의 구현 파일에 대한 이름 없는 네임스페이스에 도움말 기능을 배치할 수 있다.
함수가 호출 객체를 자연스럽게 가져간다면 public 멤버 함수로 만들어야 한다.
호출 객체를 자연스럽게 가져가지 않는 경우 정적 멤버 함수로 만들거나
(예: 11.1 및 11.2의 DigitalTime::readHour in Displays)
구현 파일의 이름 없는 네임스페이스에 배치할 수 있다
(예: 11.8 및 11.9의 readHour in Displays)
도움말 함수가 호출 개체를 필요로 하지 않는 경우,
도움말 함수를 구현 파일의 이름 없는 네임스페이스에 배치하면
인터페이스와 구현을 개별 파일로 더 잘 분리할 수 있고
너무 많은 함수 이름 자격이 필요하지 않으므로 코드를 더 깨끗하게 만들 수 있다.
예를 들어, 디스플레이 11.9에서는 이름 없는 네임스페이스에 있기 때문에
readHour 함수 이름을 사용할 수 있고,
디스플레이 11.2에서는 DigitalTime::readHour 버전을 사용해야 한다.
이름 없는 네임스페이스가 C++ 규칙과 어떻게 상호 작용하는지는
흥미롭게도 같은 네임스페이스에서는 이름의 정의를 두 개 가질 수 없다.
각 컴파일 단위에는 하나의 이름 없는 네임스페이스가 있다.
컴파일 단위가 쉽게 겹칠 수 있다.
예를 들어 클래스에 대한 구현 파일과 클래스를 사용하는 응용 프로그램은
모두 보통 클래스에 대한 헤더 파일(인터페이스 파일)을 포함한다.
따라서 헤더 파일은 두 개의 컴파일 단위에 있으므로 두 개의 이름 없는 네임스페이스에 참여한다.
위험하게 들리기는 하지만,
각 컴파일 단위의 네임스페이스가 자체적으로 고려할 때
의미가 있는 한 일반적으로 문제가 발생하지 않는다.
예를 들어 헤더 파일의 이름 없는 네임스페이스에 이름이 정의되어 있다면
구현 파일이나 응용 프로그램 파일의 이름 없는 네임스페이스에는
이름이 다시 정의될 수 없다.
따라서 이름 충돌이 방지된다.
중첩 네임스페이스
namespace를 둥지로 만드는 것은 합법적이다. 중
첩된 네임스페이스에서 이름을 따올 때, 당신은 단순히 두 번의 자격을 갖는다.
예를 들어
namespace S1
{
namespace S2
{
void sample( )
{
.
.
.
}
.
.
.
} //S2
}//S1
팁: 어떤 네임스페이스 사양을 사용해야 할까?
코드가 Space라는 네임스페이스에 정의된 f라는 이름의 함수(또는 다른 항목)의 정의를 사용하도록 지정하는 세 가지 방법이 있다.
1. 당신은 아래 문장을 삽입할 수 있다.
using namespace theSpace;
2. 당신은 아래 문장을 삽입할 수 있다.
using theSpace::f;
3. 마지막으로 using 지시문을 완전히 생략할 수 있지만
항상 단순한 f 대신 Space::f를 사용하여 함수 이름을 지정할 수 있다.
어떤 양식을 사용해야 합니까?
세 가지 방법 모두 사용할 수 있으며, 당국은 그들이 선호하는 스타일로 무엇을 추천하는지에 대해 다르다.
그러나, 이름 공간의 완전한 가치를 얻으려면, 그 양식을 피하는 것이 좋다
using namespace theSpace;
그런 사용 명령어를 파일의 맨 앞에 배치하는 것은
C++의 이전 버전이 실제로 그랬던 것처럼
전역 네임스페이스에 모든 정의를 배치하는 것과 거의 다르지 않다.
따라서 이 접근 방식은 네임스페이스 메커니즘에서 아무런 가치도 얻지 못한다.
(하지만 그런 사용 명령어를 블록 안에 배치하는 경우 해당 블록에만 적용된다.
이것은 합리적이면서도 많은 권한이 옹호하는 또 다른 대안이다.)
두 번째 방법을 사용하는 것을 선호한다.
파일의 시작 부분에 다음과 같은 문장을 삽입한다:
using theSpace::f;
이렇게 하면 네임스페이스에 있지만 사용되지 않는 이름을 생략할 수 있다.
그러면 이름 충돌을 피할 수 있다.
또한 사용하는 이름을 잘 문서화할 수 있으며,
Space::f 형식의 표기법으로 이름을 지정하는 것만큼 지저분하지 않다.
파일이 서로 다른 위치에서 서로 다른 이름 공간을 사용하도록 구조화되어 있는 경우,
명령어를 사용하고 선언을 사용하는 것이 파일의 시작이 아니라
블록 내부에 배치하는 것이 바람직할 수 있다.
네임스페이스 S1 외부에서 샘플을 호출하려면 다음을 사용한다
S1::S2::sample( );
네임스페이스 S2 외부에서 샘플을 호출하지만 네임스페이스 S1 내에서 호출하려면 다음을 사용한다
S2::sample( );
또는 지침을 사용하여 적합한 지침을 사용할 수 있다.
'프로그래밍 공부 > OOP' 카테고리의 다른 글
| 13장 재귀 (1) | 2023.11.28 |
|---|---|
| 12장 스트림 및 파일 입출력 (1) | 2023.11.27 |
| 10장 포인터와 동적 배열 (1) | 2023.11.26 |
| 9장 문자열 (1) | 2023.11.25 |
| 8장 오버로딩, friend, 그리고 참조 연산자 (1) | 2023.11.25 |