C++笔记:理解 const 指针和指向 const 对象的指针

到目前为止我遇到的最容易弄混的问题,不是理解 const 指针和指向 const 对象的指针的区别。这两个概念本身并不容易弄混。但是,它们的定义和声明,特别是加上 typedef 的干扰之后,就变得复杂和难以阅读。 首先,我给出这两种指针在 《C++ Primer》中的解释说明的原文。

4.2.5. Pointers and the const Qualifier There are two kinds of interactions between pointers and the const qualifier discussed in Section 2.4 (p. 56): We can have pointers to const objects and pointers that are themselves const. This section discusses both kinds of pointers.

Pointers to const Objects The pointers we’ve seen so far can be used to change the value of the objects to which they point. But if we have a pointer to a const object, we do not want to allow that pointer to change the underlying, const value. The language enforces this property by requiring that pointers to const objects must take the constness of their target into account:

const double *cptr; // cptr may point to a double that is const

到这里我们可以清楚的看出,对于一个指向cosnt对象的指针,它的定义是使用惯常使用的 const 限定符,同时,它的含义却不是通常的const限定符可以代表的。 const double *cptr 并不能说明我们无法修改cptr,只是说明我们无法修改*cptr。

Here cptr is a pointer to an object of type const double. The const qualifies the type of the object to which cptr points, not cptr itself. That is, cptr itself is not const. We need not initialize it and can assign a new value to it if we so desire. What we cannot do is use cptr to change the value to which it points:

*cptr = 42; // error: *cptr might be const

It is also a compile-time error to assign the address of a const object to a plain, nonconst pointer:

const double pi = 3.14; double *ptr = π // error: ptr is a plain pointer const double *cptr = π // ok: cptr is a pointer to const

We cannot use a void* pointer (Section 4.2.2, p. 119) to hold the address of a const object. Instead, we must use the type const void* to hold the address of a const object:

const int universe = 42; const void *cpv = &universe; // ok: cpv is const void *pv = &universe; // error: universe is const

A pointer to a const object can be assigned the address of a nonconst object, such as

double dval = 3.14; // dval is a double; its value can be changed cptr = &dval; // ok: but can’t change dval through cptr

Although dval is not a const, any attempt to modify its value through cptr results in a compile-time error. When we declared cptr, we said that it would not change the value to which it points. The fact that it happens to point to a nonconst object is irrelevant.

We cannot use a pointer to const to change the underlying object. However, if the pointer addresses a nonconst object, it is possible that some other action will change the object to which the pointer points.

The fact that values to which a const pointer points can be changed is subtle and can be confusing. Consider:

dval = 3.14159; // dval is not const *cptr = 3.14159; // error: cptr is a pointer to const double *ptr = &dval; // ok: ptr points at non-const double *ptr = 2.72; // ok: ptr is plain pointer cout « *cptr; // ok: prints 2.72

In this case, cptr is defined as a pointer to const but it actually points at a nonconst object. Even though the object to which it points is nonconst, we cannot use cptr to change the object’s value. Essentially, there is no way for cptr to know whether the object it points to is const, and so it treats all objects to which it might point as const.

When a pointer to const does point to a nonconst, it is possible that the value of the object might change: After all, that value is not const. We could either assign to it directly or, as here, indirectly through another, plain nonconst pointer. It is important to remember that there is no guarantee that an object pointed to by a pointer to const won’t change.

It may be helpful to think of pointers to const as “pointers that think they point to const.”

In real-world programs, pointers to const occur most often as formal parameters of functions. Defining a parameter as a pointer to const serves as a contract guaranteeing that the actual object being passed into the function will not be modified through that parameter.

const Pointers In addition to pointers to const, we can also have const pointersthat is, pointers whose own value we may not change:

int errNumb = 0; int *const curErr = &errNumb; // curErr is a constant pointer

Reading this definition from right to left, we see that “curErr is a constant pointer to an object of type int.” As with any const, we may not change the value of the pointerthat is, we may not make it point to any other object. Any attempt to assign to a constant pointereven assigning the same value back to curErris flagged as an error during compilation:

curErr = curErr; // error: curErr is const

As with any const, we must initialize a const pointer when we create it.

The fact that a pointer is itself const says nothing about whether we can use the pointer to change the value to which it points. Whether we can change the value pointed to depends entirely on the type to which the pointer points. For example, curErr addresses a plain, nonconst int. We can use curErr to change the value of errNumb:

if (*curErr) { errorHandler(); *curErr = 0; // ok: reset value of the object to which curErr is bound }

const Pointer to a const Object We can also define a constant pointer to a constant object as follows:

const double pi = 3.14159; // pi_ptr is const and points to a const object const double *const pi_ptr = π

In this case, neither the value of the object addressed by pi_ptr nor the address itself can be changed. We can read its definition from right to left as “pi_ptr is a constant pointer to an object of type double defined as const.”

Pointers and Typedefs The use of pointers in typedefs (Section 2.6, p. 61) often leads to surprising results. Here is a question almost everyone answers incorrectly at least once. Given the following,

typedef string *pstring; const pstring cstr;

what is the type of cstr? The simple answer is that it is a pointer to const pstring. The deeper question is: what underlying type does a pointer to const pstring represent? Many think that the actual type is

const string *cstr; // wrong interpretation of const pstring cstr

That is, that a const pstring would be a pointer to a constant string. But that is incorrect.

The mistake is in thinking of a typedef as a textual expansion. When we declare a const pstring, the const modifies the type of pstring, which is a pointer. Therefore, this definition declares cstr to be a const pointer to string. The definition is equivalent to

// cstr is a const pointer to string string *const cstr; // equivalent to const pstring cstr

Advice: Understanding Complicated const Type Declarations Part of the problem in reading const declarations arises because the const can go either before or after the type:

string const s1; // s1 and s2 have same type, const string s2; // they’re both strings that are const

When writing const definitions using typedefs, the fact that the const can precede the type can lead to confusion as to the actual type being defined:

string s; typedef string *pstring; const pstring cstr1 = &s; // written this way the type is obscured pstring const cstr2 = &s; // all three decreations are the same type string *const cstr3 = &s; // they’re all const pointers to string

Putting the const after pstring and reading the declaration from right to left makes it clearer that cstr2 is a const pstring, which in turn is a const pointer to string.

Unfortunately, most readers of C++ programs expect to see the const before the type. As a result, it is probably a good idea to put the const first, respecting common practice. But it can be helpful in understanding declarations to rewrite them to put the const after the type.

可是目前我对于 typedef 困惑下的 *const 和 const * 声明还是有些迷糊。