수색…


소개

벡터는 자동으로 처리되는 저장 공간이있는 동적 배열입니다. 벡터의 요소는 배열의 요소만큼 효율적으로 액세스 할 수 있으며 벡터의 크기가 동적으로 변경 될 수 있다는 장점이 있습니다.

저장소 측면에서 벡터 데이터는 일반적으로 동적으로 할당 된 메모리에 배치되므로 약간의 오버 헤드가 필요합니다. 반대로 C-arraysstd::array 는 선언 된 위치에 상대적인 자동 저장소를 사용하므로 오버 헤드가 없습니다.

비고

std::vector 하려면 #include <vector> 사용하여 <vector> 헤더를 포함시켜야합니다.

std::vector 요소는 무료 저장소에 연속적으로 저장됩니다. 벡터가 std::vector<std::vector<int> > 에서와 같이 중첩 될 때 각 벡터의 요소는 연속적이지만 각 벡터는 기본 저장소를 자유 저장소에 할당합니다.

std :: vector 초기화하기

std::vector 는 선언하는 동안 여러 가지 방법으로 초기화 될 수 있습니다.

C ++ 11
std::vector<int> v{ 1, 2, 3 };  // v becomes {1, 2, 3}

// Different from std::vector<int> v(3, 6)
std::vector<int> v{ 3, 6 };     // v becomes {3, 6}
// Different from std::vector<int> v{3, 6} in C++11
std::vector<int> v(3, 6);  // v becomes {6, 6, 6}

std::vector<int> v(4);     // v becomes {0, 0, 0, 0}

벡터는 여러 가지 방법으로 다른 컨테이너에서 초기화 할 수 있습니다.

v2 데이터를 복사하는 복사 (다른 벡터에서만) :

std::vector<int> v(v2);
std::vector<int> v = v2;
C ++ 11

v2 에서 데이터를 이동하는 구조를 다른 벡터에서만 이동합니다.

std::vector<int> v(std::move(v2));
std::vector<int> v = std::move(v2);

요소를 v 복사하는 Iterator (범위) 복사 생성 :

// from another vector
std::vector<int> v(v2.begin(), v2.begin() + 3); // v becomes {v2[0], v2[1], v2[2]}

// from an array
int z[] = { 1, 2, 3, 4 };
std::vector<int> v(z, z + 3);                   // v becomes {1, 2, 3}

// from a list
std::list<int> list1{ 1, 2, 3 };
std::vector<int> v(list1.begin(), list1.end()); // v becomes {1, 2, 3}
C ++ 11

요소를 v 로 이동시키는 std::make_move_iterator 사용하는 Iterator move-construction :

// from another vector
std::vector<int> v(std::make_move_iterator(v2.begin()),
                   std::make_move_iterator(v2.end());

// from a list
std::list<int> list1{ 1, 2, 3 };
std::vector<int> v(std::make_move_iterator(list1.begin()),
                   std::make_move_iterator(list1.end()));

assign() 멤버 함수의 도움으로 std::vector 를 생성 한 후에 다시 초기화 할 수 있습니다.

v.assign(4, 100);                      // v becomes {100, 100, 100, 100}

v.assign(v2.begin(), v2.begin() + 3);  // v becomes {v2[0], v2[1], v2[2]}

int z[] = { 1, 2, 3, 4 };
v.assign(z + 1, z + 4);                // v becomes {2, 3, 4}

요소 삽입하기

벡터의 끝에 요소 추가 (복사 / 이동) :

struct Point {
  double x, y;
  Point(double x, double y) : x(x), y(y) {}
};
std::vector<Point> v;
Point p(10.0, 2.0);
v.push_back(p);  // p is copied into the vector.
C ++ 11

요소를 적절하게 구성하여 벡터 끝에 요소를 추가하는 방법은 다음과 같습니다.

std::vector<Point> v;
v.emplace_back(10.0, 2.0); // The arguments are passed to the constructor of the
                           // given type (here Point). The object is constructed
                           // in the vector, avoiding a copy.

성능상의 이유로 인해 std::vector 에는 push_front() 멤버 함수가 없습니다 . 처음에 요소를 추가하면 벡터의 기존 요소가 모두 이동합니다. 컨테이너의 처음에 요소를 자주 삽입하려면 std::list 또는 std::deque 대신 사용할 수 있습니다.


벡터의 임의 위치에 요소 삽입 :

std::vector<int> v{ 1, 2, 3 };
v.insert(v.begin(), 9);          // v now contains {9, 1, 2, 3}
C ++ 11

요소를 적절하게 구성하여 벡터의 임의 위치에 요소 삽입 :

std::vector<int> v{ 1, 2, 3 };
v.emplace(v.begin()+1, 9);     // v now contains {1, 9, 2, 3}

벡터의 다른 위치에 다른 벡터 삽입 :

std::vector<int> v(4);      // contains: 0, 0, 0, 0
std::vector<int> v2(2, 10); // contains: 10, 10
v.insert(v.begin()+2, v2.begin(), v2.end()); // contains: 0, 0, 10, 10, 0, 0

벡터의 임의 위치에 배열 삽입 :

std::vector<int> v(4); // contains: 0, 0, 0, 0
int a [] = {1, 2, 3}; // contains: 1, 2, 3
v.insert(v.begin()+1, a, a+sizeof(a)/sizeof(a[0])); // contains: 0, 1, 2, 3, 0, 0, 0

다중 재 할당을 피하기 위해 결과 벡터 크기가 미리 알려 지려면 여러 요소를 삽입하기 전에 reserve() 를 사용하십시오 ( 벡터 크기 및 용량 참조).

std::vector<int> v;
v.reserve(100);
for(int i = 0; i < 100; ++i)
    v.emplace_back(i);

이 경우 resize() 를 호출하는 실수를 저 지르지 마십시오. 그렇지 않으면 실수로 200 개의 요소가있는 벡터가 만들어지며 후자의 백만이 의도 한 값을 갖게됩니다.

이상 반복 :: vector

여러 가지 방법으로 std::vector 를 반복 할 수 있습니다. 다음 각 절에서 v 는 다음과 같이 정의됩니다.

std::vector<int> v;

앞으로의 방향 반복

C ++ 11
// Range based for
for(const auto& value: v) {
    std::cout << value << "\n";
}

// Using a for loop with iterator
for(auto it = std::begin(v); it != std::end(v); ++it) {
    std::cout << *it << "\n";
}

// Using for_each algorithm, using a function or functor:
void fun(int const& value) {
    std::cout << value << "\n";
}

std::for_each(std::begin(v), std::end(v), fun);

// Using for_each algorithm. Using a lambda:
std::for_each(std::begin(v), std::end(v), [](int const& value) {
    std::cout << value << "\n";
});
C ++ 11
// Using a for loop with iterator
for(std::vector<int>::iterator it = std::begin(v); it != std::end(v); ++it) {
    std::cout << *it << "\n";
}
// Using a for loop with index
for(std::size_t i = 0; i < v.size(); ++i) {
    std::cout << v[i] << "\n";
}

역방향 반복

C ++ 14
// There is no standard way to use range based for for this.
// See below for alternatives.

// Using for_each algorithm
// Note: Using a lambda for clarity. But a function or functor will work
std::for_each(std::rbegin(v), std::rend(v), [](auto const& value) {
    std::cout << value << "\n";
});

// Using a for loop with iterator
for(auto rit = std::rbegin(v); rit != std::rend(v); ++rit) {
    std::cout << *rit << "\n";
}
// Using a for loop with index
for(std::size_t i = 0; i < v.size(); ++i) {
    std::cout << v[v.size() - 1 - i] << "\n";
}

iterate를 역전하기 위해 기반으로하는 범위를 사용하는 기본 제공 방법은 없지만; 이것을 고치는 것은 비교적 간단합니다. 범위 기반은 begin()end() 를 사용하여 반복자를 얻고이를 래퍼 객체로 시뮬레이션하면 필요한 결과를 얻을 수 있습니다.

C ++ 14
template<class C>
struct ReverseRange {
  C c; // could be a reference or a copy, if the original was a temporary
  ReverseRange(C&& cin): c(std::forward<C>(cin)) {}
  ReverseRange(ReverseRange&&)=default;
  ReverseRange& operator=(ReverseRange&&)=delete;
  auto begin() const {return std::rbegin(c);}
  auto end()   const {return std::rend(c);}
};
// C is meant to be deduced, and perfect forwarded into
template<class C>
ReverseRange<C> make_ReverseRange(C&& c) {return {std::forward<C>(c)};}

int main() {
    std::vector<int> v { 1,2,3,4};
    for(auto const& value: make_ReverseRange(v)) {
        std::cout << value << "\n";
    }
}

const 요소 적용하기

C ++ 11 이후 cbegin()cend() 메서드는 벡터가 비 const 인 경우에도 벡터에 대한 상수 반복자 를 얻을 수있게합니다. 상수 반복자를 사용하면 const 내용을 적용하는 데 유용한 벡터 내용을 읽을 수는 있지만 수정할 수는 없습니다.

C ++ 11
// forward iteration
for (auto pos = v.cbegin(); pos != v.cend(); ++pos) {
   // type of pos is vector<T>::const_iterator
   // *pos = 5; // Compile error - can't write via const iterator
}

// reverse iteration
for (auto pos = v.crbegin(); pos != v.crend(); ++pos) {
   // type of pos is vector<T>::const_iterator
   // *pos = 5; // Compile error - can't write via const iterator
}

// expects Functor::operand()(T&) 
for_each(v.begin(), v.end(), Functor());

// expects Functor::operand()(const T&)
for_each(v.cbegin(), v.cend(), Functor())
C ++ 17

as_const는 이를 범위 반복으로 확장합니다.

for (auto const& e : std::as_const(v)) {
  std::cout << e << '\n';
}

이것은 이전 버전의 C ++에서 구현하기 쉽습니다.

C ++ 14
template <class T>
constexpr std::add_const_t<T>& as_const(T& t) noexcept {
  return t;
}

효율성에 대한 참고 사항

클래스 std::vector 는 기본적으로 동적으로 할당 된 연속 배열을 관리하는 클래스이기 때문에 여기에서 설명하는 것과 동일한 원리가 C ++ 벡터에 적용됩니다. 행의 주요 순서 원리를 따르면 벡터의 내용을 색인으로 액세스하는 것이 훨씬 효율적입니다. 물론 벡터에 액세스 할 때마다 관리 내용도 캐시에 저장되지만 여러 번 ( 여기여기에서 ) 논쟁이 있었기 때문에 원시 배열과 비교하여 std::vector 대한 반복 성능 차이가 발생했습니다 무시할 만하다. 따라서 C의 원시 배열에 대한 효율성과 동일한 원리가 C ++의 std::vector 에도 적용됩니다.

요소에 접근하기

std::vector 에서 요소에 액세스하는 두 가지 기본 방법이 있습니다.

인덱스 기반 액세스 :

이것은 subscript 연산자 [] 또는 member 함수 at() 를 사용하여 수행 할 수 있습니다.

둘 다 std::vector 의 각 위치에있는 요소에 대한 참조를 반환하지만 ( vector<bool> 이 아닌 경우) 벡터가 const 가 아닌 경우 수정 된 것처럼 읽을 수 있습니다.

[]at() 차이점은 []at() 가 수행하는 동안 경계 검사를 수행하지 않는다는 것입니다. index < 0 또는 index >= size 요소에 액세스하면 [] 대한 정의되지 않은 동작 이며, at()std::out_of_range 예외를 throw합니다.

참고 : 아래 예제에서는 명확성을 위해 C ++ 11 스타일 초기화를 사용하지만 연산자는 모든 버전에서 사용할 수 있습니다 (C ++ 11로 표시된 경우 제외).

C ++ 11
std::vector<int> v{ 1, 2, 3 };
// using []
int a = v[1];    // a is 2
v[1] = 4;        // v now contains { 1, 4, 3 }

// using at()
int b = v.at(2); // b is 3
v.at(2) = 5;     // v now contains { 1, 4, 5 }
int c = v.at(3); // throws std::out_of_range exception

at() 메서드는 경계 검사를 수행하고 예외를 throw 할 수 있기 때문에 [] 보다 느립니다. 이것은 작업의 의미에 따라 인덱스가 경계에 있음을 보장하는 [] 우선 코드를 만듭니다. 어떤 경우에도 벡터 요소에 대한 액세스는 일정 시간 내에 수행됩니다. 즉, 벡터의 첫 번째 요소에 액세스하는 것은 두 번째 요소, 세 번째 요소 등을 액세스하는 비용과 동일합니다.

예를 들어,이 루프를 생각해보십시오.

for (std::size_t i = 0; i < v.size(); ++i) {
    v[i] = 1;
}

여기에서 우리는 인덱스 변수 것을 알고 i 경계 항상, 그래서 그것을 확인하기 위해 CPU 사이클을 낭비하는 것 i 모든 호출에 대한 경계에있는 operator[] .

front()back() 멤버 함수를 사용하면 각각 벡터의 첫 번째 요소와 마지막 요소에 쉽게 참조 할 수 있습니다. 이러한 위치는 자주 사용되며 특수 접근자는 [] 사용하는 대체 방법보다 읽기 쉽습니다.

std::vector<int> v{ 4, 5, 6 }; // In pre-C++11 this is more verbose

int a = v.front();   // a is 4, v.front() is equivalent to v[0]
v.front() = 3;       // v now contains {3, 5, 6}
int b = v.back();    // b is 6, v.back() is equivalent to v[v.size() - 1]
v.back() = 7;        // v now contains {3, 5, 7}

참고 : 빈 벡터에서 front() 또는 back() 을 호출하는 것은 정의되지 않은 동작 입니다. front() 또는 back() 호출하기 전에 empty() 멤버 함수 (컨테이너가 비어 있는지 확인 empty() 사용하여 컨테이너가 비어 있지 않은지 확인해야합니다. 빈 벡터를 테스트하기 위해 'empty ()'를 사용하는 간단한 예제는 다음과 같습니다.

int main ()
{
  std::vector<int> v;
  int sum (0);

  for (int i=1;i<=10;i++) v.push_back(i);//create and initialize the vector

  while (!v.empty())//loop through until the vector tests to be empty
  {
     sum += v.back();//keep a running total
     v.pop_back();//pop out the element which removes it from the vector
  }

  std::cout << "total: " << sum << '\n';//output the total to the user

  return 0;
}

위의 예제는 1부터 10까지의 일련 번호를 가진 벡터를 만듭니다. 그런 다음 벡터가 비어있을 때까지 ( 'empty ()'를 사용하여) 정의되지 않은 동작을 방지하기 위해 벡터 요소를 팝합니다. 그런 다음 벡터의 숫자 합계가 계산되어 사용자에게 표시됩니다.

C ++ 11

data() 메서드는 std::vector 가 내부 메모리에 요소를 저장하는 데 사용하는 원시 메모리에 대한 포인터를 반환합니다. 이것은 C 스타일 배열이 필요한 레거시 코드에 벡터 데이터를 전달할 때 가장 자주 사용됩니다.

std::vector<int> v{ 1, 2, 3, 4 }; // v contains {1, 2, 3, 4}
int* p = v.data(); // p points to 1
*p = 4;            // v now contains {4, 2, 3, 4}
++p;               // p points to 2
*p = 3;            // v now contains {4, 3, 3, 4}
p[1] = 2;          // v now contains {4, 3, 2, 4}
*(p + 2) = 1;      // v now contains {4, 3, 2, 1}
C ++ 11

C ++ 11 이전에, data() 메소드는 front() 를 호출하고 반환 된 값의 주소를 취함으로써 시뮬레이션 될 수 있습니다 :

std::vector<int> v(4);
int* ptr = &(v.front()); // or &v[0]

벡터의 내용이 단항 operator& 오버라이드하지 않는다고 가정 할 때 벡터는 항상 인접한 메모리 위치에 요소를 저장하기 때문에 벡터가 작동합니다. 그렇다면 pre-C ++ 11에서 std::addressof 를 다시 구현해야합니다. 또한 벡터가 비어 있지 않다고 가정합니다.

반복자 :

반복자는 " std::vector 반복"예제와 반복자 예제에서보다 자세히 설명합니다. 즉, 벡터의 요소에 대한 포인터와 유사하게 동작합니다.

C ++ 11
std::vector<int> v{ 4, 5, 6 };

auto it = v.begin();
int i = *it;        // i is 4
++it; 
i = *it;            // i is 5
*it = 6;            // v contains { 4, 6, 6 }
auto e = v.end();   // e points to the element after the end of v. It can be 
                    // used to check whether an iterator reached the end of the vector:
++it; 
it == v.end();      // false, it points to the element at position 2 (with value 6)
++it;
it == v.end();      // true

그 표준과 일치하는 std::vector<T> 의 반복자가 실제로 T* 들,하지만 대부분의 표준 라이브러리는이 작업을 수행하지 않습니다. 이 작업을 수행하지 않으면 오류 메시지가 개선되고, 이식성이없는 코드를 포착하며, 릴리스되지 않은 빌드에서 디버거 검사를 통해 반복기를 계측하는 데 사용할 수 있습니다. 그런 다음 릴리스 빌드에서 기본 포인터를 중심으로 배치되는 클래스가 최적화됩니다.


간접 액세스를 위해 벡터의 요소에 대한 참조 또는 포인터를 유지할 수 있습니다. 의 요소에 이러한 참조 또는 포인터 vector 안정적으로 유지하고 추가 /이나의 요소 앞에 요소를 제거하지 않는 한 액세스 정의를 유지 vector , 또는 당신이 원인이 vector 용량을 변경할 수 있습니다. 이터레이터를 무효화하는 규칙과 동일합니다.

C ++ 11
std::vector<int> v{ 1, 2, 3 };
int* p = v.data() + 1;     // p points to 2
v.insert(v.begin(), 0);    // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1;          // p points to 1
v.reserve(10);             // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1;          // p points to 1
v.erase(v.begin());        // p is now invalid, accessing *p is a undefined behavior.

std :: vector를 C 배열로 사용하기

std::vector 를 C 배열로 사용하는 방법에는 여러 가지가 있습니다 (예 : C 라이브러리와의 호환성). 이는 벡터의 요소가 연속적으로 저장되기 때문에 가능합니다.

C ++ 11
std::vector<int> v{ 1, 2, 3 };
int* p = v.data();

이전 C ++ 표준 (아래 참조) 기반의 솔루션과 달리, 멤버 함수 .data() 는 빈 벡터에도 적용될 수 있습니다.이 경우 정의되지 않은 동작이 발생하지 않기 때문입니다.

C ++ 11 이전에는 벡터의 첫 번째 요소의 주소를 가져와 등가 포인터를 얻었습니다. 벡터가 비어 있지 않으면이 두 가지 방법을 서로 바꿔 사용할 수 있습니다.

int* p = &v[0];      // combine subscript operator and 0 literal

int* p = &v.front(); // explicitly reference the first element

참고 : 벡터가 비어있는 경우 v[0]v.front() 는 정의되지 않으므로 사용할 수 없습니다.

벡터 데이터의 기본 주소를 저장할 때 많은 작업 (예 : push_back , resize 등)은 벡터의 데이터 메모리 위치를 변경하여 이전 데이터 포인터무효화 합니다. 예 :

std::vector<int> v;
int* p = v.data();
v.resize(42);      // internal memory location changed; value of p is now invalid

반복자 / 포인터 무효화

std::vector 가리키는 반복자와 포인터는 특정 연산을 수행 할 때만 유효하지 않게 될 수 있습니다. 유효하지 않은 반복자 / 포인터를 사용하면 정의되지 않은 동작이 발생합니다.

이터레이터 / 포인터를 무효화하는 연산에는 다음이 포함됩니다.

  • vectorcapacity 을 변경하는 모든 삽입 작업은 모든 반복자 / 포인터를 무효화 합니다 .

    vector<int> v(5); // Vector has a size of 5; capacity is unknown.
    int *p1 = &v[0];
    v.push_back(2);   // p1 may have been invalidated, since the capacity was unknown.
    
    v.reserve(20);    // Capacity is now at least 20.
    int *p2 = &v[0];
    v.push_back(4);   // p2 is *not* invalidated, since the size of `v` is now 7.
    v.insert(v.end(), 30, 9); // Inserts 30 elements at the end. The size exceeds the
                              // requested capacity of 20, so `p2` is (probably) invalidated.
    int *p3 = &v[0];
    v.reserve(v.capacity() + 20); // Capacity exceeded, thus `p3` is invalid.
    
C ++ 11
auto old_cap = v.capacity();
v.shrink_to_fit();
if(old_cap != v.capacity())
    // Iterators were invalidated.
  • 용량을 늘리지 않는 삽입 작업은 삽입 위치와 그 위치를 지나가는 요소를 가리키는 반복자 / 포인터를 여전히 무효화합니다. 여기에는 end 반복자가 포함됩니다.

    vector<int> v(5);
    v.reserve(20);                 // Capacity is at least 20.
    int *p1 = &v[0];
    int *p2 = &v[3];
    v.insert(v.begin() + 2, 5, 0); // `p2` is invalidated, but since the capacity
                                   // did not change, `p1` remains valid.
    int *p3 = &v[v.size() - 1];
    v.push_back(10); // The capacity did not change, so `p3` and `p1` remain valid.
    
  • 모든 제거 작업은 제거 된 요소를 가리키는 반복자 / 포인터와 제거 된 요소를 지나가는 요소를 무효화합니다. 여기에는 end 반복자가 포함됩니다.

    vector<int> v(10);
    int *p1 = &v[0];
    int *p2 = &v[5];
    v.erase(v.begin() + 3, v.end()); // `p2` is invalid, but `p1` remains valid.
    
  • operator= (복사, 이동 또는 기타) 및 clear() 는 벡터를 가리키는 모든 반복자 / 포인터를 무효화합니다.

요소 삭제하기

마지막 요소 삭제 :

std::vector<int> v{ 1, 2, 3 };
v.pop_back();                           // v becomes {1, 2}

모든 요소 삭제 :

std::vector<int> v{ 1, 2, 3 };
v.clear();                              // v becomes an empty vector

색인별로 요소 삭제 :

std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(v.begin() + 3);                 // v becomes {1, 2, 3, 5, 6}

참고 : 마지막 요소가 아닌 요소를 삭제하는 vector 삭제 된 요소를 넘어서는 모든 요소를 ​​간격을 채우기 위해 복사하거나 이동해야합니다 (아래 참고 및 std :: list 참조) .

범위의 모든 요소 삭제 :

std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(v.begin() + 1, v.begin() + 5);  // v becomes {1, 6}

참고 : 위 방법은 벡터의 용량을 변경하지 않고 크기 만 변경합니다. 벡터 크기 및 용량을 참조하십시오.

요소 범위를 제거하는 erase 메서드는 지우개 제거 관용구의 일부로 사용되는 경우가 많습니다. 즉, 첫 번째 std::remove 는 벡터의 끝으로 일부 요소를 이동 한 다음 잘라낸 부분을 지 erase . 소거 된 세그먼트 이후의 모든 요소는 새로운 위치로 재배치되어야하기 때문에 이것은 벡터의 마지막 인덱스보다 작은 모든 인덱스에 대해 상대적으로 비효율적 인 연산입니다. 컨테이너에서 임의의 요소를 효율적으로 제거해야하는 속도가 중요한 응용 프로그램의 경우 std :: list를 참조하십시오.

값으로 요소 삭제 :

std::vector<int> v{ 1, 1, 2, 2, 3, 3 };
int value_to_remove = 2;
v.erase(std::remove(v.begin(), v.end(), value_to_remove), v.end()); // v becomes {1, 1, 3, 3}

조건별로 요소 삭제 :

// std::remove_if needs a function, that takes a vector element as argument and returns true, 
// if the element shall be removed
bool _predicate(const int& element) {
    return (element > 3); // This will cause all elements to be deleted that are larger than 3
}
...
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(std::remove_if(v.begin(), v.end(), _predicate), v.end()); // v becomes {1, 2, 3}

추가 술어 함수를 작성하지 않고 람다로 요소를 삭제합니다.

C ++ 11
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(std::remove_if(v.begin(), v.end(),
     [](auto& element){return element > 3;} ), v.end()
);

루프에서 조건별로 요소 삭제 :

std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
std::vector<int>::iterator it = v.begin();
while (it != v.end()) {
    if (condition)
        it = v.erase(it); // after erasing, 'it' will be set to the next element in v
    else
        ++it;             // manually set 'it' to the next element in v
}

증가하지 않는 것이 중요하지만 it 삭제의 경우에, 당신은 루프에서 반복적으로 삭제하는 다른 방법을 사용하는 것이 좋습니다. 보다 효율적인 방법으로 remove_if 를 고려하십시오.

부 루프에서 조건별로 요소 삭제 :

std::vector<int> v{ -1, 0, 1, 2, 3, 4, 5, 6 };
typedef std::vector<int>::reverse_iterator rev_itr;
rev_itr it = v.rbegin();

while (it != v.rend()) { // after the loop only '0' will be in v
    int value = *it;
    if (value) {
        ++it;
        // See explanation below for the following line.
        it = rev_itr(v.erase(it.base()));
    } else
        ++it;
}

앞의 루프에 대한 몇 가지 점에 유의하십시오.

  • 역방향 반복자 주어 it 어떤 요소를 가리키고, 상기 방법은 base 동일한 요소에 일정한 (비 반전) 반복자 포인팅을 준다.

  • vector::erase(iterator) 는 반복자가 가리키는 요소를 지우고 주어진 요소를 따라가는 요소에 반복자를 반환한다.

  • reverse_iterator::reverse_iterator(iterator) 는 반복자에서 역 반복자를 생성합니다.

모두 넣어 라인이 it = rev_itr(v.erase(it.base())) 말한다 : 역방향 반복자를 가지고 it , 한 v 요소는 일반 반복자가 가리키는 소거; 결과 iterator를 가져 와서 iterator를 만들고 iterator의 reverse iterator에 할당 it .


v.clear() 사용하여 모든 요소를 ​​삭제해도 메모리가 비어 있지 않습니다 capacity() 벡터의 capacity() 는 변경되지 않습니다). 공간을 재 확보하려면 다음을 사용하십시오.

std::vector<int>().swap(v);
C ++ 11

shrink_to_fit() 는 사용되지 않는 벡터 용량을 해제합니다.

v.shrink_to_fit();

shrink_to_fit 은 실제로 공간을 되 shrink_to_fit 것을 보장하지 않지만, 현재의 대부분의 구현은 그렇게합니다.

std :: vector에서 요소 찾기

<algorithm> 헤더에 정의 된 std::find 함수는 std::vector 에서 요소를 찾는 데 사용할 수 있습니다.

std::findoperator== 사용하여 원소를 비교합니다. 이 값과 비교되는 범위의 첫 번째 요소에 반복기를 반환합니다.

해당 요소가 발견되지 않으면 std::findstd::vector::end (또는 벡터가 const 경우 std::vector::cend 반환합니다.

C ++ 11
static const int arr[] = {5, 4, 3, 2, 1};
std::vector<int> v (arr, arr + sizeof(arr) / sizeof(arr[0]) );

std::vector<int>::iterator it = std::find(v.begin(), v.end(), 4);
std::vector<int>::difference_type index = std::distance(v.begin(), it);
// `it` points to the second element of the vector, `index` is 1

std::vector<int>::iterator missing = std::find(v.begin(), v.end(), 10);
std::vector<int>::difference_type index_missing = std::distance(v.begin(), missing);
// `missing` is v.end(), `index_missing` is 5 (ie. size of the vector)
C ++ 11
std::vector<int> v { 5, 4, 3, 2, 1 };

auto it = std::find(v.begin(), v.end(), 4);
auto index = std::distance(v.begin(), it);
// `it` points to the second element of the vector, `index` is 1

auto missing = std::find(v.begin(), v.end(), 10);
auto index_missing = std::distance(v.begin(), missing);
// `missing` is v.end(), `index_missing` is 5 (ie. size of the vector)

큰 벡터에서 많은 검색을 수행해야하는 경우 binary_search 알고리즘을 사용하기 전에 먼저 벡터를 정렬하는 것이 binary_search .


조건을 만족하는 벡터의 첫 번째 요소를 찾으려면 std::find_if 사용할 수 있습니다. std::find 주어진 두 개의 매개 변수 외에도 std::find_if 는 세 번째 인수 std::find_if 이 인수는 술어 함수에 대한 함수 오브젝트 또는 함수 포인터입니다. 술어는 컨테이너의 요소를 인수로 받아 들여 컨테이너를 수정하지 않고 bool 로 변환 가능한 값을 반환해야합니다.

C ++ 11
bool isEven(int val) {
    return (val % 2 == 0); 
}

struct moreThan {
    moreThan(int limit) : _limit(limit) {}
    
    bool operator()(int val) {
        return val > _limit;
    }
    
    int _limit;
};

static const int arr[] = {1, 3, 7, 8};
std::vector<int> v (arr, arr + sizeof(arr) / sizeof(arr[0]) );
    
std::vector<int>::iterator it = std::find_if(v.begin(), v.end(), isEven);
// `it` points to 8, the first even element

std::vector<int>::iterator missing = std::find_if(v.begin(), v.end(), moreThan(10));
// `missing` is v.end(), as no element is greater than 10
C ++ 11
// find the first value that is even
std::vector<int> v = {1, 3, 7, 8};
auto it = std::find_if(v.begin(), v.end(), [](int val){return val % 2 == 0;});
// `it` points to 8, the first even element

auto missing = std::find_if(v.begin(), v.end(), [](int val){return val > 10;});
// `missing` is v.end(), as no element is greater than 10

배열을 std :: vector로 변환

배열은 std::beginstd::end 를 사용하여 쉽게 std::vector 로 변환 될 수 있습니다.

C ++ 11
int values[5] = { 1, 2, 3, 4, 5 }; // source array

std::vector<int> v(std::begin(values), std::end(values)); // copy array to new vector

for(auto &x: v)
    std::cout << x << " ";
std::cout << std::endl;

1 2 3 4 5

int main(int argc, char* argv[]) {
    // convert main arguments into a vector of strings.
    std::vector<std::string>  args(argv, argv + argc);
}

C ++ 11 initializer_list <>는 한 번에 벡터를 초기화하는 데에도 사용할 수 있습니다.

initializer_list<int> arr = { 1,2,3,4,5 };
vector<int> vec1 {arr};

for (auto & i : vec1)
    cout << i << endl;

벡터 : 너무 많은 규칙 때문에 많은 규칙

표준 (23.3.7 절)은 bool 값을 패킹하여 공간을 최적화하는 vector<bool> 의 특수화를 제공하므로 각각이 1 비트 만 차지합니다. 비트가 C ++에서 주소 지정이 가능하지 않기 때문에 vector 에 대한 몇 가지 요구 사항이 vector<bool> 에 배치되지 않습니다.

  • 저장된 데이터는 인접하지 않아도되므로 vector<bool>bool 배열을 필요로하는 C API로 전달 될 수 없습니다.
  • at() , operator [] 및 iterator의 참조 해제는 bool 대한 참조를 반환하지 않습니다. 오히려 할당 연산자를 오버로드하여 bool 에 대한 참조를 (불완전하게) 시뮬레이트하는 프록시 객체를 반환합니다. 예를 들어, 다음 코드는 반복기의 역 참조가 참조를 반환하지 않기 때문에 std::vector<bool> 유효하지 않을 수 있습니다.
C ++ 11
std::vector<bool> v = {true, false};
for (auto &b: v) { } // error

비슷하게 bool& argument를 기대하는 함수는 vector<bool> 적용된 operator [] 또는 at() 의 결과와 함께 사용하거나 iterator를 참조 해제 한 결과와 함께 사용할 수 없습니다.

  void f(bool& b);
  f(v[0]);             // error
  f(*v.begin());       // error

std::vector<bool> 의 구현은 컴파일러와 아키텍처에 의존합니다. 특수화는 n 부울을 메모리의 가장 낮은 주소 지정 가능 섹션에 패킹하여 구현됩니다. 여기서 n 은 가장 낮은 주소 지정 가능 메모리의 비트 단위 크기입니다. 대부분의 현대 시스템에서는 1 바이트 또는 8 비트입니다. 이는 1 바이트가 8 개의 부울 값을 저장할 수 있음을 의미합니다. 이는 1 바이트의 메모리에 1 개의 부울 값이 저장되는 기존 구현보다 향상된 기능입니다.

참고 : 아래 예제는 기존 vector<bool> 와 최적화 된 vector<bool> 에서 개별 바이트의 가능한 비트 별 값을 보여줍니다. 이것은 모든 아키텍처에서 항상 유효하지는 않습니다. 그러나 이것은 최적화를 시각화하는 좋은 방법입니다. 아래 예제에서 바이트는 [x, x, x, x, x, x, x, x]로 표시됩니다.

전통적인 std::vector<char> 8 개의 부울 값 저장하기 :

C ++ 11
std::vector<char> trad_vect = {true, false, false, false, true, false, true, true};

비트 표현 :

[0,0,0,0,0,0,0,1], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0], 
[0,0,0,0,0,0,0,1], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,1], [0,0,0,0,0,0,0,1]

Specialized std::vector<bool> 8 개의 부울 값 저장하기 :

C ++ 11
std::vector<bool> optimized_vect = {true, false, false, false, true, false, true, true};

비트 표현 :

[1,0,0,0,1,0,1,1]

위의 예제에서 std::vector<bool> 의 전통적인 버전에서는 8 개의 부울 값이 8 바이트의 메모리를 차지하지만 std::vector<bool> 의 최적화 된 버전에서는 1 바이트의 기억. 이는 메모리 사용량을 크게 향상시킵니다. C 스타일 API에 vector<bool> 을 전달해야하는 경우 메모리와 성능에 위험이있는 경우 값을 배열에 복사하거나 API를 사용하는 더 좋은 방법을 찾아야 할 수 있습니다.

벡터 크기 및 용량

벡터 크기 는 단순히 벡터 의 요소 수입니다.

  1. 현재 벡터 크기size() 멤버 함수에 의해 쿼리됩니다. 편의 empty() 함수는 size가 0 인 경우 true 반환 true .

    vector<int> v = { 1, 2, 3 }; // size is 3
    const vector<int>::size_type size = v.size();
    cout << size << endl; // prints 3
    cout << boolalpha << v.empty() << endl; // prints false
    
  2. 기본 구성된 벡터는 크기 0으로 시작합니다.

    vector<int> v; // size is 0
    cout << v.size() << endl; // prints 0
    
  3. N 요소를 벡터에 추가하면 크기N 만큼 증가 합니다 (예 : push_back() , insert() 또는 resize() 함수).

  4. 분리 N 벡터에서 요소별로 사이즈 감소하면 N (기준 예 pop_back() , erase() 또는 clear() 함수).

  5. 벡터의 크기에는 구현에 따른 상한이 있지만 도달하기 전에 RAM이 부족할 수 있습니다.

    vector<int> v;
    const vector<int>::size_type max_size = v.max_size();
    cout << max_size << endl; // prints some large number
    v.resize( max_size ); // probably won't work
    v.push_back( 1 ); // definitely won't work
    

일반적인 실수 : 크기 가 반드시 (또는 일반적으로) int 가 아닙니다.

// !!!bad!!!evil!!!
vector<int> v_bad( N, 1 ); // constructs large N size vector
for( int i = 0; i < v_bad.size(); ++i ) { // size is not supposed to be int!
    do_something( v_bad[i] );
}

벡터 용량크기 와 다릅니다. 크기 는 단순히 벡터의 현재 요소 이지만, 용량 은 메모리를 할당 / 예약 한 요소 수입니다. 크기가 너무 큰 (너무) 할당이 너무 비쌀 수 있기 때문에 유용합니다.

  1. 현재 벡터 용량capacity() 멤버 함수에 의해 쿼리됩니다. 용량 은 항상 크기 보다 크거나 같습니다.

    vector<int> v = { 1, 2, 3 }; // size is 3, capacity is >= 3
    const vector<int>::size_type capacity = v.capacity();
    cout << capacity << endl; // prints number >= 3
    
  2. reserve( N ) 기능을 사용하여 용량을 수동으로 예약 할 수 있습니다 (벡터 용량을 N 변경 함).

    // !!!bad!!!evil!!!
    vector<int> v_bad;
    for( int i = 0; i < 10000; ++i ) {
        v_bad.push_back( i ); // possibly lot of reallocations
    }
    
    // good
    vector<int> v_good;
    v_good.reserve( 10000 ); // good! only one allocation
    for( int i = 0; i < 10000; ++i ) {
        v_good.push_back( i ); // no allocations needed anymore
    }
    
  3. 초과 용량이 shrink_to_fit() 의해 해제 될 것을 요청할 수 있습니다 (그러나 구현은 당신에게 순종 할 필요가 없습니다). 사용 된 메모리를 절약하는 데 유용합니다.

    vector<int> v = { 1, 2, 3, 4, 5 }; // size is 5, assume capacity is 6
    v.shrink_to_fit(); // capacity is 5 (or possibly still 6)
    cout << boolalpha << v.capacity() == v.size() << endl; // prints likely true (but possibly false)
    

벡터는 부분적으로 용량을 자동으로 관리합니다. 요소를 추가하면 확장 할 수도 있습니다. 구현 자들은 성장 요인에 2 또는 1.5를 사용하는 것을 선호합니다 (황금 비율은 이상적인 값이되지만 합리적인 숫자로 인해 실용적이지 않습니다). 반면에 벡터는 대개 자동으로 축소되지 않습니다. 예 :

vector<int> v; // capacity is possibly (but not guaranteed) to be 0
v.push_back( 1 ); // capacity is some starter value, likely 1
v.clear(); // size is 0 but capacity is still same as before!

v = { 1, 2, 3, 4 }; // size is 4, and lets assume capacity is 4.
v.push_back( 5 ); // capacity grows - let's assume it grows to 6 (1.5 factor)
v.push_back( 6 ); // no change in capacity
v.push_back( 7 ); // capacity grows - let's assume it grows to 9 (1.5 factor)
// and so on
v.pop_back(); v.pop_back(); v.pop_back(); v.pop_back(); // capacity stays the same

벡터 연결하기

하나의 std::vector 는 멤버 함수 인 insert() 사용하여 다른 것에 추가 할 수 있습니다 :

std::vector<int> a = {0, 1, 2, 3, 4};
std::vector<int> b = {5, 6, 7, 8, 9};

a.insert(a.end(), b.begin(), b.end());

그러나 표준은 insert() 주어진 반복자가 수신자 객체의 요소와 같은 범위에 있지 않아야한다고 표준에서 지정하므로 벡터를 자체에 추가하려고하면이 솔루션이 실패합니다.

c ++ 11

벡터의 멤버 함수를 사용하는 대신 std::begin()std::end() 함수를 사용할 수 있습니다.

a.insert(std::end(a), std::begin(b), std::end(b));

예를 들어 b 도 배열이 될 수 있기 때문에 좀 더 일반적인 해결책입니다. 그러나이 솔루션은 벡터를 자체에 추가 할 수 없습니다.

수신 벡터의 요소 순서가 중요하지 않은 경우 각 벡터의 요소 수를 고려하면 불필요한 복사 작업을 피할 수 있습니다.

if (b.size() < a.size())
  a.insert(a.end(), b.begin(), b.end());
else
  b.insert(b.end(), a.begin(), a.end());

벡터 용량 줄이기

std::vector 는 필요에 따라 삽입 할 때 자동으로 용량을 늘리지 만 요소를 제거한 후에는 용량을 감소시키지 않습니다.

// Initialize a vector with 100 elements
std::vector<int> v(100);

// The vector's capacity is always at least as large as its size
auto const old_capacity = v.capacity();
// old_capacity >= 100

// Remove half of the elements
v.erase(v.begin() + 50, v.end());  // Reduces the size from 100 to 50 (v.size() == 50),
                                   // but not the capacity (v.capacity() == old_capacity)

용량을 줄이기 위해 벡터의 내용을 새로운 임시 벡터에 복사 할 수 있습니다. 새 벡터는 원래 벡터의 모든 요소를 ​​저장하는 데 필요한 최소 용량을 갖습니다. 원래 벡터의 크기 축소가 중요하면 새 벡터의 용량 감소가 중요 할 수 있습니다. 그런 다음 원래 벡터를 임시 벡터와 교체하여 최소화 된 용량을 유지할 수 있습니다.

std::vector<int>(v).swap(v);
C ++ 11

C ++ 11에서는 비슷한 효과를 내기 위해 shrink_to_fit() 멤버 함수를 사용할 수 있습니다 :

v.shrink_to_fit();

참고 : shrink_to_fit() 멤버 함수는 요청이며 용량을 줄일 수 있다고 보장하지 않습니다.

빠른 요소 조회에 정렬 된 벡터 사용

<algorithm> 헤더는 정렬 된 벡터 작업에 유용한 여러 가지 기능을 제공합니다.

정렬 된 벡터로 작업하기위한 중요한 전제 조건은 저장된 값이 < 와 비교된다는 것입니다.

정렬되지 않은 벡터는 std::sort() 함수를 사용하여 정렬 할 수 있습니다.

std::vector<int> v;
// add some code here to fill v with some elements
std::sort(v.begin(), v.end());

정렬 된 벡터를 사용하면 std::lower_bound() 함수를 사용하여 효율적인 요소 조회가 가능합니다. std::find() 와는 달리 이는 벡터에서 효율적인 이진 검색을 수행합니다. 단점은 정렬 된 입력 범위에 대해서만 유효한 결과를 제공한다는 것입니다.

// search the vector for the first element with value 42
std::vector<int>::iterator it = std::lower_bound(v.begin(), v.end(), 42);
if (it != v.end() && *it == 42) {
    // we found the element!
}

참고 : 요청 된 값이 벡터의 일부가 아닌 경우 std::lower_bound() 는 요청 된 값 보다 큰 첫 번째 요소에 반복자를 반환합니다. 이 동작을 통해 이미 정렬 된 벡터의 올바른 위치에 새 요소를 삽입 할 수 있습니다.

int const new_element = 33;
v.insert(std::lower_bound(v.begin(), v.end(), new_element), new_element);

한 번에 많은 요소를 삽입해야하는 경우 먼저 모든 요소에 대해 push_back() 을 호출 한 다음 모든 요소가 삽입되면 std::sort() 를 호출하는 것이 더 효율적일 수 있습니다. 이 경우 정렬 비용이 증가하므로 중간에 새 요소를 삽입하는 비용이 줄지 않고 벡터 끝 부분에 비용이 발생합니다.

벡터에 같은 값의 요소가 여러 개 있으면 std::lower_bound() 는 반복자를 검색된 값의 첫 번째 요소로 반환하려고 시도합니다. 그러나 검색된 값의 마지막 요소 다음에 새 요소를 삽입해야하는 경우 std::upper_bound() 함수를 사용해야합니다. 이렇게하면 요소가 덜 이동하게됩니다.

v.insert(std::upper_bound(v.begin(), v.end(), new_element), new_element);

상한과 하한 반복자가 모두 필요한 경우에는 std::equal_range() 함수를 사용하여 하나의 호출로 두 함수를 효율적으로 검색 할 수 있습니다.

std::pair<std::vector<int>::iterator,
          std::vector<int>::iterator> rg = std::equal_range(v.begin(), v.end(), 42);
std::vector<int>::iterator lower_bound = rg.first;
std::vector<int>::iterator upper_bound = rg.second;

정렬 된 벡터에 요소가 존재하는지 테스트하려면 (벡터에 한정되지는 않지만) std::binary_search() 함수를 사용할 수 있습니다.

bool exists = std::binary_search(v.begin(), v.end(), value_to_find);

큰 벡터를 반환하는 함수

C ++ 11

C ++ 11에서는 컴파일러가 반환되는 로컬 변수에서 암시 적으로 이동해야합니다. 또한 대부분의 컴파일러는 많은 경우 복사 방지 를 수행 할 수 있으며 이동을 완전히 제거 할 수 있습니다. 그 결과 값싼 이동이 가능한 대형 객체를 반환 할 때 특별한 처리가 필요하지 않습니다.

#include <vector>
#include <iostream>

// If the compiler is unable to perform named return value optimization (NRVO)
// and elide the move altogether, it is required to move from v into the return value.
std::vector<int> fillVector(int a, int b) {
    std::vector<int> v;
    v.reserve(b-a+1);
    for (int i = a; i <= b; i++) {
        v.push_back(i);
    }
    return v; // implicit move
}

int main() { // declare and fill vector
    std::vector<int> vec = fillVector(1, 10);

    // print vector
    for (auto value : vec)
        std::cout << value << " "; // this will print "1 2 3 4 5 6 7 8 9 10 "

    std::cout << std::endl;

    return 0;
}
C ++ 11

C ++ 11 이전에는 이미 대부분의 컴파일러에서 copy elision이 허용되고 구현되었습니다. 그러나이 최적화를 구현하지 않는 이전 컴파일러 버전으로 컴파일해야하는 레거시 코드 또는 코드에서 이동 의미가 없기 때문에 불필요한 복사본을 방지하기 위해 출력 인수로 전달되는 벡터를 찾을 수 있습니다.

#include <vector>
#include <iostream>

// passing a std::vector by reference
void fillVectorFrom_By_Ref(int a, int b, std::vector<int> &v) {
    assert(v.empty());
    v.reserve(b-a+1);
    for (int i = a; i <= b; i++) {
        v.push_back(i);
    }
}

int main() {// declare vector
    std::vector<int> vec;
    
    // fill vector
    fillVectorFrom_By_Ref(1, 10, vec);
    // print vector
    for (std::vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it)
        std::cout << *it << " "; // this will print "1 2 3 4 5 6 7 8 9 10 "
    std::cout << std::endl;
    return 0;
}

벡터에서 최대 및 최소 요소 및 각 색인 찾기

벡터에 저장된 가장 큰 요소 또는 가장 작은 요소를 찾으려면 std::max_elementstd::min_element 메서드를 각각 사용할 수 있습니다. 이러한 메소드는 <algorithm> 헤더에 정의됩니다. 여러 요소가 가장 큰 요소 (가장 작은 요소)와 동일한 경우이 메서드는 반복기를 첫 번째 요소로 반환합니다. 빈 벡터의 경우 v.end() 를 반환합니다.

std::vector<int> v = {5, 2, 8, 10, 9}; 
int maxElementIndex = std::max_element(v.begin(),v.end()) - v.begin();
int maxElement = *std::max_element(v.begin(), v.end());

int minElementIndex = std::min_element(v.begin(),v.end()) - v.begin();
int minElement = *std::min_element(v.begin(), v.end());

std::cout << "maxElementIndex:" << maxElementIndex << ", maxElement:" << maxElement << '\n';
std::cout << "minElementIndex:" << minElementIndex << ", minElement:" << minElement << '\n';

산출:

maxElementIndex : 3, maxElement : 10
minElementIndex : 1, minElement : 2

C ++ 11

벡터의 최소 및 최대 요소는 <algorithm> 헤더에 정의 된 std::minmax_element 메서드를 사용하여 동시에 검색 할 수 있습니다.

std::vector<int> v = {5, 2, 8, 10, 9}; 
auto minmax = std::minmax_element(v.begin(), v.end());

std::cout << "minimum element: " << *minmax.first << '\n';
std::cout << "maximum element: " << *minmax.second << '\n';

산출:

최소 요소 : 2
최대 요소 : 10

행렬 벡터 사용

벡터는 벡터 벡터로 정의하여 2D 행렬로 사용할 수 있습니다.

각 셀이 0으로 초기화 된 3 행 4 열의 행렬은 다음과 같이 정의 할 수 있습니다.

std::vector<std::vector<int> > matrix(3, std::vector<int>(4));
C ++ 11

이니셜 라이저 목록 또는 기타 방법을 사용하여 초기화하는 구문은 일반 벡터의 구문과 유사합니다.

  std::vector<std::vector<int>> matrix = { {0,1,2,3},
                                           {4,5,6,7}, 
                                           {8,9,10,11}
                                         };

이러한 벡터의 값은 2D 배열과 비슷한 방식으로 액세스 할 수 있습니다.

int var = matrix[0][2];

전체 행렬에 대한 반복은 법선 벡터와 유사하지만 추가 차원이 있습니다.

for(int i = 0; i < 3; ++i)
{
    for(int j = 0; j < 4; ++j)
    {
        std::cout << matrix[i][j] << std::endl;
    }
}
C ++ 11
for(auto& row: matrix)
{
    for(auto& col : row)
    { 
        std::cout << col << std::endl;
    }
}

벡터 벡터는 행렬을 표현하는 편리한 방법이지만 가장 효율적이지는 않습니다. 개별 벡터가 메모리 주위에 분산되어 있고 데이터 구조가 캐시 친화적이지 않습니다.

또한 적절한 행렬에서 모든 행의 길이는 동일해야합니다 (벡터 벡터의 경우는 아닙니다). 추가적인 유연성은 오류의 원인이 될 수 있습니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow