• c++ 左值引用 右值引用 及 参数引用 & 与&&


    先了解引用的作用:C++引用的作用_Lao_tan的博客-CSDN博客_c++引用的作用
    右值引用_百度百科

    C++中rvalue和lvalue详悉_Naruto_Q的博客-CSDN博客_lvalue rvalue

    lvalue(left value)、rvalue(right value)是c语言编译过程中的用于描述等号左右值的符号。但是不能直观的通过在等号左边还是右边来判断是lvalue还是rvalue。能够取地址的,有名字的就是左值;反之,不能取地址,没有名字的就是右值。
    左值(lvalue)可以是明确的变量。右值(rvalue)可以是临时变量,比如表达式、函数返回值、常量等。
    int a = 1;
    int b;

    //下面为左值引用
    int &aa0 = a;//ture
    int &bb0 = b;//ture
    int &aa1 =1;//error
    int &aa1 = a+1;//error

    //下面为右值引用

    int &&a1 = a;//error
    int &&b1 = b;//error
    int &&a2  = 1;//ture
    int &&a3 = a+1;//ture
    int &&a4 = std::move(a);//ture  ,std::move()强制将左值转换成右值

    1. //可以通过这个函数来判断是lvalue还是rvalue。
    2. bool is_r_value(int &&)
    3. {
    4. return true;
    5. }
    6. bool is_r_value(const int &)
    7. {
    8. return false;
    9. }


    可以将C++的引用理解为指针常量引用必须在声明时初始化,声明之外的赋值操作都是对引用所代指地址的内容访问。
    C++参数中的& 和&& 都表示引用。左值的引用(lvalue)声明符号为”&”, 为了和左值区分,右值的引用(rvalue)声明符号为”&&”。 右值引用初始化之后引用本身变成了lvalue。所以无法进行“&&=&&”,从而达到右值引用传递的目的;但是左值引用可以传递,所以可以进行“&=&”。引用初始化必须满足lvalue = lvalue或者rvalue = rvalue。
    不同编辑器表现可能会有差异。比如vs中对类或结构体操作时不一定非要满足lvalue = lvalue或者rvalue = rvalue的原则。

    1. static int t = 0;
    2. class Whore {
    3. public:
    4. char buf[10];
    5. Whore()
    6. {
    7. itoa(t++,buf,10);
    8. cout << "Whore():" << buf<< endl;
    9. }
    10. ~Whore()
    11. {
    12. cout << "~Whore():"<
    13. }
    14. }
    15. void fun(int &a)
    16. {
    17. cout << "fun(int &a)" << endl;
    18. }
    19. void fun(int &&a)//&&用于存放右值(可以是表达式、临时变量等等)的引用
    20. {
    21. cout << "fun(int &&a)" << endl;
    22. }
    23. void fun1(Whore &a)
    24. {
    25. cout << "fun1(Whore &a)" << endl;
    26. }
    27. void fun1(Whore &&a)
    28. {
    29. cout << "fun1(Whore &&a)" << endl;
    30. }
    31. void fun2(Whore &a)
    32. {
    33. cout << "fun2(Whore &a)" << endl;
    34. }
    35. void fun3(int &a)
    36. {
    37. cout << "fun3(int &a)" << endl;
    38. }
    39. int main()
    40. {
    41. int a = 0;
    42. Whore w;
    43. fun(a);
    44. fun(1); //1为匿名临时变量
    45. fun1(w);
    46. fun1(Whore());//Whore()为匿名临时变量,生命周期为fun1(Whore && a)的函数栈。
    47. //fun2(Whore()); //vs编辑器不报错,编译不报错,可执行;qt编辑器报错:(no matching function for call to 'fun2'),不可执行
    48. //fun3(1); //vs编辑器报错:(非常量引用的初始值必须为左值),编译报错:(无法将参数 1 从“int”转换为“int &”),不可执行;qt编辑器报错:(no matching function for call to 'fun3'),不可执行
    49. cout<<"end"<
    50. int &a0 = a;用变量对左值引用进行初始化,等价于lvalue = lvalue。
    51. a0 = 2;
    52. //int &a1 = 1;//vs编辑器报错:(非常量引用的初始值必须为左值),编译器报错:(无法从“int”转换为“int &”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &”)
    53. //int &a1 = a+1;//vs编辑器报错:(非常量引用的初始值必须为左值),编译器报错:(无法从“int”转换为“int &”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &”)
    54. int &a1 = a0;//用左值引用对左值引用进行初始化,等价于lvalue = lvalue。
    55. const int &a2 = 1;//该语句在qt和vs中的汇编语言等价于“int &&a2 = 1;”,但是该语句a2指向的地址的内容无法修改。
    56. int &&a3 = 1;//用常数对右值引用进行初始化,等价于rvalue = rvalue。
    57. a3 = 2;
    58. const int &&a4 = 1;
    59. //int &&a5 = a;//用变量对右值引用初始化,等价于rvalue = lvalue,失败。//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“int &&”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &&”)
    60. //int &&a6 = a0;//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“int &&”);qt编辑器报错:(rvalue reference to type 'int' cannot bind to lvalue of type 'int'),qt编译器报错:(无法从“int”转换为“int &&”)
    61. int &&a7 = a+0;//用表达式对右值引用进行初始化,等价于rvalue = rvalue。
    62. int &&a8 = a0+0;//用表达式对右值引用进行初始化,等价于rvalue = rvalue。
    63. //int &&a9 = a3;//用已经初始化的右值引用对右值引用初始化,然而引用初始化之后引用本身变成了lvalue,等式等价于rvalue = lvalue,所以无效。//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“int &&”);qt编辑器报错:(error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &&”)
    64. int &&a10 = a3+0;//用表达式对右值引用进行初始化,等价于rvalue = rvalue。
    65. //const int &&a11 = a0;//vs编辑器报错:(无法将右值引用绑定到左值),编译器报错:(无法从“int”转换为“const int &&”);qt编辑器报错:(error: non-const lvalue reference to type 'const int' cannot bind to a temporary of type 'int') ,qt编译器报错:(无法从“int”转换为“int &&”)
    66. int &a12 = a3;//用已经初始化的右值引用对左值引用初始化,相当于进行lvalue = lvalue操作。
    67. fun(a);
    68. fun(a0);
    69. fun(a3);
    70. //fun(a4);//vs编辑器报错:(没有与参数列表匹配的 重载函数 "fun" 实例),vs编译器报错:(“fun”: 2 个重载中没有一个可以转换所有参数类型);qt编辑器报错:( no matching function for call to 'fun'),编译器报错:(“fun”: 2 个重载中没有一个可以转换所有参数类型)
    71. fun(1+1);
    72. fun(a+1);
    73. fun(a0+0);
    74. fun(a3+1);
    75. return 0;
    76. }
    77. /*输出
    78. Whore():0
    79. fun(int &a)
    80. fun(int &&a)
    81. fun1(Whore &a)
    82. Whore():1
    83. fun1(Whore &&a)
    84. ~Whore():1
    85. end
    86. fun(int &a)
    87. fun(int &a)
    88. fun(int &a)
    89. fun(int &&a)
    90. fun(int &&a)
    91. fun(int &&a)
    92. fun(int &&a)
    93. ~Whore():0
    94. */

    C++临时变量的生命周期_snail0428的博客-CSDN博客_临时变量生命周期 
    【C++】拷贝构造函数的调用时机_翼同学的博客-CSDN博客_c++拷贝构造函数什么时候调用 
    返回引用的意义_陈小淘的博客-CSDN博客_函数返回引用的含义 
    C++将返回值为引用有什么作用? - 知乎


    (右值)引用可以改变临时变量的生命周期!!!函数返回类型绝对不能返回右值引用:T &&,返回右值引用再逻辑上不成立,且会造成非法越界访问。返回的左值引用也可能会造成越界访问,需要理解清楚左值引用所引用对象的生命周期。

    1. static int t = 0;
    2. class Whore {
    3. public:
    4. char nickname[10];
    5. Whore()
    6. {
    7. _itoa(t++,buf,10);
    8. cout << "Whore():"<< nickname << endl;
    9. }
    10. Whore(Whore & w)
    11. {
    12. char buf[10];
    13. nickname = _itoa(t++, buf, 10);
    14. cout << "Whore(Whore & w):" << nickname << endl;
    15. }
    16. ~Whore()
    17. {
    18. cout << "~Whore():"<
    19. }
    20. Whore& operator =(const Whore & w)
    21. {
    22. ///char buf[10];
    23. //nickname = _itoa(t++, buf, 10);
    24. cout << "Whore& operator =(const Whore & w):" << nickname << ":" << w.nickname << endl;
    25. strcpy(nickname , w.nickname);
    26. return *this;
    27. }
    28. Whore& operator =(Whore && w)
    29. {
    30. ///char buf[10];
    31. //nickname = _itoa(t++, buf, 10);
    32. cout << "Whore& operator =(Whore && w):" << nickname <<":"<
    33. strcpy(nickname , w.nickname);
    34. return *this;
    35. }
    36. }
    37. Whore fun4()
    38. {
    39. return Whore();
    40. }
    41. Whore fun4_1()
    42. {
    43. Whore w = Whore();
    44. return w;
    45. }
    46. Whore & fun5()//错误示范1,返回临时或局部变量的引用,在函数退出后临时变量会被回收,引用也会变的无效,fun5()之外再访问w的成员造成越界。
    47. {
    48. Whore w = Whore();
    49. return w;//临时或局部变量在函数调用完后会被释放,外部再使用会导致越界访问。
    50. }
    51. Whore && fun5_1()//错误示范2,返回临时或局部变量的引用,在函数退出后临时变量会被回收,引用也会变的无效,fun5_1()之外再访问w的成员造成越界。
    52. {
    53. return Whore();//临时或局部变量在函数调用完后会被释放,外部再使用会导致越界访问。
    54. }
    55. Whore & fun6()//错误示范3,左值引用无法改变变量生命周期
    56. {
    57. Whore wt = Whore();
    58. static Whore & w = wt;//左值引用无法改变局部变量生命周期。
    59. return w;
    60. }
    61. Whore & fun6_1()
    62. {
    63. static Whore & w = Whore();//在vs中能运行成功,在qt中编辑器报错。这里用临时变量初始化左值引用,相当于lvalue = rvalue,正常来讲应该是要报错的。在vs中,这里直接将该临时变量变成全局变量来使用了。
    64. return w;
    65. }
    66. Whore & fun6_2()
    67. {
    68. static Whore && w = Whore();//右值引用可以改变临时变量的生命周期。
    69. return w;
    70. }
    71. Whore fun6_3()
    72. {
    73. static Whore && w = Whore();//右值引用可以改变临时变量的生命周期。
    74. return w;
    75. }
    76. Whore fun7()
    77. {
    78. static Whore w = Whore();
    79. return w;
    80. }
    81. Whore& fun7_1()
    82. {
    83. static Whore w = Whore();
    84. return w;
    85. }
    86. void fun8(Whore & w)
    87. {
    88. cout << "void fun8(Whore & w)" << endl;
    89. }
    90. void fun8(Whore && w)
    91. {
    92. cout << "void fun8(Whore && w)" << endl;
    93. }
    94. int main()
    95. {
    96. /*
    97. //下面代码运行成功。注意fun4与fun4_1返回变量与返回临时变量的差异。
    98. Whore w1 = fun4();//debug 和release下结果不一样,debug中调用构造函数后再调用拷贝构造,release中只调用构造函数。
    99. cout<
    100. Whore w2;
    101. cout<
    102. w2 = fun4();//调用了赋值重载函数,此时临时变量的生命周期在这条语句结束时结束了。
    103. cout<
    104. Whore w3 = fun4_1();//只调用了一次构造函数,等价于Whore ww1 = Whore();此时临时变量的生命周期与ww1等价了。
    105. cout<
    106. Whore &w4 = fun4();
    107. cout<
    108. Whore &w5 = fun4_1();
    109. cout<
    110. Whore &&w6 = fun4();//右值引用拉长了临时变量的生存周期
    111. cout << w6.nickname << endl;
    112. Whore &&w7 = fun4_1();//右值引用拉长了临时变量的生存周期
    113. cout << w7.nickname << endl;
    114. *//*debug输出:
    115. Whore():0
    116. Whore(const Whore &w):1
    117. ~Whore():0
    118. 1
    119. Whore():2
    120. 2
    121. Whore():3
    122. Whore(const Whore &w):4//w2 = fun4();在debug中调用了拷贝构造之后再调用赋值重载函数函数。拷贝构造的作用应该是将fun4中的返回的临时变量先存放到main中一个临时变量值中,然后再main中的临时变量赋值给w2。这个过程设计的内存操作相当的多。在release下有很大的精简。
    123. ~Whore():3
    124. Whore& operator =(Whore && w):2:4
    125. ~Whore():4
    126. 4
    127. Whore():5
    128. 4
    129. Whore():6
    130. Whore(const Whore &w):7
    131. ~Whore():6
    132. 7
    133. Whore():8
    134. 8
    135. Whore():9
    136. Whore(const Whore &w):10
    137. ~Whore():9
    138. 10
    139. Whore():11
    140. 11
    141. end
    142. ~Whore():11
    143. ~Whore():10
    144. ~Whore():8
    145. ~Whore():7
    146. ~Whore():5
    147. ~Whore():4
    148. ~Whore():1
    149. *//*release 输出: //release 模式下少了很多拷贝构造函数的调用,临时变量在fun4退出后并没有被西沟,意味着都直接延长了临时变量生命周期。在release模式下,右值引用与普通变量基本没有差别
    150. Whore():0
    151. 0
    152. Whore():1
    153. 1
    154. Whore():2
    155. Whore& operator =(Whore && w):1:2
    156. ~Whore():2
    157. 2
    158. Whore():3
    159. 2
    160. Whore():4
    161. 4
    162. Whore():5
    163. 5
    164. Whore():6
    165. 6
    166. Whore():7
    167. 7
    168. end
    169. ~Whore():7
    170. ~Whore():6
    171. ~Whore():5
    172. ~Whore():4
    173. ~Whore():3
    174. ~Whore():2
    175. ~Whore():0
    176. */
    177. /*
    178. //下面代码在qt中失败,在vs中成功。按规则来讲,临时变量是不能赋值给左值引用的,但vs中对类或结构体操作时是可以的。
    179. Whore &w = fun4();
    180. cout << w.nickname << endl;
    181. */
    182. /*
    183. //下面的代码导致崩溃。fun5返回的是临时变量的引用,而该临时变量的生命周期只在“Whore &w = fun5();”语句之中,在main中通过引用再访问变量的内容时,变量已经被销毁了,所以再访问就是越界访问了。
    184. Whore &w = fun5();
    185. cout<
    186. Whore &w1 = fun5_1();
    187. cout<
    188. *//*release输出:
    189. Whore():0
    190. ~Whore():0//fun5中的临死变量在fun5退出后被析构了,与fun4 的区别在于一个是返回了临时变量或局部,一个是返回了临时或局部变量的引用。
    191. Whore(const Whore &w):1
    192. 1
    193. Whore():2
    194. ~Whore():2
    195. Whore(const Whore &w):3
    196. 3
    197. end
    198. ~Whore():3
    199. ~Whore():1
    200. *//*debug输出:
    201. Whore():0
    202. ~Whore():0
    203. Whore(const Whore &w):1
    204. 1
    205. Whore():2
    206. ~Whore():2
    207. Whore(const Whore &w):3
    208. 3
    209. end
    210. ~Whore():3
    211. ~Whore():1
    212. */
    213. /*
    214. //下面代码导致崩溃。fun6中定义了一个全局静态左值引用,并且用一个局部变量初始化左值引用,然而局部变量的生命周期只在fun6的函数栈内,在main中通过fun6返回的引用再访问变量的内容时,变量已经被销毁了,所以再访问就是越界访问了。
    215. Whore &w = fun6();
    216. cout << w.nickname << endl;
    217. Whore &w = fun6_1();
    218. cout<< w.nickname <
    219. */
    220. /*下面代码运行成功:展示了返回引用的一种正确用法,返回引用更多的是用在返回对象成员变量或传入参数的成员变量
    221. //fun6中用临时变量初始化右值引用,是合法的,临时变量生命周期也强制变长了。全局静态变量w初始化之后就成了lvalue了然后以引用的形式进行返回。
    222. Whore &w = fun6_2(); //整个过程只有一次在fun6_2中调用一次构造函数。返回引用的正确用法,人为避免调用拷贝构造函数
    223. cout << w.nickname << endl;
    224. *//*debug/release输出:
    225. Whore():0
    226. 0
    227. end
    228. ~Whore():0
    229. */
    230. /*下面代码运行成功:都是展示函数返回引用后的错误用法
    231. Whore w1 = fun6_2(); //函数虽然返回引用,但是依然调用拷贝构造函数。
    232. cout << w1.nickname << endl;
    233. //Whore &&w2 = fun6_2()//qt编辑器报错,不能将rvalue绑定到lvalue。
    234. //Whore &w3 = fun6_3(); //qt编辑器报错,不能将lvalue绑定到临时变量
    235. Whore w4 = fun6_3();//将fun6_3返回的右值引用赋值给普通变量,会调用拷贝构造函数。注意fun4的区别,fun4不会调用拷贝构造函数。
    236. cout << w4.nickname << endl;
    237. Whore && w5 = fun6_3(); //将fun6_3返回的右值引用赋值给右值引用,依然会调用拷贝构造函数。
    238. cout << w5.nickname << endl;
    239. *//*debug/release输出:
    240. Whore():0
    241. Whore(const Whore &w):1
    242. 1
    243. Whore():2
    244. Whore(const Whore &w):3
    245. 3
    246. Whore(const Whore &w):4
    247. 4
    248. end
    249. ~Whore():4
    250. ~Whore():3
    251. ~Whore():1
    252. ~Whore():2
    253. ~Whore():0
    254. */
    255. /*下面代码运行成功:正确用法
    256. Whore &w = fun7_1();//整个过程只有在fun7_1中一次调用了构造函数。人为避免调用拷贝构造函数
    257. cout<
    258. *//*debug/release 输出:
    259. Whore():0
    260. 0
    261. end
    262. ~Whore():0
    263. */
    264. /*下面代码运行成功:错误用法
    265. Whore wz = fun7();
    266. cout<
    267. Whore &&w1 = fun7();
    268. cout<
    269. Whore w2 = fun7_1();
    270. cout<
    271. *//*debug/release输出:
    272. Whore():0
    273. Whore(const Whore &w):1
    274. 1
    275. Whore(const Whore &w):2
    276. 2
    277. Whore():3
    278. Whore(const Whore &w):4
    279. 4
    280. end
    281. ~Whore():4
    282. ~Whore():2
    283. ~Whore():1
    284. ~Whore():3
    285. ~Whore():0
    286. */
    287. /*
    288. //下面代码运行成功。
    289. Whore w;
    290. fun8(w);
    291. fun8(Whore());
    292. *//*输出:
    293. Whore():0
    294. void fun8(Whore & w)
    295. Whore():1
    296. void fun8(Whore && w)
    297. ~Whore():1
    298. end
    299. ~Whore():0
    300. */
    301. cout <<"end"<
    302. return 0;
    303. }

  • 相关阅读:
    机器学习之分类
    京东面试题:ElasticSearch深度分页解决方案
    京东数据挖掘(京东数据采集):2023年Q3电脑行业数据分析报告
    公共命名空间和输入法
    高性能MySQL实战第03讲:深入理解事务与锁机制(下)
    Scala系列-5、scala中的泛型、actor、akka
    消除注释C++
    图像分割 - 阈值处理 - 固定阈值法
    JVM-查看服务器JVM垃圾收集器类型
    Proteus下仿真AT89C51单片机串行口的问题
  • 原文地址:https://blog.csdn.net/qiushangren/article/details/126053951