본문 바로가기

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

[HOONS C#.NET 기초강좌 #1] 05. C# 클래스


hoons닷넷
[ C#.NET 기초강좌 #1]
05. C# 클래스
작성자 : NGong 작성일 : 2011-04-15 오후 4:19:08
E-mail : filmgdh골뱅이gmail.com Homepage : http://blog.daum.net/coolprogramming
앞 시간에 "클래스는 객체들의 공통된 개념과 정의를 표현하는 틀이다."라고 배웠습니다.
이제 C#에서 클래스를 정의하고 객체를 생성하는 방법을 공부하겠습니다.

1, 클래스의 구조

연습용 클래스(Point)를 정의하도록 합니다.
Point 클래스는 X, Y 좌표 평면상의 한 점을 추상화한 클래스입니다.
한 점을 표현하기 위해서는 X와 Y 좌표의 상태를 표현할 수 있어야 합니다.
  • 그래서 정적 속성 x,y를 멤버 변수로 표현했습니다. 이런 속성을 C#에서 필드(field)라 합니다. 혹은 멤버 변수라 합니다.
또 X와 Y 좌표 평면상에서 수행할 수 있는 행동은 여러 가지가 있습니다. 지금은 클래스의 좌표를 출력하는 행동을 가집니다.
  • 두 가지 동적 행동 Point()와 Print()를 가집니다. 이런 행동을 C#에서 메소드(method)라 합니다. 혹은 멤버 함수라 합니다.

Point 클래스의 구조는 아래 그림과 같습니다.
클래스구조.PNG
접근 한정자 = > http://msdn.microsoft.com/ko-kr/library/ms173121.aspx

아래는 간단한 사용 예입니다.
  1. class Point

    {

    private int x;

    private int y;


    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    public void Print()

    {

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

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point(5,5);


    pt.Print();

    }

    }




(5,5)
pt가 Point 클래스의 '객체'입니다. 객체는 new 연산자로 생성합니다. 정확히 말하면 pt는 Point 객체의 참조 변수(참조자)입니다. 실제 객체는 관리 힙에 생성되며 pt는 이 객체를 참조합니다.
아래는 pt 객체의 메모리 그림입니다.
pt 객체는 클래스 형식으로 관리 힙에 생성됩니다. 클래스 형식은 참조 형식으로 모든 참조 형식 객체는 힙에 생성되며 가비지 컬렉션(자동 메모리 관리)됩니다.(아래쪽에서 설명합니다.)
Stack_Heap.PNG

아래 그림처럼
꼭 나눠 생각할 필요는 없지만 Program 클래스는 클라이언트로 Point는 서버로 동작합니다.
서버클라이언트Point객체생성.PNG

그럼 하나하나 공부해 볼까요?

2, 클래스와 객체

다음은 필드만 갖는 Point 클래스입니다.
  1. class Point

    {

    public int x;

    public int y;

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point();

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);


    pt.x = 10;

    pt.y = 20;

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);

    }

    }




(0, 0)
(10, 20)
new 키워드로 객체를 생성합니다.
출력 결과처럼 모든 멤버 변수(필드)는 기본 값으로 초기화됩니다. int의 기본 값은 0이므로 (0,0)이 출력됩니다.
기본 값 => http://msdn.microsoft.com/ko-kr/library/83fhsxwc.aspx
필드 앞에 붙은 public은 접근 한정자입니다. public은 어떤 클라이언트 코드(다른 클래스, 다른 어셈블리 클래스)에서도 접근할 수 있습니다.

위 코드는 아래 생성자를 사용합니다.
생성자란 객체가 메모리에 할당(생성) 될 때 호출되는 특수한 멤버 함수(메소드)입니다. 항상 클래스 이름과 같고 반환 자료형이 없습니다.
  1. class Point

    {

    public int x;

    public int y;

    public Point()

    {

    x = 0;

    y = 0;

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point();

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);


    pt.x = 10;

    pt.y = 20;

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);

    }

    }




(0, 0)
(10, 20)
당근 결과는 같습니다.

다른 값 초기화
  1. class Point

    {

    public int x;

    public int y;

    public Point()

    {

    x = 5;

    y = 5;

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point();

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);


    pt.x = 10;

    pt.y = 20;

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);

    }

    }




(5, 5)
(10, 20)

필드에서 바로 초기화
  1. class Point

    {

    public int x=5;

    public int y=5;

    public Point()

    {

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point();

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);


    pt.x = 10;

    pt.y = 20;

    System.Console.WriteLine("({0}, {1})", pt.x, pt.y);

    }

    }




(5, 5)
(10, 20)

  1. class Point

    {

    public int x;

    public int y;

    public Point()

    {

    x = 0;

    y = 0;

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt1 = new Point();

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);


    Point pt2 = new Point(5, 5);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);

    }

    }




(0, 0)
(5, 5)
this 키워드는 객체 자신의 참조자입니다. 또 pt1과 pt2는 서로 독립적인 객체입니다.
생성자가 함수 중복되었습니다.
아래는 그림입니다.
Point객체2.PNG

두 객체(pt1,pt2)는 독립적인 객체입니다.
  1. class Point

    {

    public int x;

    public int y;

    public Point()

    {

    x = 0;

    y = 0;

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt1 = new Point();

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);


    Point pt2 = new Point(5, 5);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);


    pt1.x = 10;

    pt1.y = 10;

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);

    }

    }




(0, 0)
(5, 5)
(10, 10)
(5, 5)
pt1객체의 값만 변경됩니다.

this 키워드 사용(보통 생략됩니다.)

  1. class Point

    {

    public int x;

    public int y;

    public Point()

    {

    x = 0;

    y = 0;

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt1 = new Point();

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);


    Point pt2 = new Point(5, 5);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);


    pt1 = pt2; // 참조 복사

    System.Console.WriteLine();

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);

    }

    }




(0, 0)
(5, 5)
(5, 5)
(5, 5)
참조 객체의 복사는 객체 값의 복사가 아닌 참조자의 복사입니다.
(참조 복사와 값 복사는 다시 설명합니다.)
참조복사(1).PNG

같은 객체를 참조하는 pt1과 pt2 중 하나를 수정하면 다른 하나도 당근 수정됩니다.
  1. class Point

    {

    public int x;

    public int y;

    public Point()

    {

    x = 0;

    y = 0;

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt1 = new Point();

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);

    Point pt2 = new Point(5, 5);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);


    pt1 = pt2; // 참조 복사

    System.Console.WriteLine();

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);


    pt1.x = pt1.y = 100;

    System.Console.WriteLine();

    System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);

    System.Console.WriteLine("({0}, {1})", pt2.x, pt2.y);

    }

    }




(0, 0)
(5, 5)
(5, 5)
(5, 5)
(100, 100)
(100, 100)
설명은 아래 그림으로..
참조복사2.PNG

객체는 위와 같이 속성(필드)과 행동(메소드) 들의 집합이지만 클라언트 측면에서 보면 속성(필드)에는 관심이 없고 단지 서버의 서비스를 이용하는 것에만 관심이 있으므로 객체는 행동들만의 집합(특히 공개 메소드 집합-인터페이스)으로 보는 경향이 강합니다.
그래야 객체지향 특징인 캡슐화가 좋아지며 변경이나 재사용성이 좋아지게 됩니다.
결론은 여러 가지 이유로 인해 클라이언트에서 객체의 속성(필드)를 직접 접근하는 것은 절대 피해야 합니다. 오직 객체가 제공하는 인터페이스(여기서는 공개 메소드 시그니처)만을 이용하여 서비스를 제공받을 수 있어야 합니다.

아래는 객체가 제공하는 인터페이스를 이용한 x,y 좌표 출력입니다.
  1. class Point

    {

    private int x; // 라이언트에게 숨김.

    private int y; // 클라이언트에게 숨김.

    public Point():this(0,0)

    {

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    public void Print( )

    {

    //this.x, this.y => this 키워드 생략

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

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt1 = new Point();

    // x, y 클라이언트에서 접금하지 못합니다.

    //System.Console.WriteLine("({0}, {1})", pt1.x, pt1.y);

    pt1.Print();

    Point pt2 = new Point(5, 5);

    pt2.Print();



    pt1 = pt2; // 참조 복사

    System.Console.WriteLine();

    pt1.Print();

    pt2.Print();

    }

    }




(0, 0)
(5, 5)
(5, 5)
(5, 5)
필드는 private 접근 한정자를 인터페이스(공개 메소드)는 public을 사용합니다.

이제 객체의 속성(필드)을 클라이언트에서 접근할 수 없으므로 필드를 변경하거나 가져오기 위한 공개 메소드를 클라이언트에게 제공해야 합니다. 이렇게 공개 메소드 중에 멤버 데이터(필드)를 변경할 목적으로 사용되는 메소드를 setter라하고 멤버 데이터(필드)를 가져오기 위해 사용되는 메소드getter라 합니다.

다음은 getter와 setter를 제공하는 Point 클래스입니다.
  1. class Point

    {

    private int x;

    private int y;

    public Point()

    : this(0, 0)

    {

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    public void Print()

    {

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

    }

    public int GetX()

    {

    return x;

    }

    public int GetY()

    {

    return y;

    }

    public void SetX(int x)

    {

    this.x = x;

    }

    public void SetY(int y)

    {

    this.y = y;

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt1 = new Point();

    pt1.Print();

    Point pt2 = new Point(5, 5);

    pt2.Print();



    pt1 = pt2;

    System.Console.WriteLine();

    pt1.Print();

    pt2.Print();


    pt1.SetX(100); //setter getter 사용

    pt1.SetY(100);

    System.Console.WriteLine();

    System.Console.WriteLine("({0}, {1})", pt1.GetX(), pt1.GetY());

    System.Console.WriteLine("({0}, {1})", pt2.GetX(), pt2.GetY());

    }

    }




(0, 0)
(5, 5)
(5, 5)
(5, 5)
(100, 100)
(100, 100)
결과는 쉽죠?

3, 클래스 속성(Property)

클래스 대부분에서 getter와 setter를 지원하기 때문에 C#에서는 이 getter, setter를 문법적으로 지원합니다.
이 문법을 C#에서 속성이라 합니다.
속성은 get, set 키워드를 사용합니다.

다음은 위 예제를 속성 문법을 사용하여 구현한 코드입니다.
  1. class Point

    {

    private int x;

    private int y;

    public Point()

    : this(0, 0)

    {

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    public void Print()

    {

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

    }

    public int X

    {

    get { return x; }

    set { x = value; }

    }

    public int Y

    {

    get { return y; }

    set { y = value; }

    }

    //public int GetX()

    //{

    // return x;

    //}

    //public int GetY()

    //{

    // return y;

    //}

    //public void SetX(int x)

    //{

    // this.x = x;

    //}

    //public void SetY(int y)

    //{

    // this.y = y;

    //}

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point();

    pt.Print();


    pt.X = 100; //pt.SetX(100);

    pt.Y = 100; //pt.SetY(100);

    System.Console.WriteLine();

    System.Console.WriteLine("({0}, {1})", pt.X, pt.Y);//pt.GetX()

    }

    }




(0, 0)
(100, 100)
보통 관례적으로 속성은 첫 글자를 대문자로 시작합니다.
이처럼 속성은 단지 특수한 메소드일 뿐입니다.
컴파일러가 내부적으로 속성에 대한 getter, setter 메소드를 만들어 냅니다.


아래처럼 연산도 가능합니다.

  1. class Point

    {

    private int x;

    private int y;

    public Point()

    : this(0, 0)

    {

    }

    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    public void Print()

    {

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

    }

    public int X

    {

    get { return x; }

    set { x = value; }

    }

    public int Y

    {

    get { return y; }

    set { y = value; }

    }

    //public int GetX()

    //{

    // return x;

    //}

    //public int GetY()

    //{

    // return y;

    //}

    //public void SetX(int x)

    //{

    // this.x = x;

    //}

    //public void SetY(int y)

    //{

    // this.y = y;

    //}

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point();

    pt.Print();


    pt.X++; //pt.SetX(pt.GetX()+1);

    pt.Y++; //pt.SetY(pt.GetX()+1);

    System.Console.WriteLine();

    System.Console.WriteLine("({0}, {1})", pt.X, pt.Y);

    }

    }




(0, 0)
(1, 1)
사용자 getter, setter를 구현하는 것보다 훨씬 간결합니다.

또 속성이 있으면 아래처럼 클라이언트 초기화가 가능합니다.
  1. class Point

    {

    private int x;

    private int y;


    public void Print()

    {

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

    }

    public int X

    {

    get { return x; }

    set { x = value; }

    }

    public int Y

    {

    get { return y; }

    set { y = value; }

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt1 = new Point() { X = 5, Y = 5 };

    pt1.Print();

    Point pt2 = new Point { X = 5, Y = 5 };

    pt2.Print();

    }

    }




(5, 5)
(5, 5)
생성자의 인자를 사용하지 않고도 속성으로 초기화가 가능합니다.

4, 클래스 수준 멤버와 인스턴스(객체) 수준 멤버

지금까지 우리가 사용한 멤버들은 인스턴스 수준의 멤버(인스턴스를 생성하고 인스턴스가 독립적으로 갖는 멤버) 필드, 속성입니다.
하지만 구현(문제 도메인)에 따라 멤버들을 인스턴스 수준이 아닌 클래스 수준의 멤버(인스턴스를 생성하지 않고 사용 가능한 멤버) 필드, 속성이 필요한 경우도 있습니다.

다음은 Point 클래스를 클래스 수준의 멤버로 정의합니다. (static 키워드 사용)
  1. class Point

    {

    private static int x = 0;

    private static int y = 0;


    public static void Print()

    {

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

    }

    }

    class Program

    {

    static void Main()

    {

    //클래스 수준의 멤버는 객체를 생성하지 않고 필드와 메소드를 사용함!

    //클래스 이름을 이용하여~

    Point.Print();

    }

    }




(0, 0)
클래스 수준의 멤버는 클래스 이름으로 멤버를 사용합니다.

또 클래스 수준의 멤버를 초기화하기 위해 static 생성자를 사용할 수 있습니다. static 생성자는 오직 static 멤버를 초기화하기 위해 사용되고 static 멤버가 참조되기 직전에 단 한 번 호출됩니다.

static 생성자 예제
  1. class Point

    {

    private static int x;

    private static int y;


    static Point()

    {

    x = 10;

    y = 10;

    }

    public static void Print()

    {

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

    }

    }

    class Program

    {

    static void Main()

    {

    Point.Print();

    }

    }




(10, 10)
static 생성자는 접근 한정자를 사용할 수 없습니다.

다음은 static 속성을 사용한 코드입니다.
  1. class Point

    {

    private static int x;

    private static int y;


    static Point()

    {

    x = 10;

    y = 10;

    }

    public static void Print()

    {

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

    }

    public static int X

    {

    get { return x; }

    set { x = value; }

    }

    public static int Y

    {

    get { return y; }

    set { y = value; }

    }

    }

    class Program

    {

    static void Main()

    {

    Point.X = 100;

    Point.Y = 100;

    Point.Print();

    System.Console.WriteLine("({0}, {1})", Point.X, Point.Y);

    }

    }




(100, 100)
(100, 100)

다음은 Point 클래스에 클래스 수준 멤버와 인스턴스 수준 멤버 모두를 갖는 코드입니다.
  1. class Point

    {

    private int x;

    private int y;


    public Point(int x, int y)

    {

    this.x = x;

    this.y = y;

    }

    public void Print()

    {

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

    }

    public static void PrintClassName()

    {

    System.Console.WriteLine("class Point");

    }

    }

    class Program

    {

    static void Main()

    {

    Point pt = new Point(5,5);

    pt.Print();


    Point.PrintClassName();

    }

    }




(5, 5)
class Point

결과는 간단하죠?


마지막으로 Main()함수도 static 메소드입니다.

수고하셨습니다.