Cách tốt nhất để thực hiện một vòng lặp ngược trong C / C # / C ++ là gì?


101

Tôi cần di chuyển ngược lại qua một mảng, vì vậy tôi có mã như sau:

for (int i = myArray.Length - 1; i >= 0; i--)
{
    // Do something
    myArray[i] = 42;
}

Có cách nào tốt hơn để làm điều này không?

Cập nhật: Tôi đã hy vọng rằng có thể C # có một số cơ chế tích hợp cho việc này như:

foreachbackwards (int i in myArray)
{
    // so easy
}

Cập nhật 2: Có những cách tốt hơn. Rune nhận giải với:

for (int i = myArray.Length; i-- > 0; )
{    
    //do something
}
//or
for (int i = myArray.Length; i --> 0; )
{
    // do something
}

trông thậm chí còn tốt hơn trong C thông thường (nhờ Twotymz):

for (int i = lengthOfArray; i--; )
{    
    //do something
}

Tôi không chắc mình hiểu tại sao bất kỳ lựa chọn thay thế nào lại tốt hơn, nếu sự tốt đẹp bao gồm sự rõ ràng hoặc khả năng bảo trì.
dkretz 09/08/08

Thật. Tôi đã hy vọng tìm ra một cách hợp lý hơn để làm điều này, vì tôi phải làm nó khá thường xuyên.
MusiGenesis

3
Hầu hết câu hỏi này là một dạng xem xét các câu trả lời bên dưới. Đề nghị nó được rút ngắn đáng kể.
einpoklum

Vui lòng xóa C và C ++ khỏi tiêu đề và thẻ bắt đầu bằng #.
jaskmar

Câu trả lời:


149

Mặc dù phải thừa nhận là hơi tối nghĩa, nhưng tôi sẽ nói rằng cách đánh máy dễ chịu nhất để làm điều này là

for (int i = myArray.Length; i --> 0; )
{
    //do something
}

32
Khi tôi lần đầu tiên đọc câu trả lời của bạn, có vẻ như nó thậm chí sẽ không biên dịch, vì vậy tôi cho rằng bạn là một người điên. Nhưng đó chính xác là những gì tôi đang tìm kiếm: một cách tốt hơn để viết vòng lặp for ngược.
MusiGenesis

3
Tôi nghĩ rằng tôi -> 0; đang có mục đích. Đó là những gì ông có nghĩa là bởi "typographically làm hài lòng"
Johannes Schaub - litb

16
Bản thân tôi thấy nó "khó hiểu về mặt đánh máy". Đối với tôi, dấu "-" có vẻ không đúng trừ khi nó nằm liền kề với biến mà nó đang ảnh hưởng.
MusiGenesis

26
Điều đó quá tối nghĩa và khó hiểu. Tôi chưa bao giờ viết một cái gì đó như thế này trong mã sản xuất ...
Mihai Todor

9
Aah toán tử go to (->) thực hiện thủ thuật!
nawfal

118

Trong C ++, về cơ bản, bạn có sự lựa chọn giữa việc lặp bằng cách sử dụng trình vòng lặp hoặc chỉ số. Tùy thuộc vào việc bạn có một mảng đơn thuần hay một mảng std::vector, bạn sử dụng các kỹ thuật khác nhau.

Sử dụng std :: vector

Sử dụng trình lặp

C ++ cho phép bạn làm điều này bằng cách sử dụng std::reverse_iterator:

for(std::vector<T>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    /* std::cout << *it; ... */
}

Sử dụng các chỉ số

Các loại không thể thiếu unsigned trả về bởi std::vector<T>::sizekhông luôn std::size_t. Nó có thể lớn hơn hoặc ít hơn. Điều này rất quan trọng để vòng lặp hoạt động.

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* std::cout << someVector[i]; ... */
}

Nó hoạt động, vì các giá trị kiểu tích phân không có dấu được xác định bằng mô đun số lượng bit của chúng. Do đó, nếu bạn đang thiết lập -N, bạn sẽ kết thúc ở(2 ^ BIT_SIZE) -N

Sử dụng Mảng

Sử dụng trình lặp

Chúng tôi đang sử dụng std::reverse_iteratorđể thực hiện lặp lại.

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

Sử dụng các chỉ số

Chúng ta có thể sử dụng an toàn std::size_tở đây, trái ngược với ở trên, vì sizeofluôn trả về std::size_ttheo định nghĩa.

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

Tránh các cạm bẫy với sizeof được áp dụng cho con trỏ

Trên thực tế, cách trên để xác định kích thước của một mảng rất tệ. Nếu a thực sự là một con trỏ thay vì một mảng (điều này xảy ra khá thường xuyên và những người mới bắt đầu sẽ nhầm lẫn nó), nó sẽ âm thầm không thành công. Một cách tốt hơn là sử dụng cách sau, sẽ không thành công tại thời điểm biên dịch, nếu được cung cấp một con trỏ:

template<typename T, std::size_t N> char (& array_size(T(&)[N]) )[N];

Nó hoạt động bằng cách lấy kích thước của mảng được truyền vào đầu tiên, sau đó khai báo để trả về một tham chiếu đến một mảng kiểu char có cùng kích thước. charđược định nghĩa là có sizeof: 1. Vì vậy, mảng được trả về sẽ có sizeof: N * 1, đó là những gì chúng ta đang tìm kiếm, chỉ với đánh giá thời gian biên dịch và chi phí thời gian chạy bằng không.

Thay vì làm

(sizeof a / sizeof *a)

Thay đổi mã của bạn để bây giờ nó hoạt động

(sizeof array_size(a))

Ngoài ra, nếu vùng chứa của bạn không có trình lặp ngược, bạn có thể sử dụng triển khai reverse_iterator của boost: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
MP24

array_size của bạn dường như hoạt động đối với các mảng được cấp phát tĩnh, nhưng không thành công đối với tôi khi một ví dụ là 'new int [7]'.
Nate Parsons

2
vâng, đó là mục đích của nó :) new int [7] trả về một con trỏ. vì vậy sizeof (new int [7]) không trả về 7 * sizeof (int), mà sẽ trả về sizeof (int *). array_size gây ra lỗi biên dịch cho trường hợp đó thay vì hoạt động âm thầm.
Johannes Schaub - litb

cũng thử array_size (+ statically_allocated_array), cách này cũng không thành công, vì toán tử + biến mảng thành một con trỏ tới phần tử đầu tiên của nó. sử dụng sizeof đồng bằng sẽ cung cấp cho bạn biết kích thước của một con trỏ một lần nữa
Johannes Schaub - litb

Đối với điều đầu tiên - tại sao không chỉ sử dụng endbegintheo thứ tự ngược lại?
Tomáš Zato - Phục hồi Monica

54

Trong C # , sử dụng Visual Studio 2005 trở lên, hãy nhập 'forr' và nhấn [TAB] [TAB] . Điều này sẽ mở rộng thành một forvòng lặp đi ngược lại qua một tập hợp.

Rất dễ mắc sai lầm (ít nhất là đối với tôi), nên tôi nghĩ rằng đưa đoạn mã này vào sẽ là một ý tưởng hay.

Điều đó nói rằng, tôi thích Array.Reverse()/ Enumerable.Reverse()và sau đó lặp lại chuyển tiếp tốt hơn - chúng nêu rõ ý định hơn.


41

Tôi luôn muốn mã rõ ràng chống lại mã ' dễ đánh máy '. Vì vậy, tôi sẽ luôn sử dụng:

for (int i = myArray.Length - 1; i >= 0; i--)  
{  
    // Do something ...  
}    

Bạn có thể coi đó là cách chuẩn để lặp lại.
Chỉ hai xu của tôi ...


Đã làm việc. Chưa làm điều này trong C #, nhưng cảm ơn bạn.
PCPGMR

Vậy làm thế nào nếu mảng thực sự lớn (vì vậy chỉ mục phải có kiểu không dấu)? size_t thực sự không có dấu, phải không?
lalala

Bạn có thể khai báo tôi là uint. Xem bài đăng của Marc Gravell.
Jack Griffin

Sẽ không hoạt động đối với kiểu không có dấu do bị quấn, không giống như kiểu dễ đánh máy. Không tốt.
Ocelot

17

Trong C # sử dụng Linq :

foreach(var item in myArray.Reverse())
{
    // do something
}

Điều này chắc chắn là đơn giản nhất, nhưng lưu ý rằng trong phiên bản hiện tại của CLR, việc đảo ngược danh sách luôn là hoạt động O (n) chứ không phải là hoạt động O (1) nếu nó là IList <T>; xem connect.microsoft.com/VisualStudio/feedback/…
Greg Beech

1
Có vẻ như .Reverse () tạo một bản sao cục bộ của mảng và sau đó lặp lại qua nó. Có vẻ như không hiệu quả khi sao chép một mảng chỉ để bạn có thể lặp lại qua nó. Tôi đoán bất kỳ bài đăng nào có "Linq" trong đó đều đáng được tăng phiếu bầu. :)
MusiGenesis

Có thể có sự đánh đổi, nhưng sau đó bạn gặp phải vấn đề rằng "đã bỏ phiếu cho câu trả lời" (i -> 0) có thể dẫn đến mã chậm hơn vì (i) phải được kiểm tra với kích thước của mảng mỗi lần được sử dụng như trong chỉ mục.
Keltex

1
Mặc dù tôi đồng ý rằng Linq là tuyệt vời, nhưng vấn đề với cách tiếp cận này là trình lặp không cho phép bạn chọn ra một cách có chọn lọc các mục của mảng - hãy xem xét tình huống mà bạn muốn điều hướng qua một phần của Mảng, nhưng không phải toàn bộ điều ...
jesses.co.tt

11

Đó chắc chắn là cách tốt nhất cho bất kỳ mảng nào có độ dài là kiểu tích phân có dấu. Đối với các mảng có độ dài là kiểu tích phân không dấu (ví dụ: một std::vectortrong C ++), thì bạn cần sửa đổi điều kiện kết thúc một chút:

for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
    // blah

Nếu bạn vừa nói i >= 0, điều này luôn đúng với một số nguyên không dấu, vì vậy vòng lặp sẽ là một vòng lặp vô hạn.


Trong C ++, "i--" sẽ tự động quấn quanh giá trị cao nhất nếu tôi là 0? Xin lỗi, tôi bị lỗi C ++.
MusiGenesis

Kinh ngạc. Các bạn chỉ giúp mình nguyên nhân gây ra lỗi làm đầy ổ cứng ở bài viết cách đây 12 năm mình đã viết. Điều tốt là tôi không làm việc trong C ++.
MusiGenesis

điều kiện kết thúc phải là: i <myArray.size (). Chỉ phụ thuộc vào thuộc tính tràn của int unsigned; không phải chi tiết triển khai của số nguyên và phôi. Do đó, dễ đọc hơn và hoạt động bằng các ngôn ngữ có các biểu diễn số nguyên thay thế.
ejgottl 09/08/08

Tôi nghĩ giải pháp tốt hơn cho tôi là ở lại thế giới C #, nơi tôi không thể làm tổn thương bất kỳ ai.
MusiGenesis

4

Co vẻ tôt vơi tôi. Nếu trình lập chỉ mục chưa được đánh dấu (uint, v.v.), bạn có thể phải tính đến điều đó. Gọi tôi là lười biếng, nhưng trong trường hợp đó (không dấu), tôi có thể chỉ sử dụng một biến phản:

uint pos = arr.Length;
for(uint i = 0; i < arr.Length ; i++)
{
    arr[--pos] = 42;
}

(thực ra, ngay cả ở đây bạn cũng cần phải cẩn thận với những trường hợp như arr.Length = uint.MaxValue ... có thể là a! = ở đâu đó ... tất nhiên, đó là trường hợp rất khó xảy ra!)


Tuy nhiên, điều "--pos" rất phức tạp. Trước đây, tôi đã từng có đồng nghiệp thích "sửa chữa" một cách ngẫu nhiên những thứ có vẻ không phù hợp với họ.
MusiGenesis

1
Sau đó, sử dụng .arr.Length-1 và pos--
Marc Gravell

4

Trong CI muốn làm điều này:


int i = myArray.Length;
while (i--) {
  myArray[i] = 42;
}

Ví dụ về C # do MusiGenesis thêm vào:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}

Tôi thích điều này. Tôi mất một hoặc hai giây để nhận ra rằng phương pháp của bạn hoạt động. Hy vọng bạn không phiền với phiên bản C # được thêm vào (dấu ngoặc nhọn bổ sung để chỉ mục được xác định phạm vi giống như trong vòng lặp for).
MusiGenesis

Tôi không chắc mình sẽ sử dụng điều này trong mã sản xuất, bởi vì nó sẽ gợi ra một phổ quát "WTF is this?" phản ứng từ bất kỳ ai phải duy trì nó, nhưng nó thực sự dễ nhập hơn vòng lặp for thông thường và không có dấu trừ hoặc ký hiệu> =.
MusiGenesis

1
Quá tệ là phiên bản C # cần thêm "> 0".
MusiGenesis

Tôi không chắc đó là ngôn ngữ gì. nhưng đoạn mã đầu tiên của bạn chắc chắn KHÔNG PHẢI là C :) chỉ để cho bạn biết điều hiển nhiên. Có thể bạn muốn viết C # và html không thể phân tích cú pháp nó hay gì đó?
Johannes Schaub - litb

@litb: Tôi đoán "myArray.Length" không hợp lệ C (?), nhưng phần vòng lặp while hoạt động và trông rất tuyệt.
MusiGenesis

3

Cách tốt nhất để làm điều đó trong C ++ có lẽ là sử dụng bộ điều hợp vòng lặp (hoặc tốt hơn, phạm vi), bộ điều hợp này sẽ chuyển đổi một cách lười biếng trình tự khi nó đang được duyệt.

Về cơ bản,

vector<value_type> range;
foreach(value_type v, range | reversed)
    cout << v;

Hiển thị phạm vi "phạm vi" (ở đây, nó trống, nhưng tôi khá chắc rằng bạn có thể tự thêm các phần tử) theo thứ tự ngược lại. Tất nhiên chỉ cần lặp lại phạm vi thì không được sử dụng nhiều, nhưng chuyển phạm vi mới đó sang các thuật toán và nội dung thì khá tuyệt.

Cơ chế này cũng có thể được sử dụng cho các mục đích sử dụng mạnh mẽ hơn:

range | transformed(f) | filtered(p) | reversed

Sẽ lười biếng tính toán phạm vi "range", trong đó hàm "f" được áp dụng cho tất cả các phần tử, các phần tử mà "p" không đúng sẽ bị loại bỏ và cuối cùng phạm vi kết quả được đảo ngược.

Cú pháp đường ống là IMO dễ đọc nhất, vì nó là infix. Bản cập nhật thư viện Boost.Range đang chờ xem xét thực hiện điều này, nhưng cũng khá đơn giản để tự thực hiện. Còn tuyệt hơn với lambda DSEL để tạo hàm f và vị từ p trong dòng.


bạn có thể cho chúng tôi biết bạn có "foreach" từ đâu không? Nó có phải là #define để BOOST_FOREACH không? Trông dễ thương hết mức.
Johannes Schaub - litb


1

Tôi thích một vòng lặp trong khi. Đối với tôi, điều đó rõ ràng hơn là giảm itrong điều kiện của vòng lặp for

int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}

0

Tôi sẽ sử dụng mã trong câu hỏi ban đầu, nhưng nếu bạn thực sự muốn sử dụng foreach và có chỉ mục số nguyên trong C #:

foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
{
    myArray[i] = 42; 
}

-1

Tôi sẽ thử trả lời câu hỏi của chính mình ở đây, nhưng tôi cũng không thực sự thích điều này:

for (int i = 0; i < myArray.Length; i++)
{
    int iBackwards = myArray.Length - 1 - i; // ugh
    myArray[iBackwards] = 666;
}

Thay vì làm .Length - 1 - i mỗi lần, có lẽ hãy xem xét một biến thứ hai? Xem bài đăng [cập nhật] của tôi.
Marc Gravell

Đã bỏ phiếu cho câu hỏi của riêng tôi. Khắc nghiệt. Nó không hề phức tạp hơn bản gốc.
MusiGenesis

-4

LƯU Ý: Bài đăng này đã trở nên chi tiết hơn nhiều và do đó lạc đề, tôi xin lỗi.

Điều đó được nói rằng các đồng nghiệp của tôi đã đọc nó và tin rằng nó có giá trị 'ở đâu đó'. Chủ đề này không phải là nơi. Tôi sẽ đánh giá cao phản hồi của bạn về việc điều này sẽ đi đến đâu (tôi mới sử dụng trang web).


Dù sao đây cũng là phiên bản C # trong .NET 3.5, điều tuyệt vời là nó hoạt động trên bất kỳ loại bộ sưu tập nào bằng cách sử dụng ngữ nghĩa đã xác định. Đây là thước đo mặc định (sử dụng lại!) Không phải là hiệu suất hoặc giảm thiểu chu kỳ CPU trong hầu hết các kịch bản phát triển phổ biến mặc dù điều đó dường như không bao giờ xảy ra trong thế giới thực (tối ưu hóa sớm).

*** Phương thức mở rộng hoạt động trên bất kỳ loại tập hợp nào và thực hiện một đại biểu hành động mong đợi một giá trị duy nhất của loại, tất cả được thực thi ngược lại trên từng mục **

Yêu cầu 3.5:

public static void PerformOverReversed<T>(this IEnumerable<T> sequenceToReverse, Action<T> doForEachReversed)
      {
          foreach (var contextItem in sequenceToReverse.Reverse())
              doForEachReversed(contextItem);
      }

Phiên bản .NET cũ hơn hoặc bạn muốn hiểu rõ hơn về nội bộ Linq? Đọc tiếp .. Hoặc không ..

ASSUMPTION: Trong hệ thống kiểu .NET, kiểu Mảng kế thừa từ giao diện IEnumerable (không phải IEnumerable chung chỉ IEnumerable).

Đây là tất cả những gì bạn cần lặp lại từ đầu đến cuối, tuy nhiên bạn muốn di chuyển theo hướng ngược lại. Vì IEnumerable hoạt động trên Mảng kiểu 'đối tượng' nên bất kỳ kiểu nào cũng hợp lệ,

ĐO LƯỜNG TIÊU CHUẨN: Chúng tôi giả định nếu bạn có thể xử lý bất kỳ chuỗi nào theo thứ tự ngược lại 'tốt hơn' thì chỉ có thể thực hiện trên số nguyên.

Giải pháp a cho .NET CLR 2.0-3.0:

Mô tả: Chúng tôi sẽ chấp nhận bất kỳ cá thể triển khai IEnumerable nào với nhiệm vụ rằng mỗi thể hiện nó chứa là cùng một loại. Vì vậy, nếu chúng ta nhận được một mảng, toàn bộ mảng chứa các thể hiện kiểu X. Nếu bất kỳ trường hợp nào khác thuộc kiểu! = X, một ngoại lệ được ném ra:

Một dịch vụ singleton:

public class ReverserService {private ReverserService () {}

    /// <summary>
    /// Most importantly uses yield command for efficiency
    /// </summary>
    /// <param name="enumerableInstance"></param>
    /// <returns></returns>
    public static IEnumerable ToReveresed(IEnumerable enumerableInstance)
    {
        if (enumerableInstance == null)
        {
            throw new ArgumentNullException("enumerableInstance");
        }

        // First we need to move forwarad and create a temp
        // copy of a type that allows us to move backwards
        // We can use ArrayList for this as the concrete
        // type

        IList reversedEnumerable = new ArrayList();
        IEnumerator tempEnumerator = enumerableInstance.GetEnumerator();

        while (tempEnumerator.MoveNext())
        {
            reversedEnumerable.Add(tempEnumerator.Current);
        }

        // Now we do the standard reverse over this using yield to return
        // the result
        // NOTE: This is an immutable result by design. That is 
        // a design goal for this simple question as well as most other set related 
        // requirements, which is why Linq results are immutable for example
        // In fact this is foundational code to understand Linq

        for (var i = reversedEnumerable.Count - 1; i >= 0; i--)
        {
            yield return reversedEnumerable[i];
        }
    }
}



public static class ExtensionMethods
{

      public static IEnumerable ToReveresed(this IEnumerable enumerableInstance)
      {
          return ReverserService.ToReveresed(enumerableInstance);
      }
 }

[TestFixture] lớp công khai Testing123 {

    /// <summary>
    /// .NET 1.1 CLR
    /// </summary>
    [Test]
    public void Tester_fornet_1_dot_1()
    {
        const int initialSize = 1000;

        // Create the baseline data
        int[] myArray = new int[initialSize];

        for (var i = 0; i < initialSize; i++)
        {
            myArray[i] = i + 1;
        }

        IEnumerable _revered = ReverserService.ToReveresed(myArray);

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
    }

    [Test]
    public void tester_why_this_is_good()
    {

        ArrayList names = new ArrayList();
        names.Add("Jim");
        names.Add("Bob");
        names.Add("Eric");
        names.Add("Sam");

        IEnumerable _revered = ReverserService.ToReveresed(names);

        Assert.IsTrue(TestAndGetResult(_revered).Equals("Sam"));


    }

    [Test]
    public void tester_extension_method()
  {

        // Extension Methods No Linq (Linq does this for you as I will show)
        var enumerableOfInt = Enumerable.Range(1, 1000);

        // Use Extension Method - which simply wraps older clr code
        IEnumerable _revered = enumerableOfInt.ToReveresed();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }


    [Test]
    public void tester_linq_3_dot_5_clr()
    {

        // Extension Methods No Linq (Linq does this for you as I will show)
        IEnumerable enumerableOfInt = Enumerable.Range(1, 1000);

        // Reverse is Linq (which is are extension methods off IEnumerable<T>
        // Note you must case IEnumerable (non generic) using OfType or Cast
        IEnumerable _revered = enumerableOfInt.Cast<int>().Reverse();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }



    [Test]
    public void tester_final_and_recommended_colution()
    {

        var enumerableOfInt = Enumerable.Range(1, 1000);
        enumerableOfInt.PerformOverReversed(i => Debug.WriteLine(i));

    }



    private static object TestAndGetResult(IEnumerable enumerableIn)
    {
      //  IEnumerable x = ReverserService.ToReveresed(names);

        Assert.IsTrue(enumerableIn != null);
        IEnumerator _test = enumerableIn.GetEnumerator();

        // Move to first
        Assert.IsTrue(_test.MoveNext());
        return _test.Current;
    }
}

2
Dude hay dudette: Tôi chỉ đang tìm kiếm một cách viết ít rườm rà hơn "for (int i = myArray.Length - 1; i> = 0; i--)".
MusiGenesis

1
không có giá trị trong bài trả lời này ở tất cả
Matt Melton
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.