업데이트:

태그: ,

카테고리:



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에서 매크로함수가 정의되어있는 헤더가 있다.
예를 들어, 헤더가 있는데, va_start, va_arg, va_end가 그 예시이다.

  • 장점 : 매크로함수들은 전처리과정에서 매크로를 읽고, 내용을 바꾸는 행동만 하기때문에 실행속도에 이점이 있다.
  • 단점 : 복잡한 함수는 정의하기가 어렵다.

매크로 함수의 장점을 유지하되,
단점을 극복하고자 나온게 인라인함수이다.


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;
}



댓글남기기