C++之面向对象程序设计的基本特点(抽象、封装、继承、多态)
https://blog.youkuaiyun.com/Mary19920410/article/details/69936438
面向对象程序设计的主要特点:抽象、封装、继承、多态。
1、抽象
编写程序的目的就是描述和解决现实世界中的问题。第一步就是将现实世界中的对象和类如实的反映在程序中。
定义:对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。
两个方面:数据抽象和行为抽象。
例1:加入我们要在计算机上实现一个简单的时钟程序,通过对时钟进行分析可以看出,需要3个整型数据来存储时间,分别表示时、分、秒——这就是对时钟所具有的数据进行抽象。另外,时钟要具有显示时间、设置时间等基本功能——这就是对它的行为抽象。用C++的变量和函数可以将抽象后的时钟属性描述如下:
-
//数据抽象
-
int hour, minute, second;
-
//行为抽象(也叫功能抽象)
-
ShowTime();
-
SetTime();
例2:对人进行抽象。
-
//数据抽象
-
char *name, *sex, *age;
-
//行为抽象
-
Eat();
-
Walk();
-
Work();
-
Study();
2、封装
定义:将抽象得到的数据和行为相结合,形成一个有机的整体。也就是将数据与操作数据的函数代码进行有机的结合,形成“类”。
例:在抽象的基础上,我们将时钟的数据和功能封装起来,构成一个时钟类:
-
class Clock{
-
public:
-
void SetTime(int NewH, int NewM, int NewS);
-
void ShowTime();
-
private:
-
int Hour, Minute, Seconds;
-
};
通过封装,使一部分成员充当类与外部的借口,而将其它的成员隐藏起来。达到对成员访问控制权限的合理控制。
将数据和代码封装为一个可重用的程序模块,在编写程序时就可以有效的利用已有的成果。通过外部接口依据特定的规则就可以使用封装好的模块,使用时也不必了解类的实现细节。
3、继承
现实世界中,很多事物之间都有着复杂的联系。继承便是其中一种:汽车和自行车都从属于“交通工具”,但是它们在外观和功能上都各具不同,各有千秋。同样,在面向对象的程序设计中,提供了类的继承机制。大大提高了代码的可重用性和可扩充性。
被继承的类:基类。通过继承得到的新类:派生类。
派生类继承了基类的所有数据成员和除构造函数和析构函数之外的函数成员。
同名隐藏:如果派生类声明了一个和基类成员同名的新成员,这时在派生类中或者通过派生类的对象,直接使用成员名就只能访问到派生类中声明的同名成员。
继承方式:public、protected、private
1)公有继承:基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员在派生类中不可直接访问。(也就是说基类的公有成员和保护成员在派生类中,访问属性不变,仍作为派生类的公有成员和保护成员,派生类的其它成员可以直接访问它们,在类外只能通过派生类的对象访问从基类继承来的公有成员)
-
#include <iostream>
-
using namespace std;
-
class Point{
-
public:
-
void InitP(float xx = 0, float yy = 0) { X = xx, Y = yy; }
-
void Move(float xOff, float yOff) { X += xOff; Y += yOff; }
-
float GetX() {return X;}
-
float GetY() {return Y;}
-
private:
-
float X, Y;
-
};
-
class Rectangle : public Point{
-
public:
-
void InitR(float x, float y, float h, float w)
-
{ InitP(x, y); W = w; H = h; } //派生类访问基类公有成员
-
float GetH(){return H;}
-
float GetW(){return W;}
-
private:
-
float W, H;
-
};
-
int main()
-
{
-
Rectangle rect;
-
rect.InitR(2, 3, 20, 10);
-
rect.Move(3, 2);
-
cout << rect.GetX() << " "
-
<< rect.GetY() << " "
-
<< rect.GetW() << " "
-
<< rect.GetH() << endl;
-
return 0;
-
}
2)私有继承:基类的公有和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可直接访问。(派生类的其它成员可以直接访问它们,但是在类外部通过派生类的对象无法直接访问它们)
-
#include <iostream>
-
using namespace std;
-
class Point{
-
public:
-
void InitP(float xx = 0, float yy = 0) { X = xx, Y = yy; }
-
void Move(float xOff, float yOff) { X += xOff; Y += yOff; }
-
float GetX() {return X;}
-
float GetY() {return Y;}
-
private:
-
float X, Y;
-
};
-
class Rectangle : private Point{
-
public:
-
void InitR(float x, float y, float h, float w)
-
{ InitP(x, y); W = w; H = h; } //派生类访问基类公有成员
-
void Move(float xOff, float yOff){ Point :: Move(xOff, yOff); }
-
float GetX(){return Point :: GetX();}
-
float GetY(){return Point :: GetY();}
-
float GetH(){return H;}
-
float GetW(){return W;}
-
private:
-
float W, H;
-
};
-
int main()
-
{
-
Rectangle rect;
-
//rect.InitP(2, 3);//错误,私有继承,派生类对象不能访问基类成员
-
rect.InitR(2, 3, 20, 10);
-
rect.Move(3, 2);
-
cout << rect.GetX() << " "
-
<< rect.GetY() << " "
-
<< rect.GetW() << " "
-
<< rect.GetH() << endl;
-
return 0;
-
}
3)保护继承:基类的公有和保护成员都以保护成员身份出现在派生类中,而基类的私有成员不可直接访问。这样,派生类的其他成员就可以直接访问从基类继承来的公有和保护成员,但在类外部通过派生类的对象无法直接访问它们。
所以在派生类中,成员的访问属性可以分为四类:
- 不可访问成员:从基类私有成员继承而来,派生类内部成员或者派生类对象均无法访问
- 私有成员
- 保护成员
- 公有成员
类型兼容性原则:在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,凡是基类能解决的问题,公有派生类也可以解决。类型兼容规则所指的替代包括以下情况:
- 派生类的对象可以赋值给基类对象
- 派生类的对象可以赋值给基类引用
- 派生类对象的地址可以赋值给基类指针
替代之后,派生类的对象就可以作为基类对象使用,但只能使用从基类继承的成员。
例如下例中,
-
#include <iostream>
-
using namespace std;
-
class B0{
-
public:
-
void dispaly() { cout << "B0 :: dispaly()" << endl; } //
-
};
-
class B1 : public B0{//公有派生类B1
-
public:
-
void dispaly() { cout << "B1 :: dispaly()" << endl; }
-
};
-
class D1 : public B1{//公有派生类D1
-
public:
-
void dispaly() { cout << "B2 :: dispaly()" << endl; }
-
};
-
void fun( B0 *ptr){
-
ptr -> dispaly();
-
}
-
int main()
-
{
-
B0 b0;
-
B1 b1;
-
D1 d1;
-
B0 *p;
-
p = &b0;
-
fun(p);
-
p = &b1;
-
fun(p);
-
p = &d1;
-
fun(p);
-
return 0;
-
}
运行结果为
4、多态
定义:同样的消息被不同类型的对象接收时导致不同的行为。所谓消息是指对类的成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的成员函数。
多态从实现的角度,可以分为编译时多态和运行时多态。
编译时多态在编译的过程中确定同名操作的具体操作对象,运行时多态在运行过程中才动态地确定操作所针对的具体对象。这种确定操作的具体对象的过程就叫做绑定(也叫联编)。用面向对象的术语讲,就是把一条消息和一个对象的方法相结合的过程。
绑定工作在编译链接阶段完成的叫做静态绑定。在程序运行阶段完成的情况叫做动态绑定。
在讲继承时,我们有提到类型兼容性原则,也就是在派生类的对象可以作为基类使用,但是只能使用从基类继承来的成员。如果我们需要通过基类的指针指向派生类的对象,并访问某个与基类同名的函数,那么就要在基类中将这个同名函数说明为虚函数。
例如:上一个例子中,我们只需要把基类的函数设置为虚函数,结果就不一样了,
-
#include <iostream>
-
using namespace std;
-
class B0{
-
public:
-
virtual void dispaly() { cout << "B0 :: dispaly()" << endl; } //设置为虚函数
-
};
-
class B1 : public B0{//公有派生类B1
-
public:
-
void dispaly() { cout << "B1 :: dispaly()" << endl; }
-
};
-
class D1 : public B1{//公有派生类D1
-
public:
-
void dispaly() { cout << "B2 :: dispaly()" << endl; }
-
};
-
void fun( B0 *ptr){
-
ptr -> dispaly();
-
}
-
int main()
-
{
-
B0 b0;
-
B1 b1;
-
D1 d1;
-
B0 *p;
-
p = &b0;
-
fun(p);
-
p = &b1;
-
fun(p);
-
p = &d1;
-
fun(p);
-
return 0;
-
}
结果:
这篇文章是在看《C++语言程序设计》时总结的。
以前还总结过一篇关于封装、继承、多态:C++之封装、继承、多态,有兴趣可以看看,让自己掌握更熟练一些。