的C++奇迹之旅:值和引用的本质效率与性能比较

news2025/1/15 14:11:41

请添加图片描述

文章目录

    • 请添加图片描述 @[TOC](文章目录)
  • 📝引用# 🌠引用概念**引用**不是新定义一个变量,而是给**已存在变量取了一个别名**,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>定义:**类型&引用变量名(对象名) = 引用实体;**例如以下代码,在变量名前加一个`&`,意思是一个引用类型,`b`是`a`的别名,也就是`a`有了一个外号,但还是`a`本身:```cppint a = 70;int& b = a; //引用:b是a的别名```我们接下来看看引用后的地址是否会发生改变:例如以下例子:```cppint main(){ int a = 70; int& b = a; //引用:b是a的别名 int& c = a; //引用:c是a的别名 c = 80; cout << a << endl; cout << &a << endl; cout << &b << endl; cout << &c << endl; return 0;}```代码运行图:在这个代码中,定义了一个变量`a`为`70`,`int& b = a`; 这里`b`是`a`的引用,给`a`取了一个外号`b`,`int& c = a`; 这里`c`是`a`的引用,又给`a`取了一个外号是`c`,因此我们对`c`还是对`b`进行修改,`a`都会发生改变,这是因为**编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间**。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/039ecfab596d41869ff8a7c90e52e714.png)>注意:**引用类型必须和引用实体是同种类型的**## 🌉引用特性1. 引用**必须在定义时初始化**:==引用必须在定义时初始化,不能在之后单独为它赋值==。```cppint a = 10; int& ra = a; // 正确,ra初始化为aint& ra; // 错误,引用必须在定义时初始化```2. 一个变量可以有多个引用```cppint a = 10;int& ref1 = a; int& ref2 = a;ref1++; // a的值变为11cout << a << endl; // 输出11ref2--; // a的值变为10cout << a << endl; // 输出10```3. 引用一旦引用一个实体,再不能引用其他实体引用本质上**就是给原变量添加一个别名,它的内存地址就是原变量的地址**。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:**一级指针**可以改变指向,如p可以从指向a改为指向其他变量,**二级指针**可以改变一级指针指向的地址,如`pp`可以改变`p`指向的地址而引用更像一个`const`指针:定义后不能改变指向的对象,就像`const`指针定义后不能改变指向但可以通过这个“`const`指针”来修改原对象,就像通过`const`指针可以修改原对象```cppint a = 10;int b = 20;int& ref = a; ref = b; // 错误!引用ref已经引用a,不能再引用bcout << ref << endl; // 输出10,ref依然引用a```如图:ref引用了a,这里的值发生改变是因为b赋值给了ref![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/09c2a3f2dc534036b45f7eef60d8744d.png)# 🌠使用场景## 🌉做参数(传值与传地址)当引用用来做参数时将会对代码进行大大的优化,并且具有可读性,如:当我们看了很多遍的交换了两个数的函数:```cppvoid Swap(int* a, int* b){ int tmp = *a; *a = *b; *b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(&ra, &rb); return 0;}```形参是实参的一份临时拷贝,所以如果想交换需要,传地址,不能传值。```cppvoid Swap(int& a, int& b){ int tmp = a; a = b; b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}````a`和`b`分别是`ra`和`rb`的别名,当你调换`a`和`b`的纸时,其实是修改了`ra`和`rb`的地址的值,这样的好处就是,当你看代码时,引用`a`和`b`给人一种感觉,就是操作`ra`和`rb`本身。这隐藏了底层是通过地址操作原变量`ra`和`rb`的实现细节。从使用者的角度看,代码读起来就像直接交换`ra`和`rb`,而不是通过**复杂的地址操作实现**。>这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,**有关链表操作**,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,**引用也不是完全替代指针的**![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d4134e1aec26472892b5035f8f74c7fe.png)回归正题,这里还有一个小注意点:作用域的不同,因此,在Swap函数里,取别的名字都可以,任由发挥,结果都相同。```cppvoid Swap(int& x, int& x){ int tmp = x; x = y; y = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}```## 🌉传值、传引用效率比较**以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。**```cpp#include <time.h>struct A { int a[10000]; };void TestFunc1(A a) {}void TestFunc2(A& a) {}void TestRefAndValue(){ A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;}int main(){ TestRefAndValue(); return 0;}```**按值传递(TestFunc1):**调`用`TestFunc1(a)`时,会将`a`进行拷贝,生成一个临时对象**a_copy**。**a_copy**作为参数传递给TestFunc1`。`TestFunc1`内部操作的实际上是`a_copy`,对`a_copy`的修改不会影响实参`a`。`TestFunc1`返回时,临时对象`a_copy`会被销毁。`TestFunc1`以值方式传递结构体`A`作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含`10000`个`int`成员的大结构体,拷贝开销很大。**按引用传递(TestFunc2):**调用`TestFunc2(a)`时,不会进行值拷贝,直接传递`a`的引用。`TestFunc2`内部操作的仍然是实参`a`本身。`TestFunc2`返回时,不需要销毁任何对象。TestFunc2以引用方式传递A。这种方式下,函数内直接操作的就是实参a本身,不会有任何拷贝开销。>总结:>`TestFunc1`值传递,效率低是因为值拷贝开销大`TestFunc2`引用传递,效率高是因为避免了值拷贝,直接操作的就是实参`a`本身![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cea37fd6819d40059d37965342ee3719.png)>通过上述代码的比较,**发现传值和指针在作为传参以及返回值类型上效率相差很大**。# 🌠引用做返回值```cppint& Count(){static int n = 0;n++;// ...return n;}```我们先看看下面代码会输出什么结果?```cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;}```在Vs编译运行图:结果是**7**,真的是正确吗?![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/fe90c7c1ead24f92b1fd7df0a2186064.png)>问题分析:>**如果函数返回时,返回的对象已经超出了函数作用域(即已经被销毁),那么不能返回该对象的引用,必须返回值。**>在第一个示例中:```cppint& Add(int a, int b){int c = a + b;return c;}```>这里函数返回了局部变量`c`的引用,但`c`在函数返回后就已经被销毁了,所以这是一个**未定义行为**,输出结果是**不确定**的。>而在第二个示例中:```cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;}```这里同样是返回了局部变量`c`的引用,但是在`main`函数中又调用了一次`Add`函数,这时第一次调用返回的引用`ret`已经指向了一个==不存在的对象==,所以输出结果==也是未定义==的。>函数返回引用时必须确保返回的对象在调用者作用域内仍然存在,否则就会产生未定义行为。这是`C++`中函数返回引用需要特别注意的地方。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/921cb60c97c3413da342531e41f3a314.png)答案思考:在`Visual Studio`上运行这段代码,输出结果是:```Add(1, 2) is :7```这个结果确实是**未定义**行为,但在某些情况下可能会输出`7`。之所以会出现这种情况,是因为**Visual Studio的编译器在处理这种未定义行为时可能会做一些特殊的优化或处理**,导致在某些环境下能够得到一个**看似合理**的结果。但这种行为是**不可靠**的,因为它依赖于具体的编译器实现细节。在不同的编译器或环境下,输出可能会完全不同。**正确的做法**:是要么返回值,要么返回一个在调用者作用域内仍然存在的对象的引用。这样可以确保代码的行为是可预测和可移植的。## 🌉引用和指针的区别1. **语法概念**:- **引用**是变量的别名,没有独立的存储空间,而是和其引用的实体共用同一块内存空间。- **指针**是一个独立的变量,存储了另一个变量的内存地址。2. **声明语法**:- 引用使用`&`符号声明,如`int& ref = x;`- 指针使用`*`符号声明,如`int* ptr = &x;`3. **操作方式**:- 引用直接访问和操作其引用的实体,如`ref = 10;`- 指针需要先解引用(`*`)才能访问其指向的实体,如`*ptr = 10;`4. **Null值**:- 引用不能为空(Null),必须在声明时初始化为一个有效的实体。- 指针可以为空(Null),指向空地址`0x0`。让我们看看例子来说明引用和指针的区别:假设我们有一个整型变量`x`,值为10。**使用引用:**```cppint x = 10;int& ref = x; // 声明引用ref,它是x的别名ref = 20; // 通过ref修改x的值cout << "x = " << x << endl; // 输出 x = 20````ref`是`x`的引用,它们共享同一块内存空间。通过`ref`修改值,实际上是在修改`x`的值。 输出`x`的值为20,因为`x`的值已经被修改了。**使用指针:**```cppint x = 10;int* ptr = &x; // 声明指针ptr,存储x的地址*ptr = 20; // 通过ptr解引用并修改x的值cout << "x = " << x << endl; // 输出 x = 20````ptr`是一个指向`x`的指针,存储了`x`的内存地址。通过`*ptr`解引用并修改值,实际上是在修改`x`的值。输出`x`的值为20,因为`x`的值已经被修改了。>在**底层实现**上实际是有空间的,因为**引用是按照指针方式来实现的**。```cppint main(){ int a = 10; int& ra = a; ra = 20; int* pa = &a; *pa = 20; return 0;}```我们来看下引用和指针的汇编代码对比得出:==在汇编中**引用**的底层逻辑还是**指针**,经过编译转换成汇编,还是进行指针的操作==![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/be0e447bf42344f2a653f8ca2108e349.png)引用和指针的不同点:1. **引用概念上定义一个变量的别名,指针存储一个变量地址**。2. **引用在定义时必须初始化,指针没有要求**3. **引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体**4. **没有NULL引用,但有NULL指针**5. 在`sizeof`中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(`32`位平台下占`4`个字节)6. **引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小**7. **有多级指针,但是没有多级引用**8. **访问实体方式不同,指针需要显式解引用,引用编译器自己处理**9. **引用比指针使用起来相对更安全**# 🌠常引用从上述代码中,我们可以得出以下关于常引用的结论:1. **常量引用**:```cppconst int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量const int& ra = a;```对于常量对象`a`,我们可以使用常引用`const int& ra = a;`来引用它。这样做可以避免对常量进行修改,直接使用非常引用`int& ra = a;`会在编译时报错,因为不允许对常量进行非常引用。2. **字面量常引用**:```cpp// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;```我们可以使用常引用`const int& b = 10;`来引用字面量常量。这样做可以避免创建临时变量, 直接使用非常引用`int& b = 10;`会在编译时报错,因为字面量不能被非常引用。3. **类型不匹配的常引用**:```cppdouble d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;```根据类型不同的变量,如`double d = 12.34;`,我们可以使用常引用`const int& rd = d;`来引用它,直接使用非常引用`int& rd = d;`会在编译时报错,因为类型不匹配。---# 🚩总结![请添加图片描述](https://img-blog.csdnimg.cn/9d50bf7913d741e98c8220ede56599d1.gif)

📝引用# 🌠引用概念引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>定义:类型&引用变量名(对象名) = 引用实体;例如以下代码,在变量名前加一个&,意思是一个引用类型,ba的别名,也就是a有了一个外号,但还是a本身:cppint a = 70;int& b = a; //引用:b是a的别名我们接下来看看引用后的地址是否会发生改变:例如以下例子:cppint main(){ int a = 70; int& b = a; //引用:b是a的别名 int& c = a; //引用:c是a的别名 c = 80; cout << a << endl; cout << &a << endl; cout << &b << endl; cout << &c << endl; return 0;}代码运行图:在这个代码中,定义了一个变量a70int& b = a; 这里ba的引用,给a取了一个外号bint& c = a; 这里ca的引用,又给a取了一个外号是c,因此我们对c还是对b进行修改,a都会发生改变,这是因为编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。在这里插入图片描述>注意:引用类型必须和引用实体是同种类型的## 🌉引用特性1. 引用必须在定义时初始化:引用必须在定义时初始化,不能在之后单独为它赋值cppint a = 10; int& ra = a; // 正确,ra初始化为aint& ra; // 错误,引用必须在定义时初始化2. 一个变量可以有多个引用cppint a = 10;int& ref1 = a; int& ref2 = a;ref1++; // a的值变为11cout << a << endl; // 输出11ref2--; // a的值变为10cout << a << endl; // 输出103. 引用一旦引用一个实体,再不能引用其他实体引用本质上就是给原变量添加一个别名,它的内存地址就是原变量的地址。所以对引用赋值或修改,实际上就是修改原变量。而指针不同,指针可以改变指向的对象:一级指针可以改变指向,如p可以从指向a改为指向其他变量,二级指针可以改变一级指针指向的地址,如pp可以改变p指向的地址而引用更像一个const指针:定义后不能改变指向的对象,就像const指针定义后不能改变指向但可以通过这个“const指针”来修改原对象,就像通过const指针可以修改原对象cppint a = 10;int b = 20;int& ref = a; ref = b; // 错误!引用ref已经引用a,不能再引用bcout << ref << endl; // 输出10,ref依然引用a如图:ref引用了a,这里的值发生改变是因为b赋值给了ref在这里插入图片描述# 🌠使用场景## 🌉做参数(传值与传地址)当引用用来做参数时将会对代码进行大大的优化,并且具有可读性,如:当我们看了很多遍的交换了两个数的函数:cppvoid Swap(int* a, int* b){ int tmp = *a; *a = *b; *b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(&ra, &rb); return 0;}形参是实参的一份临时拷贝,所以如果想交换需要,传地址,不能传值。cppvoid Swap(int& a, int& b){ int tmp = a; a = b; b = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}````a`和`b`分别是`ra`和`rb`的别名,当你调换`a`和`b`的纸时,其实是修改了`ra`和`rb`的地址的值,这样的好处就是,当你看代码时,引用`a`和`b`给人一种感觉,就是操作`ra`和`rb`本身。这隐藏了底层是通过地址操作原变量`ra`和`rb`的实现细节。从使用者的角度看,代码读起来就像直接交换`ra`和`rb`,而不是通过**复杂的地址操作实现**。>这里使用了引用挺好的,不用担心指针的解引用,地址相关操作,但是,前面我们知道,引用一旦指向一个实体,就无法改变指向,例如,**有关链表操作**,当我们要删除一个节点,是不是要改变前面节点的指针,让他指向后面节点,而引用恰恰不能改变,因此,**引用也不是完全替代指针的**![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d4134e1aec26472892b5035f8f74c7fe.png)回归正题,这里还有一个小注意点:作用域的不同,因此,在Swap函数里,取别的名字都可以,任由发挥,结果都相同。cppvoid Swap(int& x, int& x){ int tmp = x; x = y; y = tmp;}int main(){ int ra = 88; int rb = 99; Swap(ra, rb); return 0;}## 🌉传值、传引用效率比较**以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。**cpp#include <time.h>struct A { int a[10000]; };void TestFunc1(A a) {}void TestFunc2(A& a) {}void TestRefAndValue(){ A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << “TestFunc1(A)-time:” << end1 - begin1 << endl; cout << “TestFunc2(A&)-time:” << end2 - begin2 << endl;}int main(){ TestRefAndValue(); return 0;}**按值传递(TestFunc1):**调`用`TestFunc1(a)`时,会将`a`进行拷贝,生成一个临时对象**a_copy**。**a_copy**作为参数传递给TestFunc1`。`TestFunc1`内部操作的实际上是`a_copy`,对`a_copy`的修改不会影响实参`a`。`TestFunc1`返回时,临时对象`a_copy`会被销毁。`TestFunc1`以值方式传递结构体`A`作为参数。这会导致每次调用都会对A进行值拷贝,对于一个包含`10000`个`int`成员的大结构体,拷贝开销很大。**按引用传递(TestFunc2):**调用`TestFunc2(a)`时,不会进行值拷贝,直接传递`a`的引用。`TestFunc2`内部操作的仍然是实参`a`本身。`TestFunc2`返回时,不需要销毁任何对象。TestFunc2以引用方式传递A。这种方式下,函数内直接操作的就是实参a本身,不会有任何拷贝开销。>总结:>`TestFunc1`值传递,效率低是因为值拷贝开销大`TestFunc2`引用传递,效率高是因为避免了值拷贝,直接操作的就是实参`a`本身![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cea37fd6819d40059d37965342ee3719.png)>通过上述代码的比较,**发现传值和指针在作为传参以及返回值类型上效率相差很大**。# 🌠引用做返回值cppint& Count(){static int n = 0;n++;// …return n;}我们先看看下面代码会输出什么结果?cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << “Add(1, 2) is :”<< ret <<endl;return 0;}在Vs编译运行图:结果是**7**,真的是正确吗?![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/fe90c7c1ead24f92b1fd7df0a2186064.png)>问题分析:>**如果函数返回时,返回的对象已经超出了函数作用域(即已经被销毁),那么不能返回该对象的引用,必须返回值。**>在第一个示例中:cppint& Add(int a, int b){int c = a + b;return c;}>这里函数返回了局部变量`c`的引用,但`c`在函数返回后就已经被销毁了,所以这是一个**未定义行为**,输出结果是**不确定**的。>而在第二个示例中:cppint& Add(int a, int b){int c = a + b;return c;}int main(){int& ret = Add(1, 2);Add(3, 4);cout << “Add(1, 2) is :”<< ret <<endl;return 0;}这里同样是返回了局部变量`c`的引用,但是在`main`函数中又调用了一次`Add`函数,这时第一次调用返回的引用`ret`已经指向了一个==不存在的对象==,所以输出结果==也是未定义==的。>函数返回引用时必须确保返回的对象在调用者作用域内仍然存在,否则就会产生未定义行为。这是`C++`中函数返回引用需要特别注意的地方。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/921cb60c97c3413da342531e41f3a314.png)答案思考:在`Visual Studio`上运行这段代码,输出结果是:Add(1, 2) is :7这个结果确实是**未定义**行为,但在某些情况下可能会输出`7`。之所以会出现这种情况,是因为**Visual Studio的编译器在处理这种未定义行为时可能会做一些特殊的优化或处理**,导致在某些环境下能够得到一个**看似合理**的结果。但这种行为是**不可靠**的,因为它依赖于具体的编译器实现细节。在不同的编译器或环境下,输出可能会完全不同。**正确的做法**:是要么返回值,要么返回一个在调用者作用域内仍然存在的对象的引用。这样可以确保代码的行为是可预测和可移植的。## 🌉引用和指针的区别1. **语法概念**:- **引用**是变量的别名,没有独立的存储空间,而是和其引用的实体共用同一块内存空间。- **指针**是一个独立的变量,存储了另一个变量的内存地址。2. **声明语法**:- 引用使用`&`符号声明,如`int& ref = x;`- 指针使用`*`符号声明,如`int* ptr = &x;`3. **操作方式**:- 引用直接访问和操作其引用的实体,如`ref = 10;`- 指针需要先解引用(`*`)才能访问其指向的实体,如`*ptr = 10;`4. **Null值**:- 引用不能为空(Null),必须在声明时初始化为一个有效的实体。- 指针可以为空(Null),指向空地址`0x0`。让我们看看例子来说明引用和指针的区别:假设我们有一个整型变量`x`,值为10。**使用引用:**cppint x = 10;int& ref = x; // 声明引用ref,它是x的别名ref = 20; // 通过ref修改x的值cout << "x = " << x << endl; // 输出 x = 20ref`是`x`的引用,它们共享同一块内存空间。通过`ref`修改值,实际上是在修改`x`的值。 输出`x`的值为20,因为`x`的值已经被修改了。**使用指针:**```cppint x = 10;int* ptr = &x; // 声明指针ptr,存储x的地址*ptr = 20; // 通过ptr解引用并修改x的值cout << "x = " << x << endl; // 输出 x = 20ptr是一个指向x的指针,存储了x的内存地址。通过*ptr解引用并修改值,实际上是在修改x的值。输出x的值为20,因为x的值已经被修改了。>在**底层实现**上实际是有空间的,因为**引用是按照指针方式来实现的**。```cppint main(){ int a = 10; int& ra = a; ra = 20; int* pa = &a; *pa = 20; return 0;}```我们来看下引用和指针的汇编代码对比得出:==在汇编中**引用**的底层逻辑还是**指针**,经过编译转换成汇编,还是进行指针的操作==![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/be0e447bf42344f2a653f8ca2108e349.png)引用和指针的不同点:1. **引用概念上定义一个变量的别名,指针存储一个变量地址**。2. **引用在定义时必须初始化,指针没有要求**3. **引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体**4. **没有NULL引用,但有NULL指针**5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)6. **引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小**7. **有多级指针,但是没有多级引用**8. **访问实体方式不同,指针需要显式解引用,引用编译器自己处理**9. **引用比指针使用起来相对更安全**# 🌠常引用从上述代码中,我们可以得出以下关于常引用的结论:1. **常量引用**:```cppconst int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量const int& ra = a;```对于常量对象a,我们可以使用常引用const int& ra = a;来引用它。这样做可以避免对常量进行修改,直接使用非常引用int& ra = a;会在编译时报错,因为不允许对常量进行非常引用。2. **字面量常引用**:```cpp// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;```我们可以使用常引用const int& b = 10;来引用字面量常量。这样做可以避免创建临时变量, 直接使用非常引用int& b = 10;会在编译时报错,因为字面量不能被非常引用。3. **类型不匹配的常引用**:```cppdouble d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;```根据类型不同的变量,如double d = 12.34;,我们可以使用常引用const int& rd = d;来引用它,直接使用非常引用int& rd = d;`会在编译时报错,因为类型不匹配。—# 🚩总结请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1569746.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

路由器怎么做端口映射?

路由器作为连接网络的关键设备&#xff0c;在家庭和办公网络中扮演着重要的角色。除了将互联网连接到局域网中&#xff0c;路由器还提供了一些常用的功能&#xff0c;其中之一就是端口映射。 什么是路由器端口映射&#xff1f; 在了解路由器端口映射之前&#xff0c;我们先来了…

HarmonyOS NEXT应用开发之@Provide装饰器和\@Consume装饰器:与后代组件双向同步

Provide和Consume&#xff0c;应用于与后代组件的双向数据同步&#xff0c;应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递&#xff0c;Provide和Consume摆脱参数传递机制的束缚&#xff0c;实现跨层级传递。 其中Provide装饰的变…

前端三剑客 —— CSS (第六节)

目录 内容回顾&#xff1a; 弹性布局属性介绍 案例演示 商品案例 布局分析 登录案例 网格布局 内容回顾&#xff1a; 变量&#xff1a;定义变量使用 --名称&#xff1a;值&#xff1b; 使用变量&#xff1a; 属性名&#xff1a;var&#xff08;--名称&#xff09;&a…

Flutter 画笔(Paint)、绘制直线(drawLine)

override bool shouldRepaint(CustomPainter oldDelegate) > true; } class MyPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { //画背景 var paint Paint() …isAntiAlias false …strokeWidth30.0 …color Colors.red; c…

多功能知识付费源码下载-实现流量互导多渠道变现(带详细安装教程)

资源变现类产品的许多优势&#xff0c;并剔除了那些无关紧要的元素&#xff0c;使得本产品在运营和变现能力方面实现了质的飞跃。多领域素材资源知识变现营销裂变独立版本。 支持&#xff1a;视频、音频、图文、文档、会员、社群、用户发布、创作分成、任务裂变、流量主、在线…

Cisco Modeling Labs (CML) 2.7 - 网络仿真工具

Cisco Modeling Labs (CML) 2.7 - 网络仿真工具 思科建模实验室 (CML) 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-modeling-labs-2/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 Cisco Modeling Labs 是我们用于网络仿真的首要平台。凭借易…

绿联 安装zfile,创建属于自己的网盘,支持直链分享

绿联 安装zfile&#xff0c;创建属于自己的网盘&#xff0c;支持直链分享 1、镜像 zhaojun1998/zfile:latest ZFile ZFile 是一个适用于个人的在线网盘(列目录)程序&#xff0c;可以将你各个存储类型的存储源&#xff0c;统一到一个网页中查看、预览、维护&#xff0c;再也不用…

音视频开发之旅(81)- 图片视频“黑边”检测与去除

目录 1.“黑边“的场景 2. 二值化--单一颜色边缘的图像 3. canny边缘检测霍夫直线变换--处理负责的边缘图像 4. 性能优化 5. 资料 在页面展示中&#xff0c;如果图片/视频有黑边&#xff0c;比较影响体验&#xff0c;我我们今天实现下对图片/视频进行黑边检测。检测到黑边…

SQL Server维护计划

目录 1.概述 2.启动SQL Server 代理服务 3.制定维护计划 4.验证维护计划 5.删除维护计划 1.概述 此文还是存货哈&#xff01; SQL Server 2008 R2维护计划。 2.启动SQL Server 代理服务 在设置维护计划之前&#xff0c;必须先确保SQL Server 代理服务已启动。启动方法如…

Html提高——视频标签音频标签及其相关属性

HTML5 在不使用插件的情况下&#xff0c;也可以原生的支持音视频格式文件的播放&#xff0c;当然&#xff0c;支持的格式是有限的。 1、video标签 1.1、video标签的语法 <video src"文件地址" controls"controls"></video> video标签的内部…

SAP FI学习笔记04 - 基础知识 - 新规会计科目,用 应收账款科目 做个例子

上一章讲了 应付账款 的来源及操作。 SAP FI学习笔记03 - 应付账款-CSDN博客 本章先尝试做一个会计科目&#xff0c;然后再用 应收账款 科目做个例子。 应收账款 和 应付账款 一样&#xff0c;也有两个视图。 1&#xff0c;新建几个会计科目 这一章来尝试着创建会计科目。 …

Qt QML的枚举浅用

QML的枚举用法 序言概念命名规则在QML定义枚举的规范 用法QML的枚举定义方法供QML调用的&#xff0c;C的枚举定义方法 序言 概念 QML的枚举和C的其实差不多&#xff0c;但是呢&#xff0c;局限比较多&#xff0c;首先不能在main.qml里定义&#xff0c;也不能在子项中定义。 …

Java spring 01 (图灵)

01.依赖注入 这里两个方法用到了datasource方法&#xff0c;不是bean这样的使用&#xff0c;没有autowird 会创建两个datasource configuration 会运行代理模式 会产生一个AppConfig的代理对象 这个代理对象会在spring的容器先找bean&#xff0c;datasource此时已经创建了be…

ubuntu无法粘贴复制windows中的内容,分辨率无法自适应电脑自带系统

1、直接在命令行执行以下命令 sudo apt-get autoremove open-vm-tools //卸载已有的工具 sudo apt-get install open-vm-tools //安装工具open-vm-tools sudo apt-get install open-vm-tools-desktop //安装open-vm-tools-desktop 2、重启Ubuntu系统即可 3.如果上述…

【QT+QGIS跨平台编译】056:【pdal_lazperf+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、pdal_lazperf介绍二、pdal下载三、文件分析四、pro文件五、编译实践一、pdal_lazperf介绍 pdal_lazperf 是 PDAL(Point Data Abstraction Library)的一个插件,用于处理点云数据。PDAL 是一个开源的库,用于处理和分析地理空间数据,特别是点云…

Linux集群部署项目

目录 一&#xff0c;环境准备 1.1.安装MySQL 1.2.安装JDK 1.3.安装TomCat 1.4.安装Nginx 二&#xff0c;部署 2.1.后台服务部署 2.2.Nginx配置负载均衡及静态资源部署 一&#xff0c;环境准备 1.1.安装MySQL 将MySQL的安装包上传至服务器 查看系统中是否存在mariadb&…

了解 Solidity 语言:构建智能合约的首选编程语言

了解 Solidity 语言&#xff1a;构建智能合约的首选编程语言 Solidity 是一种用于编写智能合约的高级编程语言&#xff0c;广泛应用于以太坊和其他以太坊虚拟机&#xff08;EVM&#xff09;兼容的区块链平台。它是以太坊智能合约的首选语言之一&#xff0c;具有丰富的功能和灵活…

GD32F470_GY-SHT31-D 数字温湿度传感器模块移植

2.11 SHT30温湿度传感器 2.11.1 模块来源 采购链接&#xff1a; GY-SHT31-D 数字温湿度传感器模块 资料下载链接&#xff1a; https://pan.baidu.com/s/1kisMJspcV6Qdr1ye9ElOlQ 2.11.2 规格参数 工作电压&#xff1a;2.4-5.5V 工作电流&#xff1a;0.2~1500uA 温度测量范围&a…

微软文本转语音和语音转文本功能更新,效果显著!

今天我要和大家分享一个新功能更新——微软的文本转语音和语音转文本功能。最近&#xff0c;微软对其AI语音识别和语音合成技术进行了重大升级&#xff0c;效果非常好&#xff0c;现在我将分别为大家介绍这两个功能。 先来听下这个效果吧 微软文本转语音和语音转文本功能更新 …

CVPR24_ArGue: Attribute-Guided Prompt Tuning for Vision-Language Models

Abstract 尽管软提示微调在调整视觉语言模型以适应下游任务方面表现出色&#xff0c;但在处理分布偏移方面存在局限性&#xff0c;通过属性引导提示微调&#xff08;Attribute-Guided&#xff0c;ArGue&#xff09;来解决这个问题 Contributions 与直接在类名之前添加软提示…