본문 바로가기

C & C++

C/C++ 기초 - 함수 ( Function )

함수


#include <iostream>

using namespace std;

int sum(const int& a, const int& b)
{
	return a + b;
}

void function(int a, int b)
{
	printf("함수가 호출되었습니다.\n매개 변수의 합은 %d입니다.", sum(a, b));
	// sum 함수 종료 후 -> printf("함수가 호출되었습니다.\n매개 변수의 합은 %d입니다.", 30);
}

int main(void)
{
	int left = 10;
	int right = 20;

	function(left, right); // 출력 결과 함수가 호출되었습니다.\n매개 변수의 합은 30입니다.

	return 0;
}

 '함수'는 간단하게 설명하자면 특정 기능을 수행하는 코드 모음입니다. 함수를 호출하면 해당 함수의 시작점으로 이동해서 적혀있는 코드를 수행하고 함수가 종료되면 함수를 호출했던 지점으로 다시 돌아와 반환 값이 있다면 값을 반환하고 없을 경우에는 그냥 종료됩니다.

 함수를 호출할 때 함수에서 요구하는 값이 있다면 전달해야 하는데, 이것을 인수, 전달 인자 ( Argument )라고 합니다. 함수의 입장에서 전달받는 값은 인자, 매개변수 ( Parameter )라고 합니다.

 함수는 전달 받은 값을 사용하지 않아도 되지만, 반환 값이 void가 아닌 경우에는 해당 자료형에 맞는 반환 값이 꼭 있어야 합니다. 예시 코드에서 void function 함수의 경우 반환 값이 void이기 때문에 return이 없지만, int sum 함수에서는 매개변수로 전달받은 a와 b의 합을 반환하고 있습니다. 반환 타입이 int이므로 (int)30이 반환되고 이 값이 printf에 사용되는 것입니다.

 함수는 작성 후 호출할 때마다 안에 작성된 코드를 실행하기 때문에 반복적으로 나오는 코드를 함수로 묶어서 중복 작성을 방지하는 용도로 사용하거나 가독성을 위해 반복되지 않더라도 특정 기능을 하는 구간을 함수로 묶어서 함수명으로 명시해두면 전체적인 흐름을 파악하기 쉽게 만들 수 있습니다.

 


값에 의한 호출 ( Call by value )


#include <iostream>

using namespace std;

int call_by_value(int a, int b)
{
	a = 100;
	b = 20;

	return a + b;
}

int main(void)
{
	int left = 10;
	int right = 20;

	printf("함수 호출 전 left는 %d, right는 %d 입니다.\n", left, right);	// 출력 결과 10, 20
	printf("반환 값은 %d 입니다.\n", call_by_value(left, right));		// 출력 결과 120
	printf("함수 종료 후 left는 %d, right는 %d 입니다.", left, right);		// 출력 결과 10, 20

	return 0;
}

 left, right 변수를 call_by_value 함수에 인수로 사용하고 함수 안에서 각 변수에 100과 20을 넣었는데, main 함수로 돌아와서 left 변수와 right 변수를 출력했을 때 값이 변하지 않는 것을 알 수 있습니다.

 이것은 값에 의한 호출로 인한 결과입니다. 함수에서 매개변수를 int 타입으로 받게 되어 있는데, 이런 경우에 함수를 호출할 때 넣은 값을 복사해서 사용하게 됩니다. 예시 코드를 예로 들자면 int a와 int b라는 변수에 각각 left와 right의 값을 넣어 준거라고 할 수 있겠습니다.

 left와 right 변수에 변화가 없는 것이 바로 복사한 값에만 변화를 줬을 뿐이기 때문입니다.

 


주소에 의한 호출 ( Call by address ), 포인터에 의한 호출 ( Call by pointer )


#include <iostream>

using namespace std;

int call_by_pointer(int* a, int* b)
{
	*a = 100;
	*b = 30;

	return *a + *b;
}

int main(void)
{
	int left = 10;
	int right = 20;

	printf("함수 호출 전 left는 %d, right는 %d 입니다.\n", left, right);	// 출력 결과 10, 20
	printf("반환 값은 %d 입니다.\n", call_by_pointer(&left, &right));	// 출력 결과 130
	printf("함수 종료 후 left는 %d, right는 %d 입니다.", left, right);	// 출력 결과 100, 30

	return 0;
}

 주소에 의한 호출 ( 혹은 포인터에 의한 호출 )은 인수의 값을 바꾸기 위해 사용되는 하나의 방식입니다. 함수에 변수 값이 아닌 변수의 메모리 주소 값을 전달하기 때문에 그 주소를 통해 접근하여 값을 바꿀 수 있게 되므로 함수 안에서 수정한 값이 main에 돌아와서도 수정한 상태로 유지되는 것입니다.

 여기서 호칭에 대해서 하고 싶은 얘기가 있습니다. 이 호출 방식을 Call by reference라고 하는 경우도 자주 보이는데 주소 값을 받아온 후에 함수 내부에서 주소 값을 참조하여 접근하기 때문에 내부적으로 작동하는 방식이 비슷하지만 다릅니다. reference도 그렇고, Call by reference도 그렇고 C에 없다가 C++에서 생긴 개념이기도 하니, Call by value라고 하지는 않더라도 Call by address나 Call by pointer라고 하되 Call by reference라고 하면 안 된다고 생각합니다.

 


참조에 의한 호출 ( Call by reference )


#include <iostream>

using namespace std;

int call_by_reference(int& a, int& b)
{
	a = 100;
	b = 30;

	return a + b;
}

int main(void)
{
	int left = 10;
	int right = 20;

	printf("함수 호출 전 left는 %d, right는 %d 입니다.\n", left, right);	// 출력 결과 10, 20
	printf("반환 값은 %d 입니다.\n", call_by_reference(left, right));	// 출력 결과 130
	printf("함수 종료 후 left는 %d, right는 %d 입니다.", left, right);	// 출력 결과 100, 30

	return 0;
}

 '참조에 의한 호출'은 참조자를 통해 인수를 전달하는 방식입니다. 여기서 '참조자'는 변수에 새로운 이름을 붙여주는 것이라고 생각하시면 간단하게 이해하기 좋습니다. 

 참조에 의한 호출의 사용 방식은 Call by value와 비슷하지만 내부적으로 작동하는 방식은 Call by address와 비슷합니다. 단, 말 그대로 참조에 의한 호출이기 때문에, a = b 와 같은 방식으로 대입하더라도 b의 값을 a로 복사할 뿐 a가 참조하는 대상은 절대 바뀌지 않습니다.

 또, 참조이기 때문에 참조해야 할 대상이 반드시 필요합니다. Call by address에서 nullptr을 넣거나 하는 방식이 불가능하기 때문에 안전성이 올라가고 매개변수 타입에 const를 붙여야 상수를 받을 수 있다는 것도 특징입니다.