c++基础知识点(7)多态,虚函数,覆盖,重载,隐藏,抽象类,虚析构,限制构造函数

依旧还是来看一段代码

#include <iostream>
using namespace std;
class base
{
    public:
        void priMsg()
        {
            cout<<__func__<<"line :"<<__LINE__<<endl;
        }
};
class subclass :public base
{
    public:
        void priMsg()
        {
            cout<<__func__<<"line :"<<__LINE__<<endl;
        }
};
void test(base *p)
{
    p->priMsg();
}

int main()
{
    base obj1;
    subclass obj2;
    test(&obj1);
    test(&obj2);
    return 0;
}

运行结果:

priMsgline :14
priMsgline :14
首先我们要明白为什么obj2这个对象明明是subclass为什么也调用的14行的函数,是因为void test中的形参是 base* p,然后因为subclass是base的公有继承,所以向上转型,都调用的基类中的函数。

那怎么样才能实现不向上转型,分别调用父类和子类的函数呢?

加个 virtual就行了

#include <iostream>
using namespace std;
class base
{
    public:
        virtual void priMsg()
        {
            cout<<__func__<<"line :"<<__LINE__<<endl;
        }
};
class subclass :public base
{
    public:
        void priMsg()
        {
            cout<<__func__<<"line :"<<__LINE__<<endl;
        }
};
void test(base *p)
{
    p->priMsg();
}

int main()
{
    base obj1;
    subclass obj2;
    test(&obj1);
    test(&obj2);
    return 0;
}

运行结果:

priMsgline :14
priMsgline :22

这其实就是多态,那么现在开始我们今天的知识点,多态。

一.什么是多态?

-多态:字面意思”多种状态“,一个接口,多种方法,子类和基类的方法不同。(程序在运行时决定调用哪个方法,是面向对象编程的核心概念)。

        程序在运行时才决定调用哪个方法,是面向对象编程的核心概念。

-多态性:将接口与实现进行分离,也就是实现共同的方法,但因为个体差异不同,采用不同的策略。

        oop(面向对象编程)特点:

        -封装(Wrap):实现细节隐藏,使代码模块化。

        -继承(inheritance):扩展已存在的代码,目的是代码重用。

        -多态(polymorphism):实现接口重用,不论传递过来的是哪个类的对象,函数都能通过同一个接口调用到适应各自对象的实现方法。

二.多态的实现

-使用virtual修饰的成员函数(虚函数)

虚函数的设置条件:

-——非类的成员函数不能设置为虚函数(例如友元函数)

-——类的静态成员不能定义为虚函数

首先什么是static静态成员函数?静态成员函数不属于类中的任何一个对象和实例,属于类共有的一个函数。也就是说,它不能用this指针来访问,因为this指针指向的是每一个对象和实例。

对于virtual虚函数,它的调用恰恰使用this指针。在有虚函数的类实例中,this指针调用vptr指针,指向的是vtable(虚函数列表),通过虚函数列表找到需要调用的虚函数的地址。总体来说虚函数的调用关系是:this指针->vptr(4字节)->vtable ->virtual虚函数。

所以说,static静态函数没有this指针,也就无法找到虚函数了。所以静态成员函数不能是虚函数。他们的关键区别就是this指针。

构造函数不能为const函数,构造函数的目的就是为了给成员变量赋初值,不能为const函数

-——构造函数不能定义为虚函数,但是析构函数却可以设置为虚函数

-——成员函数声明时需要使用 virtual关键字修饰,定义时不需要

-——基类成员函数设置为虚函数,那么派生类中同名函数(函数名,形参类型,个数返回值完全一样)自动称为虚函数。

三.覆盖,重载及隐藏

成员函数覆盖(override,也称重写)

        是指派生类重新定义基类的虚函数,特征如下:

        A.不同的作用域(分别位于派生类与基类)

        B.函数名字相同

        C.参数相同

        D.基类函数必须有virtual关键字,不能有static

        E.返回值相同

        F.重写函数的权限访问限定符可以不同

成员函数重载(overload)

        是指函数名相同,参数不同(数量,类型,次序),特征如下:

        A.相同的范围(在同一个作用域中)

        B.函数名字相同

        C.参数不同

        D.virtual关键字可有可无

        E.返回值可以不同

成员函数的隐藏(也称重定义)

        A.不在同一个作用域

        B.函数名字可以不同

        C.返回值可以不同

        D.参数不同,此时,不论有无virtual关键字,基类的函数将被隐藏(注意和重载的区别,重载在同一个定义域中)

        E.参数相同,但是基类函数没有virtual关键字,此时,基类的函数被隐藏(注意和覆盖的区别,覆盖是有virtual关键字)

四.联编(链接)

就是将模块或者函数合并在一起生成可执行代码的处理过程。按照联编所进行的阶段不同 ,可分为两种不同的联编方法:静态联编和动态联编

1.静态联编(静态链接)

是指在编译阶段就将函数实现和函数调用关联起来,因此静态联编也叫早绑定

2.动态联编(动态链接)

是指在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定。 

————c++中一般情况下是静态联编,但是一旦涉及多态和虚拟函数就要使用动态联编了。

tips:重载只是一种语言特性,编译器根据函数不同的参数表,把同名函数区分开来,属于静态联编,与多态无关。引用一句Bruce Eckel的话:”不要犯傻,如果它不是晚绑定它就不是多态。”

五.抽象类

含有纯虚函数的类就是抽象类。

抽象类没有完整的信息,只能是派生类的基类,抽象类不能有实例,不能有静态成员,派生类应该实现抽象类的所有方法。

例:

class Graphic
{
    public:
        virtual float Area()=0;
};
class Rectangle :public Graphic
{
    public:
        float Area()
        {
            return h*w;
        }
    private:
        float h,w;
};

一般的,使用一个类,只关心public成员,故此需要隐藏类的其他成员方法。例如,动物作为一个基类可以派生出老虎,孔雀等子类,但是动物本身并不能作为任何实际明确的对象。

六.虚析构函数

用来避免子类中回收不完整的情况

例子:

#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
    public:
        base(){pri();}
        //虚析构函数
        /*virtual*/ ~base(){pri();}

};
class subclass :public base
{
    public:
        subclass(){pri();}
        //派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
        ~subclass(){pri();}

};
int main()
{
    subclass *p=new subclass;
    delete p;
    //设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
    base *q=new subclass;
    delete q;

    return 0;
}

运行:

baseline::13
subclassline::21
~subclassline::23
~baseline::15
baseline::13
subclassline::21
~baseline::15
回收不完整

解决:

#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
    public:
        base(){pri();}
        //虚析构函数
        virtual ~base(){pri();}

};
class subclass :public base
{
    public:
        subclass(){pri();}
        //派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
        ~subclass(){pri();}

};
int main()
{
    subclass *p=new subclass;
    delete p;
    //设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
    base *q=new subclass;
    delete q;

    return 0;
}

运行:

baseline::13
subclassline::20
~subclassline::21
~baseline::14
baseline::13
subclassline::20
~subclassline::21
~baseline::14
七.限制构造函数

构造函数权限不是public,那么这就是限制构造函数

#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
    protected:
        base(){pri();}
        //虚析构函数
        virtual ~base(){pri();}

};
class subclass :public base
{
    public:
        subclass(){pri();}
        //派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
        ~subclass(){pri();}

};
int main()
{
    subclass *p=new subclass;
    delete p;
    //设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
    base *q=new subclass;
    delete q;

    return 0;
}

运行报错

基类限制构造函数不能创建实例,只能派生出子类,定义子类对象来访问接口函数

#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
    protected:
        base(){pri();}
        //虚析构函数
        virtual ~base(){pri();}

};
class subclass :public base
{
    public:
        subclass(){pri();}
        //派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
        ~subclass(){pri();}

};
int main()
{
    //subclass *p=new subclass;
    //delete p;
    //设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
    subclass *q=new subclass;
    delete q;

    return 0;
}

可以运行:

baseline::13
subclassline::21
~subclassline::23
~baseline::15
如果是private限制呢?

这时候派生类也无法访问基类的私有成员,当然我们也有办法,那就是使用友元函数,打破基类私有限制的封装。

实例:

#include<iostream>
using namespace std;

#define pri() cout<<__func__<<" line: "<<__LINE__<<endl;
class Base{
	private:
		//构造函数权限不是public,那么这就是限制构造函数
		Base(){ pri(); }
	public:
		virtual ~Base(){ pri(); }
		void get(){ pri(); }
		friend Base *getObj();
		friend void freeObj(Base *);
};
#if 0
class Subclass : public Base{
	public:
		Subclass(){ pri(); }
		~Subclass(){ pri(); }
};
#endif
Base *getObj() //友元成员函数,打破类的封装,在函数可以访问 类的保护和私有成员
{
	return new Base; //开辟堆区空间时,系统会调用Base类默认构造函数
}
void freeObj(Base *p)
{
	delete p;
}

int main(int argc, char *argv[])
{
//	Base obj;   //限制构造函数不能创建实例,
	// 如果构造函数权限为 private,那么只能设计友元成员函数打破private的限制

//  Subclass obj; //错误,因为基类构造函数为 private权限,而派生类不能访问基类的 私有成员。
//	obj.get();
	Base *p = getObj();
	p->get();
	freeObj(p);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/fuyuyf/article/details/125908467