11 분 소요

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

태그:

카테고리:

업데이트:

댓글남기기