반응형

 c++ STL에서 fill을 어떻게 쓰는지 예제로 알아봅시다. 이것은 공식 문서에 따르면, forward 이터레이터 2개가 필요합니다. 그리고 해당 이터레이터는 이 문서에 설명이 자세히 되어 있습니다. 그런데 보통, 저는 배열이나 벡터 등에서 많이 썼습니다. 그 외 다른 경우에는 쓴 적이 없었습니다. 그러니, 이 자료구조에서 어떻게 fill 함수를 쓰는지를 중점적으로 설명하겠습니다.

 

 


 예제 1번입니다.

 

 

 int 벡터 배열 v가 선언되어 있습니다. 여기에 들어있는 내용 전체를 0으로 초기화 하려고 합니다. 16번째 줄에 보면, v.begin() 부터, v.end()까지를 범위로 주었습니다. 이는 v의 시작과, 끝을 의미합니다. 이것을 어떠한 값으로 초기화를 시킬 건데요. 3번째 인자에 0이 들어갔음을 볼 수 있습니다. v.end()는 마지막 원소가 있는 위치 다음을 의미합니다. 즉, 시작, 끝, 어떤 값으로 초기화 할 지를 주면 됩니다. v.begin(), v.end() 등은 이디엄처럼 외워두시면 좋습니다.

 

 

 벡터의 처음 위치부터 끝 위치까지 모두 0으로 초기화가 되었습니다. 그러면, 범위 초기화도 가능할까요?

 

 

 위와 같이 v.begin() + 3을 1번째 인자로, v.begin() + 10을 2번째 인자로 두게 되면 어떻게 초기화가 되나 봅시다.

 

 

 3번째 요소부터, 9번째 요소까지 초기화가 됨을 알 수 있어요. 대신에 시작 점으로부터 10칸만큼 떨어져 있는 10번째 위치는 0으로 초기화가 안 되었음을 알 수 있습니다. 즉, fill(s, e, v); 는 s는 포함하고 e는 포함하지 않는 구간을 1로 초기화 하라. 정도로 해석하면 됨을 알 수 있습니다.

 

 


 그런데, c에서도 memset 같은 것이 있지 않았나요? 네. 있었습니다. 그런데, 제가 이전에 썼던 이 글에서 이런 언급을 한 번 했을 겁니다. 이 함수는 바이트 단위로 초기화를 하는 거라, 1이나 2와 같은 것으로 memset를 하는 것이 불가능하다. 그런데, fill은 가능합니다. 채우는 logic이 byte 단위가 아니기 때문입니다.

 

 

 위 예제 프로그램은 vector v의 3번째 요소부터 9번째 요소까지 1이라는 값으로 채웁니다.

 

 

 그러면 놀랍게도 3번째부터 9번째까지 1로 채워졌음을 볼 수 있습니다. 그러면, 우리가 custom하게 생성한 구조체라던지, 벡터의 요소가 필드 하나만 있는 게 아닌 경우에는 어떻게 해야 할까요? 예를 들자면, ps에서 쓰는 빈도가 높은 pair는, first와 second, 이렇게 2개를 가지고 있습니다.

 

 

 이렇게 채우려고 하면 될까요? 한 번 컴파일을 해 봅시다.

 

 

 안 된다는 것을 알 수 있습니다. 엄청나게 긴 에러 메세지를 잘 읽다 보면, mismatched 어쩌고가 나왔음을 알 수 있는데요. pair<int, int>하고, int하고는 맞지 않습니다. 그러면 어떻게 해야 할까요? 간단합니다. 3번째 부터 9번째 요소를 pair (1, 1)로 초기화를 하고 싶다면, 아래 코드와 같이 하시면 됩니다.

 

 

 16번째 줄에서 fill 함수의 3번째 인자에 pii(0, 0)을 넣었음을 주목하시면 됩니다. 그러면, 이렇게 넣어진 결과는 얕은 복사가 되었을까요? 만약에 pair가 그냥 포인터만 복사가 되었다면, v[5].first에 111을 넣은 경우에, v[6]의 first에도, v[7]의 first에도 111이 들어갔을 겁니다.

 

 

 그런데 111이 1번만 나타났습니다. 이는 pair가 fill 메서드에서 대입이 될 때, 참조가 아니라, 실제 덩어리가 들어갔다는 의미입니다. 이걸 더 자세히 이해하시려면, 해당 문서의 possible implementation을 보시면 됩니다. 그리고, pair가 =으로 들어가는 방식은 이 문서를 보시면 될 듯 싶네요.

 

 이 글에서 짚고 넘어가셔야 할 것은, pair를 초기화 할 때, pair(1, 1), pair(2, 2) 등으로 초기화 할 값과, 들어 있는 원소의 형을 맞춰 주었다는 것입니다.

 

 


 배열을 초기화 할 때도 이용할 수 있습니다.

 

 

 7번째 줄에, 1번째 인자를 &(dp[0][0])으로, 2번째 인자를 &(dp[0][0]) + 20*10으로, 3번째 인자를 1로 주었는데요. 제가 선언한 dp 배열의 시작 위치는 dp[0][0]의 주소값이고, 끝나는 위치는 &(dp[0][0])에서 20 * 10 * sizeof(int) 만큼을 더한 것이기 때문입니다. 포인터의 연산을 이해하시면 아실 수 있는 부분입니다.

 

 

 실행 결과는 위와 같습니다. 그런데, 이것은 2차원 배열이 연속이 되어 있는 경우에만 쓸 수 있습니다. 그렇지 않은 경우에는 초기화 하는 방법이 다릅니다.

 

 

 이 경우에는 int 포인터를 넣어놓은 1차원 배열을 선언하였습니다. 그리고, 각각의 요소는 길이가 20인 int 배열을 가리킵니다. 언뜻 보면, 그냥 dp[20][20]이나, dp[20][10]과 같아 보입니다만, 다릅니다. 전자는 포인터들을 저장해 놓은 것이고, 8번째 줄에 malloc의 결과가 어떻게 나올지도 사실은 모릅니다.

 

 그러므로, 아까와 같이 &(dp[0][0]) 부터, &(dp[0][0]) + 20*10 까지 1로 초기화 하겠다고 하면 안 됩니다.

 

 

 세그 폴트가 장렬하게 뜸을 알 수 있습니다.

 

 

 대신에, for loop를 돌면서 1차원 배열을 0으로 초기화를 시키면 생각하시면 됩니다.

 

 

 잘 돌아가는 듯 보이네요. 이 정도만 아셔도 c++의 fill 정도는 쉽게 쓸 수 있을 거라 생각합니다.

 

반응형

댓글을 달아 주세요

  1. 비키라

    좋은 글 잘 읽었습니다.다차원 배열에서 fill 함수를 더 쉽게 쓰는 방법이 있어 제안하고 갑니다.
    int dp[n][m][k];
    배열 전체를 T으로 초기화 하고 싶은 경우
    fill(dp[0][0], dp[n][0],T) 과 같이 사용하면 됩니다.

    일반적으로 다차원 배열에서의 fill 사용은 반복문을 사용해야 한다고 알려져 있는데 , 위처럼 처음 포인터와 끝 포인터를 설정하므로서 간편하게 사용 가능합니다.