运行算符重载:
对已有的运算符进行重新定义,赋予新的功能。关键词:operator
示例:加法运算符重载;左移运算符重载(特殊),递增运算符重载;等号运算符重载;
写在前面:加法(+)运算符重载是由于想实现在编译器中写下两个类相加而进行两个类的成员数据相加,但是编译器不能识别此操作所以重载该运算符。
#include <iostream>
using namespace std;
class Box
{
double length; // 长度
double width; // 宽度
double height; // 高度
public:
Box () {
length = 0.0;
width = 0.0;
height = 0.0;
}
Box (double a, double b ,double c)
{
length = a;
width = b;
height = c;
}
double getVolume(void)
{
return length * width * height;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;//即box.length=b1.length+b2.length;
box.width = this->width + b.width;
box.height = this->height + b.height;//上述this在main函数中指向b1;
return box;
}
};
int main(void)
{
Box b1(5.0, 4.0, 3.0);
Box b2(6.0, 5.0, 4.0);
Box b3;
cout << "Volume of b1 : " << b1.getVolume() << endl;
cout << "Volume of b2 : " << b2.getVolume() << endl;
// 把两个对象相加,得到 Box3
b3 = b1 + b2;//由于重载的+运算符重建了一个新的Box类的对象然后赋值给b3;类似于b3=Box b;
// Box3 的体积
cout << "Volume of b3 : " << b3.getVolume() << endl;
return 0;
}
左移运算符(<<)重载目的:为了在cout时实现cout<<a(某个类的对象)<<endl;时,输出a对象的成员数据
class Person {
friend ostream& operator<<(ostream & out, Person & p);
public:
Person(int a, int b) {
this->m_A = a;
this->m_B = b;
}
private:
int m_A;
int m_B;
};
ostream& operator<<(ostream& out, Person& p) {
cout << "a:" << p.m_A << "b:" << p.m_B;
return cout;
}
void test() {
Person p1(10, 20);
cout << p1 << "hello,world" << endl;
}
int main() {
test();
return 0;
}
上述重载运算符的函数中返回了一个cout涉及到了链式编程,实现了当这个重载的<<运算符用完后,还能继续输出后续内容。另外需要注意的是cout是ostream类中的函数,所以返回cout时,需要用ostream&来接收。<<一般在类外进行重载。类中难以实现。
递增(++)重载:++运算符无法直接用在类的对象上,编译器无法识别。重载++后可以实现对对象的成员数据的递增。递增分为前置和后置,两者源码稍有差别。
#include <iostream>//前置++
using namespace std;
class MyInteger
{
friend ostream & operator<<(ostream &out, MyInteger mi);
private:
int m_Number;
public:
MyInteger()
{
m_Number = 0;
}
//重载前置++运算符
MyInteger& operator++()
{
// 先进行++运算
m_Number++;
// 在将自身返回,返回一个引用
return *this;
}
};
ostream & operator<<(ostream &out, MyInteger mi)
{
out << mi.m_Number;
return out;
}
void test01()
{
MyInteger mi;
cout << ++mi << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
#include <iostream>//后置++
using namespace std;
class MyInteger
{
friend ostream & operator<<(ostream &out, MyInteger mi);
private:
int m_Number;
public:
MyInteger()
{
m_Number = 0;
}
//重载前置++运算符
MyInteger& operator++()
{
// 先进行++运算
m_Number++;
// 在将自身返回,返回一个引用
return *this;
}
//重载后置++运算符
// 参数int是占位参数,用来区分前置还是后置,固定写法
MyInteger operator++(int)
{
//先记录当时结果
MyInteger temp = *this;
//然后变量递增
m_Number++;
//最后返回当时记录的结果
return temp;
}
};
ostream & operator<<(ostream &out, MyInteger mi)
{
out << mi.m_Number;
return out;
}
void test01()
{
MyInteger mi;
cout << mi++ << endl;
cout << mi << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
代码分析:首先该源码利用了上面所学的<<运算符重载的知识,对<<进行了重载。接着在类中声明了要重载++运算符,而且前置与后置的重载分开了,并在类外分别定义。注意:1.Myinterge& operator++()与Myinterge& operator++(int)该处运用了形参占位符来告诉编译器后者是后者的++类型,2.前置++与后置的++重载返回的类型不相同,前者是Myinterge类的对象,后者返回的是一个值。3.虽然后置++返回的是一个值,所以不能一直对一个数进行操作;例如(a++)++;该操作并不能实现连续两次后置++操作。
赋值运算符重载=:目的是为了在进行拷贝构造函数时,不进行系统自带的浅拷贝,以免程序有存在的隐患,即当创建了一个Person类的对象p1,p2时进行p1=p2时,系统虽然会对两个对象进行拷贝,数据也一样,但是利用了浅拷贝,p1和p2的数据同存储在同一块堆区内存之中,当我们要进行析构函数进行delete这块堆区时,程序崩溃。
#include <iostream>
#include <string.h>
using namespace std;
class ClassA
{
public:
ClassA()
{
}
ClassA(const char* pszInputStr)
{
pszTestStr = new char[strlen(pszInputStr) + 1];
strncpy(pszTestStr, pszInputStr, strlen(pszInputStr) + 1);
}
virtual ~ClassA()
{
delete pszTestStr;
}
public:
char* pszTestStr;
};
int main()
{
ClassA obj1("liitdar");
ClassA obj2;
obj2 = obj1;
cout << "obj2.pszTestStr is: " << obj2.pszTestStr << endl;
cout << "addr(obj1.pszTestStr) is: " << &obj1.pszTestStr << endl;
cout << "addr(obj2.pszTestStr) is: " << &obj2.pszTestStr << endl;
return 0;
}
代码分析:在上述代码之中~Class这个析构函数对同一块堆区pszTestr内存进行了delete重复释放,程序奔溃了。虽然该程序实现了对对象的赋值操作,但是仍有不足,所以我们才需要重载这个赋值运算符。
首先,先分析该程序的不足,我们得出两个对象使用了同一块堆区内存导致了问题的出现,所以,我们可以在重载赋值运算符时,再创建一块堆区内存,即可避免程序报错。
实现:
#include <iostream>
#include <string.h>
using namespace std;
class ClassA
{
public:
ClassA()
{
}
ClassA(const char* pszInputStr)
{
pszTestStr = new char[strlen(pszInputStr) + 1];
strncpy(pszTestStr, pszInputStr, strlen(pszInputStr) + 1);
}
virtual ~ClassA()
{
delete pszTestStr;
}
// 赋值运算符重载函数
ClassA& operator=(const ClassA& cls)
{
// 避免自赋值
if (this != &cls)
{
// 避免内存泄露
if (pszTestStr != NULL)
{
delete pszTestStr;
pszTestStr = NULL;
}
pszTestStr = new char[strlen(cls.pszTestStr) + 1];
strncpy(pszTestStr, cls.pszTestStr, strlen(cls.pszTestStr) + 1);
}
return *this;
}
public:
char* pszTestStr;
};
int main()
{
ClassA obj1("liitdar");
ClassA obj2;
obj2 = obj1;
cout << "obj2.pszTestStr is: " << obj2.pszTestStr << endl;
cout << "addr(obj1.pszTestStr) is: " << &obj1.pszTestStr << endl;
cout << "addr(obj2.pszTestStr) is: " << &obj2.pszTestStr << endl;
return 0;
}
由于重载了赋值运算符,所以不会再用同一块内存,析构时各自释放自己的new堆区,就不会再崩溃,问题解决。
关系运算符重载:
关系运算符有==,!=等
重载==和!=关系运算符的目的:编译器无法识别if(a==b){};或if(a!=b){};(a和b都是一个类的的具体对象)编译器无法识别该语句,不能对对象的成员数据进行判断是否相等,所以就需要进行==或!=重载。
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
//定义名字和年龄
string m_Name;
int m_Age;
};
void test01()
{
//int a = 0;
//int b = 0;
Person a("Tony", 18);
Person b("Annie", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
该源码利用了bool值,来判断是否相等,ture/false。再在重载中具体判断对象的每一个成员函数是否相等后,得出两个对象是否相等,在最后才返回bool值。
函数调用运算符重载():
由于重载之后的使用非常像函数的使用,所以被称为仿函数,仿函数的写法非常灵活可以实现很多功能
#include <iostream>//后置++
using namespace std;
#include<iostream>
#include<string>
using namespace std;
class person {
public:
person() {};
int* m_A = NULL;
person(int a) {
m_A = new int(a);
}
~person() {
if (m_A != NULL) {
delete m_A;
m_A = NULL;
}
}
//()括号的重载,相当灵活
person& operator()(string str, person& p) {
//函数体里写什么都行,相当灵活
cout << str << endl << *p.m_A << endl;
return p;
}
};
//函数调用时的书写格式
void fun(string str, person& p) {
cout << str << endl << *p.m_A << endl;
}
int main() {
person p(100);
person()("aaa", p); //1.调用类的()重载函数,建立一个匿名对象来调用
person p1(10); p1("aaa", p);//2.调用类的()重载函数,建一个新对象来调用,这种调用也是最像函数调用的那个方式
fun("aaa", p);//3.调用构造函数
}
可以看出来重载后的()的功能跟fun()函数一样,都是打印“aaa"以及p的成员m_A,而且两者的形式非常类似。小结:该()可以实现不止是打印功能,还能实现许多用户自行定义的功能,只需重载()。
继承:
由于某个类含有另外一个类的特性或者说是共同点,为了避免麻烦再次输入该相同的部分,所以可以利用继承,将这两个类以父类子类的形式进行继承操作,使得子类具有父类的某种特性。
首先,要了解继承的语法:class 子类 :继承方式 父类。继承方式有三种公共继承,保护继承,私有继承。
上图便是三种继承方式的概括,第一种公共继承就是指父类的公共类和保护类都可以被子类继承,并且在子类中保持原来的类型,唯独私有类的不能继承。第二种保护继承,将父类中的公共类和保护类都以保护类的方式继承到子类之中。第三种私有继承,将父类的公共类和保护类都已私有类的方式继承到子类中。
注意:虽然三种继承方式都不能在子类中访问父类的私有类成员,但是子类依然是继承了父类的private类的成员,只不过是被隐藏了起来,导致不能访问
class father {
public:
int a;
protected:
int b;
private:
int c;
};
class son:public father
{
public:
int d;
};
int main() {
son A;
cout << sizeof(A) << endl;
}
该串代码输出的结果是16;证明这个A对象继承了父类中的所有成员,所以继承是把所有的成员都继承了下来,隐藏部分内容。
继承中构造函数和析构函数的调用顺序:我们可以通过给予编译器命令来看子父类的调用顺序:
class father {
public:
father() {
cout << "父类构造函数调用" << endl;
}
~father() {
cout << "父类析构函数调用" << endl;
}
};
class son:public father
{
public:
son() {
cout << "子类的构造函数调用" << endl;
}
~son() {
cout << "子类的析构函数调用" << endl;
}
};
int main() {
son A;
}
结果如上图所示,调用顺序为先进行父类的构造函数,接着是子类的构造函数,进行析构是,则先进行子类析构,后进行父类的析构函数调用。
继承同名成员处理方式:
访问子类同名成员,直接访问即可
访问父类同名成员时,需加作用域::
class father {
public:
father() {
a = 10;
}
int a;
};
class son:public father
{
public:
son() {
a = 100;
}
int a;
};
int main() {
son A;
cout << A.a << endl;
cout << A.father::a << endl;
}
分析上面代码可得要想访问父类和子类中同名的成员a,可以通过不同的方式分别访问。子类的访问为 对象.成员。父类的访问为 对象.父类名::成员。补充:同名的成员函数调用方式也是如此。另外,如果子类中出现了和父类同名的成员函数,子类的成员函数会隐藏掉父类中所有的同名成员函数,如果想访问到父类中的同名成员函数,需加作用域。