首页 > 代码库 > C++中引用、指针与const之间的爱恨情愁
C++中引用、指针与const之间的爱恨情愁
学过C语言基础的肯定都知道变量和数据类型是再简单不过的知识,然而这个基础中确有几个泥潭,稍有不慎就粉身碎骨——编程受阻,面试被刷。其中一个就是引用、指针和const,以及相互之间剪不断理还乱的关系。今天我们就来理一理。
1.引用是个什么鬼
1.1引用的概念
引用是为对象另外起的一个名字,也就是别名而已。那什么是对象呢?注意这里说的对象和面向对象里的对象不是一回事。这里的对象是内存的一块区域,它具有某种类型,变量是命名了的对象。可以这么认为,引用与对象简单的关系就像姓名和本人。姓名可以多换几个,但是必须和一个实在的人对应起来,不管它是是男是女,是死是生,甚至可以是某个小说里杜撰的,但是一定要有个显示的人与之对应。
一般在初始化变量时,初始值会被拷贝到新建的对象中,然而在定义引用时,程序把引用和它的初始值绑定在一起了,而不是将初始值直接拷贝给引用,也就是所,引用指向对象,但数据就一份。一旦初始化完成,引用将和它的初初识值对象绑定在一起,因为引用不能重新绑定另一个对象,所以引用必须初始化(要点一)。
看下面的程序:
int ival=1024; int &refVal=ival; int &refval2;第二行的定义了ival的引用refVal。第三行的就不对了,原因就是上面的要点一:未初始化。
需要注意的是,不能解绑、必须初始化不代表着引用类型的值就不能变了,事实上其改变更加多样。定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的(要点二)。例如下面的程序:
<span style="font-size:18px;">int i,&ri=i; i=5; ri=10; cout<<i<<";"<<ri<<endl;</span>这里将ri与i绑定了,这里用i来初始化ri,但事实上在第一行i也并未初始化,这没关系。就像新婚夫妻给自己的孩子取了名字,但是孩子还没有呢,但是这没关系,名字属于未来出生的孩子。
中间两行是赋值,如果按照这个顺序,最后i和ri的值都为10。反过来,如果将中间两行反过来呢?那i和ri的值都是5.原因同样是要点二。
const本身不复杂,难的在于实践中,一旦与指针、函数和const等结合在一起,经常出现很伤脑筋的问题。const是什么呢?《C++ Primer》中给出的定义是:const是一种类型修饰符,用于说明永远不改变的对象
1.2引用的定义和使用
引用的符号是“&”,对于引用的定义,记住两个要点就行了:引用的初始值必须是一个对象(要点三),引用类型的定义类型与初始值对象类型要一样(要点四)。
例如:① int refVal4=10;是错误的,原因是要点三。
② double dval=3.14;
int &refVal5=dval; 是错误的,原因是要点四。
看几个例子:
③ int ival=1.01;int &rval2=ival; 这个代码是正确的,原因是ival的值是整数类型1,而不是1.01 ,但是这种代码有隐患,容易产生意想不到的问题。
④ int i=0, &r1=i;
double d=2.1, &r2=d;看两个赋值:
r2=r1;这么写是正确的,为什么呢,我感觉,这其实就是用普通的一个数r1给r2赋值,与r2=0没有区别。
r1=d;这么写也是正确的,其实这里就是赋值语句r1=2.1,只不过最后结果被强制类型转换成了2.
再看一个比较特殊的例子:
int i, &ri=i, &r2=ri;
i=5;
在这里,&r2=r1,在gcc下编译是正确的,但是否是说可以为引用定义引用呢?按照《C++ Primer》的说法,引用本身不是一个对象,因此不能定义引用的引用,但是最后输出的结果是三个变量值都为5。我感觉这里ri应该指向了i,这里其实还是为i定义的引用。
我测试上面的功能用的两个完整代码:
#include<iostream> using namespace std; int main() { int i=1,&r1=i; double d=2.1,&r2=d; //r2=3.1415; //cout<<"r2:"<<r2<<";d:"<<d<<endl; // r2=r1; // cout<<"r1:"<<r1<<";r2:"<<r2<<endl; // i=r2; // cout<<"i:"<<i<<";r2:"<<r2<<endl; r1=d; cout<<"r1:"<<r1<<";d:"<<d<<endl; return 0; }第二个:
#include<iostream> using namespace std; int main() { int i,&ri=i,&r2=ri; ri=10; i=5; cout<<i<<" "<<ri<<";"<<r2<<endl; return 0; }
2.指针是个什么鬼
指针可不是闹着玩的,一本书都解释不完(还真有本大牛的书,叫《C与指针》)。可以简单认为,指针就是某个对象的地址。指针的细节非常多,大部分人应该都知道,这里我们重点分析与引用的区别。
指针定义也很简单:
int ival=42;
int *p=&ival。
比较要命的就是这里有个“&”,指针里表示某个对象的地址,初学时容易与引用混在一起。上面的代码可以写成下面的形式,更容易理解:
int ival=42;
int *p;
p=&ival;
通过指针可以访问对象,例如
cout<<*p;就可以输出42.效果与cout<<ival一样的。
其实指针和引用容易混淆的关键之一就是“&”和“*”这种符号可以有多种含义。
在等号左边,符号随着类型名出现,是声明的一部分(要点5.1)。如果在等号右边,符号在一个表达式中,因此是取地址(&)或者解析的(*)。(要点5.2)
int i=42; int &r=i;//要点5.1 int *p;//要点5.1 p=&i;//要点5.2 *p=i;//这里其实就是用常量i给*p赋值,《C++ Primer》里说这是解引用,不懂,请高手指点 int &r2=*p;//这里与int &r2=42;是一样的,从地址P所指向的对象给引用r2赋值。
指向指针的引用
3 const限定符
const是一种类型,用于说明永不改变的对象,这也意味着const对象一旦定义就不能改变了,自然定义的时候就必须初始化。const的定义和使用啥的就不提了,重点介绍const与引用和const与指针。
3.1const与引用
将引用绑定到const上,就叫对常量的引用了。考虑到const的特征,对常量的引用不能用作修改它所绑定的对象。
const int ci=1024; const int &r1=ci;//正确,引用及其对应的对象都是常量 int &r2=ci;//c错误,试图让一个非常量引用指向常量引用。注意这两行代码的区别
<pre name="code" class="cpp">r1=42;//错误,r1是对常量的引用const的要求比较严,不能用对常量的引用给普通的引用初始化。例如:
<span style="font-size:18px;">int i42; const int &r1=i; const int &r2=42; const int &r3=r1*2; int &r4=r1*2;//r4是非常量引用,不能用常量引用来初始化。 </span>
在普通的引用中,引用的类型必须与其所引用对象的类型一致。但是在初始化的时候只要类型能转换过去就可以用任意表达式作为初始值。嘛意思呢?还是看代码:
double dval1=3.14; const int &r1=dval;//正确 int r2=4; const double dval2=r2;//错误其实,这里可以结合强制类型转换来理解。
由于ri引用了一个int型的数,但是dval是一个双精度浮点数,所以这里其实进行了强制转换:
const int temp=dval;
const int &ri=dval;
也即这里的ri绑定了一个临时量对象。不过这种方式虽然能编译过去,但是没什么意义,也存在隐患。C++代码还是严谨一点好。
3.1const与指针
const指针理解起来就容易多了,就是把一个指针定义成了不可改变的常量嘛。我们知道一个指针能够表示一个对象以及对象的地址,很显然,const指针就可以表示地址本身是常量还是表示的数据是常量,《C++ Primer》中前者叫顶层const,后者叫底层const。
首先看一个代码:
const double pi=3.14; double *ptr=π//错误,用常量的地址给普通指针初始化,类似于用白马代替所有马了,以偏概全了。
关于const指针,指向常量的指针不能用于改变其所指对象的值。要存放常量对象的地址,只能使用指向常量的指针。
例如:
const double pi=3.14;
const double *cptr=π *cptr=42;//错误。不能给常量地址赋值。这里即使用*p=42还是什么方式,都不可以了。
(未完待续)
C++中引用、指针与const之间的爱恨情愁