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

时间:2008-11-09 15:07:29

标签: c# c++ c

我需要向后移动一个数组,所以我有这样的代码:

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
}

16 个答案:

答案 0 :(得分:130)

虽然有点模糊,但我会说这种印刷方式最令人愉悦的方式是

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

答案 1 :(得分:110)

在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))

答案 2 :(得分:51)

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

很容易出错(至少对我而言),我认为把这个片段放在一个好主意。

也就是说,我喜欢Array.Reverse() / Enumerable.Reverse(),然后更好地迭代转发 - 它们更明确地说明意图。

答案 3 :(得分:32)

我会始终更喜欢清晰的代码,而不是'印刷品令人愉悦的'代码。 因此,我总是使用:

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

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

答案 4 :(得分:17)

C#中使用 Linq

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

答案 5 :(得分:10)

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

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

如果你刚才说i >= 0,对无符号整数总是如此,所以循环将是一个无限循环。

答案 6 :(得分:4)

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

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

(实际上,即使在这里,你也需要注意像arr.Length = uint.MaxValue这样的情况......也许是!=某处...当然,这是一个非常不可能的情况!)

答案 7 :(得分:4)

在C中我喜欢这样做:


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

MusiGenesis添加的C#示例:

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

答案 8 :(得分: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 in-line会更加酷。

答案 9 :(得分:1)

// this is how I always do it
for (i = n; --i >= 0;){
   ...
}

答案 10 :(得分:1)

我更喜欢while循环。对于我来说,比在for循环

的条件下递减i更清楚
int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

如果善意包括清晰度或可维护性,我不确定为什么任何替代品会更好。

答案 13 :(得分:0)

对于 C++:

正如其他人所提到的,在可能的情况下(即,当您一次只需要一个元素时),强烈建议使用迭代器既明确又避免常见的陷阱。现代 C++ 有一个更简洁的语法,auto:

std::vector<int> vec = {1,2,3,4};
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
    std::cout<<*it<<" ";
}

打印 4 3 2 1

你也可以在循环中修改值:

std::vector<int> vec = {1,2,3,4};
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
    *it = *it + 10;
    std::cout<<*it<<" ";
}

导致 14 13 12 11 被打印,然后 {11, 12, 13, 14} 出现在 std::vector 中。

如果您不打算在循环期间修改该值,则应确保在您尝试这样做时会出现错误,这与编写 for(const auto& element : vec) 的方式类似。这可能是这样的:

std::vector<int> vec = {1,2,3,4};
for (auto it = vec.crbegin(); it != vec.crend(); ++it) { // used crbegin()/crend() here...
    *it = *it + 10; // ... so that this is a compile-time error
    std::cout<<*it<<" ";
}

在这种情况下,我的编译器错误是:

/tmp/main.cpp:20:9: error: assignment of read-only location ‘it.std::reverse_iterator<__gnu_cxx::__normal_iterator<const int*, std::vector<int> > >::operator*()’
   20 |     *it = *it + 10;
      |     ~~~~^~~~~~~~~~

另请注意,您应该确保不要同时使用不同的迭代器类型:

std::vector<int> vec = {1,2,3,4};
for (auto it = vec.rbegin(); it != vec.end(); ++it) { // mixed rbegin() and end()
    std::cout<<*it<<" ";
}

导致详细错误:

/tmp/main.cpp: In function ‘int main()’:
/tmp/main.cpp:19:33: error: no match for ‘operator!=’ (operand types are ‘std::reverse_iterator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > >’ and ‘std::vector<int>::iterator’ {aka ‘__gnu_cxx::__normal_iterator<int*, std::vector<int> >’})
   19 | for (auto it = vec.rbegin(); it != vec.end(); ++it) {
      |                              ~~ ^~ ~~~~~~~~~
      |                              |            |
      |                              |            std::vector<int>::iterator {aka __gnu_cxx::__normal_iterator<int*, std::vector<int> >}
      |                              std::reverse_iterator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > >

如果堆栈上有 C 风格的数组,你可以这样做:

int vec[] = {1,2,3,4};
for (auto it = std::crbegin(vec); it != std::crend(vec); ++it) {
    std::cout<<*it<<" ";
}

如果您确实需要索引,请考虑以下选项:

  • 检查范围,然后使用带符号的值,例如:
void loop_reverse(std::vector<int>& vec) {
    if (vec.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
        throw std::invalid_argument("Input too large");
    }
    const int sz = static_cast<int>(vec.size());
    for(int i=sz-1; i >= 0; --i) {
        // do something with i
    }
}
  • 处理无符号值,小心并添加注释,例如:
void loop_reverse2(std::vector<int>& vec) {
    for(size_t i=vec.size(); i-- > 0;) { // reverse indices from N-1 to 0
        // do something with i
    }
}
  • 单独计算实际索引,例如:
void loop_reverse3(std::vector<int>& vec) {
    for(size_t offset=0; offset < vec.size(); ++offset) {
        const size_t i = vec.size()-1-offset; // reverse indices from N-1 to 0
        // do something with i
    }
}

答案 14 :(得分:-1)

我打算在这里回答我自己的问题,但我也不喜欢这样:

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

答案 15 :(得分:-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类型系统中,Array类型继承自IEnumerable接口(不是通用的IEnumerable IEnumerable)。

这是您从头到尾迭代所需的全部内容,但是您希望向相反的方向移动。由于IEnumerable适用于“对象”类型的数组,因此任何类型都有效,

关键度量:我们假设您能够以相反的顺序处理任何“更好”的序列,然后才能在整数上执行。

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

描述:我们将接受任何IEnumerable实现实例,其任务是它包含的每个实例都是相同的类型。因此,如果我们接收一个数组,整个数组包含类型X的实例。如果任何其他实例属于类型!= X抛出异常:

单身人士服务:

公共类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]     公共课测试123     {

    /// <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;
    }
}