수업을 들으며 노트에 정리한 것 (함수)

공재원
∙∙조회 8

함수 개요

상황해법
하는 일은 같은데 받을 값의 타입이 여러 가지임오버로딩
넘긴 변수 자체를 함수가 바꿔 주길 원함ref
돌려줄 값이 둘 이상임out · 튜플
넘길 값의 개수가 그때그때 다름params
같은 일을 점점 작은 문제로 줄여 가며 풀어야 함재귀

오버로딩 규칙

  • 오버로딩이 성립하려면 매개변수 목록이 달라야 한다.
다른 점예
개수Add(int) · Add(int, int)
타입Add(int, int) · Add(double, double)
순서Draw(string, int) · Draw(int, string)
  • 실행문 사이의 함수는 오버로딩이 불가하다.

기본값으로 대신할 수 있는 경우

  • 매개변수 개수만 다른 오버로딩은 기본값으로 대신할 수 있는 경우가 많다.

방식

알맞은 상황

기본값

하는 일이 완전히 같고 값 하나만 생략하는 경우

오버로딩

받는 타입이 다르거나, 경우마다 하는 일이 다른 경우

ref 원본을 직접 다루기

  • 매개변수와 인수 양쪽에 ref를 붙이면 복사본이 아니라 원본 변수 자체가 전달된다.
  • ref를 사용할 때 지켜야할 것 2가지
규칙이유
호출할 때도 ref를 적음원본이 바뀔 수 있다는 사실이 호출부에 드러남
넘기는 변수를 미리 초기화해 둠함수가 그 값을 읽을 수 있어야 함
  • 배열 요소도 ref로 넘길 수 있다.

out 값을 내보내는 통로

  • out은 ref와 비슷하지만 쓰임이 반대다. 함수가 값을 채워 내보내기 위한 매개변수이다.
  • ref와의 차이는 3개가 있다.
refout
넘기기 전 초기화필요함필요 없음
함수 안에서 값 할당선택반드시 해야 함
쓰임값을 주고받음값을 내보냄

out변수를 호출 자리에 선언하기

  • out인수로 쓸 변수는 호출하는 자리에서 바로 선언할 수 있다. 미리 선언해 두는 방식과 결과가 같다.

매개변수 모디파이어 정리

  • in이라는 모디파이어도 있다. 참조로 넘기되 함수 안에서 못 바꾸게 막는 것이며, 큰 값을 복사 없이 읽기만 할 때 쓴다.
모디파이어넘기기 전 초기화함수 안에서원본
(없음)필요함복사된 매개변수를 바꿀 수 있음매개변수 재대입은 원본 변수에 반영되지 않음
ref필요함바꿔도 됨바뀜
out필요 없음반드시 값을 넣음바뀜

⚠️ ref를 남용하지 말 것: 단순 결과는 반환값으로 표현하고, 호출자의 변수 자체를 변경해야 할 때

ref를 사용함. 변경할 변수가 둘 이상이어야 한다는 문법 제한은 없음. 참조 타입 인수를 값으로 전달해도 그 객체 내부는 변경할 수 있음

개수가 정해지지 않은 인수

parmas

  • 받은 인수의 개수를 미리 정할 수 없을 때 parmas를 붙인 배열 매개변수를 쓴다.
  • parmas는 맨 뒤에 하나만 올 수 있다. 앞쪽에는 일반 매개변수를 둔다.

여러 값을 한 번에 반환하기

듀플로 묶어서 돌려주기

  • out으로도 여러 값을 내보낼 수 있지만, 값 여러 개를 하나로 묶어 반환하는 방법이 따로 있다.
    반환 타입 자리에 괄호로 타입을 나열하면 된다.
csharp
using System;

var result = GetMinMax(new int[] { 5, 2, 8, 1, 9, 3 });
Console.WriteLine($"최솟값: {result.Item1}, 최댓값: {result.Item2}");

(int, int) GetMinMax(int[] numbers)
{
    int min = numbers[0];
    int max = numbers[0];

    foreach (int number in numbers)
    {
        if (number < min) min = number;
        if (number > max) max = number;
    }

    return (min, max);
}

이렇게 값 여러 개를 괄호로 묶은 것을 튜플(tuple) 이라고 함.

return (min, max); 처럼 괄호에 값을 나열해 돌려주고, 받는 쪽은

Item1 · Item2 로 꺼냄.

재귀

자기 자신을 부르는 함수

  • 함수 안에서 자기 자신을 호출할 수 있다. 이것을 재귀(recursion)라고 한다.
csharp
using System;

CountDown(5);

void CountDown(int n)
{
    if (n <= 0)
    {
        Console.WriteLine("발사!");
        return;
    }

    Console.WriteLine(n);
    CountDown(n - 1);
}
plaintext
5
4
3
2
1
발사!

종료 조건이 없으면 맞추지 않는다.

  • 재귀 함수에는 더 이상 자기를 부르지 않고 끝나는 조건이 반드시 있어야 한다. 이것을 종료 조건이라고 한다.
확인할 것앞의 CountDown
끝나는 조건이 있는가n <= 0
호출마다 그 조건에 가까워지는가n - 1이므로 매번 1씩 줄어듦

팩토리얼

  • 재귀가 잘 들어 맞는 문제는 큰 문제가 같은 모양의 작은 문제로 줄어드는 경우임. 팩토리얼이 그런 예다.
    5! = 5 × 4!이고 4! = 4 × 3!이므로, n!은 n × (n-1)!로 쓸 수 있음.
csharp
using System;

Console.WriteLine($"5! = {Factorial(5)}");
Console.WriteLine($"4! = {Factorial(4)}");
Console.WriteLine($"1! = {Factorial(1)}");

int Factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    return n * Factorial(n - 1);
}

반복문으로도 되는 경우가 많다.

  • 같은 계산을 반복문으로도 쓸 수 있다.
csharp
using System;

Console.WriteLine($"재귀: {FactorialRecursive(5)}");
Console.WriteLine($"반복: {FactorialLoop(5)}");

int FactorialRecursive(int n)
{
    if (n <= 1) return 1;
    return n * FactorialRecursive(n - 1);
}

int FactorialLoop(int n)
{
    int result = 1;
    for (int i = 2; i <= n; i++)
    {
        result *= i;
    }
    return result;
}
  • 둘 다 같은 답을 내지만 이런 경우에는 반복문 쪽이 빠르고 안전하다. (함수 호출이 쌓이지 않기 때문이다.)

재귀가 비싸질 수 있다.

  • 피보나치 수열을 재귀로 사용하면 정의 그대로여서 읽기 좋다.
  • 다만 재귀로 쓸 수 있다는 것과 재귀로 쓰는 것이 좋다는 것은 다르다.

정렬과 검색

선택 정렬

  • 정렬(sort) 은 데이터를 순서대로 늘어놓는 것이다. 여러 방법이 있는데, 그중 이해하기 쉬운 것이 선택 정렬(selection sort) 이다. 방법은 단순함. 아직 정리되지 않은 부분에서 가장 작은 값을 찾아 맨 앞과 바꾸는 것을 반복한다.

순차 검색

  • 검색(search) 은 찾는 값이 어디 있는지 알아내는 것이다. 가장 단순한 방법은 앞에서부터 하나씩 보는 것이다.

이진 검색

  • 정렬되어 있다면 훨씬 빠른 방법을 쓸 수 있다. 가운데 값을 보고 찾는 값이 그보다 큰지 작은지에 따라 한쪽 절반을 통째로 버리는 것이다.

⚠️ 전제 조건: 이진 검색은 정렬된 배열에서만 맞는 답을 냄. 정렬되지 않은 배열에 쓰면 있는 값을 못 찾고 -1 을 돌려줄 수 있음

두 검색의 비교

csharp
using System;

int[] data = new int[1000];
for (int i = 0; i < data.Length; i++)
{
    data[i] = i * 2;      // 0, 2, 4, ... 1998 (정렬된 상태)
}

Console.WriteLine($"순차 검색 비교 횟수: {CountSequential(data, 1998)}");
Console.WriteLine($"이진 검색 비교 횟수: {CountBinary(data, 1998)}");

int CountSequential(int[] values, int target)
{
    int count = 0;
    for (int i = 0; i < values.Length; i++)
    {
        count++;
        if (values[i] == target) break;
    }
    return count;
}

int CountBinary(int[] values, int target)
{
    int count = 0;
    int low = 0;
    int high = values.Length - 1;

    while (low <= high)
    {
        count++;
        int mid = (low + high) / 2;
        if (values[mid] == target) break;
        if (values[mid] < target) low = mid + 1;
        else high = mid - 1;
    }
    return count;
}
plaintext
순차 검색 비교 횟수: 1000
이진 검색 비교 횟수: 10
순차 검색이진 검색
정렬 필요필요 없음반드시 정렬돼 있어야 함
칸이 1,000개일 때최악 1,000번약 10번
칸이 100만 개일 때최악 100만 번약 20번
  • 정렬하는 데 드는 비용이 있으므로, 한 번만 찾을 것이라면 순차 검색이 나을 수도 있다.
    여러 번 찾을 데이터라면 한 번 정렬해 두고 이진 검색을 쓰는 편이 유리하다.

오늘도 어려운 함수 수업에 대해 정리해 보았다. 다만 아직 정리만 해둔 것일 뿐 머릿속에 다 들어와 있는 상태는 아니다.
문자열에서 살짝 자신감을 얻었다가 다시 함수로 돌아와서 머리가 포화 상태이다. 못다한 복습을 마저 진행하려고 한다.
2026년 10월 6일 기준으로 수업에서 텍스트 게임(카드 뒤집어 짝 맞추기)을 제작하는 시간을 가졌다.
해당 내용은 따로 디벨로그에 작성하는 것이 좋을 것 같다.