C++的引用

发布时间:2024年01月17日

C++的常用引用(左值)我们都知道,我们在这里重点讨论以下的引用:

const引用(左值)

什么是const引用?通过在声明左值引用时使用const关键字,我们告诉左值引用将其引用的对象视为const。这样的引用称为const值的左值引用(也直接称为const引用)。

int main()
{
    const int x { 0 }; 
    const int& ref { x }; //ref是一个const引用
    return 0;
}

这时候的ref和x都不可以修改值


而在下面这种方式中

#include <iostream>

int main()
{
    int x { 5 };          
    const int& ref { x };
}

x可以修改值,ref不可以修改值


同时要注意,引用(又称左值引用)是不要直接使用右值进行初始化的。但是const引用(const的左值引用)是可以使用右值进行初始化的,而且这里的右值不是临时销毁,C++编译器将这里的右值的声明周期提升到与其匹配的const引用同一个生命周期。

另外Constexpr左值引用必须是全局或者静态局部的对象,这是因为编译器知道静态对象在内存中的实例化位置,因此它可以将该地址视为编译时常量。


总结一下,与对非const的引用(只能绑定到可修改的左值)不同,对const的引用可以绑定到可修改的左值、不可修改的左值和右值,也就是说,如果引用是const的,那么它可以绑定任意的类型的实参。

在函数传递中就更加直观了

#include <iostream>

void func(int& temp)
{
}

int main()
{
  int x{ 0 };
  func(x); // ok: x 是一个可变量

  const int y{ 0 };
  func(y); // error: y 是一个const,无法将const int转换为 int&

  func(y); // error: 5 是一个const,无法将const int转换为 int&

  return 0;
}

而如果使用const int& temp,则都可以运行

#include <iostream>

void func(const int& temp) // temp 是不可修改的
{
}

int main()
{
  int x{ 1 };
  func(x); 

  const int y{ 1 };
  func(y); 

  func(1); 

  return 0;
}

同时这里的1(右值),其生命周期被提升到func函数结束了。

使用const引用最直接的目的就是为了传入右值到函数中,当然这也直接限制了传入的值无法被修改


那么什么时候使用引用呢?

函数传递参数时候分为值传递和参数传递。

  • 值传递的时候,如果参数是对象,那么会调用对象的拷贝构造函数,按参数的值构造一个临时对象,这个临时对象仅仅在函数执行是存在,函数执行结束之后调用析构函数。
#include <iostream>
using namespace std;
class A {
public:
  A() { cout << "A的默认构造函数" << endl; }
  //A(A& a) { cout << "A的拷贝构造函数" << endl; }
  A(const A& a) { cout << "A的const拷贝构造函数" << endl; }
  ~A() { cout << "A的析构函数" << endl; }
};

void foo(A a) {
  cout << "foo函数" << endl;
}

int main() {
  A a;
  foo(a); // 值传递,调用拷贝构造函数和析构函数
  return 0;
}

输出结果如下

A的默认构造函数
A的const拷贝构造函数
foo函数
A的析构函数
A的析构函数

这里首先 A a 进行构造,然后foo值传入a,这里又调用了A的构造函数(注意这里写拷贝调用不是指这里的构造函数是专门用来拷贝的,而是a选择合适函数进行构造,如果将上面注释的代码去掉,那么进行构造的函数则是上面那一条,整个过程调用符合最匹配的原则)

传入引用则不会,传入引用相当于传入一个对象的地址进行操作,消耗更小。

所以具体到如何使用按值传递和引用传递有两个主要的方面

  • 考虑成本:刚才的例子中直接展示了按值传递调用对象时候要进行构造,而只要涉及到对象的按值传递都需要涉及成本问题,主要考虑的有:对象的大小和构造的代价。一些类型在实例化时做额外的设置(比如分配额外的资源),需要需要在可操作性和可读性以及代价之间妥协,因为如果是引用传递,那么最直接的后果就是函数内可以直接修改传入的对象,这种修改是直接反映到传入对象的本体的,所以const引用传递在此时就保障了传入的对象不可被修改。
  • 引用传递以及引用绑定到对象非常快,通常就和复制基本类型的速度差不多。

那么什么时候使用按值传递呢?

引用访问对象比通过普通变量标识符访问对象开销更大。值传递的开销大部分在构造部分,而引用传递的开销在访问对象时候、值传递构造了一个对象,正在运行的程序就可以直接进入分配该对象的内存地址并访问该值。对于引用,程序必须首先访问引用以确定被引用的对象,然后再进入该对象的内存地址并访问该值,相当于访问两次,同时编译器有时也可以使用比代码更重要的值传递对象来优化代码。

因此,我们在考虑使用值传递时候考虑以下方面:

  • 复制成本较低的对象,复制的成本与绑定的成本相似,我们倾向于按值传递,而如果是其他的情况,如果想要避免修改传入对象的值,那么尽量使用const引用。如果原对象需要改变,那么直接使用引用。(注意:基本类型的值传递很快【string除外】,像int、long、double、float等数值的基本类型直接使用值传递,string_view也使用值传递)

string_view 和 string ?

具体的性能比较可以看一下这篇技术博客,如果感兴趣可以更多了解其他文档:std::string_view 与 std::string 的性能 从 C++17 开始 - DZone

截取这篇文章的重点,

By leveraging?string_view, you can achieve a lot of performance boost in many use cases. However, it's important to know that there are caveats and sometimes the perf might be even slower as compare to?std::string!

The first thing is that?string_view?doesn't own the data - thus you need to be careful, so you don't end up with references to deleted memory!

The second thing is that compilers are very smart when handling strings, especially when strings are short (so they work nicely with SSO - Small String Optimization), and in that case, the perf boost might not be that visible.

翻译成中文再简略点和总结一下就是就是:

  • std::string_view需要C++17标准
  • std::string_view不能保证以空字符结尾(c风格字符串以空字符结尾,所以在一些c风格字符串中可能出错,使用string_view就得放弃c风格的字符串处理函数或者自己进行调整修改)。
  • std::string_view在许多方面的性能优于string,string_view 不拥有它所引用的数据,它只是指向一个已经存在的字符序列。不要让 string_view 引用一个已经被删除的内存,否则会导致未定义行为。
  • 有时候编译期会针对比较短的string进行优化,所以在string比较对象的字符串长度比较小某些情况下,string和string_view差不多不大

string_view 和 const 引用字符串有一些相似之处,但也有一些区别。

相似之处

  1. string_view 和 const 引用字符串都不拥有自己的数据,而是指向一个已经存在的字符串。
  2. string_view 和 const 引用字符串都不能修改它们所指向的字符串的内容。
  3. string_view 和 const 引用字符串都可以避免不必要的内存分配和数据复制,提高性能和效率。

区别

  1. string_view 是一个轻量级的对象,它只包含一个指针和一个长度,而 const 引用字符串是一个指向 std::string 对象的引用,它包含更多的数据成员和成员函数。
  2. string_view 可以引用任何形式的字符串,包括字符串字面量,字符数组,C 风格字符串,std::string 等,而 const 引用字符串只能引用 std::string 对象。
  3. string_view 不要求它所引用的字符串是以空字符结尾的,而 const 引用字符串要求它所引用的 std::string 对象是以空字符结尾的。
  4. string_view 的成员函数只包含读取字符串内容的部分,而 const 引用字符串的成员函数包含 std::string 的所有成员函数。
  5. string_view 的 substr() 函数返回一个新的 string_view 对象,而 const 引用字符串的 substr() 函数返回一个新的 std::string 对象。

综上所述,string_view 和 const 引用字符串都可以用来提供对一个字符串的只读访问,但 string_view 更加灵活和高效,可以适应更多的场景。这也就回答了为啥string_view使用值传递,因为它本身就是类似于一个const引用,如果使用string_view引用,相当于先引用访问引用再访问字符串对象,增加了开销,所以string_view直接使用值传递!

同时因为传入参数时候存在隐式转换,

使用std::string_view值形参:

  1. 传入std::string实参,编译器会将std::string转换为std::string_view,开销不高,
  2. 传入std::string_view实参,编译器会将实参复制到形参中,复制成本低。
  3. 传入一个c风格的字符串或字符串字面量,编译器会将它们转换为std::string_view,开销也不大。

使用const std::string& 形参需要记住两个开销比较大的行为:

如果传入c风格的字符串或字符串字面量或者string_view 作为实参,这都会让实参转换为string,而string构造首先涉及到复制字符串内容的操作,同时分配了缓冲区,这都是额外开销。

使用std::string作为实参,则开销都很大!(相对来说)一切传入的实参都隐式转换为string,string_view隐式转换的是string_view所指向的字符串。

文章来源:https://blog.csdn.net/no_to_last/article/details/135613960
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。