1️⃣ 指针

什么是指针?

指针: 指向另外一种类型的复合类型。

指针的大小及用法?

在 64 位计算机中,指针占 8 个字节空间。

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;

int main()
{
int *p = nullptr;
cout<<sizeof(p)<<endl; // 8
char *p1= nullptr;
cout<<sizeof(p1)<<endl; // 8
return 0;
}

指针的用法

  • 指向普通对象的指针
  • 常量指针
  • 函数指针
  • 指向对象成员的指针,包括指向对象成员函数的指针和指向对象成员变量的指针。
  • this 指针:指向类的当前对象的指针常量。

什么是野指针和悬空指针?

  • 悬空指针

    • 若指针指向一块内存空间
    • 当这块内存空间被释放后,该指针依然指向这块内存空间
    • 此时,称该指针为”悬空指针”。
    1
    2
    3
    void *p = malloc(size);
    free(p);
    // 此时,p 指向的内存空间已释放, p 就是悬空指针。
  • 野指针

    • 不确定其指向的指针,未初始化的指针为“野指针”
    1
    2
    void *p; 
    // 此时 p 是“野指针”。

常量指针和指针常量的区别是什么?

  • 常量指针
    • 常量指针本质上是个指针,只不过这个指针指向的对象是常量。
    • const 的位置在指针声明运算符 * 的左侧。
    • 指针指向的对象不能通过这个指针来修改,也就是说常量指针可以被赋值为变量的地址,之所以叫做常量指针,是限制了通过这个指针修改变量的值。
    • 三个特点
      • one

        1
        2
        const int * p;
        int const * p;
      • two

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        #include <iostream>

        using namespace std;

        int main()
        {
        const int c_var = 8;
        const int *p= &c_var;
        *p=6; // error: assignment of read-only location'*p'
        return 0;
        }
      • three

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        #include <iostream>
        using namespace std;
        int main()
        {
        const int c_var1= 8;
        const int c_var2 = 8;
        const int *p = &c_var1;
        p= &c_var2;
        return 0;
        }
  • 指针常量
    • 指针常量的本质上是个常量,只不过这个常量的值是一个指针
    • const 位于指针声明操作符右侧,表明该对象本身是一个常量
    • 三个特点
      • one

        1
        2
        int var;
        int * const c_p = &var;
      • two

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        #include<iostream>

        using namespace std;

        int main()
        {
        int var,varl;
        int * const c_p = &var;
        c_p = &var1; // error: assignment of read-only variable 'c_p'
        return 0;
        }
      • three

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        #include<iostream>

        using namespace std;

        int main()
        {
        int var = 3;
        int * const c_p = &var;
        *c_p = 12;
        return 0;
        }

指针函数和函数指针的区别是什么?

  • 指针函数

    • 指针函数本质是一个函数,只不过该函数的返回值是一个指针。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      #include<iostream>
      using namespace std;

      struct Type
      {
      int varl;
      int var2;
      };

      Type* fun(int tmpl, int tmp2){
      Type* t = new Type();
      t->var1 = tmp1;
      t->var2 = tmp2;
      return t;
      }

      int main()
      {
      Type *p = fun(56);
      return 0;
      }
  • 函数指针

    • 函数指针本质是一个指针变量,只不过这个指针指向一个函数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      #include <iostream>
      using namespace std;

      int fun1(int tmpl,int tmp2)
      {
      return tmp1 * tmp2;
      }

      int fun2(int tmp1,int tmp2)
      {
      return tmp1 / tmp2;
      }

      int main()
      {
      int (*fun)(int x,int y);
      fun = fun1;
      cout << fun(155) << endl;
      fun= fun2;
      cout << fun(155) << endl;
      return 0;
      }

2️⃣ 指针和引用的区别

  • 指针所指向的内存空间在程序运行过程中可以改变,而引用所绑定的对象一旦绑定就不能改变。(是否可变)
  • 指针本身在内存中占有内存空间,引用相当于变量的别名,在内存中不占内存空间。(是否占内存)
  • 指针可以为空,但是引用必须绑定对象。(是否可为空)
  • 指针可以有多级,但是引用只能一级。(是否能为多级)

“引用” 的底层实现原理详解

案例引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>

using namespace std;

int main()
{
int a = 1;
int& b = a;
cout << "a:address->" << &a << endl;
cout << "b:address->" << &b << endl;

getchar();
return 0;
}

运行结果

1
2
a:address->0031FD54
b:address->0031FD54

现象?

  • 我们发现引用 b 的地址和变量 a 的地址是一样的。
  • 可能有人会猜想:是不是说变量 a 和引用 b 本身就是一个东西。所以同样的,引用本身所占内存就是变量的内存。
  • 首先对于这个说法,肯定是不正确的, 至于为什么不正确,我们接下来会以底层原理为大家解释。

原理

  • 案例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include<iostream>

    using namespace std;

    int main()
    {
    int x=1;
    int &b=x;
    return 0;
    }
  • 上述代码转汇编后的汇编代码

    1
    2
    3
    4
    5
    9:       int x = 1; 	//源代码 
    00401048 mov dword ptr [ebp-4],1 //反汇编代码
    10: int &b = x; //源代码
    0040104F lea eax,[ebp-4] //反汇编代码
    00401052 mov dword ptr [ebp-8],eax//反汇编代码
    • 解释
      • 上述三行代码的作用就是将 1 赋值给 x,然后将 x 的地址赋值给了引用 b

        引用在内存中的表现

  • 底层分析

    • 引用的本质就是所引用对象的地址
    • 通俗点理解就是引用就是通过指针来实现的
    • 引用所占的内存大小就是指针的大小。
  • 为什么在测试代码中,引用的地址和变量的地址是一样的?

    • 事实上, b 的地址我们没法通过 &b 获得。
    • 因为编译器会将 &b 解释为:&(*b) = &x,所以 &b 将得到 &x

3️⃣指针、引用与值传递

指针、引用与值传递的区别?

  • 值传递
    • 值传递是指将参数的值复制一份,传递给函数或方法进行操作。
    • 在值传递中,函数或方法对参数进行修改不会影响到原始的变量值。
  • 指针与引用
    • 指针和引用是指将参数的内存地址传递给函数或方法,使得函数或方法可以直接访问和修改原始变量的值。
    • 在指针和引用中,函数或方法对参数的修改会直接反映在原始变量上。

函数传参用引用的优点是什么?

  • 可以避免避免拷贝,使用引用传参可以避免对大型对象进行复制。
  • 如果传递一个对象作为值参数,会触发对象的拷贝构造函数,造成额外的开销。
  • 而使用引用传参,可以直接在函数中操作原始对象,避免了拷贝操作。

参数传递时,值传递、引用传递、指针传递的区别?

  • 参数传递的三种方式

    • 值传递:形参是实参的拷贝,函数对形参的所有操作不会影响实参。
    • 指针传递:本质上是值传递,只不过拷贝的是指针的值,拷贝之后,实参和形参是不同的指针,,通过指针可以间接的访问指针所指向的对象,从而可以修改它所指对象的值。
    • 引用传递:当形参是引用类型时,我们说它对应的实参被引用传递。
  • 案例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #include <iostream>

    using namespace std;

    void fun1(int tmp) { // 值传递
    cout << &tmp << endl;
    }

    void fun2(int * tmp){ // 指针传递
    cout << tmp << endl;
    }

    void fun3(int &tmp){ // 引用传递
    cout << &tmp << endl;
    }

    int main()
    {
    int var = 5;
    cout << "var 在主函数中的地址:" << &var << endl;

    cout << "var 值传递时的地址:";
    fun1(var);

    cout << "var 指针传递时的地址:";
    fun2(&var);

    cout << "var 引用传递时的地址:";
    fun3(var);

    return 0;
    }
  • 运行结果

    1
    2
    3
    4
    var 在主函数中的地址:0x23fe4c
    var 值传递时的地址:0x23fe20
    var 指针传递时的地址:0x23fe4c
    var 引用传递时的地址:0x23fe4c

参考资料:

小林 coding

c++中“引用”的底层实现原理详解