目录
1 运算符重载
概念:
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
注意:对于内置的数据类型的表达式的运算符是不可能改变的。
1.1 加号运算符重载
示例:
class Person
{
public:
//1. 成员函数实现+号运算符重载,本质为Person p3 = p1.operator+(p2);
Person operator+(const 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 m_A;
int m_B;
};
//2. 全局函数实现+号运算符重载,本质为Person p3 = operator+(p1, p2);
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
void test()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//两个函数选其一
Person p3 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
}
int main()
{
test();
return 0;
}
注意:运算符重载函数也可以发生函数重载
//可以实现Person p = p1 + 10;类似操作
Person operator+(Person &p1, int num)
{
Person temp;
temp.m_A = this->m_A + num;
temp.m_B = this->m_B + num;
return temp;
}
1.2 左移运算符重载
作用:
可以输出自定义的数据类型
示例:
class Person
{
friend ostream& operator<<(ostream &out, Person &p);
public:
//不会用成员函数重载<<运算符,因为无法实现cout在左侧
Person() {}
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
//全局函数实现<<运算符重载
//因为全局只能有一个cout,因为要用&;若用值传递会创建新副本。
ostream &operator<<(ostream &cout, Person &p)
{
cout << "p.m_A = " << p.m_A << " p.m_B = " << p.m_B;
return cout;
}
void test()
{
Person p(10, 10);
//链式编程
cout << p << " hello world" << endl;
}
int main()
{
test();
return 0;
}
总结:重载左移运算符配合友元可以实现输出自定义数据类型。
1.3 递增运算符重载
作用:
通过重载递增运算符,实现自己的整型数据。
示例:
class MyInteger
{
friend ostream &operator<<(ostream &cout, Person &p);
public:
MyInteger()
{
m_Num = 0;
}
//前置++运算符,返回引用是为了一直对一个数据进行递增操作
MyInteger &operator++()
{
m_Num++;
return *this;
}
/*
* 后置++运算符,返回引用是为了一直对一个数据进行递增操作
* int代表占位参数,可以用于区分前置和后置递增
* 使用值传递而不是引用传递,因为不能返回局部变量的引用
*/
MyInteger operator++(int)
{
//先 记录当前结果
MyInteger temp = *this;
//后 递增
m_Num++;
//将记录的结果返回
return temp;
}
private:
int m_Num;
}
//左移运算符重载
ostream &operator<<(ostream &cout, MyInteger &m)
{
cout << m.m_Num;
return cout;
}
void test1()
{
MyInteger m;
cout << ++(++m) << endl; //2
cout << m << endl; //2
}
void test2()
{
MyInteger m;
cout << m++ << endl; //0
cout << m << endl; //1
}
int main()
{
test1();
test2();
return 0;
}
总结:前置递增返回引用,后置递增返回值
1.4 赋值运算符重载
c++编译器至少给一个类添加4个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符Operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。
示例:
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
Person &operator=(const Person &p)
{
//先判断是否已有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int(&p.m_Age);
return *this;
}
int *m_Age;
}
void test()
{
Person p1(18);
Person p2(20);
cout << "p1的年龄为:" << *p1.m_Age << endl; //18
cout << "p2的年龄为:" << *p2.m_Age << endl; //20
//赋值操作
p2 = p1;
cout << "p1的年龄为:" << *p1.m_Age << endl; //18
cout << "p2的年龄为:" << *p2.m_Age << endl; //18
Person p3(20);
p3 = p2 = p1;
cout << "p1的年龄为:" << *p1.m_Age << endl; //18
cout << "p2的年龄为:" << *p2.m_Age << endl; //18
cout << "p3的年龄为:" << *p1.m_Age << endl; //18
}
int main()
{
test();
return 0;
}
1.5 关系运算符重载
作用:
重载关系运算符,可以让两个自定义类型对象进行对比操作
示例:
class Person
{
public:
Person(string m_Name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
bool operator==(const Person &p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
int m_Age;
string m_Name;
}
void test()
{
Person p1("张三", 18);
Person p2("李四", 18);
if (p1 == p2)
{
cout << "p1和p2是相等的" << endl;
}
else
{
cout << "p1和p2是不相等的" << endl;
}
}
int main()
{
test();
return 0;
}
1.6 函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此成为仿函数
- 仿函数没有固定写法,非常灵活
示例:
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
class MyAdd
{
public:
int operator()(int num1, int num2)
{
cout << "num1 + num2" << num1 + num2 << endl;
return num1 + num2;
}
};
void MyPrint2(string text)
{
cout << text << endl;
}
void test()
{
MyPrint MyPrint;
MyAdd myadd;
//仿函数
MyPrint("hello world");
//函数调用
MyPrint2("hello world");
//非常灵活,没有固定写法
myadd(100, 100);
//匿名函数对象
MyAdd()(100, 100);
}
int main()
{
test();
return 0;
}
2 继承
继承是面向对象三大特性之一。
有些类与类之间存在特殊的关系,例如如下图:
定义这些类时,夏季别的成员除了拥有上一级的共性,还有自己的特性。
这个时候就可以考虑利用继承的技术,减少重复代码。
2.1 继承的基本语法
语法:
class 子类: 继承方式 父类
- 子类也称为派生类
- 父类也称为基类
示例:很多网站中都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同。接下来我们分别利用普通写法和继承写法来实现网页中的内容,看一下继承存在的意义以及好处。
普通实现:
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(左侧公共列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(左侧公共列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
class Cpp
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(左侧公共列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test()
{
Java java;
Python python;
Cpp cpp;
cout << "Java下载视频页面如下:" << endl;
java.header();
java.footer();
java.left();
java.content();
cout << "Python下载视频页面如下:" << endl;
python.header();
python.footer();
python.left();
python.content();
cout << "Cpp下载视频页面如下:" << endl;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test();
return 0;
}
继承实现:
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(左侧公共列表)" << endl;
}
};
class Java:public BasePage
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
class Python:public BasePage
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
class Cpp:public BasePage
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
void test()
{
Java java;
Python python;
Cpp cpp;
cout << "Java下载视频页面如下:" << endl;
java.header();
java.footer();
java.left();
java.content();
cout << "Python下载视频页面如下:" << endl;
python.header();
python.footer();
python.left();
python.content();
cout << "Cpp下载视频页面如下:" << endl;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test();
return 0;
}
2.2 继承方式
语法:class 子类: 继承方式 父类
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
示例:公共继承
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son: public Base
{
public:
void func()
{
m_A = 10;//父类中的公共权限成员 到子类中依然是公共权限
m_B = 10;//父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10;//父类中的私有权限成员 子类访问不到
}
};
void test()
{
Son s;
s.m_A = 100;
//s.m_B = 100;//类外访问不到保护权限成员
}
int main()
{
test();
return 0;
}
示例:保护继承
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son: protected Base
{
public:
void func()
{
m_A = 10;//父类中的公共权限成员 到子类中变成了保护权限
m_B = 10;//父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10;//父类中的私有权限成员 子类访问不到
}
};
void test()
{
Son s;
//s.m_A = 100;//错误,保护权限成员类外访问不到
//s.m_B = 100;//错误,保护权限成员类外访问不到
}
int main()
{
test();
return 0;
}
示例:私有继承
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son: private Base
{
public:
void func()
{
m_A = 10;//父类中的公共权限成员 到子类中变成了私有权限
m_B = 10;//父类中的保护权限成员 到子类中变成了私有权限
//m_C = 10;//父类中的私有权限成员 子类访问不到
}
};
class GrandSon: public Son
{
public:
void func()
{
//m_A = 100;//父类中的私有权限成员
//m_B = 100;//父类中的私有权限成员
}
};
void test()
{
Son s;
//s.m_A = 100;//错误
//s.m_B = 100;//错误
}
int main()
{
test();
return 0;
}
2.3 继承中的对象模型
父类中所有非静态成员属性都会被子类继承,私有成员属性是被编译器隐藏了,因此访问不到,但是确实继承下去了。
示例:
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son: public Base
{
public:
int m_D;
};
void test()
{
cout << "sizeof Son = " << sizeof(Son) << endl; //16
}
int main()
{
test();
return 0;
}
可以通过开发人员命令提示工具查看到继承下来的私有成员属性
- 跳转盘符,如:F:
- 跳转文件路径,cd 具体路径
- 查看命令:cl /d1 reportSingleClassLayout类名 文件名
例如:
2.4 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
顺序为:
先构造父类,再构造子类;析构的顺序与构造顺序相反。
示例:
class Base
{
public:
Base()
{
cout << "Base构造函数" << endl;
}
~Base()
{
cout << "Base析构函数" << endl;
}
};
class Son: public Base
{
public:
Base()
{
cout << "Son构造函数" << endl;
}
~Base()
{
cout << "Son析构函数" << endl;
}
};
void test()
{
Son s;//Base构造->Son构造->Son析构->Base析构
}
int main()
{
test();
return 0;
}
2.5 继承同名成员处理方式
- 子类对象可以直接访问子类同名成员
- 子类对象加作用域可以访问父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
示例:
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
int m_A;
};
class Son: public Base
{
public:
Son()
{
m_A = 100;
}
void func()
{
cout << "Son - func()调用" << endl;
}
int m_A;
};
//同名成员
void test1()
{
Son s;
cout << "Son中m_A = " << s.m_A << endl;//200
cout << "Base中m_A = " << s.Base::m_A << endl;//100
}
//同名函数
void test2()
{
Son s;
s.func(); //调用子类func()
s.Base::func(); //调用父类func()
//s.func(100); //错误,因为依然调用子类,但是子类中没有这个函数
s.Base::func(100); //正确
}
int main()
{
test1();
test2();
return 0;
}
2.6 继承同名静态成员处理方式
同名静态成员处理方式和非静态处理方式一致。与6.5章节不同的是,有两种访问方式。
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
- 当子类与父类拥有同名的静态成员函数,子类会隐藏父类中同名静态成员函数,加作用域可以访问到父类中静态同名函数
示例:
class Base
{
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
static int m_A;
};
//静态成员类内声明,类外初始化
int Base::m_A = 100;
class Son: public Base
{
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名静态成员
void test1()
{
Son s;
//方式一:通过对象访问
cout << "Son中m_A = " << s.m_A << endl; //200
cout << "Base中m_A = " << s.Base::m_A << endl; //100
//方式二:通过类名访问
cout << "Son中m_A = " << Son::m_A << endl; //200
cout << "Base中m_A = " << Son::Base::m_A << endl; //100
}
//同名静态函数
void test1()
{
Son s;
//方式一:通过对象访问
s.func(); //调用子类func()
s.Base::func(); //调用父类func()
//方式二:通过类名访问
Son::func();
Son::Base::func(100);
//Son::func(100); //错误,因为依然调用子类,但是子类中没有这个函数
Son::Base::func(100); //正确
}
int main()
{
test1();
test2();
return 0;
}
2.7 多继承语法
c++允许一个类继承多个类。
语法:class 子类: 继承方式 父类1, 继承方式 父类2
- 多继承可能会引发父类中有同名成员出现,需要加作用域区分
- C++实际开发中不建议用多继承
示例:
//父类1
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
//父类2
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
//多继承
class Son:public Base1, public Base2
{
public:
Son()
{
m_B = 300;
m_C = 400;
}
int m_B;
int m_C;
};
void test()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl; //16
//当父类中出现同名成员,子类使用时需要加作用域区分
cout << "Base1 m_A = " << s.Base1::m_A << endl; //100
cout << "Base2 m_A = " << s.Base2::m_A << endl; //200
}
int main()
{
test();
return 0;
}
2.8 菱形继承
菱形继承概念:
- 两个子类继承同一个父类
- 又有某个类同时继承两个子类
- 这种继承被称为菱形继承,或者钻石继承
图例:
菱形继承问题:
羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。羊驼继承自动物的数据有两份,但其实我们知道这份数据我们只需要一份。
示例:
//动物类
class Animal
{
public:
int m_Age;
};
//羊类
class Sheep: public Animal {};
//驼类
class Tuo: public Animal {};
//羊驼类
class SheepTuo: public Sheep, public Tuo {};
void test()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//当菱形继承时,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; //18
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl; //28
}
int main()
{
test();
return 0;
}
解决:
//动物类
class Animal
{
public:
int m_Age;
};
//羊类:
//利用虚继承(关键字virtual)可以解决菱形继承的问题
class Sheep: virtual public Animal {};
//驼类:
//利用虚继承(关键字virtual)可以解决菱形继承的问题
class Tuo: virtual public Animal {};
//羊驼类
class SheepTuo: public Sheep, public Tuo {};
void test()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;//28
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl; //28
cout << "st.m_Age = " << st.m_Age << endl; //28
}
int main()
{
test();
return 0;
}
vbptr(virtual base pointer):虚基类指针,指向vbtable
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题
3 多态
多态是C++面向对象三大特性之一。
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
3.1 多态的基本概念
多态分为两类:
- 静态多态:函数重载和运算符重载都属于静态多态,复用函数名
- 动态多态:子类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定——编译阶段确定函数地址
- 动态多态的函数地址晚绑定——运行阶段确定函数地址
示例:
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
void speak()
{
cout << "小猫在说话" << endl;
}
};
//如果想让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定(晚绑定)
void doSpeak(Animal &animal)
{
animal.speak();
}
void test()
{
Cat cat;
//父类的引用指向子类的对象 is ok,因为c++允许父子之间的类型转换
doSpeak(cat); //输出动物在说话,因为这是地址早绑定,在编译阶段确定函数地址
}
int main()
{
test();
return 0;
}
解决后代码:
class Animal
{
public:
//虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
void speak()
{
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test()
{
Cat cat;
doSpeak(cat); //输出小猫在说话
}
int main()
{
test();
return 0;
}
动态多态满足条件:
- 有继承关系
- 子类重写父类的虚函数(函数返回值类型、函数名、参数列表完全相同)
动态多态使用:
父类的指针或引用指向子类对象,即示例中doSpeak()
深度剖析多态示例:
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
void speak()
{
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test1()
{
Cat cat;
doSpeak(cat);
}
void test2()
{
/*
* 原本为1个字节,因为class Animal相当于空类,只占1字节
* 非静态成员函数分开存放
*
* 在Animal.speak()前面加上virtual后为4字节
* 因为是一个指针,vfptr(virtual function pointer)指向vftable
* 可见下方示例图(1)(2)
*/
cout << "sizeof Animal = " << sizeof(Animal) << endl;
}
int main()
{
test1();
test2();
return 0;
}
多态前: 多态后:
3.2 多态案例一——计算器类
分别利用普通写法和多态技术实现计算器,实现两个操作数进行运算的计算器类。
普通写法示例:
class Calculator
{
public:
int getResult(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if (oper == "-")
{
return m_Num1 - m_Num2;
}
else if (oper == "*")
{
return m_Num1 * m_Num2;
}
//如果想扩展新功能,需要修改源码
//在真实开发中,提倡开闭原则
//即,对扩展进行开放,对修改进行关闭
}
int m_Num1; //操作数1
int m_Num2; //操作数2
};
void test()
{
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m+Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << "-" << c.m+Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << "*" << c.m+Num2 << " = " << c.getResult("*") << endl;
}
int main()
{
test();
return 0;
}
多态技术示例: c++开发提倡多用多态技术!
//实现计算器抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1; //操作数1
int m_Num2; //操作数2
};
//加法计算器类
class AddCalculator: public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器类
class SubCalculator: public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器类
class MulCalculator: public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test()
{
//多态使用条件:父类指针或者引用指向子类对象
AbstractCalculator *abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
AbstractCalculator *abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
AbstractCalculator *abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
}
int main()
{
test();
return 0;
}
3.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名{参数列表} = 0;
当类中有了纯虚函数,这个类也成为抽象类。
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
示例:
class Base
{
public:
/*
* func()为纯虚函数,只要有一个纯虚函数,这个类就称为抽象类
* Base为抽象类
* 抽象类特点:1. 无法实例化对象
* 2. 子类必须重写父类的虚函数
*/
virtual void func() = 0;//在虚函数的基础上才能赋值为0!!
};
class Son1: public Base
{
public:
};
class Son2: public Base
{
public:
virtual void func() {};
};
class Son3: public Base
{
public:
virtual void func()
{
cout << "func函数调用" << endl;
}
};
void test()
{
//失败,因为子类Son1没重写纯虚函数
//Son1 s1;
//成功,因为重写了,哪怕一排代码都没写......
Son2 s2;
Base *base = new Son3;
base->func();
delete base;
}
int main()
{
test();
return 0;
}
3.4 多态案例二——制作饮品
制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中- 加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶。
示例:
class AbstractDrink
{
public:
//煮水
virtual void boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink()
{
boil();
Brew();
PourInCup();
PutSomething();
}
};
class Coffee: public AbstractDrink
{
public:
virtual void boil()
{
cout << "煮纯净水" << endl;
}
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
virtual void PourInCup()
{
cout << "倒入马克杯中" << endl;
}
virtual void PutSomething()
{
cout << "加入糖和牛奶" << endl;
}
};
class Tea: public AbstractDrink
{
public:
virtual void boil()
{
cout << "煮矿泉水" << endl;
}
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
virtual void PourInCup()
{
cout << "倒入茶杯中" << endl;
}
virtual void PutSomething()
{
cout << "加入枸杞" << endl;
}
};
void doWork(AbstractDrink *a)
{
a->makeDrink();
delete a;//释放
}
void test()
{
doWork(new Coffee);
cout << "--------------" << endl;
doWork(new Tea);
}
int main()
{
test();
return 0;
}
3.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
析构类语法:
virtual ~类名() {}
纯虚析构类语法:
virtual ~类名() = 0;
类名::~类名() {}
问题代码示例:父类指针释放子类对象时不干净
#include<string>
class Animal
{
public:
//纯虚函数
virtual void speak() = 0;
Animal()
{
cout << "Animal构造函数调用" << endl;
}
~Animal()
{
cout << "Animal析构函数调用" << endl;
}
};
class Cat: public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
if (m_Name!= NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test()
{
Animal *animal = new Cat;
animal->speak();
/*
* 不会调用Cat析构函数,因为:
* 父类指针在析构时候,不会调用子类中析构函数
* 导致如果子类有堆区属性,会造成堆区泄露情况
*/
delete animal;
}
int main()
{
test();
return 0;
}
解决代码示例一:虚析构
#include<string>
class Animal
{
public:
//纯虚函数
virtual void speak() = 0;
Animal()
{
cout << "Animal构造函数调用" << endl;
}
//这样可以解决父类指针释放子类对象时不干净的问题
virtual ~Animal()
{
cout << "Animal析构函数调用" << endl;
}
};
class Cat: public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
if (m_Name!= NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test()
{
Animal *animal = new Cat;
animal->speak();
delete animal;//正常调用Cat析构函数
}
int main(
{
test();
return 0;
}
解决代码示例二:纯虚析构
#include<string>
class Animal
{
public:
//纯虚函数
virtual void speak() = 0;
Animal()
{
cout << "Animal构造函数调用" << endl;
}
//纯虚析构函数也可以解决父类指针释放子类对象时不干净的问题
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
};
//必须要有具体的函数实现
Animal::~Animal()
{
cout << "Animal纯虚析构函数调用" << endl;
}
class Cat: public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
if (m_Name!= NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test()
{
Animal *animal = new Cat;
animal->speak();
delete animal;//也可以正常调用Cat析构函数
}
int main(
{
test();
return 0;
}
3.6 多态案例三——电脑组装
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)。
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商。
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三台不同的电脑进行工作。
示例:
//抽象的CPU类
class CPU
{
public:
virtual void calculate() = 0;
};
//抽象的显卡类
class VideoCard
{
public:
virtual void display() = 0;
};
//抽象的内存类
class Memory
{
public:
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
//构造函数中传入三个零件指针
Computer(CPU *cpu, VideoCard *vc, Memory *mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
~Computer(+)
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
//提供让电脑工作的函数,调用每个零件工作的接口
void work()
{
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
private:
CPU *m_cpu;
VideoCard *m_vc;
Memory *m_mem;
};
//具体零件厂商:Intel
class IntelCPU: public CPU
{
public:
void calculate()
{
cout << "Intel CPU开始工作了" << endl;
}
};
class IntelVideoCard: public VideoCard
{
public:
void display()
{
cout << "Intel 显卡开始工作了" << endl;
}
};
class IntelMemory: public Memory
{
public:
void storage()
{
cout << "Intel 内存条开始工作了" << endl;
}
};
//具体零件厂商:Lenovo
class LenovoCPU: public CPU
{
public:
void calculate()
{
cout << "Lenovo CPU开始工作了" << endl;
}
};
class LenovoVideoCard: public VideoCard
{
public:
void display()
{
cout << "Lenovo 显卡开始工作了" << endl;
}
};
class LenovoMemory: public Memory
{
public:
void storage()
{
cout << "Lenovo 内存条开始工作了" << endl;
}
};
void test()
{
//第一台电脑零件
cout << "第一台电脑开始工作:" << endl;
CPU *intelCPU = new IntelCPU;
VideoCard *intelVideoCard = new IntelVideoCard;
Memory *intelMem = new IntelMemory;
Computer *computer1 = new Computer(intelCPU, intelVideoCard, intelMem);
computer1->work();
delete computer1;
//第二台电脑零件
cout << "--------------------" << endl;
cout << "第二台电脑开始工作:" << endl;
CPU *lenovoCPU = new LenovoCPU;
VideoCard *lenovoVideoCard = new LenovoVideoCard;
Memory *lenovoMem = new LenovoMemory;
Computer *computer2 = new Computer(lenovoCPU, lenovoVideoCard, lenovoMem);
computer2->work();
delete computer2;
//第三台电脑零件
cout << "--------------------" << endl;
cout << "第三台电脑开始工作:" << endl;
CPU *lenovoCPU = new LenovoCPU;
VideoCard *intelVideoCard = new LenovoVideoCard;
Memory *lenovoMem = new LenovoMemory;
Computer *computer3 = new Computer(lenovoCPU, intelVideoCard, lenovoMem);
computer3->work();
delete computer3;
}
int main()
{
test();
return 0;
}