1.赋值运算符
先以赋值运算符引入后面要说的运算符重载,上一节说了构造函数、拷贝构造函数;一个类要想进行更好的控制,需要定义自己的构造函数、拷贝构造函数、析构函数、当然,还有赋值运算符。常说的三大函数就是指拷贝、赋值、析构。
如果一个类不定义自己的赋值运算符,会自己生成一个默认的赋值运算操作,这个默认的赋值运算满足一般类的需求。它实现的是一个浅拷贝。但是当类的功能、作用逐渐完善时,就会出现很多问题,所以,通过自定义赋值运算符来控制赋值操作时类的行为是很有必要的。当一个类的对象与对象之间发生赋值(=)运算时,就会调用重载的赋值运算符函数。
还是以上节的例子来说,看代码:
#include <iostream>
#include <new>
#include <string>
using namespace std;
class Person
{
public:
Person();
Person(int n, const string &str);
Person(const Person &n);
Person &operator=(const Person &p);//赋值运算符函数
~Person();
private:
int age;
string *name;
};
Person::Person():age(0), name(NULL)
{
cout << "Default Person" << endl;
}
Person::Person(int n, const string &str):age(n), name(new string(str))
{
cout << "Init Person" << endl;
}
Person::Person(const Person &n)
{
if(n.name)
{
name = new string(*(n.name));
age = n.age;
}
else
{
name = NULL;
age = 0;
}
}
Person & Person::operator=(const Person &p)
{
if(this == &p)
{
return *this;//判断传入的对象是否是当前对象本身
}
string *tmp = new string(*p.name);//重新分配一段空间
delete this->name;//释放原空间
this->name = tmp;
this->age = p.age;
return *this;
}
Person::~Person()
{
cout << "~Person " << "name: " << name << " age: " << age << endl;
delete name;
}
int main()
{
Person p1(20, "SCOTT");
Person p2;
p2 = p1;
return 0;
}
在这里,如果没有自定义赋值运算符,当运行P2 = P1时,是没有错的,而错误会处在析构部分,上一篇文章中已经说明原因。
Init Person
Default Person
~Person name: 0x8264020 age: 20
~Person name: 0x8264020 age: 20
Segmentation fault (core dumped)——段错误!
自定义赋值运算符之后,运行结果如下:
Init Person
Default Person
~Person name: 0x8121030 age: 20
~Person name: 0x8121020 age: 20
注意:
1)一般来说,大多数赋值运算符函数组合了析构函数和拷贝构造函数的工作。
2)如果讲一个对象赋予它自身,赋值运算符必须能正确的工作。
3)当编写赋值运算符函数时,一个好的模式是将右侧对象拷贝到一个局部临时对象中。拷贝完成后,销毁左侧对象的现有成员就是安全的了。然后将数据从临时对象拷贝到左侧对象。
2.运算符重载
上面是众多运算符重载的一个实例。重载的运算符是具有特殊名字的函数:它们的名字是由关键字operator和其后要定义的运算符号共同组成。它与普通函数一样也有返回值、参数列表、以及函数体。
重载运算符函数的参数数量与该运算符的作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个。对于二元运算符来说,左侧运算对象传递给第一个参数,右侧运算对象传递给第二个参数。
如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上,因此,成员运算符函数的参数比运算符的运算对象少一个。(如上例中的=运算符)
2.1 输入、输出运算符
IO标准库分别使用>>和<<执行输入和输出操作。类可以按需要来自定义输入、输出运算符。
例子:
#include <iostream>
#include <new>
#include <string>
using namespace std;
class Person
{
public:
Person();
Person(int n, const string &str);
Person(const Person &n);
Person &operator=(const Person &p);
~Person();
string getName()const;
friend ostream &operator<<(ostream &out, const Person &p);//输出运算符
friend istream &operator>>(istream &in, Person &p);//输入运算符
private:
int age;
string *name;
};
Person::Person():age(0), name(NULL)
{
cout << "Default Person" << endl;
}
Person::Person(int n, const string &str):age(n), name(new string(str))
{
cout << "Init Person" << endl;
}
Person::Person(const Person &n)
{
if(n.name)
{
name = new string(*(n.name));
age = n.age;
}
else
{
name = NULL;
age = 0;
}
}
Person & Person::operator=(const Person &p)
{
if(this == &p)
{
return *this;
}
string *tmp = new string(*p.name);
delete this->name;
this->name = tmp;
this->age = p.age;
cout << "operator =" << endl;
return *this;
}
Person::~Person()
{
cout << "~Person " << "name: " << name << " age: " << age << endl;
delete name;
}
string Person::getName()const
{
if(name)
{
return *name;
}
return string();
}
//重载输出运算符
ostream &operator<<(ostream &out, const Person &p)
{
out << "p.age: " << p.age << ", p.name: " << p.getName();
return out;
}
//重载输入运算符
istream &operator>>(istream &in, Person &p)
{
string s;
cout << "please input age and name:";
in >> p.age >> s;
if(in)//判断是否读取正确
{
p.name = new string(s);
}
else
{
p = Person();
}
return in;
}
int main()
{
Person p1(20, "SCOTT");
Person p2(10, "Kate");
Person p3;
/*
cout << p1 << endl;
cout << p2 << endl;
cout << p3 << endl;
*/
cin >> p3;
cout << p3 << endl;
return 0;
}
运行程序,输入12 Mike, 结果如下:
Init Person
Init Person
Default Person
please input age and name:12 Mike
p.age: 12, p.name: Mike
~Person name: 0x939a088 age: 12
~Person name: 0x939a048 age: 10
~Person name: 0x939a020 age: 20
总结:
1.输入、输出运算符必须是非成员函数!
2.输入、输出运算符一般声明为友元类型。
3.输出运算符函数中第二个参数可以声明为const型,因为不需要改变其值;而输入运算符的第二个参数不能为const,因为它要接受输入。另外,返回值最好是引用,避免了值拷贝过程。