面向对象的编程是基于3个基本的概念:
1.数据抽象 2.多态 3. 动态绑定
什么是数据抽象呢?
数据抽象隐藏了所描述对象的内部特征,对于外部环境而言是透明的。而外部环境只有通过使用该对象所提供的操作来表现对象的动态特征。
很容易理解,其实就是类似于动态库,我们只是知道有这样的一个函数,知道他的各个参数,知道他的功能,但是具体内部怎样实现的,我们却不知道。再举个通俗的例子,你只要给我1000元钱,我就给你买件漂亮的服装,你不用管我是怎样买的,你只要给我钱就行,这里所谓的钱,也就是类中所封装起来的成员。
多态是什么呢?其实就是继承,那继承的作用是什么呢?
再举个简单的例子:有一个父亲(类),小眼睛、平头(相当于类的数据成员),他有个儿子,儿子肯定继承了父亲的特点(小眼睛、平头),但是肯定还有自己的特点,比如喜欢抽烟、喝酒、烫头,儿子从父亲遗传来的小眼睛、平头就是继承。
动态绑定更简单,就是虚函数。举个简单的例子,老子有自己的事业(成员函数),但是做老子的不希望自己的儿子坐享其成,就偷偷给自己加了个关键字——virtual,这样,老子的事业就不会传给儿子,而是让儿子开创自己自己的事业。
但是动态绑定是有条件的:
(1) 只有指定的虚函数才能执行动态绑定
(2)必须通过基类类型的引用或指针来进行动态绑定
关于第一点我们是很容易理解的,主要是第二点。在下面的讲解中,我们会遇到一系列比较难以理解的问题。
让我们先看下面这一段逆天的代码:
// 29.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#includeusing namespace std;class A{protected: int m_data;public: A(int data = 0) { m_data = data; } int GetData() { return doGetData(); }virtual int doGetData(){ return m_data; }};//class B:public A{protected: int m_data;public: B(int data = 1) { m_data = data; } int doGetData() { return m_data; }};//class C:public B{protected: int m_data;public: C(int data = 2) { m_data = data; }};int _tmain(int argc, _TCHAR* argv[]){ C c(10); /*首先调用对象c的成员函数GetData(),因为类C没有该成员函数,所以调用类B,又因为 B也没有,所以调用类A,类A中有该函数,但是类A中的该函数又调用的doGetData()这个 虚函数,因此,又会执行类B中的doGetData()*/ cout< <
需要注意的是就近调用原则:如果父辈存在相关接口,则优先调用父辈的接口,如果父辈也不存在相关接口,则优先调用祖父辈接口。
定义一个派生类对象时,我们知道构造函数的执行顺序为:基类的构造函数->......->派生类构造函数。(需要注意的是类的对象里面有成员类的情况)但是当我们指行上述代码的 C c(10)的时候,单步调试的结果是先运行C(int data = 2),但是m_data并没有被赋值,也就是说,并没有真正执行该构造函数(即使我们将C c(10) 改为C c后,仍然先跳到C(int data =2 )处。1.这是什么原因呢?
承接上面的代码:
A a; B b; cout<<&a<
我们可以假设内存中的存放规则为这样(即使实际物理地是不连续的):
这里用m_data1,m_data2,m_data3分别代表。所以,类C实际上是有3个成员变量的。
下面再看一个例子:
// 32.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#includeusing namespace std;class A{public: virtual void f();};class B:public A{public: //virtual void f(); void f();};void A::f(){ cout<<"A"< f();//A B *b = new B; cout< < f();//B //delete(a); //delete(b);//发生悬挂 /*把基类对象绑定到派生类对象的指针或引用上,调用的是派生类对象 这是C++动态绑定的关键*/ a = b; a->f(); //输出B return 0;}
正如上面说的:将基类的引用或指针绑定到派生类对象,对基类没有影响,对象本身不会改变,仍为派生类对象。
下面再说一下C++中的继承:我们知道继承关系也可以分为公有、私有、保护继承三种,但是无论哪种,子类都不会获得比父类更多的访问权限。一本参考书中这样定义:私有继承使得父类中的所有元素与子类无法联系,因为子类只能继承父类的protected和public。我认为这种说法是不准确的,子类应该也继承了父类的私有成员,只不过,父类的私有成员无法和子类获得联系。对类所继承的成员的访问有基类中的成员访问级别和派生类列表中使用的访问标号共同决定。派生类可以进一步限制,但是不能放松对所继承的成员的访问:
为了简单起见,我们先暂时不考虑友元情况
1.共有成员:支持类内(直接访问)、派生类、用户代码区访问(通过对象调用)
2.保护成员:这个比较复杂。支持类内(直接访问)、派生类访问
定义保护成员的目的是允许派生类可以访问它,但是又不允许用户进行访问。切记,protected成员的一条重要属性是:派生类只能通过派生类对象来访问基类的protected成员,派生类对其基类类型对象的protected成员没有特殊的访问权限。
3.私有成员:类内访问(派生类无法访问)。
1.公有继承:保持基类的访问属性不变。可以访问基类的公有、保护成员。
2.保护继承:基类的共有、保护成员都变为保护成员。可以访问基类的公有成员,保护成员。但是访问保护成员的时候,只有派生类对象才可以。
3.私有继承:基类的所有的成员在派生类中都为私有成员,彻底断开了基类与派生类的关系。
话说回来,保护继承和私有继承只是在技术层面上讨论的东西,实际情况下很少用到,为了加强理解,我们对共有继承和保护继承举几个例子,见代码如下:
// 33.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#includeusing namespace std;class A{public: void _A_fun(int m_a1,int m_a2,int m_a3) { a1 = m_a1; a2 = m_a2; a3 = m_a3; } int a1;protected: int a2;private: int a3;};class B:public A{public: //void Getdata1(A &oba) //{ // oba.a1 = 10;//ok,基类的公有成员 // oba.a2 = 20;//error,基类的保护成员,我们只能通过派生类对象进行调用 // oba.a3 = 30;//error,基类的私有成员,只能在基类内部调用(不考虑友元) //} //void Getdata2(B &obb) //{ // obb.a1 = 10;//ok, // obb.a2 = 20;//ok,基类的保护成员,我们通过派生类对象进行调用 // obb.a3 = 30;//error,基类的私有成员,只能在基类内部调用(不考虑友元) //}private: int b;};class C:protected A{public: //void Getdata3(A &oba) //{ // oba.a1 = 10;//ok,基类的共有成员在公有继承和派生继承下都是可见的 // //oba.a2 = 20;//error,基类的保护成员,我们只能通过派生类对象进行调用 // //oba.a3 = 30;//error,基类的私有成员,只能在基类内部调用(不考虑友元) //} void Getdata4(C &obc) { obc.a1 = 10;//ok, obc.a2 = 20;//ok,基类的保护成员,我们通过派生类对象进行调用 //obb.a3 = 30;//error,基类的私有成员,只能在基类内部调用(不考虑友元) }private: int b;};int _tmain(int argc, _TCHAR* argv[]){ B object; return 0;}