생성자는 태어날 때 딱 한 번 실행된다
앞 글까지의 코드는 이렇게 만들었습니다.
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 로 씁니다.
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메서드 안에서는 객체마다 있는 필드를 쓸 수 없습니다.