C++面向对象编程思想
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C++面向对象编程思想
前面已说明设计程序就是编写程序欲解决的问题的描述,也就是编写论调。而论调可以只用“名词性概念”和“动词性概念”表现出来,对象又正好是“名词性概念”的实现,而利用前面说的没有成员变量的类来映射“动词性概念”就可以将其转换为对象。因此,一个世界,可以完全由对象组成,而将算法所基于的世界只用对象表现出来,再进行后续代码的编写,这种编程方法就被称作面向对象的编程思想。
注意,先设计算法应基于的世界,再全部用对象将其表述出来,然后再设计算法,最后映射为代码。但前面在编写商人过河问题时是直接给出算法的,并没有设计世界啊?其实由于那个问题的过于简单,我直接下意识地设计了世界,并且用前面所说的河岸论来描述它。应注意世界的设计完全依赖于问题,而准确地说,前面我并没有设计世界,而是设计了河岸论来描述问题。
接着,由于对象就是实例,因此以对象来描述世界在C++中就是设计类,通过类的实例来组合表现世界。但应注意,面向对象是以对象来描述世界,但也描述算法,因为算法也会提出一些需要被映射的概念,如前面商人过河问题的算法中的过河方案。但切记,当描述算法时操作了描述世界时定义的类,则一定要保持那个类的设计,不要因为算法中对那个类的实例的操作过于复杂而将那部分算法映射为这个类的一个成员函数,因为这严重遮蔽了算法的实现,破坏了程序的架构。如一个算法是让汽车原地不停打转,需要复杂的操作,那么难道给汽车加一个功能,让它能原地不停地打转?!这是在设计类的时候经常犯的错误,也由于这个原因,一个面向对象编写的代码并不是想象的只由类组成,其也可能由于将算法中的某些操作映射成函数而有大量的全局函数。请记住:设计类时,如果是映射世界里的概念,不要考虑算法,只以这个世界为边界来设计它,不要因为算法里的某个需要而给它加上错误的成员。
因此,将“名词性概念”映射成类,“名词性概念”的属性和状态映射为成员变量,“名词性概念”的功能映射为成员函数。那么“动词性概念”怎么办?映射成没有成员变量的类?前面也看见,由于过于别扭,实际中这种做法并不常见(STL中也只是将其作为一种技巧),故经常是将它映射为函数,虽然这有背于面向对象的思想,但要易于理解得多,进而程序的架构要简明得多。
随着面向对象编程思想的问世,一种全新的设计方式诞生了。由于它是如此的好以至于广为流传,但理解的错误导致错误的思想遍地而生,更糟糕的就是本末倒置,将这个设计方式称作面向对象的编程思想,
它的名字就是封装。
封装
先来看现在在各类VC教程中关于对象的讲解中经常能看见的如下的一个类的设计。
class Person
{ private: char m_Name[20]; unsigned long m_Age; bool m_Sex;
public: const char* GetName() const; void SetName( const char* );
unsigned long GetAge() const; void SetAge( unsigned long );
bool GetSex() const; void SetSex( bool );
};
上面将成员变量全部定义为private,然后又提供三对Get/Set函数来存取上面的三个成员变量(因为它们是private,外界不能直接存取),这三对函数都是public的,为什么要这样?那些教材将此称作封装,是对类Person的内部内存布局的封装,这样外界就不知道其在内存上是如何布局的并进而可以保证内存的有
效性(只由类自身操作其实例)。
首先要确认上面设计的荒谬性,它是正宗的“有门没锁”毫无意义。接着再看所谓的对内存布局的封装。回想在《C++从零开始(十)》中说的为什么每个要使用类的源文件的开头要包含相应的头文件。假设上面是在Person.h中的声明,然后在b.cpp中要使用类Person,本来要#include "Person.h",现在替换成下面:
class Person
{ public: char m_Name[20]; unsigned long m_Age; bool m_Sex;
public: const char* GetName() const; void SetName( const char* );
unsigned long GetAge() const; void SetAge( unsigned long );
bool GetSex() const; void SetSex( bool );
};
然后在b.cpp中照常使用类Person,如下:
Person a, b; a.m_Age = 20; b.GetSex();
这里就直接使用了Person::m_Age了,就算不做这样蹩脚的动作,依旧#include "Person.h",如下:
struct PERSON { char m_Name[20]; unsigned long m_Age; bool m_Sex; };
Person a, b; PERSON *pP = ( PERSON* )&a; pP->m_Age = 40;
上面依旧直接修改了Person的实例a的成员Person::m_Age,如何能隐藏内存布局?!请回想声明的作用,类的内存布局是编译器生成对象时必须的,根本不能对任何使用对象的代码隐藏有关对象实现的任何
东西,否则编译器无法编译相应的代码。
那么从语义上来看。Person映射的不是真实世界中的人的概念,应该是存放某个数据库中的某个记录人员信息的表中的记录的缓冲区,那么缓冲区应该具备那三对Get/Set所代表的功能吗?缓冲区是缓冲数据用的,缓冲后被其它操作使用,就好像箱子,只是放东西用。故上面的三对Get/Set没有存在的必要,而三个成员变量则不能是private。当然,如果Person映射的并不是缓冲区,而在其它的世界中具备像上面那样表现的语义,则像上面那样定义就没有问题,但如果是因为对内存布局的封装而那样定义类则是大错特错
的。
上面错误的根本在于没有理解何谓封装。为了说明封装,先看下MFC(Microsoft Foundation Class Lib rary——微软功能类库,一个定义了许多类的库文件,其中的绝大部分类是封装设计。关于库文件在说明S