본문 바로가기

오래된 흔적/C#.NET 기초강좌 #1

[HOONS C#.NET 기초강좌 #1] 07. 참조 형식과 값 형식


hoons닷넷
[ C#.NET 기초강좌 #1]
07. 참조 형식과 값 형식
작성자 : NGong 작성일 : 2011-04-15 오후 4:21:36
E-mail : filmgdh골뱅이gmail.com Homepage : http://blog.daum.net/coolprogramming
이번 장은 중요한 장입니다. 꼭 이해해야하는...

.Net의 형식은 클래스(class), 구조체(struct), 인터페이스(interface), 델리게이트(delegate), 열거형(enum) 이렇게 다섯 가지입니다.
또 .Net은 아래 두 가지 기반의 형식으로 나뉩니다. (상속 구조 그림 = > http://msdn.microsoft.com/ko-kr/library/ms173104.aspx)
  • 첫째, 참조 기반의 형식(class, interface, delegate)
  • 둘째, 값 기반의 형식 (struct, enum)
참조 기반 형식, 값 기반 형식 => http://msdn.microsoft.com/ko-kr/library/3ewxz6et.aspx

첫째, 참조 기반 형식의 특징은
  • 관리 힙에 할당됩니다.
  • System.Value 형식이 아닌 모든 형식으로부터 파생될 수 있습니다.
  • Sealed 형식이 아니라면 기본 형식으로 사용될 수 있습니다.

둘째, 값 기반 형식의 특징은
  • 스택 메모리에 할당됩니다.
  • 항상 System.Value 형식으로부터 파생됩니다.
  • Sealed 형식입니다. 기본 형식으로 사용될 수 없습니다.
  • 기본 생성자는 예약되어 있습니다.
두 기반 형식의 가장 큰 차이점은 참조 기반 형식은 관리 힙에 할당되고 값 기반 형식은 스택 메모리에 할당된다는 것입니다. 아시는 것처럼 힙은 객체를 할당하여 사용하지 않을 때 제거될 수 있으며 스택은 블록({}) 범위에서 메모리가 할당과 해제(제거) 됩니다.

1, 대표적인 참조 기반 형식(class)과 대표적인 값 기반 형식(struct)

C++에서 클래스(class)와 구조체(struct)는 완전히 동일한 것입니다.(디폴트 접근 한정자(클래스는 private, 구조체는 public) 만 빼면)
하지만 .Net의 클래스와 구조체는 전혀 다르게 동작합니다.
클래스는 대표적인 참조 기반의 형식이며 구조체는 대표적인 값 기반의 형식입니다.

다음은 클래스와 구조체의 정의 및 사용 예제입니다.
  1. using System;

    class CPoint // Object에서 파생

    {

    private int x, y;

    public CPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    public CPoint() // 클래스는 인자 없는 기본 생성자를 만들 수 있음.

    {

    x = 0;

    y = 0;

    }

    public void Print()

    {

    Console.Write("base:{0}, ",GetType().BaseType);

    Console.WriteLine("class:({0},{1})",x,y);

    }

    }


    struct SPoint // ValueType에서 파생

    {

    private int x, y;

    public SPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    //public SPoint(){} // 구조체는 기본 생성자를 만들 수 없음.(예약됨)

    public void Print()

    {

    Console.Write("base:{0}, ", GetType().BaseType);

    Console.WriteLine("struct:({0},{1})", x, y);

    }

    }

    class Program

    {

    static void Main()

    {

    CPoint cpt = new CPoint(1, 1);

    cpt.Print();


    SPoint spt = new SPoint(5, 5);

    spt.Print();

    }

    }


base:System.Object, class:(1,1)
base:System.ValueType, struct:(5,5)
결과처럼 구조체(값 기반 형식)는 항상 ValueType으로부터 파생됩니다. 또 구조체(값 기반 형식)는 기본 생성자를 정의할 수 없습니다. 내부적으로 예약되어 있습니다.
GetType()는 자신의 형식 객체를 반환합니다. BaseType은 이 객체의 속성으로 부모 형식을 반환합니다. 다음에 설명하므로 지금은 클래스와 구조체에 집중!

다음은 위 예제의 메모리 그림입니다.
값_형식,_참조_형식_객체_메모리.PNG
cpt는 힙 객체의 참조를 갖는 참조자(참조 변수)이며 spt는 객체 값 자체를 갖는 값 변수입니다. 또 cpt(참조자)는 Main() 함수 블록({})이 종료될 때 변수(참조자)는 바로 사라지지만 cpt가 가리키는 객체는 가비지 컬렉션 대상이 되는 힙 객체입니다. spt는 Main() 함수 블록({})이 종료될 때 변수와 객체(변수가 값 자체)가 모두 바로 사라지는 스택 객체입니다.

구조체는 기본 생성자 정의 없이도(내부적으로 예약됨) 호출할 수 있음!
  1. using System;

    class CPoint

    {

    private int x, y;

    public CPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    public CPoint()

    {

    x = 0;

    y = 0;

    }

    public void Print()

    {

    Console.Write("base:{0}, ", GetType().BaseType);

    Console.WriteLine("class:({0},{1})", x, y);

    }

    }


    struct SPoint

    {

    private int x, y;

    public SPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    //public SPoint(){}

    public void Print()

    {

    Console.Write("base:{0}, ", GetType().BaseType);

    Console.WriteLine("struct:({0},{1})", x, y);

    }

    }

    class Program

    {

    static void Main()

    {

    CPoint cpt = new CPoint();

    cpt.Print();


    SPoint spt = new SPoint();

    spt.Print();

    }

    }


base:System.Object, class:(0,0)
base:System.ValueType, struct:(0,0)
굿!
다음은 메모리 그림입니다.
값_형식,_참조_형식_객체_메모리2.PNG


구조체는 변수 선언이 곧 객체 생성입니다.
  1. using System;

    class CPoint

    {

    public int x, y;

    public void Print()

    {

    Console.Write("base:{0}, ", GetType().BaseType);

    Console.WriteLine("class:({0},{1})", x, y);

    }

    }


    struct SPoint

    {

    public int x, y;

    public void Print()

    {

    Console.Write("base:{0}, ", GetType().BaseType);

    Console.WriteLine("struct:({0},{1})", x, y);

    }

    }

    class Program

    {

    static void Main()

    {

    CPoint cpt; // 객체가 없으므로 말도 않됨!

    cpt.x = 1; // 객체가 없는데 !

    cpt.y = 1; // 객체가 없는데 !

    cpt.Print();// 객체가 없는데 !


    SPoint spt; // 변수 선언이 객체 생성이므로 당근 가능!!

    spt.x = 5; // 굿!

    spt.y = 5; // 굿!

    spt.Print();// 굿!

    }

    }


클래스는 참조자(참조 변수)만 생성하고 객체를 생성하지 않아 오류!!
구조체는 변수 선언이 곧 객체 생성이며 모든 멤버 변수 초기화 후 사용할 수 있습니다.
다음은 메모리 그림입니다.
값_형식,_참조_형식_객체_메모리3.PNG

2, 참조 기반 형식(class)과 값 기반 형식(struct)의 복사

다음 클래스와 구조체 객체의 복사 동작을 설명합니다.

클래스와 구조체의 복사 예제입니다.
  1. using System;

    class CPoint

    {

    public int x, y;

    public CPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    public CPoint()

    {

    x = 0;

    y = 0;

    }

    public void Print()

    {

    Console.WriteLine("class:({0},{1})", x, y);

    }

    }


    struct SPoint

    {

    public int x, y;

    public SPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    public void Print()

    {

    Console.WriteLine("struct:({0},{1})", x, y);

    }

    }

    class Program

    {

    static void Main()

    {

    CPoint cpt1 = new CPoint(1, 1);

    CPoint cpt2 = cpt1;

    cpt1.Print();

    cpt2.Print();

    Console.WriteLine();


    SPoint spt1 = new SPoint(5, 5);

    SPoint spt2 = spt1;

    spt1.Print();

    spt2.Print();

    }

    }


class:(1,1)
class:(1,1)
struct:(5,5)
struct:(5,5)
클래스(참조 기반 형식)는 참조자가 복사되며 구조체(값 기반 형식)는 객체 자체가 복사됩니다.
다음은 예제의 메모리 그림입니다.
참조와_값_복사.PNG

클래스는 두 참조자(cpt1,cpt2)가 하나의 객체를 참조하고 있으므로 객체의 값을 변경하면 두 참조자에 모두 반영되며 구조체는 두 변수(spt1,spt2)가 독립적인 객체를 가지므로 객체의 값을 변경하면 변경된 객체에만 반영됩니다.

클래스와 구조체 객체의 값 변경 예제입니다.
  1. using System;

    class CPoint

    {

    public int x, y;

    public CPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    public CPoint()

    {

    x = 0;

    y = 0;

    }

    public void Print()

    {

    Console.WriteLine("class:({0},{1})", x, y);

    }

    }

    struct SPoint

    {

    public int x, y;

    public SPoint(int _x, int _y)

    {

    x = _x;

    y = _y;

    }

    public void Print()

    {

    Console.WriteLine("struct:({0},{1})", x, y);

    }

    }

    class Program

    {

    static void Main()

    {

    CPoint cpt1 = new CPoint(1, 1);

    CPoint cpt2 = cpt1;

    cpt1.Print();

    cpt2.Print();


    cpt1.x = cpt1.y = 2;

    cpt1.Print();

    cpt2.Print();

    Console.WriteLine();


    SPoint spt1 = new SPoint(5, 5);

    SPoint spt2 = spt1;

    spt1.Print();

    spt2.Print();


    spt1.x = spt1.y = 6;

    spt1.Print();

    spt2.Print();

    }

    }


class:(1,1)
class:(1,1)
class:(2,2)
class:(2,2)
struct:(5,5)
struct:(5,5)
struct:(6,6)
struct:(5,5)
cpt1객체와 spt1객체를 변경한 출력입니다. cpt1은 cpt2와 같은 객체를 참조하므로 하나의 객체를 변경하면 두 참조자에 모두 값이 반영됩니다.
위 예제의 메모리 그림입니다.
참조와_값_복사2.PNG
해서 참조 기반 형식을 값 복사(깊은 복사) 하려면 인터페이스를 사용해야 합니다. 요건 담에...

3, int의 복사

int는 값 기반 형식입니다. 기본 형식에 대한 정리는 다음에...

c#의 기본 형식 => http://msdn.microsoft.com/ko-kr/library/ya5y69ds.aspx
위 표에 나와 있는 형식 중 object와 string을 제외한 모든 형식은 값 기반 형식입니다.
위 내용처럼 모든 C#의 수치형 형식은 모두 구조체(값 기반 형식)입니다.

다음은 int 변수(객체)의 초기화입니다.
  1. using System;

    class Program

    {

    static void Main()

    {

    int n1 = 0;

    int n2 = new int();

    int n3 = new System.Int32();


    Console.WriteLine("{0} {1} {2}", n1, n2, n3);

    }

    }


0 0 0
int 는 System.Int32 구조체의 별칭이며 기본 생성자에서 0으로 초기화됩니다. 기본 값 => http://msdn.microsoft.com/ko-kr/library/83fhsxwc.aspx

모든 수치형은 값 기반 형식이므로 아래처럼 복사됩니다.
  1. using System;

    class Program

    {

    static void Main()

    {

    int n1 = 10;

    int n2 = n1; //1.

    Console.WriteLine("{0} {1}", n1, n2);

    n1 = 20; //2.

    Console.WriteLine("{0} {1}", n1, n2);

    n2 = 30; //3.

    Console.WriteLine("{0} {1}", n1, n2);

    }

    }


10 10
20 10
20 30
다른 수치형도 이처럼 동작하겠죠?
아래는 간단한 메모리 그림입니다.
int의_복사_동작.PNG

오늘은 여기까지~~~