深入探索 C/C++ 数组与指针的奥秘之六:指针与 const

深入探索 C/C++ 数组与指针的奥秘之六:指针与 const

const 一词在字面上来源于常量 constant,const 对象在 C/C++ 中是有不同解析的,如第二章所述,在 C 中常量表达式必须是编译期,运行期的不是常量表达式,因此 C 中的 const 不是常量表达式;但在 C++ 中,由于去掉了编译期的限定,因此是常量表达式。
对于一个指向 const 对象的指针 pointer to const T,由于把 const 视作常量表达式,常常存在如下两种观点:
1。这是一个指向常量的指针,简称常量指针;
2。这个指针指向的内容不可改变。
这是比较粗糙的理解。虽然这个指针的类型是 pointer to const T,但不代表它指向的对象真的是一个常量或者不可改变,例如:

int i = 10; const int *p = &i; i = 20;
p 指向的对象 i 明显不是常量,虽然 p 指向 i,但 i 的值依然可以改变。对于这个现象,C++ 标准有明确的论述:
7.1.5.1 The cv-qualifiers
a pointer or reference to a cv-qualified type need not actually point or refer to a cv-qualified object, but it is treated as if it does;
其中 cv 指的是 const 和 volatile,const 和 volatile 叫 type qualifier,类型限定词。const T 只是类型假定,并非指出该对象是什么,这个对象也许是 const 限定的,也许不是。既然上述两种看法都是不恰当的,pointer to const T 又应如何看待呢?一种比较好的理解是,将其视作一条访问路径。对一个对象进行取值或者修改操作,可以有很多种方法,每种方法都相当于一条能够对对象进行访问的路径,例如:

int i = 10, k; const int *p = &i; int *q = &i; i = 20; *q = 30; k = *p;
通过 *q、*p 和标识符 i 都能访问 i 所代表的整数对象,它们可以视作三条路径,i 和 *q 能够修改该整数对象的值,这两条路径是可写可读的;但 *p 不能写,因为 p 指向的对象被假定为 const,从 p 的角度看来,*p 是只读的,不能通过 p 修改它指向的对象。因此,一个 pointer to const T 指针的确切意义,不是指向常量或者指向的对象不可改变,而是指不能通过这个指针去修改其指向的对象,无论这个对象是否 const,它只指出一条到该对象的只读路径,但存在其它路径可以修改该对象。这种理解,在标准中是有根据的:
7.1.5.1 The cv-qualifiers
a const-qualified access path cannot be used to modify an object even if the object referenced is a non-const object and can be modified through some other access path.
上述条款对访问路径进行了一个清晰的描述。
一个 pointer to T 类型的指针,可以赋值给一个 pointer to const T 类型的指针,这是众所周知的语法规则。笔者曾经一度认为,两者之所以可以赋值,是基于指针的相容性原理,以为两者是相容的,后来翻阅了 C/C++ 的标准,才认识到这种解释其实是错误的,从相容性原理来说,两者恰恰是不相容的。C 标准关于指针的相容性是这样规定的:
6.7.5.1 Pointer declarators
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
两个相容的指针,既要有同一的限定修饰词,所指向的类型也要相容的。而两个相容的类型要符合如下规定:
6.2.7 Compatible type and composite type
Two types have compatible type if their types are the same.
两个相同的类型才具有相容性,那么 cont T 和 T 是否两种相同的类型呢?再看如下条款:
6.2.5 Types
The qualified or unqualified versions of a type are distinct types that belong to the same type category and have the same representation and alignment requirements.
一个类型的限定和非限定版本是同一种类类型的具有同一表示范围及对齐需求的不同类型。这就是说,const T 和 T 不是相同的类型,两者不相容,于是,虽然 pointer to const T 与 pointer to T 具有同一的限定修饰(都没有限定词),但所指向的对象类型不是相容的类型,因此 pointer to const T 与 pointer to T 是不相容的指针类型。
既然两者不相容,又是什么原因导致它们可以赋值呢?再查阅 C 标准关于赋值运算符的规定,发现有这么个条款:
6.5.16.1 Simple assignment
Constraints
One of the following shall hold:
………
— both operands are pointers to qualified or unqualified versions of compatible types,
and the type pointed to by the left has all the qualifiers of the type pointed to by the
right;
噢,其实原因在这里!左操作数所指向的类型要包含右操作数所指向类型的所有限定词。pointer to const T 比 pointer to T 多一个 const,因此可以将 pointer to T 赋值给 pointer to const T,但反过来不行。通俗一点说,就是左操作数要比右操作数更严格。C++ 中的规定与 C 有点不同,C++ 标准去掉了这一条款,代之以 more cv-qualified 的概念,一个 pointer to cv1 T 的指针,要转换为一个 pointer to cv2 T 的指针,条件是 cv2 比 cv1 要更 cv 限定化。
要注意的一点是,这条赋值运算符的规则只适用于 pointer to qualified or unqualified type,不能延伸到 pointer to pointer to qualified or unqualified type 及更高级别的指针类型,例如:

int i = 10; const int *p = &i; /* A */ int *q = &i; const int **p1 = &q; /* B */
A 合法,但 B 不合法。虽然 p1 与 &q 都是 unqualified 的,但 p1 指向的对象类型为 pointer to const int,&q 指向的类型为 pointer to int,如前所述,两者是不相容类型,不符合两操作数必须指向相容类型的规定,因此赋值非法。
根据上述规则,一个 pointer to const T 不能赋予 pointer to T,但是,一个 const pointer 却能赋予 non-const pointer,例如:

int i; int * const p = &i; int *q; q = p; /* A */
A 合法,这种情况并不属于赋值运算符的规则之内,它遵循的是另一个条款:左值转换。一个被限定修饰的左值,在进行左值转换之后,右值具有左值的非限定修饰类型:
6.3.2 Other operands
6.3.2.1 Lvalues, arrays, and function designators
Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue). If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; otherwise, the value has the type of the lvalue.
p 的值具有 p 的非限定修饰类型 int*,与 q 类型相容,因此赋值合法。对于 C++,基本上与 C 相同,但有一个例外,就是右值类对象,由于右值类对象仍然是一个对象,C++ 规定右值类对象具有与左值相同的限定修饰词。
指针与 const 的结合能够产生一些比较复杂的声明,例如:

const int * const *** const ** const p;
这是一个较为复杂的指针声明符与 const 限定修饰词的组合,声明符部分嵌套了六次,中间还带有两个 const,如何辨认哪一级是 const,哪一级不是呢?一旦明白了其中的原理,其实是非常简单的。第一和最后一个 const 大家都已经很熟悉的了。对于藏在一堆 * 号中的 const,有一个非常简单的原则:const 与左边最后一个声明说明符之间有多少个 * 号,那么就是多少级指针是 const 的。例如从右数起第二个 const,它与 int 之间有 4 个 * 号,那么 p 的四级部分就是 const 的,下面的赋值表达式是非法的:

**p = (int *const***)10;
但下面的赋值是允许的:

***p=(int*const**)10;
从左边数起第二个 const,它与 int 之间有 1 个 *,那么 p 的一级部分是 const 的,也就是 *****p = (int*const***const*)10; 是非法的。
原文链接:http://blog.csdn.net/supermegaboy/archive/2009/11/23/4854974.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值