[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

目录
相关文章
|
5月前
|
存储 安全 C++
C++中的引用和指针:区别与应用
引用和指针在C++中都有其独特的优势和应用场景。引用更适合简洁、安全的代码,而指针提供了更大的灵活性和动态内存管理的能力。在实际编程中,根据需求选择适当的类型,能够编写出高效、可维护的代码。理解并正确使用这两种类型,是掌握C++编程的关键一步。
76 1
|
6月前
|
C++
C++中的const指针与const引用
C++中的const指针与const引用
79 2
|
4月前
|
存储 安全 C++
浅析C++的指针与引用
虽然指针和引用在C++中都用于间接数据访问,但它们各自拥有独特的特性和应用场景。选择使用指针还是引用,主要取决于程序的具体需求,如是否需要动态内存管理,是否希望变量可以重新指向其他对象等。理解这二者的区别,将有助于开发高效、安全的C++程序。
32 3
|
4月前
|
存储 自然语言处理 编译器
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
|
5月前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
66 5
|
5月前
|
C++
C++引用
C++引用
|
5月前
|
存储 安全 编译器
【C++入门】—— C++入门 (中)_引用
【C++入门】—— C++入门 (中)_引用
37 5
|
5月前
|
C语言 C++ 编译器
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
|
5月前
|
安全 测试技术 C++
C++中的引用
C++中的引用
29 1