在C++中为什么使用向量的索引运算符被认为是不好的风格?

4

我正在开发一个使用向量的程序。首先要做的是声明向量。

std::vector<double> x;
x.reserve(10)

顺便问一下,这样做也算是不好的做法吗?我应该只是输入 std::vector<double> x(10) 吗?

然后我开始给向量赋值,并询问它的大小。

for (int i=0; i<10; i++)
{
    x[i]=7.1;
}
std::cout<<x.size()<<std::endl;

我不知道它会返回0,所以经过一些搜索,我发现需要使用push_back方法而不是索引运算符。
for (int i=0; i<10; i++)
{
    x.push_back(7.1);
}
std::cout<<x.size()<<std::endl;

现在它返回10

所以我想知道的是为什么索引运算符让我可以访问向量x中给定索引处“存储”的值,但不会改变其大小。此外,为什么这样做是不好的实践?


5
如果使用索引运算符,不是一种糟糕的风格。那是无意义的。如果没有调整大小就使用reserve +索引运算符是糟糕的样式(或错误的)。索引运算符很快,但没有保护/安全机制。这是唯一的问题。在怀疑或调试时,请使用 vector::at(),如果超出范围,它将抛出异常。 - The Quantum Physicist
2
@aegar 你从哪里得到了那个信息?有什么可信的参考资料吗? - πάντα ῥεῖ
1
如果你想要一个特定的由10个元素组成且值相同的向量,你可以在构造函数中创建它:std::vector<double> x(10, 7.1); - Bo Persson
我认为这是不好的风格,因为在Stack Overflow上回答另一个问题时得到了这样的答案(C++ Vector size is returning zero)。我只是想澄清一下,并更深入地了解它是如何工作的。 - Nerdrigo
5个回答

5
当你执行x.reserve(10)时,你只是将容量设置为十个元素,但大小仍然为零。
这意味着当你在循环中使用索引运算符时,你会越界(因为大小为零),并且会出现未定义的行为。
如果你想设置大小,那么可以使用resize或在构造向量时直接告诉它。
std::vector<double> x(10);

关于向量的容量,当你设置它时(例如使用reserve),它会为十个元素(在你的情况下)分配所需的内存。这意味着当你执行push_back操作时,向量数据不会重新分配。
如果你不改变容量,或者添加超过容量的元素,则每个push_back操作可能会导致向量数据的重新分配。

4

看起来你在问为什么事情变成了现在这个样子。大部分原因是出于效率考虑。

如果x[i]在没有创建值的情况下创建一个值,那么将会对效率产生两次影响。首先,索引操作的调用者应该确保索引不超出向量的当前大小。其次,即使你将要给它赋一个新值,新元素也需要进行默认构造。

拥有reserveresize的原因类似。对于每个元素都需要进行默认构造的resize。对于像vector<ComplicatedClass>这样的东西,这可能是一件大事。使用reserve是一种优化,完全可选,可以预测向量的最终大小并防止它增长时重新分配内存。

push_back避免了元素的默认构造,因为它已知内容,可以使用移动或复制构造函数。

以上风格都不是错误的,使用适合自己情况的方式。


非常感谢,您的回答帮助我更好地理解了我的问题。 - Nerdrigo

2
std::vector<double> x;
x.reserve(10)

BTW, is this also considered bad practice?

不,创建一个空向量并保留内存不是一种不好的做法。

我应该只输入 std::vector<double> (10) 吗?

如果你的意图是初始化包含10个元素的向量,而不是创建一个空向量,则是的,你应该这样做。(如果你的意图是创建一个空向量,则不需要这样做)

Then I proceeded to assign values to the vector, and ask for its size.

for (int i=0; i<10; i++)
{
    x[i]=7.1;

这将产生未定义的行为。不要尝试访问不存在的对象。

所以经过一些搜索,我发现我需要使用push_back方法而不是索引运算符。

这是其中一种选择。另一个选择是使用构造函数来初始化元素:std::vector<double>(10)。还有一种选择是使用std::vector::resize

为什么在C++中使用向量的索引运算符被认为是不好的风格?

总的来说,并不是不好的风格。但如果你尝试访问的索引处没有元素,则是错误的(而不仅仅是不好的风格)。


1

我应该只是输入 std::vector<double> x(10) 吗?

绝对是的!

@Some programmer dude's answer 中所述,std::vector::reserve() 只影响分配策略而不影响向量的大小。

 std::vector<double> x(10);

实际上等价于

 std::vector<double> x;
 x.resize(10);

1
std::vector的括号运算符允许您访问向量中索引i处的项。如果不存在项目i,则无法访问它,无论是用于写入还是读取。

那么我想知道的是,为什么索引运算符让我访问给定索引处在向量x中“存储”的值,但不会改变其大小。

因为它不是设计成那样工作的。可能设计人员认为这种行为并不理想。

请注意,std::vector :: reserve确实为向量保留内存,但实际上不会更改其大小。因此,在调用x.reserve(10)之后,您的向量仍具有0的大小,尽管已分配了10个元素的内部内存。如果现在要添加一个元素,则不能使用括号运算符,而是必须使用std::vector :: push_back。该函数将增加向量的大小一次,然后附加您的项。调用reserve的优点是,在多次调用push_back时不必重新分配向量的内存。

std::vector<double> x;
x.reserve(3);
x.push_back(3);
x.push_back(1);
x.push_back(7);

我认为你想要的行为可以通过使用std::vector::resize函数实现。该函数会像reserve一样保留内存,然后实际更改向量的大小。
std::vector<double> x;
x.resize(3);
x[0] = 3;
x[1] = 1;
x[2] = 7;

前面的代码与以下代码等效:

std::vector<double> x(3);
x[0] = 3;
x[1] = 1;
x[2] = 7;

这里的大小是构造函数参数。以这种方式创建vector会在创建时执行调整大小操作。


非常感谢,我认为你的回答是一个很好的补充,帮助我改进。 - Nerdrigo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接