C++ &以及&&的用法总结
首先,&有两种用法:
1、取地址
2、引用
取地址和引用没有任何关系,不要瞎联系!
1、取地址:
// 很常规,仅此而已
std::string *p = &s;
2、引用:
a)引用是某一个变量或者对象的别名,对引用的操作与对其所绑定的变量或对象的操作完全等价。而引用中使用 & ,跟地址没有任何关系,仅仅是起到标志作用。
int a = 10;
// 引用的类型必须和其所绑定的变量的类型相同;且必须初始化。
int &b = a;
b)引用相当于变量或者对象的别名,不能将已有的引用名作为其他变量或者对象的引用名。
c) 引用不是定义一个新的变量,不会为引用开辟新的内存空间。
d)对数组的引用需要加上数组的大小
int a[3] = {1, 2, 3};
int (&b) [3] = a;
e)对指针的引用
int a = 1;
int *p = &a;
// 指针引用
int * &pp = p;
f)引用作为函数的参数
#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;
}
其中需要指出的是:
I)当引用作为参数的时候,其效果跟利用指针作为参数的效果相当。当调用函数时,函数中的形参就会被当成实参变量或者对象的一个别名使用,也就是说函数中对形参的各种操作实际上就是对实参本身的操作,而非简单的实参变量或者对象的值拷贝给形参。
II)通常函数调用时,采用值传递的方式。系统会在内存中开辟空间用来存储形参变量,并将实参变量的值拷贝给形参变量,所以形参变量只是实参变量的副本而已,并且如果函数传递的是类的对象,系统还会调用类中的拷贝构造函数来构造形参对象。而使用引用作为参数传递时,由于此时形参只是传递函数的实参变量或者对象的别名而非副本,故系统不会耗费时间在内存中开辟空间来存储形参,因此如果参数传递的数据较大,建议使用引用作为函数的形参,提高函数的时间效率,节省内存空间。
使用指针作为函数的形参,虽然达到的效果跟使用引用一样,但当调用函数时仍然需要为形参指针分配空间,引用则不需要【引用在底层也会分配指针大小的空间,在汇编底层角度,引用和指针是一样的,不过引用类似于常量指针】。推荐使用引用而非指针作为函数的传递函数。
如果既希望通过引用作为函数形参提高效率,又希望保护传递的参数在函数中不被改变,则可以使用对常量的引用作为函数的形参。
另外,如果使用数组的引用作为函数形参,引用传递时指明的是数组则必须指定数组的长度。
#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;
}
g)常引用
常引用,不允许通过该引用对其所绑定的变量或者对象进行修改。
值得注意的是,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;
}
h)引用作为函数的返回值
引用作为函数返回值时,必须在定义函数时在函数名前加 &;使用引用作为函数的返回值的最大的好处是在内存中不产生返回值的副本。
#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'
// 逻辑是首先拷贝 temp 的值给临时变量。返回到主函数后,用临时变量来初始化引用变量 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);
// 不能返回局部变量的引用。如上面的例子,如果 temp 是局部变量,那么它会在函数返回后
// 被销毁,此时对 temp 的引用就会成为“无所指”的引用,程序会进入未知状态。
// 不能返回函数内部通过 new 分配的内存的引用。虽然不存在局部变量的被动销毁问题,但如
// 果被返回的函数的引用只是作为一个临时变量出现,而没有将其赋值给一个实际的变量,那么
// 就可能造成这个引用所指向的空间(有 new 分配)无法释放的情况(由于没有具体的变量名,
// 故无法用 delete 手动释放该内存),从而造成内存泄漏。
// 当返回类成员的引用时,最好是 const 引用。这样可以避免在无意的情况下破坏该类的成员。
cout<<a<<endl; //78.5
//cout<<b<<endl; //78.5
cout<<c<<endl; //78.5
cout<<d<<endl; //78.5
return 0;
}
i)在C++中,引用是除了指针外另一个可以产生多态效果的手段。也就是说一个基类的引用可以用来绑定其派生类的实例。ptr只能用来访问派生类对象中从基类继承下来的成员。如果基类(类Father)中定义的有虚函数,那么就可以通过在派生类(类Son)中重写这个虚函数来实现类的多态。
class Father; //基类(父类)
class Son:public Father{.....} //Son是Father的派生类
Son son; //son是类Son的一个实例
Father &ptr=son; //用派生类的对象初始化基类对象的使用
在引用的使用中,单纯给某个变量去别名是毫无意义的,引用的目的主要用于在函数参数的传递中,解决大块数据或对象的传递效率和空间不如意的问题
用引用传递函数的参数,能保证参数在传递的过程中不产生副本,从而提高传递效率,同时通过const的使用,还可以保证参数在传递过程中的安全性
引用本身是目标变量或对象的别名,对引用的操作本质上就是对目标变量或对象的操作。因此能使用引用时尽量使用引用而非指针.
其次,&& 是右值引用。
左值和右值
左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、“right value” 的缩写,其实不然。lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。
左值和右值在C语言中可以简单的理解为:左值可以位于赋值运算符的左侧,右值则不能位于运算符左侧;但是在C++中左值和右值就明显的复杂很多,C++中的左值和右值在《C++ primer》中这样归纳:当一个对象被用作右值的时候用的是对象的值(内容),当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置)。 简单说来就是左值可以被修改(可以取地址),而右值不能被修改(不能取地址)。
左值引用在声名时必须初始化。
// 左值引用
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 只对构造函数起作用,用来抑制隐式转换