[C++] 타입 변환이 모든 매개변수에 대해 적용되어야

한다면 비멤버 함수를 선언하자.


유리수를 나타내는 클래스를 만들고 있다.

이 클래스를 디자인할 때 정수에서 유리수로

암시적 변환은 허용하자고 판단을 했다.

int -> double 변환과 별반 다르지 않기 때문에.




class Rational {

public:

Rational ( int numerator = 0 , int denominator = 1 );

// explicit으로 선언하지 않았다.


int numerator() const;    // 분자 접근 함수

int denominator() const;    // 분모 접근 함수


private:

...

}



유리수를 나타내는 클래스이기 때문에 덧셈이나

곱셈 등의 수치연산은 기본으로 지원해야 할 것이다.


멤버 함수로 연산자 오버로딩을 해보자.

const Rational operator* (const Rational& rhs) const;

이로써 곱셈 연산이 가능해진다.


Rational oneEighth (1 , 8);    // 가능

Rational oneHalf(1, 2);    // 가능


Rational result = oneHalf * oneEighth;    // 가능

result = result * oneEighth;    // 가능




이제 혼합형 수치 연산을 해보자.

유리수를 int(정수)와 곱셈을 가능하게

하는 것도 어떻게 보면 당연한 일이다.


result = oneHalf * 2;    // 가능

result = oneHalf.operator(2);    // 가능


result = 2 * ontHalf;    // 에러

result = 2.operator(oneHalf);    // 에러




첫 번쨰 줄에서 oneHalf 객체는 operator* 함수를 멤버로

가지고 있는 클래스의 인스턴스이기 때문에, 컴파일러는

이 함수를 호출한다. 하지만 두 번째 줄에서 정수 2에는

클래스 같은 것이 연관되어 있지 않기 때문에 operator*

멤버 함수도 있을리가 없다. 따라서 컴파일러는 비멤버

버전의 operator*를 찾아본다.


result = operator*(2,oneHalf);


비멤버 버전의 operator*가 없기 때문에 실패.




여기서 위의 성공 케이스를 다시 보도록 하자.

매개변수가 정수 2인데 Rational 객체를 받도록

되어 있는 operator*의 호출을 가능하게 한다.

이게 어떻게 가능한 것일까?


바로 암시적 타입 변환에 의해 가능하게 되는 것이다.

컴파일러는 이 int를 Rational 클래스의 생성자에 주어

호출하면 Rational로 둔갑시킬 수 있다는 사실도 알고 있다.




따라서

const Rational temp(2);

result = oneHalf * temp;

이런식으로 동작을 하게 된다.


물론 생성자를 명시호출 (explicit)로 선언되었다면 모두 불가능.



자 다시 돌아와서 둘 다 비명시호출을 했는데도 하나는 되고

하나는 왜 되지 않는지 고민해 보도록 하자.


result = oneHalf * 2; --> 비명시호출 생성자

result = 2 * ontHalf; --> 비명시호출 생성자인데도 안됨




결론

암시적 타입 변환이 먹혀들려면 매개변수 리스트에 들어있어야만 한다.

전자의 경우 매개변수 리스트에 있는 객체가 쓰이고 있지만

후자는 그렇지 않다. operator*의 구현부를 살짝 보자면

this * rhs 두 개의 매개 변수로 이루어져 있을 것인데

this란 놈은 매개변수 리스트에 없고 rhs란 놈만 있다.

따라서 매개변수 리스트에 있는 rhs란 놈만 암시적 타입변환이 일어난다.

암시적 타입 변환이 모든 매개변수에 대해서 이루어 지지 않고 있다.


그렇다면 모든 인자에 대해 암시적 타입변환을 수행하도록 하려면

어떻게 해야할까? operator*를 비멤버 함수로 만들어버리면 된다.




const Rational operator* (const Rational& rhs, const Rational& rhs){

return Rational (lhs.numerator() * rhs.numerator(),

   lhs.denominator() * rhs.denomiator());

}


Rational oneForth (1, 4);

Rational result;


result = oneFourth * 2;    // 가능

result = 2 * oneFourth;    // 가능




위의 함수는 비멤버이지만 프렌스 함수로 선언해야

하는 것 아닌가 의아해 할 수도 있다. 하지만 위의 

operator*는 클래스의 private 부분을 하나도

건드리지 않고 있다. 오로지 public 부분만을

사용하기 때문에 프렌드 선언은 적절하지 않다.


--> 프렌드 함수는 피할 수 있으면 피할 것




POINT!!

어떤 함수에 들어가는 모든 매개변수(this도 포함)에 대해

타입 변환을 해 줄 필요가 있다면, 그 함수는 비멤버이어야 한다.

[C++ STL] 선형 검색(find 알고리즘) 그리고 단순 연결 리스트


먼저 C++에서의 선형 검색에 대해 알아보자.

STL의 일반화된 선형 검색 알고리즘인

find는 다음과 같이 구현된다.







find는 1차원적인 요소들의 순차열이라는 개념을

지원하는 임의의 자료구조에 대해 검색을 수행한다.

선형 검색을 위해 필요한 것인, 하나의 요소를 조사하고,

다음 요소로 넘어가고, 모든 요소들을 처리했는지 판정하는 것이다.

애초에 [first , last]가 하나의 선형적인 구간이라고 가정하지 않는다면 무의미




이러한 find 알고리즘이 정말 일반적이라면

배열 뿐만 아니라 단순 연결 리스트의 검색도 가능해야 한다.

다음은 연결 리스트의 구성과 이러한 노드들의 목록을

훑을 때 사용하는 코드의 예시이다.



int_node들의 리스트는 하나의 선형 순차열이므로,

선형 검색 알고리즘을 다시 작성하는 것은 낭비이다.

 find를 재사용하는 게 바람직한데, 어떻게 해야 할까?




기존에는 단순하게 ++first라는 포인터 연산으로

다음 요소를 얻었지만, 지금은 검색하고자 하는 것이

배열이 아니라 하나의 연결 리스트이기 때문에

그런 포인터 연산은 통하지 않는다.


p가 int_node를 가리킨다면, 다음 노드는

++p (p+1)가 아니라 p->next이다.




이러한 문제는 C++의 연산자 오버로드로 해결할 수 있다.

우리가 원하는 행동을 하도록 구체적으로 정의하면 된다.


그런데 int_node* 형식의 인수들에 대해

operator++를 다시 정의하는 것은 불가능하다.

때문에 int_node*처럼 보이면서도 operator++를

좀 더 적절하게 정의하는 간단한 래퍼 클래스를 만들면 된다.



int_node 뿐만아니라 next 포인터를 가진

어떠한 형식의 노드에도 작동하도록 설계




이제 로프를 사용하지 않고도 특정한 값을 가진

int_node를 찾을 수 있다. find 함수를 재사용 해보자.


find(node_wrap<int_node>(list_head) , node_wrap<int_node>(), val)


두 번째 인자 node_wrap<int_node>()는 기본 생성자를 호출.

널 포인터를 가진다. 즉 리스트의 마지막을 나타낸다.




node_wrap이라는 래퍼는 사소해 보여도, 상당히 놀라운 기능을 한다.

위에 정의한 int_node는 어떻게 보면 C++이 나오기도 전에 정의된

구조체일 수도 있다. 이렇게 오래된 자료구조와 새로운 알고리즘

find를 함께 사용할 수 있게 되었다. 즉 node_wrap 클래스는

오래된 자료구조와 새로운 알고리즘 사이의 중재자 역할을 한다.


또한 모든 멤버 함수들은 인라인으로 정의되어 있다.

때문에 효울성을 전혀 손상시키지도 않는다.




오늘은 STL의 선형 검색 알고리즘 (find 알고리즘)과

단순 연결 리스트에 대해 알아 보았다.

이제부터 C++ STL의 개념적인 이야기보다

좀 더 원론적인 이야기를 포스팅해나가겠다.




'C++' 카테고리의 다른 글

[C++ STL] C++의 선형 검색  (0) 2017.04.04
[C++ STL] C의 선형 검색  (0) 2017.04.04
[C++] UML 다이어그램 (클래스 다이어그램)  (0) 2017.01.17
[C++] static_cast , dynamic_cast  (0) 2016.12.27
[C++ STL] STL에 필요한 템플릿 예제  (0) 2016.12.21

C# 인덱서 (Indexer)


인덱서는 인덱스를 이용해서 객체 내의 데이터에

접근하게 해주는 프로퍼티라고 생각하면 된다.

객체를 마치 배열처럼 사용할 수 있게 해준다.


한정자 인덱서 형식 this [형식 Index]{

get{ ... }

set{ ... }

}



C# 인덱서 예제




foreach가 가능하게 만들어보자.

foreach 문은 IEnumerable과 IEnumerator를

상속하는 형식만 지원한다. 따라서 IEnumerable과

IEnumerator를 상속하기만 하면 foreach를 이용할 수 있다.




IEnumerable의 메소드

1. IEnumerable GetEnumerator()


IEnumerator 인터페이스를 상속하는

클래스의 객체를 반환하면 된다.

그렇다면 IEnumerator 인터페이스는 무엇일까.




IEnumerator 메소드 및 프로퍼티

1. boolean MoveNext()

2. void Reset()

3. Object Current { get; }


자 그럼 위의 예제를 foreach가 가능하도록 만들어 보자.




IEnumerator 멤버




IEnuemerable 멤버




메인




C#의 인덱서 (Indexer)에 대해 알아보았다.

C++이랑 용어만 다르지 아직까진 비슷한게 더 많은듯




+ Recent posts