在C / C#/ C ++中执行向后循环的最佳方法是什么?


101

我需要向后移动一个数组,所以我有如下代码:

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

有更好的方法吗?

更新:我希望C#可以为此提供一些内置机制,例如:

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

更新2:有更好的方法。符文获得以下奖项:

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

在普通C语言中看起来更好(这要归功于Twotymz):

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

我不确定我是否知道如果良性包括明晰性或可维护性,为什么任何其他选择都更好。
dkretz

真正。我希望找到一种更合理的方法来执行此操作,因为我必须经常这样做。
MusiGenesis

3
这个问题大部分是对以下答案的回顾。建议将其大大缩短。
einpoklum 2013年

请从标题和主题标签中删除C和C ++。
jaskmar

Answers:


148

尽管确实有点晦涩难懂,但我要说的是,最排字的方法是

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

32
当我第一次阅读您的答案时,它看起来甚至无法编译,因此我认为您是个疯子。但这正是我在寻找的东西:一种编写反向for循环的更好方法。
MusiGenesis

3
我认为我-> 0; 是故意的 那就是他所谓的“版式愉悦”的意思
Johannes Schaub-litb

16
我自己发现它“使人迷惑”。对我来说,“-”看起来不正确,除非它与它所影响的变量相邻。
MusiGenesis

26
太晦涩难懂了。我永远不会在生产代码中写这样的东西……
Mihai Todor 2012年

9
啊,去运算符(->)可以解决问题!
nawfal 2014年

118

在C ++中,您基本上可以选择使用迭代器或索引进行迭代。根据使用的是纯数组还是std::vector,可以使用不同的技术。

使用std :: vector

使用迭代器

C ++允许您使用 std::reverse_iterator:

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

使用索引

通过返回的无符号整型std::vector<T>::size不是永远std::size_t。它可以更大或更小。这对于循环工作至关重要。

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

它起作用,因为无符号整数类型值是通过对它们的位数进行模运算来定义的。因此,如果您设置-N,您最终会在(2 ^ BIT_SIZE) -N

使用数组

使用迭代器

我们std::reverse_iterator习惯于进行迭代。

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

使用索引

std::size_t与上面相反,我们可以放心使用,因为sizeof总是std::size_t根据定义返回。

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

通过将sizeof应用于指针来避免陷阱

实际上,上述确定数组大小的方法很烂。如果a实际上是指针而不是数组(这种情况经常发生,并且初学者会感到困惑),它将无提示地失败。更好的方法是使用以下代码,如果指定了指针,它将在编译时失败:

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

它的工作原理是先获取传递的数组的大小,然后声明返回对相同大小的char类型的数组的引用。char定义为具有sizeof以下条件:1.因此,返回的数组将具有以下sizeof条件:N * 1,这是我们正在寻找的结果,仅具有编译时评估和零运行时开销。

而不是做

(sizeof a / sizeof *a)

更改您的代码,使其现在可以

(sizeof array_size(a))

此外,如果您的容器没有反向迭代器,则可以使用boost的reverse_iterator实现:boost.org/doc/libs/1_36_0/libs/iterator/doc/…–
MP24

您的array_size似乎适用于静态分配的数组,但是当a为'new int [7]'时,对我来说却失败了。
内特·帕森斯

2
是的,这就是它的目的:) new int [7]返回一个指针。所以sizeof(new int [7])返回的不是7 * sizeof(int),而是返回sizeof(int *)。在这种情况下,array_size会导致编译错误,而不是无提示地工作。
Johannes Schaub-litb

也可以尝试array_size(+ statically_allocated_array),这也会失败,因为+运算符使数组成为指向第一个元素的指针。使用普通的sizeof将再次为您提供指针的大小
Johannes Schaub-litb

对于第一件事-为什么不直接使用end,并begin以相反的顺序?
托马什Zato -恢复莫妮卡

54

在C#中,使用Visual Studio 2005或更高版本,键入'forr'并点击[TAB] [TAB]。这将扩展为for循环,循环向后遍历一个集合。

犯错很容易(至少对我来说是错的),以至于我认为把这个代码片段放进去是个好主意。

这就是说,我喜欢Array.Reverse()/ Enumerable.Reverse()然后迭代前锋更好-他们更清楚地说明意图。


41

总是喜欢清晰的代码而不是“ 印刷上令人愉悦的 ”代码。因此,我将始终使用:

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

您可以将其视为向后循环的标准方法。
只是我的两分钱


工作了 尚未在C#中完成此操作,但谢谢。
PCPGMR 2014年

那么,如果数组真的很大(那么索引必须是无符号类型)怎么办?size_t实际上是未签名的,不是吗?
lalala

您可以将i声明为uint。请参阅Marc Gravell的帖子。
杰克·格里芬

由于使用换行,因此不适用于无符号类型,这与印刷上令人愉悦的类型不同。不好。
豹猫

17

在使用Linq的C#中:

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

可以肯定这是最简单的,但是请注意,在当前版本的CLR中,列表反转始终是O(n)操作而不是O(1)操作,如果它是IList <T>则可能是这样。看到connect.microsoft.com/VisualStudio/feedback/...
格雷格山毛榉

1
看起来.Reverse()制作了数组的本地副本,然后向后迭代。只是复制数组以使其遍历它似乎效率低下。我猜任何带有“ Linq”的帖子都值得投票。:)
MusiGenesis

有一个可能的折衷,但是然后您遇到一个问题,即“投票时答案”(i-> 0)可能会导致代码变慢,因为(i)每次都要检查数组的大小用作索引。
Keltex

1
虽然我同意Linq很棒,但是这种方法的问题在于,迭代器不允许您有选择地挑选数组的项目-考虑要在数组的一部分而不是整个数组中导航的情况东西...
jesses.co.tt

11

对于任何长度为带符号整数类型的数组,这绝对是最好的方法。对于长度为无符号整数类型的数组(例如,std::vector在C ++中),则需要稍微修改结束条件:

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

如果您刚刚说过i >= 0,对于无符号整数总是如此,因此循环将是无限循环。


在C ++中,如果我为0,“ i--”会自动回绕到最大值?抱歉,我C ++受损。
MusiGenesis

惊人。你们只是帮助我了解了我12年前写的东西中硬盘填充错误的原因。好东西,我不在C ++中工作。
MusiGenesis

终止条件应为:i <myArray.size()。仅取决于unsigned int的溢出属性;不是整数和强制类型转换的实现细节。因此,更易于阅读,并且可以使用具有替代整数表示形式的语言来工作。
ejgottl

我认为对我来说,更好的解决方案是留在C#世界中,我不会伤害任何人。
MusiGenesis

4

在我看来很好。如果索引器是未签名的(uint等),则可能需要考虑到这一点。叫我懒,但是在那种(无符号)情况下,我可能只使用一个反变量:

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

(实际上,即使在这种情况下,您也需要注意arr.Length = uint.MaxValue之类的情况……也许在某个地方的=!当然,这是极不可能的情况!)


但是,“-pos”是棘手的。过去,我有一些同事喜欢在代码中随机“纠正”一些看起来不太合适的东西。
MusiGenesis

1
然后使用.arr.Length-1和pos--
Marc Gravell

4

在CI中喜欢这样做:


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

MusiGenesis添加的C#示例:

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

我喜欢这个。我花了一两秒钟才意识到您的方法有效。希望您不要介意添加的C#版本(多余的花括号是为了使索引的作用域与for循环相同)。
MusiGenesis

我不确定我会在生产代码中使用它,因为它会引发一个通用的“这是WTF吗?” 来自必须维护它的人的反应,但实际上键入起来比普通的for循环更容易,并且没有减号或> =符号。
MusiGenesis

1
不幸的是,C#版本需要额外的“> 0”。
音乐创世纪

我不确定这是什么语言。但是您的第一个代码段肯定不是C :)只是为了告诉您显而易见的内容。也许您想编写C#和html未能解析它之类的东西?
Johannes Schaub-小人

@litb:我猜“ myArray.Length”不是有效的C(?),但是while循环部分有效并且看起来不错。
MusiGenesis

3

在C ++中,实现此目标的最佳方法可能是使用迭代器(或更优的范围)适配器,该适配器会在遍历序列时延迟转换序列。

基本上,

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

以相反的顺序显示范围“范围”(此处为空,但是我很确定您可以自己添加元素)。当然,简单地迭代范围并没有多大用处,但是将新范围传递给算法和内容非常酷。

此机制还可以用于更强大的用途:

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

将延迟计算范围“范围”,其中功能“ f”应用于所有元素,“ p”不为真的元素将被删除,最终结果范围被反转。

给定前缀,管道语法是最易读的IMO。Boost.Range库更新待审核将实现此目的,但是您自己也很简单。使用lambda DSEL内联生成函数f和谓词p更加酷。


您能告诉我们您在哪里“学习”吗?它是BOOST_FOREACH的#define吗?一路看起来很可爱。
Johannes Schaub-litb


1

我更喜欢使用while循环。对我来说,比i在for循环的条件下递减更清楚

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

0

我会在原始问题中使用代码,但是如果您真的想使用foreach并在C#中使用整数索引:

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

-1

我将尝试在这里回答我自己的问题,但我也不喜欢这个,或者:

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

而不是每次都使用.Length-1-i,而是考虑第二个变量?请参阅我的[更新]帖子。
马克·格雷韦尔

对我自己的问题投了反对票。苛刻。它没有原始版本那么笨拙。
MusiGenesis

-4

注意:这篇文章最终变得更加详细,因此,我对此表示歉意。

话虽如此,我的同龄人阅读它并认为它在某处有价值。这个线程不是地方。非常感谢您提供反馈意见(我是该网站的新手)。


无论如何,这是.NET 3.5中的C#版本,其惊人之处在于它可以使用定义的语义在任何集合类型上工作。这是默认的衡量标准(重用!),而不是在大多数常见开发场景中的性能或CPU周期最小化,尽管在现实世界中似乎从未发生过(过早的优化)。

***扩展方法适用于任何集合类型,并采取期望该类型的单个值的操作委托,所有操作均以相反的方式在每个项目上执行**

装备3.5

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

较旧的.NET版本,或者您想更好地了解Linq内部?继续阅读..否

假设:在.NET类型系统中,数组类型继承自IEnumerable接口(不是一般的IEnumerable而是IEnumerable)。

这就是您需要从头到尾反复进行的全部操作,但是您想朝相反的方向移动。当IEnumerable处理类型为“对象”的Array时,任何类型均有效,

至关重要的措施:我们假设,如果您可以按“更好”的相反顺序处理任何序列,则只能对整数进行处理。

.NET CLR 2.0-3.0的解决方案a:

说明:我们将接受任何IEnumerable实现实例,并且其所包含的每个实例的类型均相同。因此,如果我们接收到一个数组,则整个数组包含类型X的实例。如果任何其他实例的类型为!= X,则会引发异常:

单例服务:

公共类ReverserService {私有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]公共课程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或dudette:我只是在寻找一种不太笨拙的方式编写“ for(int i = myArray.Length-1; i> = 0; i--)”。
MusiGenesis

1
有在这一切的答复没有价值
马特·梅尔顿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.