什么是左值和右值?(右值不能被取地址)
在 C++ 中,表達(dá)式可以分為左值表達(dá)式和右值表達(dá)式。左值表達(dá)式指的是可以出現(xiàn)在賦值語(yǔ)句左邊的表達(dá)式,例如變量、數(shù)組元素、結(jié)構(gòu)體成員等;右值表達(dá)式指的是不能出現(xiàn)在賦值語(yǔ)句左邊的表達(dá)式,例如常量、臨時(shí)對(duì)象、函數(shù)返回值等。
右值是指將要被銷毀的臨時(shí)對(duì)象或者沒(méi)有名字的臨時(shí)對(duì)象。例如,一個(gè)返回臨時(shí)對(duì)象的函數(shù)調(diào)用表達(dá)式、一個(gè)匿名對(duì)象、一個(gè)類型轉(zhuǎn)換表達(dá)式等都是右值表達(dá)式,它們都是將要被銷毀的臨時(shí)對(duì)象或者沒(méi)有名字的臨時(shí)對(duì)象。
右值的特點(diǎn)是它們沒(méi)有持久的身份,不能被取地址,不能被修改,只能被使用一次。因此,右值引用的主要作用是支持移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā),從而提高程序的效率。(注意:字符串常量也是左值,因?yàn)樗鼈儽淮鎯?chǔ)在靜態(tài)數(shù)據(jù)區(qū),雖然不能被更改,但是能夠被取地址)
在 C++11 中,引入了右值引用和移動(dòng)語(yǔ)義的概念,使得程序可以更好地利用右值,提高程序的效率。
下面是一些右值的例子:
int a = 1; // a 是左值,1 是右值
int b = a + 2; // b 是左值,a + 2 是右值
int* p = &a; // p 是左值,&a 是右值
int c = func(); // c 是左值,func() 是右值
int d = std::move(a); // d 是左值,std::move(a) 是右值
在上面的例子中,1、2、&a、func()、std::move(a)
都是右值,它們都是將要被銷毀的臨時(shí)對(duì)象或者沒(méi)有名字的臨時(shí)對(duì)象。左值 a、b、p、c、d
都是可以被取地址、可以被修改、有持久的身份的對(duì)象,它們都是左值。
需要注意的是,一個(gè)對(duì)象既可以是左值,也可以是右值,這取決于它在表達(dá)式中的位置。例如,在賦值語(yǔ)句左邊的對(duì)象是左值,在賦值語(yǔ)句右邊的對(duì)象是右值。
右值分為純右值和將亡值
prvalue(pure rvalue, 純右值)
右值是臨時(shí)產(chǎn)生的值,不能對(duì)右值取地址,因?yàn)樗旧砭蜎](méi)存在內(nèi)存地址空間上。
舉例純右值如下:
- 除字符串以外的常量,如 1,true,nullptr
- 返回非引用的函數(shù)或操作符重載的調(diào)用語(yǔ)句。
- a++, a–是右值
- a+b, a << b 等
- &a,對(duì)變量取地址的表達(dá)式是右值。
- this指針
- lambda表達(dá)式
理解也很簡(jiǎn)單,其實(shí)就是一些運(yùn)算時(shí)的中間值,這些值只存在寄存器中輔助運(yùn)算,不會(huì)實(shí)際寫到內(nèi)存地址空間中,因此也無(wú)法對(duì)他們?nèi)〉刂贰?/p>
xvalue(eXpiring value, 將亡值)
- 返回右值引用的函數(shù)或者操作符重載的調(diào)用表達(dá)式。如某個(gè)函數(shù)返回值是
std::move(x)
,并且函數(shù)返回類型是T&&
- 目標(biāo)為右值引用的類型轉(zhuǎn)換表達(dá)式。如
static<int&&>(a)
參考文章:https://www.zhihu.com/question/363686723/answer/2590214399
示例:
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // std::move(v1)是一個(gè)將亡值
在上面的代碼中,std::move(v1)返回的是一個(gè)將亡值,因?yàn)関1的資源所有權(quán)即將被轉(zhuǎn)移給v2,v1即將被銷毀。
什么是左值引用(非const左值引用 和 const左值引用)
左值引用可以分為兩種:非const左值引用 和 const左值引用。
非const左值引用只能綁定左值;const左值引用既能綁定左值,又能綁定右值。
#include <iostream>
#include <cstdarg>
using namespace std;
int main()
{
int a = 1;
int &lref_a = a;
lref_a++; // 通過(guò)非 const 左值引用可以修改其值
const int &lref_const_a = a;
// lref_const_a++; // error, const左值引用不能修改其值
const int &lref_const_rvalue = 999; // const 左值引用可以直接綁定右值 999
// int &lref_const_rvalue = 999; // 錯(cuò)誤,非常量引用的初始值必須為左值(為什么要這么設(shè)計(jì)暫時(shí)沒(méi)太搞明白,說(shuō)是存在于棧上,那為啥不讓更改呢?可能是出于什么安全考慮?)
cout << "lref_const_rvalue = " << lref_const_rvalue << endl; // lref_const_rvalue = 999
cout << "&lref_const_rvalue = " << &lref_const_rvalue << endl; //&lref_const_rvalue = 0x7ffd28c02a0c
return 0;
}
可以看到,lref_const_rvalue 是 const 左值引用,但是他直接綁定到一個(gè)右值(數(shù)字常量999)上了。
有沒(méi)有想過(guò)為什么c++要這么設(shè)計(jì)呢?
舉個(gè)例子,你要設(shè)計(jì) print 方法。如何設(shè)計(jì) print 方法的參數(shù)呢?
首先,考慮到值傳遞參數(shù)會(huì)產(chǎn)生額外的拷貝,這是難以接受的。于是你想到了引用傳遞(你要用指針?那這篇文章不用看了。。。)
void print(int& a);
于是添加數(shù)據(jù)需要這樣:
int a = 1;
print(a);
好像有點(diǎn)麻煩,有時(shí)候你只需要添加一個(gè)常量(數(shù)字常量就是右值)進(jìn)去,你還得首先聲明一個(gè)變量,有點(diǎn)麻煩,如果能直接這樣添加就好了:
print(1);
也就是說(shuō),無(wú)論入?yún)⑹亲笾岛陀抑?,print函數(shù)都能正常接收。于是,用const左值引用可以解決這個(gè)問(wèn)題。實(shí)際上不知不覺(jué)中我們很多代碼都用到了這種參數(shù)形式。
void print(const int& a);
然后就只可以直接 print(1) 了。當(dāng)然,由于是 const 左值引用,因此你無(wú)法修改其值。只可讀不可寫。
ok,左值引用掌握到這種程度就可以了。接下來(lái)是右值引用。
參考文章:https://www.zhihu.com/question/363686723/answer/2590214399
什么是右值引用?
右值引用是 C++11 引入的新特性,用于支持移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā)。右值引用的語(yǔ)法是在類型名后面加上兩個(gè)引用符號(hào) &&
,例如 int&&
表示對(duì)一個(gè)右值 int 對(duì)象的引用。
右值引用的主要作用是支持移動(dòng)語(yǔ)義,即將一個(gè)對(duì)象的資源(內(nèi)存、指針等)移動(dòng)到另一個(gè)對(duì)象中,從而避免了不必要的內(nèi)存拷貝和資源分配。移動(dòng)語(yǔ)義可以提高程序的效率,特別是當(dāng)對(duì)象較大時(shí),避免了不必要的內(nèi)存拷貝和資源分配,從而提高了程序的性能。
右值引用還可以用于完美轉(zhuǎn)發(fā),即將一個(gè)函數(shù)的參數(shù)以原樣轉(zhuǎn)發(fā)給另一個(gè)函數(shù),從而避免了不必要的拷貝和轉(zhuǎn)換。完美轉(zhuǎn)發(fā)可以提高程序的效率,特別是當(dāng)函數(shù)參數(shù)較大或者類型較復(fù)雜時(shí),避免了不必要的拷貝和轉(zhuǎn)換,從而提高了程序的性能。
需要注意的是,右值引用只能綁定到一個(gè)右值對(duì)象,不能綁定到一個(gè)左值對(duì)象。如果嘗試將一個(gè)左值對(duì)象綁定到一個(gè)右值引用上,編譯器會(huì)報(bào)錯(cuò)。
下面是一個(gè)右值引用的例子:
#include <iostream>
#include <string>
using namespace std;
void print(std::string &&str)
{
cout << str << endl;
cout << "&str=" << &str << endl; //&str=0x7ffe64500230
}
int main()
{
std::string s = "Hello, world!";
cout << "&s=" << &s << endl; //&s=0x7ffe64500230
print(std::move(s)); // 將左值 s 轉(zhuǎn)換為右值引用
return 0;
}
在上面的例子中,print
函數(shù)的參數(shù)是一個(gè)右值引用 std::string&&
,表示對(duì)一個(gè)右值 std::string
對(duì)象的引用。在 main
函數(shù)中,我們定義了一個(gè)左值 std::string
對(duì)象 s
,然后將它轉(zhuǎn)換為右值引用 std::move(s)
,并將它作為參數(shù)傳遞給 print
函數(shù)。
由于 std::move(s)
返回的是一個(gè)右值引用,因此可以綁定到 print
函數(shù)的參數(shù)上。在 print
函數(shù)中,我們可以使用 str
來(lái)訪問(wèn)傳遞進(jìn)來(lái)的字符串,而不需要進(jìn)行不必要的拷貝和轉(zhuǎn)換。
需要注意的是,由于 std::move
只是將一個(gè)左值對(duì)象轉(zhuǎn)換為右值引用,它并不會(huì)移動(dòng)對(duì)象的資源。如果需要移動(dòng)對(duì)象的資源,需要在移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值運(yùn)算符中使用右值引用。
注意:上面str參數(shù)雖然聲明為右值,但是既可以當(dāng)右值用,也可以當(dāng)左值用:
#include <iostream>
#include <string>
using namespace std;
void print(std::string &&str)
{
str = "Hello, baby!";
cout << "str=" << str << endl; // str=Hello, baby!
}
int main()
{
std::string s = "Hello, world!";
print(std::move(s)); // 將左值 s 轉(zhuǎn)換為右值引用
cout << "s=" << s << endl; // s=Hello, baby!
return 0;
}
什么是移動(dòng)語(yǔ)義?(移動(dòng)構(gòu)造函數(shù)的實(shí)現(xiàn),將一個(gè)對(duì)象的資源轉(zhuǎn)移到另一個(gè)對(duì)象)
移動(dòng)語(yǔ)義是 C++11 引入的一個(gè)新特性,它可以將對(duì)象的資源(比如內(nèi)存、文件句柄等)從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象,避免了不必要的復(fù)制和銷毀操作,提高了程序的性能。
在移動(dòng)語(yǔ)義中,右值引用扮演了重要的角色。只有右值引用才能綁定到臨時(shí)對(duì)象或?qū)⒁N毀的對(duì)象,從而實(shí)現(xiàn)資源的轉(zhuǎn)移。如果使用左值引用代替右值引用,就無(wú)法實(shí)現(xiàn)移動(dòng)語(yǔ)義的效果。
下面是一個(gè)使用右值引用實(shí)現(xiàn)移動(dòng)語(yǔ)義的例子:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class MyString
{
public:
MyString() : m_data(nullptr), m_size(0) {}
MyString(const char *str) : m_data(new char[strlen(str) + 1]), m_size(strlen(str))
{
strcpy(m_data, str);
}
MyString(MyString &&other) : m_data(other.m_data), m_size(other.m_size)
{
other.m_data = nullptr;
other.m_size = 0;
}
~MyString()
{
delete[] m_data;
}
void print() const // 常量成員函數(shù)
{
if (!m_data)
{
std::cout << "m_data is null, return!" << std::endl;
return;
}
std::cout << m_data << std::endl;
}
private:
char *m_data;
size_t m_size;
};
int main()
{
MyString s1("Hello, world!");
s1.print(); // 輸出 "Hello, world!"
cout << "------------------" << endl;
MyString s2(std::move(s1)); // 將 s1 轉(zhuǎn)移為右值引用
s1.print(); // 輸出空字符串
cout << "------------------" << endl;
s2.print(); // 輸出 "Hello, world!"
cout << "------------------" << endl;
return 0;
}
在上面的代碼中,我們定義了一個(gè) MyString 類,它包含一個(gè)字符數(shù)組和一個(gè)大小成員變量。在類的構(gòu)造函數(shù)中,我們使用 new 運(yùn)算符為字符數(shù)組分配內(nèi)存,并將字符串復(fù)制到數(shù)組中。在移動(dòng)構(gòu)造函數(shù)中,我們將其他對(duì)象的指針和大小成員變量移動(dòng)到當(dāng)前對(duì)象中,并將其他對(duì)象的指針和大小成員變量設(shè)置為 null 和 0。這樣,我們就實(shí)現(xiàn)了將一個(gè) MyString 對(duì)象的資源轉(zhuǎn)移到另一個(gè)對(duì)象的功能。在 main 函數(shù)中,我們創(chuàng)建了兩個(gè) MyString 對(duì)象 s1 和 s2,然后將 s1 轉(zhuǎn)移為右值引用,從而實(shí)現(xiàn)了移動(dòng)語(yǔ)義的效果。
上面代碼還有點(diǎn)bug,我已經(jīng)上知乎問(wèn)了。。。是因?yàn)槲野裺1的m_data銷毀了,還打印它,結(jié)果程序就莫名崩潰了。。后來(lái)我修復(fù)了
代碼運(yùn)行結(jié)果:
什么是完美轉(zhuǎn)發(fā)?(右值引用+std::forward)
C++右值引用完美轉(zhuǎn)發(fā)是一種技術(shù),用于在函數(shù)調(diào)用中將參數(shù)以原樣傳遞給另一個(gè)函數(shù),同時(shí)保持參數(shù)的值類別(左值或右值)。這種技術(shù)可以提高代碼的效率和可讀性。
在C++11中,引入了右值引用和std::forward
函數(shù),使得完美轉(zhuǎn)發(fā)成為可能。右值引用是一種新的引用類型,可以綁定到右值(臨時(shí)對(duì)象或表達(dá)式的結(jié)果)也可以綁定到const左值,而左值引用只能綁定到左值(具有持久性的對(duì)象)。std::forward
函數(shù)是一個(gè)模板函數(shù),用于將參數(shù)以原樣轉(zhuǎn)發(fā)給另一個(gè)函數(shù)。
使用右值引用完美轉(zhuǎn)發(fā)可以避免不必要的對(duì)象拷貝和移動(dòng),提高代碼的效率。同時(shí),它還可以保持參數(shù)的值類別,避免了一些潛在的問(wèn)題,例如在函數(shù)模板中傳遞參數(shù)時(shí),如果不使用完美轉(zhuǎn)發(fā),可能會(huì)導(dǎo)致參數(shù)的值類別發(fā)生改變,從而影響函數(shù)的行為。
下面是一個(gè)使用右值引用完美轉(zhuǎn)發(fā)的示例:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-439973.html
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
void bar(int &x)
{
std::cout << "lvalue: " << x << std::endl;
}
void bar(int &&x)
{
std::cout << "rvalue: " << x << std::endl;
}
template <typename T, typename... Args>
void foo(Args &&...args)
{
bar(std::forward<Args>(args)...);
}
int main()
{
int x = 1;
foo<int>(x); // lvalue: 1
foo<int>(2); // rvalue: 2
return 0;
}
在這個(gè)示例中,函數(shù)foo使用了右值引用完美轉(zhuǎn)發(fā),將參數(shù)args以原樣傳遞給函數(shù)bar。bar有兩個(gè)重載版本,一個(gè)接受左值引用,一個(gè)接受右值引用,因此可以根據(jù)參數(shù)的值類別選擇正確的版本。在main函數(shù)中,分別調(diào)用了foo<int>(x)
和foo<int>(2)
,分別傳遞了一個(gè)左值和一個(gè)右值,輸出了對(duì)應(yīng)的結(jié)果。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-439973.html
到了這里,關(guān)于C++右值引用(左值表達(dá)式、右值表達(dá)式)(移動(dòng)語(yǔ)義、完美轉(zhuǎn)發(fā)(右值引用+std::forward))(有問(wèn)題懸而未決)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!