C++之面向对象设计:封装、继承和多态

面向对象程序设计(OOP)

客观世界中任何一个实物都可以看成一个对象 (object)。或者说,客观世界是由千千万万个对象组成的。任何一个对象都应当具有两个要素,即属性和行为,一个对象往往是由一组属性和一组行为构成的。例如,一台录影机是一个对象,它的属性是生产厂家、牌子、重量等,它的行为就是它的功能,例如可以给外界给他的信息进行录像、快进、快退等操作。在一个系统中的多个对象之间通过一定的渠道相互联系,要使某一个对象实现某一种行为,应当向它传入相应的消息。在C++中,每个对象都是由数据和函数这两部分组成的,函数是用来对数据进行操作的。

面向对象的三个基本特征:

  • 封装:即数据抽象,就是把客观的事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性. 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分.
  • 继承: 是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一。是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。(如果汽车制造厂想生产一款新型汽车,一般是不会全部从头开始设计的,而是选择已有的某一型号汽车为基础,再增加一些新的功能,就研制成了新型号的汽车。这是提高生产效率的常用方法。)它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展. 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现.继承概念的实现方式有二类:实现继承与接口继承.实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
  • 多态:多态主要分为两种形式:静态多态和动态多态,静态多态主要实现了函数重载和运算符重载,动态多态主要实现了虚函数。如果有几个相似而不完全相同的对象,有时人们要求在向它们发出同一消息时,它们的反应各不相同,分别执行不同的操作,这就是多态现象。即向不同对象发生同一个消息,不同的对象在接收时会产生不同的行为(即方法)。在C++中,所谓多态性是指:由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应。多态性反映了面向对象程序设计的灵活性。

继承

        通过继承联系在一起的类构成一种层次关系。通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承而来,这些继承得到的类称为派生类。基类负责定义在层次关系中所有的类共同拥有的成员,而每个派生类定义各自特有的成员。

        在C++语言中,基类将类型相关的函数与派生类不做改变直接继承的函数区分对待。对于某些函数,基类希望它的派生类各自定义适合自身的版本,那么基类就将这些函数声明成虚函数。

class A; //基类(父类)
{
    public:
        void Func1(void);
        void Func2(void);
        int i = 0;
};

class B:public A  //子类(派生类),继承关系是public,还有两种protected、private继承关系
{
    void func3(void);
    void func4(void);
}

int main()
{
    B.b;
    b.i;         //B继承A的数据类型i
    b.func1();   //B继承A的函数成员func1
    b.func2();   //B继承A的函数成员func2
    b.func3();
    return 0;
}

虚函数

在c++语言中,基类以两种成员函数分为两类:一是基类希望其派生类进行覆盖的函数;另一种是基类希望派生类直接继承而不要改变的函数。对于前者,是通过在派生类中对基类的函数后面加一个关键字override,基类通过在其成员函数的声明语句之前加上关键字virtual,即为虚函数,当我们使用指针或者引用调用虚函数时,该调用将被动态绑定(下面会讲)。根据引用或指针所绑定的对象类型不同,该调用可能执行基类也可能执行某个派生类版本。

 OOP的核心思想是多态性,其含义是“多种形式”,简单概括为“一个接口,多种方法”,只有当程序在运行时才决定调用的函数。C++多态性是通过虚函数来实现的,虚函数允许子类(派生类)重新定义成员函数,而子类重新定义基类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。

多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
      封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

虚函数的调用:当某个虚函数通过指针或引用引用调用时,编译器产生的代码直到运行时才能确定应该调用那个版本的函数,被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的哪一个。

当我们在派生类中覆盖某个虚函数时,可以再一次使用virtual关键字指出函数性质。然而并非如此,因为一旦某个函数被声明成虚函数,则所有派生类中它都是虚函数。

抽象基类

纯虚函数是在声明虚函数时被“初始化”为0的函数在虚函数的声明之后,分号之前加一个=0就可以将一个虚函数定义成一个纯虚函数,其中,基类内部只能出现声明,不能有定义,基类只提供接口,派生类可以实现不同定义。

  • 纯虚函数没有函数体;
  • 纯虚函数只有函数的名字而不具备函数的功能,不能被调用;
  • 纯虚函数的作用是在基类中为其派生类保留一个函数名字,以便派生类根据需要对它进行定义;
  • 如果一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中任然为纯虚函数。

含有(未经覆盖直接继承)纯虚函数的类是抽象基类,因为纯虚函数是不能被调用的,包含纯虚函数的类是不能无法建立对象的,它只负责定义接口,后续其他类可以覆盖该接口。

class Shape
{
    virtual float area() const {return 0.0;}
    virtual float volume() const {return 0.0;}
    virtual void shapeName() cosnt = 0;
};

class Point:public Shape
{
public:
    Point(float =0; float= 0);
    void shapeName() const {cout<<"Point:";}
}

访问控制与继承

每个类分别控制自己的成员初始化过程,与之类似,每个类还分别控制着其他对于派生类来说是可访问的。继承的访问方式分为:公有继承、私有继承和保护继承。

#include
#include
using namespace::std;
class A
{
public:
	string pub_mem;
protected:
	int prot_mem;
private:
	char priv_mem;
};

class B : public A  //B继承A,继承关系是public
{
public:
	string func1(){ return pub_mem; } //派生类可以访问基类的public成员
	int func2() { return prot_mem; } //派生类可以访问基类的protected成员
	//char func3(){ return priv_mem; } //派生类不可以访问基类的private成员
};

class C : private A //B继承C,继承关系是private
{

	string func4() const{ return pub_mem; }
	int func5() const{ return prot_mem; }
	//char func6() const{ return priv_mem; }
};

class D : protected A //B继承C,继承关系是protected
{
	string func7() const{ return pub_mem; }
	int func8() const{ return prot_mem; }
	//char func9() const{ return priv_mem; }
};

int main()
{
	A a;
	B b;
	C c;
	D d;
	//基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,
	//而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。
	b.pub_mem;
	//b.priv_mem; //错误
	//b.prot_mem; //错误
	//基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问.
	//c.pub_mem;  //错误
	//c.priv_mem; //错误
	//c.prot_mem; //错误
	//基类的所有公有成员和保护成员都成为派生类的保护成员,
	//并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
	//c.pub_mem;  //错误
	//c.priv_mem; //错误
	//c.prot_mem; //错误
	return 0;
}
    

 

          继承的方式总结如下:

            C++之面向对象设计:封装、继承和多态_第1张图片

派生类到基类的转换

//派生类到基类的类型转化
A item;    //基类对象
B bulk;    //派生类对象
A *p = &item; //p指向了基类对象item
p = &bulk;    //p指向了派生类的基类部分
A &r = bulk;    //r绑定了派生类的基类部分

我需要知道一下几个名词:

  • 静态类型:在变量声明的类型或者表达式生成的类型,在编译时就确定;
  • 动态类型:在变量或者表达式表示的内存中的对象类型,直到运行时才确定;
  • 静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
  • 动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;

举例:

#include
using namespace::std;
class base
{
public:
	virtual void func()
	{
		cout << "The func from base." << endl;
	}
};

class child : public base
{
public:
	void func()
	{
		cout << "The func from child." << endl;
	}
};

int main()
{
	base *pb = new base;
	child *pc = new child;
	base p;
	pb->func(); //调用base::func(),因为pb的静态类型和动态类型都是base;
	pc->func(); //调用child::func(),因为pc的静态类型和动态类型都是child;
	cout << endl;

	pb = pc;
	pb->func(); //调用base::func(),因为pb的静态类型是base,动态类型是child;
	pc->func(); //调用child::func(),因为pc的静态类型和动态类型都是child;

	cout << endl;

	base p1;
	child p2;
	p1 = p2;
	p1.func(); //调用base::func(),因为静态类型在编译时就确定下来了;
	p2.func(); //调用child::func()

	return 0;
}

 结果:

C++之面向对象设计:封装、继承和多态_第2张图片

 

参考资料

http://www.cnblogs.com/iloverain/archive/2016/08/05/5726752.html

https://blog.csdn.net/hackbuteer1/article/details/7475622

 

你可能感兴趣的:(C++学习)