【问题标题】:Keeping a pointer to the previous loop value?保持指向前一个循环值的指针?
【发布时间】:2015-04-27 22:10:46
【问题描述】:

我正在尝试迭代一个向量,保留上一次迭代的值的副本。令我惊讶的是,我的 prev 指针最终指向循环的当前值。

#include <iostream>
#include <vector>

int main() {
    std::vector<std::string> v;
    v.push_back("one");
    v.push_back("two");
    v.push_back("three");

    std::string *prev = nullptr;
    for (std::string s : v) {
        std::cout << "s: " << s << std::endl;
        if (prev == nullptr) {
            std::cout << "prev: (null)" << std::endl << std::endl;
        } else {
            std::cout << "prev: " << *prev << std::endl << std::endl;
        }
        prev = &s;
    }

    std::cout << "final prev: " << *prev << std::endl;
}

这是输出:

s: one
prev: (null)

s: two
prev: two

s: three
prev: three

final prev: three

这是为什么?什么是修复它的正确方法?

【问题讨论】:

  • 快速修复:通过引用传递字符串似乎可以做到。

标签: c++ c++11


【解决方案1】:

您将std::string s 变量的地址存储在std::string *prev 指针中。问题是,s 变量的值在每次迭代时都会更改为从 v 提取的当前值,这就是为什么你的 prev 指针指向循环的当前值。

解决它的一种方法是使您的prev 变量成为一个简单的std::string,并在每次迭代结束时为其分配s。您将“按价值”保存。

【讨论】:

    【解决方案2】:

    改变一下

    for (std::string s : v) {
    

    到

    for (std::string& s : v) {
    

    这样prev = &amp;s; 将实际字符串的地址存储在向量中,而不是构造一个本地副本然后获取该局部变量的地址。

    再详细一点:
    在您的原始版本中,s 是一个本地 std::string 变量,它在每次循环迭代中构造新副本。然后在循环结束时,您将获取该局部变量的地址,该变量在病房后直接销毁,在下一次迭代中访问该指针时导致悬空指针和未定义的行为。然而,因为s 通常会在每次循环迭代中创建在相同的位置,未定义的行为将以prev 现在指向new 局部变量s 的形式实现自身。已经用数组的下一个元素初始化。因此,存储在 prev 中的地址将在下一个周期中有效,但它现在指向一个新创建的局部变量,该变量又是向量中下一个元素的副本。因此std::cout &lt;&lt; "prev: " &lt;&lt; *prev 通常会打印与std::cout &lt;&lt; "s: " &lt;&lt; s 相同的字符串。然而,通过某些编译器优化,人们也可以想象其他可能的行为。

    现在有了新的变体,s 又是一个局部变量,但现在它不再是副本,而是对 v 中当前元素的引用。因此,&amp;s 现在将地址返回到 v 中的实际元素向量 - 而不是局部变量 - 其内容在循环迭代之间不会更改,您可以按照预期的方式使用 prev。

    【讨论】:

      【解决方案3】:

      s 的范围一直持续到for 循环结束。如果你存储一个指向s 的指针,它会在迭代结束时悬空。当您取消引用这个悬空指针时,您有未定义的行为。碰巧您的实现在每次迭代中为s 重用了相同的内存,因此您的指针总是恰好指向s 的当前值。

      您根本不能保留指向仅存在于循环的特定迭代中的某些内容的指针/引用,并期望它在下一次迭代中有效。在您的情况下,解决方法很简单:不要存储指针/引用 - 通过将 prev 设置为 std::string 来存储值本身。或者,您可以保留指向std::vector 的原始元素的指针,但这意味着将s 更改为std::string&amp;。

      【讨论】:

        【解决方案4】:
            string *prev = nullptr;
            for(string &s : v)  //note change
            {
                if(!prev)
                    cout<<s<<"-- null"<<endl;
                else
                    cout<<s<<"--"<<*prev<<endl;
                prev = &s;
            }
            cout << "final prev: " << *prev << endl;
        

        【讨论】:

          【解决方案5】:

          因为您将指针分配给正在更改的变量,并且当该变量更改时,您使用指针读取的内容也会更改。

          这可行:

          std::string& prev = v.front();
          for (std::string& s : v) {
              std::cout << "s: " << s << std::endl;
              std::cout << "prev: " << std::endl;
              prev = s;
          }
          

          问题在于,显然,在第一个周期中,您的状态不一致。您可能应该从向量的第二个元素开始迭代,并以特殊方式处理向量的第一个元素。例如,可以构建一个range view。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-08-11
            • 1970-01-01
            • 2013-09-08
            • 2023-03-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多