day3 3.1C++ 引用(reference)与 指针(pointer)的区别与联系

1.什么是引用?

我们知道,参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。对于像 char、bool、int、float 等基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。而数组、结构体、对象是一系列数据的集合,数据的数量没有限制,可能很少,也可能成千上万,对它们进行频繁的内存拷贝可能会消耗很多时间,拖慢程序的执行效率。
在 C/C++ 中,我们将 char、int、float 等由语言本身支持的类型称为基本类型,将数组、结构体、类(对象)等由基本类型组合而成的类型称为聚合类型(在讲解结构体时也曾使用复杂类型、构造类型这两种说法)。

故C/C++ 禁止在函数调用时直接传递数组的内容,而是强制传递数组指针,而对于结构体和对象没有这种限制,调用函数时既可以传递指针,也可以直接传递内容;
但是在 C++ 中,我们有了一种比指针更加便捷的传递聚合类型数据的方式,那就是引用(Reference)。
引用(Reference)是 C++ 相对于C语言的又一个扩充。引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。引用类似于 Windows 中的快捷方式,一个可执行程序可以有多个快捷方式,通过这些快捷方式和可执行程序本身都能够运行程序;引用还类似于人的绰号(笔名),使用绰号(笔名)和本名都能表示一个人。

2.怎样使用引用?

非函数参数时
语法

type &name = data;

type 是被引用的数据的类型,name 是引用的名称,data 是被引用的数据。引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它数据,这有点类似于常量(const 变量)。
注意事项:
1) 定义的同时必须初始化

//int &a;//错
//int &a = 3;//对
int c = 0
int &a = c;//对
cout<<a<<" "<<c;
//结果 0 0 

2)引用不能更改,一经初始化,就与原来的变量名绑定在同一块内存

int a =3;
int &r = a;
int c = 0;
//&r = c;//错!,不允许修改引用指向的变量
r = 5;//可以修改值
cout<<r<<c<<a;
r = c;//这里是赋值,不是改引用名
cout<<r;
//打印结果 5050

3)引用在定义时需要添加&,在使用时不能添加&,使用时添加&表示取地址。&也可以是与操作符
4)常引用所引用的对象的值是不能更改的,而且赋值的类型也要是const

const int *r = 3;
r =5//错误!,不允许修改常引用
const int a = 100;
const int &refa = a;  // 对!引用和被引用都是const类型
int &refa = a;  // 错,引用refa不是const类型
int &refa = 3//错,3为常量,只要是常量表达式都不行

作为函数的参数或者返回值时

1) 在定义或声明函数时,我们可以将函数的形参指定为引用的形式,这样在调用函数时就会将实参和形参绑定在一起,让它们都指代同一份数据。如此一来,如果在函数体中修改了形参的数据,那么实参的数据也会被修改,从而拥有“在函数内部影响函数外部数据”的效果。
好处:

  1. 因为函数形参和实参是同一个对象,也就不存在对象复制,避免了对象的开销。
  2. 可以在修改形参的同时对实参的修改。
#include <iostream>
using namespace std;

void swap1(int a, int b);
void swap2(int *p1, int *p2);
void swap3(int &r1, int &r2);


int main() {
    int num1, num2;
    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap1(num1, num2);
    cout << num1 << " " << num2 << endl;

    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap2(&num1, &num2);
    cout << num1 << " " << num2 << endl;

    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap3(num1, num2);
    cout << num1 << " " << num2 << endl;

    return 0;
}

//直接传递参数内容,形参影响不了实参,不能交换
void swap1(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

//传递指针,对地址操作,可以交换两个数
void swap2(int *p1, int *p2) {
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

//按引用传参,能实现交换两个数
void swap3(int &r1, int &r2) {
    int temp = r1;
    r1 = r2;
    r2 = temp;
}

2)为了避免函数对原来实参的意外修改我们可以 用const 对引用加以修饰 也就是传常引用。
传常引用有两个优势:

  1. 因为不存在拷贝构造所以,可以提高c++程序的执行效率
  2. 避免对象切割问题。关于这点的详细讨论可以看effective c++ 。

3)作函数返回值时,在将引用作为函数返回值时应该注意一个小问题,就是不能返回局部数据(例如局部变量、局部对象、局部数组等)的引用,因为当函数调用完成后局部数据就会被销毁,有可能在下次使用时数据就不存在了,C++ 编译器检测到该行为时也会给出警告。
在这里插入图片描述
plus10() 返回一个对局部变量 m 的引用,这是导致运行结果非常怪异的根源,因为函数是在栈上运行的,并且运行结束后会放弃对所有局部数据的管理权,后面的函数调用会覆盖前面函数的局部数据。本例中,第二次调用 plus10() 会覆盖第一次调用 plus10() 所产生的局部数据,第三次调用 plus10() 会覆盖第二次调用 plus10() 所产生的局部数据。

3.引用与指针有什么联系和区别?

1)有空指针,没有空引用

int num1 = 10;
    void *p = NULL;//空指针也是有地址的
    cout<<&p<<endl;
	int &r;//错误!引用不会分配内存,所以没有空引用 

在这里插入图片描述
2)虽然c++编译器会警告,但是指针可以不初始化,而引用必须初始化,并且,引用的目标一旦确定,后面不能再更改,指针可以更改其指向的目标。
3)存在指针数组 ,不存在引用数组

小结

在c++底层中,引用是通过指针实现的,所以,在实现层面上来说,引用就是指针,但是在c++语法上来说,c++编译器并不为引用类型分配内存,所以引用不能为空,必须被初始化,一旦初始化不能更改引用对象。所有对引用的操作都是对原始对象的操作

参考博客

发布了94 篇原创文章 · 获赞 66 · 访问量 5480

猜你喜欢

转载自blog.csdn.net/qq_44861675/article/details/105077365
今日推荐