常量向量是否意味着常量元素?

62

const vector<A>表示向量vector里的元素也是const吗?

在下面的代码中,

v[0].set(1234);在函数void g(const vector<A>& v)中会导致编译器错误:

const.cpp:28:3: error: member function 'set' not viable: 'this' argument has type 'const value_type' (aka 'const A'), but function is not marked const

为什么会出现这种情况?

但是,在函数void h(const vector<A *>& v)中,(*v[0]).set(1234);对于编译器来说是OK的。

这两个版本间有什么区别?

// ...........................................................
class A {
private:
  int a;
public:
  A (int a_) : a (a_) { }
  int get () const { return a; }
  void set (int a_) { a = a_; }
};

// ...........................................................
void g ( const vector<A> & v ) {
  cout << v[0].get();
  v[0].set (1234); 
} // ()

// ...........................................................
void h ( const vector<A *> & v ) {
  cout << (*v[0]).get();
  (*v[0]).set(1234);
} // ()
4个回答

38

是的,一个 const vector 提供对其元素的访问,就像它们是 const 一样,即它仅提供 const 引用。在您的第二个函数中,不是类型为 A 的对象是 const,而是指向它们的 指针const。指针被声明为 const 并不意味着指针指向的对象是 const。要声明指向常量的指针,请使用类型 A const *


16
不,这些元素不是“const”,那将是“vector<const A>”。你只是无法获取对它们的非const引用或指针。 - Mike Seymour
@lisyarus 根据这个,第二个函数中的元素被const了两次。一次是因为你不能改变数组单元格(vector是const),另一次是因为元素本身(指针)在一个const vector中是const的。这有意义吗? - cibercitizen1
2
@cibercitizen1 不是的。在第二个函数h中,您没有改变指针,而是改变了指针所指向的 A。而指针指向的是一个非const的 A,所以这不是一个问题。容器只给您提供了对保存在其中的指针的 const 访问权限,因此您不能更改它指向的内容。 - Oguk
1
@cibercitizen1 我不明白... _"元素被const两次"_?一个变量要么是const,要么不是;没有双重const的概念。在这里,向量是const的(即您不能向向量添加/删除元素或修改其大小)。一个const向量将通过[]运算符返回其元素的const引用。在第一种情况下,您无法更改_const int&_的值。在第二种情况下,您无法更改对常量指针的引用的值,但可以更改指针所指向的值。 - Julian
3
作为一个附注:vector<const T> 是不可能的。T 不允许是 constvolatile 的。在这里也可以参考这个答案:https://dev59.com/-Ww05IYBdhLWcg3w6F4w#6955332 - Zacryon

24

第一个版本

v[0].set (1234); 

由于尝试通过引用返回向量的第一个元素来改变它,因此代码无法编译。编译器认为这是一种更改,因为 set(int) 没有被标记为 const

另一方面,第二个版本只从向量中读取

(*v[0]).set(1234);

并对通过常量引用解引用指针的结果调用set

当您在const向量上调用v[0]时,会返回Aconst引用。当元素类型是指针时,在其上调用set是可以的。您可以将第二个示例更改为

v[0]->set(1234);

你会得到与之前相同的结果,这是因为你获得了一个指针的引用,该指针是常量,但指针所指向的项目不是常量。


你的第一句话是错误的,我认为。v[0].set(1234)并不是写入向量,而是写入其在0位置的元素。 - cibercitizen1
6
我最喜欢这个答案。换句话说,const vector<T> 就像在声明时同时把 vector 和 T 都设为 const,无论 T 是什么类型。即,在 const vector<A> 中,A 应该被视为常量。在 const vector<A*> 中,A* 应该是常量,但它们所指向的内容却不是常量。 - cibercitizen1

20

因此,一个const对象只能调用const方法。也就是说:

class V {
  public:
    void foo() { ... }        // Can't be called
    void bar() const  { ... } // Can be called
};

那么让我们来看一下vector的operator[]

reference       operator[]( size_type pos );
const_reference operator[]( size_type pos ) const;

当向量对象是常量时,它将返回 const_reference

关于:(*v[0]).set(1234);

让我们来分析一下:

A * const & ptr = v[0];
A & val = *ptr;
val.set(1234);

请注意,您有一个指向变量数据的常量指针。因此,您无法更改所指向的内容,但可以更改指针指向的值。

投票支持此答案明确地将元素标记为 A * const 的引用。 - dragonxlwang

8
是的,因为`std::vector`是一个值类型而不是引用类型。
简单来说:`std::vector`将其缓冲区中的值视为自身的一部分,因此改变这些值意味着改变向量本身。如果我们只将向量视为持有指向已分配缓冲区和大小的指针,那么这可能会令人困惑:当我们改变缓冲区中的元素时,并不会改变这两个字段。
这与指针相反,指针是引用类型;如果你改变了指向的值,你并没有改变指针本身。
事实上,`std::vector` 是一个值类型是一种设计选择 - 这不是 C++ 语言固有的特性。因此,例如,`std::span` 类也基本上是一个指针和一个大小的组合,但是 `std::span` 可以是 `const` 的,同时你仍然可以更改所指向的元素。(spans 和 vectors 之间还有其他差异。)

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