1. 클래스(Class)


클래스를 알아보기에 앞서 우리는 객체(Object)에 대해 알아야 될 필요가 있습니다.

서점이나 인터넷에서 프로그래밍 관련 서적 강좌들을 보면 항상 나오는 말이 객체 지향 프로그래밍

(Object Oriented Programming)이라는 OOP를 언급합니다. 도대체 객체란 무엇일까요?

쉽게 말해 우리가 인지하고 사용하고 보고 듣고 하는 모든 것을 지칭하는 단어 입니다.

영화 메트릭스에서 가상세계를 구성하는 모든 것을 객체라 할수 있고 이 객체들은 각각 유기적으로

행동하고 움직이고 구성됩니다. 이런 포괄적인 의미의 객체는 속성과 기능을 가지고 있습니다.

예를 들자면 동물의 속성은 종, 크기, 성격, 색깔 등으로 나눌수 있고 기능은 걷기, 먹기, 보기, 듣기

등으로 볼수 있습니다.


프로그래밍에서 보자면 속성은 '데이터' 기능은 '메서드'로 표현 할수 있습니다.

추운 겨울날 우리가 사먹는 붕어빵을 만들기 위해서는 많은 속성들이 필요합니다.

밀가루 반죽, 팥 앙금, 익히기 위한 불, 이러한 것 들이 모여 붕어빵이 됩니다.

이때 여러개의 붕어빵을 만들기 위한 틀을 클래스라 할수 있고 붕어빵은 객체라 볼수 있습니다.


2. 실습


class 구조

[접근 제한자] class [클래스 이름]
{
    //메서드, 데이터...
}

접근 제한자는 메서드를 배울때와 동일한 키워드(public, priavte...)를 사용합니다.

접근 제한자 키워드가 없다면 c#에서는 private로 선언됩니다.

소스코드

using System;

namespace ConsoleApp1
{
        class hero
        {
            public string sName;
            public int nHP;
            public int nPower;

            public void Attack(string target)
            {
                Console.WriteLine("{0}에 {1}의 데미지를 주었습니다.", target, nPower);
            }
        }

        static void Main(string[] args)
        {
            hero newHero = new hero();
            newHero.sName = "맥스";
            newHero.nHP = 10;
            newHero.nPower = 2;

            Console.WriteLine("이름 : {0}, 체력 : {1}, 공격력 : {2}", newHero.sName, newHero.nHP, newHero.nPower);
            newHero.Attack("허수아비");
        }
}

결과

이름 : 맥스, 체력 : 10, 공격력 : 2
허수아비에 2의 데미지를 주었습니다.


우리는 hero 라는 클래스를 만들었습니다. 이 클래스는 이름, 체력, 공격력이라는 데이터를 가지고 있고 공격이라는 메서드를

사용할수 있습니다. 완벽한 hero의 클래스는 아니지만 이렇게 추상화(중요한 내용에만 중점을 두고 이를 간략화 시킨 것)하여

다양한 hero들을 만들어 낼수 있습니다.


우리가 만든 클래스는 생성을 하여 데이터들을 초기화 해주고 사용하여야 합니다. 추상화된 클래스는 메모리에 데이터들이 들어갈

공간을 확보만 하고 데이터들은 없기 때문에 바로 사용한다면 에러가 발생합니다.

우선 우리가 주목할 코드는 클래스의 생성 코드입니다.


소스코드 19줄을 보면 new라는 키워드가 등장 합니다. 이것은 객체를 새롭게 생성할때 마다 사용되는 키워드로

객체 생성 방법은 다음과 같습니다.


[클래스명] [사용할 이름] = new [클래스명]();


생성된 객체는 닷(.)을 통해 public 필드(데이터)에 접근할수 있습니다.

이렇게 각 데이터들을 호출하여 값을 초기화 또는 변경을 하여 사용할수 있습니다.



생성자, 소멸자

우리가 만든 클래스는 생성자가 없더라도 컴파일러에서 자동적으로 기본적인 생성자를 제공합니다. 만약 직접 생성자를

클래스에서 만들었다면 컴파일러는 기본 생성자를 지원 하지 않습니다.


생성자 : 객체 생성시 호출되는 메소드

소멸자 : 객체 소멸시 호출되는 메소드


이 시대를 살아가면서 우리는 흑수저, 금수저라는 말을 씁니다.

사람 또한 객체이며 사람이 태어날때 생성자가 호출된 시기이고 사람이 죽었을때 소멸자가 호출되는 것과 마찬가지 입니다.

같은 객체지만 무엇인가를 가지고 생성되기도 하고 무엇인가를 남기고 소멸되기도 하는데 이와 같은 맥락으로

우리는 생성할때 무엇인가를 줄수도 소멸할때 무엇인가를 남길수도 있습니다.


컴퓨터가 가지는 자원은 유한하며 이때문에 자원을 유지시키고 안정시키는데 프로그래머는 많은 공을 들입니다.

이렇게 최적화를 하고 안정성을 높이기위해 생성자와 소멸자를 이용하기도 합니다.


생성자 구조

class [클래스명]
{
    [접근 제한자] [클래스명] ( 매개변수 )
    {
        //
    }
    // 필드
    // 메서드
}

생성자의 이름은 클래스의 이름과 동일하고 매개변수를 받을수 있으며 특정 반환형식을 사용할수 없습니다.


소멸자 구조

class [클래스명]
{
    ~[클래스명] ()
    {
        //
    }
}

생성자와 마찬가지로 소멸자도 클래스 이름과 동일하지만 이름 앞에 ~기호를 붙여 사용합니다.


소스코드

using System;

namespace ConsoleApp1
{
    class TestClass
    {
        private string sName;
        public TestClass(string name)
        {
            sName = name;
            Console.WriteLine(sName + " 생성!");
        }
        ~TestClass()
        {
            Console.WriteLine(sName + " 소멸!");
        }
    }

    static void Main(string[] args)
    {
        TestClass testA = new TestClass("A 객체");
        TestClass testB = new TestClass("B 객체");
        TestClass testC = new TestClass("C 객체");
    }
}

결과

A 객체 생성!
B 객체 생성!

C 객체 생성!

C 객체 소멸!
B 객체 소멸!
A 객체 소멸!


위의 소스코드는 기본적인 객체의 생성과 소멸을 보여줍니다. 여기서 특이한 점은 A, B, C 순서로 생성 되었지만

소멸시에는 C, B, A 순으로 소멸되었습니다. 이는 c#에서 지원하는 메모리 반환인 가비지 컬렉터가 실행된 결과입니다.


가비지 컬렉터

c나 c++ 같은 언어에서는 프로그래머가 언제 메모리를 반환할지에 대한 제어가 가능합니다.

c#은 생산성과 안정성을 높이기위해 다른 언어에서 제어가 가능했던 메모리 반환에 대해 가비지 컬렉터를 지원

하게 되었습니다. 메모리에 대한 제어는 많은 오류를 일으키고 생산성을 저하시키는 단점이 있었는데 이를 보완

한것입니다. 하지만 가비지 컬렉터 또한 만능은 아닙니다. 위의 코드 결과에서 보듯이 가비지 컬렉터는

언제 어떻게 어는 시점에 실행될지 아무도 모르기 때문에 소멸자를 쓰는 이점이 사라집니다.


c#에서는 이러한 가비지 컬렉터 때문에 프로그래머가 제어하기 어렵고 무분별한 소멸자 사용은

성능 저하를 일으켜 되도록이면 소멸자는 사용하지 않는것이 좋습니다.



3. 심화 학습


1. 생성자는 오버로딩이 가능하며 멤버 변수(필드)에 명시적으로 초기화가 가능합니다.

2. 초기화 되지 않은 멤버 변수는 기본적으로 0으로 초기화 됩니다.

3. 클래스에서는 this라는 키워드를 지원합니다.

   this는 자기 자신을 뜻 하고 자신의 인스턴스에 대한 참조를 의미 합니다.

4. new로 생성된 클래스 인스턴스는 메모리의 Heap 영역에 할당이 됩니다.

   이 Heap 영역에 있는 인스턴스에 대한 참조가 끊기게 되면 이는 가비지(쓰레기 데이터)가 됩니다.

5. 얕은 복사와 깊은 복사

   클래스는 태생이 참조형식 이어서 같은 클래스 끼리의 복사는 얕은 복사(메모리 주소)가 일어나고

   깊은 복사(값에 의한 복사)는 다른 인스턴스를 만들어야 합니다.

6. 임의적으로 가비지 콜렉터를 호출하였지만 호출시점과 실행시점이 다릅니다.


아래 테스트 코드를 통해 위의 내용들을 확인해 보겠습니다.



소스코드

using System;

namespace ConsoleApp1
{
        class Test
        {
            public int nNum1 = 100;
            public int nNum2;

            string sName = "객체";

            public Test()
            {
                Console.WriteLine("Test() 실행");
            }
            //1. 생성자의 오버로딩
            public Test(string name)
            {
                sName = name;
                Console.WriteLine(sName + " 생성!");
            }

            //3. 이름이 동일한 변수이지만 this 키워드를 사용하여
            //   자기 자신의 인스턴스에 접근
            public Test(int nNum1, int nNum2)
            {
                this.nNum1 = nNum1;
                this.nNum2 = nNum2;
            }
            ~Test()
            {
                Console.WriteLine("{0} 소멸!", sName);
            }

            public void Show()
            {
                Console.WriteLine("{0} nNum1 : {1}, nNum2 : {2}", sName, nNum1, nNum2);
            }

            //5. 깊은 복사
            //새로운 인스턴스를 생성해 클론을 만듭니다.
            public Test GetClone()
            {
                Test newInstance = new Test("클론 객체");
                newInstance.nNum1 = this.nNum1;
                newInstance.nNum2 = this.nNum2;

                return newInstance;
            }
        }

        static void Main(string[] args)
        {
            //2. 초기화 되지 않은 멤버 변수
            Test testA = new Test("A 객체");
            testA.Show();

            
            Test testB = new Test(1, 3);
            testB.Show();

            //4. 참조가 끊겨 가비지가 되었음
            testB = null;       

            //6. 가비지 콜렉터 호출
            System.GC.Collect();
            Console.WriteLine();
            Console.WriteLine("가비지 컬렉터 호출.....");
            Console.WriteLine();

            Console.WriteLine("얕은 복사");
            Test testC = testA;
            testC.nNum1 = -10;
            testC.nNum2 = -30;
            testA.Show();       //testC의 필드에 값을 할당했지만 testA의 값이 변경되었음
            testC.Show();
            Console.WriteLine();

            Console.WriteLine("깊은 복사");
            testC = testA.GetClone();
            testC.nNum1 = 88;
            testC.nNum2 = 99;
            testA.Show();
            testC.Show();
            Console.WriteLine();
        }
}

결과

A 객체 생성!
A 객체 nNum1 : 100, nNum2 : 0
객체 nNum1 : 1, nNum2 : 3

가비지 컬렉터 호출.....

얕은 복사
A 객체 nNum1 : -10, nNum2 : -30
A 객체 nNum1 : -10, nNum2 : -30
객체 소멸!

깊은 복사
클론 객체 생성!
A 객체 nNum1 : -10, nNum2 : -30
클론 객체 nNum1 : 88, nNum2 : 99

클론 객체 소멸!
A 객체 소멸!


디버그 모드로 주석과 같이 확인 하시면 소스코드를 이해하는데 도움이 됩니다.

main 안의 코드를 확인해보면  다음과 같습니다.


1. testA의 객체를 생성하고 필드값을 출력

2. testB의 객체를 생성하고 필드값 초기화 후 출력

3. testB 객체에 대한 참조 끊기

4. 가비지 콜렉터를 호출

5. testC는 testA의 값을 복사 ( 얕은 복사로 testA의 참조만 복사 되었음)

6. testC의 값을 변경 ( testC의 값을 변경하였으나 testA의 값이 변경)

7. 4번에서 부른 가비지 콜렉터가 이제 실행

8. testC에 testA의 복사를 다시 시도 GetClone() 메서드를 호출하여 인스터스 생성후 복사

9. 깊은 복사가된 testC의 필드 값을 변경후 두 객체를 출력


이 예제코드로 심화 학습에서 언급한 내용들을 확인해 볼수 있었습니다.

Class는 앞으로도 몇번에 걸쳐 강좌에서 다루도록 하겠습니다.


복잡하고 긴 글 학습하시느라 수고하셨습니다.

+ Recent posts