본문 바로가기
Computer Science/C++

[C++] C++ 함수

by BrickSky 2023. 12. 2.

1) 참조자


참조자는 크기가 큰 구조체와 같은 데이터를 함수의 인수로 전달해야 하는 경우에 사용할 수 있다.
 

참조자의 선언

int 변수이름;               // 변수의 선언

int& 참조자이름 = 변수이름; // 참조자 선언

위와 같은 문법으로 참조자를 선언할 수 있다.
이때 &연산자는 주소 연산자가 아닌 타입을 식별하기 위해 사용하는 식별자로 사용된 것이다.
int&는 int형 변수에 대한 참조를 의미한다. 이렇게 선언된 참조자는 대상 변수와 같은 메모리 위치를 참조한다.
 
참조자를 선언할 때는 아래의 3가지를 주의해야 한다.

  1. 참조자의 타입은 대상이 되는 변수의 타입과 일치해야 한다.
  2. 참조자는 선언과 동시에 초기화되어야 한다.
  3. 참조자는 한번 초기화되면 참조하는 대상을 변경할 수 없다.

 

int x = 10; // 변수의 선언
int& y = x; // 참조자 선언

cout << "x : " << x << ", y : " << y << endl;

y++;        // 참조자를 이용한 증가 연산
cout << "x : " << x << ", y : " << y << endl;]

cout << "x의 주소값 : " << &x << ", y의 주소값 : " << &y;
x : 10, y : 10
x : 11, y : 11
x의 주소값 : 0x7ffe6c0a1234, y의 주소값 : 0x7ffe6c0a1234

참조자를 이용해 증가연산을 수행하면 참조 변수뿐만 아니라 대상 변수도 같이 변경된다.
 

함수의 인수로서 전달

C++에서 참조자는 주로 함수에 인수를 전달할 때 사용한다.
함수가 참조자를 인수로 전달받으면, 참조자가 참조하고 있는 실제 변수의 값을 함수 내에서 조작할 수 있다. 아래의 코드는 참조자를 사용하여 두 변수의 값을 바꾸는 예제이다.

int main(void)
{
    int num1 = 3, num2 = 7;
    cout << "변경 전 num1의 값은 " << num1 << "이며, num2의 값은 " << num2 << "입니다." << endl;

    Swap(num1, num2);

    cout << "변경 후 num1의 값은 " << num1 << "이며, num2의 값은 " << num2 << "입니다." << endl;

    return 0;
} 

void Swap(int& x, int& y)
{
    int temp;

    temp = x;
    x = y;
    y = temp;
}
변경 전 num1의 값은 3이며, num2의 값은 7입니다.
변경 후 num1의 값은 7이며, num2의 값은 3입니다.

참조에 의한 전달(call by reference)은 참조자뿐만 아니라 포인터를 사용해도 같은 결과를 얻을 수 있다.
 
C++에서 함수의 인수로 참조자를 사용하는 방법의 특징은 아래와 같다.

  • 함수 내에서 참조 연산자(*)를 사용하지 않기에 코드가 깔끔해진다.
  • 함수의 호출이 값에 의한 전달(call by value) 방법과 같은 형태가 되어 코드를 읽기 쉽지 않다.

따라서! 간단한 함수에서는 참조에 의한 전달을 하기보단 값에 의한 전달을 하는 것이 좋다.
또한, 참조 호출이 꼭 필요한 경우 참조자보다는 포인터를 활용하는 것이 더 직관적이다.
 

구조체의 참조

C++에서 참조자는 구조체와 같은 사용자 정의 타입을 다룰 때 유용하다.

struct Book
{
    string title;
    string author;
    int price;
};

void Display(const Book&);

int main(void)
{
    Book web_book = {"HTML과 CSS", "홍길동", 28000};
    Display(web_book);
    return 0;
}

void Display(const Book& bk)
{
    cout << "책의 제목은 " << bk.title << "이고, ";
    cout << "저자는 " << bk.author << "이며, ";
    cout << "가격은 " << bk.price << "원입니다.";
}
책의 제목은 HTML과 CSS이고, 저자는 홍길동이며, 가격은 28000원입니다.

*위의 예제처럼 함수 내부에서 구조체를 직접 변경할 필요가 없을 때는 const 키워드를 사용하여 원본 구조체에 대한 변경을 허용하지 않을 수도 있다.
 
 

2) 디폴트 인수


디폴트 인수(default argument)는 기본값이 미리 정의되어 있는 인수를 의미한다.
함수를 호출할 때 인수를 전달하지 않으면, 함수는 자동으로 미리 정의되어 있는 디폴트 인수값을 사용한다. 인수를 전달하여 함수를 호출하면, 디폴트 인수값이 아닌 전달된 인수를 가지고 함수를 호출하게 된다.
 

디폴트 인수의 설정

C++에서 디폴트 인수를 설정할 때는 아래의 3가지를 주의해야 한다.

  1. 디폴트 인수는 함수의 원형에만 지정할 수 있다.
  2. 디폴트 인수는 가장 오른쪽부터 시작해 순서대로만 지정할 수 있다.
  3. 가운데 인수들만 별도로 디폴트 인수를 지정할 수는 없다.
1. void Display(int x, int y, char ch, int z = 4);       // 가능함.
2. void Display(int x, int y, char ch = 'a', int z = 4); // 가능함.
3. void Display(int x, int y = 2, char ch, int z = 4);   // 오류
4. void Display(int x = 1, int y = 2, char ch, int z);   // 오류

3번이 오류인 이유.
함수 선언에서 디폴트 값을 가진 매개변수는 항상 오른쪽에서부터 순서대로 제공되어야 한다. 3번 코드는 y에만 디폴트 값이 주어져 있고, 그 뒤에 오는 ch에는 디폴트 값이 주어지지 않았기 때문에 오류가 발생한다.
 
4번이 오류인 이유.
마찬가지로, 함수 선언에서 디폴트 값을 가진 매개변수는 항상 오른쪽에서부터 순서대로 제공되어야 한다. 여기서는 x와 y에는 디폴트 값이 주어져 있지만, ch에는 주어지지 않았기 때문에 오류가 발생한다.
 

디폴트 인수가 설정된 함수의 호출

함수로 전달된 인수는 왼쪽부터 순서대로 매개 변수 목록에 대입된다. 따라서 디폴트 인수가 설정된 함수를 호출할 때는 인수의 전달을 건너뛸 수 없다.

void Display(int x, int y, char ch = 'a', int z = 4); // 함수의 원형

1. Display(1, 2);         // 가능함 -> display(1, 2, 'a', 4)와 같음.
2. Display(3, 4, 'b');    // 가능함 -> display(3, 4, 'b', 4)와 같음.
3. Display(5, 6, 'c', 9); // 가능함 -> display(5, 6, 'c', 8)와 같음.
4. Display(7, 8, , 9);    // 오류 : 인수 전달은 건너뛸 수 없음.

 

double Multi(double, double = 2);

int main(void)
{
    cout << Multi(3) << endl;    // Multi(3, 2)와 같음 : 3 * 3
    cout << Multi(3, 3) << endl; // 3 * 3 * 3
    cout << Multi(3, 4);         // 3 * 3 * 3 * 3
    return 0;
}

double Multi(double x, double n)
{
    double result = x;

    for (int i = 1; i < n; i++)
    {
        result *= x;
    }
    return result;
}
9
27
81

double() 함수는 주어진 숫자 **x**를 **n**번 반복해서 곱한 값을 반환한다. 디폴트 값이 2로 설정되어 있기 때문에, 만약 함수를 호출할 때 두 번째 인자를 제공하지 않으면 자동으로 2가 사용된다. 따라서 디폴트 값이 적용되어 cout << Multi(3) << endl; 은 Multi(3, 2)와 같아 3 * 3이 되는 것이다.
 
 

3) 함수 오버로딩


디폴트 인수가 인수의 개수를 달리하여 같은 함수를 호출하는 것이라면, 함수 오버로딩은 같은 이름의 함수를 중복하여 정의하는 것이다.
 
즉, 함수 오버로딩이란 같은 일을 처리하는 함수를 매개변수의 형식을 조금씩 달리하여, 하나의 이름으로 작성할 수 있게 해주는 것이다. 이 같은 함수 오버로딩은 객체 지향 프로그래밍의 특징 중 다형성을 구현하는 것이다.
 

함수 시그니처

함수 오버로딩의 핵심은 함수 시그니처이다. 함수 시그니처란 함수의 원형에 명시되는 매개변수 리스트를 가리킨다
.
만약 두 함수가 매개변수의 개수는 물론 타입도 같다면, 두 함수의 시그니처는 같다고 할 수 있다.
즉 함수 오버로딩은 서로 다른 시그니처를 갖는 여러 함수를 같은 의미로 정의하는 것이다.
 

함수 오버로딩의 예제

C++ 컴파일러는 사용자가 오버로딩된 함수를 호출하면 그것과 같은 시그니처를 갖는 함수의 원형을 찾아 호출한다.
 
아래의 예제는 함수 오버로딩을 활용하여 정의한 Display() 함수의 원형 예제이다.

1. void Display(const char* str, int n);             // 문자열 str을 n번 출력함.
2. void Display(const char* str1, const char* str2); // 문자열 str1과 str2를 연달아 출력함.
3. void Display(int x, int y);                       // x * y를 출력함.
4. void Display(double x, double y);                 // x / y를 출력함.

 
이제 사용자가 Display() 함수를 호출하면 C++ 컴파일러는 자동으로 같은 시그니처를 갖는 함수의 원형을 찾아 호출한다.

1. Display("C++", 3);              // 1번 Display() 함수 호출 -> "C++C++C++"
2. Display("C++", " Programming"); // 2번 Display() 함수 호출 -> "C++ Programming"
3. Display(3, 4);                  // 3번 Display() 함수 호출 -> 12
4. Display(4.2, 2.1);              // 4번 Display() 함수 호출 -> 2
5. Display(4.2, 3);                // 3번과 4번 모두 호출 가능 -> 컴파일 오류가 발생함.

5번의 사례가 문제가 되는 사례이다.
첫 번째 인수로는 double형 인수를 두 번째 인수로는 int형 인수를 전달하는데, 이 같은 함수 호출은 3번과 4번 시그니처의 Display() 함수를 모두 호출할 수 있다. 이럴 때! 오류가 발생하는 것이다.
 

void Shift(int, int);
void Shift(int, int, int);
void Shift(int, int, int, int);

int main(void)
{
    Shift(1, 2);
    Shift(1, 2, 3);
    Shift(1, 2, 3, 4);
    return 0;
}
2, 1
2, 3, 1
2, 3, 4, 1

위의 예제처럼 함수의 오버로딩은 매개변수의 타입뿐만 아니라 매개변수의 개수를 달리해도 작성할 수 있다.
 
매개변수의 개수를 달리해도 작성할 수 있다?
위세 개의 함수는 이름은 모두 **Shift**이지만, 매개변수의 개수가 각각 다르다.

  • void Shift(int, int);: 두 개의 정수를 매개변수로 받는 버전.
  • void Shift(int, int, int);: 세 개의 정수를 매개변수로 받는 버전.
  • void Shift(int, int, int, int);: 네 개의 정수를 매개변수로 받는 버전.

 
이런 상황에서 함수 오버로딩을 사용하면 동일한 이름의 함수로 다양한 상황에 대응할 수 있다는 것이다.
컴파일러는 호출된 함수에 대해 전달된 매개변수의 개수와 타입에 따라 적절한 함수를 선택하여 호출한다.
 
 

4) 인라인 함수


C++에서의 함수 호출과정

함수가 호출되면 우선 스택에 함수로 전달한 매개변수와 함께 호출이 끝난 뒤 돌아갈 반환 주소값을 저장한다. 이후 프로그램의 제어가 함수의 위치로 넘어가 함수 내에 선언된 지역 변수도 스택에 저장한다.
그때부터 함수의 모든 코드를 실행하고 실행이 끝나면 반환값을 넘겨주는 것이다. 이후에 프로그램의 제어는 스택에 저장된 돌아갈 반환 주소값으로 이동하여 스택에 저장된 함수 호출 정보를 제거하게 된다.
 

인라인 함수(inline function)

함수의 실행 시간이 오래 걸리는 것은 함수를 호출할 때 문제가 되지 않지만, 실행 시간이 짧다면 함수를 호출하는 데 문제가 된다.
 
이러한 경우를 대비하기 위해 인라인 함수를 활용할 수 있다. 인라인 함수는 호출될 때 일반적인 함수의 호출 과정을 거치지 않고, 함수의 모든 코드를 호출된 자리에 바로 삽입하는 방식의 함수이다. 이 방식은 함수를 호출하는 데 걸리는 시간은 절약되지만 함수 호출 과정으로 생기는 여러 이점을 포기해야 한다. 따라서 코드가 적은 함수일 때만 인라인 함수로 선언하는 것이 좋다.
 

인라인 함수의 선언

inline 함수의원형
또는
inline 함수의정의

inline 키워드는 함수의 원형이나 함수의 정의 어느 한 부분에만 표기해도 되고 양쪽에 표기해도 된다.
 

inline int Sub(int x, int y) { return x - y; }
inline void Print(int x) { cout << "계산 결과는 " << x << "입니다."; }

int main(void)
{
    int num1 = 5, num2 = 3;
    int result;

    result = Sub(num1, num2);
    Print(result);
    return 0;
}
계산 결과는 2입니다.

위의 코드에서 Sub() 함수와 Print() 함수는 인라인 함수로 정의되어 호출된다.
보통의 인라인 함수는 크기가 작기에 함수의 원형이 나오는 자리에 함수의 본체까지 함께 정의하는 경우가 많다.
 

매크로 함수와 인라인 함수

#define 선행 처리 지시문에 인수로 함수의 정의를 전달함으로써 함수처럼 동작하는 매크로를 만들 수 있다.
이러한 매크로를 함수 같은 매크로 혹은 매크로 함수라고 부른다.
 
하지만! 매크로 함수는 일반 함수와 달리 단순한 치환만 해주므로 일반 함수와 같은 방식으로 동작하지는 않는다. 이러한 매크로 함수를 일반 함수처럼 사용하기 위해서는 모든 인수를 괄호{}로 감싸야한다.

#include <stdio.h>

#define SQR(X) X*X

int main(void)
{
    int result;
    int x = 5;

    puts(SQR(10));
    puts(SQR(x));
    puts(SQR(x+3));

    return 0;
}
100

25

23

 
💡 puts(SQR(x+3)); 은 왜… 23이 나올까?
그 이유는! 매크로 함수가 단순한 치환만 하기 때문이다.

#define SQR(X) ((X)*(X))
100
25
64

C++의 인라인 함수는 단순 치환이 아닌 함수의 모든 코드를 호출된 자리에 인라인 코드로 삽입해 준다. 따라서 일반 함수처럼 값이나 수식을 인수로 전달할 수 있으며 매개변수 타입에 맞춘 자동 타입 변환도 가능하다.
 
매크로 함수를 인라인 함수로 바꾼 코드이다.

inline int Sqr(int x) { return x * x; }

int main(void)
{
    int result;
    int x = 5;

    cout << "계산 결과는 " << Sqr(10) << "입니다." << endl;
    cout << "계산 결과는 " << Sqr(x) << "입니다." << endl;
    cout << "계산 결과는 " << Sqr(x+3) << "입니다.";

    return 0;
}
계산 결과는 100입니다.
계산 결과는 25입니다.
계산 결과는 64입니다.

'Computer Science > C++' 카테고리의 다른 글

[C++] 클래스  (0) 2023.12.10
[C++] C++ 범위  (1) 2023.12.04
[C++] 함수의 기본  (1) 2023.11.19
[C++] 구조체  (0) 2023.11.13
[C++] 문자열  (1) 2023.11.12