目录
1. 运算符重载
operator关键字
1.1 加号运算符重载
实现两个类直接相加。
1.1.1 利用成员函数重载
class Person
{
public:
int m_A;
int m_B;
Person operator+ (Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
};
int main()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1+p2; //本质:p3 = p1.operator+(p2)
cout<<p3.m_A<<endl;
cout<<p3.m_B<<endl;
}
1.1.2 利用全局函数重载
class Person
{
public:
int m_A;
int m_B;
};
Person operator+ (Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
int main()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1+p2; //本质:p3 = operator+(p1,p2)
cout<<"p3 m_A is "<<p3.m_A<<endl;
cout<<"p3 m_B is "<<p3.m_B<<endl;
}
1.1.3 运算符重载可以发生函数重载
class Person
{
public:
int m_A;
int m_B;
};
Person operator+ (Person &p, int num)
{
Person temp;
temp.m_A = p.m_A + num;
temp.m_B = p.m_B + num;
return temp
}
void test2()
{
Person p4;
p4 = p1+20;
}
1.2 左移运算符重载
实现直接利用cout<<p;打印类p中的成员变量。
1.2.1 利用成员函数重载(通常不用)
class Person
{
public:
int m_A;
int m_B;
void operator<< (Person& p)
{}
};
注意,对于operator<<,函数必须传参,不传参则自动报错。
1.2.2 利用全局函数重载
#include <iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
};
void operator<< (ostream & cout, Person& p)
{
cout<<"m_A: "<<p.m_A<<" m_B: "<<p.m_B<<endl;
}
int main(int argc, char const *argv[])
{
Person p;
p.m_A = 10;
p.m_B = 20;
cout<<p;
return 0;
}
Q1: 为什么重载函数的ostream & cout
的参数必须是引用?
因为不引用的话,相当于传值,也就是要拷贝一份cout对象;但是ostream里面的拷贝构造函数是protected的,无法拷贝。
上面的例子还不完整,需要考虑链式编程,才可以实现继续输出换行符。
ostream& operator<< (ostream & cout, Person& p)
{
cout<<"m_A: "<<p.m_A<<" m_B: "<<p.m_B<<endl;
return cout;
}
int main(int argc, char const *argv[])
{
Person p;
p.m_A = 10;
p.m_B = 20;
cout<<p<<endl;
return 0;
}
1.2.3 利用友元访问私有属性
如果类中元素是私有的,需要利用友元。
class Person
{
friend ostream& operator<< (ostream & cout, Person& p);
public:
Person(int m_A, int m_B):m_A(m_A),m_B(m_B){}
private:
int m_A;
int m_B;
};
ostream& operator<< (ostream & cout, Person& p)
{
cout<<"m_A: "<<p.m_A<<" m_B: "<<p.m_B<<endl;
return cout;
}
int main(int argc, char const *argv[])
{
Person p(10,20);
cout<<p<<endl;
return 0;
}
1.3 递增运算符重载
1.3.1 前置递增(类内实现)
class Person
{
friend ostream& operator<< (ostream& cout, Person& p);
public:
Person(int age):age(age){}
Person& operator++()
{
age = age+1;
return *this;
}
private:
int age;
};
ostream& operator<< (ostream& cout, Person& p)
{
cout<<"m_A: "<<p.age;
return cout;
}
int main(int argc, char const *argv[])
{
/* code */
Person p(10);
++p;
cout<<(++p)<<endl; //12
return 0;
}
需要先利用<<运算符重载输出类中的成员变量。
注意,前置递增中,重载函数没有参数。且返回值是当前类的引用类型。
1.3.2 后置递增(类内实现)
为了实现后置递增的特性:先进行所有运算之后再递增,返回值需要是一个新的类对象。
class Person
{
friend ostream& operator<< (ostream& cout, const Person& p);
public:
Person(int age):age(age){}
Person& operator++()
{
age = age+1;
return *this;
}
Person operator++(int)
{
//先记录当前的结果
Person temp = *this;
//后递增
age = age+1;
//返回记录的结果
return temp;
}
private:
int age;
};
ostream& operator<< (ostream& cout, const Person& p)
{
cout<<"m_A: "<<p.age;
return cout;
}
int main(int argc, char const *argv[])
{
/* code */
Person p(10);
cout<<p++<<endl; //10
cout<<p<<endl; //11
cout<<++p<<endl; //12
return 0;
}
注意,函数传参必须改成是一个const对象,因为我们返回了一个临时值。
1.4 赋值运算符重载(深拷贝浅拷贝)
可以利用赋值运算符重载解决浅拷贝的问题。
如果下面的代码不用赋值运算符重载,则会报错:double free
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
m_Age = NULL;
}
Person(int age){
m_Age = new int (age);
}
int* m_Age;
~Person()
{
if(m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
Person& operator =(Person &p)
{
if(m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
};
int main()
{
Person p1(18); //有参构造
Person p2; //无参构造
p2 = p1; //运算符重载
cout<<*p1.m_Age<<endl;
cout<<*p2.m_Age<<endl;
}
2. 前置++和后置++区别
2.1 本质:
self &operator++() {
node = (linktype)((node).next);
return *this;
}
const self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
为了区分前置和后置,重载函数以参数类型来区分。用int作为参数,编译器会给它指定一个0,说明是后置++。
2.2 常见问题
1. 为什么后置返回对象,而不是引用?
因为后置为了与内置行为一致(返回旧值)而创建了一个临时对象,在函数结束后就会被销毁,所以不能返回引用。
2. 为什么后置前面也要加const?
其实可以不加,但是为了防止使用i++++(使用两次后置++只会把结果累计加一次),所以我们手动禁止其合法化,在前面加const。
3. 最好用哪一种?
最好前置++,因为不会创建临时对象,不会造成额外开销。
3. 深拷贝和浅拷贝
3.1 区别
浅拷贝:1)简单的拷贝构造,或者2)编译器等号直接赋值
深拷贝:堆区重新申请分配空间,再进行拷贝。
3.2 浅拷贝的问题
当类中成员变量是开辟在堆区时:
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age, int height)
{
this->age = age;
this->height=new int(height);
}
~Person()
{
if(this->height != NULL)
{
delete this->height;
this->height = NULL;
}
}
int age;
int* height;
};
int main(int argc, char const *argv[])
{
/* code */
Person p1(10,170);
Person p2(p1); //or Person p3 = p1;
cout<<"p1 age is: "<<p1.age<<endl;
cout<<"p1 height is: "<<*p1.height<<endl;
cout<<"p2 age is: "<<p2.age<<endl;
cout<<"p2 height is: "<<*p2.height<<endl;
cout<<"p1 height is: "<<*p1.height<<endl;
return 0;
}
会出现double free的错误,原因如下:
由于p2先调用析构函数,堆区内容被释放;之后p1再调用析构函数,然而此时堆区内容已经被释放了,所以造成double free。
3.3 深拷贝的不同以及如何解决浅拷贝的问题
解决方法:
- 自定义拷贝构造函数
- 赋值运算符重载
3.3.1 自定义拷贝构造函数
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age, int height)
{
this->age = age;
this->height=new int(height);
}
//自定义拷贝构造
Person(Person& p)
{
this->age = p.age;
this->height = new int(*p.height); //再开辟一块内存
}
~Person()
{
if(this->height != NULL)
{
delete this->height;
this->height = NULL;
}
}
int age;
int* height;
};
int main(int argc, char const *argv[])
{
/* code */
Person p1(10,170);
Person p2(p1); //or Person p2 = p1;
cout<<"p1 age is: "<<p1.age<<endl;
cout<<"p1 height is: "<<*p1.height<<endl;
cout<<"p2 age is: "<<p2.age<<endl;
cout<<"p2 height is: "<<*p2.height<<endl;
cout<<"p1 height is: "<<*p1.height<<endl;
return 0;
}
3.3.2 赋值运算符重载
见上面代码。但要注意区分一点:
MyStr str2;
str2 = str1;
和
MyStr str3 = str2;
是不一样的。前者MyStr str2;是str2的声明加定义,调用无参构造函数。所以str2 = str1;是在str2已经存在的情况下,用str1来为str2赋值,调用的是拷贝赋值运算符重载函数;而后者,是用str2来初始化str3,调用的是拷贝构造函数。
而赋值运算符重载针对的时第一种情况。