面向对象的三大特性

本文详细介绍了面向对象编程中的三大特性——封装、继承和多态,包括它们的概念、达成的效果以及实例分析。同时讨论了虚函数、抽象类和纯虚函数在实现多态和接口规范中的作用。
摘要由CSDN通过智能技术生成

一、前言

面向对象的三大特性:封装,继承,多态

封装将对象的属性和方法封装在一个对象,形成一个独立的实体
继承子类继承父类的属性和方法
多态不同类的对象对通一消息做出不同的反映。(当一个父类引用指向一个子类对象,调用的方式回事子类的方法,这就多态性的体现)

二、封装

2.1 概念:

它指的是将数据和操作数据的方法捆绑在一起,形成一个独立的单元,这个单元就是。在封装中,数据被视为类的内部状态,而方法则用于操作这些数据。

一般情况:假设有多种需求的情景下,按照一般情况,可能需要多个普通函数。

封装:将多种需求按照它们之间的共性加以区分,然后将这些需求分别封装在不同的类中。

2.2 达成效果:

  1. 提高代码的可维护性:将功能按照其共性进行分类和封装,使得代码结构清晰、更易于理解和维护。

  2. 提高代码的复用性:当有多个需求具有相似的实现逻辑时,将它们封装在同一个类中,可以避免重复编写代码,提高代码的复用性

  3. 降低耦合度:不同功能的实现被封装在不同的类中,它们之间相互独立,降低了代码之间的耦合度,使得代码更加灵活和易于扩展

2.3 举个例子:

A需要一个功能,输入年龄和姓名,就会打印出”某某某的年龄是多少“,功能由B开发。

这个功能A不需要知道是怎么写的,只要知道是怎么用,用来干什么的就行。

B写好功能实现代码,封装成类,给A一个使用的接口(相当于实例化对象或者是调用函数),那么A就可以用了

class person {
public:
	person(int m_age, string m_name) :age(m_age), name(m_name) {}
	void printf() {
		cout << name << "的年龄是" << age << endl;
	}
private:
	int age;
	string name;
};

int main(){
    person person1(18, "王小明");
    person1.printf();
    return 0;
}

 三、继承

3.1 概念:

通过继承,一个类可以基于另一个类的属性和方法来创建新的类,从而实现代码的重用和扩展。

3.2 达成效果;

  • 代码重用:通过继承基类,子类可以继承基类中已有的属性和方法,避免了重复编写相似的代码,提高了代码的重用性。

  • 统一接口:基类定义了通用的接口,子类在继承基类的同时,可以保持统一的接口,便于管理和使用。

  • 扩展性:子类可以根据具体需求进行定制和扩展,可以覆盖基类中的方法或添加新的方法,从而满足不同的需求。

3.3 例子 菱形继承

一个爷爷基类 class animal,一个儿子类 class yang继承了class animal,一个女儿类 class tuo继承了 class animal,一个孙子类 class yangtuo继承了class yangclass tuo

 错误示范:

在下面这种情况下,yang继承了animal的变量m_age,tuo继承了animal的变量m_age。yangtuo继承了yang和tuo,所以此时它有双份的m_age(yang::m_age和tuo::m_age)。但一个yangtuo应该只有一份m_age才对。

class Animal {//虚基类
public:
	int m_age;
};
class yang :public  Animal {};
class tuo :public  Animal {};
class yangtuo :public yang, public tuo {};

//这里在main函数中实例化一个yangtuo
yangtuo yangtuo1;
//yangtuo1.m_age=20  这句话会报错
//只有加上作用域
yangtuo1::yang.m_age=20;
yangtuo1::tuo.m_age=18;
//这样不会报错,但yangtuo1会有两个年龄,一个是yang作用域下的,一个是tuo作用域下的

 以上继承称为菱形继承(在多重继承中,如果派生类继承了两个及以上具有共同基类的类,就会出现菱形继承问题

解决办法:通过虚继承(虚继承避免了重复继承同一个基类所带来的内存空间浪费,因为共同的基类只会在继承体系中保留一份实例。

class Animal {//虚基类
public:
	int m_age;
};
class yang :public virtual Animal {};//虚继承
class tuo :public virtual Animal {};//虚继承
class yangtuo :public yang, public tuo {};

//这样实例化的yangtuo就只有一份年龄了
yangtuo yangtuo1;
yangtuo1.m_age=18;

四、多态

4.1 概念

多态是面向对象编程中的一个重要概念,指的是同一个方法调用,在不同的对象上可以表现出不同的行为。它使得程序可以根据当前对象的类型来调用不同的方法,从而实现了代码的灵活性和可扩展性。

多态的实现通常依赖于继承和方法重写。当子类继承父类并重写了父类的方法时,父类引用指向子类对象时,调用的方法将会是子类中重写的方法,而不是父类中的方法。

4.2 达成效果

  • 灵活性:多态使得程序可以根据对象的实际类型来调用相应的方法,使得代码更加灵活,可以适应不同的对象类型。

  • 可扩展性:通过继承和方法重写,可以很容易地添加新的子类,并重写父类的方法,从而实现新的行为,而不需要修改已有的代码。

  • 简化代码:多态可以使得代码更加简洁和清晰,因为同样的方法调用可以在不同的对象上表现出不同的行为,减少了代码的重复和冗余。

  • 增加代码的可读性和可维护性:多态使得代码更加易于理解和维护,因为它使得对象的行为和方法调用更加直观和自然。

 4.3 例子

一个基类class animal ,一个子类class dog 继承animal

 错误示范:

class animal {
public:
	void speak() {
		cout << "动物在说话" << endl;
	}
};

class dog :public animal {
public:
	void speak() {
		cout << "小狗在说话" << endl;
	}
};
void dospeak(animal & animal1) {
	animal1.speak();
}
int main(){
    dog dog1;
    dospeak(dog1);//这样调用结果输出是”动物在说话“
}

 在main函数中实例化了一个dog类的对象dog1,并将它传入到dospeak函数中,但是由于参数类型是animal类型的引用,因此会发生早期绑定,也称为静态绑定这意味着编译器在编译时就决定了要调用哪个函数,而不是在运行时根据对象的实际类型来决定。在这种情况下,虽然传递给dosepak()函数的对象是dog类的对象dog1,但此时调用的还是animal类中的speak()函数。

使用visual_studio的工具-》命令行-》开发者powershell选项,打开到源文件目录下

可以看出当前animal类中的大小为size(1),是一个空类的大小,为一个字节,因为类的大小与类的数据成员,继承,虚函数,对齐和填充(与静态成员和普通类成员函数无关)

可以看出此时dog类中中的大小也是1个字节 

 

解决办法:通过虚函数,将基类的speak函数加上virtual关键字(其它子类中的speak函数前面可以加上virtual也可以不加virtual)

此时在dospeak()函数中实现动态绑定(Dynamic Binding):虚函数调用在运行时进行绑定,这意味着函数的调用是根据对象的实际类型来确定的,而不是根据指针或引用的类型。这种动态绑定使得程序在运行时能够适应对象的实际状态,而不是限定于静态类型的限制。

void virtual speak() {
	cout << "动物在说话" << endl;
}
..
int main(){
    dog dog1;
    dospeak(dog1);//这样调用结果输出是”小狗在说话“
}

 可以看到此时animal的size大小变为4(一个虚函数指针的大小为4字节,在不同的系统,可能不同),此时出现一个vfptr(虚函数指针),指向下面的虚函数表vftable,可以看到表中有一个函数animal::speak

 

 再看一下继承类dog中,由于继承了基类animal,size尺寸也变为4,它也有一个虚函数指针vfptr,

指向下面的虚函数表vftable,里面有一个函数dog::speak;(这是子类重写了父类中的函数,要是不重写呢,看看最后一张图)

 

这里是子类dog没有重写父类中的speak函数,展开的虚函数表,此时由于继承了父类,子类的大小还是size(4),但是由于子类没有重写父类中的函数,所以虚表中的函数继承的是父类中的speak函数animal::speak 

 

 tip:

动态多态满足条件
1、有继承关系(这里的class dog继承class animal)
2、子类重写父类的虚函数(比如:父类和子类都有void speak()函数),且返回值,返回类型,参数类型,参数列表一致)

五,构造函数和析构函数可以是虚函数嘛

5.1 构造函数不可以是虚函数

构造函数不能是虚函数。这是因为在对象的构造过程中,虚表(vtable)尚未构建,因此无法进行虚函数的动态绑定。此外,虚函数需要通过虚表来进行调用,而在构造函数执行期间,虚表尚未建立。

5.2 析构函数可以是虚函数

错误示范:

父类指针在调用析构函数时,不会调用子类的析构函数,导致子类的内存没有释放,造成内存泄露

(new string是在堆区分配内存用于存储字符串数据的动态分配形式。在C++中,new操作符用于在堆上分配内存,返回一个指向分配内存的指针。堆上的数据通常需要手动释放这块内存以避免内存泄漏。)

class animal {
public:
	animal() {
		cout << "animal构造函数在调用" << endl;
	}
	~animal() {
		cout << "animal虚析构函数在调用" << endl;
	}
	void virtual speak() {
		cout << "动物在说话" << endl;
	}
};


class dog :public animal {
public:
	string* m_name;
	dog(string name) {
		cout << "dog构造函数在调用" << endl;
		m_name = new string(name);
	}
	~dog() {
		cout << "dog析构函数在调用" << endl;
		delete m_name;
		m_name = nullptr;
	}
	void speak() {
		cout << *m_name << "小狗在说话" << endl;
	}
};
int main(){
    animal *animal1=new  dog("tom");
    animal1->speak();
    delete animal1;
    return 0;
}

 解决方法:虚析构

在基类中(animal)的析构函数前加上virtual,此时animal中的析构函数称为虚析构函数,这样在析构基类时也会调用派生类的析构函数。

virtual ~animal() {
	cout << "animal虚析构函数在调用" << endl;
}

 tip:

1 虚构函数或纯虚析构就是用来解决通过父类指针释放子类对象
2 如果子类中没有堆区数据 可以不写为虚析构或者纯虚析构
3 拥有纯虚析构的类也属于抽象类

六、 纯虚函数

6.1 概念

纯虚函数是在C++中的一种特殊的虚函数,它在基类中声明但没有提供实现。纯虚函数的声明形式是在函数声明的结尾加上 "= 0"

virtual 返回值类型 函数名称(参数类型 参数值)=0;

  • 纯虚函数的主要作用是定义一个接口,要求派生类必须提供对应的实现。
  • 这种机制使得基类能够定义通用的接口,而具体的实现则由派生类负责完成。
  • 纯虚函数使得基类可以成为一个抽象类不能被实例化,只能被用作其他类的基类。

6.2 达成效果

  1. 实现接口规范:纯虚函数在基类中只提供了函数的声明,而没有提供具体的实现。这样可以强制要求派生类必须提供对应的实现,从而确保派生类都遵循了基类的接口规范。

  2. 促进多态性:通过基类的指针或引用调用纯虚函数,可以在运行时根据实际对象类型来选择调用哪个派生类的函数。这样可以实现多态性,使得程序更加灵活和可扩展。

  3. 提供默认实现:有时候基类中可以为纯虚函数提供一个默认的实现,如果某个派生类不需要重新实现这个函数,可以直接使用基类中的默认实现。这样可以减少代码的重复编写。

  4. 抽象基类:基类中包含纯虚函数的类通常被称为抽象基类,它主要用于定义接口和行为规范,而不是提供具体的实现。抽象基类可以作为其他具体类的接口规范,有助于组织和管理程序的类结构。

6.3 举个例子

 在基类中定义一个虚函数,那么这个类就成为抽象类(像是定义了一个总体的规范)。并且在子类中必须对这个虚函数进行重写(重写的函数必须具有与基类函数相同的函数签名(即函数的名称、参数列表和常量属性),通过重写虚函数,派生类可以根据自身的需要覆盖基类的实现),否则子类也成为了抽象类(抽象类不能实例化对象)

class base {
public:
	virtual void speak() {} ;
};
class son :public base {
public:
	void speak() {
		cout << "我是儿子" << endl;
	}
};

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值