06. 클래스와 객체
데이터와 기능을 하나로 묶은 사용자 정의 타입이 클래스입니다. 필드·메서드·생성자·this·접근 제한자를 익히고, new 로 객체를 만들어 메모리 모형을 머릿속에 그려 봅니다.
이 강의에서 배우는 것
- 1`class` 키워드로 새로운 타입을 정의할 수 있다
- 2필드·메서드·생성자가 무엇인지 구별할 수 있다
- 3`this` 키워드의 역할을 안다
- 4접근 제한자(`public`/`private`/`internal`/`protected`)를 적재적소에 쓸 수 있다
- 5`new` 로 객체를 만들고, 참조 변수가 실제로 무엇을 가리키는지 이해한다
소개
지금까지는 변수와 메서드를 따로따로 다뤘다면, 이제부터는 데이터와 기능을 하나로 묶어 다루는 **클래스(class)** 를 배웁니다. 클래스는 객체를 찍어 내는 **틀(설계도)** 이고, 그 틀로 만든 인스턴스가 **객체(object)** 입니다.
핵심 개념
1) 클래스 = 설계도, 객체 = 실체
class Person // 설계도
{
public string Name = "";
public int Age;
}
Person alice = new Person(); // 설계도로 찍어 낸 실체(객체)
alice.Name = "Alice";
alice.Age = 30;`Person` 자체는 메모리에 데이터가 없습니다. `new Person()` 을 호출해야 비로소 메모리에 객체가 만들어지고, 변수 `alice` 는 그 객체의 **참조(주소)** 를 담습니다.
2) 필드와 메서드
- **필드(field)**: 클래스가 가진 데이터(변수)
- **메서드(method)**: 클래스가 가진 동작(함수)
class Counter
{
public int Count; // 필드
public void Increase() => Count++; // 메서드
}3) 생성자(constructor) 와 `this`
객체가 만들어질 때 자동으로 호출되는 특수 메서드입니다. **클래스 이름과 똑같이** 짓고, 반환 타입은 쓰지 않습니다.
class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
this.Name = name; // this.Name 은 필드, name 은 매개변수
this.Age = age;
}
}`this` 는 "지금 동작 중인 그 객체 자신"을 가리키는 키워드입니다. 매개변수와 필드 이름이 겹칠 때 구분용으로 자주 씁니다.
4) 접근 제한자(access modifier)
| 키워드 | 누구까지 접근 가능? |
|---|---|
| `public` | 어디서든 |
| `private` | 같은 클래스 안에서만 (기본값) |
| `internal` | 같은 어셈블리(프로젝트) 안에서만 |
| `protected` | 같은 클래스 + 자식 클래스 |
캡슐화의 기본: **데이터는 가능한 한 `private`**, **외부에서 쓸 동작만 `public`** 으로 노출.
5) `new` 키워드와 참조
Person a = new Person("Alice", 30);
Person b = a; // b 는 a 와 같은 객체를 가리킴 (복사 아님!)
b.Age = 99;
Console.WriteLine(a.Age); // 99 → a 도 같이 변함클래스는 **참조 타입(reference type)**. 변수에 다른 변수를 대입해도 객체가 복제되지 않고, 같은 객체를 두 변수가 함께 가리킵니다.
핵심 예제
예제 1 — `PersonBasic` : 가장 단순한 클래스
// Program.cs
using CodingNow.Lecture.Oop06;
var alice = new Person("Alice", 30);
alice.Greet();
var bob = new Person("Bob", 25);
bob.Greet();// Person.cs
namespace CodingNow.Lecture.Oop06;
internal class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
this.Name = name;
this.Age = age;
}
public void Greet()
{
Console.WriteLine($"안녕, 나는 {Name}({Age}세).");
}
}**실행 결과**
안녕, 나는 Alice(30세).
안녕, 나는 Bob(25세).**메모:** 한 `Person` 클래스로 서로 독립된 객체 두 개를 찍어 냈습니다.
예제 2 — `AccessModifiers` : 접근 제한자 시연
// Program.cs
using CodingNow.Lecture.Oop06;
var account = new Account(1000);
account.Deposit(500); // public — OK
Console.WriteLine($"잔액: {account.GetBalance()}");
// account.balance = 0; // private — 컴파일 에러
account.LogInternal(); // internal — 같은 프로젝트 안이라 OK// Account.cs
namespace CodingNow.Lecture.Oop06;
internal class Account
{
private int balance; // 외부 직접 변경 금지
public Account(int initial)
{
balance = initial;
}
public void Deposit(int amount) // 공개된 동작
{
if (amount <= 0) return;
balance += amount;
}
public int GetBalance() => balance;
internal void LogInternal() // 같은 어셈블리 안에서만
{
Console.WriteLine($"[내부 로그] 잔액={balance}");
}
}**실행 결과**
잔액: 1500
[내부 로그] 현재 잔액 = 1500**메모:** `balance` 를 `private` 으로 막고 `Deposit` 만 열어 두니, 잘못된 변경(음수 입금 등)을 막을 수 있습니다.
예제 3 — `MultiConstructor` : 생성자 오버로딩 + `this(...)`
// Program.cs
using CodingNow.Lecture.Oop06;
var p1 = new Point(); // (0, 0)
var p2 = new Point(5); // (5, 5)
var p3 = new Point(3, 7); // (3, 7)
p1.Print();
p2.Print();
p3.Print();// Point.cs
namespace CodingNow.Lecture.Oop06;
internal class Point
{
public int X;
public int Y;
public Point() : this(0, 0) { } // 다른 생성자에게 위임
public Point(int v) : this(v, v) { } // 한 값을 X, Y 둘 다에
public Point(int x, int y)
{
X = x;
Y = y;
}
public void Print() => Console.WriteLine($"({X}, {Y})");
}**실행 결과**
(0, 0)
(5, 5)
(3, 7)**메모:** `: this(...)` 는 "같은 클래스의 다른 생성자를 먼저 호출"하라는 뜻. 중복 코드를 줄여 줍니다.
예제 4 — `NewKeyword` : 참조의 의미 확인
// Program.cs
using CodingNow.Lecture.Oop06;
var a = new Box(10);
var b = a; // 복사가 아니다! 같은 객체를 b 도 가리킨다.
b.Value = 99;
Console.WriteLine($"a.Value = {a.Value}");
Console.WriteLine($"b.Value = {b.Value}");
var c = new Box(10); // 완전히 다른 객체
Console.WriteLine($"a == b ? {ReferenceEquals(a, b)}");
Console.WriteLine($"a == c ? {ReferenceEquals(a, c)}");// Box.cs
namespace CodingNow.Lecture.Oop06;
internal class Box
{
public int Value;
public Box(int value) => Value = value;
}**실행 결과**
a.Value = 99
b.Value = 99
a == b ? True
a == c ? False**메모:** `b = a` 는 "주소를 복사"입니다. 값 자체가 같은지를 보려면 별도의 비교 로직(다음 단원의 프로퍼티/`Equals` 등)이 필요합니다.
전체 예제 코드 (src/)
src/AccessModifiers/AccessModifiers.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Oop06</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/AccessModifiers/Account.cs
namespace CodingNow.Lecture.Oop06;
internal class Account
{
// private: 같은 클래스 안에서만 접근. 외부에서 직접 바꾸지 못하게 막는다.
private int balance;
public Account(int initial)
{
balance = initial;
}
// public: 외부에서 사용할 동작
public void Deposit(int amount)
{
if (amount <= 0) return; // 잘못된 입력은 무시
balance += amount;
}
// public: 잔액을 조회하는 안전한 통로
public int GetBalance() => balance;
// internal: 같은 어셈블리(프로젝트) 안에서만 보임
internal void LogInternal()
{
Console.WriteLine($"[내부 로그] 현재 잔액 = {balance}");
}
}
src/AccessModifiers/Program.cs
using CodingNow.Lecture.Oop06;
var account = new Account(1000);
account.Deposit(500); // public — 어디서든 호출 가능
Console.WriteLine($"잔액: {account.GetBalance()}");
// account.balance = 9999; // private 필드라서 외부 접근 불가 (주석 해제 시 컴파일 에러)
account.LogInternal(); // internal — 같은 프로젝트 안에서는 OK
src/MultiConstructor/MultiConstructor.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Oop06</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/MultiConstructor/Point.cs
namespace CodingNow.Lecture.Oop06;
internal class Point
{
public int X;
public int Y;
// : this(...) 는 "같은 클래스의 다른 생성자를 먼저 호출"하라는 뜻.
public Point() : this(0, 0) { }
public Point(int v) : this(v, v) { }
public Point(int x, int y)
{
X = x;
Y = y;
}
public void Print() => Console.WriteLine($"({X}, {Y})");
}
src/MultiConstructor/Program.cs
using CodingNow.Lecture.Oop06;
// 같은 클래스에서 인자 개수가 다른 생성자를 골라 호출할 수 있다.
var p1 = new Point(); // (0, 0)
var p2 = new Point(5); // (5, 5)
var p3 = new Point(3, 7); // (3, 7)
p1.Print();
p2.Print();
p3.Print();
src/NewKeyword/Box.cs
namespace CodingNow.Lecture.Oop06;
internal class Box
{
public int Value;
public Box(int value) => Value = value;
}
src/NewKeyword/NewKeyword.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Oop06</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/NewKeyword/Program.cs
using CodingNow.Lecture.Oop06;
var a = new Box(10);
var b = a; // 복사가 아니라 "같은 객체"를 b 도 가리키게 한다.
b.Value = 99;
Console.WriteLine($"a.Value = {a.Value}"); // 99 (a 도 같이 변했다)
Console.WriteLine($"b.Value = {b.Value}"); // 99
var c = new Box(10); // 완전히 별개인 새 객체
Console.WriteLine($"a == b ? {ReferenceEquals(a, b)}"); // True
Console.WriteLine($"a == c ? {ReferenceEquals(a, c)}"); // False
src/PersonBasic/Person.cs
namespace CodingNow.Lecture.Oop06;
internal class Person
{
// 필드: 객체가 가진 데이터
public string Name;
public int Age;
// 생성자: 객체가 만들어질 때 한 번 호출
public Person(string name, int age)
{
this.Name = name;
this.Age = age;
}
// 메서드: 객체가 가진 동작
public void Greet()
{
Console.WriteLine($"안녕, 나는 {Name}({Age}세).");
}
}
src/PersonBasic/PersonBasic.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Oop06</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/PersonBasic/Program.cs
using CodingNow.Lecture.Oop06;
// Person 클래스로 객체 두 개를 만들어 본다.
var alice = new Person("Alice", 30);
alice.Greet();
var bob = new Person("Bob", 25);
bob.Greet();
자주 하는 실수
- `new Person` 만 쓰고 `()` 를 빠뜨려 인스턴스가 만들어지지 않는 줄 안다 — 반드시 `new Person()` 처럼 호출 형태.
- 필드와 매개변수 이름이 같은데 `this.` 를 안 붙여 자기 자신을 자기에게 대입(`Name = Name;`)하는 코드를 쓴다.
- `private` 필드를 외부에서 직접 바꾸려고 `public` 으로 바꿔 버린다 — 캡슐화 깨짐. 다음 단원의 프로퍼티로 해결.
- 두 변수가 같은 객체를 가리키는 줄 모르고 한쪽만 바꿨다고 안심한다.
- 생성자 이름을 `Person()` 가 아니라 `Person CreatePerson()` 처럼 짓는다 — 클래스 이름과 정확히 같아야 합니다.
정리
- 클래스는 설계도, 객체는 그 설계도로 찍은 실체(`new`로 생성)
- 필드는 데이터, 메서드는 동작, 생성자는 객체 탄생 시 초기화 코드
- `this` 는 현재 객체 자신, `private`/`public` 으로 외부 노출 범위 조절
- 클래스는 참조 타입 — 변수는 객체 자체가 아니라 객체의 주소를 담는다
과제
**과제 - 06. 클래스와 객체**
문제 1 — `Book` 클래스 만들기
- 프로젝트 폴더: `Homework01/`
- 핵심 개념: 필드, 생성자, 메서드
요구사항
- `Book` 클래스를 만든다. 필드는 `Title`(string), `Author`(string), `Pages`(int).
- 생성자에서 세 값을 받아 초기화한다.
- `Describe()` 메서드: `"<제목> - <저자> 지음 (<페이지>쪽)"` 형식으로 출력.
- `Program.cs` 에서 책 2~3권을 만들어 `Describe()` 를 호출한다.
예상 출력
객체지향의 사실과 오해 - 조영호 지음 (240쪽)
이펙티브 C# - 빌 와그너 지음 (320쪽)힌트
- 필드 이름은 첫 글자를 대문자로 (관례).
- 생성자 안에서 `this.Title = title;` 처럼 대입.
문제 2 — `BankAccount` 만들기
- 프로젝트 폴더: `Homework02/`
- 핵심 개념: `private` 필드, `public` 메서드, 캡슐화
요구사항
- `BankAccount` 클래스에 `private int balance` 필드를 둔다.
- 생성자: 초기 잔액을 받음.
- `Deposit(int amount)`: 양수만 받아 잔액에 더한다. 음수/0이면 무시.
- `Withdraw(int amount)`: 잔액보다 작거나 같을 때만 인출. 부족하면 `"잔액 부족"` 출력.
- `GetBalance()`: 현재 잔액 반환.
- `Program.cs` 에서 입금/출금을 섞어 호출해 결과를 출력.
예상 출력
잔액: 1500
잔액 부족
잔액: 500힌트
- `private` 으로 `balance` 를 막아 두는 게 핵심. 외부에서는 메서드를 통해서만 바꿔야 한다.
- 음수 입금 같은 잘못된 값은 메서드 안에서 걸러 낸다.
정답 확인
직접 풀어 본 후 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.
정답 (answer/)
homework/answer/Homework01/Book.cs
namespace CodingNow.Lecture.Oop06;
internal class Book
{
public string Title;
public string Author;
public int Pages;
public Book(string title, string author, int pages)
{
this.Title = title;
this.Author = author;
this.Pages = pages;
}
public void Describe()
{
Console.WriteLine($"{Title} - {Author} 지음 ({Pages}쪽)");
}
}
homework/answer/Homework01/Homework01.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Oop06</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
homework/answer/Homework01/Program.cs
using CodingNow.Lecture.Oop06;
var b1 = new Book("객체지향의 사실과 오해", "조영호", 240);
var b2 = new Book("이펙티브 C#", "빌 와그너", 320);
b1.Describe();
b2.Describe();
homework/answer/Homework02/BankAccount.cs
namespace CodingNow.Lecture.Oop06;
internal class BankAccount
{
private int balance;
public BankAccount(int initial)
{
balance = initial;
}
public void Deposit(int amount)
{
if (amount <= 0) return;
balance += amount;
}
public void Withdraw(int amount)
{
if (amount > balance)
{
Console.WriteLine("잔액 부족");
return;
}
balance -= amount;
}
public int GetBalance() => balance;
}
homework/answer/Homework02/Homework02.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Oop06</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
homework/answer/Homework02/Program.cs
using CodingNow.Lecture.Oop06;
var acc = new BankAccount(1000);
acc.Deposit(500);
Console.WriteLine($"잔액: {acc.GetBalance()}");
acc.Withdraw(2000); // 잔액(1500)보다 큼 → 잔액 부족 출력
acc.Withdraw(1000);
Console.WriteLine($"잔액: {acc.GetBalance()}");
직접 해 보기
cd src/PersonBasic
dotnet run
cd ../AccessModifiers
dotnet run
cd ../MultiConstructor
dotnet run
cd ../NewKeyword
dotnet run다음 단원
[07_프로퍼티와_캡슐화](../07_프로퍼티와_캡슐화/) — `public` 필드 대신 더 안전한 프로퍼티로 데이터를 다룹니다.