UML을 처음 알게 되었던 것은 클라이언트 프로그래머를 준비하기 위해 학원을 다니면서였다. 하지만 그때는 깊게 공부를 하지 않았었다. UML의 사용의 이유정도 였다. 딱히 필요성도 느끼지 못했었다. 10개월 동안 업무를 진행하면서 필요성을 조금 느꼈다. 그래서 다시 사이트를 검색하며 공부/정리하게 됐다.

1. UML 

Unified Modeling Language의 약자이다. 소프트웨어 공학에서 사용되는 표준화된 범용 모델링 언어이다. 산출물을 만들때 미리 설계해 검증할 수 있으며 이것은 곧 문서화가 되게 된다. 문서화가 되면서부터 UML의 사용 이유가 생기게 된다.

  • 작성한 코드의 구조를 한눈의 볼 수 있다.
  • 추 후 유지보수의 대응이 빠르다.

그렇다고 모든 코드를 작성할 때마다 UML을 만들 필요는 없어 보인다. 실제로 작성하는 코드보다 UML의 문서화가 시간이 더 오래 걸릴 수 있기 때문이다.

 

2. 클래스 간의 관계도 표시방법

1. Generakization(일반화 관계) : 부모 클래스와 자식 클래스 간의 상속관 계를 나타낼 때.

2. Realization (실체화) :  인터페이스를 구현하는 관계를 표현한다.

3. Association (연관 관계) : 한 객체가 다른 객체를 소유하거나, 참조하여 사용할 때, 단방향과 양방향이 존재한다.

 

- Aggregation (집합) : 약한 결합, 연결된 클래스와 독립적으로 동작된다. 메인클래스가 삭제될 시 대상 클래스는 같이 삭제가 안된다.

 - Composition (합성) :  Aggregation보다 강한 결합으로 이루어져 있어 라이프 사이클이 같아(같이 사라짐) 독립적이면 의미가 없다.

4. Dependency (의존 관계) :  서로의 객체를 소유하지는 않지만, 객체의 변경이 이루어질 때 따라서 같이 변경을 해주어야 할 때 사용한다. 대상 클래스를 생성해주고 바이바이

 

 

 

모바일에서 게임 플레이 중에 홈버튼으로 나가게 된다면 애니메이션이나 프레임은 어떻게 동작하는 것이 궁금하여 큐브가 회전하는 간단한 애니메이션을 만들어 테스트해봤다.

유니티 에디터에서 모바일에서 홈버튼이 눌린 상태를 만들기 위해서는 아래와 같은 세팅이 필요하다.

Run In Background 체크를 해제한다. 그러면 유니티 에디터 창이 비활성화 상태를 만들면 게임은 멈추게 된다. 

그럼 이제 몇 가지 테스트를 해보자

1. 애니메이터 Update Mode : Normal / WaitForSecondsRealtime

    void Start()
    {
        Debug.Log("Test 시작");
        StartCoroutine(CorTest());
    }

    public IEnumerator CorTest()
    {
        yield return (CorWaitForRealSeconds());
    }
    private IEnumerator CorWaitForRealSeconds()
    {
        yield return new WaitForSecondsRealtime(5);
        Debug.Log("Test2 WaitForSecondsRealtime 5seconds");
    }
    //애니메이션 이벤트 콜.
    public void AnimEnd()
    {
        Debug.LogError("AnimEnd");
    }

 

에디터가 시작하면 CorTest를 시작하면서 RealSeconds로 5초를 기다린 후 로그를 출력하고, AnimEnd는 애니메이션에서 6초에 이벤트 키를 넣었다.

정상적으로 아무 동작을 하지 않으면 차례대로 5초 뒤에 "Test 2 WaitForSeconds 5 seconds"을 출력하고 그다음에 1초 뒤에 곧이어 "AnimEnd"을 출력하게 될 것이다.

예상 : "Test 2 WaitForSeconds 5 seconds" -> 1초 뒤  "AnimEnd"

에디터가 활성화되어있지 않을 때는 애니메이션이 멈추는 것을 볼 수 있다. 5초쯤에 유니티를 클릭하면 바로 "Test 2 WaitForSecondsRealtime 5 seconds"을 출력하게 된다. 그리고 "AnimEnd"은 1초가 아닌 약 5초 뒤에 출력하게 되는 것을 볼 수 있다.

결과 : "Test 2 WaitForSeconds 5 seconds" -> 약 5초 뒤  "AnimEnd"

테스트 결론 : 비활성화되었을 때도 WaitForSecondsRealtime은 계속해서 흐르지만 애니메이션은 활성화 되었을때 다시 카운팅을 하는 것이다. 

 

2. 애니메이터 Update Mode : Unscaled Time / WaitForSeconds

    void Start()
    {
        Debug.Log("Test 시작");
        StartCoroutine(CorTest());
    }

    public IEnumerator CorTest()
    {
        yield return StartCoroutine(CorWaitForSeconds());
        //Debug.LogError("CorTest");
    }
    private IEnumerator CorWaitForSeconds()
    {
        yield return new WaitForSeconds(5);
        Debug.Log("Test2 WaitForSeconds 5seconds");
    }
    //애니메이션 이벤트 콜.
    public void AnimEnd()
    {
        Debug.LogError("AnimEnd");
    }

애니메이션 이벤트 콜은 6초이기 때문에 WaitForSeconds 5 seconds 후 1초 뒤에 AnimEnd가 불려야 하지만 

애니메이터는 Unscale Time이기 때문에 비활성화 상태에도 시간은 흐른다. 그렇기 때문에 "AnimEnd" 후 "WaitForSeconds 5 seconds"가 출력된다. 

결론으로 짝을 매겨보자면.

Unscale Time과 WaitForSecondsRealtime과 짝꿍/  Normal과 WaitForSeconds이 짝이 될수 있겠다.

애니메이션이 종료 시점에 맞추어 코 루틴으로 제어한다면 애니메이터 업데이트 모드를 확인하고 그에 맞는 대기 코 루틴을 설정할 필요가 있다.

스크롤 셀을 만들 때 셀에 들어가는 데이터가 같은 글자 수면 참 좋겠지만 그렇지 않은 경우가 많다. 예를 들어 퀘스트 스크롤을 제작한다고 하자.

퀘스트의 설명이 어떤 퀘스트는 아주 길고 또 어떤 퀘스트의 내용은 짧고 또는 예상할수 없는 길이의 데이터가 입력될 수 있다.

그렇다고 셀들의 크기를 설명이 큰 내용의 맞추어 크게 만들 수는 없다. 또 유지보수에도 좋지않다.(이미 지정한 크기 이상의 데이터는 추가될 수도 없기 때문이다.)

이쁘지않다.

그렇다면 입력되는 데이터 텍스트의 수의 따라 파란배경 이미지의 사이즈 변경이 필요하다.

방법은 Content Size Fitter와 Layout Group컴포넌트를 이용해하는 것이다. 

사실 위 두개 컴포넌트가 텍스트 사이즈의 맞게 바로바로 변경되면 좋겠지만 그렇지 못하는 경우가 있다. LayoutRebuilder.ForceRebuildLayoutImmediate요 함수를 통해 즉시 정렬하도록 해줬다.

(요 함수는 오브젝트가 켜져있어야 정상 작동한다)

함수 오버로딩

매개변수는 다르지만 동일한 이름의 멤버 함수를 여러 개 작성할 수 있는 기능이다. 

 

1. 구현 방법

	void Add()
	{

	}	
    void Add(string s1, string s2)
	{
		...~~~
	}
	void Add(int a, int b)
	{
		...~~~
	}

함수 이름은 같지만 받는 매개변수가 다르다.

같은 이름의 함수를 호출하지만 상황에 따라 다른 결과를 얻어 낼 수 있다. 예를 들어 같은 기능이지만 매개변수 타입의 따른 다른 결과를 원할 때 사용하면 된다. 

 


연산자 오버 로딩

기존의 제공하고 있는 연산자를 재정의하여 원하는 방식으로 수정하여 사용할 수 있다. 전역 함수나 클래스로 재정의도 가능하다.

1. 구현 방법

	Student() { name = "ji"; }
	Student(string name) :name(name) {}

	Student operator + (const Student& other)
	{
		return Student(Student(name + " & " + other.name));
	}

operator를 사용하여 '+'의 연산자를 재정의 한다. 

	Student st1;
	Student st2("han");
	Student result = st1 + st2;

'+'은 기본적으로 더한다라는 정의하지만 내가 만든 Student의 객체를 '+'한다는 의미는 컴파일러는 알지 못한다. 하지만 위에서 정의한 '+'의 기능을 정의하였기에 아래와 같은 결과를 볼 수 있다. 이런 기능을 사용하므로 코드의 길이를 줄일 수 있는 장점이 있다.

결과

 

2. 규칙

연산자 오버 로딩의 규칙들이 있다.

기존의 있는 연산자만 가능하다 (새로운 연산자를 만들어 낼 수는 없음)

멤버 연산자(.), 범위 지정 연산자(::), 전처리기 연결(##)등의 몇몇 연산자는 불가능하다.

피연산자의 개수 규칙 등 기본적인 연산자의 규칙 등 기본적인 연산자의 규칙을 따라야 한다. 

오버 로딩이 된 연산자 중 하나는 사용자 정의 자료형 이어야 한다.

 

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

C++) 클래스 상속, 오버라이딩  (0) 2020.06.22
C++) 생성자와 소멸자  (0) 2020.06.10
C++)접근 제한자  (0) 2020.06.09
C) 배열/문자열과 포인터.  (0) 2020.05.02
C) 파일 입출력 (FILE,fopen,fclose)  (0) 2020.05.02

클래스 상속

부모(베이스) 클래스 안에 있던 멤버 변수와 멤버 함수를 물려받아 새로운 클래스를 작성할 수 있게 된다. 객체지향 언어의 가장 큰 특징이며 장점이라고 생각된다.


 

1. 구현방법

class Person
{
	~~~~부모
}


class Student : Person 
{
	~~~~자식
}

Person은 부모 클래스 Student는 자식 클래스라 보면 자식 클래스 옆에  : 을 사용하여 Person의 자식이라는 것을 정의한다.

2. 예시

class Person
{

private:
	string name;

public:
	Person(string name) : name(name)
	{
	}
	string GetName()
	{
		return name;
	}

};

class Student : Person 
{
private:
	int studentID;
public:
	Student(int studentID, string name) : Person(name)
	{
		this->studentID = studentID;
	}

	void Show()
	{
		cout << "학생 번호 : " << studentID << '\n';
		cout << "학생 이름 : " << GetName() << '\n';
	}

};
int main()
{
	Student student(1,"han");
	student.Show();
	system("pause");
}

Student라는 객체를 새로 생성하면서 생성자를 통해 학생 번호와 이름을 부여하고 있다. Student는 name이라는 변수는 없지만 Person(name)을 통해 부모의 변수의 값을 넣을 수 있게 된다.

실행 결과

 

3. 다중 상속

class TempClass
{
public :
	void ShowTemp()
	{
		cout << "임시 부모 \n";
	}

};

class Student : Person, public TempClass
{
private:
	int studentID;
public:

~~~...

int main()
{
	Student student(1,"han");
	student.ShowTemp();
	system("pause");
}

C#은 안되지만, C++은 다중 상속이 가능하다. 

 

4. 장점

1) 코드 중복이 없어진다. 2) 함수 재활용이 가능해진다.


 

오버라이딩

 

상속받은 자식 클래스에서 부모 클래스의 함수를 사용하지 않고 자식 클래스의 함수로 재정의 해서 사용하는 것이다. 아래 예시를 보면 쉽게 이해가 될 것이다.

class Person
{

private:
	string name;

public:
	Person(string name) : name(name)
	{
	}
	string GetName()
	{
		return name;
	}

	void ShowName()
	{
		cout << "이름 :" << GetName() << '\n';
	}
};

class Student : Person, public TempClass
{
private:
	int studentID;
public:
	Student(int studentID, string name) : Person(name)
	{
		this->studentID = studentID;
	}

	void Show()
	{
		cout << "학생 번호 : " << studentID << '\n';
		cout << "학생 이름 : " << GetName() << '\n';
	}
	void ShowName()
	{
		cout << "학생 이름 :" << GetName() << '\n';
	}
};



int main()
{
	Student student(1,"han");
	student.ShowName();
	system("pause");
}

부모인 Person클래스도 ShowNmae() 함수가 있고, 자식인 Student도 ShowName() 함수가 있다. 이 과정을 오버 라이딩이며 부모 함수를 무시하고 자식 함수를 실행하게 된다.

실행 결과


 

추가적으로

1. 자식(파생) 클래스의 객체를 생성하면, 부모(베이스) 클래스의 생성자를 호출한 뒤, 자식 클래스의 생성자가 호출된다.

2. 소멸자는 반대로 자식 클래스가 먼저 호출되고 그 후에 부모 클래스의 소멸자가 호출된다. 

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

C++) 함수 오버로딩 / 연산자 오버로딩  (0) 2020.06.23
C++) 생성자와 소멸자  (0) 2020.06.10
C++)접근 제한자  (0) 2020.06.09
C) 배열/문자열과 포인터.  (0) 2020.05.02
C) 파일 입출력 (FILE,fopen,fclose)  (0) 2020.05.02

생성자

객체를 생성함과 동시에 멤버 변수를 초기화할 수 있다. 인스턴스화될 때 자동으로 호출되며,  클래스의 멤버 변수를 적절한 기본값 또는 사용자가 정의한 값을 갖는 인스턴스가 생성되는 것이다. 

 


 

1. 구현방법

class Student
{
private:
	string name;
	int englishScore;
	int mathScore;

public:

	Student(string n ,int e, int m)
	{
		name = n;
		englishScore = e;
		mathScore = m;
	}

};

일반 메서드를 작성하는 방법과 같다. 이름은 클래스 이름과 같게 설정해주면 된다.(반환 값은 없음) Student인 새로운 객체를 만들 때 name, englishscore, mathscore의 각각 해당하는 값을 할당해주면서 이 객체는 초기화된다.

 


 

2. 사용방법

1) 클래스 이름과 같아야함 2) 반환 값이없어야함

int main(void)
{
	Student a = Student("Mr.Han",100,98); // Student 생성!

}

 이렇게 해주면 끝!

	Student* st1 = new Student("han" , 100, 98);
	st1->NumberUp();

이런식으로 동적으로 생성할때도 생성자는 동작한다.


 

3. 디폴트 생성자

public:

	//Student(string n ,int e, int m) //생성자 주석.
	//{
	//	name = n;
	//	englishScore = e;
	//	mathScore = m;
	//}
};

int main(void)
{
	Student a = Student(); //매개변수 X

}

작성한 생성자가 없을때는 매가변수가 없는 기본 생성자로 인식한다. 멤버 변수들은 '0'혹은 'NULL'인 값으로 설정된다.

 


 

4. 복사생성자

매개변수의 값을 넘겨주는 일반 생성자와 다르게 객체를 넘겨 복사를 하도록 하는 생성자이다.

int number = 0;

class Student
{


private:
	string name;
	int englishScore;
	int mathScore;
	int num;

public:

	Student(string n, int e, int m)
	{
		name = n;
		englishScore = e;
		mathScore = m;
	}
	Student(const Student &st)
	{
		name = st.name;
		englishScore = st.englishScore;
		mathScore = st.mathScore;
	}
	void NumberUp()
	{
		number++;
		num = number;
	}

	void Show()
	{
		cout << "번호 " << num << "이름 " << name << "영어 " << englishScore << "수학 " << mathScore << '\n';

	}
};

int main()
{
	Student* st1 = new Student("han" , 100, 98);
	st1->NumberUp();
	Student st2(*st1);
	st2.NumberUp();
	st1->Show();
	st2.Show();

	system("pause");
}

st1을 만들고 st2를 만들때 생성자에 *st1을 넘김으로 써 st1을 그대로 복사한다. 각 Student객체는 NumberUp()으로 int number를 한개씩 증가 시켜 각각 num의 값을 다르게 넣도록 했다. 

 

소멸자

객체의 수명이 끝났다고 판단되면 이 객체를 제거하기 위한 목적으로 사용된다. 객체의 수명이 끝났을 때 자동으로 컴파일러가 소멸자 함수를 호출한다. 클래스명 앞에 '~'기호를 사용하여 작성한다. 

 


 

1. 구현방법

	~Student()
	{
		cout << "객체 소멸 \n";
	}
int main()
{
	Student* st1 = new Student("han" , 100, 98);
	st1->NumberUp();
	Student st2(*st1);
	st2.NumberUp();
	st1->Show();
	st2.Show();
	
	delete st1;
	system("pause");
}

st1은 선언한 지역이 끝날 때 메모리를 해제하기 전에 소멸자를 먼저 수행하지만, 동적으로 생성한 st2같은 경우는 delete키워드를 이용해 메모리를 해제요청하고 소멸자를 먼저 수행하도록 해야한다.

C++ 역시 클래스 멤버 선언 시 접근 제한자를 지정할 수 있다.

접근 제한자는 왜 필요할까?

객체지향 프로 래밍은 특히 이 접근 제한자를 잘 사용할 필요가 있다. 객체지향 프로그래밍은 한 완제품을 생상하는 공장의 기계들과 같다고 생각하면 좋을 것 같다. 예를 들어 자동차 한 대를 만들어 내기 위해서는 엔진, 부품 등등 여러 가지 제작, 결합 과정을 거치게 된다. 이 공장 기계들은 서로의 어떻게 부품을 만들어 내는지, 전혀 알 필요가 없다. 공장 기계들은 설계자가 시킨 작업만 진행하면 된다. 서로의 작업들의 내용을 공유하지 않기 위해 필요한 것이 정보은닉이다.

프로그램도 프로그래머가 설계한 클래스, 함수를 통해 자기 자신의 작업만 진행하면 된다. 이 접근 제한자는 프로그램을 보통 한명이 만들 때보다 여러 프로그래머가 동시에 작업을 할 때 특히 확실히 필요하게 된다.

#include <iostream>
#include <string>

using namespace std;

class Student
{
private:
	string name;
	int englishScore;
	int mathScore;
	int getSum()
	{
		return englishScore + mathScore;
	}
public:
	Student(string n ,int e, int m)
	{
		name = n;
		englishScore = e;
		mathScore = m;
	}
	void show() { cout << name << " : [합계 " << getSum() << "점]\n"; }

};

int main(void)
{
	Student a = Student("Mr.Han",100,98);
	a.show();

	system("pause");
}

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

C++) 클래스 상속, 오버라이딩  (0) 2020.06.22
C++) 생성자와 소멸자  (0) 2020.06.10
C) 배열/문자열과 포인터.  (0) 2020.05.02
C) 파일 입출력 (FILE,fopen,fclose)  (0) 2020.05.02
C) ASCII 아스키코드  (0) 2020.05.01

 

 

모든 리스트 해제

 

일반적으로 삽입삭제가 많이 필요하다면 배열로 구현된 리스트보다 연결리스트가 더 유리하다.

배열의 사이즈 할당

리스트의 값을 맨앞 또는 맨뒤에 넣는 함수.

특정 인덱스의 값을 지우는 함수

 

결과

배열과 포인터는 동일하다고 보면된다. 배열을 선언하면 그 이름자체가 포인터 변수가 되는 것이다.

a자체가 선언한 배열의 주소를 가지고 있는 것이다. 

 

문자열의 개념

문자열은 문자들의 배열이다. 끝을 알리는 목적으로 컴퓨터 메모리 구조상 마지막에 널값을 포함한다. 

문자열 형태로 포인터를 사용하면 포인터에 특정한 문자열의 주소를 넣게된다. 

"Hello World"문자열을 읽기 전용으로 메모리 공간에 넣은 뒤 그 위치를 처리한다. 

배열은 포인터와 치환되기 때문에 이런식으로 각각 char한 문자씩 출력이 가능하다. 

간단한 txt파일에 문자 입력하기

 

fopen() 함수의 원형 

FILE * fopen( const char *, const char * );

첫번째 인자 : 처리할 파일 명 

두번째 인자 : 파일 처리 종류 지정 (모드) 

 

r = 읽기 모드 / 파일이 없을 경우 에러 발생 

w = 쓰기 모드 / 파일이 없을 경우 새로 만들고, 파일이 존재하면 내용을 삭제하고 처음부터 기록 

a = 추가 쓰기 모드 / 파일이 없을 경우 새로 만들고, 파일이 존재하면 뒤에부터 이어서 기록 

 

fopen으로 파일을 열었다면 fclose를 사용하고 닫아 주도록 한다.

 

이렇게 실행하면 해당 프로젝트 폴더에 "temp"파일이 생성된것을 볼 수 있다.

그 안에는 "Hello World"가 입력되어있다.

 

1. 파일 읽고 쓰기.

input.txt 파일을 만든다.

이제 이 input.txt파일을 한줄 씩 읽고 하나씩 출력하는 프로그램을 만들기 위한 코드를 작성한다. 

 

Student라는 데이터를 입력받을 구조체 모델을 만든다.

구조체 포인터를 이용해 크기를 동적으로 할당한다. n은 총 학생수.

 빌드 후 확인한다.

 

1. 메모리 해제, 파일 닫기

sum에 다가 학생들의 점수를 모두 더하고 출력하도록 했다.

아스키 코드

아스키코드는 0~127중의 1바이트로 구성된다.

https://namu.wiki/w/%EC%95%84%EC%8A%A4%ED%82%A4%20%EC%BD%94%EB%93%9C

캐릭터형 자체에 숫자를 넣어서 처리할 수 있다. 

char 형으로 48을 받으면 아스키코드로 0을 나타내고 있는것을 알 수있다. char각각 매칭된다는것을 알 수 있다. 컴퓨터는 실제로 받아 들일때는 문자가아닌 숫자로 받고 있다는것이다.

 

Enum.Parse메서드는 문자열 enum인스턴스로 변환한다. 이 메서드는 enum형식과 멤버 이름을 담은 문자열을 받는다.

이런 enum이 있을 때 

"dog"라는 string으로 Enum.Parse를 사용하여 Animal의 enum을 받아올 수 있다. 

 

스태틱 함수 Parse(Type, String)을 사용하면 enum을 받아 올 수있다.

반환 값이 object이기에 캐스팅하면 원하는 타입으로 값을 얻어 올 수 있다. 

 

enum으로 선언되어있지 않은 값을 부르면 에러를 뱉는다.

 

빈 문자열

빈 문자열은 길이가 0인 문자열이다. 문자열을 초기화 하거나 값을 비워줄때 빈 문자열로 만든다. 이 빈 문자열을 만드는 방법들이 몇가지 있다.  

1. 리터럴을 이용

2. 정적 string.Empty필드를 이용

 

빈 문자열을 만들었으니 실제로 값이 비워져있는지 체크해보자

결과는 모두 true

 

 

널 문자열

string은 참조 형식이므로null처리가 가능하다.

널처리된 sEmpty를 체크해보면

순서대로 true, false, NullReferenceException

전처리기 지시자

코드의 특정영역에 대한 추가적인 정보를 컴파일러에게 제공한다. 가장 흔히쓰이는 전처리기 지시자는 조건부 컴파일 지시자들이다. 

 if debug와 endif안에 있는 코드가 실행된다. 당연히 맨위 #define을 뺀다면 실행되지 않는다.

적용방법 :

1. 소스코드(클래스별로) 맨위에 #define을 선언한다.

2. 프로젝트 조건부 컴파일 기호에 define으로 사용할 기호를 넣으면 된다. 

 

조건절을 통한 사용도 가능하다.

 

단점

1. 디버깅이 힘들다.

=> 컴파일러 전단계에서 동작하므로 경고나 에러메세지 없이 코드의 흐름을 보기에 불편하다.

장점(언제쓰나)

1. 한 프로젝트로 다른 빌드버전 대응이 가능하다. 

파란색 : 문제점

빨간색 : 해결방법

초록색 : 느낀점

문제

전처리기를 사용하는 과정에 나는 오류메세지이다. 조건부 컴파일 기호도 넣었는데 안됐다.

오류 해결방법

전처리기는 코드 맨위에 선언해야한다. 

 

 

https://docs.microsoft.com/ko-kr/dotnet/csharp/misc/cs1032

바인딩이란?

형식이나 멤버, 연산을 구체적으로 결정하는 과정을 말한다. 변수를 예로 들면, 변수를 구성하는 식별자, 자료형 속성, 하나 이상의 주소, 자료 값에 구체적인 값으로 확정하는 것을 말한다. 이런 바인딩을 하는 시점에 따라 동적 바인딩, 정적 바인딩으로 나뉜다.

 

동적 바인딩

실행 시점에 진행되는 바인딩이다. 동적 바인딩은 컴파일 시점에서 어떤 함수나 멤버, 연산이 존재한다는 점을 프로그래머 자신을 알고 있지만 컴파일러는 알지 못하는 경우에 유용하다.

dynamic 키워드를 이용해 선언한다. 프로그래머는 d의 Quack()라는 함수가 있다고 가정하고 코드를 작성한다. 이렇게 하면 적으로 d와 Quack()은 바인딩된다.

 

정적 바인딩

 먼저 컴파일러는 p의 GetObj() 이름의 매개변수 없는 메서드를 찾는다. 그런 메서드가 없으면 컴파일러는 선택적 매개변수를 받는 메서드, 그다음에는 Progoram의 기반 클래스에 있는 메서드 여기에도 없으면 확장 메서드까지 찾는다. 없으면 컴파일 오류를 뱉는다. 정적 바인딩은 컴파일러가 바인딩을 과정을 수행하며 이미 선언되어 컴파일러가 확실하게 파악된 형식만 가능하게 되는 것이다. 

암묵적/명시적 널 가능 변환

T에서 T?로의 변환은 암묵적이고 T?에서 T로의 변환은 명시적이다.

명시적 캐스팅은 null가능 객체의 value속성을 조회하는 것에 직접 대응된다. 따라서 만일 HashValue가 거짓이면 에러가 발생한다. 

 


널 가능 값의 박싱과 언박싱

T?가 박싱된 경우, 힙에 있는 박싱된 값은 T?가 아니라 T를 담는다. 이러한 최적화가 가능한 것은, 박싱된 값은 이미 널을 표현할 수 있는 참조 형식이기 때문이다.

int?가 아닌 int가 담긴다.

c#은 널 가능 형식을 as연산자로 언박싱하는 연산도 허용한다. (캐스팅 실패 시 null)

순차열 합성

요소들의 계산은 최대로 지연되다가, MoveNext() 호출해서 비로소 계산된다. 


Nullable구조체

컴파일러는 T를 가벼운 불변이 구조체인  System.Nullable<T>로 바꾸어 컴파일한다. 이 구조체에는 두가지 필드가 존재한다. Value와 HashValue라는 두 필드만 있다. 

아시다시피 C#에서 정수,구조체 등은 Value타입들은 null을 가질 수 없다. 하지만 이런 값타입도 값이 할당되지 않은 상태를 표현하고자 하려할때 Nullable구조체를 사용한다.

컴파일러는 위쪽 코드를 아래와 같이 바꾸어서 컴파일한다.

 

HashValue가 거짓인 널 가능 객체의 value를 조회하려 하면 InvalidOperationException이 발생한다. GetValueOrDefault()메서드 HashValue가 참이 면 value를 돌려주고 그렇지 않으면 new T()또는 지정된 커스텀 기본값을 돌려준다.

(T?의 기본값은 NULL)

반복자

foreach문은 열거자의 값들을 사용하는 소비자라면, iterator는 열거자의 생산자의 해당한다.

다음은 피보나치 수열의 수들을 돌려주는 반복자 메서드를 이용하여 반복자에 대해서 살펴보자

yield return 문은 "이 열거자에 원하셨던 다음 요소가 여기 있으니 받으세요"라고 말하는것과 같다.

함수가 실행된 후 내부상태가 유지 되지않는 return과 다르게 yield return은 내부 상태를 유지하며 다음에 호출차가 다시 열거를 수행하면 열거자는 다음 요소를 돌려주게 된다. 

반복자 의미론

반복자는 하나 이상의 yield문이 있는 메서드나 속성, 인덱서이다. 그렇기 때문에 IEnumerable열거 가능 인터페이스와 IEnumerator열거자 인터페이스와 호환되어야 한다. 

 

열거자와 반복자

값들의 순차열으 반복하는 읽기 전용 커서이다. 구체적으로

System.Collections.IEnumerator

System.Collections.Generic.IEnumerator<T>

두가지 인터페이스 하나를 구현하는 객체이다

 

Foreach문은 열거 가능객체를 반복한다.

열거 가능 객체는 순차열의 논리적 표현이다. 다음 조건 하나를 만족하는 객체는 열거 가능객체로 간주된다.

  1. IEnumerator IEnumerable<T> 구현한다.

  2. 열거자를 돌려주는, GetEnumerator라는 메서드가 있다.

 

foreach문으로 훑는 예이다.

foreach없이 저수준으로 읽는 방법이다. 

열거자가 IDisposable 구현하는 경우, foreach문은 using문으로도 작용한다. , 반복이 끝나면 열거자 객체가 암묵적으로 처분된다.

Using문

클래스 중에는 파일 핸들이나그래픽 핸들, 데이터베이스 연결 같은 비관리 자원들을 캡슐화하는 것이 많다. 그런 자원들을 정리하기 위한, System.IDisposable인터페이스의 Dispose라는 메서드가 있다.

이번 포스트에 정리할 using은 콘솔창 맨위에 사용하는 지시문이 아니라 문장 형태의 using문이다.

using문을 사용하여 해당 리소스 범위를 벗어나게 되면 자동으로 리소스를 해제하여 관리하도록 하는 것이다.

이렇게 using문의 { }괄호를 벗어날때 Dispose가 된다.

 

다른 방법 try블록과 finally블록에서 Dispose를 호출한다.

 

Try문

try문은 오류 처리 또는 마무리코드를 위한 코드 블록을 지정한다. try블록 다음에 catch블록이나 finally블록이 올 수 있다. try블록 안에서 오류가 발생하면 catch블록이 실행한다. 

마무리 작업을 수행하기 위한 finally블록은 실행이 try블록을 벗어나면, 또는 catch블록이 존재하고 실제로 실행된 경우에는 catch문블록을 벗어나면 실행된다. 즉, finally블록은 오류가 발생하든 발생하지 않든 항상 실행된다.

어떻게 사용될지 예제를 보자.

위 예제를 실행하면 다음과 같은 오류를 뱉는다.

프로그램은 강제종료된다. 위와 같은 에러는 catch로 처리해 줄 수 있다.

실행결과 =>

실행순서는 이렇게 된다.

1. 함수에서 예외가 발생

2. catch블록으로 이동

3. catch블록의 실행이 성공적이면 try다음 문장으로 이동 (finally블록이 있다면 그것이 먼저 실행) 그렇지 않다면 실행은 함수를 호출한 곳으로 돌아간다(finally블록이 있다면 그것이 먼저 실행) 그런다음 같은 처리가 반복.

 

 


 

Catch 절

블록을 잡고자 하는 예외 형식을 괄호 쌍 안에 지정한다. 

그러한 예외 형식은 반드시 System.Exception클래스이거나 System.Exception의 파생 클래스이어야 한다.

 


finally 블록

finally블록은 항상 실행된다. 예외가 던져지든 아니든, 그리고 try블록이 끝까지 실행되든 아니든 이 블록은 항상 실행된다. 그래서 흔히 마무리 코드에서 쓰인다.

대리자 대 인터페이스

대리자로 풀 수 있는 문제는 인터페이스로도 해결이 가능하다.

 

 

그럼 어떨 때 대리자를 쓰는 게 더 효율적일까?

내가 보는 책에서는 3가지 정도의 예시를 보여주고 있다.

  1. 인터페이스가 메서드를 하나만 정의할 때

  2. 다중 캐스팅 능력이 필요하다.

  3. 여러 종류의 메서드를 인터페이스를 여러 번 구현해야 한다.

하나씩 살펴보자.

 

1. 인터페이스가 메서드를 하나만 정의할 때 & 3. 여러 종류의 메서드를 인터페이스를 여러 번 구현해야 한다

위 예시처럼  ITransformer인터페이스는 메서드를 하나만 가지고 있다. 이럴 경우 왜 델리게이트 더 좋냐면, 인터페이스 같은 경우 하나의 클래스에서는 하나의 메서드만 넘길 수 있는 반면에 델리게이트로 구현하게 되면 다른 메서드(int로 반환하는 메서드) 면 어떠한 메서드도 넘길 수 있기 때문이다.

델리게이트로 하면  Square 1도 되고~ Square2되지만

 

인터페이스로는

각각 클래스에 인터페이스를 상속받아넘겨야 한다.

 

2. 다중 캐스팅 능력이 필요하다.

하나의 대리자 인스턴스가 여러 개의 메서드를 지칭할 수 있는 델리게이트 기능이 필요하다면. 예를 들어 

예시가 너무 구린데 암튼 이런 식으로 쓸 거라면 델리게이트 고고.

 

 

대리자

대리자는 어떤 메서드를 호출하는 방법을 담은 객체이다. 대리자 형식은 그 형식의 인스턴스, 즉 대리자 인스턴스가 호출할 수 있는 종류의 메서드를 정의한다. 

 


 

대리자를 이용한 플러그인 메서드 작성

대리자 변수에서 메서드를 배정하는 연산은 실행 시점에서 일어난다. 따라서 대리자는 플러그인 메서드를 구현하기에 좋은 수단이다. 다음 예제를 보자. 

 

위 예제처럼 Square의 메서드를 매개변수로 넘길수도 있다.

 

 


대리자 다중 캐스트

모든 대리자 인스턴스에는 다중 캐스트 능력이 있다. 무슨 말이냐 하면, 하나의 대리자 인스턴스가 하나의 대상 메서드가 아니라 여러 개의 대상 메서드를 지칭할 수 있다는 것

 


 

표준 Func 대리자와 Action 대리자

Func 

리턴 타입이 있는경우

Func <T>은리턴 타입이 Generic폼 내에, 즉 템플릿 파라미터 안에 존재한다. T는 리턴 값의 타입을 가리킨다. 경우 Func <T, TResult>, 입력이 2개인 경우 Func<T1, T2, TResult> 를 사용한다.

 

Func<T, T2,  TResult>은 어떻게 쓸까?

TResult는 out키워드가 있는데 밑에 설명 그대로 반환하는 메서드를 캡슐화한다고 한다.  그럼 대충 눈치가 챌 수 있다. 

 

 

Action 

리턴 값이 없는 경우

System네임스페이스에서 제공되고, 파라미터의 수에 따라 0개부터 16개까지 파라미터를 받아들이는 delegate가 있다.

Action <T> delegate, 2개인 Action <T1, T2> delegate....

 

 

 

 

 

 

제네릭 형식의 파생

제네릭 형식도 제네릭이 아닌 클래스처럼 파생할 수 있다. 제네릭 클래스를 기반으로 삼아서 파생 클래스를 정의할 떄, 다음처럼 기반 클래스의 형식 매개변수를 옆린 채로 남겨두는 것이 가능함.

물론 이런식으로 구체적인 형식으로 매개변수를 제한 할 수도 있다.

또한 파생 형식에서 새로운 형식 매개변수를 도입할 수도 있다.

이렇게 사용하면 된다.

 

 

 


 

자신을 참조하는 제네릭 형식

파생 형식이 기반 형식의 형식 매개변수를 닫을 때, 파생 형식 자신을 형식 인수로 지정하는 것이 가능하다.

밑에 처럼 사용하면 되겠쥬?

 

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

2020311[C#] 대리자, 인터페이스 차이점  (0) 2020.03.11
2020310[C#] 대리자(delegate)  (0) 2020.03.10
20200225[C#] 제네릭 제약_2  (0) 2020.02.25
20200224[C#] 제네릭 제약_1  (0) 2020.02.24
20200220[C#] 제네릭  (0) 2020.02.20

형식 매개변수에 제약조건을 지정함으로써, 형식 매개변수에 대입할 수 있는 형식 인수들을 좀 더 제한하는 것이 가능하다.

4. 구조체 제약 조건

반드시 T가 값 형식(널 기능이 아닌)이어야 함을 뜻한다. 구조체 제약 조건의 좋은 예는 System.Nullable<T>구조체이다. 

구조체 제약 조건


2. 매개변수 없는 생성자 제약 조건

T에 반드시 매개변수 없는 공용 생성자가 있어야한다. 이를 만족하는 T에 대해서는 new()를 호출할 수 있다.


2. 적나라한 형식 제약 조건

형식 매개변수가 다른 형식 매개변수와 같은 형식이거나 그 파생 형식이어야 함을 뜻한다. 

형식 매개변수에 제약조건을 지정함으로써, 형식 매개변수에 대입할 수 있는 형식 인수들을 좀 더 제한하는 것이 가능하다. 

1. 참조 형식 제약 조건

사용 형태

제약조건은 참조 형식으로 설정했기 때문에 int는 에러를 발생한다.


2. 인터페이스 제약 조건

매개변수가 반드시 주어진 인터페이스를 구현해야 한다. 이러한 제약 조건들을 만족하는 형식 매개변수의 인스턴스는 해당 클래스나 인터페이스로 암묵적으로 변환된다. 

namespace System의 CompareTo

CompareTo메서드는 만일 this가 other보다 크면 양수를 돌려준다. 

어떤 형식도 인수로 받을 수 있다. 


3. 기반 클래스 제약 조건

형식 매개변수가 반드시 주어진 클래스 또는 그 클래스의 파생 클래스이어야 한다. 

Parent만 가능하다.


 

+ Recent posts