c++:封装、继承、多态
封装是面向对象编程中的一种重要概念,它将数据和操作封装在一起,形成一个“黑盒子”,对外部只暴露必要的接口,隐藏内部实现细节。C++通过类(Class)来实现封装,其中包括私有成员(Private)、保护成员(Protected)和公有成员(Public)等访问控制修饰符。私有成员只能在类内部访问,保护成员可以在类及其子类中访问,公有成员可以在任何地方访问。首先,在介绍封装前,应该要知道类和对象这么
目录
引言
在现代软件开发中,面向对象编程(Object Oriented Programming)已经成为一种广泛应用的编程范式。C++作为一种支持面向对象编程的语言,在封装、继承和多态方面提供了强大的特性。本文将介绍C++中的封装、继承和多态概念,并通过简单示例来说明它们的使用方法。
一、封装
1、什么是封装
封装是面向对象编程中的一种重要概念,它将数据和操作封装在一起,形成一个“黑盒子”,对外部只暴露必要的接口,隐藏内部实现细节。C++通过类(Class)来实现封装,其中包括私有成员(Private)、保护成员(Protected)和公有成员(Public)等访问控制修饰符。私有成员只能在类内部访问,保护成员可以在类及其子类中访问,公有成员可以在任何地方访问。
首先,在介绍封装前,应该要知道类和对象这么一个事,什么是类和对象?
类和对象好比你对象给你列一个愿望清单(画的饼),都是些模板,都是些抽象、模拟的东西,那么当某一天,你对象给你实现了具体出来的某一个”饼“,这个饼实际实现了。这个就有点类似。
2、为什么要封装
对象给你画的饼,你知我知。不是我的对象不让他不知,这多好,各自有各自的圈子(安全性、可维护性)。那么封装起来的就是一些密码,但是不能像“闭关锁国”一样,完全封闭自己啊(你这的这个类还有什么用啊?都不给外部提供调用的接口)。
3、访问控制修饰符
|
类内 |
派生类中 |
除两者之外 |
|
|
private |
√ |
× |
× |
|
protected |
√ |
√ |
× |
|
public |
√ |
√ |
√ |
表1-1 访问控制修饰符
这个不做详细阐述。
4、类的设计和实现
#include<bits/stdc++.h>
using namespace std;
class Dog{//创建一个Dog对象
private:
string name;//一个属性
public:
Dog(string name = ""):name(name){}//初始化构造列表(缺省参数)
void eat(string food)//一个方法
{
cout<<"I am "<<name<<", I like eat "<<food<<endl;
}
};
int main()
{
Dog dog1("汪汪");//实例一个对象
dog1.eat("骨头");//调用成员方法
Dog dog2("旺财");//实例一个对象
dog2.eat("便便");//调用成员方法
// cout<<dog1.name<<endl; //error 私有,类外无法访问
return 0;
}
当你看完这段代码的时候,有没有觉得似乎理解了一点 ,Dog是一个类,代表狗类,实际的狗都是狗类实例出来的。
5、封装的优点
封装具有提高可维护性、增加可重用性、提供安全性和降低耦合度(要做到高内聚,低耦合)的优点,并适用于类和对象的设计、接口设计、数据封装和访问控制,以及类库和框架的开发。通过合理应用封装,可以编写出更加健壮、灵活和易于维护的代码。
二、继承
1、什么是继承
在继承关系中,子类可以获得父类的属性和方法,并可以在此基础上添加自己的特定功能或修改父类的行为。子类可以继承父类的公共成员(public),但不能继承私有成员(private),私有成员只能在父类内部访问。继承过来的是共性,大家都有。我增增改改,成为自己的个性。
2、为什么要继承
-
代码重用:通过继承,子类可以直接使用父类已经定义好的属性和方法,避免了重复编写相同的代码,提高了代码的重用性。
-
组织类的层次关系:通过继承,可以将类组织成层次结构,形成父类和子类之间的关系。这样可以更好地组织和管理代码,使得代码结构更加清晰和易于理解。
-
多态性:继承是实现多态性的基础。通过继承,子类可以覆盖父类的方法,实现不同的行为。这样在使用父类类型的变量时,可以根据实际的对象类型调用相应的方法,实现不同的处理逻辑。
2、1 继承权限图
2、2 单继承案例
#include<bits/stdc++.h>
using namespace std;
class Base{
private:
int a=0;
protected:
int b=0;
public:
int c=0;
};
class Son :public Base{
//子类中能访问protected b public c
public:
void func(void)
{
cout<<b<<c<<endl;
//cout<<a<<end1;//不可访问
}
};
int main(){
Son ob;
//cout<<ob.b<<end1;//类外无法访问
cout<<ob.c<<endl;
ob.func();
return 0;
}
3、多继承
多继承,顾名思义:一个类有两个或多个父类。那么就会出现成员变量和成员函数等重名的情况,在这个情况下,应造成隐藏。万能的好方法就是加作用域限定符。
#include<bits/stdc++.h>
using namespace std;
class Base1{
public:
int a;
Base1(int a):a(a){}
};
class Base2{
public:
int a;
Base2(int a) :a(a){}
};
class Son :public Base1 ,public Base2{
public:
int a;
Son(int a,int b,int c): Base1(a),Base2(b),a(c){}
};
int main(){
Son ob(10,20,30);
cout<<ob.a<<endl;//子类a
cout<<ob.Base1::a<<endl; //Base1 a
cout<<ob.Base2::a<<endl;//Base2 a
return 0;
}
3、1 砖石继承
菱形继承(砖石继承):有公共祖先的继承叫菱形继承。
最底层的子类数据会包含多份(公共祖先的数据)
#include<bits/stdc++.h>
using namespace std;
class Animal
{
public:
int data;
};
class sheep :public Animal{
};
class Tuo :public Animal {};
class sheepTuo:public sheep , public Tuo{};
int main()
{
sheepTuo ob;
memset(&ob,0,sizeof(sheepTuo));
cout<<sizeof(ob);//8,两份数据,两个int
//cout << ob.data << endl;//二义性,不知道调用谁的data
cout << ob.sheep:: data << endl;//解决二义性,依旧可以加作用域
cout << ob. Tuo::data << endl;//解决二义性,依旧可以加作用域
return 0;
}
4、虚继承
那么像刚刚提到的,我继承了多个data。那么我可以只继承一份吗?那就不会导致二义性了。简单在继承权限符前面加上virutal就可以了。
4、1 虚继承实现
#include<bits/stdc++.h>
using namespace std;
class Animal
{
public:
int data;
};
class sheep :virtual public Animal{
};
class Tuo :virtual public Animal {};
class sheepTuo:public sheep , public Tuo{};
int main()
{
sheepTuo ob;
memset(&ob,0,sizeof(sheepTuo));
cout<<sizeof(ob)<<endl;
cout << ob.data << endl;//解决了二义性
cout << ob.sheep:: data << endl;
cout << ob. Tuo::data << endl;
return 0;
}
4、2 虚继承原理
这个可以查看须在Vmware下使用特殊命令查看类的布局分布。(查看编译器处理类布局)
可以发现,使用虚继承之后的sheepTuo的字节大小为12 。分别有两个虚基类指针,并且指向不同的虚基类表,因为偏移量的不同,所以不会出现二义性,且能访问到数据,有且只有一份data数据。
5、继承的优点
简而言之,你直接继承别人的来使用,方便高效。并且可以自己展示自己的”个性“。也很合理,但在一些特殊情况也要注意,比如上面提到的砖石继承等。解决方法介绍了直接加上作用域限定符或者虚继承等。同时这个也为后续展开的多态有所铺垫。
三、多态
1、什么是多态
多态是面向对象编程中的一个重要概念,指的是同一种类型的对象在不同的情况下表现出不同的行为。简单来说,就是同一个方法在不同的对象上可以有不同的实现。看不懂,没关系,下面会给出详细介绍。
2、为什么要多态
我先简单举一个例子:客户(房东)叫你给他配一栋楼的锁,房间很多。
程序员1:房间120间,加上大门配一把,给你配上121把钥匙,还贴心的给你贴上标签来标识房间号。
程序员2:直接给你配一把万能钥匙。房东一把锁可以开所有门。
如果是你,你想让谁接手这个项目?
3、虚函数和纯虚函数
在介绍(纯)虚函数之前,先来点下酒菜。
这个图的想传达的就是,怎么通过父类的指针或者引用来操作每一个子类中个性不同的那一部分?即使是以后即将到来的子类。
#include<bits/stdc++.h>
using namespace std;
class Animal{
public:
void speak (void){
cout<<"动物在说话"<<endl;
}
};
class Dog:public Animal{
public:
void speak (void){
cout<<"狗在汪汪"<<endl;
}
};
int main(){
Animal *p = new Dog;
p->speak() ;//动物在说话,居然不是狗在汪汪,
//这个不是我想要的结果,我是想通过基类的指针调派生类的方法
}
3、1 虚函数
//多态条件:有继承、子类重写父类的虚函数,父类指针指向子类空间。
#include<bits/stdc++.h>
using namespace std;
class Animal{
public:
virtual void speak (void){//虚函数
cout<<"动物在说话"<<endl;
}
};
class Dog:public Animal{
public:
virtual void speak (void){//子类重写虚构函数
cout<<"狗在汪汪"<<endl;
}
};
class Cat:public Animal{
public:
virtual void speak (void){//子类重写虚构函数
cout<<"猫在嘻嘻"<<endl;
}
};
int main(){
Animal *p = new Dog;
p->speak() ;
Animal *q = new Cat;
q->speak() ;
return 0;
}
//简单加了一个virtual,就实现了基类指针调用派生类的方法了。
给出布局分布:
当Dog继承了Animal之后,Dog会继承虚函数指针和虚函数表,并且当Dog重写了父类的虚函数之后,虚函数地址变成了Dog的函数地址。(图3-1 ----> 图3-2 实现原理)
3、2 纯虚函数
父类的虚函数定义了没什么用啊,反正我们的本意也不是使用它。那我们可以不定义只声明嘛?
class Animal{
public:
virtual void speak (void) = 0;//纯虚函数
};//该类变成抽象类,无法实例化对象
3、2、1 案例引入
#include<bits/stdc++.h>
using namespace std;
//抽象制作饮品
class AbstractDrinking{
public:
//烧水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//规定流程
void MakeDrink()
{
this->Boil() ;
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee : public AbstractDrinking
{
public:
//烧水
virtual void Boil(){
cout << "煮农夫山泉!" << endl;
//冲泡
}
virtual void Brew(){
cout << "冲泡咖啡!" << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "将咖啡倒入杯中!"<< endl;
}
//加入辅料
virtual void PutSomething(){
cout << "加入牛奶!"<< endl;
}
};
class Tea : public AbstractDrinking
{
public:
//烧水
virtual void Boil(){
cout << "煮农夫三泉!" << endl;
//冲泡
}
virtual void Brew(){
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "将茶倒入壶中!"<< endl;
}
//加入辅料
virtual void PutSomething(){
cout << "加入柠檬片!"<< endl;
}
};
//业务函数
void DoBussiness(AbstractDrinking* drink)
{
drink->MakeDrink() ;
delete drink;
}
int main(){
DoBussiness(new Coffee);//调用泡咖啡
cout<<"---------------\n";
DoBussiness(new Tea);//调用泡茶
return 0;
}
//有没有觉得很方便?单纯就给你一个接口
//至于调了这个接口干什么,自己决定
//像不像一个模板?
对于以上案例的实现:是不是有点像一把万能钥匙?只需要父类提供一个接口去调用,而不是使用不同的指针指向不同的对象去访问,访问起来也方便。并且当你新增楼层门户时,一样的继承那个模板,自己实现不同的功能即可。
4、虚析构和纯虚析构
4、1 虚析构(解决内存泄露)
#include<bits/stdc++.h>
using namespace std;
class Animal
{
public:
Animal(){
cout<<"Animal 的构造函数被调用"<<endl;
}
~Animal()
{
cout << "Animal 的析构函数被调用了" <<endl;
}
};
class Dog:public Animal
{
public:
Dog(){
cout<<"Dog 的构造函数被调用"<<endl;
}
~Dog()
{
cout << "Dog 的析构函数被调用了" <<endl;
}
};
int main()
{
Animal *al = new Dog;//内存泄露
delete al;
return 0;
}
解决办法:一样加virtual。 虚析构原理:通过父类指针释放整个子类空间
虚析构实现的原理,和虚函数类似,一样继承过来指针和表,但是不是覆盖以前的地址。
4、2 纯虚析构
纯虚析构的本质:
- 是析构函数,处理各个类的回收工作。而且析构函数不能被继承。
- 必须为纯虚析构函数提供一个函数体。
- 纯虚析构函数必须在类外实现.
#include<bits/stdc++.h>
using namespace std;
class Animal
{
public:
Animal(){
cout<<"Animal 的构造函数被调用"<<endl;
}
virtual ~Animal() = 0;//内类声明
};
Animal::~Animal()//内外定义
{
cout << "Animal 的析构函数被调用了" <<endl;
}
class Dog:public Animal
{
public:
Dog(){
cout<<"Dog 的构造函数被调用"<<endl;
}
~Dog()//写不写无所谓,虚virtual有”遗传“,一代虚代代虚。
{
cout << "Dog 的析构函数被调用了" <<endl;
}
};
int main()
{
Animal *al = new Dog;
delete al;
return 0;
}
//实现效果和虚析构一摸一样,只是写法上些许变化
5、多态的优点
-
代码重用:通过将一些通用的代码封装在父类中,派生类可以直接继承这些代码,从而实现代码重用。这样可以大大减少代码量,提高开发效率。
-
灵活性:使用多态可以使程序更加灵活。具体来说,它可以在运行时动态地选择不同的实现方法,这使得程序可以根据不同的情况执行不同的操作。
-
可扩展性:多态可以方便地扩展程序功能。当需要添加新的功能时,只需创建一个新的子类并重写相应的虚函数即可,而不需要修改原有的代码。
-
可维护性:多态可以使程序更易于维护。由于代码重用和灵活性的优点,程序变得更加简洁明了,易于理解和修改。
-
代码可读性:多态可以使代码更加可读。由于代码的结构清晰,每个类都只包含自己的数据和方法
希望以上内容对你有所帮助!别忘了一键三连!!!

更多推荐

所有评论(0)