c++面向对象的有关特性

写在前面

在前一篇文章说到,我们c++的一个重要部分就是类,也说到了c++是一个面向对象语言,面向对象有很多特性,c++是如何实现的呢?

一、继承

面向对象程序设计的一个重要概念就是继承,在c++里面就是可以依据一个类创建另一个类,这样的话,对于后一个类而言,他就具备了前一个类的相关东西,这样就提高了代码的复用。

实际的来说,就是你要新建一个类,但是它和另一个类有联系,这样你就可以使用另一个类来创建这个新的类,而不用重新写一些数据成员和成员函数。这样子,先存在的那个类就叫做基类,后创建的这个类就是派生类,这就是继承。

继承的意思其实不难理解,我的理解就是包含的关系,比如说如果把人算作一个类,也就是基类,被继承的类,当我们要创建一个学生类的时候,就可以直接继承人这一个基类,因为首先学生一定是一个人,他一定具备人的相关特性,其次学生在继承人这个类之后,它还可以特殊具备一些关于学生这个身份相关的其他的特性。在这里,学生就是人派生出来的派生类,人也就是基类。

#include <iostream>

using namespace std;

// 基类
class Shape 
{
public:
	void setWidth(int w)
	{
		width = w;
	}
	void setHeight(int h)
	{
		height = h;
	}
protected:
	int width;
	int height;
};

// 派生类
class Rectangle: public Shape
{
public:
	int getArea()
	{ 
		return (width * height); 
	}
};

int main(void)
{
	Rectangle Rect;
	
	Rect.setWidth(5);//派生类调用基类定义的方法
	Rect.setHeight(7);//派生类调用基类定义的方法
	
	// 输出对象的面积
	cout << "Total area: " << Rect.getArea() << endl;//派生类在基类的基础上创建新的成员函数
	
	return 0;
} 

就像代码中,Shape是基类,Rectangle是派生类,这里是公共继承了Shape类,在Rectangle类中有Shape类的保护成员变量width,height和公共成员函数setWidth(),setHeight()。并且该派生类还有自己新定义的函数,getArea()。

在上面提到了公共继承,除了这种类型的继承之外,还有保护继承,和私有继承,但是我们常用的是公共继承。公共继承、私有继承和保护继承三种继承方式的特性如下:

  • 公有继承(public):基类的公有成员是派生类的公有成员,基类的保护成员是派生类的保护成员,基类的私有成员派生类访问不到,但是可以通过基类的公有成员或者保护成员去访问。
  • 保护继承(protected):基类的公有成员和保护成员是派生类的保护成员。
  • 私有继承(private):基类的公有成员和保护成员是派生类的私有成员。

 需要注意的是派生类不会继承基类的构造函数析构函数拷贝构造函数,基类的重载运算符和基类的友元函数

并且c++支持多继承,也就是一个类可以继承多个基类。但是继承的每个类都要指定继承的类型。

写法如下:如果存在基类A、B,现在新建一个类C需要公共继承A、B。

class A {
    ···
    ···
};


class B {
    ···
    ···
};


class C : public A, public B {
    ···
    ···

};

二、多态

 多态也是c++具有的一个重要特性!

前面看过一篇博客,他举的例子非常好,比如说我们要去买票,成人买的是成人票,儿童是半价票,学生是学生票等等,这个现象其实就很好的表现了多态这个特性。多态就是多种形态,这里不同的人都是买票,但是他们的买票方法有不同的实现方法。

C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

其实我们在很早之前就用到了多态,当我们在使用cout进行变量输出的时候,其实就是用到了多态,cout会根据输出的对象不同,调用不同的方法对他们进行输出。这里我们在后面做运算符重载的时候就能详细说明。这里也就用到了多态,也就是静态的多态。

多态也分为静态和动态,我们接下来也就跟着这个讲。所谓动态和静态区别在于,静态是指在编译的时候就决定调用哪个函数,而动态是指在运行的时候决定调用哪个函数。

静态多态:

静态的多态其实就是函数重载,传入的参数不同,有不同的行为,看起来像是调用的同一个函数,前面提到的输入输出流就是这样。

动态多态:

我们重点要讲的是动态多态:一个基类引用和指针调用同一个函数,传递不同的对象,会调用不同的函数。

我们还是以上面买票的例子来说,对于机器来说我们都是人类,但是因为身份不同,买票的行为也会不一样。

#include<iostream>

using namespace std;

class Person {
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-全价" << endl; 
	}
};

class Student : public Person {
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-半价" << endl; 
	}
};

void Func(Person& p) //父类的指针/引用
{
	p.BuyTicket(); /*多态*/
}

int main()
{
	Person p;
	Student s;
	
	Func(p); //传父类对象 —— 调父类的
	Func(s); //传子类对象 —— 调子类的
	return 0;
}

 以上代码运行结果:

买票-全价
买票-半价

 我们可以发现,Person类和Student类的虚函数返回值类型、函数名、参数列表都相同,这就叫做重写或者是覆盖,根据不同类型的对象的指针和引用调用相同的函数,会有不同的效果。但是对于Func()这个函数而言,他只接收一个类型的参数,都是Person类型。

多态的实现

满足以下条件:

  • 必须通过 基类 指针 或者 引用 调用虚函数,使用对象调用虚函数则不能实现多态。
  • 被调用的函数必须是虚函数,并且派生类必须已经对基类的虚函数进行重写或者覆盖。

虚函数:被virtual修饰的类成员函数,只能是类的非静态成员函数才可以虚函数

重写(覆盖):必须是虚函数,并且满足基类和派生类的函数返回值类型函数名函数参数列表都一样,方可成为重写(覆盖)。

这里我们解释一下为什么需要满足这几个条件:

  • 为什么必须是基类的指针或者引用调用?
    • 首先解释为什么必须是基类,因为只有基类才可以既接收基类也接收派生类。这样子才能达到对于上层看来我们都是Person类型去买票,但是由于我们的身份不一样,买票的行为也就不一样
    • 其次解释一下为什么必须是基类的指针或者引用,参数传递如果不是指针或者引用的话,进行的是拷贝传参,这样子的话如果进行的是把派生类赋值给基类的话,切片就会只把基类的成员变量的值拷贝过去,而派生类的虚函数表则不会被拷贝过去,所以在调用的时候只能找到基类的虚函数表,也就实现不了多态了。而使用指针或者引用的话就是直接传递地址,虚函数表也就是派生类的虚函数表了。
  • 为什么一定是虚函数?
    • 如果不是虚函数,也就不存在重写(覆盖)了,也就不存在多态了,调用哪一个函数只取决于调用的对象类型了。

特殊情况:

  • 当基类和派生类的虚函数返回值是父子关系的引用或者指针的时候也构成多态。(不能是对象),这叫做协变
  • 当析构函数是虚函数的时候也构成多态,那是因为析构函数的名字都被特殊处理成了destructor,这就是为了实现多态。这里我们前面就验证过当派生类调用析构函数的时候,他是先调用了派生类自己的析构函数,然后再调用了基类的析构函数。

写到这里的时候看到了一个问题:既然不使用虚析构函数的时候,也可以实现释放派生类再释放基类资源,那什么时候必须使用虚析构函数呢?

        看了好多篇博客,感觉自己才理解了一些。当你实现多态的时候,使用基类的指针操作派生类的对象的时候,在析构的时候只析构基类,而不会析构派生类。这样就会导致内存泄漏。

        在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。 如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数。
        析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该把函数定义为虚函数

纯虚函数和抽象类

首先给出纯虚函数的定义:

        在基类中定义,但是没有实现的虚函数。纯虚函数在基类的派生类中必须被实现。

其次给出抽象类的定义:

        类的定义中有纯虚函数的类为抽象类,抽象类不能初始化为一个对象。

这里纯虚函数其实起到了一个接口的作用,解释一下为什么需要有纯虚函数,你可能想在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

那为什么会想要定义一个在基类中不想给出实现,但是在派生类中必须给出实现的纯虚函数呢?

其实举个例子很好理解,比如说基类是动物,而派生类就是熊、老虎、狮子等等,我们常常给熊、老虎这些具体的动物初始化一个对象,但是并不会给动物这个基类单独初始化一个对象。这里动物就是一个抽象类,而熊这些动物就是继承这个抽象类并且必须实现这个抽象类定义的方法。

怎么定义一个纯虚函数:

#include<iostream>

using namespace std;

class Animal{
public:
	string name;
	virtual void GetName() = 0;
};

class Tigger : public Animal{
public:
	virtual void GetName(){
		cout<<name<<endl;
	}
};

int main()
{
	Tigger t1;
	t1.name="tigger";
	
//	Animal a1;不能让抽象类实例化
	
	t1.GetName();
	
	
	return 0;
}

 写在最后:

        多态是c++里面一个非常重要的特性,写这篇文章作为记录!

参考文章:

[1] C++中派生类隐式调用与显式调用基类的构造函数

[2]【C++】多态 —— 条件 | 虚函数重写 | 抽象类 | 多态的原理

[3] 为什么要使用父类指针和引用实现多态,而不能使用对象?

[4] C++——什么时候析构函数需要写成虚函数

[5]  C++ 多态性:虚析构函数(学习笔记:第8章 07)

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会掉头发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值