1. 객체지향의 기본

 

1-1) 오버로딩(Overloading)

 

1) 오버로딩의 정의

- 하나의 이름으로 여러개의 함수를 만드는 기법

 

2) 오버로딩 함수를 만드는 규칙

- 반드시 매개변수의 개수와 매개변수의 형이 달라야한다.

 

3) 오버로딩 함수의 예

- public int plus(int a, int b) { ... }

- public float plus(float a, float b) { ... }

- public double plus(double a, double b) { ... }

 

1-2) 연산자 오버로딩(Operator Overloading)

 

1) 연산자 오버로딩(Operator Overloading)

- 연산자를 중복정의 하는 것: 일반적인 연산자를 생각하면 더하기 빼기 등 값에 대한 연산이다. 하지만 이러한 연산자를 객체들 사이의 연산으로 차원을 높여주는 것이 연산자 오버로딩이다.

 

2) 연산자 오버로딩의 예

 

class Top {

public int a = 0;

public int b = 0;

public Top(int a, int b) {

this.a = a;

this.b = b;

}

public static Top operator+(Top t1, Top t2) {

int m = t1.a + t2.a;

int n = t1.b + t2.b;

return new Top(m, n);

}

}

 

=> operator+ 함수의 개념적 호출

Top s1 = new Top(3, 5);

Top s2 = new Top(7, 16);

Top s3 = s1 + s2;

 

=> operator+ 함수의 실제 호출

Top s1 = new Top(3, 5);

Top s2 = new Top(7, 16);

Top s3 = s1 + s2;

 

◈ 오버로딩이 불가능한 연산자

- ?    .    =     is    new   sizeof    typeof    ||    []    ()    &&    ->    +=    -=    ?: 등

 

 

1-3) 상속(Inheritance) 

1) 상속(Inheritance) 

- 만들어둔 클래스를 다시 사용할수 있는 방법을 말한다.

 

2) 기본적인 상속 방법

public class HelloForm: Form {

..

}

 => HelloForm 클래스에서 Form 클래스를 상속받음

 

- 상위 클래스를 기본 클래스(Base Class)라고 하며, 하위 클래스(Sub Class)를 파생 클래스(Derived Class)라고 한다.

- 상속을 하면 상위 클래스의 능력을 하위 클래스에서 이용할 수 있다.

 

3) 상속 금지 키워드 sealed

public sealed class Father {

public void SayFather() {

..

}

}

 

=> Father 클래스는 상속을 할 수 없다.

 

1-4) 생성자

 

1) 생성자 함수

- 생성자 함수는 메모리가 생성된 직후 가장 먼저 호출되는 함수이다.

 

2) 생성자의 특징

- 생성자는 리턴타입이 없다.

- 생성자의 이름은 클래스의 이름과 동일하다.

- new 연산자가 힙 영역에 해당 클래스의 메모리를 생성한 직후 호출된다.

 

public class ConstructTest{

public ConstructTest() { // 생성자

     ..

}

}

 

1-5) 소멸자

 

1) 소멸자

- 객체의 메모리가 제거될 때 호출되는 함수

 

2) 소멸자의 특징

- 객체가 소멸되기 직전에 호출되어 객체에 부여된 메모리의 회수를 담당

- 실제적으로 메모리의 회수여부는 가비지 콜렉터가 담당한다.

 

public class DestructorTest {

public DestructorTest() {}

~DestructorTest() { //소멸자

Console.WriteLine("나는 소멸자");

}

public static void Main() {

DestructorTest a = new DestructorTest();

}

}

 

1-5) internal과 protected internal 접근자

 

1) 논리적 접근 제한자

- 기준: 클래스 계층 구조에서 접근을 제한

- public, private, protected

 

2) 물리적 접근 제한자

- 기준: 어셈블리 단위로 접근을 제한

- internal

 

3) internal 접근자

- 하나의 Assembly 내에서만 접근을 허락하는 접근자

- 같은 Assembly 안에 있어야 접근이 가능하다.

 

4) protected internal

- 어셈블리가 다르더라도 상속을 받은 하위 클래스에서 상위 클래스의 internal에 접근 가능하게 하기 위해서 protected internal을 제공한다.

 

1-6) 오버라이딩(Overriding) 

 

1) 오버라이딩의 종류

- new 키워드를 이용한 재정의

- virtual, override키워드를 이용한 재정의

 

2) virtual, override, new의 관계

- virtual : 하위 클래스에서 재정의해서 사용할 것을 표시

- override : 상위 클래스에서 virtual로 표시된 함수를 재정의 할 때 사용

- new : 상위 클래스의 함수를 완전히 무시할 때 사용

 

3) new 키워드를 이용하여 함수를 재정의한 후 업캐스팅 했을 때

- 업캐스팅 되었을 때 상위 클래스는 상위 클래스 내의 함수만을 호출한다.

 

class Base {

public void MethodA() {

Console.WriteLine("Base Method()");

}

}

class SimpleNew: Base {

new public void MethodA() {

Consol.WriteLine("SimpleNew Method()");

}

public static void Main() {

SimpleNew m = new SimpleNew();

m.MethodA(); // SimpleNew 메서드 호출

 

Base b = m;

b.MethodA(); // Base 메서드 호출

}

}

 

4) virtual 키워드를 이용하여 함수를 재정의 한 후 업캐스팅 했을 때

- 상위 클래스의 이름으로 하위 클래스의 함수를 호출하는 것이 된다.

 

class Base {

virtual public void MethodA() {

Console.WriteLine("Base Method()");

}

}

class SimpleNew: Base {

override public void MethodA() {

Consol.WriteLine("SimpleNew Method()");

}

public static void Main() {

SimpleNew m = new SimpleNew();

m.MethodA(); // SimpleNew 메서드 호출

 

Base b = m;

b.MethodA(); // SimpleNew 메서드 호출

}

}

 

1-7) this, 생성자를 호출하는 this()

 

1) this

- this란 자기자신을 참조하는 가상의 참조 변수이다. (this.멤버이름)

 

2) 생성자를 호출하는 this()

- 자신의 생성자를 호출할 때도 사용

- 자신의 생성자를 재사용하기 위해서 생성자를 호출하는 방법을 제공

 

3) this()를 사용하는 이유

- 생성자의 중복을 피하기 위해서

 

4) this() 생성자의 특징

- 생성자 내에서 this()를 호출한다면 this()를 이용한 생성자가 먼저 처리된 후 현재의 생성자가 처리된다.

 

using System;

public class ThisSelf {

private string name;

private int age;

public ThisSelf() : this("이름없음") {

Console.WriteLine("매개변수가 없는 생성자");

}

public ThisSelf(string name) : this(name, -1) {

Console.WriteLine("매개변수가 1개 있는 생성자");

}

public ThisSelf(string name, int age) {

this.name = name;

this.age = age;

Console.WriteLine("name: " + name + " number: " + age);

Console.WriteLine("매개변수가 2개 있는 생성자");

}

public static void Main() {

ThisSelf ts1 = new ThisSelf();

ThisSelf ts2 = new ThisSelf("홍길동");

ThisSelf ts3 = new ThisSelf("김삿갓", 50);

}

}

출력:

name: 이름없음 number: -1

매개변수가 2개 있는 생성자

매개변수가 1개 있는 생성자 

매개변수가 없는 생성자

name: 홍길동 number: -1

매개변수가 2개 있는 생성자

매개변수가 1개 있는 생성자

name: 김삿갓 number: 50

매개변수가 2개 있는 생성자

 

1-8) base 키워드, base()

 

1) base 키워드

- 재정의한 상위 클래스의 함수를 호출할 때 사용

 

using System;

public class NewFather {

public virtual void OverrideFunc() {

Console.WriteLine("아버지의 함수");

}

}

public class NewSon : NewFather {

public override void OverrideFunc() {

Console.WriteLine("아들의 재정의 함수");

}

public void TestFunc() {

base.OverrideFunc(); //NewFather의 메서드 호출

}

public static void Main() {

NewSon ns = NewSon();

ns.OverrideFunc(); // NewSon의 함수 호출

ns.TestFunc(); // TestFunc() 내에서 NewFather의 함수 호출

}

}

 

2) 상위 클래스의 생성자를 호출하는 base()

- 생성자 내에서 상위 클래스의 생성자를 호출하기 위해 사용한다.

- 상위 클래스 생성자에 매개변수가 존재하면 생성자의 매개변수의 형과 개수를 맞추어 주어야만 호출 가능하다.

 

using System;

public class BaseFather {

private stsring name;

public BaseFather(string name) {

this.name = name;

Console.WriteLine("BaseSon : {0}", name);

}

}

public class BaseSon : BaseFather {

public BaseSon(string str) : base(str) {

}

public static void Main() {

BaseSon s = new BaseSon("Base Test Problem");

}

}

출력:

BaseSon : Base Test Problem

 

 

2. 다형성

 

2-1) 다형성(Polymorphism)

1) 다형성(Polymorphism)이란?

- 다형성이란 하나로 여러가지의 일을 하는 것을 말한다.

 

2) 다형성이 적용되는 곳

- 오버로딩(Overloading)

- 오버라이딩(Overriding)

- 업캐스팅(Upcasting)

- 다운캐스팅(Downcasting)

- 추상 클래스(Abstract Class)의 상속 관계

- 인터페이스(Interface)의 상속 관계

 

3) 다형성을 지원하기 위한 도구

- 추상 클래스(Abstract Class)

- 인터페이스(Interface)

- 업캐스팅(Upcasting)과 다운캐스팅(Downcasting)

- 박싱(Boxing), 언박싱(UnBoxing)

- 대리자(Delegate)

 

2-2) Boxing & UnBoxing

1) Boxing과 UnBoxing

- Boxing : 값 타입을 참조타입으로 변환하는 기법

- UnBoxing : 참조타입을 값 타입으로 변환하는 기법

 

2) 박싱(Boxing)

- Boxing이란 값타입을 참조타입으로 변환하는 것을 말한다.

 

◈ Boxing의 예

int p = 123;

object o = p; //Boxing이 발생

 

 Boxing이 이루어지는 순서

- 값 타입 변수를 객체화하기 위한 메모리를 힙 역역에 생성한다. 이 공간을 Box라고 한다.

- p에 있는 값을 힙에 생성한 Box로 복사

- 참조타입 변수 o에 Box의 참조값을 할당

 

=> 값타입의 값을 object o에 담기 위해서 힙영역에 박스(Box)라는 것을 만들고, 이 박스에 값을 복사해서 넣어주면 모든 변환이 끝난다. 새로 생성된 참조값은 객체 o가 가지게 된다.

 

3) 언박싱(UnBoxing)

- UnBoxing은 Boxing된 참조타입을 다시 값 타입으로 변환하는 것을 말한다.

 

◈ UnBoxing의 예

int p = 123;

object o = p; //Boxing이 묵시적으로 이루어진다.

int j = (int)o; //UnBoxing 명시적으로 캐스팅해주어야 함

Console.WriteLine("j = " + j);

Console.WriteLine("p = " + p);

Console.WriteLine("o = " + o);

 

출력:

j = 123

p = 123

o = 123

 

◈ UnBoxing의 순서

- 해당 객체가 지정한 값타입을 Boxing 한 값인지 확인

- Boxing 된 객체라면 객체의 값을 값타입 변수에 복사한다.

- Boxing 한 메모리와 UnBoxing한 메모리 두개가 존재한다.

2-3) 구조체의 Boxing과 클래스의 형변환

 

1) 구조체의 Boxing

 

using System;

struct Point {

public int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

class BoxingTest2 {

public static void Main() {

Point p = new Point(10, 10); // 스택 메모리 생성 - 값타입

object box = p;                  // 힙 메모리 생성 - Boxing 발생

p.x = 20;

Console.Write((Point)box).x);

//Boxing될 때 스택의 메모리가 힙영역으로 메모리가 복사되고 스택 메모리는 변환없음

}

}

출력:

10

2) 클래스의 형변환(업캐스팅)

 

using System;

class Point {

public int x, y;

public Point(int x, int y) {

this.x = x;

this.y = y;

}

}

class BoxingTest3 {

public static void Main() {

Point p = new Point(10, 10); // 힙 메모리 생성

object box = p;               // 최상위형으로 Upcasting된다. Boxing과 UnBoxing과 무관

p.x = 20;

Console.Write((Point)box).x);

}

}

출력:

20

 

=> 클래스를 이용한 객체는 참조변수이므로 당연히 p의 값은 참조값 자체이다. 그리고 object box에 이 참조값을 할당하면 box는 p의 참조값을 할당받는다. 즉, 객체의 참조할당의 법칙이 적용되는 것이지, Boxing과 UnBoxing의 변환이 아니다.

 

2-4) 인덱서 (Indexer)

 

1) 인덱서 (Indexer)

- 클래스의 객체나 구조체의 변수를 배열형식으로 이용할 수 있게 해주는 기법이다.

 

2) 인덱서를 구현하는 예제

- 문자열을 얻어내고 지정하기 위해 인덱서 사용

 

using System;

using System.Collections;

class SimpleIndexer {

ArrayList lname = new ArrayList();

public object this[int index] {

get {

if (index > -1 & index < lname.Count) { //범위내에 있는것

return lname[index];

} else {

return null;

}

}

set {

if (index > -1 & index < lname.Count) { // 범위내에 있는 것은 변경됨

lname[index] = value;

} else if (index == lname.Count) { //순서대로 하나씩 추가하면 됨

lname.Add(value);

} else {

Console.WriteLine("sid[" + index + "] : 입력범위 초과 에러!!");

}

}

}

}

class IndexerTest {

public static void Main() {

SimpleIndexer sid = new SimpleIndexer();

sid[0] = "hong";

sid[1] = "kim";

sid[2] = "sung";

 

Console.WriteLine(sid[0]);

Console.WriteLIne(sid[1]);

Console.WriteLine(sid[2]);

sid[10] = "park"; // 범위초과

}

}

출력;

hong

kim

sung

sid[10] : 입력범위 초과 에러!!

 

2-5) 추상 클래스 (Abstract Class)

 

1) 추상 클래스가 되는 방법

- 몸체없는 함수를 하나라도 포함하고 있는 클래스

abstract class Sample1{

public abstract void F();

}
- 몸체없는 함수를 포함하고 있지 않더라도 클래스를 선언할 때 abstract 키워드를 포함하고 있는 경우

abstract class Sample2{

public void G() {}

}

 

2) 추상함수의 선언 형태

- public abstract void F();

 

3) 추상 클래스의 특징

- 추상 클래스는 객체(인스턴스)를 생성할 수 없다.

- 추상 함수는 자동으로 가상함수가 된다. (추상 함수는 virtual 키워드를 사용하지 않지만 자동으로 virtual이 된다.)

 

 

2-6) 추상 함수 (Abstract Method)

 

1) 추상함수(Abstract Method)

- 몸체가 없는 함수

- 프로토타입만 가지고 있음

 

2) 추상 클래스를 상속받으면 추상클래스에서 선언한 추상함수를 모두 재정의 해야한다.

 

2-7) 인터페이스

 

1) 인터페이스의 구성요소를 보여주는 예

public delegate void StringListEvent(IStringList sender);

public interface IStringList {

void Add(String s); //함수

int Count {get; set; } //속성

event StringListEvent Changed; //이벤트

string this[int index] { get; set; } //인덱서

}

 

2) 인터페이스의 특징1

- 인터페이스 내의 멤버는 모두 몸체가 없다.

- 인터페이스의 멤버는 디폴트로 전부 public이다.

- 내부에 필드를 가질 수 없다.

- 멤버에 어떠한 접근자, 한정자도 붙이지 않는다.

 

3) 인터페이스의 특징2

- 인터페이스끼리 상속하면 더 큰 인터페이스가 된다.

- 인터페이스는 구현을 목적으로 한다.

 

◈ 클래스의 상속과 인터페이스를 동시에 상속하는 예

 

interface IA {

void SayA();

}

interface IB {

void SayB();

}

abstract class Top{

public abstract void SayTop();

}

class TopTest : Top, IA, IB {

public void SayA(){}

public void SayB() [}

public override void Saytop() {}

}

 

4) 인터페이스에서의 오버라이딩

 

using System;

interface IMyInterface {

void IMethod();

}

class MyClass : IMyInterface {

public void IMethod() {

Console.WriteLine("인터페이스의 함수를 구현");

}

public static void Main() {

MyClass mc = new MyClass();

mc.IMethod();

IMyInterface imc = mc;

imc.IMethod();

}

}

출력:

인터페이스의 함수를 구현

인터페이스의 함수를 구현

 

=> 추상클래스와 인터페이스의 추상 함수는 모두 가상(virtual) 함수의 성질을 가지고 있다. 추상 클래스 내의 추상 함수를 재정의 할 때에는 override 키워드를 사용해서 함수를 재정의하지만, 인터페이스 내의 추상 함수를 재정의 할때는 override 키워드를 사용하지 않는다.

 

 

5) 인터페이스 내의 속성 구현

 

interface IMyInterface {

int IProperty {

get;

get;

}

}

class ImplementsClass : IMyInterface {

private int iProperty;

public int IProperty {

get {

return iProperty;

}

set {

iProperty = value;

}

}

}

ImplementsClass ic = new ImplementsClass();

IMyInterface imi = ic;

imi.iProperty = 1000; //set

Colsole.WriteLine("Interface의 Property : " + imi.IProperty); //get

 

출력:

Interface의 Property : 1000

 

6) 인터페이스 내의 인덱서 구현

 

using System;

using System.Collections;

public interface SimpleInterface{

object this[int index] { get; set; } // 인덱서

}

public class SimpleIndexer : SimpleInterface {

ArrayList lname = new ArrayList();

public object this[int index] {

get {

if (index > -1 & index < lname.Count) { //범위내에 있는것

return lname[index];

} else {

return null;

}

}

set {

if (index > -1 & index < lname.Count) { // 범위내에 있는 것은 변경됨

lname[index] = value;

} else if (index == lname.Count) { //순서대로 하나씩 추가하면 됨

lname.Add(value);

} else {

Console.WriteLine("sid[" + index + "] : 입력범위 초과 에러!!");

}

}

}

}

class InterfaceIndexerTest{

public static void Main() {

SimpleIndexer sid = new SimpleIndexer();

sid[0] = "hong";

sid[1] = "kim";

sid[2] = "sung";

 

Console.WriteLine(sid[0]);

Console.WriteLIne(sid[1]);

Console.WriteLine(sid[2]);

sid[10] = "park"; // 범위초과

}

}

출력:

hong

kim

sung

sid[0] : 입력범위 초과 에러!!

 

7) 인터페이스의 명시적 구현

 

interface InterA {

void a();

}

interface InterB {

void a();

void b();

}

class Test : InterA, InterB {

public InterA.a(){

Console.WriteLine("InterA_a");

}

public InterB.a() {

Console.WriteLine("InterB_a"); 

}

public void b() {

       Console.WriteLine("InterB_b");

}

}

 

2-8) Delegate

 

1) Delegate

- 함수의 대리자

- 함수보다 효율적으로 사용하기 위하여 특정 함수 자체를 캡슐화할 수 있게 만들어주는 방법

 

2) Delegate 구현 예

 

using System;

//Delegate 선언

delegate void SimpleDelegate1();

delegate void SimpleDelegate2(int i);

class AType {

public void F1() {

System.Console.WriteLine("AType.F1");

}

public void F2(int x) {

System.Console.WriteLine("AType.F2 x=" + x);

}

}

class DeleTest {

public static void Main() {

AType atype = new AType();

//Delegate 객체 선언

SimpleDelegate1 s1 = new SimpleDelegate1(atype.F1);

SimpleDelegate2 s2 = new SimpleDelegate(atype.f2);

s1();

s2(1000);

}

}

출력:

AType.F1

AType.F2 x=1000

 

=> 하나의 클래스에서 다른 것은 필요없고 단지 특정 객체의 함수에만 관심이 있다고 해도, 해당 객체를 생성하여 그 객체의 모든 자원을 사용해야한다. 하지만 Delegate을 쓰면 하나의 함수를 빼올 수 있다.

 

=> C#에서 스레드를 사용하는 것이 위의 예와 비슷하다. C#에서 사용되는 모든 스레드는 함수를 이용해서 스레드를 시작하면 작업이 수행된다. 10개의 함수가 있다고 가정하자. 그리고 10개의 스레드를 만들고 함수를 스레드에 하나씩 넣어주고 실행하라고 명령을 내리면 10개의 스레드가 동시에 작업이 시작될 것이다. 스레드를 만들 때 함수를 넣어주어야 하는데 이 때 Delegate를 사용한다.

 

※ 스레드에서 Delegate를 사용하는 예

Top t = new Top(); // Top 클래스에 say()함수가 있다고 가정

Thread thread = new Thread(new ThreadStart(t.say));

thread.Start();

-> 여기서 ThreadStart가 Delegate이다.

 

 

'프로그래밍 > C#' 카테고리의 다른 글

C#으로 오라클접속  (0) 2012.08.29
C#으로 MySql 접속  (0) 2012.08.28
C#으로 Active Directory 접근  (0) 2012.08.28
C#의 기본문법  (0) 2012.05.08
Posted by 초코송송이
l