본문 바로가기
C++

C++ 함수

by godfeeling 2020. 7. 5.

참조자(reference)

C++에서는 특정 변수의 실제 이름 대신 사용할 수 있는 참조자(reference)라는 새로운 기능이 추가되었습니다.

 

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

 

또한, C++의 클래스(class)를 설계할 때에도 자주 사용됩니다.

 

참조자의 선언

C++에서 참조자는 다음과 같은 문법으로 선언합니다.

 

문법

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

 

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

 

 

 

이때 & 연산자는 주소 연산자가 아닌 타입을 식별하기 위해 사용하는 식별자로 사용된 것입니다.

 

, int&int형 변수에 대한 참조를 의미합니다.

 

이렇게 선언된 참조자는 대상 변수와 같은 메모리 위치를 참조하게 됩니다.

 

 

 

참조자를 선언할 때에는 다음과 같은 사항에 주의해야 합니다.

 

 

 

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;

 

함수의 인수로서 전달

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;

 

}

 

위와 같은 참조에 의한 전달(call by reference)은 참조자뿐만 아니라 포인터를 사용해도 똑같은 결과를 얻을 수 있습니다.

 

포인터를 사용하는 방법과 참조자를 사용하는 방법 모두 결과는 같으며, 구문 형태상의 차이점만이 존재합니다.

 

 

 

C++에서 함수의 인수로 참조자를 사용하는 방법의 특징은 다음과 같습니다.

 

 

 

1. 함수 내에서 참조 연산자(*)를 사용하지 않으므로, 함수 내부의 코드가 깔끔하고 직관적이 됩니다.

 

2. 함수의 호출이 값에 의한 전달(call by value) 방법과 같은 형태가 되어, 코드를 읽기가 쉽지 않습니다.

 

 

 

따라서 간단한 함수에서는 굳이 참조에 의한 전달을 하지 말고 값에 의한 전달을 사용하는 것이 좋을 수 있습니다.

 

또한, 참조 호출이 꼭 필요할 때에는 참조자보다는 포인터를 사용하는 것이 더욱 직관적일 수 있습니다.

 

C++에서 참조자는 크기가 큰 구조체나 클래스를 다룰 때에만 사용하는 것이 좋습니다.

 

구조체의 참조

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

 

구조체를 참조하는 방법은 변수를 참조하는 방법과 같습니다.

 

 

 

예제

struct Book

 

{

 

string title;

 

string author;

 

int price;

 

};

 

 

 

void Display(const Book&);

 

 

 

int main(void)

 

{

 

Book web_book = {"HTMLCSS", "홍길동", 28000};

 

Display(web_book);

 

return 0;

 

}

 

 

 

void Display(const Book& bk)

 

{

 

cout << "책의 제목은 " << bk.title << "이고, ";

 

cout << "저자는 " << bk.author << "이며, ";

 

cout << "가격은 " << bk.price << "원입니다.";

 

}

 

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

 

디폴트 인수(default argument)

C++에서 새롭게 정의된 디폴트 인수(default argument)는 기본값이 미리 정의되어 있는 인수를 의미합니다.

 

함수를 호출할 때 인수를 전달하지 않으면, 함수는 자동으로 미리 정의되어 있는 디폴트 인수값을 사용하게 됩니다.

 

물론 인수를 전달하여 함수를 호출하면, 디폴트 인수값이 아닌 전달된 인수를 가지고 함수를 호출하게 됩니다.

 

디폴트 인수의 설정

C++에서 디폴트 인수를 설정할 때에는 다음과 같은 사항에 주의해야 합니다.

 

 

 

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); // 오류

 

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

함수로 전달된 인수는 왼쪽부터 순서대로 매개변수 목록에 대입됩니다.

 

따라서 디폴트 인수가 설정된 함수를 호출할 때에는 인수의 전달을 건너뛸 수 없습니다.

 

 

 

함수의 호출 예제

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;

 

}

 

함수 오버로딩

함수 오버로딩(function overloading)

디폴트 인수가 인수의 개수를 달리하여 같은 함수를 호출하는 것이라면, 함수 오버로딩(overloading)은 같은 이름의 함수를 중복하여 정의하는 것을 의미합니다.

 

 

 

C++에서 새롭게 추가된 함수 오버로딩은 여러 함수를 하나의 이름으로 연결해 줍니다.

 

, 함수 오버로딩이란 같은 일을 처리하는 함수를 매개변수의 형식을 조금씩 달리하여, 하나의 이름으로 작성할 수 있게 해주는 것입니다.

 

이와 같은 함수 오버로딩은 객체 지향 프로그래밍의 특징 중 바로 다형성(polymorphism)의 구현입니다.

 

함수 시그니처(function signature)

함수 오버로딩의 핵심은 바로 함수 시그니처(function signature)에 있습니다.

 

함수 시그니처란 함수의 원형에 명시되는 매개변수 리스트를 가리킵니다.

 

 

 

만약 두 함수가 매개변수의 개수와 그 타입이 모두 같다면, 이 두 함수의 시그니처는 같다고 할 수 있습니다.

 

, 함수의 오버로딩은 서로 다른 시그니처를 갖는 여러 함수를 같은 이름으로 정의하는 것이라고 할 수 있습니다.

 

함수 오버로딩의 예제

C++ 컴파일러는 사용자가 오버로딩된 함수를 호출하면, 그것과 같은 시그니처를 가지는 함수의 원형을 찾아 호출해 줍니다.

 

 

 

다음 예제는 함수의 오버로딩을 이용하여 정의한 Display() 함수의 원형 예제입니다.

 

함수의 원형 예제

1. void Display(const char* str, int n); // 문자열 strn번 출력함.

 

2. void Display(const char* str1, const char* str2); // 문자열 str1str2를 연달아 출력함.

 

3. void Display(int x, int y); // x * y를 출력함.

 

4. void Display(double x, double y); // x / y를 출력함.

 

 

 

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

 

함수의 호출 예제

1. Display("C++", 3); // 1Display() 함수 호출 -> "C++C++C++"

 

2. Display("C++", " Programming"); // 2Display() 함수 호출 -> "C++ Programming"

 

3. Display(3, 4); // 3Display() 함수 호출 -> 12

 

4. Display(4.2, 2.1); // 4Display() 함수 호출 -> 2

 

5. Display(4.2, 3); // 3번과 4번 모두 호출 가능 -> 컴파일 오류가 발생함.

 

 

 

문제는 5번과 같은 함수의 호출로 첫 번째 인수로는 double형의 인수를, 두 번째 인수로는 int형의 인수를 전달합니다.

 

이와 같은 함수 호출은 3번과 4번 시그니처의 Display() 함수를 모두 호출할 수 있으므로, 모호한 호출이 됩니다.

 

C++에서는 오버로딩한 함수의 이러한 모호한 호출을 허용하지 않으며, 이럴 때에는 오류를 발생시킵니다.

 

 

 

예제

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;

 

}

 

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

함수가 호출되면 우선 스택에 함수로 전달할 매개변수와 함께 호출이 끝난 뒤 돌아갈 반환 주소값을 저장하게 됩니다.

 

그리고서 프로그램의 제어가 함수의 위치로 넘어와 함수 내에 선언된 지역 변수도 스택에 저장합니다.

 

그때부터 함수의 모든 코드를 실행하게 되고, 실행이 전부 끝나면 반환값을 넘겨 줍니다.

 

그 후 프로그램의 제어는 스택에 저장된 돌아갈 반환 주소값으로 이동하여, 스택에 저장된 함수 호출 정보를 제거합니다.

 

이와 같은 일련의 함수 호출 과정이 함수마다 일어나게 됩니다.

 

인라인 함수(inline function)

위와 같이 C++에서 함수의 호출은 꽤 복잡한 과정을 거치므로, 약간의 시간이 걸리게 됩니다.

 

이때 함수를 실행하는 시간이 오래 걸린다면, 함수를 호출하는데 걸리는 시간은 전혀 문제가 되지 않습니다.

 

하지만 함수의 실행 시간이 매우 짧다면, 함수 호출에 걸리는 시간도 부담이 될 수 있습니다.

 

 

 

C++에서는 이러한 경우에 사용할 수 있는 인라인 함수(inline function)라는 것을 제공합니다.

 

인라인 함수는 호출될 때 일반적인 함수의 호출 과정을 거치지 않고, 함수의 모든 코드를 호출된 자리에 바로 삽입하는 방식의 함수입니다.

 

이 방식은 함수를 호출하는 데 걸리는 시간은 절약되나, 함수 호출 과정으로 생기는 여러 이점을 포기하게 됩니다.

 

따라서 코드가 매우 적은 함수만을 인라인 함수로 선언하는 것이 좋습니다.

 

인라인 함수의 선언

C++에서 인라인 함수는 다음과 같이 선언합니다.

 

문법

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;

 

}

 

위의 예제에서 Sub() 함수와 Print() 함수는 인라인 함수로 정의되어 호출됩니다.

 

보통 인라인 함수는 크기가 작으므로, 함수의 원형이 나오는 자리에 함수의 본체까지 함께 정의하는 경우가 많습니다.

 

 

 

위의 예제는 실제로는 다음과 같이 인라인 코드로 삽입되어 실행됩니다.

 

예제

int main(void)

 

{

 

int num1 = 5, num2 = 3;

 

int result;

 

 

 

{

 

int x = num1, y = num2;

 

result = x - y;

 

}

 

 

 

{

 

int x = result;

 

cout << "계산 결과는 " << x << "입니다.";

 

}

 

return 0;

 

}

 

인라인 함수에서는 재귀 호출이 허용되지 않습니다.

매크로 함수와 인라인 함수

C언어에서는 C++의 인라인 함수와 비슷한 기능의 매크로 함수(macro function)를 사용합니다.

 

#define 선행처리 지시문에 인수로 함수의 정의를 전달함으로써, 함수처럼 동작하는 매크로를 만들 수 있습니다.

 

이러한 매크로를 함수 같은 매크로(function-like macro) 또는 매크로 함수라고 합니다.

 

 

 

하지만 매크로 함수는 일반 함수와는 달리 단순 치환만을 해주므로, 일반 함수와 똑같은 방식으로 동작하지 않습니다.

 

이러한 매크로 함수를 일반 함수처럼 사용하기 위해서는 모든 인수를 괄호({})로 감싸야만 합니다.

 

예제

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

 

}

 

 

위의 예제에서 매크로 함수 SQR(x+3)의 결과는 우리의 예상과 많이 다릅니다.

 

그 이유는 매크로 함수의 단순 치환 과정에 있으며, 제대로 된 결과를 얻기 위해서는 매크로 함수의 정의 부분을 다음과 같이 수정해야 합니다.

 

예제

#define SQR(X) ((X)*(X))

 

 

하지만 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;

 

}

 

 

'C++' 카테고리의 다른 글

C++ 클래스  (0) 2020.07.05
C++ 범위  (0) 2020.07.05
C++ 함수기본  (0) 2020.07.05
C++ 구조체  (0) 2020.07.05
C++ 문자열  (0) 2020.07.05

댓글