当前位置: 首页 > news >正文

[转载]C++引用浅谈

一、引用的概念

 

引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名; 

说明:

  (1&在此不是求地址运算,而是起标识作用。

  (2)类型标识符是指目标变量的类型。

  (3)声明引用时,必须同时对其进行初始化。

  (4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。

  int a,&ra=a;

  a为目标原名称,ra为目标引用名。给ra赋值:ra=1; 等价于 a=1;

  (5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&raa相等。

  (6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。

  例如: Point pt1(10,10);

  Point &pt2=pt1; 定义了pt2pt1的引用。通过这样的定义,pt1pt2表示同一对象。

  需要特别强调的是引用并不产生对象的副本,仅仅是对象的同义词。因此,当下面的语句执行后:

  pt1.offset1212);

  pt1pt2都具有(1212)的值。

  引用必须在定义时马上被初始化,因为它必须是某个东西的同义词。你不能先定义一个引用后才

  初始化它。例如下面语句是非法的:

  Point &pt3

  pt3=pt1

  那么既然引用只是某个东西的同义词,它有什么用途呢?

  下面讨论引用的两个主要用途:作为函数参数以及从函数中返回左值。

 

二、引用参数

 

  1、传递可变参数

  传统的c中,函数在调用时参数是通过值来传递的,这就是说函数的参数不具备返回值的能力。

  所以在传统的c中,如果需要函数的参数具有返回值的能力,往往是通过指针来实现的。比如,实现两整数变量值交换的c程序如下:

  void swapint(int *a,int *b)

  {

  int temp;

  temp=*a;

  *a=*b;

  *b=temp;

  }

  使用引用机制后,以上程序的c++版本为:

  void swapint(int &a,int &b)

  {

  int temp;

  temp=a;

  a=b;

  b=temp;

  }

  调用该函数的c++方法为:swapintx,y); c++自动把x,y的地址作为参数传递给swapint函数。

  2、给函数传递大型对象

  当大型对象被传递给函数时,使用引用参数可使参数传递效率得到提高,因为引用并不产生对象的副本,也就是参数传递时,对象无须复制。下面的例子定义了一个有限整数集合的类:

  const maxCard=100;

  Class Set

  {

  int elems[maxCard]; // 集合中的元素,maxCard 表示集合中元素个数的最大值。

  int card; // 集合中元素的个数。

  public:

  Set () {card=0;} //构造函数

  friend Set operator * (Set ,Set ) ; //重载运算符号*,用于计算集合的交集 用对象作为传值参数

  // friend Set operator * (Set & ,Set & ) 重载运算符号*,用于计算集合的交集 用对象的引用作为传值参数

  ...

  }

  先考虑集合交集的实现

  Set operator *( Set Set1,Set Set2)

  {

  Set res;

  for(int i=0;i<Set1.card;++i)

  for(int j=0;j<Set2.card;++j)

  if(Set1.elems[i]==Set2.elems[j])

  {

  res.elems[res.card++]=Set1.elems[i];

  break;

  }

  return res;

  }

  由于重载运算符不能对指针单独操作,我们必须把运算数声明为 Set 类型而不是 Set * 

  每次使用*做交集运算时,整个集合都被复制,这样效率很低。我们可以用引用来避免这种情况。

  Set operator *( Set &Set1,Set &Set2)

  { Set res;

  for(int i=0;i<Set1.card;++i)

  for(int j=0;j<Set2.card;++j)

  if(Set1.elems[i]==Set2.elems[j])

  {

  res.elems[res.card++]=Set1.elems[i];

  break;

  }

  return res;

  }

 

三、引用返回值

 

  如果一个函数返回了引用,那么该函数的调用也可以被赋值。这里有一函数,它拥有两个引用参数并返回一个双精度数的引用:

  double &max(double &d1,double &d2)

  {

  return d1>d2?d1:d2;

  }

  由于max()函数返回一个对双精度数的引用,那么我们就可以用max() 来对其中较大的双精度数加1

  max(x,y)+=1.0;

 

引用作为返回值,必须遵守以下规则:

  (1)不能返回局部变量的引用。这条可以参照Effective C++Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。 

  (2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak

  (3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
  

  (4)引用与一些操作符的重载:
  流操作符<<>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

 

  (5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第23两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

 

四、常引用

 

  常引用声明方式:const 类型标识符&引用名=目标变量名;

  用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。

  【例】:

  int a ;

  const int &ra=a;

  ra=1; //错误

  a=1; //正确

  这不光是让代码更健壮,也有些其它方面的需要。

  【例】:假设有如下函数声明:

  string foo( );

  void bar(string & s);

  那么下面的表达式将是非法的:

  bar(foo( ));

  bar("hello world");

  原因在于foo( )"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。

  引用型参数应该在能被定义为const的情况下,尽量定义为const 

 

五、引用和多态

 

  引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。

  【例】:

  class A;

  class Bpublic A{……};

  B b;

  A &Ref = b; // 用派生类对象初始化基类对象的引用

  Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。

转载于:https://www.cnblogs.com/xpowerlord/archive/2012/07/29/2614411.html

相关文章:

  • 七月总结 八月安排
  • 如何实现一卡多号
  • java actor模型和消息传递简单示例
  • C语言--库文件
  • C# 4.0 新特性
  • 一花一世界--VMware Horizon 部署 Step by Step Part 1
  • vi快捷键
  • 编译linux内核Documentation为man手册
  • 自定义标签
  • 前端代码标准最佳实践:CSS篇
  • oracle set和col命令的整理
  • [翻译]计划扑克牌: 避免工作量估算中的错误
  • 对于paip..net代码生成器使用方法的总结
  • 插入类排序
  • ASP.NET MVC ModelState与数据验证【转】
  • [数据结构]链表的实现在PHP中
  • android 一些 utils
  • Apache的基本使用
  • Consul Config 使用Git做版本控制的实现
  • Java IO学习笔记一
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • miaov-React 最佳入门
  • passportjs 源码分析
  • Python 反序列化安全问题(二)
  • react 代码优化(一) ——事件处理
  • ViewService——一种保证客户端与服务端同步的方法
  • Vultr 教程目录
  • Webpack 4 学习01(基础配置)
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 欢迎参加第二届中国游戏开发者大会
  • 开源SQL-on-Hadoop系统一览
  • 译米田引理
  • 原创:新手布局福音!微信小程序使用flex的一些基础样式属性(一)
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #pragam once 和 #ifndef 预编译头
  • #QT(TCP网络编程-服务端)
  • (10)ATF MMU转换表
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (一)u-boot-nand.bin的下载
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .NET Framework杂记
  • .NET导入Excel数据
  • .Net各种迷惑命名解释
  • .sh
  • .ui文件相关
  • /bin/rm: 参数列表过长"的解决办法
  • :not(:first-child)和:not(:last-child)的用法
  • [2019.3.20]BZOJ4573 [Zjoi2016]大森林
  • [2021ICPC济南 L] Strange Series (Bell 数 多项式exp)
  • [BROADCASTING]tensor的扩散机制