메서드와 생성자

이정현
∙∙조회 4

20261008 메서드와 생성자

개요

생성자 없이 클래스를 생성 하려면 값을 채우는 것이 여러줄이 된다. 채우기 전까지는 각 객체는 값이 없는 상태로 존재하고 채우는 것을 빠뜨려도 알려주지 않는다. 생성자는 그 둘을 한 줄로 합친것이다.

인스턴스 메서드

static이 붙지 않은 메서드를 인스턴스 메서드라고 함. 객체를 통해서만 부를 수 있고, 그 객체의 필드를 이름만으로 씀. 메서드를 정의하는 문법 자체는 클래스 밖의 함수와 같음. 반환 타입, 이름, 매개변수 목록, 본문 순서로 씀

markdown
접근모디파이어 반환타입 메서드이름(매개변수 목록)
{
    본문
}

달라진 것은 본문에서 필드에 닿을 수 있다는 점 하나임. 그래서 같은 일을 하는 메서드라도 넘겨받아야 할 값이 줄어듬.

csharp
using System;

Temperature t = new Temperature();
t.SetCelsius(25);

Console.WriteLine($"섭씨: {t.GetCelsius()}");
Console.WriteLine($"화씨: {t.GetFahrenheit()}");
Console.WriteLine($"켈빈: {t.GetKelvin()}");

class Temperature
{
    private double celsius;

    public void SetCelsius(double value)
    {
        celsius = value;
    }

    public double GetCelsius()
    {
        return celsius;
    }

    public double GetFahrenheit()
    {
        return celsius * 9 / 5 + 32;
    }

    public double GetKelvin()
    {
        return celsius + 273.15;
    }
}

세 메서드 모두 매개변수가 없음. 변환에 필요한 섭씨 값이 이미 객체 안에 있기 때문임.

메서드가 다른 메서드를 부름

같은 클래스 안의 메서드끼리는 이름만으로 서로 부를 수 있음.

csharp
using System;

Character hero = new Character();
hero.SetUp("용사", 100);

hero.TakeDamage(30);
hero.TakeDamage(80);

class Character
{
    private string name;
    private int health;

    public void SetUp(string name, int health)
    {
        this.name = name;
        this.health = health;
    }

    public void TakeDamage(int damage)
    {
        health = health - damage;
        Console.WriteLine($"{name}이(가) {damage} 피해 → HP {health}");

        if (health <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        health = 0;
        Console.WriteLine($"{name} 쓰러짐");
    }
}

Die는 private이므로 클래스 밖에서는 부를 수 없음. 밖에 내놓을 필요가 없는 기능은 닫아두는 것이 기본임.

객체를 주고 받는 메서드

메서드의 매개변수와 반환 타입에도 클래스 이름을 쓸 수 있음.

csharp
using System;

Character hero = new Character();
hero.SetUp("용사", 100, 20);

Character orc = new Character();
orc.SetUp("오크", 60, 12);

hero.Attack(orc);
orc.Attack(hero);
hero.Attack(orc);

Character winner = Character.PickStronger(hero, orc);
Console.WriteLine($"우세한 쪽: {winner.GetName()}");

class Character
{
    private string name;
    private int health;
    private int attack;

    public void SetUp(string name, int health, int attack)
    {
        this.name = name;
        this.health = health;
        this.attack = attack;
    }

    public string GetName()
    {
        return name;
    }

    public int GetHealth()
    {
        return health;
    }

    public void Attack(Character target)
    {
        target.health = target.health - attack;
        Console.WriteLine($"{name} → {target.name} : {attack} 피해 (남은 HP {target.health})");
    }

    public static Character PickStronger(Character a, Character b)
    {
        if (a.health >= b.health)
        {
            return a;
        }

        return b;
    }
}

Attack안에서 target.health에 닿고 있음. health는 private인데도 되는 이유는 private이 객체 단위가 아니라 클래스 단위이기 때문임. 같은 클래스의 코드끼리는 서로의 비공개 멤버를 볼 수 있음.

식 본문 메서드

본문이 식 하나뿐이면 => 로 줄여 쓸 수 있음. 클래스 안의 메서드에도 그대로 적용됨. 예시: public int GetArea() => width * height;

클래스 안에서의 오버로딩

클래스 밖에서 실행문 사이에 정의한 함수는 같은 이름을 두번 쓸 수 없었음. 클래서 안의 메서드는 됨. 어느 것이 불릴지 정해지는 규칙은 함수 오버로딩 때와 똑같음. 달라진 것은 담는 자리가 클래스가 되면서 상자를 따로 만들 필요가 없어졌다는 점임.

인스턴스 메서드와 정적 메서드도 이름을 함께 쓸 수 있음. 매개변수가 다르면 됨.

  • 주의사항: 매개변수가 같으면 static유무만으로는 구분되지 않음.

생성자

객체를 만들 때 실행되는 자리

생성자(constructor)는 new로 객체를 만들 때 자동으로 실행되는 메서드임. 두 가지가 보통 메서드와 다름.

  • 이름이 클래스 이름과 같음
  • 반환 타입을 쓰지 않음(void도 쓰지 않음)

생성자가 하는 일은 대개 필드를 채우는 것임

생성자를 안 쓰면 기본 생성자가 생김

지금까지 생성자를 만든 적이 없는데도 new Character()가 됐음. 인스턴스 생성자를 하나도 정의하지 않으면 컴파일러가 매개변수 없는 생성자를 만들어 주기 때문임. 이것을 기본 새엇ㅇ자라고 함. 그러나 인스턴스 생성자를 하나라도 직접 선언하면 매개변수 없는 기본 생성자는 자동으로 생기지 않음. 매개변수 있는 생성자와 매개변수 없는 생성자가 필요하면 둘다 직접 만들어야 함.

매개변수가 있는 생성자

생성자도 매개변수를 받을 수 있음. 객체를 만드는 자리에 값을 함께 넘김.

csharp
// 생성자를 사용한 초기화 예시
Character hero = new Character("용사", 100, 15);
Character mage = new Character("마법사", 80, 25);

hero.Show();
mage.Show();

class Character
{
    private string name;
    private int health;
    private int attack;

    public Character(string name, int health, int attack)
    {
        this.name = name;
        this.health = health;
        this.attack = attack;
    }

    public void Show()
    {
        Console.WriteLine($"{name} / HP {health} / ATK {attack}");
    }
}

만드는 것과 채우는 것이 한줄이 되었고, 필수 인수를 빠뜨리면 컴파일 오류가 남. 받은 값을 필드에 대입하고 유효성을 검사하는 것은 생성자 본문의 책임임. 생성자에서 값을 검사할 수도 있음. 생성자의 매개변수 이름을 필드 이름과 같게 두는 코드도 흔히 만남. 그때는 this로 필드 쪽을 가리킴.

생성자 오버로딩

생성자도 매개변수가 다르면 여러 개 둘 수 있음.

this()로 다른 생성자 부르기

생성자 선언 뒤에 : this(...) 를 붙이면 같은 클래스의 다른 생성자를 먼저 실행함.

csharp
using System;

Character a = new Character();
Character b = new Character("마법사");
Character c = new Character("궁수", 90);

a.Show();
b.Show();
c.Show();

class Character
{
    private string name;
    private int health;

    public Character() : this("이름없음", 100)
    {
    }

    public Character(string name) : this(name, 100)
    {
    }

    public Character(string name, int health)
    {
        this.name = name;
        this.health = health;
    }

    public void Show()
    {
        Console.WriteLine($"{name} / HP {health}");
    }
}

필드에 값을 넣는 코드가 한 생성자에만 있음. 검사 규칙을 바꿔야 할 때도 그 한 곳만 고치면 됨.

Mermaid
렌더링 중...

실행 순서는 불린 쪽이 먼저임. 즉 : this()가 먼저 끝난 뒤 자기 본문이 실행됨.

식 본문 생성자

본문을 식 하나로 표현할 수 있으면 생성자도 =>로 줄여 쓸 수 있음.

csharp
// 생성자 줄여쓰기 예시
Pet a = new Pet("야옹이");
a.Show();

class Pet
{
    private string name;

    public Pet(string name) => this.name = name;

    public void Show() => Console.WriteLine(name);
}

readonly필드는 생성자에서 채움

readonly필드는 선언자리 말고는 생성자에서만 값을 넣을 수 있음. 객체마다 다르면서 만든 뒤에는 바뀌지 않아야 하는 값이 여기에 해당함.

필드 이니셜라이저와 생성자의 순서

필드 선언 자리의 초기화와 생성자 본문이 둘 다 있으면 필드 이니셜라이저가 먼저 실행됨. 그러므로 생성자에서 같은 필드에 값을 넣으면 생성자 쪽이 이김(필드 이니셜라이저가 먼저 실행되기 때문에 생성자쪽이 덮어씌움).

정적 생성자

클래스를 처음 쓸 때 한 번

static을 붙인 생성자는 그 클래스를 처음 쓰는 시점에 딱 한 번 실행됨. 정적 필드를 준비하는 자리임.

csharp
// 정적 생성자 사용 예시
Console.WriteLine("프로그램 시작");

Console.WriteLine($"레벨표 길이: {GameRule.LevelExp.Length}");
Console.WriteLine($"3레벨 필요 경험치: {GameRule.LevelExp[2]}");
Console.WriteLine($"5레벨 필요 경험치: {GameRule.LevelExp[4]}");

class GameRule
{
    public static readonly int[] LevelExp;

    static GameRule()
    {
        Console.WriteLine("정적 생성자 실행");

        LevelExp = new int[10];

        for (int i = 0; i < LevelExp.Length; i++)
        {
            LevelExp[i] = (i + 1) * (i + 1) * 100;
        }
    }
}

yaml
실행결과

프로그램 시작
정적 생성자 실행
레벨표 길이: 10
3레벨 필요 경험치: 900
5레벨 필요 경험치: 2500

"프로그램 시작"이 먼저 나오고 그 다음에 정적 생성자가 실행됨. 프로그램이 시작할 때가 아니라 그 클래스에 처음 손이 닿을 때 실행되기 때문임. 여러번 써도 한번만 실행됨

인스턴스 생성자와의 차이

정적 생성자는 첫 객체를 만들 때 딱 한번 끼어들고, 그 뒤로는 인스턴스 생성자만 실행됨

구분정적 생성자인스턴스 생성자
표기static 클래스이름()public 클래스이름(...)
실행 시점클래스를 처음 쓸 때new할 때마다
실행 횟수해당 타입의 초기화 시 한 번객체 생성 시마다
접근 모디파이어붙일 수 없음지정 가능하며 생략 시private
매개변수가질 수 없음있을 수도 없을 수도 있음
채우는 대상정적 필드인스턴스 필드