理清C++常量指针和指针常量这团乱麻
写在前面:
与其说C++中的常量指针和指针常量是一块很有嚼头的语法糖,不如说它是一块相当难啃的骨头。其实本来没什么,这无非是const int *p与int* const p的区别, 但一涉及到起名字,特别是给他们戴上“常量指针”和“指针常量”的中文帽子,由于作者和译者(针对外文书)的不同,就出现了“张冠李戴”和“李冠张戴”的乱像,不知道谁是谁了,弄得人一头雾水,尤其是对于初学者。本文的目的就是针对这一细节,为大家将两者理清楚,同时说明在使用上的区别。
注意:1.const int *p也可写成int const *p,即C++中const int和int const无区别,这使得本来就很乱的局面更加麻烦,本文中我只使用const int,以后不再说明。
2.如果您讨厌啰嗦,只想学“干货”,您可以直接跳到“安能辨我是雄雌——判断方法”一节(在下技术有限,就不设置页面内跳转了)。
在理清楚之前,让 我们先简单看看当前“乱象丛生”的现状吧。
一、const遇上指针——一团乱麻
同样的问题,相反的解释
1.标新立异的少数派
C++ Primer第五版的提法可谓与其它C++书籍背道而驰,它的提
常量指针——int* const p
指向常量的指针——const int *p
在英文版中,int* const p被称为const pointer,于是中文版将其译为常量指针;而const int *p被称为pointer to const,但对应的中文版并未将其译为指针常量,而是译为指向常量的指针。
无独有偶,我们国内的书籍《零起点学通C++》(范磊 著 清华大学出版社 2010年版)的提法和C++ Primer完全一致,将int* const p称为常量指针,而将const int *p称为指向常量的指针。
2.一边倒的大众说辞
更广为流传的提法是
常量指针——const int *p
指针常量——int* const p
明确写有“常量指针”和“指针常量”的书籍在下就见过一本——《C++面向对象程序设计》(郭有强 著 清华大学出版社 2009年版)(从出版年上我们可以大胆推测,相对较老的书会这样提)。更多的书籍的做法是——将int* const p称为指针常量,将const int *p称为指向常量的指针。(可见问题的关键在于int* const p,有的书中将其称为常量指针,如1,有的书中将其称为指针常量,如2)
这里的“一边倒”更主要的是针对网络而言。如果您百度“常量指针和指针常量”,将会看到几乎所有的材料中都采用了2中的提法。如果一个初学者在解决“常量指针和指针常量”的困惑时首先选择了网络途径,那么2的提法应该在他的脑海里根深蒂固了吧。
如果您已经接受了2中的提法,建议您将“常量指针”扩展为“指向常量的指针”,将“指针常量”扩展为“该指针是常量”来帮助您记忆和区别两者的本质。
作者们的拯救
大概一些IT教育者和从业者们也注意到了这一问题,近年来的一些书籍作者们开始用自己的方式来纠正,让我们来看看他们是怎么做的。
1.这样的改变好吗?
《C++程序设计基础》(管建和 著 清华大学出版社 2013年版)中将const int *p称为“常量化指针目标表达式”,而将int* const p称为“常量化指针变量”,试问,这么长的名字,能不能记住?记住了,能不能搞清楚?
2.两个弄不清,干脆叫一个
比较著名的外文书籍《C++高级编程》(这里就中文第二版讲)中在“const指针”的标题下同时介绍了const int *p和int* const p,而没有给他们具体起名字。
在国内,《新标准C++程序设计教程》(郭炜 著 清华大学出版社 2012年版)将const int *p称为常量指针,而在介绍int* const p时写道“还有一种常量指针”。
但这样做仍然不能称之为行之有效,因为这样并没有通过名字将两者从本质上区分,同时“不同的事物,同一个名字”也不便于交流。
3.一种值得推荐的命名法
《C++程序设计教程》(方超昆 著 北京邮电大学出版社 2009年版)和《C++从入门到精通》(人民邮电出版社 2015年版)是这样提的
指向常量的指针变量——const int* p
指向变量的指针常量——int* const p
指向常量的指针常量——const int* const p //后面会详细说到
这样,谁常谁变就一目了然了,而且名字反应了本质。从第一本书的出版年可以看出,其实这样比较清晰的命名方式好几年前就有了,但可惜的是,直到现在,在众多的C++书籍中,这般清晰的命名方式也并不多见。
在下的用意
一定会有人说:“你说了这么多,并没有给我们带来什么实质性的知识。”在下不反对,其实在下在这一部分的开头已经清醒过大家,可以略过。在下写这部分的用意有四:
1.向读者介绍一下现状和常见的提法。
2.告诉读者该问题上存在的不一致现象,当读者阅读不同的书籍时遇到不一致,不要过于困惑。
3.劝读者不要记忆网上的“常量指针”和“指针常量”,因为这样的命名并不好,许多书籍上也并没有同时提到这两者。
4.表达一下自己希望C++的相关命名实现术语化、标准化、大一统的愿望,尽管在下知道这一点很难实现。
在下怎么看
1.不要使用“常量指针”和“指针常量”,特别是后者,因为它连“表述对象是指针”这层意思都没有清晰地表达出来。
2.建议采用“一种值得推荐的命名法”下的名称来记忆。
3.或者采用在下的建议。现在说明在下的提法
指向常量的指针——const int *p
本身是常量的指针——int* const p
所指和本身都是常量的指针——const int* const p
这里采用“定语+主体”的结构,主体“指针”很好地表现了所描述对象是指针这一本质,而定语部分则描述了特性,同时也将“自己”和“他人”区别开,整体通俗易懂。(在下一直认为,有时一个高大上的名称就是故作高深,而通俗易懂本身就是一种真正的高大上。面向初学者,把话说明白,一直是在下文章的宗旨与追求。)唯一的不足可能是名称有点长,但在下相信,只要足够通俗易懂,长不是障碍。
从现在开始,我们在后面的讲述中将一直采用这种提法。
二、安能辨我是雄雌——判断方法
相信有些初学者会有这样的困惑:单提名字知道怎么回事,但一看到代码就不知道叫什么名字了,也就无法想起代码的语法特性。这里,在下给大家介绍一种简单易用的判断方法,一看代码就能分析出语法特性。
相信有些读者看到过所谓的“从右往左读”的办法,在下接下来要说的方法和“从右往左读”本质上一样,只不过无论是认知还是操作上都更加简单(起码在下认为是这样,这里就不谦虚了)。方法表述如下:
先找到*,然后看*的两边,右边是对指针p本身的限定,左边是对p所指向的东西的限定。
注:本文中我们将p的所指笼统地称为“东西”。
例如
1.const int *p
*的右边没有限定成分,表明p就是我们熟悉的普通指针,p的内容(即值,也就是它指向的东西的地址)是可以改变的;*的左边是const int,表明p指向的东西是一个const的int,我们不能通过p来修改这个int,因为它是const的。(关于“不能通过p来修改”,我们后边还会详细解释)
2.int* const p
*的右边是const,表明p本身是const的,我们不能对p的内容进行修改(例如++p;是不可以的),*的左边是int,即p指向的东西是普通的int,我们可以通过p来修改它(例如*p=100;是可以的)。
3.const int* const p
*的右边是const,表明指针p本身是const的,*的左边是const int,表明p指向的int也是const的。即这种情况下,p本身不能修改,同时也不能通过p修改它所指向的那个int。
三、天生我材必有用——语法特性
其实在讲判断方法的时候,已经涉及到了各自的语法特性。这里再针对一些细节深入说一下。
1.const int *p
就是所谓的“指向常量的指针”。这里注意,所谓“指向常量”只是这个指针的“一厢情愿”,只是一种效果上的等价。事实上,const int *p=&a;a既可以是常量(const int a=10;)又可以是变量(int a=10;),但p一厢情愿地认为它所指的就是一个常量,所以它不允许通过自己来修改所指,这就造成一种效果上的等价——从p的角度看,它所指的“的确”是常量。所以,对“指向常量的指针”的最佳理解应为:我们不能通过该指针修改它所指向的东西(常量或者变量)。
注意,const int *p=&a;只是说不能通过p来修改a,如果a本身不是const的,通过其它方式修改a自然是可以的(例如直接++a)。
另外一点,由于p本身只是一个普通的指针,所以允许在声明时不初始化。但需要注意的是,我们只是说可以,但并不提倡这样做。在任何时候都不应该让指针无所指,如果在声明一个指针时还不知道让它指向谁,就先将其初始化为nullptr或NULL(nullptr是C++11新特性,用它比用NULL更安全些,这里不详细介绍)。
2.int* const p
就是所谓的“本身是常量的指针”。关于“p本身不能修改但可以通过p修改其所指”这一点,我们在讲判断方法时已经说过,这里主要再说一下p的初始化。
由于p本身是const的,所以在编译的时候必须知道p的值(即p所指向的东西的地址),所以在声明p的同时必须初始化p。但要注意,对于 int* const p=&a,我们只要求a的地址是确定的,但a的值可以不确定。比如下面的代码是可行的
#include<iostream> using namespace std; int GetData(int num)
{
return num;
} int main()
{
int a;
cin >> a;
int b = GetData(a);
int* const p = &b;
cout << *p << endl;
return 0;
}
由于声明了int b,所以b的地址在编译时是确定的,但很显然,b的值只要在程序运行时才能确定。
另外注意,用nullptr或NULL初始化int* const p没有问题,因为nullptr和NULL都代表有效地址。
3.const int* const p
就是所谓的“所指和本身都是常量的指针”。它的语法特性就是前两者的结合,这里不再赘述。
四、番外篇——说说引用和const引用
文章至此本该结束了,但就C++而言,谈到了指针,似乎自然应该再谈谈引用。于是,在下接着再来简单说说引用和const引用。所谓“简单说说”,是指在下只是做一个简明扼要的介绍,对一些原理和细节不做深入探讨。为什么要在这里说?因为我们的讲述要会用到上面的概念。另外还要注意,我们这里只讨论左值引用,即大家所熟悉的一般引用,对于C++11新特性右值引用,不在讨论之列。
相信大家都知道引用的这些特性
1.如果一个引用和一个东西(这里再次使用“东西”这个词)绑定,那么它就永远只能是这个东西的“别名”,不能再说“其他人”的“别名”,即引用本身不能修改。但是,我们可以通过引用来修改它所引用的东西的值。
2.引用声明时必须同时初始化 ,且必须用左值初始化。(左值:就是可以用&求地址的量,换言之,就是有确定地址的量,而不是所谓的临时量)
对这些特性大家是不是似曾相识呢?没错,这些特性和“本身是常量的指针”(int* const p)的特性一样。事实上,我们完全可以借用“本身是常量的指针”来理解甚至定义引用:
引用是一个指向不可变的、被编译器自动解引用的指针,即,引用是一个被编译器自动解引用的“本身是常量的指针”。
看下面的代码
int a = 10;
int &ra = a;
ra = 11;
在上面的代码中,编译器将int &ra=a转化为int* const ra=&a,而将ra=11转化为*ra=11,将ra自动转化为*ra的过程,就是上面定义中所说的“自动解引用”。
那么,什么是const引用(即我们说的常量引用,但我希望大家称其为const引用而不是常量引用)呢?很显然,const int &ra=a就相当于const int* const ra=&a了。相信通过前面的讲解,这里不用在下多说了。
后记:
在下能力有限,尽管尽最大努力做到严谨,但错误疏漏之处仍在所难免,恳请大家批评指正。您的帮助,就是在下前进的不竭动力。
理清C++常量指针和指针常量这团乱麻的更多相关文章
- C++基础之二:常量指针和指针常量
1.常量指针 定义:具有只能够读取内存中数据,却不能够修改内存中数据的属性的指针,称为指向常量的指针,简称常量指针. 声明:const int * p; 注:可以将一个常量的地址赋值给一个对应类型的常 ...
- 【转】const int *p和int * const p的区别(常量指针与指向常量的指针)
[转]作者:xwdreamer 出处:http://www.cnblogs.com/xwdreamer 对于指针和常量,有以下三种形式都是正确的: const char * myPtr = &am ...
- C++引用和const引用、常量指针、指针常量
1.引用.常量引用 引用主要被用做函数的形式参数--通常将类对象传递给一个函数. 引用在内部存放的是一个对象的地址,它是该对象的别名.引用不占用内存,因为取地址引用的值和被引用变量的地址相同.但是ob ...
- C/C++中的常量指针与指针常量(转)
常量指针 常量指针是指向常量的指针,指针指向的内存地址的内容是不可修改的. 常量指针定义“const int *p=&a;”告诉编译器,*p是常量,不能将*p作为左值进行操作.但这里的指针p还 ...
- C语言学习笔记 (001) - 常量指针与指针常量的区别(转帖)
三个名词虽然非常绕嘴,不过说的非常准确.用中国话的语义分析就可以很方便地把三个概念区分开. 一) 常量指针. 常量是形容词,指针是名词,以指针为中心的一个偏正结构短语.这样看,常量指针本质是指针,常量 ...
- 数组/指针/const/字符串常量的使用传值问题
#include<stdio.h> #include<string.h> int main() { ] = "abcd"; //常指针a指向字符串常量&qu ...
- c++中指针常量,常指针,指向常量的常指针区分
const char * myPtr = &char_A;//指向常量的指针 char * const myPtr = &char_A;//常量的指针 const char * con ...
- C++ const 常量和常指针
常量,该指针所指向的值为只读 ; const int * p = &a; 常指针,该指针的值为只读,不可再指向其他地址 const * const p = &a; 常值,常指针 con ...
- 常量指针-指向常量的指针,指针常量-指针本身是常量,常量-不能更改值的常量,数组指针-是指针int (*p)[n] 指针数组-是数组int *p[n]
1.常量指针 定义:具有只能够读取内存中数据,却不能够修改内存中数据的属性的指针,称为指向常量的指针,简称常量指针. 声明:const int * p; int const * p; 注:可以将一个常 ...
随机推荐
- Java基础——基本类型和包装类、基本类型和字符串之间的转换
基本类型和包装类之间的转换 基本类型和包装类之间经常需要互相转换,以 Integer 为例(其他几个包装类的操作雷同哦): 在 JDK1.5 引入自动装箱和拆箱的机制后,包装类和基本类型之间的转换就更 ...
- 如何删除NSDictionary或NSArray中的NSNull
前段时间与某公司的技术交流,被问到一个问题,如何删除NSDictionary中的NSNull.当时在纸上写,以前太依赖Xcode编译器了,以至于方法名都写不全,最终也没写出来,我想我肯定被鄙视的体无完 ...
- HDU 5053 the Sum of Cube(简单数论)
http://acm.hdu.edu.cn/showproblem.php?pid=5053 题目大意: 求出A^3+(A+1)^3+(A+2)^3+...+B^3和是多少 解题思路: 设f(n)=1 ...
- csuoj 1394: Virus Replication
http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1394 1394: Virus Replication Time Limit: 1 Sec Mem ...
- iOS 如何在Label中显示html的文本
if (self.messageModel) { NSString * htmlString = self.messageModel.contentText; NSAttributedString * ...
- 论AVL树与红黑树
首先讲解一下AVL树: 例如,我们要输入这样一串数字,10,9,8,7,15,20这样一串数字来建立AVL树 1,首先输入10,得到一个根结点10 2,然后输入9, 得到10这个根结点一个左孩子结点9 ...
- hadoop的学习
http://www.aboutyun.com/thread-6179-1-1.html
- swift基础:第四部分:对函数和闭包的深入
()之前在什么公司,都自己做过哪些项目,从架构的角度来谈谈你的项目. () 你对iOS不同版本是怎么看的,你在做项目的过程当中,是如何应对版本问题的. () 你对iOS的性能是怎么优化的. () 你通 ...
- ACE bus
ACE bus增加的内容: 1):5状态的cache model 2):关于coherency的additional signal 3):两个cache master访问shared cache的ad ...
- XEN的启动信息输出到“Platform timer is 14.318MHz HPET”就暂停接收的解决办法
使用Serial Over Lan(SOL)来双机调试 XEN,在 XEN 启动时,引导信息输出到以下这一句时,就不再发送调试信息了: ...(XEN) Intel machine check rep ...