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;是非法的。

第六章 指针与const的更多相关文章

  1. C和指针 第六章 指针6.2 6.3字符串中查找的两个版本

    int find_char(char **strings, char ch) { char *string; while ((string = *strings++) != NULL) { while ...

  2. 深入理解 C 指针阅读笔记 -- 第六章

    Chapter6.h #ifndef __CHAPTER_6_ #define __CHAPTER_6_ /*<深入理解C指针>学习笔记 -- 第六章*/ typedef struct _ ...

  3. [C++ Primer Plus] 第7章、函数(一)程序清单——递归,指针和const,指针数组和数组指针,函数和二维数组

    程序清单7.6 #include<iostream> using namespace std; ; int sum_arr(int arr[], int n);//函数声明 void ma ...

  4. 精读《C++ primer》学习笔记(第四至六章)

    第四章: 重要知识点: 4.1 基础 函数调用是一种特殊的运算符,它对运算对象的数量没有限制. 重载运算符时可以定义运算对象的类型,返回值类型,但运算对象的个数,运算符的优先级,结合律无法改变. 当一 ...

  5. c++ 吕凤翥 第六章 类和对象(二)

    c++ 吕凤翥 第六章 类和对象(二) 指针   引用  和数组 一:对象指针和对象引用 1.指向类的成员的指针 分为指向成员变量和指向成员函数两种指针 成员变量的格式:     类型说明符  类名: ...

  6. UNP学习笔记(第二十六章 线程)

    线程有时称为轻权进程(lightweight process) 同一进程内的所有线程共享相同的全局内存.这使得线程之间易于共享信息,然后这样也会带来同步的问题 同一进程内的所有线程处理共享全局变量外还 ...

  7. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第六章:在Direct3D中绘制

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第六章:在Direct3D中绘制 代码工程地址: https://gi ...

  8. 【C++】《Effective C++》第六章

    第六章 继承与面向对象设计 条款32:确定你的public继承塑模出is-a关系 public隐含的寓意:每个派生类对象同时也是一个基类对象,反之不成立.只不过基类比派生类表现出更一般化的概念,派生类 ...

  9. 【C++】《C++ Primer 》第十六章

    第十六章 模板与泛型编程 面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况. OOP能处理类型在程序允许之前都未知的情况. 泛型编程在编译时就可以获知类型. 一.定义模板 模板:模板是泛型编 ...

随机推荐

  1. Django学习总结-之-URLS反向解析

    2018-09-15  09:58:49 在CSDN博客审核效率提高之前, 又要在此处向各位唠叨了~ URL 与 URI URL : 统一资源定位符 相当于绝对路径 URI : 统一资源标志符 相当于 ...

  2. (转)GEM -次表面散射的实时近似

    次表面散射(Subsurface Scattering),简称SSS,或3S,是光射入非金属材质后在内部发生散射, 最后射出物体并进入视野中产生的现象, 即光从表面进入物体经过内部散射,然后又通过物体 ...

  3. 1.linux环境配置

    首先说一下,这里是虚拟机环境. 1.用vbox安装centos6.8-mini 注意不要使用复制的方式安装,复制的虚拟机网络不通 安装如下: 主机 ip 角色 内存 hadoop1 192.168.0 ...

  4. HADOOP docker(三):HDFS高可用实验

      前言1.机器环境2.配置HA2.1 修改hdfs-site.xml2.2 设置core-site.xml3.配置手动HA3.1 关闭YARN.HDFS3.2 启动HDFS HA4.配置自动HA4. ...

  5. HDU 3726 Graph and Queries(平衡二叉树)(2010 Asia Tianjin Regional Contest)

    Description You are given an undirected graph with N vertexes and M edges. Every vertex in this grap ...

  6. 声明变量&定义变量

            从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存.而定义就是分配了内存.这对于以关键字extern进行声明是一定成立的,而对声明格式“ ...

  7. java实现屏幕共享的小程序

          最近在做软件软件工程的课程设计,做一个用于实验室的屏幕监控系统,参考各种前人代码,最后领悟之后要转换自己的代码,初学者都是这样模仿过来的.       说到屏幕监控系统,有教师断和学生端, ...

  8. 常用算法Java实现之选择排序

    选择排序算法在每一步中选取最小值来重新排序,通过选择和交换来实现排序. 具体流程如下: 1.首先从原数组中选择最小的1个数据,将其置于第一个位置. 2.然后从剩下的数据中再选择其中最小的一个数据,并将 ...

  9. block知识总结

    一.block在内存中存在的形式 1.当把block句法写在函数或者方法外面时,系统会在静态数据区分配一块内存区域给block对象.这片区域在程序执行期会一直存在. 2.当block句法写在函数或者方 ...

  10. Response.End方法

    文章:在try...catch语句中执行Response.End()后如何停止执行catch语句中的内容 调用Response.End()方法能保证,只输出End方法之前的内容. 调用Context. ...