← C# 강의 목록으로
🧩
객체지향
객체지향 · 선수: 메서드

06. 클래스와 객체

데이터와 기능을 하나로 묶은 사용자 정의 타입이 클래스입니다. 필드·메서드·생성자·this·접근 제한자를 익히고, new 로 객체를 만들어 메모리 모형을 머릿속에 그려 봅니다.

C#.NET 8객체지향클래스
소요 시간
약 1~1.5시간
난이도
📊 중급
선수 조건
🎯 메서드까지 이수
결과물
데이터와 기능을 하나로 묶은 사용자 정의 타입이 클래스입니다. 필드·메서드·생성자·this·접근 제한자를 익히고, new 로 객체를 만들어 메모리 모형을 머릿속에 그려 봅니다.

이 강의에서 배우는 것

  • 1`class` 키워드로 새로운 타입을 정의할 수 있다
  • 2필드·메서드·생성자가 무엇인지 구별할 수 있다
  • 3`this` 키워드의 역할을 안다
  • 4접근 제한자(`public`/`private`/`internal`/`protected`)를 적재적소에 쓸 수 있다
  • 5`new` 로 객체를 만들고, 참조 변수가 실제로 무엇을 가리키는지 이해한다

소개

지금까지는 변수와 메서드를 따로따로 다뤘다면, 이제부터는 데이터와 기능을 하나로 묶어 다루는 **클래스(class)** 를 배웁니다. 클래스는 객체를 찍어 내는 **틀(설계도)** 이고, 그 틀로 만든 인스턴스가 **객체(object)** 입니다.

핵심 개념

1) 클래스 = 설계도, 객체 = 실체

csharp
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)**: 클래스가 가진 동작(함수)
csharp
class Counter
{
    public int Count;                       // 필드
    public void Increase() => Count++;      // 메서드
}

3) 생성자(constructor) 와 `this`

객체가 만들어질 때 자동으로 호출되는 특수 메서드입니다. **클래스 이름과 똑같이** 짓고, 반환 타입은 쓰지 않습니다.

csharp
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` 키워드와 참조

csharp
Person a = new Person("Alice", 30);
Person b = a;          // b 는 a 와 같은 객체를 가리킴 (복사 아님!)
b.Age = 99;
Console.WriteLine(a.Age); // 99 → a 도 같이 변함

클래스는 **참조 타입(reference type)**. 변수에 다른 변수를 대입해도 객체가 복제되지 않고, 같은 객체를 두 변수가 함께 가리킵니다.

핵심 예제

예제 1 — `PersonBasic` : 가장 단순한 클래스

csharp
// Program.cs
using CodingNow.Lecture.Oop06;

var alice = new Person("Alice", 30);
alice.Greet();

var bob = new Person("Bob", 25);
bob.Greet();
csharp
// 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}세).");
    }
}

**실행 결과**

text
안녕, 나는 Alice(30세).
안녕, 나는 Bob(25세).

**메모:** 한 `Person` 클래스로 서로 독립된 객체 두 개를 찍어 냈습니다.

예제 2 — `AccessModifiers` : 접근 제한자 시연

csharp
// 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
csharp
// 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}");
    }
}

**실행 결과**

text
잔액: 1500
[내부 로그] 현재 잔액 = 1500

**메모:** `balance` 를 `private` 으로 막고 `Deposit` 만 열어 두니, 잘못된 변경(음수 입금 등)을 막을 수 있습니다.

예제 3 — `MultiConstructor` : 생성자 오버로딩 + `this(...)`

csharp
// 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();
csharp
// 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})");
}

**실행 결과**

text
(0, 0)
(5, 5)
(3, 7)

**메모:** `: this(...)` 는 "같은 클래스의 다른 생성자를 먼저 호출"하라는 뜻. 중복 코드를 줄여 줍니다.

예제 4 — `NewKeyword` : 참조의 의미 확인

csharp
// 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)}");
csharp
// Box.cs
namespace CodingNow.Lecture.Oop06;

internal class Box
{
    public int Value;
    public Box(int value) => Value = value;
}

**실행 결과**

text
a.Value = 99
b.Value = 99
a == b ? True
a == c ? False

**메모:** `b = a` 는 "주소를 복사"입니다. 값 자체가 같은지를 보려면 별도의 비교 로직(다음 단원의 프로퍼티/`Equals` 등)이 필요합니다.

전체 예제 코드 (src/)

src/AccessModifiers/AccessModifiers.csproj

xml
<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

csharp
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

csharp
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

xml
<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

csharp
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

csharp
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

csharp
namespace CodingNow.Lecture.Oop06;

internal class Box
{
    public int Value;

    public Box(int value) => Value = value;
}

src/NewKeyword/NewKeyword.csproj

xml
<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

csharp
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

csharp
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

xml
<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

csharp
using CodingNow.Lecture.Oop06;

// Person 클래스로 객체 두 개를 만들어 본다.
var alice = new Person("Alice", 30);
alice.Greet();

var bob = new Person("Bob", 25);
bob.Greet();

자주 하는 실수

  1. `new Person` 만 쓰고 `()` 를 빠뜨려 인스턴스가 만들어지지 않는 줄 안다 — 반드시 `new Person()` 처럼 호출 형태.
  2. 필드와 매개변수 이름이 같은데 `this.` 를 안 붙여 자기 자신을 자기에게 대입(`Name = Name;`)하는 코드를 쓴다.
  3. `private` 필드를 외부에서 직접 바꾸려고 `public` 으로 바꿔 버린다 — 캡슐화 깨짐. 다음 단원의 프로퍼티로 해결.
  4. 두 변수가 같은 객체를 가리키는 줄 모르고 한쪽만 바꿨다고 안심한다.
  5. 생성자 이름을 `Person()` 가 아니라 `Person CreatePerson()` 처럼 짓는다 — 클래스 이름과 정확히 같아야 합니다.

정리

  • 클래스는 설계도, 객체는 그 설계도로 찍은 실체(`new`로 생성)
  • 필드는 데이터, 메서드는 동작, 생성자는 객체 탄생 시 초기화 코드
  • `this` 는 현재 객체 자신, `private`/`public` 으로 외부 노출 범위 조절
  • 클래스는 참조 타입 — 변수는 객체 자체가 아니라 객체의 주소를 담는다

과제

**과제 - 06. 클래스와 객체**

문제 1 — `Book` 클래스 만들기

  • 프로젝트 폴더: `Homework01/`
  • 핵심 개념: 필드, 생성자, 메서드

요구사항

  • `Book` 클래스를 만든다. 필드는 `Title`(string), `Author`(string), `Pages`(int).
  • 생성자에서 세 값을 받아 초기화한다.
  • `Describe()` 메서드: `"<제목> - <저자> 지음 (<페이지>쪽)"` 형식으로 출력.
  • `Program.cs` 에서 책 2~3권을 만들어 `Describe()` 를 호출한다.

예상 출력

text
객체지향의 사실과 오해 - 조영호 지음 (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` 에서 입금/출금을 섞어 호출해 결과를 출력.

예상 출력

text
잔액: 1500
잔액 부족
잔액: 500

힌트

  • `private` 으로 `balance` 를 막아 두는 게 핵심. 외부에서는 메서드를 통해서만 바꿔야 한다.
  • 음수 입금 같은 잘못된 값은 메서드 안에서 걸러 낸다.

정답 확인

직접 풀어 본 후 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.

정답 (answer/)

homework/answer/Homework01/Book.cs

csharp
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

xml
<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

csharp
using CodingNow.Lecture.Oop06;

var b1 = new Book("객체지향의 사실과 오해", "조영호", 240);
var b2 = new Book("이펙티브 C#", "빌 와그너", 320);

b1.Describe();
b2.Describe();

homework/answer/Homework02/BankAccount.cs

csharp
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

xml
<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

csharp
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()}");

직접 해 보기

bash
cd src/PersonBasic
dotnet run

cd ../AccessModifiers
dotnet run

cd ../MultiConstructor
dotnet run

cd ../NewKeyword
dotnet run

다음 단원

[07_프로퍼티와_캡슐화](../07_프로퍼티와_캡슐화/) — `public` 필드 대신 더 안전한 프로퍼티로 데이터를 다룹니다.

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗