5 min read

cpp[10] - 함수와 블록

함수와 재귀함수를 호출할때, 어떤 블록이 호출되고 프로그램의 흐름이 어떻게 작동하는지 짚어보는 시간을 가지도록 하겠습니다.

함수와 블록의 이동

#include <iostream>
using namespace std;

void f(){
	cout << "A";
	return;
}

int main(){
	cout << "B";
	f();
	cout << "C";
	return 0;
}

다음 C++ 프로그램을 실행했을 때의 결과는 B -> A -> C 순으로 출력이 됩니다. 함수는 처음 만들었을 때는 아무런 실행을 하지 않으며, 먼저 main 함수나 다른 함수가 f(); 구문을 통해 호출을 해주어야 그제서야 실행됩니다.

함수 안의 내용을 풀어 적은 것과 비슷한 역할을 하는 것이 함수의 호출입니다. f(); 로 호출된 시점에서, 안의 내용물이 반드시 먼저 실행되고, 그 이후에 다시 호출되었던 원래의 블록으로 돌아와 남은 내용을 실행합니다.

함수의 목적

#include <iostream>
using namespace std;

int f(){
	cout << "A";
	return 0;
}

int main(){
	cout << "B";
	f();
	cout << "C";
	f();
	cout << "D";
	return 0;
}

이 소스코드에서의 실행 결과
B -> A -> C -> A -> D 와 같이, 똑같은 함수를 여러번 실행할 수 있다는것이 함수의 주된 사용 목적입니다. 지금은 A 의 출력이라는 간단한 역할을 부여받은 함수이지만, 보다 복잡한 처리를 함수로 분리해 낼 수록 이를 재사용 가능하다는것이 큰 장점으로 작용합니다.

함수와 인자의 전달

매번 똑같은 작동만을 하는 함수는 자주 사용할 일이 적어지게 되기에,
하나의 함수가 보다 다양한 일을 처리할 수 있도록 "인자", 매개변수를 넘겨주게 됩니다.

수학에서의 f(x) 에 x라는 값이 바뀔 수 있듯이, C++에서의 함수에도 원하는 변수를 전달할 수 있습니다.

#include <iostream>
using namespace std;

int print(int K){
	for(int i=0; i<K; i++){
		cout << "*";
	}
	return;
}

int main(){
	print(5);
	return 0;
}

위 프로그램의 결과는 ***** 으로,
함수 print(int K) 는 K라는 숫자를 입력받아
K개의 *을 출력하는 함수입니다.

함수에 이렇게 명확한 의미를 부여하면, 문제를 해결하기가 더 간단해집니다. 백준 별 찍기 - 1 (2438번) 문제를 해당 함수로 해결하면
아래와 같습니다.

#include <iostream>
using namespace std;

int print(int K){
	for(int i=0; i<K; i++){
		cout << "*";
	}
	return;
}

int main(){
	int N;
	cin >> N;
	for(int i=1; i<=N; i++){
		print(i);
		cout << "\n";
	}
	return 0;
}

1부터 시작해 N번까지 반복하는 i는 몇번째 줄을 출력할지 결정하고,
i번째 줄에 i개의 별을 출력하고 줄내림을 합니다.

함수와 반환값

main 함수는 반환값을 사용할 일이 없어 return 0; 만을 사용하지만,
일반적인 함수나 재귀함수는 반환값을 가질 수 있습니다.

함수에서 반환값을 넘겨주는 방법은

int f(){
	return 5;
}

char f2(){
	return 'A';
}

위와 같이 return 뒤에 넘겨줄 값을 적으면 됩니다. 넘겨줄 값은 변수와 식 둘 모두 가능합니다. 이때, 넘겨주는 값은 반드시 함수의 앞에 명시한 자료형 과 맞추어야 함에 유의해주세요.

반환값의 사용은

int A = f();
cout << f();

와 같이 함수의 반환값을 변수에 대입하는 것으로 사용 가능합니다.
두번째 줄과 같이 함수의 반환값을 바로 출력할 수도 있습니다.

재귀함수와 블록

재귀함수에서는 블록을 어떻게 이동하게 될까요?

#include <bits/stdc++.h>
using namespace std;

int f(int x){
	if(x==0){
		cout << "E";
		return 0;
	}
	cout << "A";
	f(x-1);
	cout << "B";
	return 0;
}

int main(){
	f(3);
}

위 프로그램의 실행 결과는
A A A E B B B 입니다. 실행 결과에 임의로 괄호를 치면 조금 더 명확해집니다.
(A(A(A(E)B)B)B) 로 괄호를 쳐보았습니다.

A와 B 사이에 f(x-1) 이 실행되면서, 새로운 괄호가 생겨
그 안에서 다시 A와 B를 출력하는 구문이 실행되고 있습니다.
깊게 들어가다가 f(0)이 호출되는 순간 탈출 조건을 충족하고,
E 를 출력하고 탈출하면 f(0) 을 호출했던 f(1) 으로 돌아가게 됩니다.
f(1)B를 출력하고 반환하며 f(2) 로 돌아가게 되며, 이렇게 들어갔던 순서의 역순으로 나오게 됩니다.

결론

함수는 사실 solved ac 기준 실버 에서는 크게 사용할 일이 없을 수 있으나, 골드 문제나 플래티넘 이상의 문제에서 매우 높은 빈도로 사용됩니다.

특히나 재귀함수에서, 들어갈때의 반대의 순서로 나온다는 이 특징이 깊이 우선 탐색(dfs) 알고리즘에서 굉장히 적극적으로 활용됩니다.

깊이우선탐색을 사용한 문제를 해결할 수 있다는 것은 코딩테스트 등에서 요구하는 영역에 도달했다는 것으로, 재귀함수에 대해 깊게 이해해두신다면 추후 dfs에 대해 공부할 때 분명히 도움이 될 것입니다.