c++第8章·多态性

c++第8章·多态性,第1张

多态的实现 

确定具体 *** 作对象的过程是绑定

静态绑定

编译连接阶段完成的情况

确定某一个同名标识到底是要调用哪一段代码

有些多态类型,其同名 *** 作的具体对象能够再编译、连接阶段确定,通过静态绑定解决,比如重载、强制、参数多态

在类的构造函数中,调用虚函数,是静态绑定,不会发生动态绑定,用对象本身调用虚函数,也是静态绑定。必须由指针或者引用调用虚函数,才会发生动态绑定

动态绑定

绑定构造在程序运行阶段完成的情况

包含多态 *** 作对象的确定就是通过动态绑定完成的

运算符重载

对已有的运算符赋予多重含义,使同一个运算符作用域不同类型的数据时导致不同的行为

规则

形式

文字中的非成员函数指一个普通的函数的形式(即不为类内部的函数) 

例子

头部要写上返回的类型,同函数一样

并不是说非要用引用&,但是用这个比较好

程序实际重载为的形式

单目运算符
#include
class Clock
{
	public:
		Clock(int h=0, int m=0, int s=0):  h(h), m(m), s(s)  {}
		void Show()  {  printf("%02d:%02d:%02d\n", h, m, s);  }
		Clock operator ++ ()		//前置"++"重载
		{
			s++;
			if(s==60)  m++, s = 0;
			if(m==60)  h++, m = 0;
			if(h==24)  h = 0;
			return *this;
		}
		Clock operator ++ (int)		//后置"++"重载
		{
			Clock old = *this;
			++(*this);
			return old;
		}
	private:
		int h, m, s;
};
int main(void)
{
	Clock c(23, 59, 58);
	(++c).Show();
	(c++).Show();
	(++c).Show();
	c.Show();
	return 0;
}
/*
23:59:59
23:59:59
00:00:01
00:00:01
*/

后置运算符中的形参int不起任何作用,只是用来区分的

class Complex{
private:
    float _x;
    float _y;
public:
    Complex(float x = 0, float y = 0):_x(x), _y(y){}
    void dis(){
        cout << "( " << _x << "," << _y << " )" << endl;
    }
    Complex operator-(){
        Complex t;
        t._x = -this->_x;
        t._y = -this->_y;
        return t;

    }
};

int main()
{
    int a = 10;
    cout << -a << endl;
    cout << -(-a) << endl;

    cout << "------------------" << endl;

    Complex c(1, 1);
    Complex t(2, 2);
    (-c).dis();
    (-(-c)).dis();
    return 0;
}

一个例子,加深理解

运算符重载为非成员函数 定义为友元函数

区别没有很大,就是在类中加了friend声明,然后形参数不能省去

程序实际重载为的形式 

好处:

1.和普通函数重载相比,它能够访问非公有成员

2.将双目运算符重载为友元函数,这样就可以使用交换律

弊端:

友元可以像类成员一样访问类的成员和函数,但是使用不慎会造成破坏类的封装性

声明为非友元函数的普通函数就没什么好说的了

重载输入输出流"<<",">>"
 1 #include 
 2 #include 
 3 
 4 using namespace std;
 5 
 6 class A {
 7 private:
 8     int a;
 9     int b;
10 public:
11     A(int x = 0, int y = 0):a(x), b(y){}
12     friend A operator+ (A &C, A &D);
13     friend ostream& operator<<(ostream & out, A &W);
14 };
15 
16 A operator+ (A &C, A &D) {
17     A G;
18     G.a = D.a + C.b;
19 
20     return G;
21 }
22 
23 ostream& operator<<(ostream & out, A &W) {
24     out << W.a << " " << W.b;
25 
26     return out;
27 }
28 
29 int main()
30 {
31     A G(5, 6);
32     A J(7, 8);
33     A K;
34     K = G + J;
35 
36     cout << K << " " << J;
37 
38     return 0;
39 }

但是这类运算符无法重载为成员函数

提示

虚函数

虚函数必须是非静态的成员函数,动态绑定

如果需要通过基类的指针指向派生类的对象,并访问某个与基类同名的成员,那么首先在基类中将这个同名函数说明为虚函数

只有拥有虚函数的类才会拥有虚函数指针,每一个虚函数也都会对应一个虚函数指针。所以拥有虚函数的类的所有对象都会因为虚函数产生额外的开销

虚函数的声明与定义要求非常严格,只有在子函数中的虚函数与父函数一模一样的时候(包括限定符)才会被认为是真正的虚函数,不然的话就只能是重载。这被称为虚函数定义的同名覆盖原则,意思是只有名称完全一样时才能完成虚函数的定义

基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性

只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数

作用 一

定义子类对象,并调用对象中未被子类覆盖的基类函数A。同时在该函数A中,又调用了已被子类覆盖的基类函数B。那此时将会调用基类中的函数B,可我们本应该调用的是子类中的覆盖函数B。虚函数即能解决这个问题

在使用指向子类对象的基类指针,并调用子类中的覆盖函数时,如果该函数不是虚函数,那么将调用基类中的该函数;如果该函数是虚函数,则会调用子类中的该函数

划线部分可能是写错了吧

规则

当基类构造函数调用虚函数时,不会调用派生类的虚函数;同样,当基类被析构时,对象已经不再是一个派生类对象了,所以不会调用派生类的虚函数

只有通过基类的指针或引用调用虚函数时,才会发生动态绑定

 

虚析构函数

在c++中,不能声明虚构造函数,但是可以声明虚析构函数

声明语法为:

virtual~类名();

如果一个类的析构函数是虚函数,那么由它派生而来的所有子类的析构函数也是虚函数

总的来说虚析构函数是为了避免内存泄漏,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的

#include
using namespace std;
class ClxBase
{
    public:
        ClxBase() {};
        virtual ~ClxBase() { cout<<"delete ClxBase"<DoSomething();
     delete pTest;
    return 0;
}

但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:

​ 

没有调动子类的析构函数
也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。

所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当类被用来作为基类的时候,才把析构函数写成虚函数

不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为

最好将基类的析构函数声明为虚函数

纯虚函数
class Person
{
   virtual void Display () = 0; // 纯虚函数
protected :
   string _name ; // 姓名
};
class Student : public Person
{};

纯虚函数声明有分号

纯虚函数只有函数的名字而不具备函数的功能,不能被调用

如果在一个类中声明了纯虚函数,在其派生类中没有对其函数进行定义,则该虚函数在派生类中仍然为纯虚函数

抽象类
#include 
using namespace std;

//声明抽象基类Shape
class Shape
{
public:
    virtual float area()const //虚函数
    {
        return 0.0;
    }


    virtual void shapeName()const  = 0;//纯虚函数
    //shapeNamea函数的作用是输出具体的形状,在派生类中定义,因此声明为纯虚函数
};

//声明Point类
class Point:public Shape
{
public:
    Point(float a = 0.0, float b = 0.0)
    {
        x = a;
        y = b;
    }

    void setPoint(float a, float b)
    {
        x = a;
        y = b;
    }

    float getX()const
    {
        return x;
    }

    float getY()const
    {
        return y;
    }

    virtual void shapeName()const
    {
        cout<<"Point:";
    }

    friend ostream & operator <<(ostream &_cout, const Point &p)
    {
        _cout<<"["<

在类的层次结构中,顶层或最上面几层可以是抽象基类。抽象基类体现了本类族中各类的共性,把各类中共有的成员函数集中在抽象基类中声明

抽象基类是本类族的共公共接口,即就是从同一基类中派生出的多个类有同一接口

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/872765.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-13
下一篇2022-05-13

发表评论

登录后才能评论

评论列表(0条)