这里写目录标题
1,函数传参时—引用和指针传参
1.1 引用传参
// // 形参是个右值引用
void func_right(int&& right_value)
{
right_value = 8;
}
// 形参是个左值引用
void func_left(int& left_value)
{
left_value = 1;
}
int main()
{
int a = 1;
int& left_a = a;//左值引用(left_a)
int&& right_a = std::move(a);//右值引用(right_a) 右值引用只能引用右值,但是a是左值,所以用move将a转为右值
func_right(std::move(a));
std::cout << &a << "\n" << &left_a << "\n" << &right_a << std::endl;//地址一样
std::cout << a << "\n" << left_a << "\n" << right_a << std::endl;//都是8
}
传参时候,无论是左值引用还是右值引用传参,都可以避免在栈区开辟临时内存进行拷贝,提高性能。
1.2 指针传参
引用的本质就是指针来实现的。指针传参当然也可以避免开辟临时栈区内存问题!
2,函数返回值
2.1 返回类型是 值返回
int test()
{
int a = 1;
return a;
}
int main()
{
int& x = test();//报错:左值引用不能引用右值
int&& x = test();//右值引用ok
std::cout << x;
}
返回值时操作主要在栈上,变量a在函数结束后会被释放,为了返回a的值,系统会在内部建立一个临时变量保存a的值,以返回给调用该函数的表达式,调用结束后变量便不再存在。如果a是简单地数据类型也无所谓,不是很占用内存,如果a是大的自定义类型的数据,那么对a的复制将会占用比较大的内存。
2.2 返回值类型是 指针
int* test()
{
int a = 1;
return &a;//这样返回局部变量指向的临时内存,不行!
return new int(2);//返回堆区内存!可以!
}
int main()
{
int *x = test();//右值引用ok
std::cout << *x << std::endl;// 1
std::cout << *x;//6564666
}
返回是指针类型时候,注意:返回的不能是局部变量,或者说返回的变量指向的内存不能是在栈区创建的内存,因为函数结束就会被释放。可以在堆区创建!
和返回类型是 值 相比,返回指针类型时候,根据函数栈的特性,也会产生复制,只是复制的是一个指针即一个地址,对于返回大型对象可以减少不少的资源消耗!!!!!!
2.3 返回类型是 引用
引用是值的别名,和指针一样不存在对大对象本身的复制,只是引用别名的复制。引用是左值,可以直接进行操作,也可以进行连续赋值,最经典的实例是拷贝构造函数与运算符重载一般都返回引用。
需要注意的是局部变量不能作为引用返回。
2.4 类中成员函数返回 对象,引用
返回对象涉及到生成对象的副本。因此返回对象的成本包括了调用复制构造函数来生成副本所需要的时间和调用析构函数删除副本所需要的时间。
当类中成员变量涉及到开辟堆区内存时,一定要重写拷贝构造函数!!!防止堆区内存重复释放;
using namespace std;
class Person {
public:
Person func()
{
return *this;//返回Person类型,相当于先创建临时对象。Person temp = *this;把temp赋值给调用者之后,再释放这个对象,这个赋值的过程就涉及到拷贝构造!一定重写拷贝构造函数,防止重复释放堆区内存!!!
}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);//默认的拷贝构造函数里面是m_height =p.m_height这是浅拷贝
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
/*if (m_height != NULL)
{
delete m_height;
}*/
delete m_height;
}
public:
int m_age;
int* m_height;//指针,在构造函数里面用new在堆区开辟内存,返回指针
};
void test01()
{
Person p1;
p1.func();
//Person p2(p1);
}
int main() {
test01();
}
额,跑题了,如果不涉及堆区内存,返回对象类型时,会在栈区生成整个对象成员的副本!浪费内存!
返回引用可以节省时间和内存。因为生成的副本增加的内存只是这个对象的引用变量所占的内存。
3,右值引用优化性能,避免深拷贝
右值引用是C++11引入的特性,它允许我们将一个临时对象(右值)绑定到一个引用上,并且可以通过移动语义(Move Semantics)来避免不必要的对象拷贝。
深拷贝构造函数就可以保证拷贝构造时的安全性,但有时这种拷贝构造却是不必要的,比如下面代码中的拷贝构造就是不必要的。代码中的 test01函数会返回临时变量Person temp = p1,临时变量在拷贝构造完成之后就销毁了,如果堆内存很大,那么,这个拷贝构造的代价会很大,带来了额外的性能损耗。
using namespace std;
class Person {
public:
Person():m_height{new int(100)}
{}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);//默认的拷贝构造函数里面是m_height =p.m_height这是浅拷贝
}
//移动构造函数
Person(Person&& p):m_age{p.m_age},m_height{p.m_height}
{
p.m_height = nullptr;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
delete m_height;
}
public:
int m_age;
int* m_height;//指针,在构造函数里面用new在堆区开辟内存,返回指针
};
Person test01()
{
Person p1;
return p1;
}
int main() {
test01();
}
从移动构造函数的实现中可以看到,它的参数是一个右值引用类型的参数 Person&&,这里没有深拷贝,只有浅拷贝,这样就避免了对临时对象的深拷贝,提高了性能。这里的 Person&& 用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数。移动构造函数只是将临时对象的资源做了浅拷贝,不需要对其进行深拷贝,从而避免了额外的拷贝,提高性能。
持续更新ing。。。。。。。。。