很久没有接触C++代码了,很多东西都快忘了,今天刷力扣的题目,发现自己竟然看不懂左右引用转换了,去仔细查了查,写下这篇博客记录。
先上代码
class Solution {
public:
string simplifyPath(string path) {
auto split = [](const string& s, char delim) -> vector<string> {
vector<string> ans;
string cur;
for (char ch: s) {
if (ch == delim) {
ans.push_back(move(cur));
cur.clear();
}
else {
cur += ch;
}
}
ans.push_back(move(cur));
return ans;
};
vector<string> names = split(path, '/');
vector<string> stack;
for (string& name: names) {
if (name == "..") {
if (!stack.empty()) {
stack.pop_back();
}
}
else if (!name.empty() && name != ".") {
stack.push_back(move(name));
}
}
string ans;
if (stack.empty()) {
ans = "/";
}
else {
for (string& name: stack) {
ans += "/" + move(name);
}
}
return ans;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/simplify-path/solution/jian-hua-lu-jing-by-leetcode-solution-aucq/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这是题解,算法就不说了,一个简单的栈,我们看语法。
vector<string> ans;
string cur;
ans.push_back(move(cur));
先查move():函数的作用:右值引用不能直接绑定到一个左值上,但我们可以用move():获得一个绑定到左值上的右值引用。
左值引用与右值引用
额,有点绕,影响我们阅读的就是不懂左值引用和右值引用,那我们先了解这是什么。
右值引用就是必须绑定到右值的引用,我们只能通过&&而不是&来获得右值引用。–《C++ Primer》
简单理解,左值就是在等号左边的值,右值是在等号右边的值。可以理解为左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。举例:
int i = 10;// i为左值,10为右值
int &r = i;// r是左值引用,绑定到左值上,合法
int &&rr = i;// rr是右值引用,绑定到左值上,不合法
int &r2 = i * 10;//r2是左值引用,绑定到左值上,合法
int &&rr2 = i*10;//rr2是右值引用,绑定到右值上,合法
const int &r3 = i * 10;//合法,这个我也没有很好的理解方法,可以理解为const是常量关键字,表示r3是常量的引用,而右值一般就是常量值。
int &&rr1 = 10;// 字面常量值右值,合法
int &&rr2 = rr1;// rr1是变量,变量是左值,这里将左值绑定到右值变量上,不合法
模板实参推断和引用
到这里任意给一个表达式应该都能判断左值右值了。下面来说函数模板对引用的类型推断。
//从左值引用函数参数推断类型
template<typename T> void f1(T&);//显然实参必须是左值
f1(i); //i是int;则T是int
f1(ci); //ci是const int;则T是const int。举个例子:const int ci = 10;f1(ci);合法,此时T是const int
f1(5); //5不是左值,不合法
//当函数参数是const T&时,我们可以传任何值给此函数
template<typename T> void f2(const T&);//可以接受右值。注意,此时T的类型推断不会是const类型,因为const已经是函数参数类型的一部分,即使推导出T是const,两个const也无意义。在f2中使用参数,此参数一定是const。
f2(i); //i是int,T是int
f2(ci); //i是const int,T是int
f3(10); //T是int,合法,参考前面的左右值举例。
template<typename T> void f3(T&&);
f3(10); //合法,T是int
引用折叠和右值引用参数
这是引用较为重要的一些特性,举例:
f3(i); //i是int对象
我们用我们已有的知识来判断,这样写应该是错的,因为i是左值,把左值绑定到右值引用上,这是不合法的,但是在时间编写中却可以这样写,因为C++规定了特例。
特例1:将左值传递给函数的右值引用参数,且此右值引用指向模板类型参数(T&&)时,编译器推断模板类型参数为实参的左值引用类型。例如调用f3(i)时,编译器推断T为int&,不是int,可以看做f3的参数是一个int类型的左值引用的右值引用。
特例2:如果我们简介创建了一个引用的引用(如上),则这些引用形成了“折叠”。
·i& &、i& &&、i&& &都折叠成类型i&;
·i&& &&折叠成i&&
注意:引用折叠只能引用于简介创建的引用的引用,如类型别名或模板参数。
由此,当我们调用f3(i);时,f3(i)可能实例化为
void f3<int&>(int& &&);
此时会形成折叠
void f3<int&>(int&);,这样传递i进来也就合法了
如果一个函数参数是一个指向模板类型参数的右值引用(T&&),则他可以绑定一个左值,且如果实参是一个左值,则T将会被推导成一个左值引用,函数参数会被实例化为一个普通的左值引用参数(T&)
总结来说就是我们可以将任意类型的实参传递给T&&类型的函数参数。
template<typename T> void f3(T&& val)
{
T t = val;
t = fun(t);
if (val == t)
{
}
}
示例代码如上,按照两个特例得出的结论:
当传入右值,如10时,T被推导成int,那t被改变后,if里面就应该是false;
当传入左值,如i时,T被推导成int&,此时t就是val的引用,t改变,val也改变,if里面永真。
std::move()
话不多说,先看源码
template<typename _Tp>
inline typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t)
{
return static_cast<typenamestd::remove_reference<_Tp>::type&&>(__t);
}
从源码可以看出,move的作用就是返回传入参数的右值引用,不管传入的是左值还是右值,都返回其右值引用。
那现在问题就迎刃而解了,题解源码中的move();就是这个作用,实际上不用move也可以。