{ }codelper

배우기 › C#

함수 · 15

함수 더 알아보기

변수 범위, 여러 값 돌려주기, 함수를 값으로 넘기기.

함수를 한 걸음 더 쓰는 세 가지예요: 변수가 어디에 사는지(범위), 값을 둘 돌려주는 법, 함수를 값처럼 넘기는 법.
아래 예제는 수들에서 가장 작은 값과 가장 큰 값을 한 번에 돌려받고, 조건 함수를 넘겨 짝수와 10 이상인 수를 세고, 조건을 모두 몇 번 확인했는지를 함수 밖 변수에 적어요.

using System;using System.Linq; class Program{    static int checks = 0;   // 함수 밖 변수(클래스의 static 필드): 프로그램이 끝날 때까지 남아요     static bool IsEven(int x)    {        return x % 2 == 0;    }     static bool IsBig(int x)    {        return x >= 10;    }     static (int, int) MinMax(int[] nums)    {        int lo = nums[0], hi = nums[0];        foreach (int x in nums)        {            lo = Math.Min(lo, x);            hi = Math.Max(hi, x);        }        return (lo, hi);   // 두 값을 튜플로 함께 돌려줘요    }     static int CountIf(int[] nums, Func<int, bool> test)   // test에는 함수가 들어와요    {        int count = 0;   // 함수 안 변수: 부를 때마다 새로 만들어져요        foreach (int x in nums)        {            checks++;    // 밖의 변수를 바꿔요            if (test(x)) count++;        }        return count;    }     static void Main()    {        int[] nums = Console.ReadLine().Split().Select(int.Parse).ToArray();        var (lo, hi) = MinMax(nums);        Console.WriteLine(lo + " " + hi);        Console.WriteLine(CountIf(nums, IsEven));        Console.WriteLine(CountIf(nums, IsBig));        Console.WriteLine(checks);    }}
stdin
4 15 8 23 6 7
stdout
4 23
3
2
12

핵심

  • 변수에는 사는 곳(범위)이 있어요.
    함수 안에서 만든 변수는 그 함수 안에서만 쓰이고, 부를 때마다 새로 만들어져요.
    예제의 count는 부를 때마다 0에서 다시 시작해요.
  • 함수 밖에서 만든 변수는 프로그램이 끝날 때까지 남아요.
    예제의 checks는 함수를 두 번 부르는 동안 값이 이어져서 12가 돼요.
  • 함수 안에서 밖의 변수를 바꾸는 방법은 언어마다 달라요.
    밖의 변수를 여기저기서 바꾸면 어디서 바뀌었는지 찾기 어려워요.
    그래서 예제의 checks처럼 프로그램 전체에 하나뿐인 기록에만 쓰고, 필요한 값은 매개변수로 받고 count 같은 계산 결과는 return으로 돌려줘요.
  • 값을 둘 돌려주려면 한 덩어리로 묶어 돌려주고, 받는 쪽에서 다시 풀어요.
    묶는 그릇(튜플, 배열, pair, 목록)이 언어마다 달라요.
    C는 결과를 적을 주소를 받아요.
  • 함수도 값이라서 다른 함수에 넘길 수 있어요.
    예제는 세는 함수 하나로 "짝수 세기"와 "10 이상 세기"를 모두 해요.
    달라지는 부분(조건)만 함수로 빼서 넘기는 거예요.
  • 넘길 때는 괄호 없이 이름만 써요.
    is_even은 함수 자체이고, is_even(4)는 함수를 불러서 나온 결과예요.

언어별로 쓰는 법

Python함수 안에서 밖의 변수를 읽기만 할 때는 그냥 쓰고, 바꿀 때는 함수 첫머리에 global checks를 적어요.
두 값은 return lo, hi(튜플)로 돌려주고 lo, hi = min_max(nums)로 받아요.
함수는 count_if(nums, is_even)처럼 이름만 넘기고, 짧은 조건은 lambda x: x >= 10으로 그 자리에서 만들어요
JavaScript함수 밖에서 let으로 만든 변수는 함수 안에서 표시 없이 읽고 바꿔요.
함수 안에서 let으로 같은 이름을 또 만들면 밖의 것과 다른 변수예요.
두 값은 return [lo, hi];로 돌려주고 const [lo, hi] = minMax(nums);로 받아요.
함수는 countIf(nums, isEven)처럼 넘기고, 짧은 조건은 화살표 함수 (x) => x >= 10
TypeScriptJavaScript와 같아요.
두 값을 돌려주는 타입은 [number, number], 함수를 받는 매개변수는 test: (x: number) => boolean처럼 적어요
Java클래스 밖에는 변수를 둘 수 없어서, 함수 밖 변수는 클래스의 static 필드로 둬요(static int checks = 0;).
두 값은 int[]에 담거나 record MinMax(int lo, int hi) {}를 만들어 돌려줘요.
함수는 IntPredicate test로 받아 test.test(x)로 부르고, 넘길 때는 Main::isEven이나 람다 x -> x >= 10.
람다 안에서 main의 지역 변수를 바꾸면 컴파일 오류예요
C#클래스 밖에는 변수를 둘 수 없어서, 함수 밖 변수는 클래스의 static 필드로 둬요.
두 값은 튜플 (int, int)로 return (lo, hi);, 받을 때는 var (lo, hi) = MinMax(nums);.
out 매개변수(out int lo)로 받는 방법도 있어요.
함수는 Func<int, bool> test로 받고, 넘길 때는 IsEven이나 람다 x => x >= 10
C++함수 밖에 쓴 변수는 전역 변수라서 어느 함수에서나 읽고 바꿔요.
두 값은 std::pair<int, int>로 return {lo, hi};, 받을 때는 auto [lo, hi] = minMax(nums);.
함수는 std::function<bool(int)>(<functional>)로 받고, 넘길 때는 isEven이나 람다 [](int x) { return x >= 10; }
C함수 밖에 쓴 변수는 전역 변수예요.
값을 둘 돌려주는 문법이 없어서, 예제처럼 결과를 적을 주소를 받거나(int *lo, int *hi, 부를 때는 &lo, &hi) 구조체에 담아 돌려줘요.
함수는 함수 포인터 int (*test)(int)로 받고 count_if(nums, n, is_even)처럼 이름을 넘겨요.
람다는 없어요
Craykon맨 위에서 만든 변수는 함수 안에서도 같은 변수라서 표시 없이 읽고 바꿔요.
함수 안에서 처음 만든 변수는 그 함수 안에서만 쓰여요.
두 값은 돌려주기 [lo, hi];처럼 목록에 담아 돌려주고 r[0], r[1]로 꺼내요.
람다가 없어서 이름 있는 함수를 countIf(nums, isEven)처럼 넘겨요

자주 하는 실수

global 없이 밖의 변수를 바꿨어요Python

checks = 0
def check(x):
    checks += 1
    return x % 2 == 0
print(check(4))

→ UnboundLocalError: cannot access local variable 'checks' where it is not associated with a value — 함수 안에서 값을 넣는 이름은 그 함수의 변수로 봐요. 그래서 아직 값이 없는 checks에 1을 더하려다 멈춰요

함수 첫머리에 global checks를 적어요.
읽기만 할 때는 필요 없어요.

return lo, hi;로 두 값을 돌려주려 했어요JavaScript · C · C++

function minMax(nums) {
  let lo = nums[0], hi = nums[0];
  for (const x of nums) { lo = Math.min(lo, x); hi = Math.max(hi, x); }
  return lo, hi;
}
console.log(minMax([4, 15, 8]));

→ 15 — 쉼표 연산자는 뒤의 값만 남겨서 hi만 돌아와요. C·C++도 경고만 하고 컴파일돼서 똑같이 15예요. TypeScript는 타입 검사에서 오류(TS2695)로 막아 줘요

JavaScript는 return [lo, hi];, C++는 std::pair로 return {lo, hi};, C는 주소를 받거나 구조체로 돌려줘요.
return lo, hi가 두 값을 돌려주는 건 Python이에요.

함수 안 변수 이름이 맨 위 변수와 같았어요Craykon

total = 100;
twice(x)
{
    total = x * 2;
    돌려주기 total;
}
콘솔.쓰기(twice(4));
콘솔.쓰기(total);

→ 8, 8 — 함수가 새 변수를 만든 게 아니라 맨 위의 total을 바꿔서, 100이 사라졌어요

함수 안에서만 쓸 변수는 맨 위 변수와 다른 이름으로 지어요.
매개변수는 이름이 같아도 그 함수만의 변수예요.
다른 언어에서는 함수 안에서 let total·int total처럼 새로 만들면(Python은 global 없이 값을 넣으면) 밖의 것과 다른 변수라서 8, 100이 나와요.

언어마다 다른 점
함수를 넘길 때 countIf(nums, isEven())처럼 괄호를 붙이면 넘기는 게 아니라 그 자리에서 부르는 거예요.
JavaScript는 TypeError: test is not a function, Python은 TypeError: is_even() missing 1 required positional argument: 'x'로 멈춰요.
TypeScript는 타입 검사에서, 크레이콘은 "isEven에는 값이 1개 필요한데 0개를 넣었어요"라는 오류로 알려 줘요.

연습 문제