当我改变它时,指向向量中的元素的指针会发生什么?

dan*_*ani 16 c++ pointers vector

我有一个std::vector<int>指针指向int*向量中的元素.假设指针指向第三个元素:pointer=&vector.at(2).如果我现在对矢量进行混洗,它是否仍然指向相同的元素(第三个),还是它指向新的位置,而这个元素曾经是第三个元素现在已经移动了?

在那之后,我想让问题更加通用:当向量扩展或缩小时,向量中元素的指针和迭代器如何表现?

Jer*_*fin 17

指针将继续指向同一位置,因此当您进行随机播放时,它将指向已移动到您指定位置的任何元素.

展开向量的大小时,向量中的所有现有指针和迭代器都可能变为无效.当你洗牌时,他们会继续引用相同的位置,这通常会包含与改组前不同的值.

减小矢量的大小将取决于您如何做到这一点.一种方法是创建一个临时向量作为当前向量的副本,交换两个,然后销毁临时(通常隐式地,让它超出范围).如果你这样做,指针将进入临时指针,并在它被销毁时失效.

如果你使用shrink_to_fit它(可能)不会使迭代器/指针无效,但可能没有任何影响(标准指定它是一个非绑定请求,并没有说它使迭代器/指针无效).

  • "向量中的所有现有指针和迭代器都变为无效"可能是真的,但可能更正确,因为"现有指针和迭代器不能保证保持有效". (4认同)
  • @thang那些意味着同样的事情. (4认同)
  • @JerryCoffin关于`shrink_to_fit`与迭代器失效问题,见[LWG 2223](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2223). (3认同)
  • 你如何实现`shrink_to_fit`而不会使指针和迭代器失效? (2认同)

M.M*_*M.M 10

如果在不调整大小的情况下对矢量进行混洗,则指针仍然指向相同的位置,该位置可能包含不同的元素.

如果向量被调整为更大,则指针被称为"无效"并且它具有与未初始化指针相同的状态,即评估它或尝试读取它会导致未定义的行为.


Mat*_*lia 10

如果我现在对矢量进行混洗,它是否仍然指向相同的元素(第三个),还是指向使用到第三个元素移动的新位置?

改组元素只是通过数组中的各种"桶"复制/交换元素,而指针只指向"内存中的固定位置".因此,它将继续指向阵列中第三个位置的任何位置.

然后我想让问题更加通用:当向量展开,缩小或洗牌时,向量中元素的指针和迭代器如何表现?

展开:所有迭代器/引用/指针都可能无效.

减少:只要他们在删除之前指向元素,它们将保持有效,除非你做了shrink_to_fit.您删除的元素的迭代器/指针显然无效.

改组:你正在移动东西而不会导致重新分配,因此迭代器和引用仍然有效.

请注意,所有这些内容通常都会在大多数C++文档源中报告.


要记住向量的概念规则是它们只是一个围绕动态数组的框,并且迭代器和指向元素的指针在概念上是相同的(实际上,std::vector<T>::iterator可能是typedeffor T *).对于引用(伪装的指针)也是如此.

如果操作可能需要重新分配数组(=数组需要增长,或者您明确要求它缩小),那么所有迭代器/指针/引用都将失效.如果删除元素,则指向过矢量"概念结束"的指针将指向无效元素.如果大小保持不变,则不需要重新分配.


dav*_*ler 8

地址不会改变,但存储在该地址的值将会改变.

#include <iostream>
#include <algorithm>

static void print_vec(const std::vector<int>& v) {
    for (int i = 0; i < v.size();  ++i) {
        std::cout << "Value: " << v.at(i) << " Address: " << &v.at(i) << std::endl;
    }
}

static void shuffle_vec(std::vector<int>& v) {
    auto engine = std::default_random_engine{};
    std::shuffle(v.begin(), v.end(), engine);
}

int main() {
    std::vector<int> v{1, 2, 3, 4, 5};
    std::cout << "Before Shuffle: " << std::endl;
    print_vec(v);
    shuffle_vec(v);
    std::cout << "After Shuffle: " << std::endl;
    print_vec(v);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

Before Shuffle: 
Value: 1 Address: 0x16eb320
Value: 2 Address: 0x16eb324
Value: 3 Address: 0x16eb328
Value: 4 Address: 0x16eb32c
Value: 5 Address: 0x16eb330
After Shuffle: 
Value: 3 Address: 0x16eb320
Value: 1 Address: 0x16eb324
Value: 5 Address: 0x16eb328
Value: 4 Address: 0x16eb32c
Value: 2 Address: 0x16eb330
Run Code Online (Sandbox Code Playgroud)


Yak*_*ont 6

实际上,向量是代码维护的连续数据缓冲区.每个元素以类似数组的方式设置为与下一个元素相邻.

当元素四处移动时,实际上它们只是四处移动.指针指向该缓冲区中的位置,因此如果元素移动,实际上指针最终指向其他位置.

但是,C++标准更严格.当迭代器失效时,引用和指向该位置的指针也是如此.有许多操作可以使迭代器无效,这在逻辑上不会改变数组实际上将成为同一缓冲区的事实.例如,如果您.erase是一个元素,它会使该位置和之后的每个迭代器无效.

在实践中,指向元素的指针最终将指向列表中的"下一个"元素,但标准并不能保证这一点.

std::shuffle不会使任何迭代器无效.它只是改变那里存储的值.所以指向第n个元素的指针在实践中都是如此,理论上仍然指向第n个元素.

展开向量时,如果将其展开,则.capacity()所有迭代器都将失效.在实践中,它实际上将数据移动到一个新位置,指针现在正在敲击指针.

当您减少(通过.erase(it).erase(a,b))时,第一个参数处或之后的所有迭代器都将失效.这意味着对这些元素的引用和指针也会失效.在实践中,他们现在将引用"进一步向下"的元素(如果存在这样的元素),因为两者都.erase不会导致缓冲区重新分配,但标准不保证这一点.

还有其他操作可以使迭代器无效. .shrink_to_fit()可以,vector<X>(vec).swap(vec)(缩小到适合的C++ 03版本),以及.reserve()超出规模的操作.capacity().

.capcity()在实践中和理论上,导致更改的操作实际上会使指针无效(或使指针指向超出端的指针).


phi*_*pxy 5

阅读您调用的每个功能的文档.如果您不知道何时以及如何调用它以及它的作用,那么您为什么要使用它?

通常,您不能依赖地址或数组等实现概念,也不能依赖测试程序.当迭代器对于特定容器,迭代器和运算符的元素无效或无效时,必须读取.

vector::shrink_to_fit使
vector::resize相同或更小的所有迭代器无效,使新大小之外的迭代器完全
vector::resize无效,使得所有迭代器无效

从C++ 14标准[iterator.requirements.general]:

[P] ointers是迭代器.取消引用已失效的迭代器的效果未定义.

http://en.cppreference.com/w/cpp/container/vector

std::vector是一个封装动态大小数组的序列容器.
元素是连续存储的,这意味着元素不仅可以通过迭代器访问,还可以使用常规指向元素的偏移量来访问.

迭代器RandomAccessIterator

迭代器失效
swap, std::swap     从不
shrink_to_fit     总是
resize     如果向量改变了容量,所有这些都是.如果不是,只有插入点后的那些.

http://en.cppreference.com/w/cpp/container/vector/resize

当调整大小到较小时,向量容量永远不会减少,因为这会使所有迭代器无效,而不是仅通过等效pop_back()调用序列无效的那些迭代器.

经过vector::shuffle迭代器/指针是不变,但取消引用新值.

因为使迭代器不变的shuffle用法swap:

http://en.cppreference.com/w/cpp/algorithm/random_shuffle

template< class RandomIt, class URNG >
void shuffle( RandomIt first, RandomIt last, URNG&& g );
Run Code Online (Sandbox Code Playgroud)

RandomIt必须满足ValueSwappable和RandomAccessIterator的要求.

http://en.cppreference.com/w/cpp/concept/Iterator

Iterator是其他迭代器类型使用的基本概念:InputIterator,OutputIterator,ForwardIterator,BidirectionalIterator和RandomAccessIterator.迭代器可以被认为是指针的抽象.[...]

` - lvalues类型它满足Swappable [...]

http://en.cppreference.com/w/cpp/concept/ValueSwappable

如果
    1)类型T满足迭代器要求,则类型T是ValueSwappable
    2)对于任何类型为T的可解除引用的对象x(即除了结束迭代器之外的任何值),*x满足可交换的要求.

http://en.cppreference.com/w/cpp/concept/Swappable

using std::swap;
swap(u, t);
Run Code Online (Sandbox Code Playgroud)

在调用之后,t的值是在调用之前由u保持的值,并且u的值是在调用之前由t保持的值.