&以及&&的用法总结

一.&两种用法[取地址/引用]

取地址和引用没有任何关系,不要瞎联系!

1.1 取地址

// 很常规,仅此而已
std::string *p = &s;   

1.2 引用

  • 引用是某一个变量的别名对引用的操作就是对绑定变量的操作
  • 引用是变量的别名,故在定义时必须初始化,且引用名具有唯一性,绑定后不可再绑定其他变量
  • 引用不是定义一个新的变量,不会开辟新的内存空间
int a = 10;
 
// 引用的类型必须和其所绑定的变量的类型相同;且必须初始化。
int &b = a;

1.3 两大主要场景【传参/返回值-避免对象拷贝

1.3.1 作为函数参数【引用对象,无拷贝行为,高效

c++传值方式比较:

  • 值传递(by value): 采用值传递,系统会在内存中开辟空间用来存储形参变量,并将实参变量的值拷贝给形参变量,所以形参只是实参的副本,故在函数体内值发生变化,也不会影响实参的值【发生一次拷贝,效率较低】;
  • 引用传递(by reference):引用传递,此时形参是实参的别名而非副本系统不会发生拷贝行为,因此建议尽量使用引用作为函数的形参,尤其是大数据传递,提高效率,节省内存【引用对象,无拷贝行为,高效】;
  • 指针传递(by pointer/reference):指针传递,虽然达到的效果跟使用引用一样,但当调用函数时仍然需要为形参指针分配空间,引用则不需要。【引用在底层也会分配指针大小的空间,在汇编底层角度,引用和指针是一样的,不过引用类似于常量指针】。推荐使用引用而非指针作为函数的传递函数。

需要指出的是,当引用作为参数的时候,形参是实参的别名而非副本,也就是说函数中对形参操作实际上就是对实参本身操作,形参在函数体内值发生变化,会影响实参的值;但是如果既希望通过引用作为函数形参提高效率,又希望保护传递的参数在函数中不被改变,则可以使用对常量的引用作为函数的形参,这也是工程中常见的传递方式。

#include<iostream>
using namespace std;
 
void swap(int &a,int &b){
    int temp=a;
    a=b;
    b=temp;
}
int main(){
    
    int value1=10,value2=20;
    
    cout<<"----------------------交换前----------------------------"<<endl;
    cout<<"value1的值为:"<<value1<<endl;
    cout<<"value2的值为:"<<value2<<endl;
    
    swap(value1,value2);
    
    cout<<"----------------------交换后----------------------------"<<endl;
    cout<<"value1的值为:"<<value1<<endl;
    cout<<"value2的值为:"<<value2<<endl;
 
    return 0;
}

常引用,不允许通过该引用对其所绑定的变量或者对象进行修改。值得注意的是,C++ 中所有的临时变量都是 const 类型的。

#include<iostream>
#include<string>
using namespace std;
 
string func1(){
    string temp="This is func1";
    return temp;
}
void func2(const string &str){
    cout<<str<<endl;
}
 
int main(){
    // 在 main 中会将 func1() 返回的对象 temp 首先赋予一个临时对象,
    // 之后再对该临时变量操作,如赋值给新的变量等。
    string returned_temp = func1();
    
    // 所以将 func1() 产生的临时对象值传递给 func2() 时,
    // 必须要注意的是: 要使用 const 的形参 void func2(const string &str)...
    // 否则会出现“试图将 const 对象赋值给非 cosnt 对象的错误”。
    func2(func1());
 
    // “Tomwenxing” 类似。
    func2("Tomwenxing");
    return 0;
}

另外,如果使用数组的引用作为函数形参,引用传递时指明的是数组则必须指定数组的长度

#include<iostream>
using namespace std;
 
void func(int(&a)[5]){
// !-! 数组引用作为函数的参数,必须指明数组的长度
 
// do something ...
}
 
int main(){
    int number[5]={0,1,2,3,4};
    func(number);
    return 0;
}

1.3.2 作为函数的返回值

  • 引用作为函数返回值时,必须在定义函数时在函数名前加 &
  • 引用作为函数的返回值的最大的好处是在内存中不产生返回值的副本
#include<iostream>
using namespace std;
float temp; float fn1(float r){ temp = r*r*3.14; return temp; } float &fn2(float r){ // & 说明返回的是 temp 的引用,也就是返回 temp 本身 temp = r*r*3.14; return temp; } int main(){ // case 1:返回值 ----------------------------------------------------------------- // 在内存中创建临时变量,并将 temp 的值拷贝给该临时变量。 // 当返回到主函数 main 后,赋值语句 a = fn1(5.0) // 会把临时变量的值再拷贝给变量 a。 float a = fn1(5.0);
// case 2: 用函数的返回值作为引用的初始化值 --------------------------------------- // float &b = fn1(5.0); // [Error] invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'
// fn1的返回类型是float值类型,并非float&类型,编译报错:error C2440: “初始化”: 无法从“float”转换为“float&”
// fn1以值方式返回,返回时首先拷贝 temp 的值给临时变量。返回到主函数后,用临时变量来初始化引用变量 b, // b就成为该临时变量的别名。但是临时变量的作用域短暂(仅仅是一句表达式)会使得b有无效的风险,b很可能成为无所指未知状态。
// 所以不建议使用,非要用函数返回值作为引用返回值,正确方式如下:
float x = fn1(5.0); float &b = x; // case 3:返回引用 -------------------------------------------------------------- // 函数 fn2() 的返回值不产生副本,而是直接将变量 temp 返回给主函数,即主函数的赋值语 // 句中的左值是直接从变量 temp 中拷贝而来(也就是说 c 只是变量 temp 的一个拷贝而非别 // 名, 拷贝是从赋值语句来的) float c = fn2(5.0); // case 4:用函数返回的引用作为新引用的初始化值【推荐用法】 ---------------------------------- // 函数返回引用类型函数 fn2() 的返回值不产生副本,而是直接将变量 temp 返回给主函数。
// 接收也为引用类型在主函数中,
引用声明 d 用引用类型返回值初始化,也就是说此时d成为变量temp的别名。
// 返回值为全局变量:由于 temp 是全
局变量,所以在 d 的有效期内 temp 始终保持有效,故这种做法是安全的。 float &d = fn2(5.0); //注意事项:
// 1.不能返回局部变量的引用。如上面的例子,如果 temp 是局部变量,那么它会在函数返回后
被销毁,此时对 temp 的引用就会成为“无所指”的引用,程序会进入未知状态。 // 2.不能返回函数内部通过 new 分配的内存的引用。虽然不存在局部变量的被动销毁问题,但如果被返回的函数的引用只是作为一个临时变量出现,而没有将其赋值给一个实际的变量,那么 // 就可能造成这个引用所指向的空间(有 new 分配)无法释放的情况(由于没有具体的变量名,故无法用 delete 手动释放该内存),从而造成内存泄漏。 // 3.当返回类成员的引用时,最好是 const 引用。这样可以避免在无意的情况下破坏该类的成员。 cout<<a<<endl; //78.5 //cout<<b<<endl; //78.5 cout<<c<<endl; //78.5 cout<<d<<endl; //78.5 return 0; }

1.4 其他场景【利用引用实现多态

  • 在C++中,引用是除了指针外另一个可以产生多态效果的手段。也就是说一个基类的引用可以用来绑定其派生类的实例。ptr只能用来访问派生类对象中从基类继承下来的成员。如果基类(类Father)中定义的有虚函数,那么就可以通过在派生类(类Son)中重写这个虚函数来实现类的多态
class Father;                   //基类(父类)
class Son:public Father{.....} //Son是Father的派生类
Son son;                        //son是类Son的一个实例
Father &ptr=son;                //用派生类的对象初始化基类对象的使用
  • 对指针的引用
int a = 1;
int *p = &a;
 
// 指针引用
int * &pp = p;

1.5 &总结:

  • 在引用的使用中,单纯给某个变量去别名是毫无意义的,引用的目的主要用于在函数参数的传递中,解决大块数据或对象的传递效率和空间不如意的问题
  • 用引用传递函数的参数,能保证参数在传递的过程中不产生副本,从而提高传递效率,同时通过const的使用,还可以保证参数在传递过程中的安全性
  • 引用本身是目标变量或对象的别名,对引用的操作本质上就是对目标变量或对象的操作。因此能使用引用时尽量使用引用而非指针。

二. && 是右值引用

2.1 左值和右值

左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、"right value" 的缩写,其实不然。lvalue 是“loactor value的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 "read value",指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)

// 左值引用
int num = 10;
int &b = num;     // 正确
int &c = 10;      // 错误
 
int num = 10;
const int &b = num;   // 正确
const int &c = 10;    // 正确
 
 
// 右值引用
int num = 10;
//int && a = num;    // 错误,右值引用不能初始化为左值
int && a = 10;       // 正确
 
a = 100;
cout << a << endl;   // 输出为100,右值引用可以修改值
 
 
// 右值引用的使用
// 如 thread argv 的传入
template<typename _Callable, typename... _Args>
explicit thread(_Callable&& __f, _Args&&... __args) { 
//.... 
}
// Args&&... args 是对函数参数的类型 Args&& 进行展开
// args... 是对函数参数 args 进行展开
// explicit 只对构造函数起作用,用来抑制隐式转换

————————————————
参考链接:https://blog.csdn.net/Z_xOOOO/article/details/119515300

posted on 2023-01-31 17:07  斗战胜佛美猴王  阅读(651)  评论(0编辑  收藏  举报