디폴트 인수
디폴트는 기본값, 즉 내정된 값이며 별다른 지정이 없을때 적용되는 값이다.
디폴트 인수는 함수의 선언부에 = 기호와 기본값이 정의되어있는 인수이다. 호출부에 별다른 지정이 없으면 기본값을 알아서 적용한다. 이때 값을 명시적으로 전달하면 기본값은 무시된다.
#include <stdio.h>
int GetSum(int from, int to, int step = 1, int base = 0);
int main()
{
printf("%d\n", GetSum(1, 10));
printf("%d\n", GetSum(1, 10, 2));
printf("%d\n", GetSum(1, 10, 2, 10));
}
int GetSum(int from, int to, int step/*=1*/, int base /*=0*/)
{
int sum = base;
for (int i = from; i <= to; i += step)
{
sum += i;
}
return sum;
}

함수의 인수가 많으면 변화를 줄 여지가 많아서 활용성이 증가한다. 하지만 웬만하면 바꾸지 않는 무난한 값까지 인수로 받으면 호출할때 일일이 전달해야 하니 불편하다. 디폴트 인수를 생략해도 상관없는 인수에 대해 기본값을 지정하여 활용성과 편의성을 동시에 만족시키는 기법이다.
디폴트 인수는 몇가지 규칙이 있다.
- 디폴트 인수는 함수 원형에 지정하며 함수 정의부에는 지정하지 않는다. 정의부에는 인수 목록만 적거나 기본값을 주석처리해서 기본값이 있다는 표시만 해두는게 좋다.
- 디폴트 인수는 오른쪽부터 순서대로 지정할 수 있으며 가운데 인수는 지정할 수 없다. 따라서 생략 가능한 인수를 뒤쪽에 배치해야한다.
- 디폴트 인수를 가진 함수를 호출할때도 뒤쪽의 인수만 생략할 수 있으며 중간의 한 인수를 생략할 수 없다.
int GetSum(int from, int to = 10, int step = 1, int base = 0); // 가능
int GetSum(int from, int to = 10, int step, int base = 0); // 불가능
int GetSum(int from = 1, int to = 10, int step = 1, int base = 0); // 가능
int GetSum(int from = 1, int to, int step = 1, int base = 0); // 불가능
GetSum(1, 10,, 200) // 불가능
함수 오버로딩
명칭은 중복되면 안되는 것이 원칙이며, 같은 이름의 변수를 두 개 선언할 수 없다.
그러나 함수는 예외적으로 인수열이 다르면 같은 이름으로 중복 정의(오버로딩)가 가능하다.
동작은 같되 인수의 형식이나 구현 방식이 약간 다른 함수는 이름이 같아도 무관하다.
#include <stdio.h>
int Add(int a, int b);
int Add(int a, int b, int c);
double Add(double a, double b);
int main()
{
printf("1 + 2 = %d\n", Add(1, 2));
printf("3 + 4 + 5 = %d\n", Add(3, 4, 5));
printf("1.414 + 2.54 = %f\n", Add(1.414, 2.54));
}
int Add(int a, int b)
{
return (a + b);
}
int Add(int a, int b, int c)
{
return (a + b + c);
}
double Add(double a, double b)
{
return (a + b);
}

오버로딩은 명칭은 중복되지 않는 것이 원칙이며, 같은 이름의 변수를 두 개 선언할 수 없다. 그러나 함수는 예외적으로 인수열이 다르면 같은 이름으로 중복정의(overloading)이 가능하다. 동작은 같되 인수의 형식이나 구현 방식이 다른 함수는 이름이 같아도 오류가 뜨지않고 정상적으로 프로그램 구동이 가능하다.
- 오버로딩(중복정의) : 함수는 인수열이 다르면 같은 이름으로 중복정의(overloading)가능하다.
- 오버라이딩(재정의) : 상속에서 쓰는 개념으로 나중에 알아보도록 하자.
매개변수의 타입이나 개수가 다르면 사용가능.
함수이름과 매개변수가 같은데 출력타입만 다를때는 오버로딩이 불가능하다.
C에서는 컴파일러가 함수를찾을때는 함수이름으로만 찾기때문에 오버로딩이 불가능하다.
C++은 함수를 찾는 규칙이 함수이름 + 매개변수로 찾기때문에 오버로딩이 가능하다.
// 함수 오버로딩
#include <stdio.h>
int Add(int a, int b);
int Add(int a, int b, int c);
double Add(double a, double b);
int main()
{
printf("1 + 2 = %d\n", Add(1, 2));
printf("3 + 4 + 5 = %d\n", Add(3, 4, 5));
printf("1.414 + 2.54 = %f\n", Add(1.414, 2.54));
}
int Add(int a, int b)
{
return (a + b);
}
int Add(int a, int b, int c)
{
return (a + b + c);
}
double Add(double a, double b)
{
return (a + b);
}

이 예제는 세 개의 Add 함수를 정의하고, 두 인수를 더한다는 동작은 같지만 인수의 타입이나 개수가 모두 다르다.
함수 이름은 같지만 인수열이 달라서 구별이되고, 이때 컴파일러는 호출부의 인수를 보고 호출할 함수를 찾는다.
함수를 이름만으로 구분하는 C에서는 동작이 같아도 함수명을 각각 다르게 작성해야하는 점과 차이점이 있다.
C++은 인수로 구분가능하면 함수에 같은 이름을 줄 수 있고, 호출부의 실인수 개수와 타입에따라 적당한 함수를 골라주니 편리하다. 만약 실인수와 정확히 일치하는 함수가 없으면 산술 변환하여 함수를 찾는다.
short a = 1, b = 2, c
c = Add(a,b);
두 개의 short 값을 인수로 전달했는데 Add(short, short) 함수는 없지만 short가 int형으로 자동 변환할 수 있으므로 Add(int, int)가 호출된다.
만약 일치하는 함수도없고 산술변환도 불가능하면 에러로 처리된다. Add("one", "two"); 나 Add(2.34, 5) 처럼 정수나 실수를 넣어야하는데 문자열을 넣거나, (실수, 정수)세트처럼 산술변환이 모호한 경우도 에러처리된다.
매개변수에 디폴트 인수를 사용할때 예를 들어보자.
#include <iostream>
//int func(int a)
//{
// return a;
//}
int func(int a, int b = 0) // 디폴트 매개변수를 사용할때는 조심해야함.
{
return a + b;
}
int main()
{
func(10);
return 0;
}

위 예제의 경우 함수의 정의가
첫번째는 매개변수가 하나인 경우,
두번째는 매개변수가 2개인데 두번째 인수가 디폴트 인수를 사용한 경우이다.
이렇게 두개의 모호한 함수가 존재할때 컴파일러가 뭘 호출할지 몰라서 오류가뜬다.
따라서 디폴트 매개변수를 사용할때는 조심해서 사용해야한다.
다음 예제역시 포인터를 사용하는 경우인데 에러는 아니지만 위험한 경우이다.
// 파일이름 : ptroverload.cpp
#include <stdio.h>
void sub(int a)
{
printf("int : %d\n", a);
}
void sub(int* a)
{
printf("pointer:%p\n", a);
}
int main()
{
int i = 1234;
int* pi = &i;
sub(i);
sub(pi);
sub(NULL); // NULL은 주소값을 가지고있는데 지금 코드에서는 컴파일러가 int로 인식했음
sub((int*)NULL); // NULL의 강제 형변환
// NULL은 정수값 0을 나타내는데 아무것도 가리키지않는 주소를 저장할 수 있는 포인터다.
// 라고 하기위해서는 NULL도 강제형변환(주소임을 나타내는)을 해줘야함.
// 오버로딩은 편하긴한데 가급적이면 중복정의를 쓰지않는 것이 좋다.
}

위 예제는 정수형을 받는 sub 함수와 정수형 포인터를 받는 sub 함수가 중복 정의되어있다.
sub(i), sub(pi)에 대해 호출할 함수는 명확하지만 sub(NULL)이 애매해진다.
NULL은 정수 0으로 볼 수도 있고 NULL포인터로 볼 수 있는데 이때 컴파일러는 NULL을 정수로 해석한다.
sub(NULL)에 대해 포인터가 호출되는 것을 관찰하려면 sub((int*)NULL);로 캐스팅해서 호출해야한다.
오버로딩은 편리하기는한데 애매한 경우 엉뚱한 함수가 호출되는 단점 또한 존재한다.
같은 이름으로 함수를 중복 정의할 수 있는 이유는 인수열을 보고 호출할 함수를 결정할 수 있기 때문인데, 호출부만으로 어떤 함수를 선택할지 명확히 구분되지 않는 모호한 경우는 오버로딩할 수 없다.
참고로 const는 오버로딩일때는 인수가 같거나 매개변수 개수가 같아도 오버로딩이 가능하다.
인라인 함수
함수는 반복된 동작을 하나의 틀로 정의해 두고 필요할 때마다 호출해 사용하는 것이다. 다음 예제의 randfunc 함수는 인수로 전달된 n보다 작은 난수 하나를 생성하여 돌려주는 예제이다.
// 파일이름 : randfunc.cpp
#include <stdio.h>
#include <stdlib.h>
inline int randfunc(int n)
{
return rand() % n;
}
int main()
{
int i, j, k;
i = randfunc(10);
j = randfunc(100);
k = randfunc(50);
printf("난수 = %d, %d, %d", i, j, k);
}
함수는 호출할때마다 분기가 일어나며 이 과정에서 내부적으로 스택 프레임이 생성되고 해제하는 과정을 거친다.
이 과정은 호출과정에서 비용이 발생한다고 볼 수 있다.
함수가 거대하고 많은 일을 한다면 이 비용이 덜 부담스럽지만 난수 하나를 생성하는 간단한 작업을 하는데는 너무 오랜 시간이 걸린다.
이럴때는 함수로 분기하지말고 호출부에 해당 코드를 직접 삽입하는 것이 더 빠르다.
이런식으로 동작하는 함수를 인라인(Inline) 함수라고 부른다.
분기하지 않고 함수 본체의 코드를 바로 새겨 넣어 버림으로써 호출의 부담을 제거하는 기법이다.
함수를 인라인으로 만드려면 함수 선언문 앞에 inline 키워드를 붙여주면 된다.