最近在搞C++的引用,闲来无事,顺便写写引用的基础知识
引言:什么是引用
引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
引用的格式:
数据类型 &引用变量名 = 目标变量名;
第一章 引用的一些基本语法
(1)&在此不是求地址运算,而是起标识作用。
(2)类型标识符是指目标变量的类型。
(3)声明引用时,必须同时对其进行初始化。
(4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
(5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。
(6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
第二章 引用的应用
1、作为函数的传递参数
例:
1 #include <iostream> 2 using namespace std; 3 4 void foo(int &a, int &b) 5 { 6 int c = 0; 7 c = a + b; 8 cout << c <<endl; 9 } 10 int main() 11 { 12 int x = 1; 13 int y = 2; 14 foo(x, y); 15 16 system("pause"); 17 return 0; 18 }
其效果与值传递是一样的,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。但需要注意的是:
①使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作。这样做的好处在于:有大块数据作为参数传递的时候,可以避免将整块数据全部压栈,可以提高程序的效率;
②要达到这种效果,使用指针传递也是一样的,但是在阅读性上,还是不如引用的,因为指针这个东东,出错的概率很高>_<!
比如,这个就很不好看
1 #include <iostream> 2 using namespace std; 3 4 void foo(int *a, int *b) 5 { 6 int c = 0; 7 c = *a + *b; 8 cout << c <<endl; 9 } 10 int main() 11 { 12 int x = 1; 13 int y = 2; 14 foo(&x, &y); 15 16 system("pause"); 17 return 0; 18 }
2、常量引用
事先需要说明一点:引用是不能直接来常量引用的,比如:int &a = 1;就是一个错误的语句。
但是我们可以通过const来实现常量引用,上面的语句,我们可以改为:const int &a = 1;
需要注意的是:这种方式的声明,不能通过引用对引用变量进行修改,但是可以对目标变量名进行修正来达到目的:
#include <iostream> using namespace std; void foo(int *a, int *b) { int c = 0; c = *a + *b; cout << c <<endl; } int main() { int x = 1; int y = 2; foo(&x, &y); int z = 2; const int &a =z; //a = 5; wrong! z = 4; cout << a << '\n' <<endl; system("pause"); return 0; }
输出结果为:
3 4
3、引用作为返回值
函数声明格式:类型标识符 &函数名(形参列表及类型说明);
这种使用方法的好处在于,在内存中不产生被返回值的副本(即临时变量),例如:
使用引用作为返回值时,还需要注意以下几点:
①不能返回局部变量的引用。因为局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
②不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
③可以返回类成员的引用,但最好是const。
4、引用和多态
产生这个问题的根本来源就在于:一个基类的引用可以指向它的派生类实例。这就很容易产生一个问题:引用也会产生多态效果。
例:
1 #include <iostream> 2 using namespace std; 3 4 class A { 5 public: 6 A(); 7 virtual void foo() { 8 cout << "a" << endl; 9 }; 10 }; 11 12 A::A() { 13 } 14 15 class B: public A { 16 public: 17 void foo1() { 18 cout << "b" << endl; 19 }; 20 }; 21 22 int main() 23 { 24 B b; 25 A &a = b; 26 a.foo(); // 访问派生类对象中从基类继承下来的成员 27 // a.foo1(); // wrong! 28 b.foo(); 29 b.foo1(); 30 system("pause"); 31 return 0; 32 }
第三章 引用和指针
不同点:
①初始化:引用必须在创建的时候就初始化,但是指针可以在任何时候被初始化
②引用不能为空,但是指针可以为NULL
③引用的对象被初始化后就无法更改,但是指针指向的对象是可以更改的
④内存:引用与被引用对象共享内存空间,系统不会为引用变量分配内存空间,但系统会为指针分配内存空间
⑤在作为形参传递的时候,引用不拷贝副本,指针拷贝副本
⑥引用访问的对象是直接访问,而指针是间接访问
关系:
引用的实现,在内部来看,其实就是用指针来实现的,可以来看一个指针和指针的引用的例子:
1 #include <iostream> 2 using namespace std; 3 4 void foo(int *&x) { 5 *x = 100; 6 } 7 8 int main() { 9 int *x; 10 x = (int *)malloc(sizeof(int)); 11 *x=300; 12 foo(x); 13 //foo(*x);// wrong cannot convert parameter 1 from 'int ' to 'int *& ' 14 //foo(&x);//cannot convert parameter 1 from 'int **' to 'int *& ' 15 cout << "*x = " << *x <<endl; 16 17 system("pause"); 18 return 0; 19 }
从上面的例子我们可以看到,其实指针的引用本质上就是双指针。foo(*&x);这个函数也是可以编译通过得到结果的。