5 运算符重载
对已有运算符重新进行定义,赋予另一种功能,以适应不同的数据类型。
运算符重载也可以进行函数重载。
5.1 + 运算符重载
class Person
{
public:
int m_A;
int m_B;
//1.通过成员函数重载+号
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;
}
};
//2.也可以通过全局函数重载+号
Person operator+ (Person& p1 , Person& p2)
{
Person temp;
temp.m_A = p1.m_B + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
int main()
{
Person p1, p2;
p1.m_A = 10;
p1.m_B = 10;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2; // 想要创建一个p3,对应属性相加,直接这样写肯定报错
cout << p3.m_A << endl << p3.m_B;
}
5.2 < <运算符重载
//左移运算符一般只使用全局函数重载
ostream & operator<< (ostream &cout, Person &p)
{
cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
return cout;
}
int main()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
cout << p1 ; //想要输出p1中的所有属性,直接这样写肯定报错
cout << "hello" << endl;//当不满足重载条件时,充当其原来的角色。可以用函数参数重载来理解。
}
5.3 ++运算符重载
class MyInt
{
friend ostream& operator<< (ostream& cout, MyInt myint);
public:
MyInt()
{
m_num = 0;
}
//重载前置++
MyInt& operator++() //返回引用是为了一直对一个数据进行递增操作
{
m_num++;
return *this;
}
//重载后置++
MyInt operator++(int)//int代表占位参数,用于区分前置后置。后置要返回值而不是引用
{
//先 记录当时结果
MyInt temp = *this;
//后 递增
m_num++;
//最后将记录的结果返回
return temp;
}
private:
int m_num;
};
//为了输出,首先要重载左移运算符
ostream& operator<< (ostream& cout, MyInt myint)//注意这里不是引用
{
cout << myint.m_num;
return cout;
}
int main()
{
MyInt myint;
cout << myint++ << myint; //结果:01
}
5.4 赋值运算符重载
class Person
{
public:
Person(int age)
{
m_age = new int(age);
}
~Person()
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
}
//重载 赋值运算符
Person& operator= (Person& p)
{
//编译器提供的是浅拷贝
//m_age = p.m_age;
//由于p2本身在堆区有属性,应该先释放再深拷贝
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age);//深拷贝
return *this; //返回自身
}
int* m_age;
};
int main()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1;
cout << *p1.m_age << *p2.m_age << *p3.m_age << endl;
}
5.5 关系运算符重载
class Person
{
public:
Person(string name, int age)
{
m_age = age;
m_name = name;
}
bool operator==(Person &p)
{
if (this->m_name == p.m_name && this->m_age == p.m_age)
{
return true;
}
return false;
}
string m_name;
int m_age;
};
int main()
{
Person p1("Leo", 18);
Person p2("Tim", 20);
if (p1 == p2)
{
cout << "相等" << endl;
}
else
{
cout << "不相等" << endl;
}
}
5.6 函数调用运算符重载(仿函数)
仿函数实质上是对()的重载。该用法在STL中会用到很多。
class MyPrint
{
public:
void operator()(string test)//重载小括号
{
cout << test << endl;
}
};
int main()
{
MyPrint mp;
mp("Hello World"); //由于使用起来很像函数调用,因此叫仿函数
}
6 继承
继承是面向对象三大特性之一。(封装继承多态)
6.1 继承的基本语法
作用:减少重复代码。
class 子类 : 继承方式 父类
子类也称为派生类
父类也称为基类
class 子类 : 继承方式 父类
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
};
class JavaPage : public BasePage
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
class Cpp : public BasePage
{
public:
void content()
{
cout << "Cpp学科视频" << endl;
}
};
6.2 继承方式
继承方式:public, protected, private。
父类的private内容,子类无论如何也无法访问。
6.3 继承中的构造和析构顺序
构造父类->构造子类->析构子类->析构父类
6.4 继承同名成员处理方式
//Son继承了Base,二者都有m_A属性时
Son s;
//同名属性
cout << "Son下的m_A: " << s.m_A << endl;//子类属性可以直接调用
cout << "Base下的m_A: " << s.Base::m_A << endl;//父类属性要加作用域
//同名函数
cout << "Son下的func(): " << s.func() << endl;//子类属性可以直接调用
cout << "Base下的func(): " << s.Base::func() << endl;//父类属性要加作用域
6.5 继承同名静态成员处理方式
class Base
{
public:
static int m_A;
static void func(){...};
};
int Base::m_A = 100; //静态成员类内声明,类外初始化
class Son : public Base
{
public:
static int m_A;
static void func(){...};
};
int Son::m_A = 200;
int main()
{ //1.通过对象访问
Son s;
cout << s.m_A;// 输出200
cout << s.Base::m_A;// 输出100
s.func();
s.Base::func();
//2.通过类名访问(所有对象使用同一个静态成员,可以通过类名访问)
cout << Son::m_A;//输出200
cout << Base::m_A;//输出100
cout << Son::Base::m_A;//效果和上一行一样
Son::func();
Son::Base::func();
}
6.6 多继承语法
class 子类 : 继承方式 父类1, 继承方式 父类2, 继承方式 父类3,…
一个儿子认多个爹
实际开发中不建议使用
6.7 菱形继承/钻石继承
两个子类继承同一个父类,又有一个类同时继承两个子类
假设动物、羊、驼、羊驼都有属性age,有以下两个问题。
问题1:调用羊驼的age时,会有二义性,不知道age继承自羊还是驼。
问题2:羊驼继承时,只需要取一个age就足够,取两个多余了。
解决1:添加作用域
解决2:利用虚继承,在继承钱加入关键词virtual
class Animal{...};
class Sheep:virtual public Animal{};
class Camel:virtual public Animal{};
class CNM:public Sheep, public Camel{};
//加入虚继承后,Sheep::age和Camel::age就用同一个内存空间了。
6 多态
6.1 多态基本语法
分类:
静态多态:函数重载、运算符重载
动态多态:派生类和虚函数实现运行时多态
区别:
静态多态的函数地址早绑定,在编译阶段确定
动态多态的函数地址晚绑定,在运行阶段确定
class Animal
{
public:
void speak()
{
cout << "动物在叫唤" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "猫叫" << endl;
}
};
void doSpeak(Animal& animal)//地址早绑定 编译阶段确定
{
animal.speak();
}
int main()
{
Cat cat;
doSpeak(cat); //由于地址早绑定,运行结果是Animal类中的函数
//如果想执行Cat类中的函数,需要在运行阶段绑定地址
}
在上述代码段中,执行doSpeak(cat)会运行Animal类中的函数,因为函数地址在编译时就决定了。要想执行Cat类中的函数,需要在运行阶段绑定地址。只需要将Animal类的代码改为:
class Animal
{
public:
virtual void speak()
{
cout << "动物在叫唤" << endl;
}
};
这样各类中的speak会共用一个地址空间。
动态多态条件:
- 有继承关系
- 子类要重写父类的虚函数
动态多态的调用:
父类的引用指向子类对象。在上述例程中为:
void doSpeak(Animal& animal){...};
doSpeak(cat);
6.1.1 计算器案例
该例子可以体现多态的优点:易读、易维护。
//实现一个抽象类,这样便于将功能一步一步地添加进去
class AbsCalculator
{
public:
int m_num1;
int m_num2;
virtual int getRes()
{
return 0;
}
};
class AddCal : public AbsCalculator
{
public:
virtual int getRes() // 这里的virtual可加可不加
{
return m_num1 + m_num2;
}
};
class SubCal : public AbsCalculator
{
public:
virtual int getRes()
{
return m_num1 - m_num2;
}
};
class mulCal : public AbsCalculator
{
public:
virtual int getRes()
{
return m_num1 * m_num2;
}
};
class divCal : public AbsCalculator
{
public:
virtual int getRes()
{
return m_num1 / m_num2;
}
};
int main()
{
//多态的使用条件:父类指针或引用指向子类对象
//此处使用指针
AbsCalculator* abc = new AddCal;
abc->m_num1 = 10;
abc->m_num2 = 10;
cout << abc->getRes() << endl;
//用完后记得释放堆区数据
delete abc;
}
6.2 纯虚函数和抽象类
上面各例程中,父类的虚函数没有作用,要调用的都是子类中被重写了的同名函数。因此可以改写为纯虚函数:
virtual 返回类型 函数名( 参数列表)=0;
当类中有纯虚函数,这个类被称为抽象类。
特点:子类必须重写抽象类中的纯虚函数,否则也属于抽象类,无法实例化对象。
6.2.1 做饮料例子
//实现一个抽象类,这样便于将功能一步一步地添加进去
class AbsDrink
{
public:
void boil()
{
cout << "煮水" << endl;
}
virtual void brew() = 0;
virtual void pour()
{
cout << "装杯" << endl;
}
virtual void fill() = 0;
void makeDrink()
{
boil();
brew();
pour();
fill();
}
};
class Coffe : public AbsDrink
{
public:
void brew()
{
cout << "放入咖啡粉" << endl;
}
void fill()
{
cout << "加奶" << endl;
}
};
class Tea : public AbsDrink
{
public:
void brew()
{
cout << "放入茶叶" << endl;
}
void fill()
{
cout << "加柠檬" << endl;
}
};
void doDrink(AbsDrink* abs)
{
abs->makeDrink();
delete abs;
}
int main()
{
doDrink(new Coffe);
doDrink(new Tea);
}
6.3 虚析构和纯虚析构
多态使用时,如果子类有属性开辟到堆区,父类指针释放时无法调用子类析构代码。
解决方法:将父类中的析构函数改为虚析构或纯虚析构。
虚析构和纯虚析构的区别在于是否抽象。
~virtual 类名()