现在的位置: 首页 > 综合 > 正文

C++中的reference

2013年04月15日 ⁄ 综合 ⁄ 共 3386字 ⁄ 字号 评论关闭

基础知识:
引用和指针看起来很不一样(包括定义,赋值和使用),但是从本质上说它们又是类似的,无论是指针还是引用都可以间接的指向一个变量,也可以说,指针和引用引入和一个间接层.
在C++中,只有指针和引用才能够支持多态,这并不是偶然的.

让我们首先看看引用的语法,不需要一上来就是一堆标准引用,只需要一小段精心设计的代码就可以了
 int m = 10;
 int n = 20;
 
 int *p = NULL; //指针的定义
 p = &m;   //指针的赋值
 p = &n;   //指针可以重新赋值
 m = *p;   //指针的使用

 int& rn = n; //引用的定义和赋值
 rn = m;   //引用的使用

我们以这段简单的代码来说明指针和引用的区别:
1.没有空引用,而指针可以有空指针
2.引用在定义的同时必须初始化,指针定义后可以不初始化
3.引用在初始化后就指定了,不能再重新指定,指针可以重新指定
 int& rn = n;
仅仅这一句话,一个引用的前世今生已经命中注定了.引用rn就是变量n的一个别名,对rn的修改就是修改n,对n的修改也是修改rn,呵呵,到好像忠贞不渝,同生共死的一对夫妻啊,
再重新复习一下<<Effictive C++>>中的忠告吧:"任何时候看到一个引用,你都应该立刻问自己,它的另一个名称是什么,因为它一定是某物的另一个名称.",好在,大多数情况下,
引用的另一个名称并不难找到
 rn = m;   //引用的使用
如果非要对引用重新赋值又怎么样呢?这里rn仍然指向n,你不过是把m的值通过引用赋给了n

const引用:
 int& rn = 5;并不存在,但是const int& rn = 5;是存在的,这其实也没有什么可奇怪的,本来常量的类型就应该const的,在C++这种强类型语言中不这样反倒是奇怪的.
但是,我还是希望你真的理解const int& rn = 5;的含义,引用是变量的别名,不是一个常量的别名,要这句代码成立,编译器必须在后面做以下的转换:
const int temp = 5;
const int& rn = temp;
或许有人该说我过于教条了:)就把rn理解为常量5的引用又如何呢?嗯,好像也没有什么问题.
const引用最大的作用就是可以接受non-lvalue初始化
void print(const string & s)

}
 print("wgs");
如果没有const引用,这里的print("wgs");是无法编译通过的
 int n = 10;
 const int& rn = n;
比较有趣的是,这两句代码也是成立的,这里的语意是声明rn为变量n的引用,同时用const来限制rn的修改.
但是严格的说这里不是引用和引用的变量类型不同了吗?嗯,更严格的说法应该是CV-qualifiers中的偏序关系no CV-qualiifer < const吧,也就是说
增加const后其实只是增强了类型的约束

指针的引用:
 int*& p2 = p;
存在指向指针的引用,这里p2是指针p的引用,这有什么奇怪的吗?指针也是一个变量啊,或许记怪的是*&的语法吧
  int&& nn = rn;//错误
但是不存在指向引用的引用,呵呵&&的语法应该是运算符吧
  int* p3 = &rn;
存在指向引用地址的指针,引用的地址就是引用变量的地址,因为引用仅仅是引用变量的别名,这里的指针也不过是一个普通的指针而已
  int&* p4 = rn;//错误
但是不存在指向引用的指针,这时候该有人愤怒了,好好,我承认,这些东西编译器会报错的,不用记,如果谁敢考你这些东西你可以和他拼命

引用参数:
void swap(int& a, int& b)
{
 int temp = a;
 a = b;
 b = temp;
}
终于到了引用用得最多的地方了,函数的参数有传值和传引用的区别,或许有人还记得C语言中为了实现参数传引用而不得不使用的*甚至**声明参数的语法,同时还要记得
在函数中不断的解引用,而C++直接支持引用就简单多了
但是如果我们只有指针呢?
 int m = 10;
 int n = 20;
 int* pm = &m;
 int* pn = &n;
 swap(m, n);
 swap(*pm, *pn);
指针可以转换为引用,这里pm解引用后就是m,因此引用参数a就成了m的别名,或许有人喜欢拿int& nn = *(int*)n;之类的公式来解释指针到引用的转换,但是我还是喜欢
更简单点的说法"引用就是一个变量的别名".
void print(const int& a)
这种const引用参数的写法也是非常常见的,这里的语意是说参数仍然是传引用的,只不过因为声明了const类型而不允许在函数内部修改,这样做的好处就是效率

引用返回值:
struct Point
{
 double x;
 double y;

 Point& operator=(const Point& other)
 {
  if (this != &other)
  {
   x = other.x;
   y = other.y;
  }
   
  return *this;
 }
};
函数返回值为引用类型也是很常见的,这里举的例子是关于运算符重载的,这并不是偶然的,引用最初确实就是为了支持运算符重载而引入的,让我们再次强调一下,
"引用就是变量的别名",函数返回值为引用类型最直接的好处就是效率,另一方面,对于operate=这类操作符,我们也确实需要返回lvalue,这也是正是别名可以大显身手的地方
函数返回值为引用类型似乎已经足够简单了,但是我希望你还记得<<Effictive C++>>中关于函数返回值为引用的几个相关条款:
条款30:不要在对象成员函数的返回值引用中传回对象内部的成员变量,除非这个引用是const的
因为这很容易破坏对象的封装性
条款31:千万不要在函数的返回值引用中传回函数内局部对象的引用
因为函数内的局部对象在函数结束时就消失了,至于在函数的返回值引用中传回函数内new获得的指针说指的对象,这违背了指针分配和释放必须对称的原则,根本想都不要想

成员变量的引用:
template <class T>
class RefHolder
{
 T& m_ref;
public:
 RefHolder(T& ref) : m_ref(ref) {}
 operator T& () const
 {
  return m_ref;
 }
};
类中的成员变量也是变量,那么能不能定义指向成员变量的引用呢?当然可以,根本不用怀疑.但是指向成员变量的引用也带来了一个问题,我们说引用应该是定义时同时被
初始化的,但是在类的定义中声明一个指向成员变量的引用同时想指定一个该引用指向的变量却是根本不可能的,因为这是根本就不会有一个真实的变量存在.没有办法,只能采用
一种间接的方式为指向成员变量的引用初始化,这就是初始化行
 T& m_ref;
这里声明了一个引用,这里使用了模版,当然你可以把T换成任何一种具体的类型
 RefHolder(T& ref) : m_ref(ref) {}
在构造函数的初始化行上为为指向成员变量的引用初始化

危险的类型:
"引用就是变量的别名",如果引用的类型和变量的类型不同呢?
 int nn = 20;
 const short& s = nn;
 nn = 0;
 printf("%d %d", s, nn);
这里声明s是指向nn的引用,但是s的类型和nn的类型却是不同的,这当然违背了别名的本意,但是大多数编译器却会自作聪明的做出以下转换
short temp = nn;
const short& s = temp;
编译是通过了,但是执行呢?
 printf("%d %d", s, nn);
这里的打印结果是:20,0
嗯,大多数编译器如果设置了高的错误等级可以检测出这个bug,但是也有一些编译器根本不会给出任何提示,唉,拜托,你只是一个引用,是人家的别名,不要玩这种
改变人家身份的把戏好不好?老老实实的声明一个引用并初始化,连类型也要一致.

函数的别名:
尽管并不常用,但是引用确实可以指向一个函数:
void print(const string & s)

}
 void (&pfun)(const string&) = print;
 pfun("wgs");
这里的pfun就是print函数的别名

参考<<More Effective C++>>相关章节
参考<<C++高级编程>>相关章节
参考<<Effective C++>>相关章节
参考<<Imperfect C++>>相关章节
参考<<C++语言的设计和演化>>相关章节 

【上篇】
【下篇】

抱歉!评论已关闭.