保持指向前一个循环值的指针?

时间:2015-04-27 22:10:46

标签: c++ c++11

我试图迭代一个向量,保留上一次迭代的值的副本。令我惊讶的是,我的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

这是为什么?什么是解决问题的正确方法?

5 个答案:

答案 0 :(得分:5)

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

解决问题的方法是让您的prev变量变为简单std::string,并在每次迭代结束时为其分配s。你会“按价值”保存。

答案 1 :(得分:3)

只需更改

for (std::string s : v) {

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

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

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

因为您正在为正在更改的变量分配指针,并且当该变量也随着指针的变化而变化时也会发生变化。

这样可行:

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

答案 4 :(得分:0)

    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;