c++——左值、右值、左值引用、右值引用

c++——左值、右值、左值引用、右值引用1 左值和右值左值 left values 缩写 lvalues 右值 right values 缩写 rvalues 直接上官网查 我一向倡导自己去懂得原理 而原理都是老外写的 当然我只是针对 c 编程语言这样说 https msdn microsoft com en us library f90831hc aspx 翻译 所有的 c 表达 不是左值就是右值 lvalu

1、左值和右值

左值(left-values),缩写:lvalues

右值(right-values),缩写:rvalues

直接上官网查,我一向倡导自己去懂得原理,而原理都是老外写的,当然我只是针对c++编程语言这样说。

https://msdn.microsoft.com/en-us/library/f90831hc.aspx

翻译:所有的c++表达,不是左值就是右值。

lvalues是指存在于单个表达式之外的对象。你可以把左值当成有名字的对象。

所有的变量,包括常变量,都是左值。

rvalues是一个暂时存在的值存在于单个表达式之内的对象。

有点拗口(难理解),通俗来说就是,左值的生存期不只是这句话,后面还能用到它。

而右值呢,出了这句话就挂了,所以也叫(将亡值)。

它举了一个栗子:

#include 
  
    using namespace std; int main() { int x = 3 + 4; cout << x << endl; } 
  

在以上实例中,很显然,x是左值,3 + 4是右值。

它又举了一个栗子,来说明错误的使用和正确的使用

// lvalues_and_rvalues2.cpp int main() { int i, j, *p; // 正确的使用: 变量是左值 i = 7; // 错误的使用: 左边的操作 必须是 左值 (C2106) 7 = i; // C2106 j * 4 = 7; // C2106 // 正确的使用: 被间接引用的指针是左值 *p = i; const int ci = 7; // 错误的使用: 左边的操作 是 常量左值 (C3892) ci = 9; // C3892 // 正确的使用: 条件操作 返回了左值 ((i < 3) ? i : j) = 7; } 

2、左值引用、右值引用

左值引用:参考说明书《Lvalue Reference Declarator: &》,网站如下:

https://msdn.microsoft.com/en-us/library/w7049scy.aspx

使用语法:类型 + &(引用符) + 表达式

type-id & cast-expression 

翻译:

你可以把左值引用当成对象的另一个名字,lvalue引用声明由一个可选的说明符列表和一个引用声明符组成。

引用必须初始化,而且不能改变。

一个对象的地址可以 转化成 一种指定类型的指针 或者 转化成 一个 相似类型的引用。意义是相同的。

demo:

char c_val = 'c'; char *ptr = &c_val; char &r_val = c_val;

不要混淆 取地址 和 引用,当&说明符前面带有类型声明,则是引用,否则就是取地址。

通俗来说 &在 ”=” 号左边的是引用,右边的是取地址。 

右值引用:参考说明书《Rvalue Reference Declarator: &&》,网站如下:

https://msdn.microsoft.com/en-us/library/dd.aspx

使用语法:类型 + && + 表达式

type-id && cast-expression 

翻译:

Move Semantics:移动语义

右值引用使您能够区分左值和右值。Lvalue引用和rvalue引用在语法和语义上是相似的。

右值引用支持移动语义的实现,可以显著提升应用程序的性能。移动语义允许您编写将资源(例如动态分配的内存)从一个对象传输到另一个对象的代码,移动语义行之有效,因为它允许从程序中其他地方无法引用的临时对象转移资源。

为了实现移动语义,你在类中提供一个动态构造,和可选择的动态赋值运算符(operator=)。拷贝和赋值操作的资源是右值的可以自动调用移动语义。不像缺省的拷贝构造,编译器并不提供缺省的动态构造。

demo:

#include 
  
    #include 
   
     using namespace std; int main() { string s = string("h") + "e" + "ll" + "o"; cout << s << endl; } 
    
  

在Visual C++ 2010之前,每个调用 “+”运算符会分配和返回一个新的临时的string对象,

“+”运算符不能从一个string扩展到另一个,因为它不知道string是左值还是右值。如果源字符串都是lvalues,那么它们可能在程序的其他地方被引用,因此不能被修改。通过使用右值引用“+”运算符能够修改那些不能在程序中别处引用的右值,所以现在“+”运算符可以有一个string扩展到另一个。这可以显著减少字符串类必须执行的动态内存分配的数量。

为了更好地理解移动语义,考虑向向量对象插入一个元素的例子。如果超出了vector对象的容量,vector对象必须为其元素重新分配内存,然后将每个元素复制到另一个内存位置,以便为插入的元素腾出空间。当插入操作复制一个元素时,它创建一个新元素,调用copy构造函数将数据从前一个元素复制到新元素,然后销毁前一个元素。移动语义允许您直接移动对象,而不必执行昂贵的内存分配和复制操作。

Perfect Forwarding:完美转发

完美的转发减少了重载函数 避免了转发的问题。转发的问题出现在你写通用函数将引用作为参数,将这些参数由函数调用的时候。

举个例子,如果通用函数将 type const T&作为参数,那么调用函数不能修改参数的值。

如果通用函数 将 type T&作为参数,那么当参数是右值的时候,函数不能调用。

通常来说,为了解决上述的问题,你需要提供重载函数,既要有type const T&参数的函数,也要有type T&参数的函数。

结果呢,重载函数的数量随着参数数量呈指数递增。而右值引用能够使你只用一个函数就能适用于任意数量的参数。

原先的做法如下:

先写出所有适用的通用函数

struct W { W(int&, int&) {} }; struct X { X(const int&, int&) {} }; struct Y { Y(int&, const int&) {} }; struct Z { Z(const int&, const int&) {} }; 

再将带有不同类型的参数的函数用模板结合起来

template 
  
    T* factory(A1& a1, A2& a2) { return new T(a1, a2); } 
  

调用:需要根据适用的类型用相应的指针对接。

当调用的是左值时

int a = 4, b = 5; W* pw = factory 
  
    (a, b); 
  

当调用的是右值时。但是,下面的示例中没有包含对工厂函数的有效调用,因为工厂将可修改的lvalue引用作为其参数,但是它是通过使用右值调用的:

这里要注意的是const int &是lvalue 而不是 rvalue

而2是rvalue,函数会编译不过。

Z* pz = factory 
  
    (2, 2); 
  

为了解决这类问题,需要将模板函数修改成如下形式,右值引用可以适用const T& 和 T&形式的参数:

template 
  
    T* factory(A1&& a1, A2&& a2) { return new T(std::forward 
   
     (a1), std::forward 
    
      (a2)); } 
     
    
  

经过上述修改,均可以调用,如下图代码所示: 

int main() { int a = 4, b = 5; W* pw = factory 
  
    (a, b); X* px = factory 
   
     (2, b); Y* py = factory 
    
      (a, 2); Z* pz = factory 
     
       (2, 2); delete pw; delete px; delete py; delete pz; } 
      
     
    
  

除了上述所示的右值引用,还有额外的强大功能:

Additional Properties of Rvalue References

1、可以通过重载函数,调用左值或右值参数。具体来讲,你可以通过重载区分出无法修改的对象(const T&):左值和可修改的临时对象(T &):右值。

demo:

#include 
  
    using namespace std; // A class that contains a memory resource. class MemoryBlock { // TODO: Add resources for the class here. }; void f(const MemoryBlock&) { cout << "In f(const MemoryBlock&). This version cannot modify the parameter." << endl; } void f(MemoryBlock&&) { cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl; } int main() { MemoryBlock block; f(block); //左值引用调用 f(MemoryBlock()); //右值引用调用 } 
  

调用结果如下: 

In f(const MemoryBlock&). This version cannot modify the parameter. In f(MemoryBlock&&). This version can modify the parameter. 

2、再看下一个demo:与上述demo不同的是

f函数中参数命名了block,值得一提的是在右值引用中,若参数带有名字则视为左值,因为该参数可以在别处调用。

#include 
  
    using namespace std; // A class that contains a memory resource. class MemoryBlock { // TODO: Add resources for the class here. }; void g(const MemoryBlock&) { cout << "In g(const MemoryBlock&)." << endl; } void g(MemoryBlock&&) { cout << "In g(MemoryBlock&&)." << endl; } MemoryBlock&& f(MemoryBlock&& block) { g(block); return block; } int main() { g(f(MemoryBlock())); } 
  

调用结果如下:在这个例子中main函数传递了右值给了f函数,f函数将参数block视为了左值,调用了左值引用的函数g(const MemoryBlock&)   ,接着返回一个右值对象,然后调用了右值引用函数g(MemoryBlock&&) 。

In g(const MemoryBlock&). In g(MemoryBlock&&). 

3、右值引用中还可以通过static_cast将左值转成右值,进行右值引用函数调用

demo:

#include 
  
    using namespace std; // A class that contains a memory resource. class MemoryBlock { // TODO: Add resources for the class here. }; void g(const MemoryBlock&) { cout << "In g(const MemoryBlock&)." << endl; } void g(MemoryBlock&&) { cout << "In g(MemoryBlock&&)." << endl; } int main() { MemoryBlock block; g(block); g(static_cast 
   
     (block)); } 
    
  

调用结果如下:static_cast

(block)将block转成了右值。

In g(const MemoryBlock&). In g(MemoryBlock&&). 

4、模板函数可以推断出模板参数类型,进而使用“引用折叠”规则

#include 
  
    #include 
   
     using namespace std; template 
    
      struct S; template 
     
       struct S 
      
        { static void print(T& t) { cout << "print 
       
         : " << t << endl; } }; template 
        
          struct S 
         
           { static void print(const T& t) { cout << "print 
          
            : " << t << endl; } }; template 
           
             struct S 
            
              { static void print(T&& t) { cout << "print 
             
               : " << t << endl; } }; template 
              
                struct S 
               
                 { static void print(const T&& t) { cout << "print 
                
                  : " << t << endl; } }; template 
                 
                   void print_type_and_value(T&& t) { S 
                  
                    ::print(std::forward 
                   
                     (t)); } const string fourth() { return string("fourth"); } //这个函数为了返回const string类型的 "fourth" 即const + 右值 int main() { string s1("first"); //左值调用 print_type_and_value(s1); const string s2("second"); //const 左值 print_type_and_value(s2); print_type_and_value(string("third")); //右值 print_type_and_value(fourth()); //const 右值 } 
                    
                   
                  
                 
                
               
              
             
            
           
          
         
        
       
      
     
    
  

调用结果:

print 
  
    : first print 
   
     : second print 
    
      : third print 
     
       : fourth 
      
     
    
  

查看一下引用折叠规则:

   
Expanded type Collapsed type
T& & T&
T& && T&
T&& & T&
T&& && T&&

对照代码:

调用print_type_and_value(T&& t)

 

 例如first 是左值引用 则把t 参数看成T& 类型 

即T变成 T& 这里T 是 string

则T 变成了 String&

即如下代码所示:

print_type_and_value 
  
    (string& && t) 
  

forward的函数实现如下:将上述类型T& 转发成 T& &&

T& && forward(remove_reference 
  
    ::type& a) noexcept { return static_cast 
   
     (a); } 
    
  

进而通过引用折叠规则(表的第二条)对应的Collapsed type变成 了 T&即调用函数struct S


 

总结:

右值引用将左值与右值区分开来。它们可以帮助您通过消除不必要的内存分配和复制操作来提高应用程序的性能。它们还使您能够编写接受任意参数的函数的一个版本,并将其转发给另一个函数,就好像直接调用了另一个函数一样

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211127.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午11:09
下一篇 2026年3月18日 下午11:09


相关推荐

  • ctf MISC 学习总结「建议收藏」

    ext3linux挂载光盘,可用7zip解压或者notepad搜flag,base64解码放到kali挂载到/mnt/目录mount630a886233764ec2a63f305f318c8baa/mnt/cd/mnt/ls寻找find|grep‘flag’或find-name’flag’*查看cat./O7avZhikgKgbF/flag.txt查找…

    2022年4月8日
    150
  • 【分布式】Zookeeper服务端启动

    【分布式】Zookeeper服务端启动

    2022年2月22日
    41
  • 【13】进大厂必须掌握的面试题-配置管理面试

    点击上方“全栈程序员社区”,星标公众号 重磅干货,第一时间送达 Q1。配置管理流程的目标是什么? 配置管理(CM)的目的是通过使开发或部署过程可控和可重复,从而创建更高质量的产品或…

    2021年6月23日
    118
  • settings官方网站_phpstorm中文

    settings官方网站_phpstorm中文setting —> php,选择php版本,并点击…,选择到php.exe进入到appserv底下找到php.ini文件,查找date.timezone,去掉前面的;号,添加”Asia/Shanghai”重启appserv环境,就是重启下apache 和 mysql服务发现依然失败后面重启电脑就可以了哈哈哈哈哈哈哈哈…

    2022年8月18日
    8
  • log4cpp浅析

    log4cpp浅析log4cpp 是个基于 LGPL 的开源项目 移植自 Java 的日志处理跟踪项目 log4j 并保持了 API 上的一致 其类似的支持库还包括 Java log4j C log4cpp log4cplus C log4c python log4p 等 Log4cpp 中最重要概念有 Category 种类 Appender 附加器 Layout 布局 Priorty 优先级 NDC 嵌套的诊断上下文 Category Appender 与 Layout 三者的关系如下图所示

    2026年3月17日
    2
  • 门面模式和适配器模式_数字化门店转型

    门面模式和适配器模式_数字化门店转型门面模式Facade动机模式定义结构要点总结笔记动机上述A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化.这种过多的耦合面临很多变化的挑战如何简化外部客户端和系统间的交互接口呢?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦模式定义为子系统中的一组接口提供一个**一致(稳定)**的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)结构要点总结从客户程序的角度来看,Facade模式简化了整个

    2022年8月9日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号