引用与指针的比较

一、引用与指针基本概念的异同

 

1、引用必须依附一个已存在对象,所以在定义一个引用变量时,必须用一个已存在的对象,对其进行初始化。而指针变量则可以独立存在。例如:

class CBase

{...}

 

CBase oB1;

CBase &rB1=oB1; //合法,引用变量rB1指向对象oB1.

CBase &rB2;       //非法,编译时,编译器报错,必须对其进行初始化。

  

CBse *pB1=&oB1; //合法,用oB1的地址初始化指针变量pB1。

CBse *pB2;           //合法,pB2是一个未初始化的指针变量,指向一个未知空间(有的编译器将其初始化

                             为null)。

 

 

2、引用变量一旦被始始化,就不能指向其它对象。所有对此引用变量的操作(注意:包括赋值操作。),都

     会作用到它所引用的对象上。这就是戏称中的引用的“从一而终”的属性。而指针变量显然不同。例如:

 

在《C++ Primer》中举了以下例子

 

int ival = 1024;

int ival2 = 2048;
int *pi = &ival;

int *pi2 = &ival2;

 

pi = pi2;  //此时pi与pi2指向了相同的内存空间——变量ival2所占用的内存空间。

 

int &ri = ival;

int &ri2 = ival2;


ri = ri2;  //此时ri仍旧指向ival,只不过ival的值通过此赋值语句变得与ival2相同了。也就是说这个赋值语句

                的实际意义是:用ival2的值来改变ival的值。

 

 

其实引用变量与指针变量,在本质上都是一个某地址空间的地址值,在这一点上两者是相同的。只不过两者在属性上有所不同而已。假设上例中ival的地址是0xff5f则,ri的实际值就是0xff5f;ival2的地址是0xfffa则ri2的值就是0xfffa。

语句 ri = ri2; 改变的是ri所指向的对象ival的值,此时ri的值仍为0xff5f 而ival的值变成了2048。

 

3、同为地址变量的引用和指针还有如下不同

a、初始化

     在前面的例子里有:    

     CBase oB1;

     CBase &rB1=oB1; //合法,引用变量rB1指向对象oB1.

    

     此时我们可以这样来初始化rB2:

     CBase &rB2=oB1;

     也可以这样来初始化它:

     CBase &rB2=rB1;    //此时rB2与rB1引用了同一个对象oB1,或者说对象oB1有了两个别名。

 

     但显然我们可以这样来初始化指针变量pB1:

     CBase *pB1=&oB1;

     但却不能这样来初始化它:

     CBase *pB1=oB1; //非法,类型不匹配

    

b、赋值

     同样在赋值时我们可以这样写:

     rB2 = rB1;

     也可以这样写:

     rB2 = oB1;

 

     但显然我们可以这样写:

     CBase *pB2;

     pB2 = pB1;

     却不能这样写:

     pB2 = oB1;  //必须用取址运算符&

 

之所以有这种区别是因为,引用变量虽然是一个地址变量,但它可以被当作它所引用的对象来使用,而我们可以用同类型的对象,来直接对同类型的其它对象进行初始化和赋值操作。也就是说一个引用变量既可以被看作一个地址变量,又可以被看作它所引用的对象。

显然指针变量就不具备这种特点。它只能被当作一个地址变量来看待。

 

 

二、我们的思考

 

如果如上所说引用的赋值,实际上是对被引用对象的值的“拷贝”,那么我们在使用引用时就有下面的问题:

问题一、类类型对象的引用之间在进行赋值操作时,会出现什么情况?

问题二、类类型对象的引用之间在进行赋值操作时,其速度比指针变量间的赋值操作快还是慢?

问题三、类类型对象的引用变量进行传递时,会产生对象切片现象吗?

 

假设我们有两个类:

 

class CBase
{
public:
 CBase(const char* pszName): m_oName(pszName){}

 virtual void Show()
 {
  cout << "I am Base :" << m_oName.c_str()<< endl;
 }

protected:
 const string m_oName;
};

 

 

class CDerive : public CBase
{
public:
 CDerive(const char* pszName):CBase(pszName){}
 virtual void Show()
 {
  cout << "I am Derive :" << m_oName.c_str() << endl;
 }
};

 

 

考虑第一个问题,我们有:

CBase oB1("One");

CBase oB2("Two");

 

CBase &rB1=oB1;

CBase &rB2=oB2;

 

 如果我们做如下操作:

 rB1 = rB2;

 

 由于引用的赋值操作,实质上是它们所引用的对象间的赋值。所以以上的赋值就相当于:

 oB1 = oB2;

 

 因为我们没有对CBase类定义赋值运算,所以编译器会报错。

(很明显,对指针变量来说,就不存在这样的问题。)

 由于类类型对象的引用之间进行的赋值运算,要通过重载赋值运算符来进行,也就暗示了它应该是对
对象的成员变量逐值进行拷贝的。这说明从本质上来说它是以对象间的传值方式来进行的。而类类型对象
的指针变量在赋值运算时,仅仅是在两个变量间传递了一个对象的内存地址。

显然,后者的速度要快的多。

 

现在考虑第三个问题:

按照《C++编程思想》里的说法,对象切片发生在派生类对象向其基类对象进行值传递时(这一般发生在作为
函数参数进行传递时)。比如:

void call(CBase oB)
{
    oB.Show();
}

int main()
{
  CDerive oD1("aa");
  call(oD1);

  return 0;
}

其运行结果为显示:"I am Base : aa",而不是"I am Derive : aa"。这是因为在调用call时,oD1会以值传递的方式传入,而call函数内部会生成一个CBase类型的对象,只接受oD1对象的相对应的部分,其余部分则被舍弃(就象是被切除了)。在生成这个CBase对象时,会调用CBase的复制构造函数,使得对象的VPTR指针指向CBase的VTABLE。这样就会在虚函数调用时只调用相应的CBase的虚函数。

 

引用变量间的赋值运算由赋值运算符的重载函数来决定,而引用变量作为函数参数传递时只会传入地址值。

可见引用变量间的传递并不会产生对象切片现象。

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值