티스토리 뷰


 본 강좌는 아래 동영상 강좌와 같이 진행됩니다. 되도록이면 동영상과 같이 보시는 것을 추천합니다.

 

유튜브 채널 가기

 

강좌 17편 동영상 보기

 


 

 이번시간에는 C언어 프로그램의 실행 단위인 '함수'에 대해 알아보도록 하겠습니다.

 

 

 

 1. 함수의 선언과 구성요소

 

 '함수'란 일종의 작은 프로그램 단위 입니다. 우리가 지금까지 사용해온 printf()나 scanf()등도 C언어에서 미리 만들어 제공한 함수입니다. 심지어는 메인 함수 'main()' 도 프로그램이 실행될때 처음으로 실행하도록 약속된 함수입니다.

 

 이처럼 '함수'는 특정한 기능을 하는 코드들을 묶어 하나의 명령어처럼 사용이 가능하게 해줍니다. 기본적으로 이미 만들어서 제공하는 printf() 같은 함수 이외에 우리가 새로운 함수를 만들 수도 있는데, 그 방법을 알아보겠습니다.

 

반환형 함수명(인자, 인자 ...)

{

코드들...

}

 

 함수는 위와 같은 형태로 선언 됩니다. 예를 들어

 

int sum(int a, int b)

{

int c = a + b;

return c;

}

 

 간단하게 a와 b를 더하여 그 결과를 반환하는 함수를 만들어 보았습니다. 실제 프로그램에서 사용할 때에는

 

int a = sum(3, 5);

 

 이와 같이 하나의 명령어처럼 사용이 가능합니다. 위의 예의 자세한 설명에 앞서 함수의 선언 위치부터 살펴보도록 하겠습니다.

 

#include <stdio.h>

 

int sum(int a, int b)

{

int c = a + b;

return c;

}

 

void main()

{

int num1 = 3, num2 = 5;

int total = sum(num1, num2);

 

printf("%d\n", total);

}

 

 전체 코드는 이런 식입니다. C언어에서는 위에서 아래 방향으로 프로그램이 실행되고, 아래에서 사용할 것들은 반드시 위에서 선언이 되어야 합니다. 위의 예처럼 sum()을 메인 함수에서 사용하기 위해서는, 메인 함수보다 위에 선언해야 합니다.

 

 하지만 이렇게 위에 많은 함수들을 선언하게 되면 보기 좋지 못하고, 메인 함수를 찾기 위해 스크롤바를 한참 내려야 할 수도 있습니다. 이런 점때문에 보통은 이런식으로 많이 사용합니다.

 

#include <stdio.h>

 

int sum(int a, int b);

 

void main()

{

int num1 = 3, num2 = 5;

int total = sum(num1, num2);

 

printf("%d\n", total);

}

 

int sum(int a, int b)

{

int c = a + b;

return c;

}

 

 이번에는 sum()을 메인 함수 아래로 내렸습니다. 그리고

 

int sum(int a, int b);

 

 이 부분을 메인 함수 위에 추가하였습니다. 이 부분을 '함수의 원형' 이라고도 하는데, 이렇게 위에 선언만 해주고 실제 함수의 내용은 아래쪽에 둘 수 있습니다. 선언만 해줄때는 마지막에 세미콜론 ';'을 반드시 붙여줘야 한다는 것을 기억해 둡시다.

 

 이제 위에서 설명하지 않았던 함수의 선언을 자세히 살펴봅시다.

 

반환값 함수명(인자, 인자 ...)

{

코드들...

}

 

 함수의 원형은 반환형, 함수명, 인자들로 이루어지고 그 아래에 함수의 기능을 구현하는 코드들을 중괄호로 묶어 가지고 있습니다. 처음부터 하나하나 알아봅시다.

 

 ◆ 반환형

 

 반환형이란 함수가 실행을 마치고 돌려주는 결과값의 자료형이고, 흔히 리턴형이라고도 합니다. 실제 반환 되는 값은 반환값, 또는 리턴값이라고도 합니다. 위의 예에서 sum()은 a와 b를 더한 후 c에 저장하고, 그 c를 return 제어문을 사용해 반환하였습니다. return문은 지금 있는 함수를 빠져나가는 역할을 하는데, 반환형이 있을 경우는 return 뒤에 변수나 상수 등을 붙여서 값을 반환할 수 있게 하는 제어문입니다.

 

 함수는 무조건 한개의 반환형을 가지고, 선언할때에는 그 자료형을 써줍니다. 만약 반환이 필요 없을 때에는 다음과 같이

 

void sum(int a, int b);

  

 'void' 자료형을 써줍니다. (이 void형에 대해서는 아래에서 자세히 설명하겠습니다.) 그리고 아래처럼

 

sum(1, 2);

 

 반환값을 반환하는 함수라도 해당 반환값을 프로그램내에서 받을 필요가 없다면 받지 않아도 상관없습니다.

  

 ◆ 함수명

 

 함수명은 그 함수의 고유한 이름이기 때문에 변수명과 비슷한 규칙이 있습니다.

 

첫글자는 알파벳으로 시작한다.

특수문자는 언더바 '_' 이외에는 사용할 수 없다.

함수들끼리는 중복된 이름을 사용할 수 없다.

 

 함수들끼리는 중복된 이름을 사용할 수 없지만, 변수와 함수는 다른 존재이기 때문에 a라는 변수를 만들었다 해서 a라는 함수를 못만드는 것은 아닙니다.

 

 ◆ 인자

 

 인자, 흔히 매게변수 혹은 파라미터(Parameter)라고도 불립니다. 선언시에는 자료형과 변수명으로 이루어지며, 실제 사용할 때에는 해당 자료형에 맞는 변수나 상수가 들어가게 됩니다.

 

선언시

int sum(int a, int b);

 

실제 사용시

c = sum(a1, a2);

  

 인자는 원하는 갯수만큼 선언할 수 있고, 필요가 없다면 써주지 않을 수도 있습니다. 하지만 함수를 사용할때에는 선언한 인자의 갯수만큼 같은 자료형의 변수나 상수를 반드시 넣어줘야 합니다.

 

선언시

int sum(int a, int b);

int output();

 

실제 사용시

c = sum(1, 2);

c = output();

  

 sum()은 int형 인자 2개로 선언이 되었으므로 두개를 넣어주었고, output()은 인자가 없으므로 넣어주지 않고 실행을 하였습니다.

 

 위의 인자가 없는 output()과 같은 경우는 명시적으로

 

int output(void);

 

 와 같이 'void'를 넣어 선언할 수도 있습니다. 반드시 붙일 필요는 없고, 사용시에도 안붙여도 상관이 없습니다.

 

 함수의 구성요소를 살펴보았으니 이제 처음 예를 든 sum()을 다시 살펴봅시다.

 

int sum(int a, int b)

{

int c = a + b;

return c;

}

 

 sum()은 int형인 인자 2개를 받고, int형을 반환하는 함수로 선언되었습니다. 인자로 선언된 a, b는 함수내에서 일반 변수처럼 사용이 가능합니다. 그래서 중괄호 안 코드를 보면 a와 b를 더한 다음 c에 저장하는 연산을 하였습니다. 마지막줄에 return 제어문을 사용해 a, b를 더한 결과값이 저장된 c의 값을 반환하고 함수는 종료됩니다.

 

 함수가 반환할 자료형이 void형이 아닌 경우, 반드시 함수가 끝날때는 return문을 사용해 값을 반환해주어야 합니다. 반환할 자료형이 void형을 경우에는 반드시 return문을 써줄 필요는 없고, 중간에 함수를 종료해야 할 경우에는 단독으로 'return' 이라고 써줍니다.

 

 

 

 2. void형

 

 함수의 반환값이 없을때는 void 라고 붙여주면 된다고 했습니다. 이 void라는 것은 '무치형' 이라고 하는 자료형으로, 아무것도 없다는 것을 명시해줄때 사용합니다. 아래 예처럼

 

void output(void);

 

 output()은 인자도 없고 반환값도 없는 함수로 선언되었습니다. 이런 형식으로 굉장히 자주 선언하게 되니 반드시 기억하시기 바랍니다.

 

 하지만 이 void가 포인터가 된다면 뜻이 정반대로 바뀌게 됩니다.

 

char a = 'A';

int b = 10;

void *p = NULL;

 

p = (void*) &a;

p = (void*) &b;

 

 위의 예에서 보듯 어떤 포인터건 간에 void*로 형전환을 시켜준다면 모두 넣을 수가 있는, 어찌보면 만능 포인터가 되어버립니다. 어떤 자료형이든 주소는 4바이트로 동일하기 때문에 이와 같은 일이 가능합니다.

 

 정리하자면

 

void : 아무것도 없다.

void* : 어떤 포인터든 넣을 수 있다.

 

 이와 같이 나타낼 수 있습니다.

 

 

 

 3. 함수의 인자 전달 방식

 

 아래 예를 봅시다.

 

int sum(int a, int b);

 

void main()

{

int num1 = 1, num2 = 3;

int a = sum(num1, num2);

 

printf("%d + %d = %d\n", num1, num2, a); 

}

 

int sum(int a, int b)

{

a += b;

return a;

}

 

실행 결과

 

1 + 3 = 4

 

 sum()은 int형 a와 b를 더해 반환하는 함수입니다. 이전 예와는 다르게 별도의 변수를 선언하지 않고, a와 b를 더하여 a에 저장하였습니다. main()에서 num1과 num2를 sum()의 인자로 넘겨주어 계산하도록 하고, 반환값을 a로 받아 출력하였습니다.

 

 출력결과를 보면 sum() 내에서 a에 b를 더한 값을 덮어씌웠음에도 num1의 값이 변하지 않은 것을 알 수 있습니다. 이러한 결과가 나오는 이유는, 함수에 인자로 어떤 값을 넘길때는 별도의 메모리 공간에 값이 복사가 돼서, 함수 내에서는 그 복사가 된 값을 가지고 사용하기 때문입니다. 이것을 '값에 의한 호출 (Call by Value)' 이라고 합니다.

 

 위의 예에서는 num1과 num2의 값이 각각 별도의 메모리에 복사가 되고, 그곳의 이름은 sum() 내에서 각각 a, b라는 이름으로 사용되었습니다. sum() 안에서 이 값을 어떻게 바꾸든 함수 밖의 값은 변하지 않습니다.

 

 이번에는 위의 예제를 조금 고쳐보겠습니다.

 

int sum(int *a, int *b);

 

void main()

{

int num1 = 1, num2 = 3;

int a = sum(&num1, &num2);

 

printf("%d + %d = %d\n", num1, num2, a); 

}

 

int sum(int *a, int *b)

{

*a += *b;

return *a;

}

 

실행 결과

 

4 + 3 = 4

 

 sum()의 인자의 형을 포인터로 바꿔줬습니다. 이렇게 주소값을 인자로 넘기면 위의 '값에 의한 호출'에 의해 num1과 num2의 주소가 sum()의 a, b에 각각 복사가 되지만, 두 주소가 같으므로 예에서 a의 값을 바꾸면 실제 변수 num1의 값도 바뀝니다.

 

 이렇게 주소값을 인자로 넘기는 것을 '주소에 의한 호출 (Call by address)'라고도 하는데, C언어에서는 사실상 값에 의한 호출만 존재하지만 편의상 그렇게 부르는 것일 뿐입니다. 앞으로 C++을 공부한다면 '참조에 의한 호출 (Call by Reference)' 이라는 것도 나오게 될 것입니다.

 

 일반적인 경우는 일반 변수를 인자로 선언하지만, 실제 변수를 조작하는 함수가 필요한 경우는 포인터로 인자를 선언합니다. 가장 대표적인 예로, 두 변수의 값을 바꾸는 함수를 들 수 있겠습니다.

 

void swap(int *a, int *b)

{

int c = *a;

*a = *b;

*b = c;

}

 

 이 함수가 만약 주소를 넘기지 않고 일반 변수를 넘겼다면 함수 안에서 아무리 변수를 바꿔도 실제 값이 변하지 않았을 것입니다. 이처럼 인자의 유형을 적절하게 결정하는 것이 함수에서는 매우 중요합니다.

 

 

 

 4. 재귀 호출

 

 '재귀 호출'이란 어떤 함수 내에서 자기 자신을 또다시 호출하는 방식을 말합니다. 이것을 설명하기 앞서 우선 간단한 함수를 하나 만들어 봅시다.

 

void output(int min, int max)

{

int num = min;

int i = 0, k = 0;

 

for (i = min; i <= max; i++)

{

for (k = 0; k < num; k++)

printf("%c", '*');

 

printf("\n");

num++;

}

}

 

 위의 예제는 min부터 max까지 '*'을 출력합니다. 실행을 해보면

 

output(1, 5); // 일때

 

*

**

***

****

*****

  

 이와 같이 min개부터 한줄에 '*'의 출력이 한개씩 증가하면서 max개까지 출력하는 기능을 합니다. 이것을 '재귀 호출'로 바꿔보겠습니다.

 

void output(int min, int max)

}

int i = 0;

 

for (i = 0; i < min; i++)

printf("%c", '*');

 

printf("\n");

 

if (min < max)

output(min + 1, max);

}

 

 훨씬 간단해진것을 볼 수 있습니다. 함수를 자세히 살펴보면 마지막 두줄을 제외하고는 한줄만 출력하는 함수입니다. 이렇게 한줄을 출력하고 if문을 만나는데, min이 max보다 작으면 자기 자신을 다시 실행하도록 하고 있습니다. 여기서 첫번째 인자에 1을 더해 넣어주었습니다. 실행을 해보면 재귀호출을 사용하지 않은 첫번째 예제와 같은 결과가 나옵니다. 이렇게 재귀호출을 사용하면 코드가 간결해지는 경우가 많습니다.

 

 그러면 위 함수의 실행 과정을 알아봅시다. 아래 그림을 봅시다.

 


 그림처럼 함수의 실행이 끝나면 자기를 호출한 위치의 다음부터 실행이 되는데, 재귀호출은 그것을 이용한 하나의 사용 방법일 뿐입니다. 자기 자신을 호출한다고 앞서 이야기했지만 그것은 실제로는 자기 자신이 아니라 임의의 공간에 생성된 자기와 똑같은 함수일 뿐입니다.

 

 재귀호출은 보통 위의 예처럼 작은 단위의 작업이 반복되는 곳에 많이 쓰입니다. 재귀호출을 이용한 함수를 작성할때에는 무한 루프에 빠지지 않도록 함수가 잘 종료될 수 있게 하여야 합니다.

 

 C언어는 함수 단위로 프로그램이 실행 됩니다. 우리가 지금까지 프로그래밍을 한 곳 조차 '메인 함수' 였었습니다. 이처럼 C언어에서 함수가 차지하는 비중이 높기 때문에 사용법을 잘 알아둘 필요가 있습니다.

 

 다음시간에는 전역 변수와 정적 변수에 대해 알아보고, 성적관리 프로그램에 함수를 적용해 보도록 하겠습니다.

 

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30