一、面向对象的三大特性
三大特性:封装,继承,多态
- 封装:封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
- 继承:继承主要实现重用代码,节省开发时间。子类可以继承父类的一些东西。
- 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。分为编译时多态和运行时多态。
二、详细理解
(1) 封装
都能了解到的是实现面向对象程序设计的第一步。那么我们需要封装什么呢?下面是具体讲解。
封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过 外部接口,一特定的访问权限来使用类的成员。
从上面的说明可以看出类具有属性和行为。举个简单的列:小明吃饭,小红行走,小刚睡觉。根据这些简单的情况,我们可以抽象出姓名这个属性,吃饭、行走、睡觉这几个行为。进而封装为类。
class People
{
public:
string strName;
public:
void Walk() { cout << strName << "正在走路" << endl; }
void Eat() { cout << strName << "正在吃饭" << endl; }
void Sleep() { cout << strName << "正在睡觉" << endl; }
};
既然讲到了类,我们就借助菜鸟编程先聊聊类的使用https://www.runoob.com/cplusplus/cpp-classes-objects.html。
1. 类
之前讲了类具有属性和行为,我们借助菜鸟编程的例子,编写一个Box类。
class Box
{
//属性
public:
double m_dLength;
double m_dWidth;
double m_dHeight;
//方法
public:
//返回体积
double m_getVolume(void) { return m_dLength * m_dWidth * m_dHeight; }
};
接下来我们就要具体化了,根据实际情况产生一个对象,调用它的方法。
int main()
{
//比方: 现在有一个水池, 长20 宽10 深5 , 要求出它的最大蓄水量
//我们就可以根据上面抽象出的类, 进行操作了
//1. 先生存一个对象
Box BoxPool;
//2. 具体化它的属性
BoxPool.m_dLength = 20;
BoxPool.m_dWidth = 10;
BoxPool.m_dHeight = 5;
//3. 调用它的方法体积,就可以求出结果了
cout << BoxPool.m_getVolume() << endl;
}
//输出: 1000
2. 类访问修饰符
讲到这,可能会对上面出现的public产生疑问。这里我们就不得不重新将会面向对象了。
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。
- Public: 公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值。
- Private: 私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。
- Protected:保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
继续着上个例子讲,如果数据提供者,不想让别人知道他们家水池的深度,我们该怎么操作呢?
class Box
{
//属性
public:
double m_dLength;
double m_dWidth;
private:
double m_dHeight;
//方法
public:
//返回体积
double m_getVolume(void) { return m_dLength * m_dWidth * m_dHeight; }
};
只要将水池的深度属性的类访问修饰符改为Privated即可,尝试着访问一下。很明显的出错了,这时候我们连赋值都做不到了,怎么办么?
类访问修饰符具体的使用,会在之后例子中说明。
3. 构造函数
针对上面的情况,我们可以在类内部编写一个Public赋值的方法,毕竟private只是在类外不能访问,通过这个方法我们可以在类内访问private成员。
class Box
{
//属性
public:
double m_dLength;
double m_dWidth;
private:
double m_dHeight;
//方法
public:
//返回体积
double m_getVolume(void) { return m_dLength * m_dWidth * m_dHeight; };
void m_setLength(double dHeight) { m_dHeight = dHeight; };
};
int main()
{
//比方: 现在有一个水池, 长20 宽10 深5 , 要求出它的最大蓄水量
//我们就可以根据上面抽象出的类, 进行操作了
//1. 先生存一个对象
Box BoxPool;
//2. 具体化它的属性
BoxPool.m_dLength = 20;
BoxPool.m_dWidth = 10;
BoxPool.m_setLength(5);
//3. 调用它的方法体积,就可以求出结果了
cout << BoxPool.m_getVolume() << endl;
}
莫得问题,程序完美执行。
单个赋值终归太繁琐,而且还要考虑到属性是否是private的。所以这时候我们采用构造函数的方式进行初始化。
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
在类创建的时候, 都会调用构造函数。但是有的时候不写自己的构造函数的话, 系统会调用默认的构造函数 (也就是什么都不做)
但是当写了构造函数后, 系统就不会调用默认的构造函数
class Box
{
// 系统默认产生的啥都不做的构造函数,这个显式的注释出来了。
//Box() {};
public:
Box(double dLength, double dWidth, double dHeight) : m_dLength(dLength), m_dWidth(dWidth), m_dHeight(dHeight) {};
//属性
public:
double m_dLength;
double m_dWidth;
private:
double m_dHeight;
//方法
public:
//返回体积
double m_getVolume(void) { return m_dLength * m_dWidth * m_dHeight; };
void m_setLength(double dHeight) { m_dHeight = dHeight; };
};
生成一个对象,运行下结果试试。
int main()
{
Box BoxPool(20, 10, 5);
cout << BoxPool.m_getVolume() << endl;
}
//输出: 1000
聊到了构造函数,那么它的最佳CP析构函数就不得不讲一下了。
4. 析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
这里有一篇关于析构函数的讲解https://blog.csdn.net/weizhee/article/details/562833,析构函数还是很重要滴。
(2) 继承
继承字面含义就可以知道是谁继承了谁的意思,那么就存在一个主体和继承者。在面向对象里,我们成这个主体为基类,继承者为派生类。派生类拥有者基类的一切。公有继承、保护继承和私有继承,个人的理解,就是将继承过来的属性(就是自己没有的),改变他们的访问级别,public最低级,什么都不改;protected,将基类的public更改为protected,private不变;private,全部更改为private。
继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
公有继承、保护继承、私有继承具体讲解可以看这里https://www.cnblogs.com/weiyouqing/p/9648563.html
这里就给出一个公有继承的例子用于理解继承吧,代码取自菜鸟教程。
#include <iostream>
using namespace std;
// 基类: 形状
class Shape
{
protected:
int m_nWidth;
int m_nHeight;
public:
void m_setWidth(int nWidth) { m_nWidth = nWidth; };
void m_setHeight(int nHeight) { m_nHeight = nHeight; };
};
// 派生类: 长方体
class Rectangle : public Shape
{
public:
int getArea() { return m_nHeight * m_nWidth; };
};
int main()
{
Rectangle Rect;
Rect.m_setWidth(5);
Rect.m_setHeight(7);
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
}
//输出: Total area: 35
现实当中孩子的样貌会分别继承父亲和母亲的一些特征。在继承中,这叫做多继承。
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 基类 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
// 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
// 输出: Total area: 35
// Total paint cost: $2450
了解了上面的继承方式,我们可能会提交这样的疑问,如果基类有一辆车,子类也有一辆相同的车,那么该如何区分呢?
(3) 多态
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
#include <iostream>
using namespace std;
class A
{
public:
void Show() { cout << "A::Show" << endl; };
};
class B : public A
{
public:
void Show() { cout << "B::Show" << endl; };
};
class C : public A
{
public:
void Show() { cout << "C::Show" << endl; };
};
int main(void)
{
B b;
b.Show();
C c;
c.Show();
A* a = &b;
a->Show();
a = &c;
a->Show();
}
/*
输出:
B::Show
C::Show
A::Show
A::Show
*/
上面就是一个很明显的多态例子,但是为什么会输出连个A::Show呢。因为a的静态类型是A*,函数调用在程序执行前就准备好了。有时候这也被称为早绑定。而b, c的动静态类型是都B, C。
C++中的静态绑定和动态绑定: https://www.cnblogs.com/lizhenghn/p/3657717.html
那么该如何更改呢?这时候引入一个名词:虚函数
1. 虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
上例中可以更改为:
class A
{
public:
virtual void Show() { cout << "A::Show" << endl; };
};
/*
输出:
B::Show
C::Show
B::Show
C::Show
*/
现实当中,我们将孔雀、老虎、狮子等抽象为动物作为基类,具体的动物则作为派生类。很明显,派生类可以实例化为具体的事物,但是如果将基类-动物实例化,明显是不合理的。这时候我们就需要引入纯虚函数,来告诉用户不可实例化。
2. 纯虚函数
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加"=0"。声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。
虚函数和纯虚函数的区别: https://blog.csdn.net/Hackbuteer1/article/details/7558868
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
class abstractcls
{
public:
abstractcls(float speed, int total) //构造函数
{
this->speed = speed;
this->total = total;
}
virtual void showmember() = 0; //纯虚函数的定义
protected:
float speed;
int total;
};
class car : public abstractcls
{
public:
car(int aird, float speed, int total) :abstractcls(speed, total)
{
this->aird = aird;
}
virtual void showmember()
{
cout << speed << "--------" << total << "-----------" << aird << endl;
}
protected:
int aird;
};
int main()
{
car b(250, 150, 4);
b.showmember();
return 0;
}
/*
输出:
150--------4-----------250
*/