【问题标题】:Why does this happen at the end of the for loop with vectors c++为什么在带有向量c ++的for循环结束时会发生这种情况
【发布时间】:2021-11-27 04:13:03
【问题描述】:

我想擦除向量中的重复元素;我使用for 循环来检查向量中的下一个元素是否与迭代中的当前元素相同,如果为真则将其删除,但由于某种原因,它会删除最后一个元素而不是相等。

这是我的代码:

#include <string>
#include <vector>
#include <iostream>

using namespace std;

template <typename T> vector<T> uniqueInOrder(const vector<T>& iterable){
    vector<T> coolestVector = iterable;
    for (int i = 0; i < coolestVector.size(); i++)
    {
        if (coolestVector[i] == coolestVector[i+1]){
            coolestVector.erase(coolestVector.begin()+i);
            i--;
        }
        /*for (int i = 0; i < coolestVector.size(); i++)
        {
            cout<<coolestVector[i]<<", ";
        }
        cout<<i<<", ";
        cout<<coolestVector.size();
        cout<<endl;*/
    }

    for (int i = 0; i < coolestVector.size(); i++)
    {
        cout<<coolestVector[i]<<endl;
    }
    
    return coolestVector;
}
vector<char> uniqueInOrder(const string& iterable){
    vector<char> coolVector = {};
    for (int i = 0; i < iterable.size(); i++)
    {
        coolVector.push_back(iterable[i]);
    }
    const vector<char> realVector = coolVector;
    uniqueInOrder(realVector);
}

int main(){
    const string test = "AAAABBBCCDAABBB";
    uniqueInOrder(test);
}

输出:

vector 0: A, A, A, B, B, B, C, C, D, A, A, B, B, B, iterator value -1, vector size 14
vector 0: A, A, B, B, B, C, C, D, A, A, B, B, B, iterator value -1, vector size 13
vector 0: A, B, B, B, C, C, D, A, A, B, B, B, iterator value -1, vector size 12
vector 1: A, B, B, B, C, C, D, A, A, B, B, B, iterator value 0, vector size 12
vector 1: A, B, B, C, C, D, A, A, B, B, B, iterator value 0, vector size 11
vector 1: A, B, C, C, D, A, A, B, B, B, iterator value 0, vector size 10
vector 2: A, B, C, C, D, A, A, B, B, B, iterator value 1, vector size 10
vector 2: A, B, C, D, A, A, B, B, B, iterator value 1, vector size 9
vector 3: A, B, C, D, A, A, B, B, B, iterator value 2, vector size 9
vector 4: A, B, C, D, A, A, B, B, B, iterator value 3, vector size 9
vector 4: A, B, C, D, A, B, B, B, iterator value 3, vector size 8
vector 5: A, B, C, D, A, B, B, B, iterator value 4, vector size 8
vector 5: A, B, C, D, A, B, B, iterator value 4, vector size 7
vector 5: A, B, C, D, A, B, iterator value 4, vector size 6
vector 5: A, B, C, D, A, iterator value 4, vector size 5
A
B
C
D
A

预期:

A
B
C
D
A
B

【问题讨论】:

  • 您知道coolestVector[(coolestVector.size()-1)+1] 会调用未定义的行为,对吗? ;)
  • 了解std::unique
  • 您注释掉的诊断是一个好方法。但是,我会将它放在循环的开头而不是结尾。这使您可以查看进入 if 语句的数据,因此您可以更好地判断 coolestVector[i] == coolestVector[i+1] 在最后一个元素被删除之前应该评估什么;也就是说,当coolestVector 包含A, B, C, D, A, B,而i5。 (这个六元素数组的第六个和第七个元素是什么?它们相等吗?)

标签: c++ loops for-loop vector std


【解决方案1】:

为什么代码不正确?

许多人通过记住设置来学习迭代数组或向量

for (int i = 0; i < X.size(); i++)

这对基本循环很有用,但有时它不够用。你知道为什么条件是i &lt; X.size()吗?一个基本的理解会说这个条件确保循环体被执行的次数等于X的大小。这没有错,但是当i 没有在循环体内使用时,这个理由更适用。 (例如,如果i1 开始并且循环持续到i &lt;= X.size(),则该原理同样适用,但这不是遍历数组/向量的好方法。)

更深入的了解看i 在循环体中是如何使用的。一个常见的例子是打印X 的元素。 (这是初步的;我们稍后会回到问题的情况。)打印X 元素的循环可能如下所示:

for (int i = 0; i < X.size(); i++)
    std::cout << X[i] << ' ';

注意X 的索引——这是循环条件的关键。该条件的更深层目的是确保索引保持在有效范围内。分配给X 的索引不得低于0,并且必须保持低于X.size()。也就是说,index &lt; X.size() 其中index 被括号中的任何内容替换。在这种情况下,括号中的东西是i,所以条件变成了熟悉的i &lt; X.sixe()

现在让我们看看问题的代码。

for (int i = 0; i < coolestVector.size(); i++)
{
    if (coolestVector[i] == coolestVector[i+1]){
        // Code not using operator[]
    }
    // Diagnostics
}

在循环内部有两个地方使用operator[]。将上述“更深入的理解”应用于每个条件,然后将生成的条件与逻辑“与”结合起来。

  • 第一个索引是i,因此在这种情况下目标index &lt; X.size() 变为i &lt; coolestVector.size()
  • 第二个索引是i+1,因此在这种情况下目标index &lt; X.size() 变为i+1 &lt; coolestVector.size()

结合这些得到i &lt; coolestVector.size() &amp;&amp; i+1 &lt; coolestVector.size()。这就是循环的条件应该是确保索引保持在有效范围内。逻辑上等价的东西也可以。假设i+1 没有溢出(这将引发另一类问题),如果i+1 小于某个值,那么i 也是如此。检查i+1 是否在范围内就足够了,因此我们可以将此条件简化为i+1 &lt; coolestVector.size()

for (int i = 0; i+1 < coolestVector.size(); i++)  // <--  Fixed!
{
    if (coolestVector[i] == coolestVector[i+1]){
        // Code not using operator[]
    }
    // Diagnostics
}

(我知道,要说“添加一个”,写了很多文章。重点是为您和未来的读者提供使下一个循环正确的工具。)


请注意,同样的原则也适用于循环的开始。我们从0 开始i,以便i &gt;= 0。这恰好也暗示了i+1 &gt;= 0,因此在这种情况下,无需做任何额外的事情。但是,如果使用的索引之一是i-1,那么您需要确保i-1 &gt;= 0,这将通过在1 开始i 来完成。

查看索引以确定循环控制变量应该在哪里开始和停止。

【讨论】:

    【解决方案2】:

    我已将其与我的 earlier answer 分开,因为之前的答案可以独立存在,我不希望它卷入因解释未定义行为而引起的潜在争议。

    为什么程序总是删除最后一个元素?

    正式地说,我们处于未定义行为的领域,所以一切皆有可能。但是,这种行为很可能会出现在所有发布版本中,但有两点需要注意。

    1. 删除了较早的元素。如果不应该删除任何元素(值得添加到测试套件中的情况),则行为是不可预测的,可能是崩溃,但很可能是预期的行为。
    2. 移动构造会留下一个副本。对于像char 这样的简单类型来说是这样。对于std::string 的向量,您可能不会看到这种行为。

    std::vector 中间的元素被擦除时,该元素之后的所有元素都会向下移动一个索引;它们被复制(或移动)到前面的元素。

    A B B C D
      ^
      |-- erase this
    
    A B B C D
      ^ ^ ^    <--- shift and copy (or move)
      B C D
    
    A B C D D
          ^
          |-- Last element in the vector
    

    请注意,擦除时不会释放空间。向量仍然拥有D 曾经所在的内存;只是从向量实现之外访问元素是未定义的行为。此外,该内存不太可能在发布版本中通过向量更改其位。所以很有可能向量的末尾是向量最后一个元素的副本,除非移动构造函数改变了它。

    现在是你的条件。当icoolestVector.size()-1 时,您检查向量的最后一个元素(coolestVector[i])是否等于向量末尾的元素(coolestVector[i+1])。发布版本不会验证索引是否有效,并且操作系统不关心内存中的该位置是否被访问,因此这种比较可能会像人们天真地期望的那样进行。向量的最后一个元素是否等于复制它的对象?是的! OK,删除最后一个元素。

    很可能在发布版本中,但不要依赖它。

    【讨论】:

      【解决方案3】:

      您也可以在线性时间 O(1) 内将 std::set 用于唯一元素。

      void Unique_Vector(vector<string>&v,int size)
      {
         std::set<string>s;
         for(auto i : v)
         {
            s.insert(i);
         }
         std::cout<<"Vector after removing duplicate :";
         for(auto i : s)
         {
             std::cout<<i<<" ";
         }
      }
      

      【讨论】:

      • "在线性时间 O(1)" -- O(1) 表示恒定时间,不是线性的。插入一个集合需要对数时间,并且插入完成v.size() 次,因此它的时间复杂度既不是“线性”也不是“O(1)”。也许您打算使用无序集来平均计算线性复杂度?
      • 迭代一个集合不会产生预期的结果。预期的结果保持原来的顺序,只要不相邻就允许重复值。
      猜你喜欢
      • 2016-02-11
      • 1970-01-01
      • 2021-01-24
      • 1970-01-01
      • 2013-08-02
      • 2018-12-19
      • 2021-07-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多