2024. 4. 11. 13:36ㆍ[C++]/C++ 언어 기초
🍃 이름 공간의 등장 배경
프로그램이 대형화되어 가면서 이름의 충돌문제가 등장했다.
세 개의 회사에서 은행관리 시스템을 개발하는데, 이들은 일을 구분하여 독립적으로 진행하고 6개월 뒤 모여서 하나의 프로젝트를 완성하기로 약속했다.
6개월이라는 시간이 지난 뒤, 각 회사가 구현한 모듈을 하나로 묶고 부족한 부분을 완성할 때가 되었다. 그러나 문제가 생겼다. 1번 회사에서 정의한 함수와 2번 회사에서 정의한 함수의 이름이 같이 "이름 충돌"이 난 것이다.
함수의 이름을 변경하기에는 시간이 부족하다. 게다가 3번 회사에서 정의한 이름까지 충돌이 나게 되었다.
- 그렇다면 무엇이 해결책이었을까?
- 프로젝트 진행 전 함수 및 변수의 이름을 모두 정해 이름충돌이 발생하지 않게 하는 것이 해결책인가?
- 이는 근본적인 해결책이 되지 못한다.
그래서 C++의 표준에서는 '이름공간(namespace)' 이라는 문법을 정의해 이에 대한 근본적인 해결책을 제시한다.
🍃이름공간의 기본원리
- 한 집에 철수라는 이름을 가진 사람이 두 명 산다면, 철수야!하고 불렀을 때 누구를 부르는지 모른다.
- 하지만 202호에 사는 철수야! 라는 식으로 구분을 지어준다면 문제가 발생하지 않는다.
- 이것이 이름공간의 기본 원리이다.
namespace1.cpp
#include <iostream>
namespace BestComImpl{
void SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
}
}
namespace ProgComImpl{
void SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
}
int main(void) {
BestComImpl::SimpleFunc();
ProgComImpl::SimpleFunc();
return 0;
}
위의 예제를 보면 아래와 같이 이름공간을 마련한 것을 볼 수있다.
namespace BestComImpl{
void SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
}
}
위를 보면 BestComImpl이라는 이름공간을 마련했고, 이 안에 함수 SimpleFunc를 정의했다. 따라서 이 함수는 'BestComImpl::SimpleFunc()'이라고 지칭한다.
namespace ProgComImpl{
void SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
}
위를 보면 ProgComImpl이라는 이름공간을 마련했고, 이 안에 함수 SimpleFunc를 정의했다. 따라서 이 함수는 'ProgComImpl::SimpleFunc()'이라고 지칭한다.
예제에서 사용된 연산자 :: 을 가리켜 '범위지정 연산자(scope resolution operator)'라 한다.
🍃 이름공간 기반의 함수 선언과 정의의 구분
- 함수는 선언과 정의를 분리하는 것이 일반적이다.
- '함수의 선언'은 헤더파일에, '함수의 정의'는 소스파일에 저장하는 것이 일반적이다.
namespace2.cpp
#include <iostream>
namespace BestComImpl{
void SimpleFunc(void);
}
namespace ProgComImpl{
void SimpleFunc(void);
}
int main(void) {
BestComImpl::SimpleFunc();
ProgComImpl::SimpleFunc();
return 0;
}
void BestComImpl::SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
}
void ProgComImpl::SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
BestCom이 정의한 함수
ProgCom이 정의한 함수
namespace3.cpp
#include <iostream>
namespace BestComImpl{
void SimpleFunc(void);
}
namespace BestComImpl{
void PrettyFunc(void);
}
namespace ProgComImpl{
void SimpleFunc(void);
}
int main(void) {
BestComImpl::SimpleFunc();
return 0;
}
void BestComImpl::SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
PrettyFunc(); //동일 이름공간
ProgComImpl::SimpleFunc(); //다른 이름공간
}
void BestComImpl::PrettyFunc(void) {
std::cout<<"So Pretty!!"<<std::endl;
}
void ProgComImpl::SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
BestCom이 정의한 함수
So Pretty!!
ProgCom이 정의한 함수
namespace BestComImpl{
void SimpleFunc(void);
}
namespace BestComImpl{
void PrettyFunc(void);
}
- 둘 이상의 영역에 나누어서 함수를 추가할 수 있다.
- SimpleFunc, PrettyFunc는 BestComImpl이라는 동일한 이름공간에 존재한다.
🍃 이름공간의 중첩
이름공간은 다른 이름공간 안에 삽입될 수 있다. 다음과 같은 형태로 말이다.
namespace Parent {
int num=2;
namespace SubOne {
int num=3;
}
namespace SubTwo {
int num=4;
}
}
- 위의 코드에는 num이 3개가 존재한다. 하지만 각각이 선언된 이름공간이 다르기에 이름충돌이 일어나지 않았다.
- 다음의 문장을 실행하면 순서대로 2, 3, 4가 출력된다.
std::cout << Parent::num <<std::endl;
std::cout << Parent::SubOne::num <<std::endl;
std::cout << Parent::SubTwo::num <<std::endl;
🍃 std::cout, std::cin, std::endl
지금까지 콘솔 입출력을 진행할 때 std::cout, std::cin을 사용해 왔다. 그런데 이제 ::연산자의 의미를 이해했기 때문에 다음 세가지가 뜻하는 바를 설명할 수 있다.
std::cout
std::cin
std::endl
순서대로,
"이름공간 std에 선언된 cout"
"이름공간 std에 선언된 cin"
"이름공간 std에 선언된 endl"
따라서 다음과 같은 이름공간의 구성을 생각할 수 있다.
namespace std {
cout . . . .
cin . . . .
endl . . . .
}
- 헤더파일 < iostream > 에 선언되어 있는 cout, cin 그리고 endl은 이름공간 std안에 선언되어 있다는 결론을 내릴 수 있다.
- 이렇듯 이름충돌을 막기 위해, C++ 표준에서 제공하는 다양한 요소들은 이름공간 std안에 선언되어 있다.
🍃 using을 이용한 이름공간의 명시
- cout, cin, endl을 참조할 때마다 std::를 앞에 붙여야 하는 이유를 알게 되었다.
- 하지만 쓸 때마다 붙이는 것은 귀찮은 일이다.
- 이에 using 키워드를 사용할 수 있다.
UsingDcl1.cpp
#include <iostream>
namespace Hybrid {
void HybFunc(void) {
std::cout<<"So Simple function!"<<std::endl;
std::cout<<"In namespace Hybrid!"<<std::endl;
}
}
int main(void) {
using Hybrid::HybFunc;
HybFunc();
return 0;
}
So Simple function!
In namespace Hybrid!
- 이 예제에서는 Hybrid라는 이름공간 안에 선언된 함수를 범위지정 없이 그냥 호출할 수 있음을 보이고 있다.
using Hybrid::HybFunc;
- 키워드 using을 이용해 '이름공간 Hybrid에 정의된 HybFunc를 호출할 때에는, 이름공간을 지정하지 않고 호출하겠다!'는 것을 명시(선언)하고 있다.
HybFunc();
- 이렇게 using선언을 통해 이름공간의 지정 없이 HybFunc() 함수를 호출하고 있다.
- 이때, 위 예제에서는 using 선언이 main 함수 내에 존재하는데, 이러한 경우 지역변수의 선언과 마찬가지로 선언된 이후부터 효력을 발휘하며, 선언된 지역을 벗어나면, 선언의 효력은 잃게 된다.
- 따라서 프로그램 전체영역에 효력을 미치게 하려면 전역변수와 마찬가지로 함수 밖에 선언을 해야한다.
UsingDcl2.cpp
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main(void)
{
int num=20;
cout<<"Hello World!"<<endl;
cout<<"Hello "<<"World!"<<endl;
cout<<num<<' '<<'A';
cout<<' '<<3.14<<endl;
return 0;
}
Hello World!
Hello World!
20 A 3.14
- 예제를 보면 using 선언을 함수 밖에 전역의 형태로 삽입했다. 따라서 이제부터는 cin, cout, endl의 사용에 있어 이름공간의 지정이 불필요하고, 이를 통해 코드의 구성이 한결 간단해졌다.
- 만약에 위 예제에서 보이는 것처럼 일일이 using 선언을 하기가 귀찮다면, '이름공간 std에 선언된 모든 것에 대해 이름공간 지정의 생략'을 명령할 수 있다.
using namespace std;
- 일일이 std 이름공간에 있다는 것을 지정하는 게 너무 귀찮고 코드가 지저분해 보였는데 드디어 한결 편안한 코딩을 할 수 있게 되었다.
UsingDcl3.cpp
#include <iostream>
using namespace std;
int main(void)
{
int num=20;
cout<<"Hello World!"<<endl;
cout<<"Hello "<<"World!"<<endl;
cout<<num<<' '<<'A';
cout<<' '<<3.14<<endl;
return 0;
}
- 우리가 볼 때 위 예제의 using namespace 선언이 매우 매력적으로 보일거다.
- 하지만 너무 빈번한 using namespace의 선언은 이름의 충돌을 막기위한 이름공간의 선언을 의미 없게 만든다.
- 따라서 상황을 판단해서 적절히 혼용하도록 제한적으로 사용할 필요가 있다.
🍃 이름공간의 별칭 지정
이름공간이 중첩되면서까지 과도하게 사용되는 경우는 드물지만, 과도하게 사용되었을 때의 예를 들어보자.
NameAlias.cpp
#include <iostream>
using namespace std;
namespace AAA {
namespace BBB {
namespace CCC {
int num1;
int num2;
}
}
}
int main(void) {
AAA::BBB::CCC::num1=20;
AAA::BBB::CCC::num2=30;
namespace ABC = AAA::BBB::CCC;
cout<<ABC::num1<<endl;
cout<<ABC::num2<<endl;
return 0;
}
20
30
- num1, num2에 접근할 때 다음과 같이 접근하게 되면 불편하다.
AAA::BBB::CCC::num1=20;
AAA::BBB::CCC::num2=30;
- 이러한 경우, AAA::BBB::CCC에 별칭을 줄 수 있다.
namespace ABC = AAA::BBB::CCC;
- 이렇게 되면 num1, num2에 다음과 같이 접근이 가능하다.
ABC::num1=10;
ABC::num2=20;
🍃 범위지정 연산자(Scope Resolution Operator)의 또 다른 기능
- 지역변수의 이름이 전역변수의 이름과 같은 경우, 전역변수는 지역변수에 의해 가려진다.
int val=100;
int SimpleFunc(void) {
int val=20; // 지역변수
val+=3; // 지역변수 val의 값 3 증가
}
- 위의 코드에서 보이듯이 SimpleFunc내에서 전역변수와 이름이 동일한 지역변수 val이 선언되었으므로 val+=3;은 전역변수 val이 아닌, 지역변수 val의 값을 3 증가시킨다.
- 그렇다면, 전역변수 val에 접근하려면 '범위지정 연산자'를 사용하면 된다.
int val=100;
int SimpleFunc(void) {
int val=20; // 지역변수
val+=3; // 지역변수 val의 값 3 증가
::val+=7; // 전역변수 val의 값 7 증가
}
'[C++] > C++ 언어 기초' 카테고리의 다른 글
6. const, 메모리 공간, Call-by-value & Call-by-reference (0) | 2024.04.11 |
---|---|
01-06 은행계좌프로그램 실습 1단계 (0) | 2024.04.11 |
4. [C++] 인라인(inline) 함수 (0) | 2024.04.11 |
3. [C++] 매개변수의 디폴트 값(Default Value) (0) | 2024.04.11 |
2. [C++] 함수 오버로딩 (0) | 2024.04.11 |