함수가 존재하는 이유
함수는 호출하여 원할 때 실행할 수 있는, 이름이 붙은 코드 블록입니다. 같은 로직을 여러 곳에서 반복하는 대신, 한 번만 작성하고 이름을 붙인 뒤 필요한 곳마다 그 이름을 호출합니다. 이렇게 하면 프로그램이 더 짧아지고, 읽기 쉬워지고, 고치기도 쉬워집니다. 로직을 한 곳에서 바꾸면 호출하는 모든 곳이 그 변경을 반영받기 때문입니다.
사실 여러분은 이 내내 이미 함수 하나를 호출해 왔습니다. 바로 main입니다. 이것은 모든 C++ 프로그램이 시작되는 진입점입니다. 이제 여러분만의 함수를 작성하게 됩니다. 앞에서 본 범위 기반 for 같은 반복문은 흔히 함수 안에 들어 있어, 한 덩어리의 로직을 이름으로 재사용할 수 있게 해 줍니다.
함수의 구조
모든 함수에는 네 부분이 있습니다. 반환 타입, 이름, 괄호 안의 매개변수 목록, 그리고 중괄호 안의 본문입니다.
int add(int a, int b) { // 반환 타입 | 이름 | 매개변수
return a + b; // 본문
}
int는 반환 타입으로, 함수가 돌려주는 값의 종류입니다.add는 호출할 때 사용하는 이름입니다.(int a, int b)는 매개변수로, 호출하는 쪽이 제공하는 입력입니다.- 중괄호는 본문, 즉 호출했을 때 실행되는 코드를 담습니다.
이것을 완전한 프로그램으로 만든 모습입니다. 함수를 main 위에 정의하면, main이 호출할 때 그 함수를 볼 수 있습니다.
add(2, 3) 호출은 a = 2, b = 3으로 함수를 실행하고, 식 전체가 반환된 값으로 바뀝니다. 그 값을 변수에 저장해도 되고, 두 번째 cout 줄처럼 다른 식 안에서 바로 사용해도 됩니다.
값 반환하기
return 문은 두 가지 일을 합니다. 호출한 쪽에 값을 돌려주고, 함수를 즉시 끝냅니다. return 뒤에 있는 코드는 실행되지 않습니다. 제어는 함수가 호출된 곳으로 곧장 돌아갑니다.
반환되는 값의 타입은 선언된 반환 타입과 일치해야 합니다(또는 그 타입으로 변환될 수 있어야 합니다). int로 선언된 함수는 int를 반환해야 합니다. 아무것도 반환하지 않거나 return 없이 끝을 벗어나는 것은 void가 아닌 모든 함수에서 정의되지 않은 동작입니다.
void 함수
모든 함수가 값을 만들어 내는 것은 아닙니다. 함수가 그저 무언가를 하기만 할 때(출력을 찍거나 상태를 갱신하는 등), 그 반환 타입은 void입니다. void 함수는 일찍 빠져나가기 위해 단독 return;을 쓸 수도 있고, 그냥 닫는 중괄호까지 실행할 수도 있습니다.
void 함수의 결과를 사용하려는 것(int x = greet("Ada");)은 컴파일 오류입니다. 대입할 값이 없기 때문입니다. 흔한 실수는 void 함수 안에서 return someValue;를 쓰는 것인데, 컴파일러는 이것도 거부합니다.
선언 대 정의
C++는 파일을 위에서 아래로 읽으므로, 기본적으로 함수는 그것을 호출하는 코드보다 앞에 나타나야 합니다. 그 순서가 곤란할 때는 함수를 선언(프로토타입이라고도 함)과 정의로 나눕니다.
선언은 함수의 시그니처를 밝히고 세미콜론으로 끝나며, 본문이 없습니다. 컴파일러에게 "이 함수는 존재한다. 호출하는 방법은 이렇다"라고 약속하는 것입니다. 그러면 완전한 정의는 나중에, 심지어 main 뒤에 올 수도 있습니다.
4번째 줄의 프로토타입이 없으면, 컴파일러는 square를 한 번도 본 적 없는 상태에서 main 안의 square(5)를 만나게 되고, 빌드가 실패합니다. 프로토타입은 또한 헤더 파일이 여러 소스 파일에서 같은 함수를 공유하게 해 주는 방식이기도 합니다. 선언에서 매개변수의 이름은 선택 사항임에 유의하세요. int square(int);도 똑같이 잘 동작합니다. 컴파일러에게 중요한 것은 타입뿐입니다.
흔한 실수
몇 가지 함정이 초보자를 거듭해서 걸려 넘어지게 합니다.
- 선언하기 전에 호출하기. "
addwas not declared in this scope" 오류가 난다면, 그 함수는 첫 호출보다 아래에 정의되어 있고 프로토타입이 없는 것입니다. 정의를 위로 옮기거나 프로토타입을 추가하세요. - return을 잊기.
void가 아닌 함수의 끝에return없이 도달하는 것은 정의되지 않은 동작이며, 호출한 쪽은 쓰레기 값을 받습니다. 경고를 켜고(-Wall) 컴파일하면 컴파일러가 이를 지적해 줍니다. - 정의와 호출 혼동. 정의에는 중괄호로 감싼 본문이 있고 끝에 세미콜론이 없습니다. 선언에는 세미콜론이 있고 본문이 없습니다. 이 둘을 헷갈리는 것 — 예를 들어 정의하려던 함수의 매개변수 목록 바로 뒤에 세미콜론을 붙이는 것 — 은 혼란스러운 오류를 만듭니다.
- 반환 값 무시하기.
add(2, 3);를 한 줄에 단독으로 쓰면 컴파일은 되지만, 계산된 합은 조용히 버려집니다. 함수가 반환하는 것을 실제로 사용하고 있는지 확인하세요.
// 정의처럼 보이지만, 엉뚱한 ; 때문에 이것은 선언이 되고
// 그 뒤에 떨어진 블록이 남습니다 — 자주 나오는 오타입니다:
int triple(int n); // <- 이 ;가 문장을 끝냄
{
return n * 3; // 여기서 n은 정의되지 않음; 이 블록은 이제 고아가 됨
}
다음: 함수 매개변수
함수가 매개변수 목록을 통해 입력을 받는 모습을 보았지만, 거기에는 훨씬 더 많은 이야기가 있습니다. 다음 페이지에서는 함수 매개변수를 깊이 파고듭니다. 값에 의한 전달과 참조에 의한 전달, 기본 인수, const 매개변수, 그리고 그 선택이 함수가 호출한 쪽의 데이터를 바꿀 수 있는지 여부에 어떻게 영향을 미치는지 다룹니다.
자주 묻는 질문
C++에서 함수는 어떻게 작성하나요?
반환 타입, 이름, 매개변수를 담을 괄호, 그리고 중괄호로 감싼 본문을 줍니다: int add(int a, int b) { return a + b; }. add(2, 3)처럼 이름과 인수를 붙여 호출합니다. 함수가 아무것도 반환하지 않는다면 반환 타입으로 void를 사용하세요.
C++에서 함수 선언과 정의의 차이는 무엇인가요?
선언(또는 프로토타입)은 컴파일러에게 함수의 이름, 반환 타입, 매개변수를 알려 주며 세미콜론으로 끝납니다: int add(int a, int b);. 정의는 여기에 더해 중괄호로 감싼 본문까지 제공합니다. 함수를 main보다 먼저 선언하고 나중에 정의할 수 있는데, 선언이 있으면 정의가 나타나기 전에도 그 함수를 호출할 수 있습니다.
C++ 함수가 값을 반환하지 않으면 어떻게 되나요?
void 함수라면 아무 일도 없이 그냥 끝납니다. 하지만 void가 아닌 함수에서 return 없이 끝에 도달하는 것은 정의되지 않은 동작입니다. 호출한 쪽은 쓰레기 값을 받고 프로그램이 잘못 동작할 수 있습니다. 대부분의 컴파일러가 이를 경고합니다. void가 아닌 함수에서는 모든 경로에서 반드시 값을 반환하세요.