#4 C++ 함수 (인라인함수, 디폴트인자, 함수 오버로딩)
1. 인라인 함수
1.1 인라인 함수란?
inline키워드를 사용하면, 함수 호출 시 발생하는 함수 호출 오버헤드를 줄이기 위해 컴파일러가 함수 호출을 제거하고 함수의 코드를 직접 호출 위치에 삽입하는 방식이다.이를 통해 함수 호출 없이도 마치 함수의 코드를 직접 사용하는 것과 같은 효과를 낸다.
1.2 문제상황: 함수 호출에 따른 오버헤드가 심각한 사례
#include <iostream>
using namespace std;
int odd(int x) {
return (x % 2); // x를 2로 나눈 나머지를 리턴 (홀수이면 1, 짝수이면 0)
}
int main() {
int sum = 0;
// 1부터 10000까지의 홀수의 합 계산
for (int i = 1; i <= 10000; i++) {
if (odd(i)) // odd 함수가 10000번 호출됨
sum += i;
}
cout << sum;
}
- 10000번 함수 호출: for 루프에서 1부터 10000까지 odd 함수가 반복적으로 호출되고 있다. 즉, odd(i)가 10000번 실행되면서 함수 호출에 따른 오버헤드(추가적인 시간 및 자원 소모)가 발생한다.
- 함수 호출 오버헤드:
함수 호출은 단순한 연산보다 시간이 더 오래 걸린다. 특히 반복문 안에서 불필요한 함수 호출이 계속되면 성능에 큰 영향을 줄 수 있다.
해결책: 간단한 함수는 인라인함수로 사용하라.
1.3 인라인 함수의 사용 예
1.3.1 인라인 함수를 적용한 코드
#include <iostream>using namespace std;
inline int odd(int x) { // inline 키워드 사용
return (x % 2);
}
int main() {
int sum = 0;
for (int i = 1; i <= 10000; i++) {
if (odd(i)) // 함수 호출 대신, 함수의 코드가 직접 삽입됨
sum += i;
}
cout << sum;
}
위 코드에서 inline 키워드를 추가하여 odd 함수를 인라인 함수로 만들었다.
**컴파일러는 함수 호출 대신 함수 코드를 해당 위치에 직접 삽입**하게 된다. 이는 함수 호출 오버헤드를 줄여 성능을 향상시킬 수 있다.
1.3.2 컴파일러의 처리 방식
컴파일러는 inline 함수를 처리할 때 실제로는 함수 호출을 없애고, 함수의 코드를 호출 위치에 직접 삽입하게 된다. 즉, 다음과 같이 컴파일된다:
→ 실제로 내부에서 인라인함수가 동작하는 방식
#include <iostream>using namespace std;
int main() {
int sum = 0;
for (int i = 1; i <= 10000; i++) {
if ((i % 2)) // 함수 호출 대신, 코드가 직접 삽입됨
sum += i;
}
cout << sum;
}
1.3.3 인라인 함수의 제약 사항
- 컴파일러에게 주는 요청 메시지: inline은 컴파일러에게 “이 함수를 인라인으로 처리하라”는 요청을 의미하지만,
실제로 인라인 여부는 컴파일러가 결정한다. - 인라인 처리 불가능한 경우: 다음과 같은 경우 인라인 처리가 불가능할 수 있다:
- 함수가 너무 긴 경우
- 함수에 재귀 호출, 반복문이 포함된 경우
- 함수에 static 변수 또는 goto 문이 있는 경우
매크로와 인라인 함수의 차이점
1.4 인라인 함수 vs 매크로 함수
1.4.1 매크로 함수
- 매크로 함수는 선행처리기(preprocessor)에 의해 처리되는 방식이다. 매크로는 단순히 문자열 대체 방식으로 작동하며, 코드가 컴파일되기 전에 해당 매크로가 정의된 부분을 모두 대체한다.
- 예시:매크로
MUL(10+20, 30+40)은 문자 그대로10+20 * 30+40으로 대체되어,
⇒ 연산 우선순위에 문제가 발생할 수 있다.
⇒ 인자의 형검사를 하지 않는다.
#define MUL(x, y) (x * y)
1.4.2 인라인 함수
- 인라인 함수는 컴파일러에 의해 처리된다. 인라인 함수는 매크로처럼 함수 호출 오버헤드를 제거하지만, 매크로와는 달리 컴파일러가 타입 검사를 하며, 코드 대체는 컴파일 과정에서 이루어진다.
- 예시:
인라인 함수는 컴파일러가 타입을 체크하고, 안전하게 연산 순서를 처리한다. 따라서 연산 우선순위 문제를 방지할 수 있다.
inline int Mul(int x, int y) {
return x * y;
}
1.4.3 매크로 함수의 문제점
- 연산자 우선순위 문제: 매크로는 단순한 문자 대체 방식이기 때문에, 복잡한 수식에서는 예상치 못한 결과가 발생할 수 있다. 예를 들어,
MUL(10+20, 30+40)은10+20 * 30+40으로 처리되어 예상한30 * 70이 아닌,10 + 600 + 40으로 계산된다. 인자 형 검사 부족: 매크로는 타입 검사를 하지 않는다. 그래서 매크로를 사용할 때 실수로 잘못된 타입의 인자를 넘겨도 오류가 발생하지 않아 예기치 못한 결과를 낳을 수 있다.
인라인 함수를 사용하는 것이 좋은 이유
인라인 함수는 매크로의 단점을 보완하면서도 함수 호출 오버헤드를 줄이는 이점을 제공한다. 컴파일러는 인라인 함수가 호출된 부분에 함수의 코드를 직접 삽입해 성능을 최적화하며, 타입 검사 및 연산자 우선순위 문제를 해결한다.
결론: 매크로 함수보다 인라인 함수를 사용하는 것이 더 안전하고 효율적이다.
1.5 인라인 함수의 장단점
장점
실행 속도 향상: 인라인 함수는 함수 호출을 없애고 함수의 코드를 직접 삽입하기 때문에 프로그램의 실행 시간이 빨라진다. 함수 호출에 따른 오버헤드가 없어져 성능이 향상된다.
단점
코드 크기 증가: 인라인 함수는 함수 호출을 대체하는 것이 아니라 함수의 코드를 그대로 삽입하므로, 그에 따라 전체 코드 크기가 증가할 수 있다. 코드가 반복적으로 삽입되기 때문에, 통계적으로 최대 30% 정도 코드 크기가 증가할 수 있다. ⇒ 따라서 짧은 코드의 함수를 인라인으로 선언하는것이 좋다.
2. 디폴트 인자
2.1 디폴트인자란?
인자에 값이 넘어오지 않는 경우, 디폴트 값을 받도록 선언된 인자.인자 = 디폴트값구조이다.- 디폴트 인자를 지정할때는
함수의 선언부에 지정한다.
2.2 디폴트 인자 지정 순서
함수의 가장 오른쪽 인자부터 지정해야한다.
/*디폴트 인자*/
// 디폴트 인자는 오른쪽부터 채워야한다.
int sum3(int a, int b=200 , int c = 100)
{
return a+b+c;
}
2.3 함수의 인자 생략
호출할때는 함수의 가장 오른쪽 인자부터 생략해야한다.
int main(void)
{
int x=10, y=20, z=30;
int w = sum3(x,y,z); //60
int u = sum3(x,y); //130
int v = sum3(x); //310
cout << w << " " << u <<" "<< v << endl;
return 0;
}
3. 함수 중복 (오버로딩)
3.1 함수 중복 = 함수 오버로딩 (이름은 항상 같다!)
C++에서 함수 중복(오버로딩, Overloading)이 가능하다는 것은 “**함수의 이름이 같더라도” 매개변수의 타입**이나 **개수**가 다르면 서로 다른 함수로 인식된다는 것을 의미한다.
함수 중복의 편리함, 오류 가능성 낮춤 : 동일한 이름을 사용하면 함수 이름을 구분하여 기억할 필요 없고, 함수 호출을 잘못하는 실수를 줄일 수 있다.
3.2 함수 중복 성공 조건
- 중복된 함수들의 이름 동일
- 중복된 함수들의
매개 변수 타입이 다르거나,개수가 달라야함. 리턴 타입은 함수 중복으로 인정되지 않는다.
3.2.1 함수의 중복 조건 정리
즉, 함수 오버로딩에서는 **매개변수의 타입, 개수, 순서**만 다르면 함수 이름이 같아도 여러 번 정의할 수 있다.
3.3 함수 중복 성공 사례
3.3.1 매개변수의 타입이 다른 경우 (오버로딩 가능)
double ssum(double a, double b, double c) {
return a + b + c;
}
int ssum(int a, int b, int c) {
return a + b + c;
}
int ex0923_3(void)
{
cout << ssum(2, 5, 33) << endl; // 40
cout << ssum(2.0, 5.0, 33.0) << endl; // 40
return 0;
}
이 코드는 오버로딩이 가능하다. 이유는 매개변수의 타입이 다르기 때문에 C++ 컴파일러는 어떤 함수를 호출해야 할지 구분할 수 있다.
3.3.2 매개변수의 개수가 다른 경우 (오버로딩 가능)
double ssum(double a, double b, double c) {
return a + b + c;
}
double ssum(double a, double b) {
return a + b;
}
이 경우도 매개변수의 개수가 다르기 때문에 오버로딩이 가능하다.
⇒ 즉, 매개변수의 개수 혹은 매개변수의 타입이 다르면 된다.
#include <iostream>
using namespace std;
// 세 개의 정수 합을 구하는 함수
int sum(int a, int b, int c) {
return a + b + c;
}
// 두 개의 double 값을 더하는 함수
double sum(double a, double b) {
return a + b;
}
// 두 개의 정수 합을 구하는 함수
int sum(int a, int b) {
return a + b;
}
int main() {
cout << sum(2, 5, 33) << endl; // 첫 번째 sum 함수 호출 (int, int, int)
cout << sum(12.5, 33.6) << endl; // 두 번째 sum 함수 호출 (double, double)
cout << sum(2, 6) << endl; // 세 번째 sum 함수 호출 (int, int)
return 0;
}
3.4 함수 중복 실패 사례
3.4.1 반환 타입만 다른 경우 (오버로딩 불가능)
- 리턴 타입만 다르고, 매개변수의 타입이나 개수는 동일하다.
- 리턴 타입만으로는 컴파일러가 어떤 함수를 호출할지 구분할 수 없다.
int sum(int a, int b) {
return a + b;
}
double sum(int a, int b) {
return (double)(a + b);
}
int main() {
cout << sum(2, 5);
}
3.4.2 함수 중복시 주의사항
1. 인자의 이름만 다른 경우 오버로딩할 수 없다.
int foo1(int a, int b);
int foo1(int x, int y); // 불가능
- 함수의 매개변수 이름만 다르고 타입이나 개수는 동일할 경우, 오버로딩이 불가능하다. 이름만 바꾸는 것은 함수의 시그니처가 같기 때문에 구분할 수 없다.
2. 함수의 리턴형만 다른 경우 오버로딩할 수 없다.
int foo2(char c, int num);
void foo2(char c, int num); // 불가능
- 함수의 리턴 타입만 다르고 매개변수의 타입이나 개수가 동일할 경우, 오버로딩이 불가능하다. 리턴 타입은 함수 호출 시 구분 기준에 포함되지 않기 때문이다.
3. 데이터 형과 해당 형의 레퍼런스 형으로 오버로딩된 경우 오버로딩할 수 없다.
int foo3(int a, int b);
int foo3(int &a, int &b); // 불가능
int x =10, y=20;
foo3(x,y);
- 매개변수의 데이터 타입이 값 전달과 레퍼런스 전달인 경우, 이는 구분되지 않기 때문에 오버로딩이 불가능하다.
4. typedef로 정의된 데이터 형에 대해 오버로딩할 수 없다.
typedef unsigned int UINT;
void foo4(UINT a, UINT b);
void foo4(unsigned int a, unsigned int b); // 불가능
foo4(10,20);
typedef로 정의된 타입은 실제로 동일한 타입이기 때문에, 이러한 형식으로 오버로딩하는 것은 불가능하다.
5. 디폴트 인자에 의해 인자의 개수가 같은 경우 오버로딩할 수 없다.***
void foo5(int a);
void foo5(int a, int b = 0); // 불가능
foo5(10);
- 두 번째 함수에 디폴트 값이 지정되면, 호출 시 인자의 개수가 같아지므로 구분할 수 없게 되어 오버로딩이 불가능하다.
3.5 함수 중복예시 코드
함수가 호출하는 인자의 타입에 따라서 함수를 구분할 수 있다.
/* 함수 중복 (오버로딩)*/
int ssum(int a, int b, int c)
{
return a+b+c;
}
//c와 다르게 c++에서는 매개변수의 타입이 다르기 때문에 함수 중복이 가능하다.
double ssum(double a, double b, double c)
{
return a+b+c;
}
int main(void)
{
cout<<ssum(2,5,33)<<endl; // 40
cout<<ssum(2.0,5.0,33.0)<<endl; // 40
return 0;
}
다음과 같이 배열과, 배열의 크기를 매개변수로 받는 함수와, 정수형 매개변수를 받는 두 함수는 함수중복이 인정된다.
#include <iostream>
using namespace std;
// 두 정수 중 더 큰 값을 리턴하는 함수
int big(int a, int b) {
if (a > b) return a;
else return b;
}
// 배열에서 가장 큰 값을 리턴하는 함수
int big(int a[], int size) {
int res = a[0]; // 배열의 첫 번째 값을 초기값으로 설정
for (int i = 1; i < size; i++) {
if (res < a[i]) res = a[i]; // 더 큰 값이 있으면 res를 갱신
}
return res;
}
int main() {
int array[5] = {1, 9, -2, 8, 6};
// 두 정수 중 큰 값을 리턴하는 함수 호출
cout << big(2, 3) << endl; // 출력: 3
// 배열에서 가장 큰 값을 리턴하는 함수 호출
cout << big(array, 5) << endl; // 출력: 9
return 0;
}
3.6 디폴트 인자 vs 함수 중복 둘을 언제 써야할까?
3.6.1 디폴트 인자의 사용
두 함수의 처리과정이 비슷하고, 한 함수가 다른 함수의 특별한 경우로 간주되는 경우
int GetSum(int x, int y, int z = 0) { // 디폴트 인자 z 지정
return x + y + z;
}
int main() {
cout << GetSum(10, 20) << endl; // GetSum(10, 20, 0)으로 호출됨
cout << GetSum(10, 20, 30) << endl; // GetSum(10, 20, 30)으로 호출됨
}
3.6.2 함수 오버로딩의 사용
두함수의 구체적인 처리과정은 다르지만, 같은 함수 이름으로 표현될수 있다는 공통점만 갖는 경우
int GetSum(int x, int y) {
return x + y;
}
int GetSum(const int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++)
sum += arr[i];
return sum;
}
3.7 디폴트 인자를 사용한 함수 중복 간소화**
3.7.1 기존 방식: 함수 중복
// 25개의 '*' 문자를 출력하는 함수
void fillLine() {
for (int i = 0; i < 25; i++)
cout << '*';
cout << endl;
}
// n개의 c 문자를 출력하는 함수
void fillLine(int n, char c) {
for (int i = 0; i < n; i++)
cout << c;
cout << endl;
}
위의 방식에서는 25개의 ‘*‘을 출력하는 fillLine() 함수와, 원하는 개수의 문자를 출력하는 fillLine(int n, char c) 함수 두 개가 오버로딩되어 있다.
3.7.2 디폴트 인자를 사용한 간소화된 방식 **
⇒ 디폴트 인자를 이용하여 매개변수가 입력안되면 디폴트 인자문자로,
매개변수가 입력되면 입력된 문자를 처리한다.
#include <iostream>
using namespace std;
// 디폴트 인자를 사용하여 함수 간소화
void fillLine(int n = 25, char c = '*') {
for (int i = 0; i < n; i++)
cout << c;
cout << endl;
}
int main() {
fillLine(); // 25개의 '*' 출력
fillLine(10, '%'); // 10개의 '%' 출력
}
- 이 방법은 코드의 간결성을 높이고, 함수 호출 시 선택적인 매개변수를 제공하여 다양한 상황에 맞게 사용할 수 있는 유연성을 제공한다.
4. 실습
실습 3-1 * (디폴트인자)
함수 f()를 호출하는 경우가 다음과 같을때 f()를 디폴트인자로 가진 함수로 작성하라.
f(); //한줄에 빈칸을 10개 출력한다.
f(’%’); //한 줄에 %를 10개 출력한다.
f(’@’,5); //다섯줄에 @를 10개 출력한다.
/*실습 3-1 디폴트인자*/
void f(char c = ' ', int line = 1) //아무 인자가 안 들어오면 공백을 한줄 출력한다.
{
for (int i = 0; i < line; i++)
{
for (int j = 0; j < 10; j++)
{
cout << c;
}
cout << endl;
}
return;
}
int ex0923_2(void)
{
f(); // 빈칸을 10개 1줄 출력
f('%'); //%를 10개 1줄 출력
f('@', 5); //@를 10개 5줄 출력
return 0;
}
실습 3-2 (함수 중복)**
- (1)
big()함수를 2개 중복하여 작성하고 프로그램을 완성하라. - (2) 디폴트 인자를 가진 하나의 함수로
big()을 작성하고 프로그램을 완성하라.
int main() {
int x = big(3, 5); // 3과 5 중 큰 값 5는 최대값 100보다 작으므로, 5 리턴
int y = big(300, 60); // 300과 60 중 큰 값 300이 최대값 100보다 크므로, 100 리턴
int z = big(30, 60, 50); // 30과 60 중 큰 값 60이 최대값 50보다 크므로, 50 리턴
cout << x << " " << y << " " << z << endl;
}
(1) big() 함수를 2개 중복하여 작성하기
/* 함수중복 예시 */
int big(int a, int b)
{
int max_value = (a > b) ? a : b;
if (max_value <= 100)
{
return max_value;
}
else
{
return 100;
}
}
int big(int a, int b, int c) // c가 최대값이면 c를 반환해라.
{
int max_value = (a > b) ? a : b;
max_value = (max_value > c) ? c : max_value;
return max_value;
}
int ex0923_4(void)
{
int x = big(3, 5); // 3과 5중 큰수를 출력. 큰수가 100보다 크면 100을 출력
int y = big(300, 60); // 결과는 100
int z = big(30, 60, 50); // 결과는 50
cout << x << " " << y << " " << z << endl;
}
(2) 디폴트 인자를 가진 하나의 함수로 big()을 작성하기
int big(int a, int b, int max= 100)
{
int max_value = (a>b)?a:b;
if (max_value<= max)
{
return max_value;
}
else
return max;
}
댓글남기기