함수는 간단히 statement들을 묶고 이름을 붙인 것이다. C에서 함수는 반드시 값을 리턴하지 않아도 되고, argument가 있지 않아도 된다. 

함수는 하나의 작은 프로그램이고 각각 선언과 statement를 가진다. 함수는 재사용이 가능하기 때문에 중복된 코드를 줄일 수 있다.

이제까지는 main이라는 함수만 사용했지만, 다른 함수들을 사용하게 될 것이다.


9.1 Defining and Calling Functions


두 수의 평균을 구하는 함수 average를 정의하기. 이 정의는 여기에선 main함수 바깥에, main보다 앞서서 적도록 하자. 이유는 9.2에 나올것

double average(double a, double b)

{

return (a + b) / 2;

}

double: average 함수의 return type

identifiers a and b(parameters of the function): average함수가 불려질 때 제공되는 두 숫자. 각각의 parameter 앞에 type이 따로 따로 적혀 있어야 한다(생략 불가능).

{}로 묶인 부분: body. 함수의 실행되는 부분.


함수를 호출할 때에는, 함수의 이름을 쓰고, 뒤이어 괄호 안에 arguments를 적는다. 

 

Function Definitions

return-type function-name ( parameters )

{

declarations

statements

}

return type에 대해: 배열을 return할 수는 없으나, 그 외에 제한은 없다.

함수의 type을 void로 지정하는 것은 그 함수가 어떤 값을 return하지 않는다는 것을 뜻한다.

만약 return-type을 생략하면 C89는 int로 간주한다. C99에서는 생략을 허용하지 않는다. 하지만 VS2015에서는 C89 방법을 따르는 듯 하다.


Function Calls

average(x, y)

print_count(i)

print_pun()    // void function

괄호가 뒤에 붙지 않으면 - average, print_count 오류가 나지는 않지만 아무일도 일어나지 않는다. 함수 호출이 되지 않는다.

void function 뒤에는 항상 세미콜론(;)을 붙여 statement로 만든다. 다른 type의 function은 어떤 값이 만들어지기 때문에 다른 변수에 넣거나, 프린트하거나 등등으로 사용할 수 있다. 

non-void function 뒤에 세미콜론이 붙어 statement가 되면, 어떤 값이 만들어지지만 쓰이지 않고 버려진다.

함수의 값이 버려진다는 것을 명확히 나타내기 위해서 함수 호출 앞에 (void) 라고 붙여줄 수 있다. "casting to void" = "throwing away"

서로 다른 함수 안에서 같은 이름의 변수가 선언될 때, 각각의 변수는 서로 다른 메모리에 저장되므로 서로의 값에 영향을 끼치지 않는다.

함수 내에 return statment가 여러개 올 수 있다. 그러나 그 중 단 하나만 실행 가능하다. 함수 내에서  return statement가 실행되면 함수는 그 값을 return하고 끝낸다.


9.2 Function Declarations


앞의 예에서 순서를 바꿔, main 함수가 앞에 오고, main 안에서 쓰이는 average 함수가 뒤에 오도록 배치해 보자. main에서는 그 함수에 대한 정보가 없다. 이 경우 컴파일러는 그 함수가 int value를 return한다고 간주한다. 이를 implicit declaration이라고 함. 그러나 사실은 average 함수의 return value는 double이기 때문에, 결국 오류가 발생한다. main 함수를 앞에 배치하면서 이같은 오류를 방지하기 위해 function declaration이 필요하다. 함수를 호출하기 전에 선언(declare)만 해 주면서 콤파일러에 그 함수의 간략한 정보를 미리 전달하는 것.

return-type function-name ( parameters ) ;

이 형태의 function declaration을 function prototype이라고 부른다. 이 용어는 옛 버전의 C에서 사용했던 스타일 - 괄호 안을 비워놓음 - 과 구분하기 위함. 

parameter의 경우에는, name은 생략하고 type만 입력해도 된다. 

ex) double average(double, double);



9.3 Arguments


parameters: 함수를 정의할 때 사용

arguments: 함수를 호출할 때 사용

두 용어의 차이에 대한 설명: http://ohgyun.com/410

의미를 엄격히 구분해서 사용하지는 않고, argument를 두 가지 모두의 의미로 사용하기도 함.

arguments are passed by value. 값이 복사되어 전달되며, 함수 내부에서 그 값이 바뀌더라도 argument의 복사된 값이 바뀌는 것이고 그 변수 자체는 바뀌지 않음. 

예를 들어 x라는 변수를 함수의 argument로 쓰고, 함수 내부에서 그 값을 바꿔버려도, 원래 x의 값은 변하지 않고 그대로 있다. 

Argument Conversions

parameters의 type과 맞지 않는 type이 arguments로 사용되는 것을 허용한다. 이 때 arguments를 형변환하게 되는데 규칙은 함수 호출에 앞서 prototype이나 정의가 있었는지 없었는지에 따라 다르다.

-호출에 앞서 prototype이 있었던 경우

각 arguments의 값은 parameters의 type에 맞게 암시적 형변환된다. 

-호출에 앞서 prototype이 없는 경우(이 방법은 안전하지 않다)

default argument promotion이 이루어짐. float는 double로, char나 short는 int로(integral promotion). C99에서는 integer promotion


Array Argmuments

배열은 종종 arguments로 사용된다. 배열의 parameter가 1차원 배열인 경우, 배열의 길이는 지정되지 않아도 되며 보통 지정하지 않는다.

int f(int a[])

그러나 배열의 길이를 전달은 해야 하는데 

int f(int a[], int n)과 같이 두번째 파라미터로 배열의 길이를 지정해 준다.

다차원 배열의 경우에는, 첫번째 차원의 길이만 생략 가능하고 나머지는 지정해야 한다.


#define LEN 10

int sum_two_dimensional_array(int a[][LEN], int n) {

...

}


9.4 The return Statement


non-void function의 경우 반드시 return statement를 통해 어떤 값이 return 되어야 한다.

return expression ;

return n >= 0 ? n : 0;

이 statement는 n이 0보다 크거나 같으면 n을, 아니면 0을 리턴한다.

void function에서는

return;

이렇게 뒤에 아무런 expression 없이 나타나기도 한다.



9.5 Program Termination


The exit Function

main 함수 내에 return statement를 넣는 것 외에 프로그램을 종료시키는 방법에는 exit 함수를 호출하는 것이 있다(stdlib.h에 속함). exit에 전달된 argument는 main의 return value와 동일한 의미(종료 시점의 프로그램 상태)이다. 0(=EXIT_SUCCESS)은 정상적인 종료를, 1(=EXIT_FAILURE)은 비정상적인 종료를 의미한다.


exit(EXIT_SUCCESS);    /* normal termination */

exit(EXIT_FAILURE);    /* abnormal termination */

return expression;

exit(expression);    /* main안에서는 두 statement는 동일하다.


9.6 Recursion


자기 자신을 호출하는 함수를 재귀적(recursive)이라고 한다. factorial이나 power를 구하는 함수에서 이용할 수 있다. 어떤 언어는 recursion에게 굉장히 의존하고, 허용조차 하지 않는 언어도 있는데 C언어는 그 중간 쯤에 있다.


/* function computes factorial of n recursively */

int fact(int n)

{

if (n <= 1)

return 1;

else

return n * fact(n - 1);

}


/* power function using conditional expression */

int power(int x, int n)

{

return n == 0 ? 1 : x * power(x, n - 1);

}


The Quicksort Algorithm

사실 위에 예를 든 두 함수는 재귀가 굳이 필요하지 않다. 재귀는 자기 자신을 두번 혹은 그 이상 호출하는 복잡한 알고리즘에서 매우 유용하게 쓰인다.

divide-and-conquer: 큰 문제를 같은 알고리즘이 적용되는 작은 조각으로 나누어 해결하는 것. 가장 고전적인 예로 Quicksort algorithm이 있다.

1부터 n까지의 index가 있는 배열을 quicksort하는 방법:

1. 배열의 원소 e(partitioning element, 칸막이 원소?)를 선택하고, 원소 1, 2, ..., i-1 은 e보다 같거나 작고, 원소 i, i+1, ..., n은 e보다 크거나 같도록 배열을 재배열한다.

2. 1, ..., i-1까지의 원소를 Quicksort로 재귀적으로 sort한다.

3. i, ..., n까지의 원소를 Quicksort로 재귀적으로 sort한다.






+ Recent posts