Function
함수
- C++에서의 프로그래밍 Module(모듈)이라 할 수 있다.
- 컴파일러가 제공하는 Library Function(라이브러리)과 프로그래머가 직접 정의하는 User-defined Function(사용자 정의 함수)로 구분지을 수 있다.
User-defined Function (사용자 정의 함수)
- 프로그래머가 직접 정의한 함수이다.
- Function Definition(함수 정의), Function Prototype(함수 원형), Call the Function(함수 호출)과 같은 전 과정을 프로그래머가 직접 구현해야 한다.
Parameter (매개변수)
- 호출한 함수와 호출된 함수 사이를 오가는 데이터이다.
* Formal Parameter (형식 매개변수) : 전달되는 값을 대입받는 데 쓰이는 변수를 의미하며, 이를 줄여서 "Parameter"라 한다.
* Actual Argument (실제 매개변수) : 함수에 전달되는 값 그 자체를 의미하며, 이를 줄여서 "Argument"라 한다.
ex) 함수에 매개변수를 전달하는 것은 Parameter에 Argument를 대입하는 과정이다.
- 형식 매개변수를 포함하여 함수 안에서 선언된 모든 변수는 해당 함수와 생애주기를 함께한다.
(이러한 변수들을 Local Variable(지역 변수)이라 부르며 Automatic Variable(자동 변수)라 부르기도 한다.)
- 이렇게 사용할 범위 내에서만 변수의 생애주기를 설정하는 방법은 Data Integrity(데이터 무결성)을 높여준다.
Function Definition (함수 정의)
- 리턴 데이터 타입, 함수 이름, 매개변수의 데이터형과 이름, 함수 동작 내용이 정의되어 있는 부분이다.
typeName functionName (parameterList) {
Statement(s)
return;
}
* 함수는 리턴값의 유무에 따라 두 가지로 나눌 수 있는데 특히, 리턴값이 없는 함수를 "type \(\texttt{void}\) functions"("\(\texttt{void}\)형 함수")라고 부른다.
- \(\texttt{void}\)형 함수에서는 \(\texttt{return}\) 구문을 생략할 수 있으며 \(\texttt{return}\) 구문이 없다면 함수는 닫는 중괄호에서 끝나게 된다.
- \(\texttt{return}\)형 함수는 Pascal의 "Procedures", FORTRAN의 "Subroutines", BASIC의 "Subprogram Procedures"에 해당된다.
Function Prototype (함수 원형)
- \(\texttt{main()}\) 함수가 수행되기 전에 컴파일러에게 정의된 함수의 인터페이스를 미리 알리는 역할을 한다. (에러를 줄인다.)
- 프로그램이 함수에 매개변수를 전달하는 데 실패하면, 컴파일러는 함수 원형에 근거하여 에러를 검출한다.
- 함수 원형은 하나의 구문이므로 세미콜론으로 마무리되어야 한다.
- 함수 원형 부분에서는 매개변수의 데이터형만 지정하는 것으로 충분하며 매개변수 이름까지 지정할 필요가 없다.
(그러나, 다수의 매개변수 중 같은 데이터형이 존재할 경우, 구분을 위해 이름을 제시하는 것이 프로그램 가독성에 좋다.)
- 함수 원형 부분에서 정의한 매개변수의 이름과 함수 정의 부분에서의 매개변수 이름은 서로 반드시 일치하지 않아도 된다.
- C++에서는 함수의 매개변수를 입력하는 소괄호 내부가 비어있으면 자동으로 \(\texttt{void}\) 키워드가 들어있다고 간주한다. 또한 소괄호 내부를 Ellipsis(생략부호, "...")로 채우면 매개변수 정보를 밝히는 것을 거부함을 의미한다.
(생략 부호의 사용은 \(\texttt{printf()}\) 함수와 같이 매개변수의 수가 가변적인 C 함수와의 인터페이스에만 요구되는 경향이 있다.)
#include <headerName>
...
typeName functionName (parameterList); // Function Prototype
void main() {
...
}
// Function Definition
* 함수 원형의 기능
- 컴파일러가 함수의 리턴값을 바르게 처리할 수 있게한다.
- 사용자가 정확한 개수의 매개변수, 정확한 데이터형의 매개변수를 사용했는지 컴파일러가 검사할 수 있게한다.
(사용자가 정확하지 않은 데이터형을 사용했다면, 컴파일러가 알아서 정확한 데이터형으로 변환한다.)
* Static Type Checking (정적 데이터형 검사) : 컴파일 시에 이루어지는 함수 원형 비교를 의미한다. 정적 데이터형 검사는 프로그램 실행 중에는 잡기 어려운 에러들을 컴파일 시에 잡아내는 역할을 한다.
* 컴파일러가 프로그래머로부터 함수 원형을 제공받지 않고, 직접 조사하게 될 경우의 문제점
- 컴파일러가 해당 함수를 찾기 위해 소스코드를 뒤지는 동안 \(\texttt{main()}\) 함수의 컴파일을 잠시 보류해야 한다는 점에서 컴파일 시간 측면에서 비효율이 발생한다.
- 최악의 경우, 해당 함수가 현재 소스코드가 아닌 다른 파일에 정의되어 있는 경우, 해당 소스코드를 컴파일 할 수 없게 된다. (C++에서는 하나의 프로그램을 여러 파일로 분할할 수 있으며, 이 파일들은 독립적으로 컴파일 된 후에 합쳐진다.)
- 이에 대한 유일한 해결책으로 함수를 처음 사용하는 부분보다 앞서서 함수를 정의하는 방법인데, 이 방법은 항시 가능한 방법이 아니다.
- 일반적으로 C++에서는 프로그램의 전체 구조를 \(\texttt{main()}\) 함수가 제공하기 때문에 프로그램의 맨 앞에 \(\texttt{main()}\) 함수를 작성하는 것이 관례이다.
* ANSI C와 C++에서의 함수 원형 비교
- ANSI C는 C++로부터 함수 원형의 개념을 차용했다.
- ANSI C는 클래식 C언어와의 호환성을 위해 함수 원형 사용이 선택적이나, C++에서는 함수 원형 선언이 필수적이다.
- ANSI C에서는 매개변수 정보가 담긴 소괄호 내부를 비워놓으면 매개변수 정보를 은폐하는 것을 의미하며, C++에서 이것은 \(\texttt{void}\) 키워드가 들어있음(매개변수로 아무것도 받지 않는)을 의미한다.
Return Value (리턴 값, 반환 값)
- 함수가 최종적으로 반환하는 값을 의미하며 보통 값을 반환하는 데에는 \(\texttt{return}\) 구문을 이용한다.
(조건문과 같은 구문들을 통해 함수 내에 다수의 \(\texttt{return}\) 구문을 위치시키는 것은 가능하나, 혼동을 가져올 우려가 있어 권장되지 않으며, 어떤 컴파일러는 이에 대한 경고 메세지를 출력 하기도 한다.)
- 리턴 값으로 상수나 변수 또는 일반적인 표현식이 될 수 있다.
- 배열 자체는 리턴값으로 사용될 수 없으나, 구조체나 객체의 일부로 되어 있는 배열은 리턴할 수 있다.
* C++에서는 정의된 반환 데이터형이 아닌 다른 데이터형을 반환하는 경우에는 컴파일러가 정의된 반환 데이터형으로 데이터형 변환을 수행한 다음 반환한다.
ex) 리턴형이 \(\texttt{double}\)형으로 선언되어 있는 함수에서 \(\texttt{int}\)형 데이터를 반환한다면, 그 데이터는 컴파일러에 의해 \(\texttt{double}\)형으로 변환되어 리턴된다.
+ 그러나 Function Overloading(함수 오버로딩)은 자동 변환이 이루어지지 못하게 방해한다.
+ 또한, 컴파일러가 발생 가능한 모든 경우를 자동 변환을 통해 적절히 해결하는 것은 아니다. (본래 리턴형보다 큰 데이터나 다른 카테고리의 데이터형(가령, 정수형과 구조체의 경우)일 경우에는 자동 변환이 불가능하다.)
* 함수가 값을 리턴하는 구체적인 방법
- 일반적으로 함수는 자신의 리턴값을 CPU의 지정된 레지스터나 메모리에 복사하는 방법으로 리턴한다.
- 그 과정에서 함수를 호출한 프로그램은 해당 레지스터나 메모리에 무슨 값이 있는지를 조사하는데, 그 때문에 리턴하는 함수와 호출하는 함수는 주고받을 데이터형을 일치시켜야 한다.
- Function Prototype(함수 원형)은 호출한 프로그램에게 그 위치에 어떤 타입의 데이터가 놓일지 알려주는 역할을 한다.
- Function Definition(함수 정의)은 리턴하는 함수에게 어떤 타입의 데이터를 리턴해야 하는지를 알려주는 역할을 한다.
* 중간 계산값 처리방법
- 프로그램이 복잡한 산수과정을 통해 값을 계산하는 과정에서 중간값(최종적으로 리턴하는 값이 아닌)이 매우 커지거나 작아지게 되면 의도치 않게 Underflow나 Overflow가 발생할 수 있으므로 중간값의 절댓값이 커지지 않게 계산과정을 조정해주는 작업이 필요하다.
- 중간 계산값이 적절한 범위내에 오도록, 곱셈과 나눗셈이 반복되는 과정에서는 한 번에 모든 곱셈이나 모든 나눗셈을 한꺼번에 처리하지 말고 곱셈과 나눗셈을 적절히 섞어서 중간값이 너무 커지지 않도록 처리하도록 해야한다.
(10 * 9) / (2 * 1) // 이 방법은 곱셈을 한꺼번에 처리한다. (Not good)
(10 / 2) * (9 / 1) // 이 방법은 나눗셈을 먼저 수행해서 값을 낮춘 다음 곱셈을 진행한다. (Better)
// 두 수식의 처리과정은 상이하지만, 결과값은 45로 동일하다!
함수 포인터 (Function Pointer)
- 다른 포인터들 처럼, 함수의 주소는 그 함수에 해당하는 바이너리 코드가 저장되어 있는 메모리 블록의 시작주소이다.
- 배열과 같이, 소괄호를 제외한 함수이름은 그 자체로 함수의 주솟값을 저장하고 있는 변수와 같다.
- 함수를 다른 함수의 매개변수로 전달하려면 함수 이름만 전달하면 된다.
void think(int num);
process (think); // process()에 think()의 주소를 전달한다.
process (think()); // process()에 think()의 리턴값을 전달한다.
- 함수를 지시하는 포인터를 선언할 때에도, 지시할 함수의 리턴형과 Signature(매개변수 리스트)를 명시해야 한다.
funcReturnType (*pointerName) (Signature);
// 실 사용 예시
double (*pt) (int); // 포인터 pt는 double형을 리턴하고,
// int형을 매개변수로 갖는 함수를 지목하는 포인터이다.
// 연산자 우선순위에 따른 적절한 소괄호 사용법
double (*pt) (int); // pt는 함수를 지시하는 포인터이다.
double *pt (int); // pt는 double형 포인터를 리턴하는 함수이다.
- 쉽게 말해, 함수의 원형을 먼저 선언한 다음, 함수 이름을 \(\texttt{(*ptr)}\) 형식으로 대체하면 된다.
- 함수 포인터와 함수 원형은 Signature와 리턴형이 일치해야 하며, 그렇지 않을 경우 오류가 발생한다.
+ 원칙적으로, 함수 포인터는 \(\texttt{(*ptr) (argument)}\) 와 같이, \(\texttt{(*ptr)}\)이 함수 이름을 대체하지만, \(\texttt{ptr}\)도 함수이름처럼 사용할 수 있게 허용된다.
(따라서, 함수 포인터라 할지라도 기존의 함수처럼 이용할 수 있게 되었다.)
(원칙적인 표기가 작성시에 다소 불편하더라도 함수 포인터를 사용하고 있음을 더욱 명시적으로 표현하는 좋은 방법이다.)
* C++11부터는 \(\texttt{auto}\) 키워드를 통해 간편한 작성이 가능하다.
const double* f1 (const double*, int);
const double* f2 (const double*, int);
const double* f3 (const double*, int);
const double* (*p1) (const double*, int) = f1;
auto p2 = f2; // 위 구문과 같은 기능
const double* (*pa[3]) (const double*, int) = {f1, f2, f3};
const double* px = pa[0](val, 3); // 함수 포인터 배열의 사용 예시 (val은 임의의 변수이다.)
// pa는 함수 포인터를 원소로 저장하는 배열이다.
// 여기서는 auto 키워드 사용이 불가능하다. (auto는 리스트를 초기화할 때는 사용할 수 없다.)
auto pb = pa;
// 하지만 이렇게 간접적으로 대입하는 구문은 가능하다.
- \(\texttt{auto}\) 키워드는 리스트를 초기화할 때는 사용할 수 없다.
- 함수 포인터나 배열은 표기가 복잡해보일수 있기 때문에 적절한 \(\texttt{typedef}\) 키워드를 사용했을 때 효과를 극대화할 수 있다.