網站首頁 編程語言 正文
C++的值類型
我們知道,每個變量都有類型,或整形或字符型等來進行了分類,不僅如此,C++表達式(帶有操作數的操作符、字面量、變量名等)在類型的屬性上,還有一種屬性,即值類別(value category)。且每個表達式只屬于三種基本值尖別中的一種:左值(lvalue),右值(rvalue),將亡值(xvalue),每個值類別都與某種引用類型對應。
其中,左值和將亡值成為泛左值(generalized value,gvalue),純右值和將亡值合稱為右值(right value,rvalue)。
一般我們講,左值就是可以取地址的,具有名字的,比如 int a; a是變量的名字,&a是變量的地址,a就是左值。那么右值呢,自然就是不可以取地址的,比如int b=10; 而這個10就是一個右值,在內存中不會分配有地址,自然也不能取地址。
將亡值,則是指在調用某個函數退出返回時,如果函數有返回值,那么就會有將亡值的存在,為什么稱之為將亡值,就是說這個值在函數作用域創建,但由于函數返回結束,局部變量都會銷毀,故會產生一個將亡值來接收這個值,完成賦值的任務。
從上圖也可以看出,將亡值既可能轉為左值,也可能成為右值,那么關鍵就在于要看是否具有名字了。
下面看這樣一段程序:
#include<iostream>
#include<type_traits>
using namespace std;
class MyString
{
private:
char* str; // heap;
public:
MyString(const char* p = nullptr) :str(nullptr)
{
if (p != nullptr)
{
int n = strlen(p) + 1;
str = new char[n];
strcpy_s(str, n, p);
}
cout << "Create MyString: " << this << endl;
}
MyString(const MyString& st)
{
if(st.str!=NULL)
str = st.str;
cout << "Copy Create MyString: " << this << endl;
}
MyString& operator=(const MyString& st)
{
if (st.str != NULL)
str = st.str;
cout << this << " operator=(const MyString &): " << &st << endl;
return *this;
}
~MyString()
{
delete[]str;
str = nullptr;
cout << "Destroy MyString : " << this << endl;
}
void PrintString() const
{
if (str != nullptr)
{
cout << str << endl;
}
}
};
int main()
{
MyString *a=new MyString("lisa");
MyString *b = a;
delete b;
a->PrintString();
return 0;
}
MyString類型成員有指針變量,且采用淺拷貝方式。當程序運行時,可以看到,兩個指針指向了同一個地址,此時,若釋放了b指針,再以a指針訪問指針成員,就會出現問題。
還有,當函數以值類型返回,構造臨時對象,若有指針變量,且采用淺拷貝,就會出現多次析構的問題,導致程序崩潰。
當我們將程序都改為深拷貝時,深拷貝又會導致,程序多次騷擾對空間,此時就提出了move語義。
std::move
std::move其實并沒有移動任何東西,它唯一的功能是將一個左值強制轉化為右值引用,繼而可以通過右值引用使用該值,以用于移動語義。從實現上講,move基本等同于一個類型轉換。
值得注意的是,通過move轉化成右值后,被轉化的左值的生命周期并沒有隨著左右值的轉化而改變。但通常情況下,我們需要轉換成右值引用的還是一個確定生命期即將結束的對象。
右值引用與移動構造和移動賦值
在c++11中增加了右值引用的概念,即對右值的引用,通過右值引用,可以延長右值的生命期。我們都知道左值引用是變量值的別名,那么右值引用則是不具名變量的別名。
右值引用是不能綁定到任何左值的,但有個例外,常量左值是一個萬能引用,可以引用任何值,包括右值引用。
class MyString
{
private:
char* str; // heap;
public:
MyString(const char* p = nullptr) :str(nullptr)
{
if (p != nullptr)
{
int n = strlen(p) + 1;
str = new char[n];
strcpy_s(str, n, p);
}
cout << "Create MyString: " << this << endl;
}
MyString(const MyString& st)
{
if (st.str != nullptr)
{
int n = strlen(st.str) + 1;
str = new char[n];
strcpy_s(str, n, st.str);
}
cout << "Copy Create MyString: " << this << endl;
}
MyString& operator=(const MyString& st)
{
if (this != &st && str != st.str)
{
delete[]str;
if (st.str != nullptr)
{
int n = strlen(st.str) + 1;
str = new char[n];
strcpy_s(str, n, st.str);
}
}
cout << this << " operator=(const MyString &): " << &st << endl;
return *this;
}
MyString(MyString&& st)
{
str = st.str;
st.str = nullptr;
cout << "Move Copy Create MyString" << this << endl;
}
MyString& operator=(MyString&& st)
{
if (this == &st) return *this;
if (this->str == st.str)
{
st.str = nullptr;
return *this;
}
delete[]str;
str = st.str;
st.str = nullptr;
cout << "Move operator=(MyString &&)" << endl;
return *this;
}
~MyString()
{
delete[]str;
str = nullptr;
cout << "Destroy MyString : " << this << endl;
}
void PrintString() const
{
if (str != nullptr)
{
cout << str << endl;
}
}
};
int main()
{
const MyString stra("hello");
MyString strb;
strb = std::move(stra);//調用普通的賦值方法
strb.PrintString();
return 0;
}
這里的move還是調用普通的賦值函數,并未做到真正的資源轉移,但是若寫成如下結構:
int main()
{
const MyString stra("hello");
MyString strb;
//strb = std::move(stra);//調用普通的賦值方法
strb = (MyString&&)stra;
strb.PrintString();
return 0;
}
通過右值引用,可以延長右值的生命期。從而,有了右值引用出現,這個時候配合移動構造與移動賦值,就可以完成資源轉移了。
然后,我們再看一個例子:
MyString& fun()
{
MyString st=("newdata");
return st;//xvalue
}
int main()
{
MyString("zhangsan").PrintString();
const MyString& a = fun();
a.PrintString();
MyString& b = fun();
b.PrintString();
return 0;
}
在程序運行時,會發現程序崩潰了,原因是:
函數中返回局部對象的引用,因為函數調用結束會銷毀局部對象,而引用則就成為了非法的訪問。因為不要在函數中返回局部對象的引用。
若我們將fun()函數的返回改為右值引用呢?
MyString&& fun()
{
return MyString("newdata");
}
int main()
{
MyString("zhangsan").PrintString();
const MyString& a = fun();//x
a.PrintString();
//MyString& b = fun();
//b.PrintString();
MyString&& c = fun();//x
c.PrintString();
MyString&& d = c;//error
return 0;
}
將亡值回去的時候,就得看看有沒有具名,一旦具名就是左值了,否則是右值
可以發現,右值引用是不具名的,但是右值引用本身卻是個左值,經過右值引用b接收后,就已經變成了左值,具有了名字。
原文鏈接:https://blog.csdn.net/zypf_lover/article/details/126423979
相關推薦
- 2022-05-22 C#編程之依賴倒置原則DIP_C#教程
- 2021-12-07 C#?微信支付回調驗簽處理的實現_C#教程
- 2022-11-02 Python?IDLE設置清屏快捷鍵的方法詳解_python
- 2022-07-16 淺談常見的加密算法
- 2022-12-07 C語言內存分布與heap空間分別詳細講解_C 語言
- 2023-05-23 golang中的單引號轉義問題_Golang
- 2022-06-30 Python+SymPy實現秒解微積分詳解_python
- 2022-05-14 C++?STL中vector容器的使用_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支