从OOP的角度重看C++(三)——类属、静态变量和抽象类
在上节《从OOP的角度重看c++(二)》中讲到了在面向对象中,仅有5个基本概念(类、对象、方法和消息传递,继承)是很不够的,在以类为中心的概念中,我们又扩展出了重置和过载这两个概念。在本节中,我们继续扩展,包括:类属类、数据成员值的共享以及没有实例的类(其实,这3个概念依然是以类为中心的)。
首先,看类属这个概念。在第一次的博客中,我们从ADT这个概念中提到了类属(generic)这个概念。他是一种更高层次的抽象(类是对象的抽象,而类属类则是类的抽象),允许将类型作为参数的抽象结构。C++中体现类属的机制是 模板(template)。包含有 类模板和函数模板。类模板的一个重要作用是对类库提供了强有力的支持。
首先我们主要探讨一下类模板。类模板的定义格式为:
template<calss C> calss name{} 在里面定义数据成员和成员函数的时候可以用类型参数C来定义。合法的模板参数有:
从以上可以看出来,参数必须要是 有地址的!比如: constant可以但是string literal (字符串字面值)不行就是因为constant 在堆中存储,可以字符串字面值只有值没有地址!(因为常量在预处理的时候就已经展开成符号标识,是不占空间的;只有变量占用空间,constant类型的常量本质上是个只读变量)因为, 类模板产生类是在编译时而不是运行时!编译的时候是没有办法提前知道值的。a type
a constant expression(a string literal is not acceptable)
the address of an object or function with external linkage
a non-overloaded pointer to member
尽管类模板产生类和类生成对象很像,但是他们是不同的:此外,参数列表可以声明多个形参,并且,先声明的形参可以立刻用来声明同一个参数表中的其他形参,比如 template <class T ,T def_val> class Count{// }.
可以继承。因为类模板也是类型。既可以继承其他类模板的实例,也可以继承其他类。比如: template<class T> class Vec: public Vector<T>{};甚至,在继承其他类模板实例的时候,可以讲自己实例化后得出的类型作为那个类模板的实参! 总之,在所有允许出现类的地方,都可以出现类模板的实例!
用类模板产生类必须要与用这个类产生对象同时进行。这是因为类模板没有名字,么有名字就意味着没有存储空间;这归根结底还是刚才说的它产生类是在编译时而非运行时。(这里有个细节问题,对类属类的实例化编译,一般是滞后到链接时才进行的。因此,如果类属类本身发生了修改,就需要编译那些与该类属类实例化相关的所有文件,因此在make文件中药定义相应的依赖关系)。
此外,对象的定义是:可以根据操作来改变状态。而类模板除了可以生成类以外,没有任何操作,这个类的结构是不可以改变的。
有了类模板的定义,老师当堂讲了一个例子,感觉超棒!要求使用类模板的定义来实现一个接口相同、按列存储的、任意元素类型的二维数组。答案如下(反正我不会,,)
从这里我们可以看出以下几个问题:
从这里,我们可以看出: 不同层次(功能也许类似)的数据和操作,应当分离到不同的类/类模板中,再设计相应的关联结构和协同操作。数据成员对象也可以表示类之间的层次关系。在这里,我们把一个二维的数组拆成两个一维的数组去做就是这个意思,而传递行号的icurrentrow就是二者之间的关联结构。还有我们可以看出,简化操作的接口有利于理解和使用,但是需要更多的支持结构和操作,我们这里就是这样,为了方便取下标这个操作,设计了两个类。但是,设计复杂的接口也许操作会更容易,但是可用性却较差。(PS: 我在每次写博客的时候一方面是为了复习老师所讲的知识,做个记录,给像我一样的初学者参考;更主要的,我是希望从最最基本的概念里面找到优秀的设计思维)使用了friend,即限定了对外的访问控制,说明只有arrary 可以操作它,从实例的生成就设置了能见度控制;
在arraytmp和array之间传递的行号是通过icurrentrow来传递的;
充分考虑了取下标操作[ ]是二元操作符,因此才会这么麻烦的写一个arraytmp来
关于类属函数,实际中貌似用的并不多。需要注意的是,由于参数的不同,在里面使用和和参数相关的操作的时候,注意这种类型过载了这种操作,或者直接在参数列表中应用相应函数的指针,这样就避免了刚才说过的问题!课堂上有一点我记住了,就是自己写的排序算法再好,都没有库里面的好!为什么,请看他们的函数声明:
基于比较的排序算法中,我们不能确定待排序的内容都已经过载了 >,<,=这些操作符,所以,我们的参数列表中给出了一个函数指针来确保!不愧是库函数啊!想的真周到~void qsort(const void* base, size_t nelem, size_t width, int (*fcmp)(const void* ,const void*))
数据成员只的共享,简单来说,就是这个类的所有实例都共享的数据,这个数据是以数据成员的方式出现的,在C++中就是静态数据成员(static)。一方面他支持了对象共享数据的情况,同时又具备了数据成员的特征。
没有实例的类。在OOP的发展之初,应该是没有的;但是有了继承之后,就可以了,因为,可能需要在类层次结构较高的层次上存在着始终没有实例的类。在C++中,抽象类(abstract classes)就是干这个的。他的定义是;至少含有一个纯虚拟函数类(纯虚拟函数的定义是:不用也不允许定义方法体的虚拟函数)。他对于软件结构设计的作用是:规范了一组语义允许不同,语法必须应该相同的操作接口。
从我们上面的讲述来看,数据成员的共享和抽象类, 都有一箭双雕的作用!!这种设计思维是我们最应该学习的!