5、【C++】指针/引用(指针和引用的区别)

一、指针

    int  p=12;
    int *q;
    int *q=&p;

    这里p为int类型的变量,&p就是p的内存地址,*q是一个int类型的变量(是一个值),q为指针是地址,int q=&p;把p的地址赋给了指针q,所以q就等于p的值=12,而q=&p,因为指针本身也是变量,所以&q就是指针q的内存地址。

注意
    不能写成int *q=p;会报错int类型的值不能初始化int类型的实体。因为没有给int类型分配内存地址,可以先用new 分配内存然后再赋值,例如:

    int *q=new int;
    *q=p;

    有了内存地址,然后再赋值就不会报错了。当然这里的q就不等同于&p了,是一个新地址。还可以写成:

    int *q;
    q=&p;

    反正就是要先分配内存地址,才能赋值。

二、引用

    引用(Reference)是C++语言相对于C语言的又一个扩充,是C++常用的一个重要内容之一。类似于指针,只是在声明的时候用"&“取代了”*"。

1、引用的相关概念

引用是别名,在声明时必须初始化,在实际代码中主要用作函数的形参

    (1)&在此不是求地址运算,而是起标识作用。
  
  (2)类型标识符是指目标变量的类型。
  
  (3)声明引用时,必须同时对其进行初始化。
  
  (4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
  
  (5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。
  
  (6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
【示例】

#include <stdio.h>
#include <iostream>
using namespace std;
 
//c++中的引用实例
void fun(int &x)
{
     printf("形式参数的值为:%d\n",x);
     printf("形式参数的地址为:%d\n",&x);
     x+=10;
}
void fun2(int *num){
  printf("形式参数的值为:%d\n",*num);
  printf("形式参数的地址值为:%d\n",num); //2686724
  printf("形式参数的地址为:%d\n",&num);  //2686704 这个和
  *num =(*num)+10;
}
 
int main(void){
   int num = 4,a=3;
 
   printf("实参num的值为:%d\n",num);
   printf("实参num的地址为:%d\n",&num);//都是一样的
   fun(num);
   cout<<"num的值为:"<<num<<endl; //14 num的值改啦
 
   fun2(&a);
   printf("实参a的值为:%d\n",a);
   printf("实参a的地址为:%d\n",&a);//
   return 0;
}

执行结果如下:

从上图中可以看出
    1.引用和目标变量的地址是一样的,对引用的修改就是对目标变量的修改。

    2. 而后边用指针作为函数参数,把地址传过去,指针变量的地址是2686608,而它的值是2686696(在指针的地址上存的值),2686696这个地址指向的值是3,也就是num的值。

    3.指针变量是有自己的值的(2686608),和num的地址(2686696)不一样,而引用的地址是和变量的值一样的。

2、引用的应用

(1)引用作为参数

引用的一个重要作用就是作为函数的参数。使用引用作为函数参数的优点如下:

    (1)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

    (2)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

    如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

(2)常引用

    常引用声明方式:

     const  类型标识符  &引用名 = 目标变量名;

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

#include <stdio.h>
#include <iostream>
using namespace std;
void test_const();
 
int main(void){
   test_const();
   return 0;
}
 
void test_const(){
   int a=1;
     int &b=a;
     b=2;
    cout<<"a="<<a<<endl;//2
    int c=1;
    const int &d=c;
  //   d=2;//编译错误 error: assignment of read_only reference 'd'
    c=2;//正确
    cout<<"c="<<c<<endl;//
}

(3)引用作为返回值
    要以引用返回函数值,则函数定义时要按以下格式:

    类型标识符  &函数名 (形参列表及类型说明)
    {  函数体  }

说明
  (1)以引用返回函数值,定义函数时需要在函数名前加&
  (2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
【示例】

#include <iostream>
using namespace std;
float temp;//定义全局变量temp
float fn1(float r);//声明函数fn1
float &fn2(float r);//声明函数fn2 r
//定义函数fn1,它以返回值的方法返回函数值    
float fn1(float r){
	temp=(float)(r*r*3.14);
        return temp;
}
//定义函数fn2,它以引用方式返回函数值
float &fn2(float r){
	temp=(float)(r*r*3.14);
	return temp;
}
 
int main(){
	float e=10.0;
	float a=fn1(10.0);//第1种情况,系统生成要返回值的副本(即临时变量)
	// float &b=fn1(10.0); //第2种情况,可能会出错(不同 C++系统有不同规定)
	/*error: invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'
   */
	//不能从被调函数中返回一个临时变量或局部变量的引用
	float c=fn2(10.0);//第3种情况,系统不生成返回值的副本
	//可以从被调函数中返回一个全局变量的引用
	float &d=fn2(10.0); //第4种情况,系统不生成返回值的副本
	e=d;
 
	cout<<"a="<<a<<",c="<<c<<",d="<<d<<",e="<<e<<endl;
	//a=314,c=314,d=314
	return 0;
}

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

    (1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。如【例5】中的第2种情况出现编译错误。
  
  (2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
  
  (3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
  
  (4)引用与一些操作符的重载:流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
  
(4)引用与多态
    引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
【示例】

    class  A;
    class  B:public  A{ ... ... }
    B  b;
    A  &Ref = b;//用派生类对象初始化基类对象的引用

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

三、指针和引用的差别

    (1)指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。

    (2)引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL)

    (3)有多级指针,但是没有多级引用,只能有一级引用。

    (4)指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)

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

    (6)引用访问一个变量是直接访问,而指针访问一个变量是间接访问。

猜你喜欢

转载自blog.csdn.net/sinat_33924041/article/details/83621220