함수 사용하기

  • 102 minutes to read

함수(Function) 또는 메서드(Method)는 재사용을 목적으로 만들어진 특정 작업을 수행하는 코드 블록입니다. 이번 강의에서는 반복되는 코드를 줄일 수 있는 함수 사용법을 알아봅니다.

> // 함수(Function): 반복해서 사용하도록 만들어진 하나 이상의 문을 포함하는 코드 단위(블록)

함수(Function)

프로그래밍하다 보면 같은 유형의 코드가 반복되는 경우가 많습니다. 이런 코드들을 매번 입력하면 불편하고 실수할 수도 있습니다. 이럴 때 함수를 사용하면 편리합니다. 함수는 어떠한 동작 및 행위를 표현하며, 코드 재사용을 목적으로 합니다. 한 번 만들어 놓은 함수는 프로그램에서 한 번 이상 사용될 수 있습니다. 지금까지 사용해 온 Main() 메서드는 C#의 시작 지점을 나타내는 특수한 목적의 함수로 볼 수 있습니다. 또한 Console 클래스의 WriteLine() 메서드도 함수로 볼 수 있습니다.

함수는 반복하여 사용하도록 하나의 이름으로 만들어 놓은 코드의 집합입니다. C#에서는 이러한 함수를 부를 때 함수(Function)보다는 메서드(Method)로 주로 표현합니다. 이 강의에서는 메서드(Method), 함수(Function), 서브 프로시저(Sub Procedure)를 모두 메서드로 통일하겠습니다. 단, 이번 장과 클래스 내의 또 다른 메서드를 표현할 때에는 함수로 표현하겠습니다.

  • 함수란 어떤 값을 받아서 그 값을 가지고 가공을 거쳐 어떤 결괏값을 반환시켜주는 코드입니다.
  • 함수는 프로그램 코드 내에서 특정한 기능을 처리하는 독립적인 하나의 단위 또는 모듈을 가리킵니다.

참고로, 오래된 프로그래밍 책에서는 함수와 메서드와 동일한 단어로 루틴(Routine)이라고도 표현하며 서브 프로시저, 서부 루틴(Sub Routine)이라고도 합니다.

입력, 처리, 출력

함수는 다음 그림의 처리를 담당하는 부분을 따로 이름 있는 코드 블록으로 지정하는 것을 말합니다.

그림: 함수의 실행 단계

입력, 처리, 출력

입력, 처리, 출력

함수의 종류(내장 함수와 사용자 정의 함수)

내장 함수는 C#이 자주 사용하는 기능을 미리 만들어서 제공하는 함수로 특정 클래스의 함수로 표현됩니다. 내장 함수는 그 사용 용도에 따라서 문자열 관련 함수, 날짜 및 시간 관련 함수, 수학 관련 함수, 형식 변환 관련 함수 등으로 나눌 수 있습니다. 이러한 내장 함수를 API(Application Programming Interface)라고 표현합니다. 내장 함수의 주요 기능들은 뒤에서 자세히 다루겠습니다. 내장 함수와 달리 사용자 정의 함수는 프로그래머가 필요할 때마다 새롭게 기능을 추가시켜 사용하는 함수입니다.

함수 정의하고 사용하기

함수 정의(Define)는 함수를 만드는 작업입니다. 함수 호출(Call)은 함수를 사용하는 작업을 말합니다. 함수 생성 및 호출은 반드시 소괄호 기호가 들어갑니다. 함수를 정의하는 모양은 지금까지 사용해 온 Main() 메서드와 유사합니다. 다음 코드 예시는 함수를 만드는 가장 기본적인 모양을 보여줍니다.

static void 함수이름()
{
    // 함수내용
}

만들어진 함수를 호출하는 방법은 다음과 같이 3가지 형태로 표현됩니다.

  1. 함수이름();
  2. 함수이름(매개 변수);
  3. 결괏값 = 함수이름(매개 변수);

함수 만들고 호출하여 사용하기

이번에는 함수의 가장 기본적인 모양을 만들고 사용해보겠습니다. 참고로 다음 코드의 Show() 메서드는 Main() 메서드 밑에 코드를 작성해도 상관 없습니다.

코드: FunctionDemo.cs

// FunctionDemo.cs
using System;

class FunctionDemo
{
    //[1] Show 메서드(함수)
    static void Show()
    {
        Console.WriteLine("Hello World");
    }

    // Main 메서드(함수)
    static void Main()
    {
        Show(); //[2] 호출
    }
}
Hello World

위 코드는 [1]번 코드 영역에 Show() 이름의 함수를 만들고 이를 Main() 메서드의 [2]번에서 호출해 보는 내용입니다. Show() 함수와 같은 형태를 가장 간단한 형태의 함수로 매개 변수(Parameter)도 없고 반환값(Return Value)도 없는 형태입니다. 잠시 후에 매개 변수와 반환값에 대해 알아봅니다.

함수 만들고 반복 사용하기

함수를 만드는 목적 중 하나는 반복 사용입니다. 함수를 만들고 여러 번 호출해서 사용하는 방법을 알아봅시다.

코드: FunctionCall.cs

//[?] 함수(Function): 반복하여 사용하도록 하나의 이름으로 만들어 놓은 코드의 집합
using System;

// 함수 정의 및 호출
class FunctionCall
{
    //[1] 함수 만들기(정의, 선언)
    static void Hi()
    {
        Console.WriteLine("안녕하세요.");
    }

    static void Main()
    {
        //[2] 함수 사용하기(호출): 여러 번 호출
        Hi(); Hi(); Hi();
    }
}
안녕하세요.
안녕하세요.
안녕하세요.

이번 예제에서는 Hi()라는 이름으로 함수를 만들고, 이를 Main() 메서드에서 3번 호출하여 사용합니다. 일반적으로 프로그래밍에서 함수 선언은 함수를 호출하기 전에 코드가 위치해야 하지만, C#에서는 Main() 메서드 앞이나 뒤에 위치해도 전혀 상관없습니다. 함수의 첫 번째 사용 목적 중 하나는 반복 사용입니다. 한 번 만들어 놓은 함수를 여러 번 호출하여 사용할 수 있습니다.

매개 변수와 반환값

함수는 동일한 기능만 수행하지 않습니다. 호출할 때마다 조금씩 다른 기능을 적용하려면 함수의 매개 변수(Parameter)를 변경하여 호출할 수 있습니다. 함수에 전달하는 값은 문자열, 숫자 등 모든 데이터 형식을 사용할 수 있으며, 인수(Argument), 인자, 파라미터(Parameter), 매개 변수 등으로 불립니다. 현재 시점에서는 이 모든 용어가 동일한 개념으로 간주됩니다.

NOTE

이 강의에서는 Parameter, Argument, 인자, 인수, 매개 변수를 모두 같은 개념으로 하나의 용어로 표현하겠습니다.

함수의 매개 변수 선언

매개 변수는 함수에 어떠한 정보를 전달하는 데이터를 나타냅니다. 콤마 기호를 기준으로 여러 개의 매개 변수를 설정할 수 있습니다.

매개 변수 없는 함수

매개 변수도 없고 반환되는 값도 없는 함수는 가장 단순한 형태의 함수입니다. 함수 이름 뒤의 괄호에 인자로 아무 값도 지정하지 않는 모양을 말합니다. 이전에 사용한 ToString() 메서드와 같이 빈 괄호만 사용하는 함수 형식이 이에 해당합니다.

매개 변수 있는 함수

특정 함수에 한 개 이상의 인자값을 전달하는 방식으로, 정수형, 실수형, 문자형, 문자열형, 개체형 등 다양한 데이터 형식을 인자값으로 전달할 수 있습니다.

반환값 있는 함수

함수의 처리 결과를 호출한 쪽으로 반환하려면 return 키워드를 사용하여 데이터를 돌려줄 수 있습니다. 반환값이 있는 함수를 펑션(Function)이라고 부르고, 반환값이 없는 함수를 서브 프로시저(Sub Procedure)라고 부릅니다.

매개 변수가 가변인 함수

C#에서는 클래스에 매개 변수의 형식과 개수를 변경하여 동일한 이름의 함수를 여러 개 만들 수 있습니다. 이를 함수 중복 또는 함수 오버로드(Overload)라고 합니다. 이에 대한 내용은 이후에 자세히 다룹니다.

매개 변수가 있는 함수

이번에는 매개 변수(Parameter)가 있는 함수를 만들고 사용해보겠습니다.

코드: FunctionParameter.cs

// 매개 변수(Parameter)가 있는 함수 만들고 호출하기 
using System;

class FunctionParameter
{
    // 매개 변수가 있는 함수
    static void ShowMessage(string message)
    {
        Console.WriteLine(message); // 넘어온 매개 변수의 값을 출력
    }

    static void Main()
    {
        ShowMessage("매개 변수"); // ShowMessage() 함수에 "매개 변수" 문자열 전달
        ShowMessage("Parameter"); // ShowMessage() 함수에 "Parameter" 문자열 전달
    }
}
매개 변수
Parameter

함수의 괄호 안에는 매개 변수를 선언할 수 있는데, ShowMessage(string message) 형태로 함수 호출시 문자열을 받아서 message 변수에 저장합니다. 함수를 호출할 때 동일한 데이터 형식을 전달해야 합니다. 함수의 매개 변수는 콤마 기호를 구분으로 하나 이상을 줄 수 있습니다.

반환값이 있는 함수

함수의 여러 가지 특징 중 하나인 반환값(Return Value)은 함수에서 어떤 처리를 진행 후 그 결과를 다시 함수를 호출한 부분으로 되돌려 주는 것을 말합니다. 되돌려주는 값은 매개 변수와 마찬가지로 C#의 모든 데이터 형식으로 줄 수 있습니다. 반환값이 있는 함수를 만들 때에는 반환값이 없다는 의미의 void 키워드 자리에 반환되는 데이터 형식을 기술하면 됩니다. 반환값이 없는 함수는 void로 지정합니다. 즉, 지금까지 사용한 C#의 기본 코드인 Main() 메서드의 void 자리는 반환값의 형을 나타냅니다.

문자열 반환값이 있는 함수 만들고 사용하기

문자열 데이터를 반환하는 함수를 만들고 호출해보겠습니다.

코드: ReturnValue.cs

// 반환값이 있는 함수 만들고 호출하기 
using System;

class ReturnValue
{
    static string GetString()
    {
        return "반환값(Return Value)"; // return 키워드로 단순 문자열 반환
    }

    static void Main()
    {
        // GetString() 함수 호출 후 반환된 반환값을 returnValue 변수에 저장
        string returnValue = GetString();
        Console.WriteLine(returnValue);
    }
}
반환값(Return Value)

GetString() 함수를 호출하면 return 키워드에 의해서 "반환값(Return Value)" 문자열이 함수를 호출한 부분으로 반환이 됩니다. 이 반환된 문자열을 returnValue 변수에 담아서 Main() 메서드에서 사용하는 내용입니다.

정수형 반환값이 있는 함수 만들고 사용하기

이번에는 int 형식의 반환값이 있는 함수를 사용해보겠습니다.

코드: FunctionReturnValue.cs

// 반환값이 있는 함수(메서드)
using System;

class FunctionReturnValue
{
    static int SquareFunction(int x)    // 입력: 매개 변수도 있고 
    {
        int r = x * x;                  // 처리: 함수의 본문
        return r;                       // 출력: 반환값도 있는 
    }
    static void Main()
    {
        int r = SquareFunction(2); // 함수 호출 및 반환값 받기 
        Console.WriteLine(r); // 4
    }
}
4 

매개 변수 x로 정수형 데이터가 넘어오면 그 값을 2번 곱해서 그 결괏값을 함수를 호출한 Main() 메서드 영역으로 되돌려주는 내용입니다. 입력(Input), 처리(Process), 출력(Output)이 있는 전형적인 함수의 유형으로 이러한 함수 스타일은 앞으로 계속해서 사용하게 될 것입니다.

두 수의 합을 구하는 함수

다음 코드는 2개의 실수 데이터를 받아서 그 합을 구한 후 그 값을 결괏값으로 되돌려주는 함수입니다.

코드: GetSumTwoNumber.cs

using System;

class GetSumTwoNumber
{
    // 두 수의 합을 구하는 함수(메서드)
    static double GetSum(double x, double y)
    {
        double r = x + y;
        return r;
    }

    static void Main()
    {
        double result = GetSum(3.0, 0.14);
        Console.WriteLine(result); // 3.14
    }
}
3.14

GetSum() 함수는 2개의 double 형식을 받아서 더한 후 그 결과를 다시 Main() 메서드로 돌려주는 형태입니다.

함수 선언 형식과 관련 용어 정리

함수(메서드) 관련 용어를 정리하겠습니다. 앞으로 계속 다룰 내용이므로 한 번만 읽고 넘어가도 좋습니다.

public static void Main(string[] args)
{

}
  • 메서드 시그니처(Method Signature): 메서드는 위 코드와 같은 구조를 가지며, 서로 다른 모습으로 보여질 수 있습니다.
  • 액세스 한정자(Access Modifier): public은 메서드가 모든 클래스에서 사용 가능하다는 것을 나타내며, private은 현재 클래스에서만 사용 가능한 메서드를 만들 수 있습니다. 지금까지는 public을 생략한 채로 코드를 작성하였습니다.
  • 정적 메서드(Static Method) vs 인스턴스 메서드(Instance Method): static 키워드가 붙으면 정적 메서드, 붙지 않으면 인스턴스 메서드입니다. 지금까지는 모든 메서드에 static을 붙였습니다. 이후 클래스를 배우고 나서 static이 없는 함수를 다룰 것입니다.
  • 반환 형식(Return Type): void 키워드는 반환값이 없음을 나타냅니다. 메서드 수행 결과를 반환할 때 사용되는 데이터 형식을 말합니다.
  • 메서드 이름(Method Name): 메서드의 이름은 영문 대문자로 시작합니다.
  • 매개 변수(Parameter): 메서드에 값을 전달하거나 받을 때 사용되는 변수(배열)를 나타냅니다. 콤마(,) 기호를 구분으로 0개 이상의 매개 변수를 둘 수 있습니다. Main 메서드에서는 string[] args를 사용하는데, 이는 명령줄 인수(Command Line Argument)라고도 합니다. 예전 운영체제인 DOS 시절에 주로 사용하던 방식으로, 실행 파일(EXE 파일)에게 인수(Parameter) 값을 넘겨줄 때 사용합니다. C#에서는 Main() 메서드의 매개 변수로 받아 사용합니다.
  • 블록(Block): 메서드의 실행 범위(Scope)를 나타내며, 메서드의 블록 내에서 선언된 변수는 해당 메서드가 종료되면 소멸됩니다.

참고: public 키워드

클래스를 만드는 코드와 함수를 만드는 코드 앞에는 public이 붙을 수도 있고 붙지 않을 수도 있습니다. 학습 내용상 현재까지는 둘다 동일합니다. 굳이 신경쓰지 않아도 됩니다. 책에서는 최소한의 코드로 보여주는 걸 추구하기에 public을 생략하는 편입니다. 액세스 한정자라 불리는 public, private, protected 등의 키워드에 대해선 뒤에서 자세히 다룹니다.

함수를 사용하여 큰값과 작은값 또는 절댓값 구하기

매개 변수도 있고 반환값도 있는 함수를 사용하여 두 수 중에서 큰 값과 작은 값을 구해 보도록 하겠습니다.

코드: MaxMinFunction.cs

// 함수를 사용하여 큰 값과 작은 값 구하기 
using System;

class MaxMinFunction
{
    // Max 함수: 두 수 중에서 큰 수를 반환시켜주는 함수
    static int Max(int x, int y)
    {
        return (x > y) ? x : y; // 3항 연산자로 큰 수 구하기 
    }

    // Min 함수: 두 수 중에서 작은 수를 반환시켜주는 함수
    static int Min(int x, int y)
    {
        // if else 문으로 작은 수 구하기 
        if (x < y)
        {
            return x; // 작은 값
        }
        else
        {
            return y; 
        }
    }

    static void Main()
    {
        Console.WriteLine(Max(3, 5)); // 5
        Console.WriteLine(Min(-3, -5)); // -5
    }
}
5
-5

Max() 함수는 2개의 정수를 받아서 그 중에서 큰 수를 반환해줍니다. Min() 함수는 2개의 정수를 받아서 그 중에서 작은 수를 반환해줍니다.

다음 코드를 사용하여 절댓값을 구할 수 있습니다.

코드: FunctionAbs.cs

> //[?] 절댓값을 구하는 함수 만들기
> static int Abs(int num)
. {
.     return (num < 0) ? -num : num;
. }
> 
> int num = -21;
> int abs = Abs(num);
> Console.WriteLine($"{num}의 절댓값: {abs}");
-21의 절댓값: 21

XML 문서 주석을 사용하여 함수에 대한 설명 작성하기

Visual Studio를 사용하여 프로그램을 작성할 때 특정 함수에 대한 사용법 등의 설명을 표시하고자할 때에는 <summary></summary> 형태로 슬래시 기호 3개를 연속한 주석 영역에 작성합니다. 이러한 것을 XML 문서 주석이라고 합니다. XML 문서 주석을 사용하면 Visual Studio의 편집기에서 해당 함수에 마우스를 올리거나 코드를 작성하는 순간에 해당 함수에 대한 설명을 볼 수 있습니다. XML 문서 주석을 사용하는 내용을 다루어 보겠습니다. XML 문서 주석은 먼저 작성하지 말고 AddNumbers() 함수를 먼저 만든 후 함수 선언 문 앞에서 /// 코드를 작성하면 자동으로 완성해 줍니다. <summary> 코드가 만들어지면 그 안에 함수에 대한 설명과 매개 변수 그리고 반환 값에 대한 설명을 넣어줍니다.

코드: FunctionAddNumbers.cs

using System;

class FunctionAddNumbers
{
    /// <summary>
    /// 두 수를 더해서 그 결괏값을 반환시켜주는 함수
    /// </summary>
    /// <param name="a">첫번째 매개변수</param>
    /// <param name="b">두번째 매개변수</param>
    /// <returns>a + b 결과</returns>
    static int AddNumbers(int a, int b)
    {
        return a + b;
    }

    static void Main()
    {
        int a = 3;
        int b = 5;
        int c = AddNumbers(3, 5);
        Console.WriteLine($"{a} + {b} = {c}");
    }
}
3 + 5 = 8

XML 문서 주석의 내용에는 해당 함수의 이름과 매개 변수들에 대한 설명 그리고 반환값을 작성할 수 있습니다. XML 문서 주석은 프로그램 작성 후 유지보수를 할 때 함수의 내용을 금방 알 수 있는 장점을 제공해 줍니다.

기본 매개 변수(Default Parameter)

메서드 선언시 매개 변수를 선언과 동시에 초기화해 놓으면 메서드 호출시 해당 매개 변수를 지정하지 않으면 기본값으로 자동으로 설정됩니다. 이러한 기능을 기본 매개 변수(Default Parameter) 또는 선택적 매개 변수(Optional Argument)라고 합니다. 이번에는 기본 매개 변수(Default Parameter)를 사용해보겠습니다.

코드: DefaultParameter.cs

using System;

class DefaultParameter
{
    static void Main()
    {
        Log("디버그");        // [A] 두 번째 매개 변수 생략
        Log("에러", 4);       // [B] 전체 매개 변수 사용
    }

    // [1] 기본 매개 변수(선택적 매개 변수): 매개 변수 선언과 동시에 초기화
    static void Log(string message, byte level = 1)
    {
        Console.WriteLine($"{message}, {level}");
    }
}
디버그, 1
에러, 4

Log() 메서드는 2개의 매개 변수를 갖습니다. 두 번째 매개 변수를 1로 초기화(byte level = 1)해 놓은 상태라서 생략하면 기본값인 1이 설정되고 생략하지 않고 값을 지정하면 해당 값으로 전달되는 형태입니다.

명명된 매개 변수

이번에는 명명된 매개 변수(Named Parameter, 명명된 인수)를 사용해보겠습니다. 명명된 매개 변수를 사용하면 함수 호출시 필요한 매개 변수 이름을 직접 지정해서 호출할 수 있는 편리함을 제공합니다.

코드: NamedParameter.cs

// 함수의 매개 변수 이름을 지정하여 호출하기 
using System;

class NamedParameter
{
    static void Main()
    {
        // 기본 형태
        Sum(10, 20); // 30

        // [1] 매개 변수 이름과 콜론(:) 기호를 사용하여 호출
        Sum(first: 10, second: 20); // 30

        // [2] 매개 변수 이름 지정하면 변수 위치 변경 가능
        Sum(second: 20, first: 10); // 30
    }

    // 명명된 인수(Named Parameter)
    static void Sum(int first, int second)
    {
        Console.WriteLine(first + second);
    }
}
30
30
30

[1]번 코드와 같이 함수 호출시 함수의 매개 변수 이름을 지정한 후 콜론(:) 기호를 붙여 값을 지정할 수 있습니다. 이렇게 하면 매개 변수의 순서와 상관없이 매개 변수의 값을 설정할 수 있습니다. [2]번 코드는 매개 변수의 순서를 변경하여 호출하는 모양을 보여줍니다.

함수(메서드) 오버로드(Overload): 다중 정의

하나의 클래스에 매개 변수를 달리해서 동일한 이름을 갖는 여러 개의 함수를 정의할 수 있는데 이를 함수 오버로드(Overload) 또는 메서드 오버로드(Overload)라 합니다. 우리말로 해석하면 함수 "다중 정의", 즉, 여러 번 정의한다는 의미로 해석해도 됩니다.

함수 오버로드 사용하기

이번에는 함수 오버로드를 사용해보겠습니다.

코드: MethodOverloadNumber.cs

// 메서드(함수) 오버로드(다중 정의) 
using System;

class MethodOverloadNumber
{
    // GetNumber 함수: int 매개 변수
    static void GetNumber(int number)
    {
        Console.WriteLine($"Int32: {number}");
    }

    // GetNumber 함수: long 매개 변수
    static void GetNumber(long number)
    {
        Console.WriteLine($"Int64: {number}");
    }

    static void Main()
    {
        GetNumber(1234);    // Int32: 1234
        GetNumber(1234L);   // Int64: 1234
    }
}
Int32: 1234
Int64: 1234

GetNumber() 함수는 int 매개 변수와 long 매개 변수 중 하나를 받습니다. 만약 매개 변수로 넘어온 데이터가 1234면 int로 받고 1234L처럼 L 접미사를 사용하면 long으로 받습니다. 이처럼 넘겨주는 매개 변수에 해당하는 함수를 자동으로 호출해 줍니다.

매개 변수가 없거나 있는 함수 오버로드

이번에는 전혀 다른 매개 변수를 전달하는 함수 오버로드 예제를 살펴보겠습니다.

코드: MethodOverload.cs

//[?] 메서드 오버로드: 동일한 이름의 메서드를 매개 변수를 달리하여 여러 개 생성(다중 정의)
using System;

class MethodOverload
{

    // 매개 변수가 없는 Hi() 함수
    static void Hi()
    {
        Console.WriteLine("안녕하세요.");
    }

    // 매개 변수가 있는 Hi() 함수
    static void Hi(string msg)
    {
        Console.WriteLine(msg);
    }

    static void Main()
    {
        Hi();
        Hi("반갑습니다.");
    }
}
안녕하세요.
반갑습니다.

Hi() 형태로 함수를 호출하면, 매개 변수가 없는 Hi() 함수가 호출되고, Hi("반갑습니다.") 형태로 호출하면, 매개 변수가 문자열인 Hi(string msg) 함수가 호출됩니다.

서로 다른 매개 변수를 갖는 함수 오버로드

이번에는 함수 오버로드 중 서로 다른 매개 변수의 데이터 형식을 사용하는 예제를 살펴보겠습니다.

코드: FunctionOverload.cs

// 함수(메서드) 오버로드(Overload; 오버로딩): 다중 정의/여러 번 정의/중복
using System;

class FunctionOverload
{

    static void Multi()
    {
        Console.WriteLine("안녕하세요.");
    }

    static void Multi(string message)
    {
        Console.WriteLine(message);
    }

    static void Multi(string message, int count)
    {
        for (int i = 0; i < count; i++)
        {
            Console.WriteLine("{0}", message);
        }
    }

    static void Main()
    {
        Multi();
        Multi("반갑습니다.");
        Multi("또 만나요.", 3);
    }
}
안녕하세요.
반갑습니다.
또 만나요.
또 만나요.
또 만나요.

함수를 호출할 때에는 함수 호출 형태에 따라서 그와 동일한 메서드 시그니처를 갖는 함수를 찾아서 실행합니다.

  • Multi() => Multi() 함수 호출
  • Multi("반갑습니다.") => Multi(string message) 함수 호출
  • Multi("또 만나요.", 3) => Multi(string message, int count) 함수 호출

만약, Multi(1234) 형태처럼 없는 함수를 호출하면 에러가 발생합니다.

재귀 함수

함수에서 함수 자기 자신을 호출하는 것을 재귀(Recursion) 또는 재귀 함수라고 합니다.

재귀 함수 사용하기

이번에는 재귀 함수를 사용해보겠습니다. 반복문에서 만들어 본 팩토리얼 문제를 재귀 함수를 사용하여 다시 만들어보겠습니다. 재귀 함수 예제는 코드로 이해하기가 어려우니 부록의 "디버거 사용하기"를 참고하여 F10번 또는 F11번을 여러 번 눌러가면서 하나씩 코드를 실행해 보는 걸 권장합니다.

이번 예제에서는 팩토리얼을 계산하는 여러 가지 방법을 소개합니다. 팩토리얼은 숫자를 1부터 자기 자신까지 차례로 곱한 결과입니다. 예를 들어, 4! = 4 * 3 * 2 * 1 = 24입니다. 이 예제에서는 재귀 함수를 사용하여 팩토리얼을 계산하는 방법과 다른 두 가지 방법을 소개합니다.

코드: RecursionDemo.cs

// 재귀 함수(Recursion)
using System;

class RecursionDemo
{
    static void Main()
    {
        // 재귀 호출을 사용하여 Factorial을 구하기: 4! = 4 * 3 * 2 * 1 = 24
        Console.WriteLine(4 * 3 * 2 * 1); // 24
        Console.WriteLine(FactorialFor(4));
        Console.WriteLine(Factorial(4));
        Console.WriteLine(Fact(4)); 
    }

    // 3항 연산자를 사용한 Factorial 구하기
    static int Fact(int n)
    {
        return (n > 1) ? n * Fact(n - 1) : 1;
    }

    // 재귀 함수를 사용한 Factorial 함수 만들기: 단, 재귀함수는 Tree 구조 탐색에 유리
    static int Factorial(int n)
    {
        // 종료
        if (n == 0 || n == 1)
        {
            return 1;
        }
        // 재귀 호출
        return n * Factorial(n - 1);
    }

    // 단순한 Factorial은 이 방법이 좋음
    static int FactorialFor(int n)
    {
        int result = 1;
        for (int i = 1; i <= n; i++)
        {
            result *= i; // ((((1 * 1) * 2) * 3) * 4)
        }
        return result;
    }
}
24
24
24
24

위 코드에서는 세 가지 방법으로 팩토리얼을 계산합니다. 첫 번째 방법은 3항 연산자를 사용한 팩토리얼 구하기입니다. 두 번째 방법은 재귀 함수를 사용한 팩토리얼 계산입니다. 재귀 함수는 주로 트리 구조를 탐색하는 데 유리합니다. 마지막 방법은 반복문을 사용한 팩토리얼 계산입니다. 이 방법은 단순한 팩토리얼 계산에 적합합니다. 결과를 확인하면 모든 방법이 동일한 값을 출력하는 것을 확인할 수 있습니다.

재귀 함수를 활용한 n의 m제곱 계산하기

이번 예제에서는 재귀 함수를 사용하여 주어진 숫자의 거듭제곱 값을 계산하는 프로그램을 작성해보겠습니다.

코드: RecursionPower.cs

// 재귀(Recursion)를 활용한 n의 m승 계산하기
using System;

class RecursionPower
{
    static int MyPower(int num, int cnt)
    {
        if (cnt == 0)
        {
            return 1;
        }
        return num * MyPower(num, --cnt); // 2 * (2 * (1))
    }

    static void Main()
    {
        Console.WriteLine(MyPower(2, 2)); // 2 * 2 * 1 = 4
    }
}
4

MyPower() 함수는 숫자 num과 거듭제곱 횟수 cnt를 인자로 받아서 n의 m승을 계산합니다. 예를 들어, MyPower(2, 2)를 전달하면 2의 2승(2 * 2)인 4를 반환합니다. 마찬가지로, MyPower(2, 10)을 전달하면 2를 10번 곱하여 2의 10승을 구해줍니다. 이렇게 재귀 함수를 활용하면 간결하게 거듭제곱 값을 계산할 수 있습니다.

함수 범위(Function Scope), 전역변수와 지역변수

클래스와 같은 레벨에서 선언된 변수를 전역 변수(Global Variable) 또는 필드(Field)라고 하고 함수 레벨에서 선언된 변수를 지역 변수(Local Variable)라고도 합니다. 이때 동일한 이름으로 변수를 전역 변수와 함수내의 지역 변수로 선언할 수 있는데, 함수 내에서는 함수 범위내에 있는 지역 변수를 사용하고 함수 범위내에 선언된 변수가 없으면 전역 변수 내에 선언된 변수를 사용합니다. 단, C#에서는 전역 변수라는 단어를 잘 사용하지 않고 필드라고 하며 전역 변수는 _(언더스코어) 또는 m_ 접두사를 붙이는 경향이 있습니다.

이번에는 함수의 범위를 알아보겠습니다.

코드: FunctionScope.cs

// Function Scope: 함수(메서드) 범위, 전역 변수와 지역 변수
using System;

public class FunctionScope
{
    static string message = "전역 변수"; // 필드

    static void ShowMessage()
    {
        string message = "지역 변수";
        Console.WriteLine(message);  // 지역 변수
    }

    static void Main()
    {
        ShowMessage();
        Console.WriteLine(message); // 전역 변수
    }
}
지역 변수
전역 변수

Main() 메서드와 동일한 레벨에 static string message = "전역 변수"; 형태로 문자열 변수를 선언할 수 있습니다. 이렇게 클래스 내에서 선언하는 변수는 Main() 메서드와 ShowMessage() 함수에서 모두 사용할 수 있는 전역 변수가 됩니다. 다만, ShowMessage() 함수에서는 동일한 이름의 message 변수를 선언하고 사용하기에 전역 변수가 아닌 함수 내에서 선언된 지역 변수가 사용됩니다. 지금까지 우리가 선언하고 사용해 온 모든 변수는 지역 변수에 해당됩니다. 전역 변수 즉 필드에 대해서는 뒤에서 자세히 다루도록 하겠습니다.

화살표 함수(Arrow Function): =>

화살표 모양의 연산자인 화살표 연산자(=>)를 사용하여 메서드 코드를 줄일 수 있습니다. 이를 화살표 함수라고 합니다. 프로그래밍에서 화살표 함수 또는 화살표 메서드는 람다 식(Lambda Expression)의 또 다른 이름입니다. 화살표 함수를 사용하면 메서드를 줄여서 표현할 수 있습니다. 메서드 고유의 표현을 줄여서 표현하면 처음에는 어색할 수 있습니다. 하지만 이러한 방식이 익숙해지면 차차 코드의 간결함을 유지할 수 있는 형태로 프로그램 코드를 작성할 수 있습니다.

화살표 함수 사용하기

이제 화살표 함수를 사용해보겠습니다. 참고로, Hi() 함수와 Mutiply() 함수가 Main() 메서드 뒤에 나오지만, 프로그램 작성 순서는 Hi()Multiply()를 먼저 작성 후 Main()에서 호출하면 됩니다.

코드: ArrowFunction.cs

// 화살표 함수(Arrow Function): 화살표 메서드
using System;

class ArrowFunction
{
    static void Main()
    {
        Hi(); // 안녕하세요.
        Multiply(3, 5); // 15
    }

    static void Hi() => Console.WriteLine("안녕하세요.");
    static void Multiply(int a, int b) => Console.WriteLine(a * b);
}
안녕하세요.
15

메서드 본문이 단순한 형태일 때에는 중괄호 기호를 생략하고 바로 화살표 연산자(=>)를 사용하여 화살표 함수로 구현할 수 있습니다. 참고로, 박용준 강사는 화살표 연산자를 영어 원문 그대로 "goes to"로 발음합니다. 또 다른 발음법은 "Arrow"로 발음합니다.

식 본문 메서드(Expression-Bodied Method)

화살표 함수는 함수를 축약해서 표현해주는 기능입니다. 이러한 함수 축약의 영어 표현은 "Expression-Bodied Method"라고 하며 번역은 "식 본문 메서드"라고 합니다.

코드: ExpressionBodiedMethod.cs

using System;

class ExpressionBodiedMethod
{
    static void Main()
    {
        //[!] 아래에 함수를 먼저 만들고 호출해야 함
        Log("함수 축약");
        Console.WriteLine(IsSame("A", "B")); // False
    }

    static void Log(string message) => Console.WriteLine(message);

    static bool IsSame(string a, string b) => a == b;
}
함수 축약
False

참고로, Log()IsSame()과 같은 사용자 정의 함수는 일반적으로 Main() 메서드 앞에 코드를 위치합니다.

로컬 함수

C# 7.0 버전 부터는 로컬 함수(Local Function) 기능을 제공합니다. 로컬 함수는 함수(메서드) 내에 또 다른 함수를 만드는 것을 의미합니다. 특정 함수 내에서만 사용되는 또 다른 함수를 로컬 함수라고 합니다. 다음 간단한 코드를 살펴보세요.

코드: LocalFunctionDemo.cs

using System;

class LocalFunctionDemo
{
    static void Main()
    {
        // 로컬 함수: 메서드(함수)내에서만 사용되는 또 다른 함수
        void Display(string text)
        {
            Console.WriteLine(text);
        }

        Display("로컬 함수");
    }
}
로컬 함수

Main() 메서드내에는 Display()라는 로컬 함수를 두고 이를 Main() 메서드에서 문자열 출력 용도로 사용될 수 있습니다. 로컬 함수는 해당 위치를 벗어난 다른 메서드(함수)에서는 호출이 되지 않습니다.

로컬 함수에 화살표 연산자 사용하기

다음 샘플 코드는 Main() 메서드 안에 화살표 연산자를 사용하여 로컬 함수 2개를 만들고 사용하는 내용을 보여줍니다.

코드: LocalFunctionAdd.cs

using System;

class LocalFunctionAdd
{
    static void Main()
    {
        // 로컬 함수 만들기
        int Add(int a, int b) => a + b;
        decimal Subtract(decimal x, decimal y) => x - y;

        // 로컬 함수 사용하기 
        Console.WriteLine($"3 + 5 = {Add(3, 5)}");
        Console.WriteLine($"34.56 - 12.34 = {Subtract(34.56M, 12.34M)}");
    }
}
3 + 5 = 8
34.56 - 12.34 = 22.22

Main 메서드의 명령줄 인수

Visual Studio를 사용하여 C# 콘솔 응용 프로그램 프로젝트를 생성하면 Main() 메서드에 문자열 배열이 매개 변수로 포함된 Main(string[] args) 형태로 만들어집니다. Main() 메서드의 매개 변수인 string[] args도 문자열 배열인 것을 이제는 알 수 있습니다. args 배열은 명령 프롬프트로부터 실행 파일명 뒤에 오는 문자열을 배열로 받아서 사용할 수 있는 기능을 제공합니다. 이를 명령줄 인수(Command Line Arguments 또는 Command Line Parameters)라고 하는데 명령 줄에서 넘어오는 매개 변수를 하나씩 받아서 사용할 수 있습니다. 예를 들어, 도스 명령어 중에서 dir.exe *.* 처럼 dir.exe 명령어 뒤에 오는 옵션 문자열들을 명령줄 인수라 합니다.

명령줄 인수로 문자열 데이터 2개 입력받기

처음으로 Main 메서드에 매개 변수로 명령줄 인수를 사용해보겠습니다. 지금까지는 Main() 메서드에 아무런 매개 변수를 넣지 않았지만, Main(string[] args) 형태로 매개 변수를 지정하였습니다.

코드: CommandLineArgument.cs

using System;

class CommandLineArgument
{
    static void Main(string[] args)
    {
        foreach (string arg in args)
        {
            Console.WriteLine(arg);
        }
    }
}

코드를 작성 후 Ctrl+F5 단축키로 실행하면 아무것도 실행되지 않습니다. 문자열 배열인 args 배열을 foreach 등의 반복문을 사용하여 데이터가 있는만큼 반복해서 문자열을 하나씩 뽑아서 출력하는 구문입니다. 명령줄 인수는 도스 창으로 불리는 명령 프롬프트 또는 터미널에서 실행 파일 뒤에 추가적으로 문자열을 전달할 때 이를 공백을 기준으로 문자열 배열로 받는 역할을 합니다. 만약, 현재 프로젝트를 실행했을 때 실제 실행 파일이 위치하는 경로로 명령 프롬프트로 이동한 후 다음과 같이 CommandLineArgument.exe Hello World를 지정하면 "Hello""World"가 각각 args[0]args[1]에 저장됩니다. 그리고 args.Length를 요청하면 2개의 요소가 있으므로 2를 반환합니다.

그림: 터미널에서 실행하기

터미널에서 실행하기

C:\C#\CommandLineArgument\CommandLineArgument\bin\Debug>CommandLineArgument.exe Hello World
Hello
World

명령줄 인수는 큰 따옴표를 사용하여 공백을 포함한 문자열을 받을 수 있습니다. 다음과 같이 "안녕 Hello" 형태로 큰 따옴표로 묶어 입력하면 이 값은 args[0]에 저장됩니다.

C:\C#\CommandLineArgument\CommandLineArgument\bin\Debug>CommandLineArgument.exe "안녕 Hello" World
안녕 Hello
World

Visual Studio 프로젝트 속성에서 명령줄 인수 제공받기

Visual Studio에서는 직접 명령 프롬프트(cmd.exe)로 이동하여 명령줄 인수를 테스트하지 않고 Visual Studio에서 옵션으로 바로 설정할 수 있는 기능을 제공해 줍니다.

3단계에 걸쳐서 Visual Studio에서 명령줄 인수 값을 지정하는 방법을 살펴보겠습니다.

(1) 다음 내용을 Visual Studio의 소스 코드 편집 창에 입력하세요.

코드: CommandLineParameterDemo.cs

using System;

class CommandLineParameterDemo
{
    static void Main(string[] args)
    {
        for (int i = 0; i < args.Length; i++)
        {
            Console.WriteLine(args[i]);
        }
    }
}

(2) Visual Studio에서 CommandLineParameterDemo 프로젝트에 마우스 오른쪽 버튼을 클릭한 후 속성을 클릭하여 속성 창을 엽니다. 다음 그림과 같이 속성 창이 열리면 디버그 탭을 선택합니다. 디버그 시작 프로필 UI 열기 링크 버튼을 클릭하여, 명령줄 인수에 안녕하세요 반갑습니다 문자열을 입력합니다.

명령줄 인수

애플리케이션 인수

(3) Visual Studio에서 Ctrl+F5를 눌러 프로젝트를 실행합니다. 프로젝트를 실행하면 args[0]"안녕하세요"가 저장되고 args[1]"반갑습니다"가 저장된 후 for 문에 의해서 args 배열의 값을 각각 출력합니다.

안녕하세요
반갑습니다

이와 같이 3단계를 거치면 직접 명령 프롬프트로 이동하지 않고도 명령줄 인수를 손쉽게 테스트할 수 있습니다.

지금까지 우리가 사용해 온 Main() 메서드는 매개 변수(파라미터)로 문자열 배열을 받습니다. 이 문자열 배열은 명령 프롬프트에서 C# 프로그램 실행시 뒤에다가 특정 문자열을 지정해서 추가적인 기능을 구현하도록 하는데 이 문자열을 받는 기능을 합니다.

C#에서 명령줄 인수는 string[] args로 받습니다. args는 원하는 이름으로 바꿔서도 무관합니다. 명령 프롬프트에서 C# 콘솔 응용 프로그램 실행시 공백을 기준으로 args[0], args[1], ... 순서로 문자열 값을 받습니다. 또한, args.Length 속성으로 배열의 요소수를 알아낼 수 있습니다. 우리가 명령줄인수라는 이름으로 프로그램을 만들었다면 아래와 같이 호출할 수 있습니다.

C:\C#\명령줄인수 안녕하세요 반갑습니다 또만나요

명령줄 인수에 공백을 기준으로 전달된 값은 다음과 같이 각각의 배열의 요소가 생성되고 초기화 됩니다.

args[0] => 안녕하세요 args[1] => 반갑습니다 args[2] => 또만나요

명령줄 인수로 숫자 데이터 2개 입력받기

이번에는 Visual Studio에서 Main 메서드의 명령줄 인수를 전달하여 그 값을 사용해보겠습니다. 주의할 것은 반드시 프로젝트 속성의 디버그 탭에서 명령줄 인수에 1 2를 입력해야 합니다. 그렇지 않으면 실행할 때 에러가 발생됩니다.

코드: CommandLineDemo.cs

using System;

class CommandLineDemo
{
    // Command Line Arguments
    static void Main(string[] args)
    {
        int first = int.Parse(args[0]);             // 1
        int second = Convert.ToInt32(args[1]);      // 2
        Console.WriteLine(first + second);          // 1 + 2 = 3
    }
}
3

명령줄 인수에 값을 입력하지 않고 실행하면 다음과 같은 에러 메시지가 출력됩니다.

처리되지 않은 예외: System.IndexOutOfRangeException: 인덱스가 배열 범위를 벗어났습니다.

명령줄 인수에 값이 정상적으로 전달되어 CommandLineDemo.exe 3 5 형태로 전달하면 35를 합한 8이 결과로 출력될 것입니다.

C#에서 프로젝트를 생성 후 기본 cs 파일을 생성하게되면 Main() 메서드에 기본적으로 명령줄 인수가 붙어 있는 상태로 만들어집니다만, 박용준 강사의 강의에서는 string[] args는 대부분 생략한 채로 코드를 표시할 예정입니다.

가변 길이 매개 변수 사용하기

params 키워드를 사용하면 함수에 임의의 개수의 인자를 전달할 수 있습니다. 이를 활용하면 같은 함수가 다양한 개수의 값을 처리할 수 있습니다. 이번 예제에서는 가변 길이 매개 변수를 간단히 살펴보겠습니다. params 키워드의 동작 원리와 세부적인 내용은 이후 메서드를 학습하는 장에서 더 자세히 다룰 예정이므로, 지금은 아래 예제를 실행해 보고 개념을 익히는 것만으로 충분합니다.

코드: ParamsFunctionDemo.cs

using System;

public class ParamsFunctionDemo
{
    // 가변 길이 매개 변수를 사용하여 정수 합계를 계산하는 함수
    public static int Sum(params int[] numbers)
    {
        int total = 0;
        foreach (int number in numbers)
        {
            total += number;
        }
        return total;
    }

    public static void Main()
    {
        // Sum 메서드를 호출하여 여러 개의 정수를 전달
        int result = Sum(1, 2, 3, 4);
        Console.WriteLine($"합계: {result}");
    }
}
합계: 10

Sum 메서드는 params int[] numbers를 사용해 여러 개의 정수를 받을 수 있으며, foreach 문을 이용해 전달된 숫자를 모두 더한 후 결과를 반환합니다.
Main 메서드에서는 Sum(1, 2, 3, 4)를 호출하여 네 개의 정수를 합산하고, 그 결과를 출력합니다.
가변 길이 매개 변수는 함수 호출 시 전달할 인자의 개수를 미리 알 수 없을 때 유용합니다.

장 요약

함수(메서드)는 C# 프로그래밍에서 가장 중요한 요소 중 하나입니다. 그러다보니, 이번 강의는 내용이 많았는데요. 앞으로 이번 강의의 내용은 계속해서 반복해서 나올 예정입니다. 함수를 사용하여 반복되는 코드와 중복되는 코드를 줄여 효율성을 높일 수 있습니다.


함수 사용하기 퀴즈 및 연습문제

퀴즈

(1) 다음 중 함수의 반환값이 없음을 의미하는 키워드는 무엇인가요?

a. int
b. void
c. static
d. not

정답: b

void 키워드는 함수가 반환값을 가지지 않을 때 사용합니다.


(2) 정수형 매개변수가 2개이고, 반환값이 정수형인 함수를 선언할 때 올바른 코드는 무엇인가요?

a. float Add(float a, float b)
b. void Add(int a, int b)
c. int Add(int b, float b)
d. int Add(int a, int b)

정답: d

int Add(int a, int b) 형태로 함수를 선언하면 int 형식의 매개변수 2개를 받아, int 형식의 값을 반환하는 함수가 됩니다.


연습문제: 곱셈 함수 만들기

이제 책의 마지막 연습문제를 풀어보겠습니다. 다음 소스 코드를 완성하여 31.40이 출력되도록 하세요.

코드

// 곱셈 함수 만들기
using System;

class FunctionPractice
{
    static void Main()
    {
        decimal result = 0;

        result = Multiply(3.14m, 10.0M);

        Console.WriteLine($"{result:0.00}"); // 31.40
    }

    static decimal Multiply(decimal a, decimal b)
    {
        return a * b; 
    }
}

실행 결과

31.40

정답

static decimal Multiply(decimal a, decimal b)
{
    return a * b; 
}

해설

Multiply() 함수는 두 개의 decimal 값을 매개변수로 받아야 합니다. 함수 내부에서 이 두 값을 곱한 후, 결과를 decimal 타입으로 반환합니다. 반환된 값은 result 변수에 저장된 후 출력됩니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com