C++/C++ 기초

[C++] 함수 중복과 디폴트 매개변수 (Function overloading and default parameter)

Song 컴퓨터공학 2023. 7. 11. 14:35

이번 포스팅은 함수 오버로딩과 디폴트 매개변수에 대해 알아보는 포스팅입니다. 오버로딩은 OOP 의 3가지 특성 중 polymorphism, 다형성(프로그램 언어 각 요소들(상수, 변수, 식, 객체, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질) 의 구현입니다.

 

 

 함수 오버로딩 (function overloading)

 

C++에서는 C와 달리 이름이 같은 함수를 여러 개 만들 수 있습니다. 같은 이름의 함수를 중복하여 정의하는 것을 함수 중복, 함수 오버로딩이라고 합니다. 예를 들어 우리가 2개의 숫자를 더해주는 함수와 3개의 숫자를 더하는 함수를 정의한다고 생각해봅시다.

int sum2(int a, int b){ return a + b; }
int sum3(int a, int b, int c) { return a + b + c; }

// -----------------------------------------------

int sum(int a, int b){ return a + b; }
int sum(int a, int b, int c) { return a + b + c; }

int main(){
	cout << sum(1,2) << sum(1, 2, 3) << endl;
    ...
}

C언어로 프로그래밍 중이라면 함수의 이름이 겹치면 안되기 때문에 위처럼 함수의 이름이 다르게 선언해줘야 합니다. 이렇게 되면 코드의 가독성이 떨어질 뿐만 아니라 코드를 디버깅하기도 어려워지겠죠. C++ 컴파일러는 아래처럼 같은 이름의 함수를 선언하고 사용하는 것이 가능합니다.

 

중복 함수에는 특정 조건이 있습니다. 중복된 함수의 이름이 같아야 하고, 중복된 함수들은 매개변수의 타입이 다르거나 개수가 달라야 합니다. 이 때 리턴 타입은 함수 중복과 상관 없습니다. 리턴 타입이 다르더라도 매개변수와 함수 이름이 같다면 에러가 발생합니다. 함수의 원형에 명시되는 매개변수 리스트함수 시그니처(function signature) 라고도 부릅니다.

 

즉 함수의 오버로딩은 서로 다른 시그니처를 갖는 여러 함수를 같은 이름으로 정의하는 것입니다.

#include <iostream>
using namespace std;

int big(int a, int b) {	return (a > b) ? a : b; }

int big(int a[], int size) {
	int res = a[0];
	for (int i = 1; i < size; i++) if (a[i] > res) res = a[i];
	return res;
}

int main()
{
	int array[5] = { 1, 9, -2, 8, 6 };
	cout << big(2, 3) << " ";
	cout << big(array, 5) << endl;
}

// 출력 : 3 9

이 뿐만 아니라 이미 앞에서 여러번 코드를 봤던 클래스의 생성자 구현도 함수 오버로딩의 일종입니다.

 

 

 

 디폴트 매개변수 (default parameter)

 

클래스에서도 생성자가 따로 없는 경우 객체를 생성할 때 기본 생성자, 즉 디폴트 생성자가 호출되었습니다. 마찬가지로 함수가 호출될 때 매개 변수에 값이 넘어오지 않는다면, 미리 정해진 디폴트 값을 받도록 매개 변수를 선언할 수 있고, 이 때의 매개 변수를 디폴트 매개변수(default parameter) 혹은 기본 매개 변수 라고 부릅니다.

 

디폴트 매개변수를 설정할 때 다음의 사항들에 주의해야합니다.

 

  1. 디폴트 매개변수는 함수의 원형에만 지정할 수 있다.
  2. 디폴트 매개 변수는 가장 오른쪽부터 시작하여 순서대로만 지정할 수 있다. (가장 뒤에 몰려서 선언해야한다.)
  3. 가운데 매개 변수들만 별도로 디폴트 인수를 지정할 수 없다. (디폴트 매개변수가 가장 뒤에 몰리는 것과 동일한 내용) 

 

2번과 3번 같은 주의사항이 있는 이유는, 컴파일러가 함수 호출문에 나열된 실인자 값들을 앞에서부터 순서대로 함수의 매개 변수에 전달하고, 나머지를 디폴트 값으로 전달하기 때문입니다. 위와 같은 규칙을 간단한 예제로 확인해보면

void msg(int id, string text = "Hello");

msg(10); // id에 10, text 에 "Hello" 전달
msg(20, "Good Morning"); // id에 20, text 에 "Good Morning" 전달
msg(); // 컴파일 오류
msg("Good Morning"); // 컴파일 오류

void calc(int a, int b = 5, int c); // 컴파일 오류
void sum (int a = 0, int b, int c); // 컴파일 오류
void calc(int a, int b = 5, int c = 0); // 컴파일 성공

string text 에만 디폴트 인수가 있기 때문에 그냥 msg() 로 호출하면 id 값이 없기 때문에 오류가 발생하고, 아래는 2번, 3번 규칙이 성립되지 않아 컴파일 오류가 발생하게 됩니다.

 

디폴트 매개 변수의 최대 장점은, 함수 중복을 간소화 할 수 있다는 점입니다. 예를 들어 클래스의 생성자를 확인해봅시다.

 

class Circle{
	int radius;
	...
public:
	Circle() { radius = 1; }
	Circle(int radius) { this->radius =radius; }
	...
};

//--------------디폴트 매개 변수 사용------------------
class Circle{
	int radius;
	...
public: 
	Circle(int radius = 1) { this->radius = radius; }
	...
}


//---------디폴트 매개 변수 + 멤버 이니셜라이저---------

class Circle{
	int radius;
	...
public: 
	Circle(int radius=1): radius(radius){ }
	...
}

아직 멤버 이니셜라이저는 포스팅 안했기 때문에 모르시는 내용이면 넘어가도 됩니다. 따로 포스팅을 할 예정입니다.

2번째를 위주로 보면, 2번 선언해야했던 생성자를 디폴트 매개변수를 이용하면 한번만 선언해도 동일한 동작을 합니다.