[C++: 引用】(二)

简介: [C++: 引用】(二)

4 使用场景

像上面举出来引用的栗子,实际工程之中基本上不会这样用,而引用的使用场景主要是下面这两方面:

  • 1. 做参数
  • 2. 做返回值

4.1 引用做参数

举一个最简单的例子,交换两个变量,以前我们是这样做的:

void Swap(int* p1, int* p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}

现在用引用就简单一些了:

void Swap(int& x, int& y)
{
  int tmp = x;
  x = y;
  y = tmp;
}

14cf053c838249a2bc7739dfd74e552f.png

这两个函数是构成函数重载的。

另外大家还记得链表那里的二级指针吗?当初可是把我们坑惨了的,忘记了的话可以参考博主写的这篇博客:单链表

如果现在用引用那不得爽死,这里,我只给了其中一个接口,其他接口也是一样的道理:

void SLPushFront(SLNode*& phead, SLTDataType x)
{
  SLNode* newNode = SLCreat(x);
  newNode->next = phead;
  phead = newNode;
}

4.2 做返回值

我们来看下面的一个程序有没有问题:

int& fun()
{
  int a = 10;
  return a;
}
int main()
{
  int ret = fun();
  cout << ret << endl;
  return 0;
}

我们运行一下程序:


a6b75bc196eb4da6aa47ee5b15849aa7.png

好像没有啥问题,但是我们再小小的修改一下程序:


72094e2ddb4d40d7a3f703fcce1e329c.png

为啥我就加了一个cout输出一堆字符,但是我ret的结果就变成了一堆垃圾数了呢?

我们知道a是一个局部变量,出了作用域就要被销毁,再销毁前我们将a的引用用了一个临时引用变量来保存(假设这个临时变量为tmp(类型是int&)),我们发现tmp和ret都是变量a的别名,而a出了作用域就被释放了,没有加cout输出一堆字符时我们发现结果正确只是因为释放过了空间后a还没有被改变,当为cout重新建立栈帧的时候a已经被修改了,这种返回局部变量的引用是一件极其危险的事情。

但是这样修改一下就不会担心出现这种问题了:

int& fun()
{
  static int a = 10;
  return a;
}

加一个static就不会出现这种问题了。

注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

我们再来看下面一个题:

int At(int i)
{
  static int a[10];
  return a[i];
}
int main()
{
  for (int i = 0; i < 10; i++)
  {
    At(i) = 10 + i;
  }
  for (int i = 0; i < 10; i++)
  {
    cout << At(i) << " ";
  }
  cout << endl;
  return 0;
}

这个程序有问题吗?我们运行一下:

a094b5fae27a425c84bf1eca03c87caf.png

程序报错了,为啥呢?

通过C语言的学习我们知道,传值返回是返回的一个临时变量,该变量具有常属性(只能读,不能够被写)也就是只能够作为右值,不能够作为左值,上面的第一个for循环时对其进行写操作,当然会报错,正确的处理方式是用引用返回:

1f85333db7ac473b902f1d1d2df8b622.png

为啥引用返回是正确的呢?由于引用只是给a[i]取了一个别名,a[i]是具有读写功能的,所以就不会报错了。


5 传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是 当参数或者返回值类型非常大时,效率就更低。

我们可以来测试一下:

做参数:

#include <time.h>
struct A { int a[100000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
  A a;
  // 以值作为函数参数
  size_t begin1 = clock();
  for (size_t i = 0; i < 10000; ++i)
    TestFunc1(a);
  size_t end1 = clock();
  // 以引用作为函数参数
  size_t begin2 = clock();
  for (size_t i = 0; i < 10000; ++i)
    TestFunc2(a);
  size_t end2 = clock();
  // 分别计算两个函数运行结束后的时间
  cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
  cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

1e6ae49d362b49118141529f8684c1ff.png

做返回值:

struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
  // 以值作为函数的返回值类型
  size_t begin1 = clock();
  for (size_t i = 0; i < 100000; ++i)
    TestFunc1();
  size_t end1 = clock();
  // 以引用作为函数的返回值类型
  size_t begin2 = clock();
  for (size_t i = 0; i < 100000; ++i)
    TestFunc2();
  size_t end2 = clock();
  // 计算两个函数运算完成之后的时间
  cout << "TestFunc1 time:" << end1 - begin1 << endl;
  cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

c1cd8d5a4aca48c4b14814c472aec0d3.png

通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大


6 引用和指针的区别

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

我们来看下引用和指针的汇编代码对比:

7ed009f26ebd4f40a09a96c38d4f515b.png

 引用和指针的不同点:

1. 引用 在定义时 必须初始化 ,指针没有要求

2. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何一个同类型实体

3. 没有 NULL 引用 ,但有 NULL 指针

4. 在 sizeof 中含义不同 : 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占4 个字节 )

5. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小

6. 有多级指针,但是没有多级引用

7. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理

8. 引用比指针使用起来相对更安全

7 总结

本文主要介绍了引用的概念,怎样使用引用以及引用的使用场景,还举了很多容易出错的栗子来帮助理解引用,最后列举了引用和指针的区别。如果该文对你友帮助的话能不能3连支持已选博主呢?

af1bc812b53049f88ac242e4c6deed1f.png

目录
相关文章
|
21天前
|
存储 Java C++
C++ 引用和指针:内存地址、创建方法及应用解析
C++中的引用是现有变量的别名,创建时需用`&`运算符,如`string &meal = food;`。指针存储变量的内存地址,使用`*`创建,如`string* ptr = &food;`。引用必须初始化且不可为空,而指针可初始化为空。引用在函数参数传递和提高效率时有用,指针适用于动态内存分配和复杂数据结构操作。选择使用取决于具体需求。
38 9
|
22天前
|
存储 安全 编译器
【C++专栏】C++入门 | 函数重载、引用、内联函数
【C++专栏】C++入门 | 函数重载、引用、内联函数
27 0
|
29天前
|
存储 分布式计算 安全
我的C++奇迹之旅:值和引用的本质效率与性能比较2
我的C++奇迹之旅:值和引用的本质效率与性能比较
|
29天前
|
编译器 C++
我的C++奇迹之旅:值和引用的本质效率与性能比较1
我的C++奇迹之旅:值和引用的本质效率与性能比较
|
2月前
|
安全 编译器 Linux
【C++练级之路】【Lv.1】C++,启动!(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for,nullptr)
【C++练级之路】【Lv.1】C++,启动!(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for,nullptr)
|
2月前
|
存储 安全 C++
在C++指针和引用
在C++指针和引用
|
2月前
|
存储 JSON 安全
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
67 0
|
3天前
|
存储 安全 编译器
【C++入门】缺省参数、函数重载与引用(下)
【C++入门】缺省参数、函数重载与引用
|
2月前
|
设计模式 算法 数据安全/隐私保护
【C++ 引用 】C++深度解析:引用成员变量的初始化及其在模板编程中的应用(二)
【C++ 引用 】C++深度解析:引用成员变量的初始化及其在模板编程中的应用
26 0
【C++ 引用 】C++深度解析:引用成员变量的初始化及其在模板编程中的应用(二)
|
2月前
|
存储 算法 编译器
【C++ 引用 】C++深度解析:引用成员变量的初始化及其在模板编程中的应用(一)
【C++ 引用 】C++深度解析:引用成员变量的初始化及其在模板编程中的应用
46 0