메인페이지 . 바뀐페이지 .

\cat StudyMaterial

참여자: HuidaeCho, uskusi

클래스

네임스페이스(namespace)
using
구조체(struct)
클래스(class)
클래스의 기본 개념
인스턴스(instance)
static 멤버
private 멤버
Property(속성)
토론

객체지향프로그래밍(Object Oriented Programming 또는 OOP)의 핵심이라고 할 수 있는 클래스(class)에 대해 정리해 보자. 클래스 이전에 구조체(struct)라는 것이 있었다. 구조체는 단순타입의 변수들을 모아서 하나의 묶음으로서의 값타입 자료형을 제공한다. 클래스는 여기에 상속, 접근제어 등의 개념을 추가한 참조타입의 자료형이라고 할 수 있다. C#에서는 구조체와 클래스 모두를 지원한다.

네임스페이스(namespace)

네임스페이스 이전에 컴파일단위를 이해하는것이 필요하다. (ImproveMe)

네임스페이스의 기본적인 형식은 키워드 namespace, 네임스페이스 이름, 본문, 선택적 세미콜론으로 구성된다.

namespace identifier namespace-body ;

이와 같은 기본형식에서 한 네임스페이스가 다른 네임스페이스의 멤버로 선언 될 수 있는데 중첩된 네임스페이스를 정의하기 위해 . 토큰을 사용할 수 있다.

namespace i1
{
    namespace i2
    {
        class a {}
        class b {}
    }
}

위 식은 아래와 같이 나타낼 수 있다.

namespace i1.i2
{
    class a {}
    class b {}
}

또한 같은 네임스페이스의 클래스를 정규화된 이름으로 따로 선언할 수도 있다.

namespace i1.i2
{
    class a {}
}
namespace i1.i2
{
    class b {}
}

using

using의 기본적인 개념과 이해 ImproveMe

구조체(struct)

우선 구조체를 어떻게 사용하는지 다음의 예를 보자.

using System;

struct Person{
	public string name;
	public int age;
	public Person(string infoName, int infoAge){
		name = infoName;
		age = infoAge;
	}
	public void PrintPerson(string whose){
		Console.WriteLine(whose+" Name: "+name+", "+whose+" Age: "+age);
	}
}

class StructExample{
	public static void Main(){
		// My info
		Person me;
		me.name = ReadName("My");
		me.age = ReadAge("My");
		PrintInfo("My", me);

		// Your info
		Person you = new Person();
		you.name = ReadName("Your");
		you.age = ReadAge("Your");
		PrintInfo("Your", you);

		// His info
		Person him = new Person(ReadName("His"), ReadAge("His"));
		PrintInfo("His", him);

		// Her info
		Person her = new Person(ReadName("Her"), ReadAge("Her"));
		her.PrintPerson("Her");

		// 구조체가 진짜 값타입인가?
		ChangeInfo(her);
		// her 자체를 매개변수로 넘긴 것이 아니고 그 값만 전달했기
		// 때문에 ChangeInfo() 메서드는 her의 값을 바꿀 수 없다.
		her.PrintPerson("Her");
	}

	private static string ReadName(string whose){
		Console.WriteLine(whose+" Name?");
		return Console.ReadLine();
	}

	private static int ReadAge(string whose){
		Console.WriteLine(whose+" Age?");
		return Int32.Parse(Console.ReadLine());
	}

	private static void PrintInfo(string whose, Person info){
		Console.WriteLine(whose+" Name: "+info.name+", "+whose+" Age: "+info.age);
	}

	private static void ChangeInfo(Person info){
		info.name = "Anonymous";
		info.age = 0;
	}
}

C와는 달리 변수를 정의할 때 struct를 사용하지 않는다. 그리고 멤버들을 public으로 지정해 줘야 접근이 가능하다. new를 이용해서 클래스처럼 변수를 생성할 수도 있으나 여전히 값타입 변수이다. 참고로 구조체에서도 클래스처럼 생성자와 메서드를 정의할 수 있다. C#이 기본으로 제공하는 생성자는 Your info의 경우처럼 매개변수를 필요로 하지 않는다. 위의 예제에서 ChangeInfo() 메서드를 이용해서 구조체가 값타입 변수임을 보였다. 즉, 매개변수로 구조체를 넘길 경우 구조체 자체가 넘어가는 것이 아니라 구조체 내의 "값"이 전달되므로 전달 받은 메서드는 구조체 자체의 값을 바꿀 수 없다.

클래스(class)

클래스의 기본 개념

클래스 한정자 : new public protected internal private abstract sealed

봉인(sealed)클래스와 추상(abstract)클래스

봉인클래스란 더이상 파생될 수 없는 클래스이다. sealed 지시자로 선언된 클래스는 다른 클래스에 의해 상속될 수 없다.

sealed classA {}
class classB: classA {}

// error!

추상클래스는 반대로 무조건 파생되어야만 쓰일 수 있는 불완전한 클래스이다. 추상클래스 내에는 추상멤버들이 속할 수 있는데 추상멤버가 있는 경우 파생클래스에서 모두 재정의 되어서 사용되어야만 한다.

abstract class classA 
{
    public abstract void F();
}
abstract class classB: classA
{
    public void G() {}
}
class classC: classB
{
    public override void F() 
    {
        // actual implementation
    }
}

A에서 정의 된 추상메서드 F는 클래스 C에 가서야 다시 구현되어 실제 모습을 갖추게 된다. 여기서 중요한것은 B클래스는 A클래스를 상속하면서 F를 실제로 구현하지 않으므로 반드시 추상클래스로 선언되어야 한다.

간단한 클래스 예제

클래스를 만드는 방법도 구조체와 크게 다르지 않다. 다음의 코드를 보고 구조체와 비교해 보자. 주석을 주의 깊게 읽을 필요가 있다.

using System;

class AnonymousPerson{
	// 클래스 내의 변수를 멤버필드라고 한다. 구조체와는 달리 멤버필드의
	// 초기값을 지정할 수 있다.
	public string name = "Anonymous";
	public int age;

	// 클래스 내의 함수를 멤버메서드라고 한다. 클래스의 이름과 동일한
	// 이름의 멤버메서드를 생성자라고 하는데 정의하지 않을 경우 매개변수가
	// 없는 생성자가 자동으로 생성된다.
	public void PrintPerson(string whose){
		Console.WriteLine(whose+" Name: "+name+", "+whose+" Age: "+age);
	}
}

class Person{
	public string name = "Anonymous";
	public int age;

	// 다음과 같이 생성자를 정의할 수 있다. 하지만 주의할 것은 원하는
	// 생성자를 생성하는 순간 자동으로 생성되던 매개변수를 받지 않는
	// 생성자가 자동으로 생성되지 않는다. 따라서 필요한 경우 다음과 같이
	// 아무런 작업도 하지 않는 메서드를 정의한다.
	public Person(){
	}
	public Person(string infoName, int infoAge){
		name = infoName;
		age = infoAge;
	}
	public void PrintPerson(string whose){
		Console.WriteLine(whose+" Name: "+name+", "+whose+" Age: "+age);
	}
}

class ClassExample{
	public static void Main(){
		// My info
		// AnonymousPerson 클래스의 인스턴스 me를 *선언*한다. 이 때 me
		// 변수에는 메모리를 실제 할당하지는 않는다.
		AnonymousPerson me;
		// me 인스턴스를 사용하기 위해서는 반드시 new 키워드를 이용해서
		// 메모리를 할당해야 한다. 이것이 구조체와의 차이점이다.
		me = new AnonymousPerson();
		// 멤버의 초기값을 덮어 쓴다.
		me.name = ReadName("My");
		me.age = ReadAge("My");
		PrintInfo("My", me);

		// Your info
		// you 인스턴스를 선언함과 동시에 Person 클래스에서 *직접*
		// 정의한 생성자를 호출해서 메모리를 할당한다.
		Person you = new Person();
		you.name = ReadName("Your");
		you.age = ReadAge("Your");
		PrintInfo("Your", you);

		// His info
		// Person 클래스에서 이름과 나이를 읽어 들이는 생성자를
		// 호출한다.
		Person him = new Person(ReadName("His"), ReadAge("His"));
		PrintInfo("His", him);

		// Her info
		Person her = new Person(ReadName("Her"), ReadAge("Her"));
		her.PrintPerson("Her");

		// 클래스가 진짜 참조타입인가?
		ChangeInfo(her);
		// her 자체를 매개변수로 넘기기 때문에 ChangeInfo() 메서드는
		// her의 값을 바꿀 수 있다.
		her.PrintPerson("Her");
	}

	private static string ReadName(string whose){
		Console.WriteLine(whose+" Name?");
		return Console.ReadLine();
	}

	private static int ReadAge(string whose){
		Console.WriteLine(whose+" Age?");
		return Int32.Parse(Console.ReadLine());
	}

	// 같은 이름의 메서드를 두 개 정의했다. 두 메서드의 매개변수 타입에
	// 따라 호출되는 실제 메서드가 결정된다. 여기서는 두 가지 클래스의
	// 정보를 출력하는 하나의 메서드를 정의했다.
	private static void PrintInfo(string whose, AnonymousPerson info){
		Console.WriteLine(whose+" Name: "+info.name+", "+whose+" Age: "+info.age);
	}

	private static void PrintInfo(string whose, Person info){
		Console.WriteLine(whose+" Name: "+info.name+", "+whose+" Age: "+info.age);
	}

	private static void ChangeInfo(Person info){
		info.name = "Anonymous";
		info.age = 0;
	}
}

인스턴스(instance)

위의 예제에서 인스턴스라는 말을 사용했다. 인스턴스란 클래스에 의해 생성된 변수라고 할 수 있다. 이처럼 클래스를 직접 사용하지 않고 인스턴스를 사용해서 멤버에 접근하게 된다. 이는 마치 데이터타입을 직접 사용하지 않고 데이터타입으로 정의한 변수를 사용하는 것과 비슷하다. 하지만 인스턴스를 통하지 않고 클래스 내의 멤버에 직접 접근할 수 있게 해주는 속성이 있는데 그것이 바로 정적 속성(static)이다.

static 멤버

클래스를 작성하여 필드나 메서드를 정의할 때 static을 흔하게 볼 수 있다. static은 정적멤버를 정의할 때 쓰이는데 이것에 대해 알아보고자 한다. 우선 다음의 예제를 보자.

using System;

class AnonymousPerson{
	// name 멤버를 static으로 지정해서 인스턴스 없이 접근이 가능하게 한다.
	public static string name = "Anonymous";
	public int age;
	public void PrintPerson(string whose){
		Console.WriteLine(whose+" Name: "+name+", "+whose+" Age: "+age);
	}
}

class ClassExample{
	public static void Main(){
		// static 멤버인 name은 클래스 차원에서 접근 가능하다.
		Console.WriteLine(AnonymousPerson.name);

		// static 멤버의 초기값을 바꿀 수도 있다.
		AnonymousPerson.name = "Me";
		// me 인스턴스를 생성한다.
		AnonymousPerson me = new AnonymousPerson();

		// 다음 줄은 에러를 발생시킨다. 즉, static 멤버는 인스턴스를
		// 통해서는 접근할 수 없다.
		// me.name = "Me";

		me.PrintPerson("My");
	}
}

위 소스를 컴파일해서 실행하면 다음과 같은 출력을 볼 수 있다.

Anonymous
My Name: Me, My Age: 0

클래스와 인스턴스의 관계에서 new 연산자를 사용하여 클래스를 인스턴스로 생성하기 전에는 일반적으로 클래스 내의 메서드를 사용할 수 없다. 비정적(non static)필드, 메서드, 속성은 인스턴스가 필요한 반면 정적(static)으로 선언해 준 필드나 메서드는 인스턴스 없이도 사용이 가능하다. 전자를 인스턴스 종속적, 후자를 클래스 종속적이라고 한다. 클래스 종속적인 static 멤버들은 개체를 생성하지 않고 바로 불러다 쓸 수 있는데 바로 절차지향적인 언어에서 라이브러리 함수를 쓰는것과 비슷하다. 비유가 이상하지만 따로 라이브러리 함수의 인스턴스를 만들지 않고(당연하지만) 쓸 수 있는것처럼 생각할 수 있다.

이와 같은 이유로 C#의 Main 메서드는 당연히 정적이어야 한다. 프로그램이 실행되기 전에는 아무런 인스턴스가 존재하지 않는다. 정적이지 않다면 클래스 내의 Main 메서드를 호출하려고 할 때 개체가 없기 때문에 다음과 같은 오류가 발생한다.

오류: 진입점에 적합한 정적 'Main' 메서드가 '~~~~~\ConsoleApplication1.exe' 프로그램에 없습니다.

다른 특징으로는 "상속에 영향을 받지 않는다"는 것이다. 상속은 non static한 멤버들에게만 해당이 된다. 따라서 static staticMember 따위가 들어있는 A 클래스를 상속한 B 클래스는 해당 static 멤버를 base.staticMember로 불러다 쓸 수 없다. 클래스를 상속할 때 static 멤버는 해당이 되지 않으므로 그대로 클래스로 접근하여 A.staticMember와 같이 사용하여야만 한다.

인스턴스 내에서 자신을 가리키는 this 키워드를 당연히 쓸 수 없으므로 this.staticMember가 불가능하다.

private 멤버

위의 예제에서 private이라는 키워드를 사용했다. 클래스 내의 멤버필드와 멤버메서드는 public 또는 private 속성을 가질 수 있다. 멤버가 public인 경우 클래스 밖에서도 접근이 가능하지만 private인 경우 클래스 안에서만 접근이 가능하다. StructExampleClassExample 클래스 내에 정의된 private 메서드들은 각 클래스 내부에서만 접근이 가능하다. private 멤버필드에 접근하기 위한 유일한 방법은 해당 클래스의 멤버메서드를 이용하는 것 뿐이다. 다음의 예제를 보자.

using System;

class AnonymousPerson{
	// private static한 name을 정의한다. 즉, AnonymousPerson 클래스의
	// 멤버메서드에서만 접근이 가능하고 (private), 인스턴스 없이 필드의
	// 사용이 가능하다 (static).
	private static string name = "Anonymous";

	// public이나 private을 따로 지정하지 않으면 private이 된다.
	int age;

	// static한 name의 변경을 위해 Name() 메서드 static으로 정의한다.
	public static void Name(string myName){
		if(!myName.Equals(""))
			name = myName;
	}

	public void Age(int myAge){
		if(myAge >= 0)
			age = myAge;
	}

	// name과 age에 직접 접근이 불가능하므로 이들의 출력을 위해서는 별도의
	// public 메서드가 반드시 필요하다.
	public void PrintPerson(string whose){
		Console.WriteLine(whose+" Name: "+name+", "+whose+" Age: "+age);
	}
}

class ClassExample{
	public static void Main(){
		// public static Name() 메서드를 이용해서 name의 기본값을
		// 변경한다.
		AnonymousPerson.Name("Me");

		// me 인스턴스를 생성한다.
		AnonymousPerson me = new AnonymousPerson();
		// static 멤버인 name은 me.Name()으로 접근할 수 없다. me
		// 인스턴스를 생성한 후라도 static 멤버의 변경운 me 인스턴스에
		// 영향을 미친다.
		AnonymousPerson.Name("You");
		// Age() 메서드를 이용해서 할당하는 값을 검사할 수 있다.
		me.Age(-10);
		me.Age(20);
		me.PrintPerson("Your");
	}
}

이와 같이 멤버를 private으로 정의해서 외부접근을 불가능하게 하는 것을 은폐(Encapsulation) 또는 은닉이라고 한다.

Property(속성)

private 멤버필드에 접근하기 위한 public 멤버메서드를 정의하는 대신 속성이라는 기법을 이용할 수도 있다. 기능상으로는 동일하지만 멤버필드에 직접 접근하는 것과 같은 문법을 이용하므로 문법상으로 더 편리하다고 할 수 있겠다. 다음의 코드를 보자.

using System;

class AnonymousPerson{
	private static string name = "Anonymous";
	private int age;

	public static string Name{
		// value라는 키워드를 통해 들어오는 값을 name에 할당한다.
		set{
			if(!value.Equals(""))
				name = value;
		}
		// name의 값을 내보낸다.
		get{
			return name;
		}
	}
	public int Age{
		set{
			if(value >= 0)
				age = value;
		}
		get{
			return age;
		}
	}

	public static void SetName(string myName){
		if(!myName.Equals(""))
			name = myName;
	}
	public static string GetName(){
		return name;
	}

	public void SetAge(int myAge){
		if(myAge >= 0)
			age = myAge;
	}
	public int GetAge(){
		return age;
	}

	public void PrintPerson(string whose){
		Console.WriteLine(whose+" Name: "+name+", "+whose+" Age: "+age);
	}
}

class ClassExample{
	public static void Main(){
		AnonymousPerson me = new AnonymousPerson();

		// 코드블럭 1
		AnonymousPerson.SetName("You");
		me.SetAge(20);
		me.PrintPerson("Your");
		Console.WriteLine(me.GetAge());

		// 코드블럭 2
		AnonymousPerson.Name = "Him";
		me.Age = 25;
		me.PrintPerson("His");
		Console.WriteLine(me.Age);
	}
}

코드블럭1과 2는 할당하는 값을 제외하면 동일한 역할을 한다. 위의 예제에서는 별도의 생성자를 정의하지 않았고 참조를 통한 호출을 이용하지 않았기 때문에 AnonymousPerson 클래스를 AnonymousPerson 구조체로 바꾸어도 동일한 결과를 얻을 수 있다.

토론

편집 . 바뀐부분 . 역링크 .
2020년 7월 12일 오전 6:31:59 . XHTML 1.1 . CSS 3 . 용쓴다. !
이 사이트의 모든 문서는 크리에이티브 커먼즈 라이센스를 따릅니다.