引用和指针

引用(reference)是c++对c语言的重要扩充。引用就是某一已存在变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

引用的声明方法:类型标识符 &引用名=目标变量名;

假若有一变量a,想给它起一别名,可以这样写:

int  a; //定义a是整形变量
int  &b = a; //声明b是a的引用
以上语句声明了b是a的引用,即b是的别名,经过这样声明后,a或b作用相同,代表同一变量。

注意:

(1)&在此不是求地址运算,而是起标识作用。
(2)类型标识符是指目标变量的类型。
(3)声明引用时,必须同时对其进行初始化。
(4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
int a=2,int &ra=a;
a为目标原名称,ra为目标引用名。给ra赋值:ra=1; 等价于 a=1;
(5)对引用求地址,就是对目标变量求地址。&ra与&a相等。即我们常说引用名是目标变量名的一个别名。别名一词好像是说引用不占据任何内存空间。但是编译器在一般将其实现为const指针,即指向位置不可变的指针。即引用实际上与一般指针同样占用内存。
(6)不能建立引用的数组。因为数组是一个由若干个元素所组成的集合,所以无法建立一个由引用组成的集合。但是可以建立数组的引用.
例如: int& ref [3]= {2,3,5};//声明ref引用的数组错误
但是可以这样写:
const int (&ref)[3] ={2,3,5}; //gcc编译的时候加上选项 -std=c++0x
    ref[0] = 35; //错误

为什么要加上const ,因为{2,3,5}此时是个字面值数组,是保存在代码段里,只读的属性,如果不加,编译错误,而且后面对ref[0]的赋值也不会成功.
需要特别强调的是引用并不产生对象的副本,仅仅是对象的同义词。
引用必须在定义时马上被初始化,因为它必须是某个东西的同义词。你不能先定义一个引用后才 初始化它。
(7)一个变量可以有多个引用,但是一个引用只能有一个变量。


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


引用参数

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++方法为:swapint(x,y); c++自动把x,y的地址作为参数传递给swapint函数。
2、给函数传递大型对象
当大型对象被传递给函数时,使用引用参数可使参数传递效率得到提高,因为引用并不产生对象的
副本,也就是参数传递时,对象无须复制。
下面的例子定义了一个有限整数集合的类:
const maxCard=100;
Class Set
{
      int elems[maxCard];//集合中的元素,maxCard表示集合中元素个数的最大值。
      int card;//集合中元素的个数。
public:
      Set(){card=0;}//构造函数
      friend Setoperator *(Set,Set);//重载运算符号*,用于计算集合的交集用对象作为传值参数
      //friendSetoperator*(Set&,Set&)重载运算符号*,用于计算集合的交集用对象的引用作为传值参数
...
}

常引用声明方式:const 类型 标识符 &引用名 = 目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
【例】:
void Test
{   
      int a ;
      const int &ra=a;
      ra=1; //错误,const修饰不可改
      a=1; //正确
}
【例】:假设有如下函数声明:
string foo( );
    void bar(string & s);
那么下面的表达式将是非法的:
bar(foo( ));
    bar("hello world");

//原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const 。



#include <iostream>
using namespace std;

int& fun(int& a)
{
      a++;
      return a;
} //把a返回引用函数,也就是说这个fun()就是a的别名
int main()
{
      int b =10;
      fun(b); //同理,fun(b)就是b自增后的b的别名
      cout << b <<endl;
      return 0;
}

【例】:
void Test
{
       const int a = 10;
       int &b = a; //错误。如果引用成功,可通过a修改成功,而原题是const修饰b,是不可修改的。
       const  int &b = a; // 正确
       const int &c = 10; //正确,表示10不可修改
}
【例】:
void Test
{
       double a = 10.34;
       const int &b = a; //正确,
       a = 23.13;
}
      // b = 12;
      //如果b是a得引用(两者类型相同),则两者使用同一空间,但事实上b并不是a得引用,因为两者有不同的空间,借助一段未命名空间过度,临时变量有常性,所以要加上const修饰。

【例】:数组引用

void Test
{
       int a[10];
       int (&ra) [10] = a;
       int (&ra) [9] = a; // 错误,两者类型相同,不可引用
       a [0] = 10;
       ra[1] = 20;
}


        引用返回值
不要返回栈内存引用
方法:(1)创建全局变量
           (2)
FunTest(&a)
{
       int a = 10;
       return a;
}
          保证返回值生命周期比函数生命周期长
int & FunTest()
{
       int ret = 10;
       return ret;
}


int main()
{
        int &r = FunTest();
        printf("%d\n", r);
        printf("%d\n", r);
} 

// 10
    随机值    //第一次调用时,回来就销毁ret, 重新创建栈帧, 第二次打印出随机值



引用和多态


引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
【例】:
class A
{};
class B
{
public A{……}
};

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

1.指针和引用的定义和性质区别:

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:

int a=1;int *p=&a;//定义了一个整形变量和一个指针变量p,p的值是a存储单元的地址。

    int a=1;int &b=a;//定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。

(2)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)

(3)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;

(4)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。

(5)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;

(6)指针和引用的自增(++)运算意义不一样;

2.指针和引用作为函数参数进行传递时的区别:

(1)指针作为参数进行传递:

#include<iostream>
using namespace std;

void swap(int *a,int *b)
{
  int temp=*a;
  *a=*b;
  *b=temp;
}

int main(void)
{
  int a=1,b=2;
  swap(&a,&b);
  cout<<a<<" "<<b<<endl;
  system("pause");
  return 0;
}

结果为2 1;

用指针传递参数,可以实现对实参进行改变的目的,是因为传递过来的是实参的地址,因此使用*a实际上是取存储实参的内存单元里的数据,即是对实参进行改变,因此可以达到目的。

再看一个程序:

#include<iostream>
using namespace std;

void test(int *p)
{
  int a=1;
  p=&a;
  cout<<p<<" "<<*p<<endl;
}

int main(void)
{
    int *p=NULL;
    test(p);
    if(p==NULL)
    cout<<"指针p为NULL"<<endl;
    return 0;
}

运行结果为:

0x22ff44 1

指针p为NULL

大家可能会感到奇怪,怎么回事,不是传递的是地址么,怎么p会是NULL?事实上,在main函数中声明了一个指针p,并赋值为NULL,当调用test函数时,事实上传递的也是地址,只不过传递的是指地址。也就是说将指针作为参数进行传递时,事实上也是值传递,只不过传递的是地址。当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,即上面程序main函数中的p何test函数中使用的p不是同一个变量,存储2个变量p的单元也不相同(只是2个p指向同一个存储单元),那么在test函数中对p进行修改,并不会影响到main函数中的p的值。

如果要想达到也同时修改的目的的话,就得使用引用了。

2.将引用作为函数的参数进行传递。

在讲引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。

看下面这个程序:

#include<iostream>
using namespace std;

void test(int &a)
{
  cout<<&a<<" "<<a<<endl;
}

int main(void)
{
    int a=1;
    cout<<&a<<" "<<a<<endl;
    test(a);
    system("pause");
    return 0;
}

输出结果为: 0x22ff44 1

          0x22ff44 1

再看下这个程序:

这足以说明用引用进行参数传递时,事实上传递的是实参本身,而不是拷贝。

所以在上述要达到同时修改指针的目的的话,就得使用引用了。

#include<iostream>
using namespace std;

void test(int *&p)
{
  int a=1;
  p=&a;
  cout<<p<<" "<<*p<<endl;
}

int main(void)
{
    int *p=NULL;
    test(p);
    if(p!=NULL)
    cout<<"指针p不为NULL"<<endl;
    system("pause");
    return 0;
}

输出结果为:0x22ff44 1

         指针p不为NULL









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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值