Tại sao sử dụng các vòng lặp thay vì các chỉ số mảng?


239

Lấy hai dòng mã sau:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Và điều này:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Tôi nói rằng cách thứ hai được ưa thích. Tại sao chính xác là đây?


72
Cách thứ hai được ưa thích là bạn đổi some_iterator++sang ++some_iterator. Tăng sau tạo ra một trình vòng lặp tạm thời không cần thiết.
jason

6
Bạn cũng nên đưa end()vào điều khoản khai báo.
Các cuộc đua nhẹ nhàng trong quỹ đạo

5
@Tomalak: bất cứ ai sử dụng triển khai C ++ với hiệu quả kém vector::endcó thể có vấn đề đáng lo ngại hơn là liệu nó có được đưa ra khỏi các vòng lặp hay không. Cá nhân tôi thích sự rõ ràng - mặc dù đó là một cuộc gọi findtrong điều kiện chấm dứt, tôi lo lắng.
Steve Jessop

13
@Tomalak: Mã đó không cẩu thả (tốt, có thể là tăng sau), nó ngắn gọn và rõ ràng, theo như trình lặp C ++ cho phép tính đồng nhất. Thêm nhiều biến số thêm nỗ lực nhận thức vì mục đích tối ưu hóa sớm. Thật cẩu thả.
Steve Jessop

7
@Tomalak: sẽ sớm nếu không phải là nút cổ chai. Điểm thứ hai của bạn có vẻ vô lý với tôi, vì so sánh chính xác không phải là giữa it != vec.end()it != end, đó là giữa (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)(vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). Tôi không cần phải đếm các nhân vật. Bằng mọi cách, thích cái này hơn cái kia, nhưng người khác không đồng ý với sở thích của bạn không phải là "sự cẩu thả", đó là ưu tiên cho mã đơn giản hơn với ít biến hơn và do đó ít phải suy nghĩ hơn khi đọc nó.
Steve Jessop

Câu trả lời:


210

Hình thức đầu tiên chỉ hiệu quả nếu vector.size () hoạt động nhanh. Điều này đúng với vectơ, nhưng không phải cho danh sách, ví dụ. Ngoài ra, bạn dự định làm gì trong cơ thể của vòng lặp? Nếu bạn có kế hoạch truy cập các yếu tố như trong

T elem = some_vector[i];

sau đó bạn đang đưa ra giả định rằng container đã operator[](std::size_t)được xác định. Một lần nữa, điều này đúng với vector nhưng không đúng với các container khác.

Việc sử dụng các trình vòng lặp đưa bạn đến gần hơn với sự độc lập của container . Bạn không đưa ra các giả định về khả năng truy cập ngẫu nhiên hoặc size()hoạt động nhanh , chỉ có điều rằng container có khả năng lặp.

Bạn có thể nâng cao mã của mình hơn nữa bằng cách sử dụng các thuật toán tiêu chuẩn. Tùy thuộc vào những gì bạn đang cố gắng đạt được, bạn có thể chọn sử dụng std::for_each(), std::transform()v.v. Bằng cách sử dụng một thuật toán tiêu chuẩn chứ không phải là một vòng lặp rõ ràng, bạn sẽ tránh phát minh lại bánh xe. Mã của bạn có khả năng hiệu quả hơn (được chọn đúng thuật toán), chính xác và có thể sử dụng lại.


8
Ngoài ra, bạn quên rằng các trình vòng lặp có thể thực hiện những việc như không thành công, do đó, nếu có một sửa đổi đồng thời cho cấu trúc bạn đang truy cập, bạn sẽ biết về nó. Bạn không thể làm điều đó chỉ với một số nguyên.
Marcin

4
Điều này làm tôi bối rối: "Điều này đúng với các vectơ, nhưng không phải cho danh sách, ví dụ." Tại sao? Bất cứ ai có bộ não sẽ theo dõi một size_tbiến thành viên size().
GManNickG

19
@GMan - trong hầu hết tất cả các triển khai, size () là nhanh đối với các danh sách cũng nhiều như các vectơ. Phiên bản tiếp theo của tiêu chuẩn sẽ yêu cầu điều này là đúng. Vấn đề thực sự là sự chậm chạp của việc trả thù theo vị trí.
Daniel Earwicker

8
@GMan: Lưu trữ kích thước danh sách yêu cầu cắt và ghép danh sách là O (n) thay vì O (1).

5
Trong C ++ 0x, size()hàm thành viên sẽ được yêu cầu có độ phức tạp thời gian không đổi cho tất cả các container hỗ trợ nó, bao gồm cả std::list.
James McNellis

54

Đó là một phần của quy trình truyền bá C ++ hiện đại. Lặp đi lặp lại là cách duy nhất để lặp lại hầu hết các container, vì vậy bạn sử dụng nó ngay cả với các vectơ chỉ để đưa mình vào suy nghĩ đúng đắn. Nghiêm túc mà nói, đó là lý do duy nhất tôi làm điều đó - tôi không nghĩ rằng mình đã từng thay thế một vectơ bằng một loại vật chứa khác.


Wow, điều này vẫn đang bị hạ cấp sau ba tuần. Tôi đoán nó không phải là một cái lưỡi nhỏ bé.

Tôi nghĩ rằng chỉ số mảng là dễ đọc hơn. Nó phù hợp với cú pháp được sử dụng trong các ngôn ngữ khác và cú pháp được sử dụng cho mảng C kiểu cũ. Nó cũng ít dài dòng hơn. Hiệu quả nên được rửa nếu trình biên dịch của bạn là tốt, và hầu như không có trường hợp nào nó quan trọng.

Mặc dù vậy, tôi vẫn thấy mình sử dụng các trình vòng lặp thường xuyên với các vectơ. Tôi tin rằng iterator là một khái niệm quan trọng, vì vậy tôi quảng bá nó bất cứ khi nào tôi có thể.


1
C ++ iterator cũng bị phá vỡ khủng khiếp về mặt khái niệm. Đối với các vectơ, tôi vừa bị bắt vì con trỏ kết thúc là kết thúc + 1 (!). Đối với các luồng, mô hình lặp chỉ là siêu thực - một mã thông báo tưởng tượng không tồn tại. Tương tự như vậy cho các danh sách liên kết. Mô hình chỉ có ý nghĩa cho các mảng, và sau đó không nhiều. Tại sao tôi cần hai đối tượng lặp, không chỉ một ...
Tuntable

5
@aberglas Chúng hoàn toàn không bị hỏng, bạn chỉ không quen với chúng, đó là lý do tại sao tôi ủng hộ việc sử dụng chúng ngay cả khi bạn không phải làm thế! Phạm vi nửa mở là một khái niệm phổ biến và các câu lệnh không bao giờ được truy cập trực tiếp có nghĩa là lâu đời như chính chương trình.
Đánh dấu tiền chuộc

4
hãy xem các trình vòng lặp luồng và suy nghĩ về những gì == đã bị biến thái để làm cho phù hợp với mô hình, và sau đó cho tôi biết các trình vòng lặp không bị hỏng! Hoặc cho danh sách liên kết. Ngay cả đối với các mảng, việc chỉ định một quá khứ là một ý tưởng kiểu C bị hỏng - con trỏ vào không bao giờ không bao giờ. Chúng phải giống như Java hoặc C # hoặc bất kỳ trình lặp của ngôn ngữ nào khác, với một trình lặp được yêu cầu (thay vì hai đối tượng) và kiểm tra kết thúc đơn giản.
Tuntable

53

bởi vì bạn không buộc mã của mình vào việc triển khai cụ thể danh sách some_vector. nếu bạn sử dụng các chỉ mục mảng, nó phải là một dạng của mảng; nếu bạn sử dụng các trình vòng lặp, bạn có thể sử dụng mã đó trong bất kỳ triển khai danh sách nào.


23
Giao diện std :: list có ý định không cung cấp toán tử [] (size_t n) vì nó sẽ là O (n).
MSalters

33

Hãy tưởng tượng some_vector được thực hiện với một danh sách liên kết. Sau đó, yêu cầu một mục ở vị trí thứ i yêu cầu các thao tác i phải được thực hiện để duyệt qua danh sách các nút. Bây giờ, nếu bạn sử dụng iterator, nói chung, nó sẽ nỗ lực hết sức để có hiệu quả nhất có thể (trong trường hợp danh sách được liên kết, nó sẽ duy trì một con trỏ tới nút hiện tại và tiến lên trong mỗi lần lặp, chỉ cần một hoạt động đơn lẻ).

Vì vậy, nó cung cấp hai điều:

  • Trừu tượng sử dụng: bạn chỉ muốn lặp lại một số yếu tố, bạn không quan tâm đến cách thực hiện
  • Hiệu suất

1
"nó sẽ duy trì một con trỏ tới nút hiện tại và nâng cao nó [công cụ tốt về hiệu quả]" - vâng, tôi không hiểu tại sao mọi người gặp khó khăn trong việc hiểu khái niệm về các trình vòng lặp. về mặt khái niệm chúng chỉ là một siêu con trỏ. Tại sao phải tính toán bù cho một số phần tử lặp đi lặp lại khi bạn chỉ có thể lưu trữ một con trỏ tới nó? tốt, đó là những gì lặp đi lặp lại quá.
gạch dưới

27

Tôi sẽ trở thành ác quỷ ủng hộ ở đây, và không đề xuất các trình vòng lặp. Lý do chính tại sao, là tất cả các mã nguồn mà tôi đã làm việc từ phát triển ứng dụng Máy tính để bàn đến phát triển trò chơi, tôi cũng không cần phải sử dụng các trình vòng lặp. Tất cả thời gian chúng không được yêu cầu và thứ hai là các giả định ẩn và mã lộn xộn và gỡ lỗi ác mộng bạn gặp phải với các trình vòng lặp làm cho chúng trở thành một ví dụ điển hình không sử dụng nó trong bất kỳ ứng dụng nào đòi hỏi tốc độ.

Ngay cả từ một quan điểm duy trì họ là một mớ hỗn độn. Không phải vì họ mà vì tất cả những bí danh xảy ra đằng sau hậu trường. Làm thế nào để tôi biết rằng bạn đã không triển khai danh sách mảng hoặc vectơ ảo của riêng mình, thứ thực hiện một cái gì đó hoàn toàn khác với các tiêu chuẩn. Tôi có biết loại nào hiện đang trong thời gian chạy không? Bạn có quá tải một nhà điều hành tôi không có thời gian để kiểm tra tất cả mã nguồn của bạn. Tôi thậm chí còn biết phiên bản STL nào bạn đang sử dụng?

Vấn đề tiếp theo bạn gặp phải với các trình vòng lặp là sự trừu tượng bị rò rỉ, mặc dù có rất nhiều trang web thảo luận chi tiết về vấn đề này với chúng.

Xin lỗi, tôi chưa và vẫn chưa thấy điểm nào trong các vòng lặp. Nếu họ trừu tượng danh sách hoặc vectơ ra khỏi bạn, trong thực tế, bạn nên biết vectơ nào hoặc liệt kê giao dịch của bạn nếu bạn không, bạn sẽ tự thiết lập một số phiên gỡ lỗi tuyệt vời trong tương lai.


23

Bạn có thể muốn sử dụng một trình vòng lặp nếu bạn định thêm / xóa các mục vào vectơ trong khi bạn đang lặp lại nó.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Nếu bạn đang sử dụng các chỉ mục, bạn sẽ phải xáo trộn các mục lên / xuống trong mảng để xử lý các phần chèn và xóa.


3
nếu bạn muốn chèn các phần tử vào giữa container thì có thể một vectơ không phải là lựa chọn container tốt để bắt đầu. tất nhiên, chúng tôi trở lại lý do tại sao các trình vòng lặp là mát mẻ; Thật là tầm thường khi chuyển sang một danh sách.
wilmustell

Tuy nhiên, việc lặp lại tất cả các yếu tố khá tốn kém std::listso với a std::vector, nếu bạn khuyên bạn nên sử dụng danh sách liên kết thay vì a std::vector. Xem Trang 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf Theo kinh nghiệm của tôi, tôi đã tìm thấy một std::vectortốc độ nhanh hơn std::listngay cả khi tôi đang tìm kiếm trên tất cả các vị trí đó và loại bỏ các yếu tố tại các vị trí tùy ý.
David Stone

Các chỉ số ổn định, vì vậy tôi không thấy sự xáo trộn bổ sung nào là cần thiết cho việc chèn và xóa.
musiphil

... Và với một danh sách được liên kết - đó là những gì nên được sử dụng ở đây - câu lệnh lặp của bạn sẽ for (node = list->head; node != NULL; node = node->next)ngắn hơn hai dòng mã đầu tiên của bạn được đặt cùng nhau (khai báo và đầu vòng lặp). Vì vậy, tôi nói lại - không có nhiều khác biệt cơ bản về sự ngắn gọn giữa việc sử dụng các trình lặp và không sử dụng chúng - bạn vẫn đáp ứng ba phần của một fortuyên bố, ngay cả khi bạn sử dụng while: khai báo, lặp lại, kiểm tra chấm dứt.
Kỹ sư

16

Tách mối quan tâm

Thật tuyệt khi tách mã lặp khỏi mối quan tâm 'cốt lõi' của vòng lặp. Đó gần như là một quyết định thiết kế.

Thật vậy, lặp đi lặp lại bởi chỉ số ràng buộc bạn với việc thực hiện container. Yêu cầu bộ chứa cho bộ lặp bắt đầu và kết thúc, cho phép mã vòng lặp để sử dụng với các loại bộ chứa khác.

Ngoài ra, std::for_eachtheo cách này, bạn NÓI bộ sưu tập phải làm gì, thay vì HỎI nó một cái gì đó về nội bộ của nó

Tiêu chuẩn 0x sẽ giới thiệu các bao đóng, điều này sẽ giúp cách tiếp cận này dễ sử dụng hơn nhiều - hãy xem sức mạnh biểu cảm của ví dụ như Ruby [1..6].each { |i| print i; } ...

Hiệu suất

Nhưng có lẽ một vấn đề được quan tâm nhiều là, sử dụng cách for_eachtiếp cận mang lại cơ hội để lặp lại song song - các khối luồng intel có thể phân phối khối mã qua số lượng bộ xử lý trong hệ thống!

Lưu ý: sau khi khám phá algorithmsthư viện và đặc biệt foreach, tôi đã trải qua hai hoặc ba tháng để viết các cấu trúc toán tử 'người trợ giúp' nhỏ một cách lố bịch sẽ khiến các nhà phát triển đồng nghiệp của bạn phát điên. Sau thời gian này, tôi đã quay trở lại một cách tiếp cận thực dụng - cơ thể vòng lặp nhỏ không xứng đángforeach còn nữa :)

Một tài liệu tham khảo phải đọc trên các trình vòng lặp là cuốn sách "STL mở rộng" .

GoF có một đoạn nhỏ ở cuối mẫu Iterator, nói về thương hiệu lặp này; nó được gọi là "trình lặp nội bộ". Có một cái nhìn ở đây , quá.


15

Bởi vì nó hướng đối tượng hơn. nếu bạn đang lặp lại với một chỉ mục bạn đang giả sử:

a) rằng các đối tượng đó được ra lệnh
b) rằng các đối tượng đó có thể được lấy bởi một chỉ mục
c) rằng chỉ số tăng sẽ đánh vào mọi mục
d) rằng chỉ mục đó bắt đầu từ 0

Với một trình vòng lặp, bạn đang nói "hãy cho tôi mọi thứ để tôi có thể làm việc với nó" mà không cần biết triển khai cơ bản là gì. (Trong Java, có các bộ sưu tập không thể được truy cập thông qua một chỉ mục)

Ngoài ra, với một iterator, không cần phải lo lắng về việc đi ra khỏi giới hạn của mảng.


2
Tôi không nghĩ "hướng đối tượng" là thuật ngữ chính xác. Lặp đi lặp lại không "hướng đối tượng" trong thiết kế. Họ thúc đẩy lập trình chức năng nhiều hơn lập trình hướng đối tượng, bởi vì họ khuyến khích tách các thuật toán khỏi các lớp.
wilmustell

Ngoài ra, các trình vòng lặp không giúp tránh ra khỏi giới hạn. Các thuật toán tiêu chuẩn làm, nhưng các trình lặp một mình thì không.
wilmustell

Đủ công bằng @wilmustell, rõ ràng tôi đang nghĩ về điều này từ quan điểm trung tâm của Java.
hoài nghi

1
Và tôi nghĩ rằng nó thúc đẩy OO, bởi vì nó đang tách các hoạt động trên các bộ sưu tập khỏi việc thực hiện bộ sưu tập đó. Một tập hợp các đối tượng không nhất thiết phải biết những thuật toán nào nên được sử dụng để làm việc với chúng.
hoài nghi

Trên thực tế, có những phiên bản STL ngoài đó đã kiểm tra các trình vòng lặp, có nghĩa là nó sẽ đưa ra một số ngoại lệ ngoài luồng khi bạn cố gắng làm điều gì đó với trình vòng lặp đó.
Daemin

15

Một điều thú vị khác về các trình vòng lặp là chúng tốt hơn cho phép bạn thể hiện (và thực thi) tùy chọn const của bạn. Ví dụ này đảm bảo rằng bạn sẽ không thay đổi vectơ ở giữa vòng lặp của bạn:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

Điều này có vẻ hợp lý, nhưng tôi vẫn nghi ngờ rằng nếu đó là lý do để có const_iterator. Nếu tôi thay đổi vectơ trong vòng lặp, tôi sẽ làm điều đó vì một lý do và trong 99,9% thời gian thay đổi đó không phải là một tai nạn và đối với phần còn lại, đó chỉ là một lỗi như bất kỳ loại lỗi nào trong mã của tác giả cần sửa chữa. Bởi vì trong Java và nhiều ngôn ngữ khác, hoàn toàn không có đối tượng const, nhưng người dùng của các ngôn ngữ đó không bao giờ gặp vấn đề khi không có hỗ trợ const trong các ngôn ngữ đó.
neevek

2
@neevek Nếu đó không phải là lý do để có const_iterator, thì lý do trên trái đất là gì?
gạch dưới

@underscore_d, tôi cũng đang thắc mắc. Tôi không phải là chuyên gia về điều này, chỉ là câu trả lời là không thuyết phục với tôi.
neevek

15

Ngoài tất cả các câu trả lời xuất sắc khác ... intcó thể không đủ lớn cho vectơ của bạn. Thay vào đó, nếu bạn muốn sử dụng lập chỉ mục, hãy sử dụng size_typecho vùng chứa của bạn:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@Pat Notz, đó là một điểm rất tốt. Trong quá trình chuyển một ứng dụng Windows dựa trên STL sang x64, tôi đã phải đối phó với hàng trăm cảnh báo về việc gán size_t cho một int có thể gây ra cắt ngắn.
bk1e

1
Không đề cập đến thực tế là các loại kích thước không dấu và int được ký, do đó bạn có các chuyển đổi ẩn không trực quan, ẩn lỗi đang diễn ra chỉ để so sánh int ivới myvector.size().
Adrian McCarthy

12

Tôi có lẽ nên chỉ ra bạn cũng có thể gọi

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

Các trình lặp STL chủ yếu ở đó để các thuật toán STL như sort có thể độc lập với container.

Nếu bạn chỉ muốn lặp qua tất cả các mục trong một vectơ, chỉ cần sử dụng kiểu vòng lặp chỉ mục.

Nó ít gõ và dễ phân tích hơn đối với hầu hết mọi người. Sẽ thật tuyệt nếu C ++ có một vòng lặp foreach đơn giản mà không quá nhiệt tình với ma thuật mẫu.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

Tôi không nghĩ rằng nó làm cho nhiều vector khác nhau. Tôi thích sử dụng một chỉ mục cho bản thân mình vì tôi cho rằng nó dễ đọc hơn và bạn có thể truy cập ngẫu nhiên như nhảy về phía trước 6 mục hoặc nhảy lùi nếu cần.

Tôi cũng muốn tạo một tham chiếu đến mục bên trong vòng lặp như thế này để không có nhiều dấu ngoặc vuông xung quanh vị trí:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

Sử dụng một trình vòng lặp có thể tốt nếu bạn nghĩ rằng bạn có thể cần phải thay thế vectơ bằng một danh sách tại một thời điểm nào đó trong tương lai và nó cũng có vẻ phong cách hơn đối với các quái vật STL nhưng tôi không thể nghĩ ra bất kỳ lý do nào khác.


hầu hết các thuật toán hoạt động một lần trên mỗi phần tử của một container, theo tuần tự. Tất nhiên, có những trường hợp ngoại lệ trong đó bạn muốn duyệt qua một bộ sưu tập theo một thứ tự hoặc cách thức cụ thể, nhưng trong trường hợp này tôi sẽ cố gắng và viết một thuật toán tích hợp với STL và hoạt động với các trình vòng lặp.
wilmustell

Điều này sẽ khuyến khích việc tái sử dụng và tránh các lỗi do lỗi sau này. Sau đó, tôi gọi thuật toán đó giống như bất kỳ thuật toán tiêu chuẩn nào khác, với các trình vòng lặp.
wilmustell

1
Thậm chí không cần trước (). Trình lặp có các toán tử + = và - = giống như một chỉ mục (đối với các thùng chứa vectơ và vectơ).
MSalters

I prefer to use an index myself as I consider it to be more readablechỉ trong một số tình huống; ở những người khác, các chỉ số nhanh chóng trở nên rất lộn xộn. and you can do random accessvốn không phải là một tính năng độc đáo của các chỉ số: xem en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d

3

Hình thức thứ hai thể hiện những gì bạn đang làm chính xác hơn. Trong ví dụ của bạn, bạn không quan tâm đến giá trị của i, thực sự - tất cả những gì bạn muốn là yếu tố tiếp theo trong trình vòng lặp.


3

Sau khi tìm hiểu thêm một chút về chủ đề của câu trả lời này, tôi nhận ra đó là một chút đơn giản hóa. Sự khác biệt giữa vòng lặp này:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Và vòng lặp này:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Là khá tối thiểu. Trong thực tế, cú pháp thực hiện các vòng lặp theo cách này dường như đang phát triển trong tôi:

while (it != end){
    //do stuff
    ++it;
}

Các trình vòng lặp thực hiện mở khóa một số tính năng khai báo khá mạnh mẽ và khi kết hợp với thư viện thuật toán STL, bạn có thể thực hiện một số điều khá thú vị nằm ngoài phạm vi quản trị chỉ mục mảng.


Sự thật là nếu tất cả các trình vòng lặp nhỏ gọn như ví dụ cuối cùng của bạn, ngay lập tức, tôi sẽ không gặp vấn đề gì với chúng. Tất nhiên, điều đó thực sự tương đương với for (Iter it = {0}; it != end; ++it) {...}- bạn vừa bỏ qua phần khai báo - vì vậy sự ngắn gọn không khác nhiều so với ví dụ thứ hai của bạn. Tuy nhiên, +1.
Kỹ sư

3

Lập chỉ mục đòi hỏi một mulhoạt động thêm . Ví dụ, cho vector<int> v, trình biên dịch chuyển đổi v[i]thành &v + sizeof(int) * i.


Có lẽ không phải là một bất lợi đáng kể so với các trình vòng lặp trong hầu hết các trường hợp, nhưng đó là một điều tốt để nhận thức được.
tộc

3
Đối với truy cập yếu tố duy nhất bị cô lập, có lẽ. Nhưng nếu chúng ta đang nói về các vòng lặp - giống như OP - thì tôi chắc chắn câu trả lời này dựa trên trình biên dịch không tối ưu hóa tưởng tượng. Bất kỳ một nửa nào cũng sẽ có cơ hội và khả năng lưu trữ bộ đệm sizeofvà chỉ cần thêm nó một lần mỗi lần lặp, thay vì thực hiện lại toàn bộ phép tính bù trừ mỗi lần.
gạch dưới

2

Trong quá trình lặp lại, bạn không cần biết số lượng mục cần xử lý. Bạn chỉ cần các mục và lặp đi lặp lại làm những điều như vậy rất tốt.


2

Chưa ai đề cập đến một ưu điểm của các chỉ số là chúng không trở nên không hợp lệ khi bạn nối vào một thùng chứa liền kề như thế std::vector, vì vậy bạn có thể thêm các mục vào vùng chứa trong quá trình lặp.

Điều này cũng có thể với các trình vòng lặp, nhưng bạn phải gọi reserve()và do đó cần phải biết có bao nhiêu mục bạn sẽ nối thêm.


1

Vài điểm tốt rồi. Tôi có một vài ý kiến ​​bổ sung:

  1. Giả sử chúng ta đang nói về thư viện chuẩn C ++, "vectơ" ngụ ý một thùng chứa truy cập ngẫu nhiên có các đảm bảo của mảng C (truy cập ngẫu nhiên, bố trí bộ nhớ contiguos, v.v.). Nếu bạn đã nói 'some_container', nhiều câu trả lời ở trên sẽ chính xác hơn (tính độc lập của container, v.v.).

  2. Để loại bỏ bất kỳ sự phụ thuộc nào vào tối ưu hóa trình biên dịch, bạn có thể di chuyển some_vector.size () ra khỏi vòng lặp trong mã được lập chỉ mục, như vậy:

    const size_t numElems = some_vector.size ();
    cho (size_t i = 0; i 
  3. Luôn luôn lặp trước tăng dần và coi sau tăng là trường hợp ngoại lệ.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// làm công cụ}

Vì vậy, giả sử và lập chỉ mục std::vector<> như container, không có lý do chính đáng để thích cái này hơn cái khác, tuần tự đi qua container. Nếu bạn phải thường xuyên tham khảo các chỉ mục ưu tú cũ hơn hoặc mới hơn, thì phiên bản được lập chỉ mục sẽ phù hợp hơn.

Nói chung, việc sử dụng các trình vòng lặp được ưu tiên hơn vì các thuật toán sử dụng chúng và hành vi có thể được kiểm soát (và được ghi lại thành tài liệu) bằng cách thay đổi loại trình vòng lặp. Các vị trí mảng có thể được sử dụng thay cho các trình vòng lặp, nhưng sự khác biệt về cú pháp sẽ xuất hiện.


1

Tôi không sử dụng các trình vòng lặp cho cùng một lý do tôi không thích các câu lệnh foreach. Khi có nhiều vòng lặp bên trong, đủ khó để theo dõi các biến toàn cục / thành viên mà không cần phải nhớ tất cả các giá trị cục bộ và tên lặp. Điều tôi thấy hữu ích là sử dụng hai bộ chỉ số cho các dịp khác nhau:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Tôi thậm chí không muốn viết tắt những thứ như "animation_matrices [i]" thành một số iterator "anim_matrix" ngẫu nhiên, vì sau đó bạn không thể thấy rõ giá trị của mảng này bắt nguồn từ mảng nào.


Tôi không thấy các chỉ số tốt hơn theo nghĩa này. Bạn có thể dễ dàng sử dụng lặp và chỉ cần chọn một quy ước để tên của họ: it, jt, kt, vv hoặc thậm chí chỉ cần tiếp tục sử dụng i, j, k, vv Và nếu bạn cần phải biết chính xác những gì một iterator đại diện, sau đó đối với tôi cái gì đó như for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()sẽ mô tả nhiều hơn vì phải liên tục chỉ số như animation_matrices[animIndex][boneIndex].
gạch dưới

wow, cảm giác như đã lâu rồi khi tôi viết ý kiến ​​đó. ngày nay sử dụng cả hai trình lặp foreach và c ++ mà không co rúm nhiều. Tôi đoán làm việc với mã lỗi trong nhiều năm sẽ tăng khả năng chịu đựng của một người, vì vậy việc chấp nhận tất cả các cú pháp và quy ước ... dễ dàng hơn, miễn là nó có thể về nhà bạn biết;)
AareP

Haha, thực sự, tôi đã không thực sự nhìn vào cái này bao nhiêu tuổi trước đây! Một điều khác mà tôi không nghĩ đến lần trước là ngày nay chúng ta cũng có forvòng lặp dựa trên phạm vi , điều này làm cho cách thức dựa trên vòng lặp thực hiện điều này thậm chí còn ngắn gọn hơn.
gạch dưới

1
  • Nếu bạn thích gần gũi với kim loại / không tin tưởng vào chi tiết triển khai của họ, đừng sử dụng các trình vòng lặp.
  • Nếu bạn thường xuyên chuyển đổi một loại bộ sưu tập cho loại khác trong quá trình phát triển, hãy sử dụng các trình vòng lặp.
  • Nếu bạn cảm thấy khó nhớ cách lặp lại các loại bộ sưu tập khác nhau (có thể bạn có một số loại từ một số nguồn bên ngoài khác nhau đang sử dụng), hãy sử dụng các trình lặp để thống nhất các phương tiện mà bạn đi qua các yếu tố. Điều này áp dụng để nói chuyển đổi một danh sách được liên kết với một danh sách mảng.

Thực sự, đó là tất cả để có nó. Trung bình không phải là bạn sẽ đạt được nhiều sự hấp dẫn hơn, và nếu sự ngắn gọn thực sự là mục tiêu của bạn, bạn luôn có thể quay trở lại với các macro.


1

Nếu bạn có quyền truy cập vào các tính năng của C ++ 11 , thì bạn cũng có thể sử dụng vòng lặp dựa trên phạm vifor để lặp lại trên vectơ của mình (hoặc bất kỳ vùng chứa nào khác) như sau:

for (auto &item : some_vector)
{
     //do stuff
}

Lợi ích của vòng lặp này là bạn có thể truy cập trực tiếp các phần tử của vectơ thông qua itembiến mà không gặp rủi ro làm rối chỉ mục hoặc mắc lỗi khi hủy bỏ trình duyệt lặp. Ngoài ra, trình giữ chỗ autongăn bạn khỏi phải lặp lại loại phần tử vùng chứa, điều này đưa bạn đến gần hơn với giải pháp độc lập với vùng chứa.

Ghi chú:

  • Nếu bạn cần chỉ số phần tử trong vòng lặp của bạn và operator[]tồn tại cho vùng chứa của bạn (và đủ nhanh cho bạn), thì tốt hơn bạn nên đi theo cách đầu tiên.
  • Một forvòng lặp dựa trên phạm vi không thể được sử dụng để thêm / xóa các phần tử vào / từ một container. Nếu bạn muốn làm điều đó, thì tốt hơn hãy bám vào giải pháp được đưa ra bởi Brian Matthews.
  • Nếu bạn không muốn thay đổi các thành phần trong vùng chứa của mình, thì bạn nên sử dụng từ khóa constnhư sau : for (auto const &item : some_vector) { ... }.

0

Thậm chí tốt hơn "nói cho CPU biết phải làm gì" (bắt buộc) là "nói với các thư viện những gì bạn muốn" (chức năng).

Vì vậy, thay vì sử dụng các vòng lặp, bạn nên tìm hiểu các thuật toán có trong stl.



0

Tôi luôn sử dụng chỉ mục mảng vì nhiều ứng dụng của tôi yêu cầu một cái gì đó như "hiển thị hình ảnh thu nhỏ". Vì vậy, tôi đã viết một cái gì đó như thế này:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

Cả hai cách triển khai đều đúng, nhưng tôi thích vòng lặp 'for' hơn. Vì chúng tôi đã quyết định sử dụng Vector và không phải bất kỳ container nào khác, sử dụng các chỉ mục sẽ là lựa chọn tốt nhất. Sử dụng các trình vòng lặp với vectơ sẽ làm mất đi lợi ích của việc có các đối tượng trong các khối bộ nhớ liên tục giúp dễ dàng truy cập.


2
"Sử dụng các trình vòng lặp với vectơ sẽ làm mất đi lợi ích của việc có các đối tượng trong các khối bộ nhớ liên tục giúp dễ dàng truy cập." [cần dẫn nguồn]. Tại sao? Bạn có nghĩ rằng sự gia tăng của một trình vòng lặp đến một thùng chứa liền kề không thể được thực hiện như một sự bổ sung đơn giản không?
gạch dưới

0

Tôi cảm thấy rằng không có câu trả lời nào ở đây giải thích lý do tại sao tôi thích các trình vòng lặp như một khái niệm chung về việc lập chỉ mục vào các thùng chứa. Lưu ý rằng hầu hết trải nghiệm của tôi khi sử dụng các trình vòng lặp không thực sự đến từ C ++ mà từ các ngôn ngữ lập trình cấp cao hơn như Python.

Giao diện iterator áp đặt ít yêu cầu hơn đối với người tiêu dùng về chức năng của bạn, cho phép người tiêu dùng làm nhiều hơn với nó.

Nếu tất cả bạn cần là để có thể chuyển tiếp-lặp, các nhà phát triển không giới hạn việc sử dụng container lập chỉ mục - họ có thể sử dụng bất kỳ lớp thực hiện operator++(T&), operator*(T)operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

Thuật toán của bạn hoạt động cho trường hợp bạn cần - lặp lại qua một vectơ - nhưng nó cũng có thể hữu ích cho các ứng dụng mà bạn không nhất thiết phải dự đoán:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

Cố gắng thực hiện một toán tử ngoặc vuông làm một cái gì đó tương tự như trình vòng lặp này sẽ bị hạn chế, trong khi việc thực hiện vòng lặp tương đối đơn giản. Toán tử dấu ngoặc vuông cũng đưa ra hàm ý về các khả năng của lớp của bạn - rằng bạn có thể lập chỉ mục cho bất kỳ điểm tùy ý nào - có thể khó thực hiện hoặc không hiệu quả.

Lặp đi lặp lại cũng cho vay để trang trí . Mọi người có thể viết các trình vòng lặp lấy một trình vòng lặp trong hàm tạo của chúng và mở rộng chức năng của nó:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

Mặc dù những đồ chơi này có vẻ trần tục, nhưng không khó để tưởng tượng bằng cách sử dụng các trình lặp và trang trí lặp để làm những việc mạnh mẽ với giao diện đơn giản - trang trí một trình lặp kết quả cơ sở dữ liệu chỉ với một trình vòng lặp xây dựng một đối tượng mô hình từ một kết quả duy nhất, ví dụ . Các mẫu này cho phép lặp lại hiệu quả bộ nhớ của các bộ vô hạn và, với bộ lọc như bộ tôi đã viết ở trên, có khả năng đánh giá kết quả lười biếng.

Một phần sức mạnh của các mẫu C ++ là giao diện lặp của bạn, khi được áp dụng cho các mảng C có độ dài cố định, phân rã thành số học con trỏ đơn giản và hiệu quả , làm cho nó trở thành một sự trừu tượng hóa chi phí thực sự.

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.