유니티 C# : C#의 깊은 복사 동작


C++ 에서도 얕은 복사와 깊은 복사 동작이 있었다.

기본 복사 생성자에 의해 얕은 복사가 진행되어

깊은 복사 동작은 우리가 직접 정의해 줬어야했다.




C#도 클래스의 기본 복사동작은 얕은 복사를 하고 있는데

깊은 복사를 정의하는 과정과 사용방법이 C++과

다른 점이 있어 포스팅을 하게 되었다.




우선 C#은 값 형식과 참조 형식으로 나누어지는데

클래스의 인스턴스는 태생부터 참조 형식이다.

때문에 단순히 대입을 하여 복사를 할 경우,

힙에 있는 인스턴스가 복사가 되는 것이 아니라

스택에 이 인스턴스를 가리키는 놈이 하나 더 생성된다.

(요기 까지는 C++의 문제점과 다를게 없다.)




자 그럼 간단하게 코드를 작성해보자.


class MyClass{


public int MyField1;

public int MyField2;


public MyClass DeepCopy(){


MyClass newCopy = new MyClass();

newCopy.MyField1 = this.MyField1;

newCopy.MyField2 = this.MyFiedl2;


return newCopy;

}

}


class Main{

static void main(string[] args) {


MyClass source = new MyClass();

source.MyField1 = 10;

source.MyField2 = 20;


MyClass target = source.DeepCopy();

target.MyField = 30;


}

}



이제 위의 예에서는 깊은 복사가 진행되어

source와 target이 힘에 서로 다른 객체를 가리킬 수 있다.

따라서 우리가 예상했던 결과가 나올 것이다.




또 하나 중요한 부분은 ICloneable.Clone() 메소드이다.

.NET 프레임워크의 System 네임스페이스에는 ICloneable

이라고 하는 인터페이스가 있다. 깊은 복사 기능을 가질

클래스가 .NET 프레임워크의 다른 유틸리티 클래스나

다른 프로그래머가 작성한 코드와 호환되도록 하고 싶다면

ICloneable을 상속하도록 하는 것이 좋다. ICloneable

인터페이스는 Clone() 메소드 하나만을 가지고 있다.




class MyClass : ICloneable{


public int MyField1;

public int MyField2;


public Object Clone(){

MyClass newCopy = new MyClass();

newCopy.MyField1 = this.MyField1;

newCopy.MyField2 = this.MyFiedl2;


return newCopy;

}

}



이제 이 클래스는 .NET 프레임워크와 호환성을 가지게 된다.

(Object로 반환 받을 수 있는 이유는 C#의 모든 데이터 형식은

Object를 상속받기 때문 : 전 포스팅 박싱/언박싱 참조)



C++ 디자인 패턴 : 전략 패턴 (strategy pattern)

함수 포인터로 구현한 전략 패턴




가상 함수를 대신할 방법을 찾아보자는 이슈에서

시작하여 전략패턴을 구현하는 방법까지 알아보게 되었다.

이미 NVI 관용구라는 훌륭한 방법이 있지만 좀 더 알아보겠다.




지금 우리는 게임의 캐릭터 클래스를 디자인 하고 있고

체력치를 계산하는 작업을 가상 함수를 통해 하고 있다.

체력을 계산하는 알고리즘은 캐릭터마다 다를 것이기 때문에

지극히 정상적인 설계이다. 하지만 여기서 가상 함수 대신

함수 포인터를 사용하여 좀 더 유연한 설계를 해보도록 하자.




함수 포인터를 이용한 전략 패턴은 체력치 계산을 어떤

캐릭터의 일부로 두지 않는다는 것부터 시작한다.

캐릭터의 생성자에 체력치 계산용 함수 포인터를

넘기게 만들고, 이 함수를 호출해서 실제 계산을 수행한다.




함수포인터를 이용한 전략(Strategy) 패턴의

단순한 예를 보여주는 클래스이다.






같은 캐릭터 타입으로부터 만들어진 객체들도

체력치 계산 함수를 다르게 가질 수 있는 융통성이 생겼다.

또한 게임이 실행되는 도중에 특정 캐릭터에 대한

체력치 계산함수를 바꿀수도 있다.




하지만 이 전략 패턴은 체력치가 계산되는

대상 객체의 비공개 데이터는 이 함수로 접근할 수 없다.

public 인터페이스로 얻은 정보만을 사용할 수 있기 때문이다.

( 멤버 함수가 아니기 때문에)




따라서 프렌드 함수로 선언을 하여 비공개 데이터에

접근을 할 수 있게 하여 캡슐화를 떨어뜨릴 것이냐.

즉 , 캡슐화냐 캡슐화를 떨어뜨려 얻을 수 있는 전략 패턴의

이점이 중요한가는 실제로 맡은 설계를 보면서 판단해야 한다.




C# 기초 : 가변길이 매개 변수 , 명명된 매개 변수




C++를 하다가 넘어 왔는데, 다른 부분이 조금씩 보이기 시작

가변길이 매개 변수에 대해 먼저 알아보도록 하자.




1. 가변길이 매개 변수

함수를 오버로딩하는 과정에서

똑같은 타입인데 매개변수의 '수'가

다르다는 이유만으로 오버로딩을 했다.

C#은 가변길이 매개 변수를 제공하여

그 개수가 유연하게 변할 수 있게 한다.




int total = 0;


total = Sum( 1, 2, 3);

total = Sum( 1, 2, 3, 4, 5);

total = Sum( 1, 2, 3, 4, 5, 6, 7, 8);


위와 같이 매개 변수의 수가 정해져 있지 않다면

(똑같은 타입일 경우에만) 그 만큼 함수를 오버로딩해서

사용하는 것이 아니라 가변길이 매개 변수를 사용한다.




가변길이 매개 변수는 params 키워드와 배열을 이용해서 선언.


int Sum ( params int[] args ){


int sum = 0;


for( int i = 0; i<args.Length; i++){

sum += args[i];

}


return sum;

}


==> 형식은 같으나 매개 변수의 개수만 유연하게 달라질 경우에 적합하다.




2. 명명된 매개 변수


메소드를 호출할 때 매개 변수 목록 중 어느 매개 변수에 데이터를

할당할 것인지를 지정하는 것은 '순서'이다. 명명된 매개 변수는

순서가아닌 매개 변수의 이름에 근거해서 데이터를 할당한다.




static void Print( string name , int phone ){ ... }


static void Main(string[] args){

Print ( name : "홍길동" , phone : "2222" );

Print ("김사랑" , "11111" );

Print ( phone : "3333" , name : "한고은" );

}




다소 번거로워 보일 수도 있지만, 매개 변수의 수가 많은 경우

가독성을 높여줄 수 있다. 또한 디폴드 매개 변수가 많은 경우에도

가독성이 상당히 안좋아질 수가 있는데, 명명된 매개 변수를

사용할 경우 이 문제를 해결할 수 있다.









C# 기초 : (C++ 에서 참조자라 불리는) ref 키워드

그리고 out 키워드 (출력 전용 매개 변수)




C++에서 레퍼런스(참조자)는 큰 성능향상을 가져다 주었다.

상수 객체 참조자에 의한 전달 방식이 그 예이다.

call by refference 방식을 구현할 때에도 포인터를

이용하는 것이 아닌 레퍼런스를 이용해 보다 안전하고

간편하게 구현할 수 있었다. C# 또한 마찬가지




하지만 다른점이 있는데

&가 아닌 ref를 붙여줘야 한다는 점


void swap( ref int a, ref int b){

...

}


사용할 떄에도


int x;

int y;


swap(ref x , ref y);




사용할 때에도 왜 귀찮게 ref를 붙여줄까..

생각해봤는데, C++에서 swap함수를 참조자로

만들었다면 사용하는데 있어 이 함수가

call by value를 하는 것인지

call by refference를 하는 것인지

분간이 안간다는 것이다.


하지만 C#에서는 ref를 붙여줌으로써

구분이 가능하다. (단순히 내 생각..)




out 키워드 (출력 전용 매개 변수)

out 키워드는 ref 키워드와 똑같은 일을 하지만

좀 더 안전한 방법으로 일을 한다.




void Divide ( int a , int b, ref int quotient , ref int remainder ){

quotient = a / b;

remainder = a % b;

}


int main(){

int a = 20 , b = 3, c = 0, d = 0;


Divide( a, b, ref c, ref d);

}


a를 b로 나눈 뒤 나머지와 몫을 구해서 c 와 d에 저장한다.

위 코드를 out 키워드로 바꾸어 보자.


void Divide ( int a , int b, out int quotient , out int remainder ){

quotient = a / b;

remainder = a % b;

}


int main(){

int a = 20 , b = 3, c = 0, d = 0;


Divide( a, b, out c, out d);

}




결과는?

ref 키워드와 똑같다. 그렇다면 out 키워드는 왜 쓰는가?




ref 키워드를 이용해서 매개 변수를 넘기는 경우에는 메소드가

해당 매개 변수에 결과를 저장하지 않더라도 컴파일러는

아무런 경고를 하지 않는다. 이와 달리 out 키워드를

이용해서 매개 변수를 넘길 때는 메소드가 해당 매개 변수에

결과를 저장하지 않으면 컴파일러가 에러 메시지를 출력한다.




또한 메소도를 호출하는 쪽에서는 초기화를 하지 않은

지역 변수를 메소드의 out 매개 변수로 넘기는 것이 가능.

컴파일러가 호출당하는 메소드에서 그 지역 변수를

할당할 것을 보장하기 때문이다.




즉, 런타임 에러를 컴파일 타임 에러로 잡아낼 수 있다는 장점이 있다.



[자료구조] 단순 정렬 알고리즘


1. 버블 정렬

2. 삽입 정렬

3. 선택 정렬




정렬 알고리즘을 자료구조로 보기는 힘들지만

자료구조의 구성에 있어 꼭 필요한 부분이기 때문에

정렬 알고리즘에 대해 간략하게 알아보는 시간을 가지겠다.




그 중에서도 단순 정렬 알고리즘


1. 버블 정렬

가장 널리 알려진 정렬방법이 아닌가 한다.

대학교수님이 굉장히 강조를 하셔서 아직 기억에 남아있다.

하지만 n^2 이라는 어마어마한 시간복잡도를 보이기 때문에

정렬 대상이 소규모일 경우에만 사용하도록 한다.




정렬 알고리즘은 개인적인 생각으론 그림으로

이해하는 것이 가장 좋다고 생각하는데, 그림을

그릴 시간이 없는 관계로 ㅜㅜ 

잘 정리해놓은 포스팅을 소개하도록 하겠다.

http://prosto.tistory.com/161




2. 삽입 정렬

선택 정렬과 엄청 유사하다.

키(key)값을 가지고 비교를 해나가며

정렬을 하는 것이 특징이다.

이 또한 밑의 포스팅을 보면 이해가 갈 것이다.

http://prosto.tistory.com/163




3. 선택 정렬

처음부터 마지막까지 해당 자리에

적절한 값을 잡아가는 것이 특징이다.

무척 단순하다. 따라서 구현을 하는 것이

상당히 쉽다. 하지만 단순 정렬 알고리즘은

모두 n^2의 시간복잡도를 보인다는 것.

http://prosto.tistory.com/159




Prosto님의 힘을 빌려 단순 정렬 알고리즘인

버블 정렬, 삽입 정렬, 선택 정렬에 대해 알아보았다.

구현이 간편하다는 장점말고는 딱히 다른 장점을

찾아볼 수가 없다. 때문에 STL의 sort 알고리즘을 보면

위의 단순 정렬 알고리즘이 아닌퀵 소트로 구현이 되어

있다는 것을 볼 수 있다. 퀵 소트는 상당이 복잡한 알고리즘인데

 그 만큼 성능상의 이점이 있기 때문이다.





'C, 자료구조' 카테고리의 다른 글

[자료구조] 연결 리스트의 이해(2)  (0) 2017.04.02
[자료구조] 연결 리스트의 이해(1)  (0) 2017.03.29
[C] 재귀 함수  (0) 2016.12.21
[C] static 변수  (0) 2016.12.21
[C] 함수 포인터와 void 포인터  (0) 2016.12.20

DFS(백트래킹) 알고리즘 문제 : 영역구하기 (정올)




오랜만에 DFS(백트래킹) 알고리즘을 풀어보았다.

DFS는 흔히 재귀로 많이 풀기 때문에 소위 말하는

재귀적인 사고(?)가 필요하다. 코드를 설명하는 부분에도

이러한 부분을 설명하기가 상당히 까다롭기 때문에

설명이 조금 부실하다.




일단 DFS(백트래킹)은 깊이 우선 탐색을 한다.

쉽게 말해 한 방향으로 쭉 들어갔다가 빠져나오는 것

스택의 원리와 같기 때문에 스택을 이용하거나

함수가 스택에 쌓였다가 풀리는 과정을 이용(재귀)

하여 풀 수 있다. 이번 문제는 난이도가 높지는 않은 듯 하다.





01. 변수 선언, 초기화

DFS (백트래킹)문제를 잘 풀기 위해서는

문제를 제대로 이해하고 초기화 하는 것 부터 시작.




02. 재귀 함수

최대한 자세히 설명한다고 했지만

재귀적인 사고(?)가 필요함.




03. 메인 함수



위에 코드에서도 언급했지만

한번의 함수호출로 답을 찾아낼 수 있는 방법이

있는지 조금 더 생각해봐야 할 부분이다.




오늘은 오랜만에 DFS(백트래킹) 알고리즘을 풀어보았다.

사실 초반에 BFS로 풀까 말까 고민하다가 정신없었던 건 사실

DFS, BFS에 익숙해져서 어떠한 알고리즘을 써야하는지

한번에 캐치하는 것도 중요한 듯 하다.

유니티 C# : 박싱과 언박싱




C#은 object가 모든 데이터를 다룰 수 있도록 하기 위해

모든 데이터 형식, 심지어 프로그래머들이 만드는

데이터 형식마저도 자동으로 object 형식을 상속 받는다.

즉, object는 모든 데이터 형식의 base class가 된다.




object 형식은 참조 형식이기 때문에 힙에 데이터를 할당한다.

반면, int나 double은 값 형식이기 때문에 스택에 데이터를

할당한다. 하지만 앞서 모든 데이터는 object를 상속받는다고

했는데 어떻게 값 형식의 데이터를 object에 담을 수 있는가?




object 형식은 값 형식의 데이터를 힙에 할당하기 위해

박싱 (boxing) 기능을 제공한다.


object a = 20;


20은 힙에 할당 되고, a는 그 주소를 참조한다. (박싱)




반대로 힙에 있던 값 형식 데이터를 값 형식 객체에

다시 할당해야 하는 경우가 있는데

이를 언박싱(unboxing)이라고 한다.


object a = 20; //// 박싱

int b = (int)a //// 언박싱


a는 박싱되어 20이 저장되어 있는 힙을 참조하고 있다.

b는 a가 참조하고 있는 메모리로부터 값을 복사한다. (언박싱)




정리하자면

박싱 : 값 형식을 참조 형식으로 변환

언박싱 : 참조 형식을 값 현식으로 변환




박싱의 과정 : 

1. 값 타입을 힙에 생성하기 위해 메모리를 힙 영역에 생성

2. 값을 힙 영역에 할당된 메모리로 복사

3. 참조할 변수에 할당된 메모리 주소를 할당




언박싱의 과정 :

1. 박싱값인지 확인

2. 박싱된 값이라면 값 타입 변수에 복사

3. 박싱한 메모리와 언박싱한 메모리 2개 존재 ( 가비지 발생 )


주의 : 모든 객체가 값 형식으로 언박싱이 될 수 없고, 이전에 박싱이 된

데이터에 한하여 언박싱이 가능하다. 또한 박싱하기전의 타입을 따라야 한다.




박싱/언박싱(boxing/unboxing)

MSDN 에 의하면 값 형식을 박싱할 때에는 완전히 새로운

개체가 만들어져야 하며, 이러한 작업에는 할당 작업보다

최대 20배의 시간이 걸린다고 한다. 언박싱 또한 캐스팅

과정이 할당 작업보다 4배의 시간이 걸릴 수 있다고 나와있다.




보시다시피 엄청난 성능상의 단점이 있다.

하지만 박싱/언박싱의 편리성 때문에 간혹 쓰이기도 한다.

--> ArrayList , 헤쉬테이블이 박싱/언박싱의 대표적인 예이다.


성능상의 단점을 감수하면서까지 쓸 필요는 없다고 생각한다.

리스트나 딕셔너리로도 충분히 대체할 수 있기 때문에

이를 공부하여 적절히 사용하는 방법이 좋을 것 같다.




1차원 배열을 사용하는 알고리즘 기초 문제 (백준)


숫자의 개수 , OX 퀴즈 , 음계 , 평균 점수 (백준)




알고리즘의 기초가 되는 1차원 배열

난이도가 낮은 편에 속하지만

기초를 튼튼히 다지지 않으면

사상누각에 불과한 것.




백준 사이트의 1차원 배열 사용하기 마스터~




01. 숫자의 개수




숫자를 어떻게 체크해야할지 엄청 고민했다.

문자열로 받아야 하나.. 이런 저런 고민하다가

그냥 나누어서 해결.




02. OX 퀴즈




그냥 O,X 구분해서 점수 누적




03. 음계




함수를 쓰기가 귀찮아서 안썻다가

결국은 함수를 써서 해결..




04. 평균 점수




백준의 알고리즘 문제는 가끔씩 터무니 없이

쉬운 문제도 나온다. 가뭄의 단비 같은 존재




역시나 알고리즘은 술술 풀려야 하는 맛이 나는데..

요즘 하고 있는 DFS , BFS , 다이나믹 알고리즘은

죽을 맛이다. 알고리즘 기초문제라 쉽게 풀리지만

어떤 분야든 기본은 튼튼하게!!


다이나믹 (동적 계획법) 알고리즘 문제. 배낭 채우기1 (정올)




동적 계획법의 가장 큰 특징은

전에 찾아놓았던 답을 가져다가 후의 답을 찾는 데 사용한다는 것

이 문제는 이러한 규칙을 잘 이용하면 풀 수 있다.




사실 DFS , BFS 같은 경우 복잡하긴 했지만

스택이나 큐의 성질을 이용하면서 풀기 때문에

나름 코딩을 하고 있다는 느낌이 들었지만,

다이나믹 (동적 계획법)알고리즘은 내가 수학공식을

만들어 내는 듯한 느낌을 준다.( 아직 많은 문제를 풀진 않았지만 )




이 문제 또한 답을 보지 않았다면 언제 풀었을지 모르겠다..

이 알고리즘에 익숙해지기 까지는 한참 답을 참고해야 할 듯 하다.





01. 클래스 선언

한 가지 위안이라면, 그리디 알고리즘을 풀 때 처럼

객체화를 해서 풀 수 있다는 점.. 나름 재미를 느끼고 있는 부분이기에




02. 초기화




03. 답 찾기

차근차근 개념을 적립해둘 겸 그리고

혹시나 나처럼 다이나믹 (동적 계획법) 때문에

헤메고 있을 중생들이 있을까봐 최대한 자세히 설명

(위에 설명한 적립식으로 푼다는 점을 짚고 들어 가겠다.)



04. 메인





다이나믹 (동적 계획법) 알고리즘은 진짜 수학적 사고력이

 뛰어나야 한다는 느낌을 받는다. DFS나 BFS처럼

다이나믹도 곧 익숙해지리라 믿는다.


알고리즘 기초문제 문자열

문자열 반복, 단어의 개수 (백준)




정올에 이어 백준 문자열 문제까지 마스터~

백준에 꽤 까다로운 문제가 많이 나와서

시간이 좀 오래 걸린 듯 하다.





1. 문자열 반복




말 그대로 입력된 숫자만큼 문자를 반복해서

출력하면 되는 문제다. 이중 for문으로 간단하게

해결 가능하기 때문에 주석은 달지 않았다.




2. 단어의 개수


알고리즘 기초 문자열 문제를 풀면서

함정이 많이 있다고 느꼈는데 그 중 가장 대표적인 문제다.




항상 느끼지만 알고리즘 기초 문제 중에서도

문자열은 특히 더 중요하기 때문에 더욱 신경 써서 해둬야겠다.


DFS, BFS, 다이나믹 등등 주요 알고리즘도

풀어야 하는데.. 너무 어려워서 풀기 싫네..

+ Recent posts