一、引用的概念
参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。
对于像 char、bool、int、float 等基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。而数组、结构体、对象是一系列数据的集合,数据的数量没有限制,可能很少,也可能成千上万,对它们进行频繁的内存拷贝可能会消耗很多时间,拖慢程序的执行效率。
C/C++禁止在函数调用时直接传递数组的内容,而是强制传递数组指针。而对于结构体和对象没有这种限制,调用函数时既可以传递指针,也可以直接传递内容;为了提高效率,可以传递指针。
但是在 C++ 中,我们有了一种比指针更加便捷的传递聚合类型数据的方式,那就是引用(Reference),用&表示。
引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。
引用的语法格式如下:
Type &name=data
/*
Type 是被引用的数据的类型;
name是引用的名称;
data是被引用的数据
*/
使用引用的规则:
1)引用被创建时,必须被初始化(指针可以在任何时候被初始化)。
2)一旦一个引用被初始化为指向一个对象,就不能改变为另一个对象的引用(指针可以在任何时候指向另一个对象)。
3)没有NULL引用。必须确保引用是一块合法的存储单元关联。
示例演示:
int a = 10;
int& num = a; //&表示引用
cout << "a的地址:"<< &a << endl; //&表示取地址
cout << "a的值:" << a << endl;
cout << "num的地址:" << &num << endl;
cout << "num的值:" << num << endl;
num = 100;
cout << "a的地址:" << &a << endl;
cout << "a的值:" << a << endl;
cout << "num的地址:" << &num << endl;
cout << "num的值:" << num << endl;
由上结果可知,变量a和引用num表示的是同一份数据,同一块内存,所以无论通过变量a还是num都是访问同一数据。通过对引用修改也可以修改原始变量所存储的数据。
常引用
如果不希望通过引用来修改原始数据,那么可以在定义时添加const 进行限制,语法格式如下:
const Type &num=data;
或
Type const &num=data;
这种引用方式称为常引用。
示例演示:
int a = 10;
const int& num = a;
num = 100;//编译时报错,提示不能给常量赋值
二、函数中的引用
引用作为函数参数
在函数定义或声明时,可以将函数的形参指定为引用的形式,这样调用函数时就会将实参和形参捆绑在一起,让它们指代同一份数据。这样,函数体中对形参进行修改,那么实参也会被修改。
示例演示:
void swap1(int a, int b);
void swap2(int* p1, int* p2);
void swap3(int& r1, int& r2);
//直接传递参数内容
void swap1(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//传递指针
void swap2(int* p1, int* p2) {
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
//按引用传参
void swap3(int& r1, int& r2) {
int temp = r1;
r1 = r2;
r2 = temp;
}
int main() {
int num1=10, num2=20;
swap1(num1, num2);
cout << num1 << " " << num2 << endl;
swap2(&num1, &num2);
cout << num1 << " " << num2 << endl;
swap3(num1, num2);
cout << num1 << " " << num2 << endl;
return 0;
}
由以上结果可知,通过值传递的方式,不能达到交换两个数的目的。传指针或传引用都能达到交换目的。
参数传递原则:
传值方式需要调用构造函数和析构函数,如果不想改变参数,则可通过常量引用传递,它仅需要将地址压栈。只有一种情况不适合用传递地址,就是传值为唯一安全的途径。
引用作为函数返回值
int& Myplus(int& i)
{
i += 1;
return i;
}
int main()
{
int num = 10;
int num2 = Myplus(num);
cout << num << endl;
cout << num2 << endl;
cout << &num << endl;
cout << &num2 << endl;
}
将引用作为函数返回值时应注意不能返回局部数据(例如局部变量、局部对象、局部数组)的引用,因为当函数调用完成后局部数据就会被销毁,可能下次使用时数据就不存在了。
int& Myplus(int& i)
{
int temp = i;
temp += 1;
return temp;
}
int main()
{
int num = 10;
int num2 = Myplus(num);
cout << num << endl;
cout << num2 << endl;
int &num3 = Myplus(num);
int &num4 = Myplus(num3);
cout << num << endl;
cout << num3 << endl;
cout << num4 << endl;
}
上面结果并非我们想要的结果。导致结果怪异的原因就是返回一个局部变量的引用。第二次调用Myplus时会覆盖第一次调用Myplus产生的局部变量,第三次同理。
三、临时对象
C++的临时对象是不可见的匿名对象,但程序在运行时确实生成了的对象。
以下是产生临时对象的场景:
1)构造函数作为隐式类型转换函数时,会创建临时对象,用作实参传递给函数
class Integer
{
public:
Integer(int i) :m_val(i)
{
cout << "constructor" << endl;
}
~Integer()
{
cout << "destructor" << endl;
}
int getVal()
{
return m_val;
}
private:
int m_val;
};
void testFunc(Integer itgr)
{
cout << itgr.getVal() << endl;
}
int main()
{
int i = 10;
testFunc(i);//产生临时变量
}
testFunc(i)会调用构造函数Integer(int i)进行隐式数据转换产生一个临时对象,作为实参传递到testFunc函数中。
消除临时对象方法:只能尽量避免隐式转换
2)函数返回一个对象时,会产生临时对象。以返回的对象最作为拷贝构造函数的实参构造一个临时对象。
class Integer
{
public:
Integer()
{
cout << "default-Constructor" << endl;
};
Integer(const Integer& arg)
{
this->m_val = arg.m_val;
cout << "Copy Constructor" << endl;
};
Integer(int i) :m_val(i) {
cout << "Constructor" << endl;
};
Integer& operator=(const Integer& arg)
{
cout << "Assignment operator function" << endl;
if (this != &arg) //防止自赋值
{
this->m_val = arg.m_val;
}
return *this;
}
~Integer() {};
int m_val;
};
Integer testFunc(Integer inter)
{
inter.m_val++;
cout << "before return" << endl;
return inter;
}
int main(int argc, char* argv[])
{
Integer inter(5);//Constructor
Integer resutl; //default constructor
resutl = testFunc(2);//Constructor,then Copy Constructor,then Assignment operator
cout << resutl.m_val << endl;
return 0;
}
消除临时对象方法:以引用的方法返回
void testFunc(Integer& inter)
{
inter.m_val++;
cout << "before return" << endl;
}
int main(int argc, char* argv[])
{
Integer inter(5);//Constructor
Integer resutl; //default constructor
testFunc(inter);//这样就不会产生临时变量
cout << inter.m_val << endl;
return 0;
}