【C++】面向对象(二)——继承与多态

继承与多态

该文仅做为个人学习总结,包含学习中的理解与感悟,望各位大佬不吝赐教。

继承

类似于生物界对物种的定义,界、门、纲、目等,已定义的类(基类)也可以给它指定一些派生类。我们可以通过这种方法新建一些类,这样基类中已经声明的数据成员和函数都可以被继承(inheritance)下来,还可以添加新的成员。

class Ball
{
	...
};
class SmallBall: public Ball
{
	...
}

一个基类可以被多个派生类继承,同样的,一个派生类也可以继承于多个基类。

class LittleBall: public Ball, public SmallBall
{
	...
}

对于派生类,基类中的所有成员(包括私有成员,不过是隐式的,无法直接使用)都被继承,但注意以下情况:

  1. 基类的构造函数拷贝构造函数以及析构函数无法被继承。
  2. 基类重载的运算符无法被继承。
  3. 基类的友元函数,不属于基类成员,无法被继承。

基类的公有成员和保护成员可以被派生类的成员函数所访问(这也是引入保护成员的原因),而私有成员不可以。
注意可继承可访问的区别,继承后便成为派生类所拥有的成员,而访问则指访问基类的成员。

定义派生类时,我们会使用访问修饰符来指定继承类型。class关键字下,默认是私有继承(private)。

公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。

继承类型重新定义了基类的成员在被派生类继承后的访问属性。我们一般指定的是公有继承(public),即保持继承的类成员在基类中的地位不变。

另外要注意,派生类不能在成员初始化列表中直接初始化基类的成员。而构造函数与基类的其他成员不同,不能被派生类继承。

因此为了初始化基类中的成员变量,需要在派生类中调用基类的构造函数(显式调用);如果派生类没有调用则默认调用基类的不含参构造函数(隐式调用)。

//基类
class Animal
{
    protected:     
        int height;
    public:
        Animal()	//不含参构造函数
        {
            height = 0;
        }
        Animal(int h)	//含参构造函数
        {
            this->height = h;
        }
};
//派生类
class Fish:public Animal
{
	int length;
    public:
        Fish(int l): length(l)	//隐式调用
        {
        }
        Fish(int h, int l):Animal(h), length(l)	//显式调用
        {
        }
};

多继承时需列出所有基类构造函数的函数名以及参数列表。

在创建派生类对象时,先调用基类的构造函数,然后调用派生类的构造函数;撤销对象时,析构函数被调用的顺序则相反。

多态

关于多态(Polymorphism):

多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。

多态性在C++中是通过虚函数基类指针实现的。定义一个基类指针,我们可以将派生类的对象地址赋值给这个指针。这个指针就拥有了访问派生类成员的能力,尽管它是基于基类声明的指针。

但是这还不是很完善,观察以下代码:

#include <iostream>
using namespace std;

class Shape
{
protected:
    int width, height;

public:
    int sides; //边数
    Shape(int a = 0, int b = 0, int c = 0) : width(a), height(b), sides(c)
    {
    }
    int area()
    {
        cout << "Parent class area." << endl;
        return 0;
    }
};
class Rectangle : public Shape
{
public:
    Rectangle(int a = 0, int b = 0, int c = 4) : Shape(a, b, c) {}
    int area()
    {
        cout << "Rectangle class area : " << width * height << endl;
        return (width * height);
    }
};
class Triangle : public Shape
{
public:
    Triangle(int a = 0, int b = 0, int c = 3) : Shape(a, b, c) {}
    int area()
    {
        cout << "Triangle class area : " << width * height / 2 << endl;
        return (width * height / 2);
    }
};
// 程序的主函数
int main()
{
    Shape *shape;
    Rectangle rec(10, 7);
    Triangle tri(10, 5);

    // 存储矩形的地址
    shape = &rec;
    // 输出矩形的边数
    cout << shape->sides << endl;	//4
    // 调用矩形的求面积函数 area
    shape->area();	//"Parent class area."

    // 存储三角形的地址
    shape = &tri;
    // 输出三角形的边数
    cout << shape->sides << endl;	//3
    // 调用三角形的求面积函数 area
    shape->area();	//"Parent class area."

    return 0;
}

根据上述代码的执行结果,同一基类指针在查找其派生类的继承成员时,公共成员变量能够准确找到,但成员函数却执行了基类中的定义。

这与C++的多态方式有关,上述实例属于静态多态,即在编译时就已确定基类指针调用的函数定义(根据声明指针时所用的类型),也称为早绑定

在使用关键字virtual声明该函数后,该函数的调用就会变为动态多态,在程序运行时,才会根据基类指针指向的派生类对象,确定调用的派生类函数(前提是该函数在派生类中被重写),称作动态链接后期绑定。这些操作是由虚函数表和虚函数指针实现的。

//基类中给area()添加virtual关键字
virtual int area()
    {
        cout << "Parent class area." << endl;
        return 0;
    }
************************************************
//再次调用基类指针
shape = &rec;
shape->area();	//Rectangle class area : 70
shape = &tri;
shape->area();	//Triangle class area : 25

要注意的是,基类无法访问派生类的保护成员和私有成员,基类指针也无法访问派生类中基类所没有的成员和非公有成员(即只能访问继承的公共成员)。

通过在基类中定义虚函数,便于在派生类中重写该函数,以更好的适于调用的对象。如果在基类中不对该函数予以定义(定义为空),这便出现了纯虚函数

可以使用下面的方法表示纯虚函数:

virtual int area() = 0;

编译器便可识别该函数没有主体,是纯虚函数。

Q重写(覆盖)与重载的区别?

重写与重载的相同点在于,都出现了相同的函数名,但编译器对于它们的处理方式截然不同。重载要求同名函数的作用域相同,且参数不同,由编译器的重载决策来判断使用的函数定义。重写的作用域在基类与派生类之间,函数的参数列表必须一致,且函数必须被声明为虚函数,这时编译器无法决定,在运行时才能由基类指针指向的对象确定。

重载只是一种语言特性,而重写才是多态的体现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值