技术交流QQ群:1027579432,欢迎你的加入!

  • 指针:指针是一个变量,只不过这个变量中存储的是一个地址,指向内存中的一个单元。
  • 引用:引用和原变量是同一个东西,只不过是原变量的一个别名。
        int a = 10;  定义一个整型变量a
        int *p = &a;  定义一个指向整型变量的指针变量p,该指针指向a的存储单元,即p的值是a存储单元的地址
        int &b = a;   定义一个整型变量a的引用,a和b是同一个东西,在内存中占用同一个存储单元
    

一、引用的特性:

  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦绑定某个实体,就不能再是其他变量的引用。

二、引用和指针的区别与联系:

  • 1.相同点:
    • 底层的实现方式相同,都是按照指针的方式实现的
  • 2.不同点:
    • 引用定义的时候必须初始化,指针可以不用初始化;
    • 引用一旦初始化为指向一个对象,就不能再指向其他对象,而指针可以在任何时候指向任何一个同类型的对象;
    • 没有空引用,但是有空指针;
    • 在sizeof中的含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节的个数(在32为平台下,指针求sizeof永远是4);
    • 引用++改变的是变量的内容,指针++改变的是指针的指向;
    • 有多级指针,没有多级引用;
    • 引用使用起来比指针安全;
    • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏;

三、传值、传地址、传引用的区别,哪个更高效?

  • 1.传值
    • 这种传递方式中,实参和形参是两个不同的地址空间,参数传递的实质是将原函数中变量的值,复制到被调用函数形参所在的存储空间中,这个形参的地址空间在函数执行完毕后,会被回收掉。整个被调用函数对形参的操作,只影响形参对应的地址空间,不影响原函数中变量的值,因为这两个不是同一个存储空间。
      即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。
  • 2.传地址
    • 这种传递方式中,实参是变量的地址,形参是指针类型的变量,在函数中对指针变量的操作,就是对实参(变量地址)所对应的变量的操作,函数调用结束后,原函数中的变量的值将会发生改变。
      被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。
  • 3.传引用
    • 这种传递方式中,形参是引用类型变量,其实就是实参的一个别名,在被调用函数中,对引用变量的所有操作等价于对实参的操作。这样,整个函数执行完毕后,原先的实参的值将会发生改变。
      被调函数对形参做的任何操作都影响了主调函数中的实参变量。
  • 4.哪种更高效?
    • 在内置类型当中三种传递方式的效率上都差不多;
    • 在自定义类型当中,传引用方式效率的更高效一些,因为它没有对形参进行一次拷贝

四、常引用

int a = 10;
int &b = a;  等价于 int *const b = a;即引用是一个指针常量(又称常指针,即一个常量,其类型是指针)
常引用:const int &a=b;等价于const int * const a=b;不仅仅是a这个地址不可修改,而且其指向的内存空间也不可修改。

五、引用的使用场景

  • 1.给变量起别名
    int a;
    int &b = a;
    
  • 2.将引用作为函数的参数
    • 使用引用类型就不必在swap中声明形参是指针变量,指针变量要另外开辟内存单元,其内容是地址。而引用变量不是一个独立的变量,不单独占内存单元。而且在调用swap函数时,只需要传值即可,将引用作为函数的形参更加简单、直观、方便。
      void swap(int &a, int &b)
      {
          int temp;                               
          temp = a;                                
          a = b;                                   
          b = temp;
      }
      
  • 3.返回值
        // 值返回
        int Add(int _iLeft, int _iRight)
        {
        return _iLeft + _iRight;
        } 
        // 引用返回
        int& Add(int & _iLeft, int& _iRight)
        {
        int iResult = _iLeft + _iRight;
        return iResult;
        }
    

六、代码实例

#include "iostream"

using namespace std;

void swap_by_value(int x, int y);
void swap_by_pointer(int *p1, int *p2);
void swap_by_reference(int &x, int &y);
/*
C++的函数参数传递方式,可以是传值方式,也可以是传引用方式。
传值的本质是:形参是实参的一份复制。
传引用的本质是:形参和实参是同一个东西。
传值和传引用,对大多数常见类型都是适用的。指针、数组,它们都是数据类型的一种,没啥特殊的
因此,指针作为函数参数传递时,也区分为传值和传引用两种方式。
void fun_1(int a); int类型,传值(复制产生新的变量)
void fun_2(int &a); int类型,传引用(形参和实参是同一个东西)
void fun_3(int *pi); 指针类型,传值(复制产生新的变量)
void fun_4(int *&pi); 指针类型,传引用(形参和实参是同一个东西)
如果希望通过将参数传递到函数中,从而来改变变量的值(比如变量是T a,T表示类型),
则可以有这2种方式选择:
    1.传a的引用: void my_fun(T &a);
    2.传a的地址: void my_fun(T *a);
*/

// 一、值传递
/*
形参意思是被调用函数的参数/变量,实参意思是主调函数中放到括号中的参数/变量。
传值方式下,形参是实参的拷贝:重新建立了变量,变量取值和实参一样。
即实参a和b的值为20和10,形参x和y的值都是20和10;而a与x的地址、b与y的地址并不相同
表明形参x和y是新建的变量,也即实参a, b是从形参复制了一份
*/

int main(){
    int a = 20, b = 10;
    cout << "交换前: " << endl; 
    cout << "a = " << a << " ,b = " << b << endl;
    swap_by_value(a, b); // 值传递
    cout << "交换后: " << endl;
    cout << "a = " << a << " ,b = " << b << endl;
    /*-------------------------分界线1------------------*/
    cout << "交换前: " << endl; 
    cout << "a = " << a << " ,b = " << b << endl;
    swap_by_pointer(&a, &b); // 值传递
    cout << "交换后: " << endl;
    cout << "a = " << a << " ,b = " << b << endl;
    /*-------------------------分界线2------------------*/
    cout << "交换前: " << endl; 
    cout << "a = " << a << " ,b = " << b << endl;
    swap_by_reference(a, b); // 引用传递
    cout << "交换后: " << endl;
    cout << "a = " << a << " ,b = " << b << endl;
    return 0;
}


void swap_by_value(int x, int y){
    int temp;
    temp = x;
    x = y;
    y = temp;
}

// 二、传指针(地址),实质还是传值
void swap_by_pointer(int *p1, int *p2){
    int t;
    t = *p1;
    *p1 = *p2;
    *p2 = t;
}

// 三、传引用
/*
传引用,传递的是实参本身,而不是实参的一个拷贝,形参的修改就是实参的修改,即值相同,地址也相同
相比于传值,传引用的好处是省去了复制,节约了空间和时间。
假如不希望修改变量的值,那么请选择传值而不是传引用。
*/

void swap_by_reference(int &x, int &y){
    int temp;
    temp = x;
    x = y;
    y = temp;
}

// 四、总结
/*
“引用”类型变量的声明方式:变量类型 &变量名;   int &b;
“指针”类型的声明方式:基类型 *变量名;  int *pi;
“指针的引用类型”应当这样声明:基类型 *&变量名   int *&pi;
指针类型,也是有传值、传引用两种函数传参方式的:
    1.指针的传值方式
        void my_fun(int *a, int n);
    2.指针的传引用方式
        void my_fun(int *&pi, int n);
// 普通类型,以int a为例
void myfun(int a)    //传值,产生复制
void myfun(int &a)   //传引用,不产生复制
void myfun(int *a)   //传地址,产生复制,本质上是一种传值,这个值是地址
// 指针类型,以int *a为例
void myfun(int *a)   //传值,产生复制
void myfun(int *&a)  //传引用,不产生复制
void myfun(int **a)   //传地址,产生复制,本质上是一种传值,这个值是指针的地址
// 数组类型,以int a[10]为例
void myfun(int a[], int n) //传值,产生复制
void myfun(int* a, int n) //传值,产生复制,传递的数组首地址
void myfun(int (&arr)[10]) //传引用,不产生复制。需要硬编码数组长度
template<size_t size> void myfun(int (&arr)[size]) //传引用,不产生复制。不需要硬编码数组长度
*/

七、参考博客

1.Chris的技术博客