1. 취업하기 (3200+)

2. 토익 750 넘기기

3. -25kg

4. BridgeBBCC 대체 플러그인 만들기 (이왕 만드는김에 엑스스플릿이나 OBS 플러그인으로)

'신변잡기 > 일상' 카테고리의 다른 글

올해의 목표  (0) 2021.01.03
근황 2  (0) 2020.10.15
근황  (0) 2020.07.14
오랫만에 책을 펴보았습니다.  (0) 2020.01.01
2020년 할일  (0) 2019.12.19

아는분의 제안 겸 조언으로 '투명한 물' 구현을 해보았다.

원래는 '언어나 프로그램 제약없이 할수 있도록' 다이렉트X를 추천받았으나...

하도 진행을 못하는걸 보고는 그냥 다른 엔진 써보고 해서... 원래 잡던 유니티 말고 언리얼엔진 처음 열어서 구현했습니다.

 

 

일단. 뭘 만들었는지는 영상부터 보고 갑시다.

 

(영상은 개선판입니다)

 

투명한 물, 물 표면 표현, 굴절, 반사 가 들어간 기본적인 물 표현입니다.

 

 

 

* 해당 자료 구현시 Pub Games의 영상을 참고하였습니다.


 

전체 구조도입니다.

 

아는건 별로 없지만, 까먹지 않기 위해서라도 하나씩 정리해봅시다.

* 틀린게 많을 수 있습니다. 틀린것이 있다면 지적바랍니다.

 

1. 포지션

 

1) 절대 월드 포지션 (Absolute Absolute world position) :

이 텍스쳐 구현에서 월드 포지션을 사용하는 목적은 텍스쳐가 적용된 오브젝트의 '위치에 무관하게' (=동일한 위치에) 텍스쳐가 적용될 수 있도록 하는 목적으로 사용된다.

즉, 텍스쳐 표현을 절대 월드 좌표로 고정해버림으로써, 오브젝트가 이동하더라도 텍스쳐 표현이 동일한 위치에 적용된다.

 

뭐 이렇게 쓰니 같은말 두번 쓴거같은데요... 표현 기준이 오브젝트 별 이 아니라 오브젝트가 놓인 월드 좌표계 기반이다... 정도면 될거같습니다.

 

(자료 참조)

 

2) Divide? :

값을 나누는 이유는... 자체 그림자 방지 (To provide a fall off to the self shadowing)를 위해서라는데... 아직 잘 이해가 안되는 부분이네요.

여긴 공부가 더 필요할것 같습니다.

나누는 값 자체는 텍스쳐를 적용할 오브젝트의 사이즈 만큼... 이라고 하는거 같네요.

 

3) Mask :

R은 X축, G는 Y축, B는 Z축으로 표현되는 모양이더라구요.

그리고 UE4에서는 Z축이 높이를 의미하기 때문에... 평면 가로/세로의 값만 중요하여 두 값을 추출하기 위해 R,G 마스크를 사용한다... 고 하는것 같습니다. 즉, 'XY 평면에 평행한 텍스쳐를 구현하기 위해 X,Y 값만 추출한다.' 고 볼수 있겠네요.

월드 포지션에는 당연하겠지만 3개의 값이 들어갑니다. X축, Y축, Z축.

사용한것은 ComponentMask였으며, 적당히 축약어로 검색해도 잘 튀어나옵니다.

축약어 사용 예

 

2. 노멀맵

'노멀맵' 자체에 대한 설명은 검색해보시거나 들고계신 책 보는게 빠르고... (사실 이해 덜했음)

이 텍스쳐에서는 어떻게 써먹고 있나 봅시다.

 

 

1) Panner : Panner를 사용하는 이유는 단순하지만 큰 이유입니다. 물이 흐르는것을 표현하기 위해 텍스쳐를 움직여줘야 하기 때문이죠.

텍스쳐의 U (가로) / V (세로) 를 조합한 방향으로 움직여 복잡한 애니메이션이 일어나는것처럼 보이는 목적입니다.

레퍼런스 참조 하시면 더 좋습니다. 넹.

input은 월드 좌표계였습니다.

 

2) Texture Sample : 말할것도 필요없는 노멀맵입니다.

위 아래의 텍스쳐가 조금 다른것처럼 보이는데, 사실 두개 같은거에요. 그냥 복사 - 붙여넣기 한것.

텍스쳐에서 '질감'을 표현해주는 노멀맵을 가져와서 적용시킨것입니다.

두개인 이유는 물 흐르는 표현을 2개로 겹쳐서 그런것이고... 사방에서 퍼지는걸 하고싶으시다면 Panner를 포함해서 4개를 넣으시면 되겠습니다.

 

3) Add? : 말도 필요없이 그냥 만들어진 Panner - Texture 세트를 합치는 목적입니다.

그래야 한번에 출력되죠.

 

4) Constant 3 Vector : 솔직히 얘가 제일 이해가 안갑니다.

기능 자체는 '3채널 상수값'을 벡터 형식으로 출력해주는것이 전부입니다.

3채널을 사용하는 텍스쳐 좌표 변경시 사용하는것이 좋다는데...

잠깐 찾아보니 실행 직전에 한번 계산되고, 플레이 도중에는 변경이 불가능한 마테리얼 인스턴스. 라고 하네요. 

근데 만일, 물이 튀기는 표현등을 넣는다면... Dynamic 값을 이용해야하지않을까 싶네요.

이것도 레퍼런스 참조해보시면 더 도움이 될것 같습니다.

 

5) Multiply : 움직임이 구현된 Texture 와 인스턴스 값을 곱해서 적용시키는 목적입니다.

더하면 단순히 수치만 고정되는것이고, 곱하게 되면 각 R/G/B값에 해당되는 수치가 곱해져 계산이 되겠네요.

 

여기까지 붙은것을 마테리얼 파라미터의 Normal에 고정시켜줍니다.

 

이러면 물 표면 표현이 완성됩니다.

 

 

3. Opacity (투명도)

1) Depth Fade : 말그대로 '깊이별 Fade' 입니다. 인자로는 투명도 값과, Fade가 시작되는 월드좌표 수치를 받네요.

2) Fresnel (프레넬) : 실제로는 '관찰자가 바라보는 각도에 따라 반사되는 빛의 세기가 달라지는 현상을 설명하는 데 사용되는 용어', 즉 반사와 관련된 값인데요...

물의 투명도는 Fresnel값 계산과 반대로, 수직으로 보면 투명하고, 평행하게 될수록 덜 투명하게 보여야 합니다.

그래서 Fresnel값에 1-X를 해줘서 값을 뒤집는것이구요.

이를 곱해서 Fresnel에 따라 Fade가 일어나도록 구현이 됩니다.

 

4. Reflection (반사)

사실 구현하면서 제일 골치아프게 했던 부분인데요...

구현 자체는 금방했으나, 반사값을 너무 크게 하는 바람에 카메라 사이즈(= 스크린 사이즈)만큼의 사각형 격자처럼 반사가 일어나는 참사가 일어나기도 했습니다.

 

구현자체는 2시간만 걸렸으나 이거 원인 찾는데 3시간 넘게 써버려서 총 작업시간이 6시간 언저리로 만든 원흉...

대충 이렇게 말이죠.

 

뭐. 지금은 수정이 되었습니다.

 

 

 

 

반사값 파라미터는 딱히 뭐 더 없습니다.

Depth Fade 값에 반사 수치 인자를 곱해줘서 그걸 Reflection 파라미터에 넣어주면 끝입니다.

그냥 값 조절 잘 하십시오. 말고는 할말이 없네요. 넹

 

5. Base Color

 

 

Fresnel에 붙어있는 Normal Map 값은 아까 만들었던 그 Normal Map 값입니다.

 

1) Color : 말그대로, 밝은 면 / 어두운 면의 컬러링입니다. 큰 차이는 없지만, 색상이 변경되면 차이가 있긴 하더라구요.

원하신다면 저 값을 녹색이나 보라색 등으로 바꾸면 독물이 되어버리는 진기한 현상을 목격하실 수 있습니다.

 

2) Lerp (Linearly interpolate, 선형 보간) :

네. 선형대수 시간에 나오던 그 선형보간입니다. 단순히 선형보간 짧게 설명하자면 A / B 점을 가지고 중간의 임의의 값을 뽑아내기 위한 방법인데요. 왜 갑자기 여기서 쓰이느냐.

컬러가 2개밖에 없잖아요? 근데 자연스러운 컬러링을 위해서라면 범위에 따라 색상이 자유롭게 바뀌어야하니... 그때 적용이 되어서 들어가는 셈입니다.

그래야 약간 깊이, 더 깊은곳 등등에서 색이 자연스레 들어가는것이겠죠.

이를 Depth Fade 값과 곱해줌으로써 Depth 값에 따라 적용이 되도록 해줍니다.

 

3) Diffuse (디퓨즈) :

일반적으로 Diffuse Map은 물체의 입체감, 깊이감, 색을 부여해주는 요소인데...

단순하게 설명하자면 텍스쳐의 색감과 질감을 나타내주는 요소라고 할 수 있겠다.

사실 이쪽도 아직 이해가 부족해서... 다른 자료를 찾아보시는걸 추천한다.

 

아무튼 이 값은 마테리얼 파라미터의 베이스 컬러에 고정시켜줍니다.

 

이로써 4가지 항목 (노멀맵, 베이스 컬러(=디퓨즈 맵), 리플렉션, 투명도)를 모두 적용시켰다.

 

그렇게 나오는 마테리얼 결과값은 아래와 같다.

(실제로는 움직입니다)

잘 보면 투명도, 반사등이 적용되어있는걸 볼 수 있다.

 

이걸 물 표면에 적용시켜주면 위 영상과 같은것이 나오게 되는것.

 

단순히 영상 하나를 참고해서 야매로 만든것인데, 다음에는 좀 공부를 해서 물리가 들어간 물 구현도 해봐야 할 듯 싶다.

https://docs.microsoft.com/en-us/windows/win32/api/directxmath/ns-directxmath-xmmatrix

 

XMMATRIX (directxmath.h) - Win32 apps

Describes a 4*4 matrix aligned on a 16-byte boundary that maps to four hardware vector registers.

docs.microsoft.com

 

빙글빙글 

https://www.reddit.com/r/Unity3D/comments/gctq1q/i_made_a_tool_that_bakes_unity_physics_into/?utm_source=share&utm_medium=web2x

 

I made a tool that bakes Unity physics into texture and replays it on GPU with virtually zero cost.

Posted in r/Unity3D by u/Tomek_SC • 3,327 points and 134 comments

www.reddit.com

 

www.reddit.com/r/Unity3D/comments/gbwya1/i_worked_a_lot_on_this_while_in_quarantine_and_in/?utm_source=share&utm_medium=web2x

 

'게임 개발 > Unity' 카테고리의 다른 글

짧은 근황  (0) 2021.06.13
Rect Transform 잠깐 메모  (0) 2020.02.09
분명 자주 쓰는데 자주 까먹는 기능들  (0) 2019.12.27

앞에서 간단히 C++ String과 Java String을 알아봤다.

 

이제 각설하고, 두 String의 구현법 및 내부 구현 함수들을 보며 뭐가 다른지 파악해보자.

(Java에서는 메소드로 읽는게 정석이나, 편의를 위해 전부 함수로 칭하도록 하겠다)

 

(빠지거나 잘못된것이 있다면 덧글로 지적바랍니다.)

(C++의 경우 : C++17까지 / Java의 경우 : Java 8+ 까지 를 기준으로 작성하였습니다.)


1. 절대값

일단 이 방식이 매우 안좋은 방식인건 알지만, 단순히 각 String의 함수 개수를 세어보기로 했다.

1) C++

https://en.cppreference.com/w/cpp/string/basic_string

기준, Operator 연산을 제외하고 31개 (+a)의 함수가 존재한다.

 

2) Java

https://docs.oracle.com/javase/8/docs/api/

기준, 중복을 제외하고 40개 (+a) 메소드가 존재한다.

 

2. 문자열 접근

특정 위치의 문자열에 접근할 수 있는 함수들이다.

1) C++

(1) front / back : 각 String의 맨 앞 / 맨 뒤 값을 리턴해준다.

(2) begin / end : Iterator로, 각각 String의 맨 앞 / 맨 뒤 '위치'에 접근가능하게 해준다. 

(3) data : 맨 앞의 '포인터 값'을 리턴해준다.

(4) at / '[ ]' : 특정 위치의 값을 리턴해준다.

 

2) Java

(1) charAt : 특정 위치의 값을 리턴해준다.

의외로 이거 하나밖에 안보이는것같지만....

 

3. 문자열 삽입

문자열에 값을 삽입하는 함수들이다. 생성 및 수정 제외.

 

1) C++

(1) push_back / pop_back : 문자열의 맨 뒤/맨 앞에 문자를 추가한다.

push_back와 append, '+=' 는 모두 같은 결과를 보여준다.

 

2) Java

(1) concat(string) : 호출한 String의 뒤에 concat에 인자로 삽입된 문자열을 덧붙인다.

 

 

4. 문자열 관리

문자열의 특정 값을 수정하거나, Case를 변경해주는 등의 함수들이다.

1) C++

(1) replace() : 특정 위치의 값을 변경해준다. (C++11부터 가능)

(2) swap(string) : 두 String의 값을 변환해준다. 

 

2) Java

(1) replace(A, B) : A 문자열을 B 문자열로 일괄 변환한다.

(2) toLower/UpperCase() : 각각 전체 문자열을 소문자/대문자로 변환한다.

(3) trim() : 문자열 앞 뒤의 '공백'을 제거한다.

(4) valueOf(Type) : 인자로 주어진 값을 문자열로 변환한다. 모든 기본형(PrimitiveType)에 대응.

 

 

5. 문자열 추출

특정 문자열을 자르는 함수들이다.

1) C++

(1) copy(char* arr, size_t length, size_t index) : arr에 index부터 length까지의 문자열을 복사해준다.

(2) substr(size_t index, size_t length(기본값 : Underflow -> INT_MAX)) : 문자열을 index부터 length만큼 리턴한다.

(3) find_last_of() : 인자로 받은 문자열이 '마지막으로 나타난 위치' 이후를 리턴해준다.

 

2) Java

(1) split(string) : 인자로 들어온 정규 표현식에 따라 문자열을 나누어 리턴

(2) substring(int(, int)) : 인자로 들어온 인덱스부터 새로운 문자열로 리턴

 

 

6. 문자열 비교

문자열을 찾거나, 비교에 사용되는 함수들이다

1) C++

(1) find(string) : 인자로 받은 문자열이 '어디에 있는지'를 찾아준다.

 

2) Java

+ 가 붙은 함수는 'Case'를 구분하지 않는 함수가 존재하는 경우이다.

(1) indexOF(string) : 인자로 받은 문자열이 '어디에 있는지'를 찾아준다.

(2) contains(char) : 인자로 받은 문자열이 존재하는지 아닌지를 찾아준다.

(3) compareTo(string)+ : 인자로 받은 문자열과의 사전식 비교를 실시한다.

(4) equals(string)+ : 인자로 받은 문자열과 동일한 문자열인지 비교한다.

(5) LastIndexOf(string) : 인자로 받은 문자열이 '마지막으로 나타난 위치'를 찾아준다.

(6) matches(string) : 정규 표현식인지 아닌지 체크.

 

 

7. 기타 기본사항

1) C++

(1) C++ string은 char* 문자열 배열로 이루어져 있어, 각 항목이 1바이트로 처리된다.

ASCII가 아닌 타 문자 (CP949 등)에 대해서는, 내부적으로 항목 2개를 1개로 치환해서 비교하게 되어, 단순 순차접근을 실시하게 될 시 "깨진 값"을 얻게 된다.

 

2) Java

(1) Java에서는 char 형 또한 2바이트로 이루어져 있어, UTF-16 데이터를 한 묶음으로 처리가 가능하다.

C++ 에서처럼 순차접근 했다고 깨지는 불상사는 안 일어난다 (...)

 

 


뭔가 단순 비교를 했을때, Java의 경우 변환, 비교 함수 등이 많이 갖춰져 있었고, C++의 경우엔 의외로 삽입쪽이 Java보다 풍성하게 되었음을 알 수 있었다.

 

실제로는 7번 항목의 문자열 처리방식때문에 문자열을 많이 다룰시 C++보다는 Java를 선택하게 되고, 요즘은 단순 문자열 비교 등에 경우엔 Python 등 다른 생산성 높은 언어로 옮겨가는 추세로 보인다.

 

간단히 레퍼런스 자료만 보며 작성한것이므로, 다른 점이나 문제가 있다면 알려주시면 반영하겠습니다. 

C++ String에 대해서는 이전 글 참고

 

이번에는 Java다.

 

Java String은 익히 알려진것처럼 UniCode 사용을 예상하고 작업되어 각 문자 1개가 2바이트씩 차지하고, 문자열을 수정시에는 문자열 자체가 수정되는것이 아니라 수정된 문자열이 새로 생성되는 등의 특징이 있다.

하지만 '체감상' Java String이 C++ String보다 다루기 편했던것으로 기억하는데, 오랫만에 한번 뜯어보도록 하자.

 

이번 레퍼런스 자료는 Open JDK의 소스코드입니다.

검색하면 금방 찾으실 수 있으니 링크는 딱히 달지 않겠습니다.

 

2. Java String의 경우

 

    public String(char value[]) {
        this(value, 0, value.length, null);
    }

 

Java String 또한 Char형 배열로 이루어져 있으며 (* C++과는 다르게 포인터 개념을 사용할 수 없기에 일반 배열이다), Java에서는 Char형의 바이트 값이 2바이트 이므로, String의 내부 구현또한 2바이트 문자열을 저장하는것으로 이루어진다.

어찌보면 당연한 소리인데... 배열의 각 값이 2바이트면 전체가 2바이트 데이터의 배열 구조인것은 당연한 소리 아니겠는가..

 

여기서 바로 차이점이 나타난다.

Java String의 경우 처음 값이 정해져버리기 때문에, 사이즈 변환에 좀 심한 에로사항이 생기게 된다. C++의 경우에는 (아무튼) 배열 포인터로 구현이 되어있으니, 값을 늘리는것 만큼은 (메모리 침범이 일어나지 않는다는 전제하에) 쉽게 늘릴 수 있으나...

 

그리고 또한, Java String은 기본적으로 'UTF-16' 인코딩을 사용하고 있다.

 

참고중인 Open JDK의 String.java 파일에 보면...

 

A {@code String} represents a string in the UTF-16 format in which supplementary characters are represented by surrogate pairs. 

(see the section Character.html#unicode">Unicode haracter Representations in the {@code Character} class for
more information).

 

(Surrogate pairs 에 대해서는 나중에 따로 다루도록 하겠습니다. 저도 조금 이해할 필요가 있어보이네요)

 

아무튼 UTF-16 포멧으로 표현된다고 나타나있다.

UTF-8과 다른점은, 뭐 당연히 표현 비트 수가 8비트 / 16비트라는 차이점이라는것... 정도겠다.

 

 

아래부터는 String str = "Hello World";  라는 String을 선언해둔것으로 가정하고 시작한다.

* 또한, 기본적으로 세부 자료형은 StringUTF16임을 명시해둔다.

1) 접근법

(1) charAt(N);

C++ String의 at과 기능상 동일하다. N의 위치에 존재하는 값을 리턴해주는 메소드로, 내부적으로는 String Data Array(= byte 배열) 과 int N 값을 인자로 받고, 이를 getChar() 메소드에서 탐색하고 리턴해주는 방식이다.

 

그래서 getChar()는 어떻게 되어있느냐...

 

    static char getChar(byte[] val, int index) {
        assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
        index <<= 1;
        return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                      ((val[index]   & 0xff) << LO_BYTE_SHIFT));
    }

이런식으로, byte배열에서 index에 맞는 위치의 값을 bit shift를 통해 리턴해주는 모습을 보여준다.

Java에서는 Char형도 2바이트로 표현하니, 바이트 값이 2개가 올라가는것을 볼 수 있다.

 

 

(2)str.indexof(temp);

temp 라는 문자가 어디에 존재하는지 탐색해주는 메소드이다.

내부 구현은, indexof가 호출시, 호출된 String의 구조가 latin1 (잠깐 찾아보니 그냥 아스키코드값하고 동일하다.) 인가, 아닌가로 비교를 실시한다.

만일 latin1 형식이 아닌경우, StringUTF16라는 클래스의 indexof 메소드로 넘어가게되는데, 일반 구조의 경우에는 'getChar()' 를 호출하여, 문자열의 위치를 탐색하고 값을 리턴해주는 방식으로 이루어진다.

그런데 재밌는것이...

 

해당 메소드의 구현 밑바닥까지 가면

    @HotSpotIntrinsicCandidate
    private static int indexOfChar(byte[] value, int ch, int fromIndex, int max) {
        checkBoundsBeginEnd(fromIndex, max, value);
        return indexOfCharUnsafe(value, ch, fromIndex, max);
    }

    private static int indexOfCharUnsafe(byte[] value, int ch, int fromIndex, int max) {
        for (int i = fromIndex; i < max; i++) {
            if (getChar(value, i) == ch) {
                return i;
            }
        }
        return -1;
    }

'Unsafe' 라는 문구가 붙어있는것을 확인 할 수 있다.

범위 침범을 할 수 있어서 그런건지, 아니면 다른 이유가 있는건지는 모르겠지만 구현시에 '사실 좀 위험하긴한데...' 라는 생각을 했던건 아닌가 싶다.

 

2) 길이 파악

length(); 라는 메소드가 존재한다.

 

근데 구현이... 내가 잘못본게 아니라면

    public static int length(byte[] value) {
        return value.length >> 1;
    }

이게 끝이다.

배열값을 input으로 받고, 배열의 길이를 1 shift (뒤의 '\0' 문자 제거용으로 추정)하여 리턴해준다.

 

더 찾아봐도 저거 외에는 제대로 된 메소드를 찾을수가 없었다...

 

 

3) 비교

Java String에서는 '==' 를 쓰면 안된다는 사실을 알것이다.

==를 사용하게 되면, 값 자체를 비교하는것이 아니라 값의 위치, "객체"가 동일한지 아닌지를 비교하기 때문에 무조건 false가 날 수 밖에 없다.

그래서 사용하는것이 equals인데...

    public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            int len = value.length >> 1;
            for (int i = 0; i < len; i++) {
                if (getChar(value, i) != getChar(other, i)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

구조는 다음과 같다.

1)) 길이를 비교한다.

2)) 길이가 같다면 -> 동일한 위치의 문자를 1개씩 비교한다

3)) 이 중 하나라도 틀린다면 -> false

4)) 모든 비교를 통과했다면 true 

 

 

이런 식으로 구현이 되어있다.

 

아직 주제인 'C++ String 과 Java String의 차이' 에 대해서는 언급을 하지 않았는데, 이는 다음장에서 언급하도록 하겠다.

모 회사 면접 준비중에 예전에 당했던 문제가 생각나서 정리하려다, 문득 제대로 조사해본적이 없는거같아 기록용으로 작성한다.

사실, 검색해보다가 내가 시원하게 이해할만한 자료가 없었던것도 있고...

 

C에서 Char 형 배열 / 포인터로 겨우겨우 만들던 String에서 벗어나, 관리 함수등을 추가한 C++ String Class로 되면서, '어느정도는' 타 언어의 String 사용법과 비슷하게 이루어지기 시작했다. 물론, 문자열 다루는거는 Java 보다는 불편하긴한데, 이전 면접에서 '왜 불편한가요?' 라는 질문에 좀 어영부영 대답한 감이 없잖아 있는것같다.

 

따라서 포인터 개념이 살아있는 OOP인 C++과, 전부 GC로 관리되는 Java와 비교를 해보고자 한다.

 

 

1. C++ String의 경우 (wstring은 포함시키지 않음)

기본적으로 C++ String은 'Basic String' 이라는 자체 클래스를 내부에 구현한 상태로 되어있으며, Basic String의 생성자는 크게 'const Char *' 를 input으로 받는 경우, 자체 Allocator를 input으로 받는 경우(= 타 value_Type 배열포인터를 input으로 받는 경우), basic_string 참조값을 input으로 받는 경우로 나뉘어진다.

즉, 실제 구현에서는 '일단 모든 데이터형을 받을수 있도록' 구현이 되어있긴 하나, 문자열 자체를 받는경우는 여전히 Char 형 배열 포인터를 값으로 받는다... 고 설명할 수 있다.

그리고, String Class가 되면서, 각종 접근법 / 용량 체크 / 복사 / equal 등의 함수가 가능해졌다.

 

 

아래부터는 String str = "Hello World";  라는 String을 선언해둔것으로 가정하고 시작한다.

1) 접근법

str.at(N) = N번 자리의 문자열을 리턴해준다.

str.at(1) 을 사용시, 1번째 값인 e를 리턴해주게 된다.

 

str[N] = N번 자리의 문자열을 리턴해준다.

str.at(N)과 다른게 뭐냐고 한다면, 둘다 결국엔 str[N]으로 귀결되는건 동일하나,

basic_string<_CharT, _Traits, _Allocator>::at(size_type __n) const
{
    if (__n >= size())
        this->__throw_out_of_range();
    return (*this)[__n];
}

at()의 경우에는 잘못된 값 삽입 시 out of range() 경고를 리턴해주는 차이가 있다.

속도 자체는 자체 함수로 이동하지 않는 str[N]이 빠르긴 하나, 내가 짠 코드를 100% 믿을 수 있는게 아니라면 at()을 쓰는것을 추천한다...

 

 

2) 길이 파악

size() 함수와 length() 함수가 존재하는데...

 

    _LIBCPP_INLINE_VISIBILITY size_type size() const _NOEXCEPT
        {return __is_long() ? __get_long_size() : __get_short_size();}
    
    _LIBCPP_INLINE_VISIBILITY size_type length() const _NOEXCEPT {return size();}
    

그냥 length 함수 호출해도, size를 호출해도 결국엔 size() 함수가 호출되는거니까 아무거나 써도 된다 (...)

 

 

3) 비교

str.compare() 이라는 함수가 있다.

str.compare("ABC") 와 같은 방식으로 사용이 가능한데...

 

일반적으로, '같으면 0', '함수를 호출한 String이 사전순으로 빠를때 -1', '사전순으로 느릴때 1' 의 값을 리턴하는것으로 알고 있을것이다.

 

이는 또 재밌게도, 

basic_string<_CharT, _Traits, _Allocator>::compare(const _Tp& __t) const
{
    __self_view __sv = __t;
    size_t __lhs_sz = size();
    size_t __rhs_sz = __sv.size();
    int __result = traits_type::compare(data(), __sv.data(),
                                        _VSTD::min(__lhs_sz, __rhs_sz));
    if (__result != 0)
        return __result;
    if (__lhs_sz < __rhs_sz)
        return -1;
    if (__lhs_sz > __rhs_sz)
        return 1;
    return 0;
}

'일단' 두 String을 가져와 min 비교를 걸친 후 (String 별 호출순서 비교), 만일 min() 비교가 동일한것으로 나오면, 길이 비교를 진행, 이후 해당 값을 리턴해주는 식으로 진행된다.

즉, 만일 str.compare("Hello"); 를 넣어줬다면

Hello World / Hello 두 문자열의 비교 자체는 4번째 값인 o까지 비교를 하고, 그 이상은 비교가 불가능하니 if문으로 넘어가게 된다.

근데 여기서 좌측값 (= 함수를 호출한 String)의 길이가 더 길기때문에, 1을 리턴해주는것으로 결과가 종료되는것이다.

 

Min의 내부 구현은 (아스키코드 값의 경우) A값과 B값의 코드 순서로 비교를 수행하게된다.

자세한건 검색을 통해 찾아보시길...

 

 

 

아무튼, 요런 식으로 구현이 되어있는데, 위에 언급했다시피, '배열 포인터' 로 구현이 되어있기에, 문자열을 수정하면 해당 문자열 메모리 자체를 수정하는식으로 값을 수정하게 된다.

이것이 메모리 관리  관점에서 Java String과의 가장 큰 차이점이라고 볼 수 있기도 하다.

 

나머지는 다음장에... 생각보다 길어질거같아서 쪼개야할것같다.

 

 

 

더보기

https://code.woboq.org/llvm/libcxx/include/string.html

 

string source code [libcxx/include/string] - Woboq Code Browser

 

code.woboq.org

참고자료.

C++ 내부 구현을 기록해 둔 레퍼런스 사이트.

해당 글을 작성할때 참고한 페이지를 링크해두었다.

 

초보자가 볼 만한 사이트는 아니므로, 레퍼런스 자료가 필요하면 https://en.cppreference.com/를 참고하거나, 검색을 통해 한글로 설명된 다른 페이지를 참고하는걸 추천드린다..

 

(기억을 더듬어가며 쓰는거라 설명이 정확하지 않을 수 있습니다. 참고자료들을 봐주세요.)

 

오랫만에 잡설 하나 적어둔다.

 

float 등 부동소수점 변수 연산을 하다보면, 처음에는 실수로 var1 == var2를 했다가 틀려먹는 경우가 잦아, 이를 확인해보면 소수점 저 밑 끝자리에 내가 적지도 않은 이상한 값이 묻어있는 경우가 종종 보이곤 한다.

 

해서, 검색하다보면 비교시에 (var == 0.0f) 가 아니라 (* 이게 되는 언어도 있긴하다) 다른 방법을 쓰라고 하는 경우가 많고, 이 어처구니없는 상태를  해결하고자 찾다보면 'epsilon' 이라는 변수/함수 등이 보이게 된다.

Epsilon Delta가 필요한 이유 . JS

 

Epsilon Delta, Epsilon은 일반적인 사용으로는 '수치 범위' 를 지정하기 위한 예약변수로 쓰기도 하는데,

 

보통은 ( var < Epsilon && var > -Epsilon )  와 같은 형식으로 비교하는 모습으로 쓰곤 한다.

 

이는, 부동소수점 연산의 특이사항때문에 사용하는것으로,

 

각 부동소수점 표현 포멧에 따라 '안전이 보장되는' 길이가 달라지는것을 인지하고, 이에 피해를 입지 않고자 (해당 증상을 '막는것'은 거의 불가능하다. 그럼 정밀도를 올려야하는데... 정밀도가 올라가면 연산 속도가 주르륵 떨어진다...) 추가하는 일종의 '상대 오차 반올림 값' (relative error unit roundoff, 일반적으로 쓰는 표현은 아닙니다.) 으로, 값을 보장할 수 없는 부분에서 반올림 등으로 근사값 처리를 해주기 위함입니다.

 

보통은 이를 프로그래머가 직접 만들기도 하지만 (만일 소수점 3번째 자리까지만 쓴다 => 4번째 자리나 5번째 자리에서 반올림 하겠다 등등?) 언어에 따라 이미 해당 값을 리턴해주는 함수가 존재하기도 합니다.

 

C++의 경우, limits 헤더의 numeric_limits 템플릿 클래스 안에 epsilon() 이라는 함수로 존재하며, 목적에 맞게 '거의 비슷한' 값을 비교 시에 사용하는 예시로 올라와 있습니다.

 

 

따라서...

 

이 값들을 이용함으로써 부동소수점 변수들에 대해 의미없는 오차 및 발생할 수 있는 문제들을 미연에 방지할 수 있게 된다... 는 점이 주요 포인트.

 

보통 부동소수점 연산이 많은 프로그램들에 경우에는, eplison delta 값을 이용하여 자체적으로 값을 비교해줄 수 있는 함수를 만들어 쓰곤 하는 편이다.

 

그게, 매번 ( var < Epsilon && var > -Epsilon ) 처럼 지저분하게 쓰는것보다는 좀 더 안전하고, '깔끔하게' 보일 수 있으니까. 

 

 

 

 

 

 

 

 

* 참고자료

1) https://en.wikipedia.org/wiki/(%CE%B5,_%CE%B4)-definition_of_limit

2) https://en.wikipedia.org/wiki/Machine_epsilon

3) https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon

4) https://gigglehd.com/gg/hard/5427559

+ Recent posts