C Study 0006

C언어 공부 0006

함수의 필요성

  • 프로그램이 커질수록 그 내용을 읽기가 힘들다.
  • 즉, 프로그램도 줄거리를 나열하듯이 작성해야 하는데, 이렇게 작성하기 위해서는 함수가 필요하다.

모듈

  • 독립되어 있는 프로그램의 일부분이다.
  • 모듈을 이용하면 유지보수와 재사용이 쉬워진다.
    • 코드의 중복 또한 줄여준다.

함수

  • C언어에서 함수는 모듈과도 비슷한 개념을 가진다.
  • 즉, 특정한 작업을 분리하여 독립적으로 만들어둔 것이다.
  • 함수는 입력과 반환을 가질 수 있다.
    • 함수 정의시의 인자값들은 parameter라고 한다.
      • 프로그램에서 함수를 호출할 때 들어가는 값들은 argument라고 한다.
      • argument는 함수로 값이 복사되어 사용된다.
    • 반환값은 함수 이름앞에 명시한 자료형의 타입을 가져야한다.
      • 함수의 가장 마지막 부분에서 반환하고 싶은 값 앞에 return을 사용한다.
      • 값을 반환하고 싶지 않으면 void를 사용한다.
  • 기본적인 형태
자료형 함수이름(입력값들)
{
    수행할 내용
    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!!!"인 것을 알 수 있다.
    • 다른 변수에 함수 결과를 대입하거나, 다른 연산에 사용하지 않으므로 반환값은 없다는 것을 알 수 있다.
    • 대신, 함수의 수행 내용이 화면에 입력된 값을 출력하는 것이다.
    • 이렇게 프로그램 안에서 함수를 이용하는것을 호출 한다고 한다.

함수의 종류

  • 사용자 정의 함수 : 사용자가 직접 만들어 사용하는 함수로, 위에서 함수의 기본적인 형태를 이용하여 만들 수 있다.
  • 라이브러리 함수 : 라이브러리(즉, 헤더파일)에 만들어 져 있는 함수이다.
    • 시스템 라이브러리 함수 : C언어 자체에 있는 라이브러리에 있는 함수이다.
      • #include <라이브러리 이름.h=""></code>를 이용한다.
      • 위의 프로그램에서 printf함수는 stdio.h라는 헤더파일에 정의되어있다.
    • 사용자 라이브러리 함수 : 사용자가 직접 만든 라이브러리에 있는 함수이다.
      • #include "라이브러리 이름.h"를 이용한다.

자주 사용되는 라이브러리 함수

  • 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가 무엇인지 알 수 없다.

변수의 생존기간

  • 변수의 생존기간은 보통 자신이 속한 구역(블럭)에서만 사용될 수 있다.
  • 그러나, 저장 유형 지정자를 변수 선언부의 가장 앞에 써주면, 변수의 생존기간을 바꾸는 것이 가능하다.
  • 저장 유형 지정자의 종류
    • 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

Reference

0%