생성자는 태어날 때 딱 한 번 실행된다
Esc

    생성자는 태어날 때 딱 한 번 실행된다

    –C#기초

    앞 글까지의 코드는 이렇게 만들었습니다.

    Dog a = new Dog();
    a.Name = "바둑";
    a.Age = 3;

    세 줄이 한 벌입니다. 그런데 문법이 그걸 모릅니다.

    Dog b = new Dog();
    b.Name = "초코";
    // Age 를 빠뜨렸습니다

    컴파일도 되고 실행도 됩니다. b.Age 는 0 입니다. 0살인 개인지 안 넣은 것인지 구분할 방법이 없습니다.

    만드는 순간 값을 받게 하면 빠뜨릴 방법이 없어집니다.

    생성자는 이렇게 생겼다

    class Dog
    {
        public string Name;
        public int Age;
    
        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }
    
    Dog a = new Dog("바둑", 3);

    메서드와 비슷한데 두 가지가 다릅니다.

    • 이름이 클래스와 같습니다.
    • 반환 타입이 없습니다. void 도 안 적습니다.

    돌려줄 것을 적지 않는 이유는 하는 일이 정해져 있기 때문입니다. 객체를 만드는 것이지 값을 계산하는 것이 아닙니다.

    new Dog("바둑", 3) 의 괄호에 넣은 값이 여기로 들어옵니다. 지금까지 new Dog() 라고 빈 괄호를 썼는데, 그 괄호가 이 자리였습니다.

    이제 Age 를 빠뜨릴 수 없습니다. 안 넣으면 컴파일 에러입니다.

    매개변수 이름과 필드 이름

    위 코드에서 매개변수는 name, 필드는 Name 입니다. C# 에서는 필드를 대문자로, 매개변수를 소문자로 시작하는 관습이 있어서 이렇게 겹치지 않습니다.

    같게 쓰고 싶으면 this 를 붙입니다.

    public Dog(string Name, int Age)
    {
        this.Name = Name;    // this.Name 은 필드, Name 은 매개변수
        this.Age = Age;
    }

    this 는 지금 이 객체 를 가리킵니다. a.Bark() 로 부르면 그 안에서 this 는 a 입니다.

    메서드 안에서 필드를 그냥 쓸 때도 사실은 this 가 생략된 것입니다. 겹치는 이름이 없으면 안 써도 됩니다.

    만들면 반드시 실행된다

    생성자는 객체가 태어나는 순간 딱 한 번 실행됩니다. 부르는 것을 잊을 수가 없습니다. new 가 곧 호출이니까요.

    그래서 “이 객체는 항상 이런 상태로 시작한다” 를 보장할 수 있습니다.

    class Player
    {
        public string Name;
        public int Hp;
    
        public Player(string name)
        {
            Name = name;
            Hp = 100;        // 넘겨받지 않고 정해 둡니다
        }
    }

    Hp 는 밖에서 받지 않습니다. 모든 플레이어는 100 으로 시작합니다. 이 규칙이 코드 한 곳에 적혀 있고, 새로 만드는 모든 객체가 그 규칙을 따릅니다.

    하나라도 만들면 기본 생성자가 사라진다

    지금까지 new Dog() 를 빈 괄호로 썼습니다. 생성자를 안 만들었는데도 됐습니다. 아무것도 안 만들면 컴파일러가 매개변수 없는 생성자를 자동으로 만들어 주기 때문입니다.

    그런데 하나라도 직접 만들면 그게 사라집니다.

    class Dog
    {
        public Dog(string name, int age) { ... }
    }
    
    Dog a = new Dog();      // 컴파일 에러

    일부러 그렇게 되어 있습니다. “이름과 나이를 받아야 한다” 고 적어 뒀는데 빈 괄호로 만들 수 있으면 그 규칙이 반쪽이 됩니다.

    둘 다 열어 두고 싶으면 생성자를 여러 벌 만들면 됩니다. 앞에서 본 오버로딩이 생성자에도 그대로 적용됩니다.

    class Dog
    {
        public string Name;
        public int Age;
    
        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
        }
    
        public Dog(string name) : this(name, 0)
        {
        }
    }

    : this(name, 0) 은 같은 클래스의 다른 생성자를 먼저 부른다는 뜻입니다. 같은 코드를 두 번 적지 않으려고 씁니다.

    기본값 매개변수로도 됩니다. 이쪽이 짧습니다.

    public Dog(string name, int age = 0)

    static 은 틀에 붙는다

    필드는 객체마다 하나씩 생긴다고 했습니다. static 을 붙이면 달라집니다.

    class Dog
    {
        public static int Count = 0;     // 클래스 전체에 하나
        public string Name;              // 객체마다 하나
    
        public Dog(string name)
        {
            Name = name;
            Count++;
        }
    }
    
    new Dog("바둑");
    new Dog("초코");
    
    Console.WriteLine(Dog.Count);     // 2

    Count 는 붕어빵이 아니라 틀에 붙어 있습니다. 개가 몇 마리든 하나뿐입니다. 그래서 객체를 만들지 않고 Dog.Count 로 씁니다.

    클래스 Dog static Count: 2 Name: "바둑" Name: "초코" 객체 하나 객체 둘 둘이 같은 Count 를 본다
    static 은 틀에 붙고, 나머지는 찍어낸 것마다 붙는다.

    static 메서드도 같습니다. 객체 없이 부릅니다.

    Math.Sqrt(16);           // Math 객체를 만든 적이 없습니다
    int.TryParse(s, out n);  // int 에 붙은 static 메서드입니다
    Console.WriteLine();     // 이것도 마찬가지입니다

    지금까지 마침표 앞에 클래스 이름이 오던 것들이 전부 static 이었습니다. 객체 이름이 오면 그 객체의 것이고, 클래스 이름이 오면 틀의 것입니다.

    한 가지 규칙이 있습니다. static 메서드 안에서는 일반 필드를 못 씁니다.

    public static void Print()
    {
        Console.WriteLine(Name);    // 컴파일 에러
    }

    Name 은 객체마다 하나인데, static 메서드는 객체 없이 불립니다. 누구의 Name 인지 정할 수가 없습니다.

    정리

    • 생성자는 객체가 태어나는 순간 딱 한 번 실행됩니다.
    • 이름이 클래스와 같고 반환 타입을 안 적습니다. new 의 괄호에 넣은 값이 여기로 옵니다.
    • 만들면서 값을 받게 하면 빠뜨릴 방법이 없어집니다.
    • this 는 지금 이 객체입니다. 이름이 겹칠 때 필드와 매개변수를 구분합니다.
    • 생성자를 하나라도 직접 만들면 매개변수 없는 기본 생성자가 사라집니다.
    • 생성자도 오버로딩됩니다. : this(...) 로 다른 생성자를 먼저 부를 수 있습니다.
    • static 은 클래스 전체에 하나입니다. 객체 없이 클래스 이름으로 씁니다.
    • static 메서드 안에서는 객체마다 있는 필드를 쓸 수 없습니다.