C언어 공부 0006
함수의 필요성
- 프로그램이 커질수록 그 내용을 읽기가 힘들다.
- 즉, 프로그램도 줄거리를 나열하듯이 작성해야 하는데, 이렇게 작성하기 위해서는 함수가 필요하다.
모듈
- 독립되어 있는 프로그램의 일부분이다.
- 모듈을 이용하면 유지보수와 재사용이 쉬워진다.
- 코드의 중복 또한 줄여준다.
함수
- C언어에서 함수는 모듈과도 비슷한 개념을 가진다.
- 즉, 특정한 작업을 분리하여 독립적으로 만들어둔 것이다.
- 함수는 입력과 반환을 가질 수 있다.
- 함수 정의시의 인자값들은 parameter라고 한다.
- 프로그램에서 함수를 호출할 때 들어가는 값들은 argument라고 한다.
- argument는 함수로 값이 복사되어 사용된다.
- 반환값은 함수 이름앞에 명시한 자료형의 타입을 가져야한다.
- 함수의 가장 마지막 부분에서 반환하고 싶은 값 앞에
return
을 사용한다. - 값을 반환하고 싶지 않으면
void
를 사용한다.
- 함수의 가장 마지막 부분에서 반환하고 싶은 값 앞에
- 함수 정의시의 인자값들은 parameter라고 한다.
- 기본적인 형태
자료형 함수이름(입력값들)
{
수행할 내용
return 반환값;
}
C언어의 기본 프로그램으로 알아보는 함수들
- 가장 기본적인 프로그램으로 “Hello World!!!”를 출력하는 프로그램이 있다.
#include <stdio.h>
int main()
{
printf("Hello World!!!\n");
return 0;
}
- 위 프로그램에서,
main()
도 함수이다.- 입력값은 없지만, 반환값은 0을 반환 하는것을 알 수 있다.
- main함수는 프로그램에서 가장 먼저 호출되는 함수이다.
printf("Hello World!!!\n")
;도 함수이다.- printf함수는 입력값이
"Hello World!!!"
인 것을 알 수 있다. - 다른 변수에 함수 결과를 대입하거나, 다른 연산에 사용하지 않으므로 반환값은 없다는 것을 알 수 있다.
- 대신, 함수의 수행 내용이 화면에 입력된 값을 출력하는 것이다.
- 이렇게 프로그램 안에서 함수를 이용하는것을 호출 한다고 한다.
- printf함수는 입력값이
함수의 종류
- 사용자 정의 함수 : 사용자가 직접 만들어 사용하는 함수로, 위에서 함수의 기본적인 형태를 이용하여 만들 수 있다.
- 라이브러리 함수 : 라이브러리(즉, 헤더파일)에 만들어 져 있는 함수이다.
- 시스템 라이브러리 함수 : C언어 자체에 있는 라이브러리에 있는 함수이다.
#include <라이브러리 이름.h=""></code>를 이용한다.라이브러리>
- 위의 프로그램에서 printf함수는 stdio.h라는 헤더파일에 정의되어있다.
- 사용자 라이브러리 함수 : 사용자가 직접 만든 라이브러리에 있는 함수이다.
#include "라이브러리 이름.h"
를 이용한다.
- 시스템 라이브러리 함수 : C언어 자체에 있는 라이브러리에 있는 함수이다.
자주 사용되는 라이브러리 함수
- stdio.h
printf("%x or 문장")
: 콘솔창에 원하는 값이나 문장을 출력하는 함수%x
에서 x는 값의 자료형에 따라 다르다.
scanf("%x", &n)
: 콘솔창을 통해 사용자에게 값을 입력받는 함수%x
에서 x는 값의 자료형에 따라 다르다.&n
에서 n은 사용자에게 입력받고자 하는 변수이며, &는 변수의 주소를 뜻한다.
- math.h
pow(x, y)
: xy를 구하는 함수sqrt(x)
: 루트x 값을 구하는 함수
- stdlib.h
rand()
: 난수를 생성하는 함수
변수의 범위
- 함수를 처음 사용하다보면, 자연스레 ‘왜 main함수에서 선언한 함수를 내가 만든 함수에서 못쓰지?’ 하는 생각이 들게 된다. 그 이유는, 변수의 작동범위가 있기 때문인데, 변수의 범위에 따라 지역변수와 전역변수로 나눌 수 있다.
- 지역변수 : 지역적, 즉 선언된 위치(블럭, 함수) 안에서만 작동하는 변수이다.
- 전역변수 : 전역적, 즉 어디서나 사용할 수 있는 변수이다.
- 물론, 위 변수들은 사용하기 전에 항상 선언이 되어있어야 한다.
- 예
#inclue <stdio.h>
#define a 15
void printA()
{
char c = A;
printf("%c\n", c);
}
int main()
{
int b = 20;
printf("a = %d, b = %d, c = %c\n", a, b, c); // ERROR
return 0;
}
- 위 예시코드에서 main함수의 출력문이 문제가 없을 것 같지만, 실행시켜보면 오류가 발생할 것이다.
- a는
#define
을 이용하게 되면 매크로라는 기능(매크로 이름을 적으면 자동으로 그 값으로 대체해주는 역할)이지만, 여기서는 변수로 보기로 하고, 프로그램 구조에서 최상단부(어떤 함수에도 속하지 않고, 밖에 위치)에 있으므로, 어디서나 사용할 수 있다. - b는 main함수 안에 있고(지역변수), 출력문 또한 main함수에 있으므로 사용이 가능하다.
- 그러나 c는 코드상의 위치로는 main함수보다 먼저 선언되었지만, 그 위치가 printA라는 함수 안에 있으므로(지역변수), 이는 printA함수 안에서만 사용할 수 있는 것이다. 즉, main함수에서는 c가 무엇인지 알 수 없다.
- a는
변수의 생존기간
- 변수의 생존기간은 보통 자신이 속한 구역(블럭)에서만 사용될 수 있다.
- 그러나, 저장 유형 지정자를 변수 선언부의 가장 앞에 써주면, 변수의 생존기간을 바꾸는 것이 가능하다.
- 저장 유형 지정자의 종류
register
: 레지스터에 변수를 저장하여 프로그램 동작속도를 높일 수 있지만, 레지스터의 공간은 제한적static
: 블록에서만 사용되지만 블록을 벗어나도 자동으로 삭제되지 않는 변수extern
: 다른 곳에서 선언된 변수를 현재 위치에서 사용하기 위해 사용
프로토타이핑
- 컴파일러에게 함수에 대하여 미리 알리는 것이다.
- 기본적인 형태
#include <stdio.h>
자료형 함수이름(입력값들);
int main()
.
.
.
자료형 함수이름(입력값들)
{
수행할 내용
return 반환값;
}
- 프로토타이핑이 필요한 이유
- 개발자가 가장 먼저 보게되는 것은 프로그램의 흐름인데, 프로그램의 흐름은 main함수에 드러나기 때문이다. main함수를 빨리 찾을 수 있다는 장점과 동시에, 프로그램에서 이러한 함수들을 사용할 것이라는 정보 또한 빠르게 확인이 가능하다.
- 특수한 경우에는 프로토타이핑이 있어야만, 작동시킬 수 있기 때문이다.
- 함수가 서로를 호출하는 경우에, 만약 프로토타이핑이 없다면 먼저 선언된 함수는 이후에 선언되는 함수를 호출할 수 없기 때문이다.
Recursion(재귀)
- 재귀함수란 함수가 자기자신을 호출하는 함수를 말한다.
- 간단하게는 수학에서 점화식이라고 생각할 수 있다.
- 형태
DataType FunctionName(parameter)
{
if(조건식)
{
return 재귀를 멈추는 부분;
}
else
{
return FunctionName(argument);
}
}
- 대부분의 재귀는 반복으로 변환할 수 있지만, 변환할 수 없는 경우도 있다.
- 또한, 이렇게 반복으로 변환할 수 있는 경우, 반복문이 보통 수행속도가 빠르다.
- 종류
- direct recursion : 자기자신을 부르는 재귀함수
- indirect recursion : 두 개 이상의 함수가 서로를 부르는 재귀함수
- 예 : 홀짝 판별
- tail recursion : parameter만 계산하고, 함수에서는 계산식이 없는 재귀함수
- optimize compiler가 자동으로 loop로 변환을 할 수 있음
예제를 통한 함수 연습
주어진 수의 절댓값 구하기
- 코드
#include <stdio.h>
double absval(double val)
{
if(val >= 0)
{
return val;
}
else
{
return -1 * val;
}
}
int main()
{
double val = -10;
printf("The absolute value of %lf is %lf.\n", val, absval(val));
return 0;
}
- 출력
The absolute value of -10 is 10.
세개의 수 중, 가장 큰 값 찾기
- 코드
#include <stdio.h>
int max2(int a, int b)
{
if(a > b)
{
return a;
}
else
{
return b;
}
}
int max3(int a, int b, int c)
{
return max2(max2(a, b), c);
}
int main()
{
int a = 2;
int b = 7;
int c = 5;
printf("Maximum value is %d.\n", max3(a, b, c));
return 0;
}
- 출력
Maximum value is 7.
재귀를 이용한 1부터 n까지 자연수의 합 구하기
- 코드
#include <stdio.h>
int sum(int n)
{
if(n <= 0)
{
return -1;
}
if(n == 1)
{
return 1;
}
else
{
return n + sum(n - 1);
}
}
int main()
{
int n = 5;
printf("The sum of 1 to %d is %d.\n", n, sum(n));
return 0;
}
- 출력
The sum of 1 to 5 is 15.
재귀를 이용한 피보나치 수열의 n번째 값 구하기
- 코드
#include <stdio.h>
int fib(int n)
{
if(n <= 0)
{
return -1;
}
if(n == 1 || n == 2)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
int main()
{
int n = 6;
printf("%d-th fibonacci number is %d.\n", n, fib(n));
return 0;
}
- 출력
6-th fibonacci number is 8.
direct recursion을 이용한 n! 구하기
- 코드
#include <stdio.h>
int factorial(int n)
{
if(n <= 1)
{
return 1;
}
else
{
return n * factorial(n - 1);
}
}
int main()
{
int n = 4;
printf("%d! = %d\n", n, factorial(n));
return 0;
}
- 출력
4! = 24
tail recursion을 이용한 n! 구하기
- 코드
#include <stdio.h>
int factorial(int a, int n)
{
if(n == 0)
{
return a;
}
else
{
return factorial(a * n, n - 1);
}
}
int main()
{
int n = 4;
printf("%d! = %d\n", n, factorial(n));
return 0;
}
- 출력
4! = 24