Sự khác biệt giữa const_iterator và non-const iterator trong C ++ STL là gì?


139

Sự khác biệt giữa a const_iteratorvà an iteratorvà nơi bạn sẽ sử dụng cái này so với cái kia là gì?


1
Chà, tên const_iterator nghe giống như iterator là const, trong khi thứ mà trình lặp này trỏ đến là const thực tế.
talekeDskobeDa

Câu trả lời:


124

const_iterators không cho phép bạn thay đổi các giá trị mà chúng trỏ tới, iterators thường làm.

Như với tất cả mọi thứ trong C ++, luôn luôn thích const, trừ khi có lý do chính đáng để sử dụng các trình vòng lặp thông thường (nghĩa là bạn muốn sử dụng thực tế là chúng không constthay đổi giá trị trỏ).


8
Trong một thế giới hoàn hảo đó sẽ là trường hợp. Nhưng với C ++ const chỉ tốt như người đã viết mã :(
JaredPar

mutable tồn tại vì một lý do rất tốt. Nó hiếm khi được sử dụng và nhìn vào Google Code Search, dường như có tỷ lệ phần trăm sử dụng hợp lệ. Từ khóa là một công cụ tối ưu hóa rất mạnh mẽ và không giống như loại bỏ nó sẽ cải thiện tính chính xác ( hopointerscoughandcoughreferencescough )
coppro

2
Giống như một hack mạnh mẽ. Trong tất cả các trường hợp tôi từng thấy về việc sử dụng từ khóa 'có thể thay đổi', nhưng tất cả chỉ có một chỉ số chính xác rằng mã được viết kém và có thể thay đổi là một hack để khắc phục các khiếm khuyết.
John Dibling

7
Nó có những sử dụng hợp pháp, như lưu trữ kết quả của một phép tính dài trong một lớp const. Mặt khác, đó là lần duy nhất tôi sử dụng một đột biến trong gần hai mươi năm phát triển C ++.
Head Geek

Một ví dụ chung cho việc sử dụng const_iterator sẽ là khi iterator là một giá trị
talekeDskobeDa

40

Họ nên khá nhiều để tự giải thích. Nếu iterator trỏ đến một phần tử của loại T, thì const_iterator trỏ đến một phần tử của loại 'const T'.

Về cơ bản, nó tương đương với các loại con trỏ:

T* // A non-const iterator to a non-const element. Corresponds to std::vector<T>::iterator
T* const // A const iterator to a non-const element. Corresponds to const std::vector<T>::iterator
const T* // A non-const iterator to a const element. Corresponds to std::vector<T>::const_iterator

Một iterator const luôn trỏ đến cùng một phần tử, vì vậy chính iterator là const. Nhưng phần tử mà nó trỏ đến không phải là const, vì vậy phần tử mà nó trỏ đến có thể được thay đổi. Một const_iterator là một trình vòng lặp trỏ đến một phần tử const, do đó, trong khi chính trình lặp đó có thể được cập nhật (chẳng hạn như tăng hoặc giảm), thì phần tử mà nó trỏ đến không thể thay đổi.


1
"Trình lặp const luôn luôn trỏ đến cùng một phần tử," Điều này không chính xác.
John Dibling

2
Làm sao vậy Lưu ý thiếu dấu gạch dưới. Tôi đối chiếu một biến kiểu const std :: vector <T> :: iterator với std :: vector <T> :: const_iterator. Trong trường hợp trước, chính trình lặp là const, vì vậy nó không thể được sửa đổi, nhưng phần tử mà nó tham chiếu có thể được sửa đổi một cách tự do.
jalf

4
Ah tôi thấy. Có tôi đã bỏ lỡ dấu gạch dưới bị thiếu.
John Dibling

3
@JohnDibling Upvote để giải thích sự tinh tế giữa const iteraterconst_iterator.
huyền thoại2k

8

Thật không may, rất nhiều phương thức cho các bộ chứa STL lấy các vòng lặp thay vì const_iterators làm tham số. Vì vậy, nếu bạn có một const_iterator , theo ý kiến ​​của tôi, bạn không thể nói "chèn một phần tử trước phần tử mà trình lặp này trỏ tới" (theo ý kiến ​​của một điều đó không phải là vi phạm const về mặt khái niệm). Nếu bạn muốn làm điều đó bằng mọi cách, bạn phải chuyển đổi nó thành một trình lặp không phải const bằng cách sử dụng std :: advanced () hoặc boost :: next () . Ví dụ. boost :: next (container.begin (), std :: distance (container.begin (), the_const_iterator_we_want_to_unconst)) . Nếu containerdanh sách std :: thì thời gian chạy cho cuộc gọi đó sẽ là O (n) .

Vì vậy, quy tắc phổ quát để thêm const bất cứ nơi nào "hợp lý" để làm như vậy, sẽ ít phổ biến hơn khi nói đến các container STL.

Tuy nhiên, các thùng chứa boost mất const_iterators (ví dụ: boost :: unordered_map :: erase ()). Vì vậy, khi bạn sử dụng các thùng chứa boost, bạn có thể "const agressive". Nhân tiện, có ai biết nếu hoặc khi nào các thùng chứa STL sẽ được sửa chữa không?


1
Nó có thể là một vấn đề của ý kiến. Trong trường hợp vectordeque, việc chèn một phần tử làm mất hiệu lực tất cả các trình vòng lặp hiện có, điều này không đúng const. Nhưng tôi thấy quan điểm của bạn. Các hoạt động như vậy được bảo vệ bởi độ chứa const, không phải các vòng lặp. Và tôi tự hỏi tại sao không có chức năng chuyển đổi vòng lặp const-to-nonconst trong giao diện bộ chứa tiêu chuẩn.
Potatoswatter

Bạn đúng là Potatoswatter, tôi phân loại, đó là vấn đề quan điểm đối với các container truy cập ngẫu nhiên và container.begin () + (the_const_iterator_we_want_to_unconst - container.begin () ) dù sao cũng là O (1) . Tôi cũng tự hỏi tại sao không có chức năng chuyển đổi cho các container truy cập không ngẫu nhiên, nhưng có lẽ có một lý do chính đáng? Bạn có biết nếu có một số lý do mà các hàm cho các thùng chứa truy cập không ngẫu nhiên không lấy const_iterators không?
Magnus Andermo

"Nói một điều như vậy về mặt khái niệm không phải là vi phạm const" - đây là một nhận xét rất thú vị, tôi có những suy nghĩ sau đây về chủ đề này. Với các con trỏ đơn giản, người ta có thể nói int const * foo; int * const foo;int const * const foo;cả ba đều hợp lệ và hữu ích, mỗi cách theo cách riêng của chúng. std::vector<int> const barnên giống như cái thứ hai, nhưng thật không may, nó thường được coi như cái thứ ba. Nguyên nhân cốt lõi của vấn đề là chúng ta không thể nói std::vector<int const> bar;khi nào có nghĩa là không có cách nào để có được hiệu ứng tương tự như int const *foo;trong một vectơ.
thức

5

Sử dụng const_iterator bất cứ khi nào bạn có thể, sử dụng iterator khi bạn không có lựa chọn nào khác.


4

Ví dụ runnable tối thiểu

Các trình lặp không phải const cho phép bạn sửa đổi những gì chúng trỏ đến:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
*it = 1;
assert(v[0] == 1);

Lặp đi lặp lại không:

const std::vector<int> v{0};
std::vector<int>::const_iterator cit = v.begin();
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

Như đã trình bày ở trên, v.begin()constquá tải, và trả về một trong hai iteratorhoặc const_iteratortùy thuộc vào const-Ness của biến container:

Một trường hợp phổ biến khi const_iteratorbật lên là khi thisđược sử dụng bên trong một constphương thức:

class C {
    public:
        std::vector<int> v;
        void f() const {
            std::vector<int>::const_iterator it = this->v.begin();
        }
        void g(std::vector<int>::const_iterator& it) {}
};

constlàm thisconst, mà làm cho this->vconst.

Bạn thường có thể quên nó đi auto, nhưng nếu bạn bắt đầu vượt qua các vòng lặp đó, bạn sẽ cần phải nghĩ về chúng cho các chữ ký phương thức.

Giống như const và non-const, bạn có thể chuyển đổi dễ dàng từ non-const sang const, nhưng không phải là cách khác:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();

// non-const to const.
std::vector<int>::const_iterator cit = it;

// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

// Compile time error: no conversion from const to no-const.
//it = ci1;

Cái nào sẽ sử dụng: tương tự như const intvs int: thích const iterators bất cứ khi nào bạn có thể sử dụng chúng (khi bạn không cần sửa đổi container với chúng), để ghi lại ý định đọc mà không cần sửa đổi.


0

(như những người khác đã nói) const_iterator không cho phép bạn sửa đổi các phần tử mà nó trỏ tới, điều này rất hữu ích bên trong các phương thức lớp const. Nó cũng cho phép bạn thể hiện ý định của mình.


0

ok Hãy để tôi giải thích nó với ví dụ rất đơn giản trước mà không sử dụng trình lặp liên tục xem xét chúng tôi có bộ sưu tập số nguyên ngẫu nhiên "RandomData"

    for(vector<int>::iterator i = randomData.begin() ; i != randomData.end() ; ++i)*i = 0;
for(vector<int>::const_iterator i = randomData.begin() ; i!= randomData.end() ; ++i)cout << *i;

Như có thể thấy để ghi / chỉnh sửa dữ liệu bên trong bộ sưu tập Trình lặp thông thường được sử dụng nhưng với mục đích đọc, trình lặp không đổi đã được sử dụng. Nếu bạn thử sử dụng iterator liên tục trong vòng lặp đầu tiên, bạn sẽ gặp lỗi. Như một quy tắc ngón tay cái sử dụng iterator liên tục để đọc dữ liệu bên trong bộ sưu tập.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.