Lặp lại trên std :: vector: unsign vs biến chỉ mục đã ký


470

Cách lặp chính xác của một vectơ trong C ++ là gì?

Hãy xem xét hai đoạn mã này, đoạn này hoạt động tốt:

for (unsigned i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

và cái này nữa:

for (int i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

mà tạo ra warning: comparison between signed and unsigned integer expressions.

Tôi là người mới trong thế giới của C ++, vì vậy unsignedbiến này có vẻ hơi đáng sợ đối với tôi và tôi biết unsignedcác biến có thể nguy hiểm nếu không được sử dụng đúng cách, vậy - điều này có đúng không?


10
Dấu không dấu là chính xác vì polygon.size () có kiểu không dấu. Không ký có nghĩa là luôn luôn dương hoặc 0. Đó là tất cả ý nghĩa của nó. Vì vậy, nếu việc sử dụng biến luôn chỉ dành cho số đếm thì không dấu là lựa chọn đúng.
Adam Brussel

3
@AdamBruss .size()không phải là loại unsignedaka unsigned int. Đó là loại std::size_t.
gạch dưới

1
@underscore_d size_t là một bí danh cho dấu không dấu.
Adam Brussel

2
@AdamBruss Số std::size_tlà một typedef được xác định bằng _im THỰC HÀNH . Xem Tiêu chuẩn. std::size_tcó thể tương đương với unsignedviệc triển khai hiện tại của bạn, nhưng điều đó không liên quan. Giả vờ nó có thể dẫn đến mã không di động và hành vi không xác định.
gạch dưới

2
@LF ... chắc chắn, có lẽ là std::size_ttrong thực tế. Bạn có nghĩ rằng chúng tôi đã bao gồm tất cả mọi thứ trong dòng ý kiến ​​lan man hơn 6 năm này không?
gạch dưới

Câu trả lời:


817

Đối với lặp đi lặp lại xem câu trả lời này .

Lặp đi lặp lại gần như giống hệt nhau. Chỉ cần thay đổi các lần lặp / giảm trao đổi theo gia số. Bạn nên thích lặp đi lặp lại. Một số người bảo bạn sử dụng std::size_tlàm kiểu biến chỉ số. Tuy nhiên, đó không phải là di động. Luôn luôn sử dụng size_typetypedef của container (Mặc dù bạn có thể thoát khỏi chỉ với một chuyển đổi trong trường hợp lặp lại phía trước, nhưng nó thực sự có thể sai hoàn toàn trong trường hợp lặp lại khi sử dụng std::size_t, trong trường hợp std::size_trộng hơn so với typedef của size_type) :


Sử dụng std :: vector

Sử dụng các vòng lặp

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    /* std::cout << *it; ... */
}

Quan trọng là, luôn luôn sử dụng biểu mẫu tăng tiền tố cho các trình vòng lặp có định nghĩa mà bạn không biết. Điều đó sẽ đảm bảo mã của bạn chạy chung chung nhất có thể.

Sử dụng Phạm vi C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Sử dụng chỉ số

for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
    /* std::cout << v[i]; ... */
}

Sử dụng mảng

Sử dụng các vòng lặp

for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
    /* std::cout << *it; ... */
}

Sử dụng Phạm vi C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Sử dụng chỉ số

for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
    /* std::cout << a[i]; ... */
}

Đọc trong câu trả lời lặp đi lặp lại những gì vấn đề mà sizeofphương pháp có thể mang lại, mặc dù.


loại kích thước của con trỏ: sử dụng differ_type có thể dễ mang theo hơn. thử iterator_traits <Element_type *> :: Dif_type. đây là một
câu nói ngắn gọn

wilmustell, tôi nên sử dụng Dif_type để làm gì? sizeof được định nghĩa để trả về size_t :) tôi không hiểu bạn. nếu tôi trừ các con trỏ khỏi nhau, Dif_type sẽ là lựa chọn đúng đắn.
Julian Schaub - litb

Lặp lại các mảng bằng cách sử dụng kỹ thuật mà bạn đã đề cập trong bài đăng này sẽ không hoạt động nếu việc lặp lại đang được thực hiện trong một hàm trên một mảng được truyền cho hàm đó. Bởi vì mảng sizeof sẽ chỉ trả về con trỏ sizeof.
lỗi

1
@Nils Tôi đồng ý rằng sử dụng bộ đếm vòng không dấu là một ý tưởng tồi. nhưng vì thư viện chuẩn sử dụng các kiểu số nguyên không dấu cho chỉ mục và kích thước, tôi thích các loại chỉ mục không dấu cho thư viện chuẩn. do đó, các thư viện khác chỉ sử dụng các loại đã ký, như lib Qt.
Julian Schaub - litb

32
Cập nhật cho C ++ 11: phạm vi dựa trên vòng lặp. for (auto p : polygon){sum += p;}
Siyuan Ren

170

Bốn năm trôi qua, Google đã cho tôi câu trả lời này. Với C ++ 11 tiêu chuẩn (còn gọi là C ++ 0x ) thực sự có một cách dễ chịu mới để làm điều này (với mức giá phá vỡ tính tương thích ngược): autotừ khóa mới . Nó giúp bạn bớt đau đớn khi phải chỉ định rõ ràng loại trình lặp sẽ sử dụng (lặp lại loại vectơ), khi nó rõ ràng (với trình biên dịch), loại nào sẽ sử dụng. Với vviệc là của bạn vector, bạn có thể làm một cái gì đó như thế này:

for ( auto i = v.begin(); i != v.end(); i++ ) {
    std::cout << *i << std::endl;
}

C ++ 11 thậm chí còn đi xa hơn và cung cấp cho bạn một cú pháp đặc biệt để lặp qua các bộ sưu tập như vectơ. Nó loại bỏ sự cần thiết phải viết những thứ luôn giống nhau:

for ( auto &i : v ) {
    std::cout << i << std::endl;
}

Để xem nó trong một chương trình làm việc, hãy tạo một tệp auto.cpp:

#include <vector>
#include <iostream>

int main(void) {
    std::vector<int> v = std::vector<int>();
    v.push_back(17);
    v.push_back(12);
    v.push_back(23);
    v.push_back(42);
    for ( auto &i : v ) {
        std::cout << i << std::endl;
    }
    return 0;
}

Khi viết bài này, khi bạn biên dịch nó với g ++ , thông thường bạn cần phải thiết lập nó để hoạt động với tiêu chuẩn mới bằng cách đưa ra một cờ bổ sung:

g++ -std=c++0x -o auto auto.cpp

Bây giờ bạn có thể chạy ví dụ:

$ ./auto
17
12
23
42

Xin lưu ý rằng các hướng dẫn về biên dịch và chạy là dành riêng cho trình biên dịch gnu c ++ trên Linux , chương trình phải độc lập với nền tảng (và trình biên dịch).


7
C ++ 11 mang đến cho bạnfor (auto& val: vec)
Flexo

@flexo Cảm ơn, tôi không biết làm thế nào tôi có thể quên điều đó. Không làm đủ C ++, tôi đoán vậy. Không thể tin rằng có một cái gì đó thực tế (thực sự nghĩ đó là cú pháp JavaScript). Tôi đã thay đổi câu trả lời để bao gồm điều đó.
kratenko

Câu trả lời của bạn rất hay. Thật không may là phiên bản mặc định của g ++ trong các hệ điều hành khác nhau dưới 4.3 khiến cho nó không hoạt động.
Ratata Tata

Bạn có cần khởi tạo vectơ với std::vector<int> v = std::vector<int>();, hoặc đơn giản là bạn có thể sử dụng std::vector<int> v;thay thế?
Bill Cheatham

@BillCheatham Vâng - Tôi chỉ dùng thử mà không cần khởi tạo, và nó đã hoạt động, vì vậy có vẻ như nó hoạt động mà không có.
kratenko

44

Trong trường hợp cụ thể trong ví dụ của bạn, tôi sẽ sử dụng các thuật toán STL để thực hiện điều này.

#include <numeric> 

sum = std::accumulate( polygon.begin(), polygon.end(), 0 );

Đối với một trường hợp tổng quát hơn, nhưng vẫn khá đơn giản, tôi sẽ đi với:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );

38

Về câu trả lời của Julian Schaub:

for(std::vector<T*>::iterator it = v.begin(); it != v.end(); ++it) { 
...
}

Điều đó có thể làm việc với một số trình biên dịch nhưng không phải với gcc. Vấn đề ở đây là câu hỏi nếu std :: vector :: iterator là một kiểu, một biến (thành viên) hoặc một hàm (phương thức). Chúng tôi nhận được lỗi sau với gcc:

In member function void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant

Giải pháp là sử dụng từ khóa 'typename' như đã nói:

typename std::vector<T*>::iterator it = v.begin();
for( ; it != v.end(); ++it) {
...

2
Bạn nên giải thích rằng điều này chỉ áp dụng khi Tlà đối số mẫu và do đó biểu thức std::vector<T*>::iteratorlà tên phụ thuộc. Để một tên phụ thuộc được phân tích cú pháp dưới dạng một loại, nó cần phải được thêm vào bởi typenametừ khóa, như chẩn đoán chỉ ra.
Phục hồi

17

Một cuộc gọi để vector<T>::size()trả về một giá trị của loại std::vector<T>::size_type, không phải int, unsign int hoặc cách khác.

Ngoài ra, việc lặp lại thông thường trên một container trong C ++ được thực hiện bằng cách sử dụng các trình vòng lặp , như thế này.

std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();

for(; i != end; i++){
    sum += *i;
}

Trong đó T là loại dữ liệu bạn lưu trữ trong vector.

Hoặc sử dụng các thuật toán lặp khác nhau ( std::transform, std::copy, std::fill, std::for_eachvân vân).


Các trình vòng lặp nói chung là một ý tưởng tốt, mặc dù tôi nghi ngờ rằng cần phải lưu trữ "kết thúc" trong một biến riêng biệt và tất cả có thể được thực hiện trong một câu lệnh for (;;).
Saulius emaita viêm

1
Tôi biết start () và end () được khấu hao theo thời gian không đổi, nhưng tôi thường thấy điều này dễ đọc hơn là nhồi nhét mọi thứ vào một dòng.
Jasper Bekkers

3
Bạn có thể chia for thành các dòng riêng biệt để cải thiện khả năng đọc. Khai báo các vòng lặp bên ngoài vòng lặp có nghĩa là bạn cần một tên vòng lặp khác nhau cho mỗi vòng lặp trên các thùng chứa các loại khác nhau.
Jay Conrod

Tôi nhận thức được tất cả sự khác biệt, và những gì nó cơ bản xuất phát là sở thích cá nhân; đây thường là cách tôi kết thúc việc làm.
Jasper Bekkers

2
@pi saoagy Tôi đoán rằng sẽ được đặt nó trong phần đầu tiên của vòng lặp for. ví dụ. for (auto i = polygon.begin (), end = polygon.end (); i! = end; i ++)
Jasper Bekkers

11

Sử dụng size_t:

for (size_t i=0; i < polygon.size(); i++)

Trích dẫn Wikipedia :

Các tệp tiêu đề stdlib.h và stddef.h xác định một kiểu dữ liệu được gọi size_tlà được sử dụng để thể hiện kích thước của một đối tượng. Các hàm thư viện có kích thước mong đợi chúng có kiểu size_tvà toán tử sizeof ước tính size_t.

Loại thực tế size_tlà phụ thuộc vào nền tảng; một lỗi phổ biến là giả sử size_tgiống như int unsign, điều này có thể dẫn đến lỗi lập trình, đặc biệt khi kiến ​​trúc 64 bit trở nên phổ biến hơn.


size_t OK cho vector, vì nó phải lưu trữ tất cả các đối tượng trong một mảng (cũng là một đối tượng) nhưng danh sách std :: có thể chứa nhiều hơn các phần tử size_t!
MSalters

1
size_t thường đủ để liệt kê tất cả các byte trong không gian địa chỉ của một tiến trình. Mặc dù tôi có thể thấy làm thế nào điều này có thể không xảy ra trên một số kiến ​​trúc kỳ lạ, nhưng tôi không lo lắng về nó.

AFAIK khuyên bạn nên #include <cstddef>thay vì <stddef.h>hoặc tệ hơn là toàn bộ [c]stdlibvà sử dụng std::size_tthay vì phiên bản không đủ tiêu chuẩn - và tương tự cho bất kỳ tình huống nào khác mà bạn có lựa chọn giữa <cheader><header.h>.
gạch dưới

7

Một chút về lịch sử:

Để thể hiện một số có âm hay không, máy tính sử dụng bit 'dấu'. intlà một kiểu dữ liệu đã ký có nghĩa là nó có thể giữ các giá trị dương và âm (khoảng -2 tỷ đến 2 tỷ). Unsignedchỉ có thể lưu trữ các số dương (và vì nó không lãng phí một chút vào siêu dữ liệu nên nó có thể lưu trữ nhiều hơn: 0 đến khoảng 4 tỷ).

std::vector::size()trả về một unsigned, làm thế nào một vectơ có thể có chiều dài âm?

Cảnh báo cho bạn biết rằng toán hạng bên phải của câu lệnh bất đẳng thức của bạn có thể chứa nhiều dữ liệu hơn bên trái.

Về cơ bản, nếu bạn có một vectơ có hơn 2 tỷ mục nhập và bạn sử dụng một số nguyên để lập chỉ mục cho các vấn đề tràn (int sẽ quay trở lại âm 2 tỷ).


6

Tôi thường sử dụng BOOST_FOREACH:

#include <boost/foreach.hpp>

BOOST_FOREACH( vector_type::value_type& value, v ) {
    // do something with 'value'
}

Nó hoạt động trên các thùng chứa STL, mảng, chuỗi kiểu C, v.v.


2
Câu trả lời tốt cho một số câu hỏi khác (tôi nên lặp lại một vectơ như thế nào?), Nhưng hoàn toàn không phải là những gì OP đang hỏi (ý nghĩa của cảnh báo về một biến không dấu là gì?)
abelenky

3
Chà, anh ta hỏi cách lặp đúng của một vectơ là gì. Như vậy có vẻ đủ liên quan. Cảnh báo là tại sao anh ta không hài lòng với giải pháp hiện tại của mình.
jalf

5

Để hoàn thành, cú pháp C ++ 11 chỉ cho phép một phiên bản khác cho iterators ( ref ):

for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
  // do something with *it
}

Điều này cũng thoải mái cho việc lặp lại

for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
  // do something with *it
}

5

Trong C ++ 11

Tôi sẽ sử dụng các thuật toán chung như for_eachđể tránh tìm kiếm đúng loại biểu thức lặp và lambda để tránh các hàm / đối tượng được đặt tên thêm.

Ví dụ "khá" ngắn cho trường hợp cụ thể của bạn (giả sử đa giác là một vectơ số nguyên):

for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });

đã thử nghiệm trên: http://ideone.com/i6Ethd

Đừng quên bao gồm: thuật toán và, tất nhiên, vector :)

Microsoft thực sự cũng có một ví dụ hay về điều này:
nguồn: http://msdn.microsoft.com/en-us/l Library / dd293608.aspx

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
   // Create a vector object that contains 10 elements.
   vector<int> v;
   for (int i = 1; i < 10; ++i) {
      v.push_back(i);
   }

   // Count the number of even numbers in the vector by 
   // using the for_each function and a lambda.
   int evenCount = 0;
   for_each(v.begin(), v.end(), [&evenCount] (int n) {
      cout << n;
      if (n % 2 == 0) {
         cout << " is even " << endl;
         ++evenCount;
      } else {
         cout << " is odd " << endl;
      }
   });

   // Print the count of even numbers to the console.
   cout << "There are " << evenCount 
        << " even numbers in the vector." << endl;
}

4
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 

2
Đối với vectơ thì điều này là tốt, nhưng nói chung, tốt hơn là sử dụng ++ thay vì ++, trong trường hợp bản thân trình lặp là không tầm thường.
Steve Jessop

Cá nhân, tôi đã quen sử dụng ++ i, nhưng tôi nghĩ hầu hết mọi người thích kiểu i ++ (đoạn mã VS mặc định cho "for" là i ++). Chỉ cần một suy nghĩ
Mehrdad Afshari

@MehrdadAfshari Ai quan tâm "hầu hết mọi người" làm gì? "Hầu hết mọi người" đều sai về rất nhiều thứ. Post-inc / decrement trong đó giá trị trước không bao giờ được sử dụng là sai và không hiệu quả, ít nhất là về mặt lý thuyết - bất kể mức độ thường xuyên được sử dụng trong mã ví dụ phụ ở mọi nơi. Bạn không nên khuyến khích các thực hành xấu chỉ để làm cho mọi thứ trông quen thuộc hơn với những người chưa biết rõ hơn.
gạch dưới

2

Đầu tiên là loại chính xác, và chính xác trong một số ý nghĩa nghiêm ngặt. (Nếu bạn nghĩ là, kích thước không bao giờ có thể nhỏ hơn 0.) Cảnh báo đó đánh tôi là một trong những ứng cử viên tốt để bị bỏ qua, mặc dù.


2
Tôi nghĩ rằng đó là một ứng cử viên khủng khiếp bị bỏ qua - thật dễ dàng để sửa chữa và thỉnh thoảng xảy ra lỗi chính hãng do lỗi so sánh các giá trị được ký / không dấu một cách không phù hợp. Chẳng hạn, trong trường hợp này, nếu kích thước lớn hơn INT_MAX thì vòng lặp không bao giờ kết thúc.
Steve Jessop

... hoặc có thể nó chấm dứt ngay lập tức. Một trong hai. Phụ thuộc vào việc giá trị đã ký được chuyển đổi thành không dấu để so sánh hay không dấu được chuyển đổi thành đã ký. Trên nền tảng 64 bit với int 32 bit, tuy nhiên, như win64, int sẽ được thăng cấp thành size_t và vòng lặp không bao giờ kết thúc.
Steve Jessop

@SteveJessop: Bạn không thể nói chắc chắn rằng vòng lặp không bao giờ kết thúc. Trên vòng lặp khi i == INT_MAX, sau đó i++gây ra hành vi không xác định. Tại thời điểm này, bất cứ điều gì cũng có thể xảy ra.
Ben Voigt

@BenVoigt: đúng và vẫn không cung cấp căn cứ để bỏ qua cảnh báo :-)
Steve Jessop

2

Xem xét liệu bạn có cần lặp đi lặp lại không

Các <algorithm>tiêu đề tiêu chuẩn cung cấp cho chúng với cơ sở vật chất cho việc này:

using std::begin;  // allows argument-dependent lookup even
using std::end;    // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);

Các chức năng khác trong thư viện thuật toán thực hiện các tác vụ phổ biến - đảm bảo bạn biết những gì có sẵn nếu bạn muốn tiết kiệm công sức cho mình.


1

Ít chi tiết nhưng quan trọng: nếu bạn nói "for (auto it)" như sau, bạn sẽ có được một bản sao của đối tượng, không phải là phần tử thực tế:

struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.push_back(x);
for(auto it : v)
    it.i = 1;         // doesn't change the element v[0]

Để sửa đổi các phần tử của vectơ, bạn cần xác định iterator làm tham chiếu:

for(auto &it : v)

1

Nếu trình biên dịch của bạn hỗ trợ nó, bạn có thể sử dụng một phạm vi dựa trên để truy cập các phần tử vectơ:

vector<float> vertices{ 1.0, 2.0, 3.0 };

for(float vertex: vertices){
    std::cout << vertex << " ";
}

In: 1 2 3. Lưu ý, bạn không thể sử dụng kỹ thuật này để thay đổi các yếu tố của vectơ.


0

Hai đoạn mã làm việc như nhau. Tuy nhiên, tuyến int "không dấu là đúng. Sử dụng kiểu int không dấu sẽ hoạt động tốt hơn với vectơ trong trường hợp bạn đã sử dụng. Gọi hàm thành viên size () trên vectơ trả về giá trị nguyên không dấu, vì vậy bạn muốn so sánh biến "i" với một giá trị của loại riêng của nó.

Ngoài ra, nếu bạn vẫn còn một chút lo lắng về việc "unsign int" trông như thế nào trong mã của bạn, hãy thử "uint". Về cơ bản, đây là phiên bản rút gọn của "unsign int" và nó hoạt động giống hệt nhau. Bạn cũng không cần bao gồm các tiêu đề khác để sử dụng nó.


Số nguyên không dấu cho kích thước () không nhất thiết phải bằng "số nguyên không dấu" trong thuật ngữ C ++, thường 'số nguyên không dấu' trong trường hợp này là số nguyên không dấu 64 bit trong khi 'số nguyên không dấu' thường là 32 bit.
Medran

0

Thêm điều này khi tôi không thể tìm thấy nó được đề cập trong bất kỳ câu trả lời nào: đối với phép lặp dựa trên chỉ mục, chúng ta có thể sử dụng decltype(vec_name.size())để đánh giástd::vector<T>::size_type

Thí dụ

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
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.