C++中的引用详解
1.引用的本质
在 C/C++中,变量仅能且只能通过两种方式被访问、传递或获取。即:
⑴通过值 访问 / 传递变量
⑵通过地址 访问 / 传递变量 – 这种方法就是指针
引用常被认为是变量的别名,实际上 C++ 中根本就没有什么叫做别名的定义。引用变量也就是个指针变量,它也拥有内存空间。最关键的是引用是一种会被编译器自动解引用的指针。引用本质上被编译成指针常量(constant pointers)。这意味着:int &i = j ---> int *const i = &j
引用的常见例子:
#include <iostream.h> int main() { int i = 10; int &j = i;<pre name="code" class="cpp"> int* const k=&i; j++; cout<<i<<j<<*k<<endl; //相同的值 11 11 11 (*k)++; cout<<i<<j<<*k<<endl; //相同的值 12 12 12 cout<<&i<<&j<<k<<endl;//相同的 地址 return 0;}
为什么打印相同的地址?引用变量时会被编译器自动解引用,诸如"cout << &j << endl;"的语句,编译器就会将其转化成语句"cout << &*j << endl"。
这样来理解。如前描叙,j实际为指针常量,即int *const j= &i。所以语句"cout << &i << &j<< endl"变为"cout << &i << &*j<< endl"也就是"cout << &i << j<< endl"。所以打印相同的地址。
2.引用的级联(cascading)
看下面的代码:
#include <iostream.h> int main() { int i = 10; // A Simple Integer variable int &j = i; // A Reference to the variable int &k = j; // A reference to a reference variable int &l = k; // A reference to a reference to a reference variable. cout<<i<<","<<j<<","<<k<<","<<l<<endl;// The print should be 10,10,10,10 j++; cout<<i<<","<<j<<","<<k<<","<<l<<endl;// The print should be 11,11,11,11 k++; cout<<i<<","<<j<<","<<k<<","<<l<<endl;// The print should be 12,12,12,12 l++; cout<<i<<","<<j<<","<<k<<","<<l<<endl;// The print should be 13,13,13,13 return 0;}不依赖编译器的自动替换功能,手动进行替换也能达到相同的目标。上面的代码等同于:
#include <iostream.h> int main() { int i = 10; int *const j = &i; int *const k = &*j; int *const l = &*k; cout<<i<<","<<*j<<","<<*k<<","<<*l<<endl;// The print should be 10,10,10,10 (*j)++; cout<<i<<","<<*j<<","<<*k<<","<<*l<<endl;// The print should be 11,11,11,11 (*k)++; cout<<i<<","<<*j<<","<<*k<<","<<*l<<endl;// The print should be 12,12,12,12 (*l)++; cout<<i<<","<<*j<<","<<*k<<","<<*l<<endl;// The print should be 13,13,13,13 return 0;}其中j,k.l的value都是i的地址
3.引用占据内存
#include <iostream.h> class Test { int &i; // int *const i; int &j; // int *const j; int &k; // int *const k; }; int main() { cout<<"size of class Test = "<<sizeof(class Test)<<endl;// This will print 12 i.e. size of 3 pointers return 0; }
C++标准并没有解释编译器如何实现引用的行为。所以实现取决于编译器,而大多数情况下就是将其实现为一个const指针。因此引用仍然占据内存。
4.引用支持虚函数机制
#include <iostream.h> class A {public: virtual void print() { cout<<"A.."<<endl; }}; class B : public A {public: virtual void print() { cout<<"B.."<<endl; }}; class C : public B {public: virtual void print() { cout<<"C.."<<endl; }}; int main() { C c1; A &a1 = c1; a1.print(); // prints C,此时实质为基类指针变量 A a2 = c1; a2.print(); // prints A,此时实质为 A a2=(A)c1 return 0; }
引用支持虚函数机制,而虚函数的动态信息只有通过指针实现。更加说明引用其实就是一个const指针。
5.数组引用和常量引用
❶数组引用
#include"iostream" using namespace std; void display(const int a[],int length){ int i=0; while(i<length) {cout<<a[i]<<" ";i++;} } void add(int a[],int length){ int i=0; while(i<length) {a[i]++;i++;} } int main() { int a[5]={0}; int (&b)[5]=a; display(a,5); cout<<endl; //输出 0 0 0 0 0 display(b,5); cout<<endl; //输出 0 0 0 0 0 add(b,5); display(a,5); cout<<endl;//输出 1 1 1 1 1 display(b,5); cout<<endl;//输出 1 1 1 1 1 return 0; }
❷常量引用
#include"iostream" using namespace std; int _tmain(int argc, _TCHAR* argv[]) { int a = 30; const int &b = 30; cout <<"a= " << a << "\tb= " << b << endl;//30 30 a = 40; cout << "a= " << a << "\tb= " << b << endl;//30 40 return 0; }❸对于指针也有对应的引用。
6.引用作为形参和返回类型
Person old;
Person new=old;
在JAVA中,使用引用传递,修改old则new值也变化。C++中除数组外,默认为值传递,即隐式的调用了Person类的默认拷贝构造函数。C++中同类型的对象赋值基本为值传递。
❶引用做为形参,能够避免拷贝时间,提高效率。但是不能传递局部变量给上一级引用变量。如:
Person test(Person &p){ ....... return p;} Person &pNew=test(p);//报错
因为return p到 =test(P)过程中间,程序调用了默认拷贝函数,生成了一个test函数局部域的临时Person对象,而临时变量在test函数结束的时候销毁了。因为引用类型变量时必须初始化为一个已经定义并且有效的对象,所以调用失败了。
❷引用也可以作为传递类型,这时候复制不会调用拷贝构造函数:
Person& test(Person &p){ ....... return p;} Person &pNew=test(p);//不调用拷贝函数 Person pNew=test(p);//调用拷贝构造函数
7.总结
C++标准规定,引用可以占内存也可以不占,取决于编译器实现。
❶占内存的实现就是使用指针了。很多编译器为了简单都全部采用这种实现方法。
❷不占内存的实现,是引用和引用的对象在同一个函数中的时候。局部变量名其实是一个相对于栈基址的偏移量,这种情况下你定义这个局部变量的引用,编译器可以直接给这个引用赋同样的偏移值,这样引用名和变量名完全没有区别。这就是别名的含义了。
❸引用为形参只有用指针实现这种方法。例如换值函数swap,以下两种定义等同:
void swap(int const* a,int const * b ) void swap(int&a, int&b)
参考:
2.C++中的引用