先写出关于对象传递和对象返回的总结:相对函数来说,如果是传递对象请使用pass-by-reference 而对象返回请使用pass-by-value.
为什么对象传递的时候要用pass-by-reference,而不用pass-by-value呢,举个例子,如下:
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
Person(string _name, string _address):name(_name), address(_address) {
}
~Person() {}
private:
string name;
string address;
};
class Student:public Person {
public:
Student(string _name, string _address, string _schoolName, string _schoolAddress)
:Person(_name, _address), schoolName(_schoolName), schoolAddress(_schoolAddress){
}
~Student() {}
private:
string schoolName;
string schoolAddress;
};
Student student("tom", "yilu", "mit", "America");
bool isValidateStudent(Student s) {
// to do
}
pass-by-value会引起的另一个问题就是对象切割,还是以上面那个例子做基础,做稍微的更改,如下:
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
Person(string _name, string _address):name(_name), address(_address) {
}
~Person() {}
void liveWhere() {
cout << address << endl;
}
virtual void likingWhat();
private:
string name;
string address;
};
class Student:public Person {
public:
Student(string _name, string _address, string _schoolName, string _schoolAddress)
:Person(_name, _address), schoolName(_schoolName), schoolAddress(_schoolAddress){
}
~Student() {}
virtual void likingWhat() {
// to do
}
private:
string schoolName;
string schoolAddress;
};
Student student("tom", "yilu", "mit", "America");
void isLikingWhat(Person p) {
cout << p.likingWhat() << endl;
}
上面的例子假设社会上的每一个群体都有自己独特的喜好,那么函数isLikingWhat将接受一个基类Person的对象,并打印出每个对象所喜欢的东西,可以看出,这是对应多台的应用。然而当传递student对象给isLikingWhat函数的时候,他不知道应该怎么来构造这个对象,因为他的参数类型是Person,它不能确定,继承于Person的子类对象是什么,所以在参数传递的时候,只有student对象的Person部分被复制,而属于student专有的内容被抛弃了,则在isLikingWhat函数进行打印的时候他调用的是Person的函数,而不是Student实现的函数。这就是pass-by-value造成的对象切割。如果我们换成引用传递的话,我们应该还记得,子类的对象是可以用来给父类对象的引用赋值的。所以当在引用传递的时候,会根据虚函数列表找到相应的函数进行调用。
总结一下:pass-by-reference解决了两个pass-by-value不能解决的问题,一个是构造函数和析构函数调用引起的资源浪费,另一个是对象切割。
既然能看到pass-by-reference这么多好处,那么我们在函数返回值的时候也考虑一下使用引用返回,怎么样呢,因为在函数返回值的时候要进行对象拷贝,把返回的值拷贝到接收对象之中,如果让接收对象只是一个引用,那么就省去了构造函数和析构函数调用所带来的代价。
考虑下面这样一个例子:
#include<iostream>
#include<string>
using namespace std;
class Rational {
public:
Rational(int _n, int _d) {
n = _n;
d = _d;
}
~Rational() {
}
const Rational& operator*(const Rational& lhs, const Rational &rhs);
private:
int n;
int d;
};
const Rational& Rational::operator *(const Rational &lhs, const Rational &rhs) {
Rational result = Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
可以看看这个operator*函数,首先它建立了一个Rational对象,因为是函数的内部对象,所以应该放在栈中,之后函数返回对对象result的引用。想想会出现什么问题,当oper*函数结束的时候,result对象也被析构了,它在内存中不在存在,但是函数之外还是会对它进行引用,这就会像悬垂指针一样,乱指内存,当有其他函数应用它的时候,就会出现问题,或者造成整个程序的崩溃,(我写过这样的程序,因为野指针造成运行在手机上的程序崩溃,手机死机)。既然这样,那我们就会马上想到另外一种方法,就是用堆,这样,函数结束的时候,对象就会存在,引用它的时候就不会出问题了,看看下面的实现:
const Rational& Rational::operator *(const Rational &lhs, const Rational &rhs) {
Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
把对象存储在堆上我们就放心了,但是还要考虑的问题就是,谁来释放我们分配在对象的对象呢,释放对象一个很好的时机就是没有人引用它的时候进行释放。那么请看下面这个例子:
Rational a, b ,c , d;
d = a * b * c;
按照上面operator*的实现方法,new要被调用两次,但是只有一个引用来指向两次中最后一次分配的Rational,第一次分配的就没有人最管它,想释放的时候都不知道怎么释放了。
总结上面两点:如果将对象放在heap或者stack上都会造成问题,那么我们可以考虑另外一个方法,就是把对象放在可以长期保存又有人去管理的地方怎样呢-静态存储区。
实现如下:
const Rational& Rational::operator *(const Rational &lhs, const Rational &rhs) {
static Rational result =Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
这样实现,我们既不用考虑分配在stack上的问题,也不用考虑分配在heap上的问题,但是考虑下面这个调用:
bool operator==(const Rational &lhs, const Rational &rhs), 有Rational a, b, c, d;
if (a * b == c * d) {
} else {
}
大家能猜到结果么,结果就是不管a,b,c,d 是什么值,条件判断的结果总是真,来分析一下吧,operator返回的是静态变量的引用,那么不管怎么改变它的值,引用都是不知道的, 上面的条件判断中,一共调用了两次operator*, 第一次是a*b,结果存储的是a*b的值,而第二次是c*d的值,仔细想一想,operator*返回的引用总是指向最新的值,而不管它被调用了多少次,因为他是静态变量。
通过以上的分析,总结出一点:当向函数内部传递参数的时候,请尽量使用pass-by-reference,当从函数向外部传递的时候,请尽量使用pass-by-value,也就是说,请不要在接收函数返回值的时候吝啬调用构造函数和析构函数。