클래스와 객체
틀(클래스)과 그것으로 찍어 낸 것(객체)의 관계입니다.
흩어진 값을 하나로 묶기
학생 한 명의 이름과 점수를 다루려면 변수 두 개면 됩니다. 그런데 학생이 셋으로 늘면 변수가 여섯 개가 되고, 어느 이름이 어느 점수와 짝인지는 변수 이름 끝의 숫자로만 짐작하게 됩니다. 짝을 잘못 맞춰도 컴파일러는 알려 주지 않습니다.
클래스는 함께 다녀야 할 값들을 하나로 묶는 틀입니다. 그 틀로 찍어 낸 것 하나하나를 객체(object) 또는 인스턴스라고 부릅니다.
- 클래스 — 무엇을 담고 무엇을 할 수 있는지 정한 틀입니다. 하나만 적습니다.
- 객체 — 그 클래스로 실제로 만들어 낸 실체입니다. 얼마든지 만들 수 있고, 저마다 자기 값을 가집니다.
붕어빵 틀과 붕어빵에 견주면 쉽습니다. 틀은 하나지만 붕어빵은 여럿이고, 각각에 든 팥의 양은 서로 다를 수 있습니다.
클래스를 만들고 객체 찍어 내기
아래 코드는 브라우저 안에서 실제로 실행됩니다. 고쳐서 눌러 보셔도 됩니다.
var a = new Student(); a.Name = "홍길동"; a.Score = 90; var b = new Student(); b.Name = "김철수"; b.Score = 85; Console.WriteLine($"{a.Name}: {a.Score}점"); Console.WriteLine($"{b.Name}: {b.Score}점"); // 타입 선언은 최상위 문 뒤에 옵니다 class Student { public string Name; public int Score; }
코드는 고쳐서 실행해 볼 수 있습니다. 처음 누를 때만 실행기를 내려받느라 잠시 걸립니다. 적은 코드는 서버로 나가지 않습니다.
class Student { ... } 가 클래스 선언이고,
new Student() 가 그것으로 객체 하나를 찍어 내는
일입니다. a 와 b 는
같은 클래스에서 나왔지만 서로 다른 객체라, 한쪽의 이름을 바꿔도 다른 쪽은 그대로입니다.
타입 선언이 맨 아래에 있는 것을 눈여겨보세요. 최상위 문을 사용하는 파일에서는 실행할 문장이 먼저 오고 클래스 선언이 그 뒤에 와야 합니다. 순서를 뒤집으면 컴파일되지 않습니다.
왜 묶는가
클래스를 쓰지 않아도 같은 일을 할 수는 있습니다. 다만 다루는 것이 늘어날수록 짝을 사람이 기억해야 하는 코드가 됩니다.
string name1 = "홍길동"; int score1 = 90; string name2 = "김철수"; int score2 = 85; // 짝은 이름 끝의 숫자로만 압니다 // 셋째가 늘면 변수도 둘 늘어납니다
var list = new List<Student>(); list.Add(a); list.Add(b); // 짝이 흩어지지 않습니다 // 몇 명이 늘어도 담는 곳은 하나
묶어 두면 여럿을 한 자리에 담을 수 있다는 것이 특히 큽니다. 이름과 점수가 따로 놀면 목록으로 만들 수도 없습니다.
클래스는 참조 형식입니다
1.3에서 값 형식과 참조 형식의 차이를 보았습니다. 클래스로 만든 객체는 참조 형식이라, 다른 변수에 담으면 값이 복사되는 것이 아니라 같은 것을 가리키게 됩니다.
var a = new Student(); a.Name = "홍길동"; var b = a; // 위치를 복사 — 새로 찍어 낸 것이 아닙니다 b.Name = "김철수"; Console.WriteLine(a.Name); // 김철수 — a 도 함께 바뀌었습니다
새로 하나 더 만들려면 new 를 다시 불러야 합니다.
= 로 담는 것은 이름을 하나 더 붙이는
일일 뿐입니다.
클래스 안에 하는 일도 담습니다
클래스에는 값만이 아니라 그 값으로 하는 일도 담을 수 있습니다. 1.8에서 만든 메서드를 클래스 안에 두면, 그 객체의 값을 그대로 사용할 수 있습니다.
class Student { public string Name; public int Score; // 매개 변수로 받지 않아도 제 값을 압니다 public string Describe() { return $"{Name}: {Score}점"; } } Console.WriteLine(a.Describe()); // 홍길동: 90점
Describe 안의 Name 은
자기를 부른 객체의 이름입니다.
a.Describe() 는 a의 이름을,
b.Describe() 는 b의 이름을 사용합니다. 이것을 분명히
적고 싶을 때는 this.Name 이라고 합니다.
지금은 값을 public 필드로 그대로 드러냈습니다. 밖에서
아무 값이나 넣을 수 있다는 뜻이라 실제로는 이렇게 두지 않는데, 그것을 다루는 것이
바로 다음 단원입니다.
이 문법, 몇 버전부터 사용할 수 있나요
- 1.0클래스 · 객체 · 메서드
- 9.0record — 값을 담는 형식을 한 줄로
- 12.0기본 생성자 — 받을 것을 이름 옆에
클래스와 객체는 C# 1.0부터 있었고 지금까지 뼈대가 바뀌지 않았습니다. 뒤에
더해진 것은 같은 일을 짧게 적는 길입니다 — 값을 담기만 하는
형식을 위한 record(C# 9.0)와, 받을 것을 클래스
이름 옆에 적는 기본 생성자(C# 12.0)가 그렇습니다. 둘 다 이 단원 뒤에
만납니다.
직접 해보기
제목(string)과 쪽수(int)를 담는 Book 클래스를 만들고,
책 두 권을 만들어 각각 출력해보세요.
앞에서 만든 Book 을
List<Book> 에 세 권 담고, 쪽수가 100쪽을 넘는
책의 제목만 출력해보세요. 클래스 안에 설명을 돌려주는 메서드를 두어도 좋습니다.
- 클래스는 틀이고,
new로 찍어 낸 하나하나가 객체입니다. - 객체마다 자기 값을 가집니다. 한쪽을 바꿔도 다른 쪽은 그대로입니다.
- 클래스는 참조 형식이라,
=로 담으면 이름만 하나 더 붙습니다. - 최상위 문을 사용하는 파일에서는 클래스 선언을 맨 아래에 둡니다.
C# 버전별 변경 이력 에서 각 버전이 무엇을 더했는지 볼 수 있습니다.