포스트

(C#) 10. 코드의 흐름 제어 (function)

반복되는 코드를 함수로 묶으면서 확인한 것. 매개변수가 복사본이라 안에서 바꿔도 밖이 안 바뀐다는 점과, static이 붙은 이유, 클래스 이름을 생략할 수 있는 조건을 정리했다.

(C#) 10. 코드의 흐름 제어 (function)

같은 코드를 두 번 쓰기 싫어서

6편의 가위바위보에서 내 선택과 상대 선택을 출력하는 switch가 거의 똑같이 두 번 나왔다. 이런 게 쌓이면 한 곳을 고칠 때 다른 곳을 빠뜨린다.

코드 덩어리에 이름을 붙여두고 필요할 때 부르는 게 함수다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System;

namespace functionEX
{
    class Program
    {
        // 메소드 함수
        /*
            한정자 반환형식 이름(매개변수목록)
            {
            }
        */

        static void HelloWorld()
        {
            Console.WriteLine("Hello World");
        }

        // 덧셈 함수
        static int Add(int a, int b)
        {
            return a + b;
        }

        static void AddOne(ref int number)  // int값을 참조하는 것을 넘기겟다.
        {
            number = number + 1;
        }
        static void Main(string[] args)
        {
            Program.HelloWorld();
            // int result = Program.Add(4, 5);
            // int result = Add(4, 5);

            /////////////////////////////////////
            int a = 4;
            int b = 5;
            int result = Program.Add(b, a);
            Console.WriteLine(result);

            /////////////////////////////////////
            int c = 0;
            Program.AddOne(ref c);
            Console.WriteLine(c);
        }
    }
}

선언 형태 뜯어보기

1
2
한정자   반환형식   이름   (매개변수목록)
static   int        Add    (int a, int b)

반환형식은 이 함수가 돌려주는 값의 타입이다. 아무것도 안 돌려주면 void라고 쓴다. void가 아니면 모든 경로에서 return을 해야 하고, 하나라도 빠지면 컴파일 에러다.

1
2
3
4
5
static int Bad(int x)
{
    if (x > 0) return 1;
    // 에러: 모든 코드 경로가 값을 반환하지는 않습니다
}

이 검사 덕분에 “값을 안 돌려주고 끝나는” 실수가 원천적으로 막힌다.

매개변수는 함수 안에서만 쓰는 지역 변수다. 부를 때 넘긴 값이 여기에 복사된다.

한정자static은 이 함수가 객체가 아니라 클래스에 속한다는 뜻이다. Mainstatic이라 여기서 부르는 함수도 static이어야 한다. 객체를 만들지 않았으니 객체에 속한 함수를 부를 수가 없기 때문이다. 이 이야기는 19편에서 제대로 나온다.

Program. 을 붙이는 것과 안 붙이는 것

1
2
3
Program.HelloWorld();
// int result = Program.Add(4, 5);
// int result = Add(4, 5);

주석에 두 형태가 나란히 있다. 둘 다 된다.

AddProgram 클래스 안에 있고 MainProgram 안에 있으니, 같은 클래스 안에서는 클래스 이름을 생략할 수 있다. 다른 클래스의 static 함수를 부를 때만 클래스명.함수명()이 필요하다.

Console.WriteLineConsole.이 붙는 것도 같은 이유다. Console이라는 다른 클래스의 static 함수라서다.

인자 순서는 이름이 아니라 위치로 정해진다

이 줄이 눈에 걸린다.

1
2
3
int a = 4;
int b = 5;
int result = Program.Add(b, a);   // a, b 가 아니라 b, a

Add(int a, int b)의 매개변수 이름도 a, b인데, 넘길 때는 b를 먼저 넘겼다. 함수 안의 a에는 바깥 b의 값인 5가 들어간다.

이름이 같아도 아무 상관이 없다. 위치로만 대응된다. 덧셈이라 결과가 같지만 뺄셈이었다면 결과가 달라진다.

바깥 변수와 매개변수의 이름이 겹치면 헷갈리니, 매개변수 이름은 역할이 드러나게 짓는 편이 낫다. Add(int left, int right)처럼.

인자가 많아지면 순서를 헷갈리게 되는데, 그때는 이름을 지정해서 넘길 수 있다. 이건 12편에서 다룬다.

1
Program.Add(a: 4, b: 5);

매개변수는 복사본이다

1
2
3
4
static void AddOne(ref int number)
{
    number = number + 1;
}

여기 ref가 왜 붙었는지가 핵심이다. 빼고 해보면 바로 안다.

1
2
3
4
5
6
7
8
static void AddOne(int number)     // ref 없이
{
    number = number + 1;
}

int c = 0;
AddOne(c);
Console.WriteLine(c);              // 0. 안 바뀐다

함수에 넘어간 numberc복사본이다. 복사본을 1 올려봐야 원본은 그대로다. 함수가 끝나면 복사본은 사라진다.

ref를 붙이면 복사본이 아니라 원본 자체를 넘긴다. 그래서 c가 1이 된다.

1
2
3
int c = 0;
Program.AddOne(ref c);
Console.WriteLine(c);              // 1

C#은 부르는 쪽에도 ref를 쓰게 한다. 이게 좋은 점이다. 호출 코드만 봐도 “이 함수가 내 변수를 바꾸겠구나”가 보인다. C++는 호출부에 표시가 없어서 함수 선언을 찾아봐야 안다.

ref와 짝인 out, 그리고 클래스를 넘길 때 이야기는 11편에서 이어진다.

값을 돌려주는 쪽이 대체로 낫다

같은 일을 두 가지로 쓸 수 있다.

1
2
static void AddOne(ref int number) { number = number + 1; }   // 인자를 고친다
static int  AddOne2(int number)    { return number + 1; }     // 값을 돌려준다

두 번째가 나은 경우가 많았다.

바깥 상태를 안 건드리니 이 함수만 따로 놓고 이해할 수 있고, 같은 입력에 항상 같은 출력이 나오니 테스트하기도 쉽다. 무엇보다 호출부를 봤을 때 무엇이 바뀌는지가 대입 연산자 왼쪽에 그대로 보인다.

1
2
a = AddOne2(a);        // a 가 바뀐다는 게 보인다
AddOne(ref a);         // 선언을 봐야 안다

ref가 필요한 건 돌려줄 값이 여러 개거나, 큰 구조체를 복사하기 싫을 때다.

함수를 나누는 기준

처음엔 “몇 줄 넘으면 나눈다”로 생각했는데, 줄 수보다 이름을 붙일 수 있는가가 기준이 됐다.

이름이 잘 안 지어지면 그 덩어리가 한 가지 일을 하는 게 아니라는 뜻이었다. ProcessAndSaveAndNotify 같은 이름이 나오면 셋으로 쪼개야 한다.

이름은 동사로 짓고, bool을 돌려주면 IsHas로 시작하게 했다. 9편IsPrime이 그런 예다.

정리하면

  • void가 아닌 함수는 모든 경로에서 return해야 하고, 컴파일러가 확인해준다
  • 같은 클래스 안이면 클래스명.을 생략할 수 있다
  • 인자는 이름이 아니라 위치로 대응된다. 이름이 같아도 순서가 다르면 다르게 들어간다
  • 매개변수는 복사본이라 안에서 바꿔도 원본이 안 바뀐다. 바꾸려면 ref
  • C#은 호출부에도 ref를 쓰게 해서, 부르는 코드만 봐도 변수가 바뀌는지 알 수 있다
  • 인자를 고치는 것보다 값을 돌려주는 쪽이 대체로 읽기 쉽다
  • 함수를 나누는 기준은 줄 수가 아니라 이름을 붙일 수 있는지다
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.