Làm thế nào để dựa trên phạm vi hoạt động cho các mảng đơn giản?


87

Trong C ++ 11, bạn có thể sử dụng một phạm vi dựa trên for, hoạt động như foreachcác ngôn ngữ khác. Nó hoạt động ngay cả với các mảng C đơn giản:

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

Làm thế nào nó biết khi nào để dừng lại? Nó chỉ hoạt động với các mảng tĩnh đã được khai báo trong cùng phạm vi forđược sử dụng? Làm thế nào bạn sẽ sử dụng điều này forvới các mảng động?


10
Không có mảng "động" nào trong C hoặc C ++ - có những kiểu mảng và sau đó có những con trỏ có thể trỏ đến một mảng hoặc một khối bộ nhớ được cấp phát động, chủ yếu hoạt động giống như một mảng. Đối với bất kỳ mảng nào thuộc kiểu T [n], kích thước của nó được mã hóa theo kiểu và có thể được truy cập bởi for. Nhưng thời điểm mảng đó phân rã thành một con trỏ, thông tin về kích thước sẽ bị mất.
JohannesD

1
Trong ví dụ của bạn, số lượng các yếu tố trong numberssizeof(numbers)/sizeof(int), ví dụ.
JohannesD

Câu trả lời:


57

Nó hoạt động cho bất kỳ biểu thức nào có kiểu là một mảng. Ví dụ:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

Để được giải thích chi tiết hơn, nếu kiểu của biểu thức được truyền sang bên phải :là kiểu mảng, thì vòng lặp sẽ lặp lại từ ptrđến ptr + size( ptrtrỏ đến phần tử đầu tiên của mảng, sizelà số phần tử của mảng).

Điều này trái ngược với các kiểu do người dùng xác định, hoạt động bằng cách tìm kiếm beginendlà thành viên nếu bạn truyền một đối tượng lớp hoặc (nếu không có thành viên nào được gọi theo cách đó) các hàm không phải thành viên. Các hàm đó sẽ mang lại các trình vòng lặp bắt đầu và kết thúc (trỏ đến trực tiếp sau phần tử cuối cùng và phần đầu của chuỗi tương ứng).

Câu hỏi này làm rõ tại sao sự khác biệt đó tồn tại.


8
Tôi nghĩ câu hỏi là nó hoạt động như thế nào , chứ không phải khi nào nó hoạt động
xem

1
@sehe câu hỏi chứa nhiều '?' es. Một là "Nó hoạt động với ...?". Tôi đã giải thích cả về cách thứcthời điểm nó hoạt động.
Johannes Schaub - litb

8
@JohannesSchaub: Tôi nghĩ vấn đề "làm thế nào" ở đây là cách bạn lấy kích thước chính xác của một đối tượng thuộc kiểu mảng ngay từ đầu (vì sự nhầm lẫn giữa con trỏ và mảng, không phải ai cũng biết rằng kích thước của mảng có sẵn cho lập trình viên.)
JohannesD

Tôi tin rằng nó chỉ trông cho người không thành viên begin'cuối . It just happens that std :: bắt đầu `std::endsử dụng các hàm thành viên, và sẽ được sử dụng nếu một trận đấu tốt hơn là không có sẵn.
Dennis Zickefoose

3
@Dennis no ở Madrid đã quyết định thay đổi điều đó và ưu tiên các thành viên bắt đầu và kết thúc. Không ủng hộ các thành viên bắt đầu và kết thúc đã gây ra sự mơ hồ khó tránh.
Johannes Schaub - litb

44

Tôi nghĩ rằng phần quan trọng nhất của câu hỏi này là, làm thế nào C ++ biết kích thước của một mảng là bao nhiêu (ít nhất tôi muốn biết điều đó khi tôi tìm thấy câu hỏi này).

C ++ biết kích thước của một mảng, vì nó là một phần trong định nghĩa của mảng - đó là kiểu của biến. Một trình biên dịch phải biết loại.

Vì C ++ 11 std::extentcó thể được sử dụng để lấy kích thước của một mảng:

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

Tất nhiên, điều này không có nhiều ý nghĩa, vì bạn phải cung cấp rõ ràng kích thước ở dòng đầu tiên, sau đó bạn có được ở dòng thứ hai. Nhưng bạn cũng có thể sử dụng decltypevà sau đó nó trở nên thú vị hơn:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;

6
Đây thực sự là những gì tôi đã hỏi về ban đầu. :)
Paul Manta

19

Theo Bản nháp làm việc C ++ mới nhất (n3376), câu lệnh có phạm vi cho tương đương như sau:

{
    auto && __range = range-init;
    for (auto __begin = begin-expr,
              __end = end-expr;
            __begin != __end;
            ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

Vì vậy, nó biết làm thế nào để dừng lại theo cùng một cách for vòng lặp sử dụng trình vòng lặp làm.

Tôi nghĩ rằng bạn có thể đang tìm kiếm thứ gì đó giống như sau để cung cấp cách sử dụng cú pháp trên với các mảng chỉ bao gồm một con trỏ và kích thước (mảng động):

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

Lớp mẫu này sau đó có thể được sử dụng để tạo ra một phạm vi, qua đó bạn có thể lặp lại bằng cách sử dụng mới dao động cho cú pháp. Tôi đang sử dụng điều này để chạy qua tất cả các đối tượng hoạt ảnh trong một cảnh được nhập bằng thư viện chỉ trả về một con trỏ đến một mảng và một kích thước dưới dạng các giá trị riêng biệt.

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

Cú pháp này, theo ý kiến ​​của tôi, rõ ràng hơn nhiều so với những gì bạn sẽ nhận được bằng cách sử dụng std::for_eachhoặc một forvòng lặp đơn giản .


3

Nó biết khi nào nên dừng lại vì nó biết giới hạn của các mảng tĩnh.

Tôi không rõ ý của bạn về "mảng động", trong mọi trường hợp, nếu không lặp qua các mảng tĩnh, một cách không chính thức, trình biên dịch sẽ tra cứu tên beginendtrong phạm vi lớp của đối tượng mà bạn lặp lại hoặc tìm lên cho begin(range)end(range) sử dụng tra cứu và sử dụng chúng như lặp luận phụ thuộc.

Để biết thêm thông tin, trong tiêu chuẩn C ++ 11 (hoặc bản nháp công khai của nó), "6.5.4 Câu forlệnh dựa trên phạm vi ", pg.145


4
Một "mảng động" sẽ được tạo với new[]. Trong trường hợp đó, bạn chỉ có một con trỏ không có chỉ báo về kích thước, vì vậy không có cách nào để phạm vi dựa trên forhoạt động với nó.
Mike Seymour

Câu trả lời của tôi bao gồm một mảng động có kích thước (4) được biết tại thời điểm biên dịch, nhưng tôi không biết liệu cách giải thích "mảng động" đó có phải là ý định của người hỏi hay không.
Johannes Schaub - litb

3

Làm thế nào để dựa trên phạm vi hoạt động cho các mảng đơn giản?

Điều đó có nghĩa là, " Hãy cho tôi biết một ranged-for làm gì (với mảng)? "

Tôi sẽ trả lời giả sử rằng - Lấy ví dụ sau bằng cách sử dụng các mảng lồng nhau:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

Phiên bản văn bản:

ialà một mảng các mảng ("mảng lồng nhau"), chứa [3]các mảng, với mỗi [4]giá trị chứa . Ví dụ trên lặp lại iatheo 'phạm vi' ( [3]) chính của nó và do đó lặp lại [3]thời gian. Mỗi vòng lặp tạo ra một trong iacác [3]giá trị chính bắt đầu từ giá trị đầu tiên và kết thúc bằng [4]giá trị cuối cùng - Một mảng chứa các giá trị.

  • Vòng lặp đầu tiên: plbằng {1,2,3,4}- Một mảng
  • Vòng lặp thứ hai: plbằng {5,6,7,8}- Một mảng
  • Vòng lặp thứ ba: plbằng {9,10,11,12}- Một mảng

Trước khi chúng tôi giải thích quy trình, đây là một số lời nhắc thân thiện về mảng:

  • Mảng được hiểu là con trỏ đến giá trị đầu tiên của chúng - Sử dụng mảng mà không có bất kỳ lần lặp nào trả về địa chỉ của giá trị đầu tiên
  • pl phải là một tham chiếu vì chúng tôi không thể sao chép các mảng
  • Với mảng, khi bạn thêm một số vào chính đối tượng mảng, nó sẽ tiến lên nhiều lần và 'trỏ' tới mục nhập tương đương - Nếu nlà số được đề cập, thì ia[n]giống như *(ia+n)(Chúng tôi đang tham khảo địa chỉ của nmục nhập chuyển tiếp), và ia+ngiống như &ia[n](Chúng tôi đang lấy địa chỉ của mục nhập đó trong mảng).

Đây là những gì đang xảy ra:

  • Trên mỗi vòng lặp, plđược thiết lập như một tài liệu tham khảo để ia[n], với nbằng với số lần lặp hiện tại bắt đầu từ 0. Vì vậy, plia[0]trên vòng đầu tiên, ngày thứ hai nó ia[1], và vân vân. Nó lấy giá trị thông qua lặp lại.
  • Vòng lặp cứ kéo dài như vậy ia+nlà ít hơn end(ia).

... Và đó là về nó.

Nó thực sự chỉ là một cách đơn giản để viết điều này :

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int n = 0; n != 3; ++n)
  auto &pl = ia[n];

Nếu mảng của bạn không được lồng vào nhau, thì quá trình này sẽ trở nên đơn giản hơn một chút vì không cần tham chiếu , bởi vì giá trị được lặp lại không phải là một mảng mà là giá trị 'bình thường':

 int ib[3] = {1,2,3};

 // short
 for (auto pl : ib)
   cout << pl;

 // long
 for (int n = 0; n != 3; ++n)
   cout << ib[n];

Một số thông tin bổ sung

Điều gì sẽ xảy ra nếu chúng tôi không muốn sử dụng autotừ khóa khi tạo pl? Nó sẽ trông như thế nào?

Trong ví dụ sau, plđề cập đến một array of four integers. Trên mỗi vòng lặp plđược cung cấp giá trị ia[n]:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int (&pl)[4] : ia)

Và ... Đó là cách nó hoạt động, với thông tin bổ sung để loại bỏ mọi nhầm lẫn. Nó chỉ là một forvòng lặp 'tốc ký' tự động tính cho bạn, nhưng thiếu cách để lấy lại vòng lặp hiện tại mà không cần thực hiện thủ công.


@Andy 9 trong số 10 lần tiêu đề phù hợp với Google / bất kỳ tìm kiếm nào - Tiêu đề hỏi chúng hoạt động như thế nào? , không biết khi nào thì dừng? . Mặc dù vậy, câu hỏi cơ bản ngụ ý được bao hàm trong câu trả lời này ở một mức độ nào đó và sẽ tiếp tục trả lời cho bất kỳ ai khác đang tìm kiếm câu trả lời khác . Các câu hỏi cú pháp như thế này nên có tiêu đề được viết theo cụm từ để câu trả lời có thể được viết chỉ bằng cách sử dụng nó vì đó là tất cả thông tin mà người tìm kiếm cần để tìm câu hỏi. Bạn chắc chắn không sai - Câu hỏi không có tiêu đề như nó phải như vậy.
Super Cat

0

Một số mã mẫu để chứng minh sự khác biệt giữa mảng trên Stack và mảng trên Heap


/**
 * Question: Can we use range based for built-in arrays
 * Answer: Maybe
 * 1) Yes, when array is on the Stack
 * 2) No, when array is the Heap
 * 3) Yes, When the array is on the Stack,
 *    but the array elements are on the HEAP
 */
void testStackHeapArrays() {
  int Size = 5;
  Square StackSquares[Size];  // 5 Square's on Stack
  int StackInts[Size];        // 5 int's on Stack
  // auto is Square, passed as constant reference
  for (const auto &Sq : StackSquares)
    cout << "StackSquare has length " << Sq.getLength() << endl;
  // auto is int, passed as constant reference
  // the int values are whatever is in memory!!!
  for (const auto &I : StackInts)
    cout << "StackInts value is " << I << endl;

  // Better version would be: auto HeapSquares = new Square[Size];
  Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's it has
  // for (auto &Sq : HeapSquares)
  //    cout << "HeapSquare has length " << Sq.getLength() << endl;

  // does not compile, same reason as above
  // for (const auto &I : HeapInts)
  //  cout << "HeapInts value is " << I << endl;

  // Create 3 Square objects on the Heap
  // Create an array of size-3 on the Stack with Square pointers
  // size of array is known to compiler
  Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
  // auto is Square*, passed as constant reference
  for (const auto &Sq : HeapSquares2)
    cout << "HeapSquare2 has length " << Sq->getLength() << endl;

  // Create 3 int objects on the Heap
  // Create an array of size-3 on the Stack with int pointers
  // size of array is known to compiler
  int *HeapInts2[]{new int(23), new int(57), new int(99)};
  // auto is int*, passed as constant reference
  for (const auto &I : HeapInts2)
    cout << "HeapInts2 has value " << *I << endl;

  delete[] HeapSquares;
  delete[] HeapInts;
  for (const auto &Sq : HeapSquares2) delete Sq;
  for (const auto &I : HeapInts2) delete I;
  // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
}
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.