[C++] 함수 오버로딩과 이름공간
REF : 윤성우의 열혈 C++
함수 오버로딩
- C언어에서는 함수의 이름만 이용해 호출 대상을 찾는다.
- C언어에서는 동일한 이름의 함수가 정의되면 컴파일 오류가 발생한다.
- C++은 함수의 이름과 매개변수를 확인하고 호출대상을 찾는다.
- C++에서는 매개변수의 선언형태가 다르면 동일한 이름의 함수 정의가 가능하다.
이를 함수 오버로딩(Function Overloading)
이라고 한다.
함수 오버로딩은 매개변수의 자료형 또는 개수가 다르다
는 조건을 만족해야한다.
매개변수의 디폴트 값
함수오버로딩의 형태로 다른 매개변수마다 다른 함수를 새로 만들어야하는 건 불필요한 시간이 든다.
매개변수의 디폴트 값을 지정해줌으로써 이를 보완할 수 있다.
C++에서는 함수 호출시 인자를 전달하지 않았을 때 사용할 디폴트 값을 선언할 수 있다.
int myfunc(int num=7) //매개변수가 없으면 num=7
{
return num+1;
}
#include <iostream>
int Adder(int num1=1, int num2=2)
{
return num1+num2;
}
int main()
{
std::cout<<Adder()<<std::endl;
std::cout<<Adder(5)<<std::endl;
std::cout<<Adder(3, 5)<<std::endl;
return 0;
}
위의 경우,
Adder()
는 num1=1, num2=2
Adder(5)
는 num1=5, num2=2
매개변수가 들어올 때, 왼쪽부터 채워진다는 것을 알 수 있다.
인라인 함수
프로그램 코드라인 안에 있는 함수
매크로함수
C에서 매크로함수가 정의되어있는 헤더가 있다.
예를 들어,
- 장점 : 매크로함수들은 전처리과정에서 매크로를 읽고, 내용을 바꾸는 행동만 하기때문에 실행속도에 이점이 있다.
- 단점 : 복잡한 함수는 정의하기가 어렵다.
매크로 함수의 장점을 유지하되,
단점을 극복하고자 나온게 인라인함수이다.
C++ 기반의 인라인함수 정의
-
예시
#include <iostream> inline int SQUARE(int x) { return x*x; } int main() { std::cout<<SQUARE(5)<<std::endl; std::cout<<SQUARE(12)<<std::endl; return 0; }
위 코드는 전처리 과정에서 아래와같이 바뀌게된다.
#include <iostream>
int main()
{
std::cout<<5*5<<std::endl;
std::cout<<12*12<<std::endl;
return 0;
}
위의 인라인함수의 문제는 자료형이 double
일때는 자료형의 손실이 일어난다는 것이다.
후에, C++의 템플릿 개념을 배우면 자료형에 의존적이지 않은 함수를 작성할 수 있다.
이름공간, namespace
특정 영역에 붙여놓은 이름을 의미한다.
한 프로그램 내에서 동일한 이름을 가지고, 동일한 매개변수형을 가진 함수는 컴파일 과정에서 오류를 일으키게된다.
이런 문제를 해결하기위해 함수의 이름을 조금 바꿔서 작성하든가 해야하는데, 협업을 할 때에 이런 규칙들을 따로 정하는데에는 불필요한 시간이 소요된다.
이를 위해, 이름공간
의 개념이 등장하게 됐다.
예시
namespace myukang
{
int test_func(int a)
{
return a;
}
}
namespace myukang42
{
int test_func(int a)
{
return a;
}
}
이름, 매개변수형까지 동일한 함수를 이름공간을 분리해서 선언할 수 있다.
위 코드를 실행하기 위해선
myukang::test_func();
myukang42::test_func();
위 처럼 이름공간::함수이름
으로 실행할 수 있다.
범위지정 연산자로 함수의 원형과 몸체를 구분하기
::
은 범위지정 연산자라고 불리고, 이름공간을 지정할 때 사용하는 연산자이다.
- C에선 함수의 원형(프로토타입)과 몸체부분을 분리해서 선언할 수 있다.
- 흔히 사용하는 헤더들에 함수의 프로토타입이 명시되어있는데, 헤더들을
include
함으로써 그 밑줄부터 헤더파일에 프로토타입이 있는 함수들을 사용할 수 있는 것이다.
C++의 이름공간을 기반으로 원형과 몸체를 구분하기 위해선 위의 범위지정 연산자를 잘 사용해야한다.
-
원형과 몸체의 구분
#include <iostream> namespace BestComImpl { void SimpleFunc(void); } namespace ProgComImpl { void SimpleFunc(void); void DoubleFunc(void); } void BestComImpl::SimpleFunc(void) { std::cout<<"BestCom 함수"<<std::endl; } void ProgComImpl::SimpleFunc(void) { std::cout<<"ProgCom 함수"<<std::endl; } void ProgComImpl::DoubleFunc(void) { std::cout<<"ProgCom의 DoubleFunc 함수"<<std::endl; SimpleFunc(); }
SimpleFunc
의 몸체부분을 정의할때, 앞에 이름공간을 지정해줘야 컴파일러가 해당 함수가 어느 이름공간에 속하는 함수인지 알 수 있다.DoubleFunc
부분을 보면, 내부에SimpleFunc
가 이름공간 없이 호출된다. 같은 이름공간에서는 이름공간을 명시해줄 필요가 없다.
이름공간의 중첩
이름공간은 다른 이름공간 안에 삽입될 수 있다.
#include <iostream>
namespace Parent
{
int num = 1;
namespace Daughter
{
int num = 2;
}
namespace Son
{
int num = 3;
}
}
int main()
{
std::cout<<Parent::num<<std::endl;
std::cout<<Parent::Daughter::num<<std::endl;
std::cout<<Parent::Son::num<<std::endl;
return 0;
}
같은 이름의 변수라도, 이름공간이 다르면 충돌하지 않는다.
std::
iostream 헤더를 이용해 출력할때, std::
를 계속 사용해왔다.
std::cout
std::cin
std::endl
std 공간에 선언되어있는 cout
, cin
, endl
을 불러와서 사용해왔던 것이다.
실제로, iostream의 헤더파일을 뜯어보면 아래와 같이 std가 namespace로 구현되어 있는 것을 알 수 있다.
namespace std { inline namespace __1 {
extern __attribute__ ((__visibility__("default"))) istream cin;
extern __attribute__ ((__visibility__("default"))) wistream wcin;
extern __attribute__ ((__visibility__("default"))) ostream cout;
extern __attribute__ ((__visibility__("default"))) wostream wcout;
extern __attribute__ ((__visibility__("default"))) ostream cerr;
extern __attribute__ ((__visibility__("default"))) wostream wcerr;
extern __attribute__ ((__visibility__("default"))) ostream clog;
extern __attribute__ ((__visibility__("default"))) wostream wclog;
} }
using을 이용한 이름공간의 명시
-
이름공간 안의 모든 것 사용하기
iostream
헤더를 사용하다보면 함수를 사용할때마다std
로 이름공간을 명시해줘야한다.
여간 귀찮은게 아닌데,using을 사용해 이를 생략할 수 있다.
#include <iostream> using namespace std; int main() { int num = 20; cout<<"Hello world!"<<endl; cout<<"Hello "<<"World!"<<endl; cout<<num<<endl; cout<<' '<<3.14<<endl; return 0; }
using namespace std;
이 키워드는std
공간 안의 모든 것에 대해 이름공간 지정을 생략하겠다는 선언이다.
-
조금 더 안전하게 사용하기 그런데, 이렇게 std안의 모든 것에 이름공간 지정을 생략하면
충돌가능성이 높아진다.
그렇기에, 아래와 같이 사용할 함수에 대해서만 적용해주면 충돌가능성을 낮출 수 있다.
#include <iostream> using std::cin; using std::cout; using std::endl; int main() { int num = 20; cout<<"Hello world!"<<endl; cout<<"Hello "<<"World!"<<endl; cout<<num<<endl; cout<<' '<<3.14<<endl; return 0; }
-
이름 공간의 별칭 지정
namespace AAA
{
int num3;
namespace BBB
{
namespace CCC
{
int num1;
int num2;
}
}
}
위의 경우처럼, 이름공간이 중첩되어 AAA::BBB::CCC:num1= 20;
처럼 깊게 접근해야할 때,
namespace ABC=AAA:BBB:CCC
처럼 이름에 별칭을 줄 수 있다.
-
전역변수와 지역변수가 동일한 이름으로 사용될때
전역변수는 Data 공간에, 지역변수는 Stack 공간에 할당된다.
함수를 실행하면 매개변수가 선언되면서 스택 공간에 매개변수가 저장되는데,
아래 코드는 스택 공간의 변수 val을 인식하고 23이된다.
int val = 100;
int SimpleFunc(void)
{
int val=20;
val+=3;
}
이땐, 범위지정연산자 ::
를 사용해 전역변수에 접근할 수 있다.
int val = 100;
int SimpleFunc(void)
{
int val=20;
val+=3;
::val += 7;
}
댓글남기기