C++的常用引用(左值)我们都知道,我们在这里重点讨论以下的引用:
什么是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选择合适函数进行构造,如果将上面注释的代码去掉,那么进行构造的函数则是上面那一条,整个过程调用符合最匹配的原则)
传入引用则不会,传入引用相当于传入一个对象的地址进行操作,消耗更小。
所以具体到如何使用按值传递和引用传递有两个主要的方面
引用访问对象比通过普通变量标识符访问对象开销更大。值传递的开销大部分在构造部分,而引用传递的开销在访问对象时候、值传递构造了一个对象,正在运行的程序就可以直接进入分配该对象的内存地址并访问该值。对于引用,程序必须首先访问引用以确定被引用的对象,然后再进入该对象的内存地址并访问该值,相当于访问两次,同时编译器有时也可以使用比代码更重要的值传递对象来优化代码。
因此,我们在考虑使用值传递时候考虑以下方面:
具体的性能比较可以看一下这篇技术博客,如果感兴趣可以更多了解其他文档: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.
翻译成中文再简略点和总结一下就是就是:
string_view 和 const 引用字符串有一些相似之处,但也有一些区别。
相似之处
区别
综上所述,string_view 和 const 引用字符串都可以用来提供对一个字符串的只读访问,但 string_view 更加灵活和高效,可以适应更多的场景。这也就回答了为啥string_view使用值传递,因为它本身就是类似于一个const引用,如果使用string_view引用,相当于先引用访问引用再访问字符串对象,增加了开销,所以string_view直接使用值传递!
同时因为传入参数时候存在隐式转换,
使用std::string_view值形参:
使用const std::string& 形参需要记住两个开销比较大的行为:
如果传入c风格的字符串或字符串字面量或者string_view 作为实参,这都会让实参转换为string,而string构造首先涉及到复制字符串内容的操作,同时分配了缓冲区,这都是额外开销。
使用std::string作为实参,则开销都很大!(相对来说)一切传入的实参都隐式转换为string,string_view隐式转换的是string_view所指向的字符串。