c++(六)
- 多态
- 概念
- 在c++中是如何实现多态
- 静态多态(绑定)
- 动态多态(绑定)
- 动态多态的实现原理
- 动态内存分配中遇到的问题
- 重载、重定义、重写的区别
- 抽象类
- 接口类---抽象类
- 空类对象的内存大小
- explicit
- final
- 修饰类
- 修饰成员函数
多态
概念
多种状态(一个事物的多种状态或形态)
<1>水在不同的温度下呈现的不同状态
<2>买票
学生票;
成人票;
老年票;
军人;
在c++中是如何实现多态
绑定:就是把函数调用语句与对应的代码块进行了绑定!。
静态多态(绑定)
在编译的时候,就知道执行的是哪一个函数体
调用函数时,函数名是一样的,当我们传入不同参数的时候,执行的是不同的函数体(函数重载、运算符重载)
动态多态(绑定)
在运行的时候,才知道执行的是哪一个函数体
案例:如果要求两个子类的周长和面积之和,就得写两个函数来实现,传入不同的对象
#include <iostream>
using namespace std;
class Shape
{
public:Shape(int s = 0, int c = 0) :s(s), c(c){cout << "Shape构造" << endl;}~Shape(){cout << "Shape析构" << endl;}int getC(){cout << "周长:" << this->c << endl;return this->c;}int getS(){cout << "面积:" << this->s << endl;return this->s;}
private:
protected:int s;int c;
};
class Rect:public Shape
{
public:Rect(int a = 0, int b = 0) :a(a), b(b){cout << "Rect构造" << endl;}~Rect(){cout << "Rect析构" << endl;}int getC(){this->c = (this->a + this->b) * 2;return this->c;}int getS(){this->s = this->a * this->b;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int a;int b;
};
class Circle:public Shape
{
public:Circle(int r = 0) :r(r){cout << "Circle构造" << endl;}~Circle(){cout << "Circle析构" << endl;}int getC(){this->c = 2 * 3.14 * this->r;return this->c;}int getS(){this->s = 3.14 * this->r * this->r;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int r;
};
void Rcs(Rect & demo)
{cout << demo.getC()+ demo.getS()<< endl;
}
void Ccs(Circle& demo)
{cout << demo.getC() + demo.getS() << endl;
}
int main()
{//矩形对象Rect rect(2,3);Rcs(rect);//圆形对象Circle circle(3);Ccs(circle);}
解决:用父类统一管理子类
问题:通过父类的引用操作子类的对象时,并没有执行子类的函数,执行的是子类从父类继承过来的函数
解决:目的:执行子类重定义的函数,将父类对应的函数写成虚函数
虚函数的格式:
virtual 返回值类型 函数名(参数列表){}
未加victual之前,不能访问子类重定义函数的原因:
将子类对象强制赋值给父类的引用,父类的引用所访问的范围小,访问不到子类的函数
#include <iostream>
using namespace std;
class Shape
{
public:Shape(int s = 0, int c = 0) :s(s), c(c){cout << "Shape构造" << endl;}~Shape(){cout << "Shape析构" << endl;}virtual int getC(){cout << "周长:" << this->c << endl;return this->c;}virtual int getS(){cout << "面积:" << this->s << endl;return this->s;}
private:
protected:int s;int c;
};
class Rect:public Shape
{
public:Rect(int a = 0, int b = 0) :a(a), b(b){cout << "Rect构造" << endl;}~Rect(){cout << "Rect析构" << endl;}int getC(){this->c = (this->a + this->b) * 2;return this->c;}int getS(){this->s = this->a * this->b;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int a;int b;
};
class Circle:public Shape
{
public:Circle(int r = 0) :r(r){cout << "Circle构造" << endl;}~Circle(){cout << "Circle析构" << endl;}int getC(){this->c = 2 * 3.14 * this->r;return this->c;}int getS(){this->s = 3.14 * this->r * this->r;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int r;
};
void Scs(Shape & demo)
{cout << demo.getC()+ demo.getS()<< endl;
}
int main()
{//矩形对象Rect rect(2,3);Scs(rect);//圆形对象Circle circle(3);Scs(circle);}
动态多态的实现原理
有虚函数一定能实现动态多态吗?不一定
多态的实现:
<1>一个父类,有多个子类
<2>父类中有虚函数,子类重写父类的虚函数
<3>用父类对象的指针或者引用去操作子类的对象并调用虚函数的受,才会触发动态多态(决定性因素)
将父类的对应的函数写成虚函数,在子类中进行重写,通过父类对象的指针/引用去操作子类对象就可以调用到子类的函数了
指针所能访问的空间的大小:由指针指向的数据类型大小决定
int *p; //4字节
shape *p;// 8个字节
为什么添加virtual就可以访问子类的函数?
虚函数表:保存虚函数的地址(函数指针数组)
什么时候有虚函数表?
一个类中一旦有了虚函数,那么这个类中就有了一个虚函数表,保存这个类中所有虚函数的地址,如果这个类被子类继承了,子类中也会有一张虚函数表
父类的虚函数表和子类的虚函数表一样吗?
<1>如果子类不重写父类的虚函数,那么父类和子类的虚函数表是一样的
<2>如果子类重写了父类的虚函数,那么子类虚函数表中对应的就是子类重写之后的虚函数地址
- 父类有虚函数,那么被子类继承之后,子类中对应的函数也是虚函数!子类中对应函数的virtual关键字可加可不加!
- 父类中有函数是虚函数,如果子类有重定义的话,此时被称为重写(覆盖–就是子类中的函数把从父类中继承来的给覆盖了。子类中有且只有一个这样子的函数!!!)!!!!
- 重写就必须保证 子类中的函数首部与父类中函数的首部是一模一样的,首部指的是:函数类型 函数名(参数列表)。
动态内存分配中遇到的问题
问题:把new出来的子类对象,赋值给了父对象类型的指针,在整个操作过程中,都是通过父类的指针来操作,使用delete去释放空间的时候,只执行了父类的析构函数,并没有子类的析构函数,可能会造成内存泄漏的问题(在子类的构造函数去new空间了)
解决:目标:执行子类的析构函数
将父类的析构函数写成虚函数,子类的析构自然也是虚函数!
#include <iostream>
using namespace std;
class Shape
{
public:Shape(int s = 0, int c = 0) :s(s), c(c){cout << "Shape构造" << endl;}virtual ~Shape(){cout << "Shape析构" << endl;}virtual int getC(){cout << "周长:" << this->c << endl;return this->c;}virtual int getS(){cout << "面积:" << this->s << endl;return this->s;}
private:
protected:int s;int c;
};
class Rect :public Shape
{
public:Rect(int a = 0, int b = 0) :a(a), b(b){cout << "Rect构造" << endl;}~Rect(){cout << "Rect析构" << endl;}int getC(){this->c = (this->a + this->b) * 2;return this->c;}int getS(){this->s = this->a * this->b;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int a;int b;
};
class Circle :public Shape
{
public:Circle(int r = 0) :r(r){cout << "Circle构造" << endl;}~Circle(){cout << "Circle析构" << endl;}int getC(){this->c = 2 * 3.14 * this->r;return this->c;}int getS(){this->s = 3.14 * this->r * this->r;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int r;
};
void Scs(Shape* demo)
{cout << demo->getC() + demo->getS() << endl;
}
int main()
{Shape* p = new Circle(4);Scs(p);delete p;}
<1>在设计类的时候,为什么建议把析构函数写成虚函数
防止内存泄漏
<2>为什么不执行子类的析构函数就可能会存在内存泄漏的问题
在子类的构造函数去new空间了
重载、重定义、重写的区别
重载:同一个作用域内,函数功能相似,函数名相同、参数不同,与返回值无关的一组函数
重定义:在继承关系中,子类重定义父类的函数,函数名相同即可
重写(覆盖):在继承关系中,子类重写父类的虚函数
备注:函数首部必须一样
首部:返回值类型 函数名(形式参数列表)
抽象类
在设计类的时候,发现这个类确实需要这样子的一个操作函数,但是在父类中不知道该怎么实现,它的所有的子类都要实现这个函数,并且所有的子类实现这个函数的效果是不一样的,这种情况下,就可以把这个函数写成纯虚函数
virtual 返回值类型 函数名(形参列表) = 0;
注意:抽象类是不能创建对象的
作用:就是用来被继承的,在子类中去实现这个纯虚函数(每一个子类都要去实现这个纯虚函数)
如果子类没有实现这个纯虚函数,子类也变成了抽象类
#include <iostream>
using namespace std;
class Shape //抽象类
{
public:Shape(int s = 0, int c = 0) :s(s), c(c){cout << "Shape构造" << endl;}virtual ~Shape(){cout << "Shape析构" << endl;}virtual int getC() = 0;//纯虚函数virtual int getS() = 0;private:
protected:int s;int c;
};
class Rect :public Shape
{
public:Rect(int a = 0, int b = 0) :a(a), b(b){cout << "Rect构造" << endl;}~Rect(){cout << "Rect析构" << endl;}int getC(){this->c = (this->a + this->b) * 2;return this->c;}int getS(){this->s = this->a * this->b;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int a;int b;
};
class Circle :public Shape
{
public:Circle(int r = 0) :r(r){cout << "Circle构造" << endl;}~Circle(){cout << "Circle析构" << endl;}int getC(){this->c = 2 * 3.14 * this->r;return this->c;}int getS(){this->s = 3.14 * this->r * this->r;return this->s;}void show(){cout << "周长:" << this->c << ",面积:" << this->s << endl;}
private:
protected:int r;
};
void Scs(Shape* demo)
{cout << demo->getC() + demo->getS() << endl;
}
int main()
{Shape* p = new Circle(4);Scs(p);delete p;}
接口类—抽象类
接口类其实就是抽象类的应用
当抽象类不能实例化对象时,抽象类中的成员变量也就不能初始化,其构造函数也就不能被执行
接口类:类中只有成员函数,没有数据成员,并且成员函数必须是纯虚函数
作用:就是用来被继承的,描述一些能力、协议
#include <iostream>
using namespace std;
//接口类
class fly_land
{
public:virtual void fly() = 0;//纯虚函数virtual void land() = 0;
};
class Bird :public fly_land
{
public:void fly(){cout << "bird fly ....." << endl;}void land(){cout << "bird land....." << endl;}
};
class Plane :public fly_land
{
public:void fly(){cout << "plane fly....." << endl;}void land(){cout << "plane land....." << endl;}
};
void dosomething(fly_land& demo)
{demo.fly();
}
int main()
{Bird bird;dosomething(bird);Plane plane;dosomething(plane);
}
空类对象的内存大小
默认构造函数、析构函数、默认拷贝构造函数、赋值运算符函数、取值运算符函数 const 修饰的取值运算符
空类占内存大小:1字节
explicit
作用:修饰构造函数的,对应的构造函数被称为 转换构造函数!!!!
意义:防止构造函数单参数的时候进行自动类型的转换
final
作用:修饰类和类中的成员函数
修饰类
作用:不能被继承
修饰成员函数
作用:无法重写