第十章 对象和类
爬山涉水终于来到了类的面前,该章开始,终于将于类进行零距离接触了。
OOP设计者的思考方式
采用过程性编程方法时,首先考虑要遵循的步骤,然后考虑如何表示这些数据。OOP程序员首先考虑数据——不仅要考虑如何表示这些数据,还要考虑如何使用数据。
用户与数据交互的方式有三种:初始化、更新和报告——这就是用户接口。在C++中,用户定义类型指的是实现抽象接口的类设计,一个好的接口设计应该与具体的数据关系不大,使用者无需考虑内部数据时如何运转的,修改一个类也不要去改动接口,只需要去修改实现的细节。
成员变量、函数
类不同于C语言中的结构体,类中还定义了类中数据与程序员交互的操作,成员变量和函数的作用域都在类内,定义出对象后也就是无法直接访问数据或者函数,这个操作必须由一个对象发出(静态成员除外,其由类进行限定)。对于类中的变量在其作用域为类内的基础上,为了实现数据的封装,成员变量又有公有和私有之分,后者不能在类外通过对象直接访问。假设有类名为NewType的类,那么NewType::init()是函数的限定名(qualified name)而init()是全名的缩写(非限定名,unqualified name)。
如果在类中对一个成员函数进行了定义,那么这个函数就自动成为内联函数。另外如果不在类中定义的函数而想将其变为内联函数的话,应该在函数定义或者是函数声明前加上inline,且应该将定义与头文件放在一起,这样不会导致多个文件对一个函数的多次定义。内联函数的特殊规则要求在每一个使用它们的文件中都对其进行定义(某些开发系统包含智能链接程序,允许将内联定义放在一个独立的实现文件,经测试Dev-C++不可以)。
构造函数和析构函数
构造函数和析构函数能够让一个类变得更加的智能,我们都知道结构题能够使用花括号进行初始化,而一个类如果成员存在私有成员的话将不允许这种方式,因此提供一个构造函数就能够很好的解决这个问题,而对于析构函数而言,其主要的作用就是能够在类被销毁的时候释放一些对象占用的动态内存,防止内存溢出。其实一个构造函数也可以看做是对一组数据的强制类型转换,当提供只有一个参数的构造函数时,甚至提供了隐式的类型转换,即NewType variable = 12; 如果提供一个接受一个int型作为函数的构造函数,左边的表达式完全正确,这就将类与基本类型的关系拉的更近了。
如果不提供任何构造函数,那么系统将自动调用一个什么也不做的默认的构造函数,但是如果用户定义了一个带参数的构造函数,那么系统就将不提供默认的构造函数,如果用户需要,将要求自己提供一个可接受零参数的构造函数。析构函数是能够显示的调用的,但是一般不这样做。
C++11允许使用花括号的初始化列表类初始化类,前提是参数个数要有相应的构造函数对应。
常函数
成员函数的类型众所周知都有一个this指针,但是一旦某个对象被限定为const之后那么调用该成员函数就会出错了。因此可以在成员函数定义的最后面加上一个const将函数声明为常函数,定义同样也要为加上const,这样相应的this指针也就变成了const类型了。对于类外的普通函数是不能够声明为常类型的。
对象数组
创建对象数组一定要提供默认的构造函数,大概是因为先申请内存在进行初始化的缘故的吧,因此使用花括号来初始化数组时一定会产生临时变量(会执行析构函数)。
声明类
通常,将类声明分为两部分组成,这两部分通常保存在不同的文件中。类声明(包括由函数原型表示的方法)应放到头文件中。定义成员函数的源代码放在方法文件中。这样便将接口描述与实现细节分开了。
定义头文件时,如果头文件名字为My.h,那么在开始时加上如下语句,保证不会多次编译头文件:
ifndef MY_H_
define MY_H_
....
endif
第十一章 使用类
运算符的重载
实现类的又一个进步是让其与系统类型更加靠近,换句话说C++开放了更多的权限允许我们定义各种各样的类型。运算符重载就是一个很重要的标志。其格式为:
operator op (argument-list)
假设定义了<操作符,那么对象A,B就可以直接执行A<B了,或者A.operator<(B)前者为隐式调用,而后者为显示调用。其实也就是一个函数而已,目的是为了让我们通过类比基本类型给这个操作符产生新的意义。对于系统重载的操作符如减号(-)和负号(-)我们也可以重载两次,函数的特征值也就不一样了。
如果一个重载的函数时成员变量的话,那么就会隐含一个参数,如果是友元的话,那么就需要将所有的参数都写出来了。二元操作符在隐式调用时将左边的变量作为对象来调用,右边的变量则是函数的参数。
重载限制
1.重载后的运算符必须至少有一个是用户自定义的类型,这将防止用户为标准类型重载运算符,不能为两个double型重载减号运算符。
2.使用运算符时不能违反运算符原来的句法法规。例如不能将求模运算符(%)重载成使用一个操作数。
3.不能创建新的运算符。
4.不能重载以下运算符:. ,.* ,:: ,? : ,sizeof,typeid,const_cast,dynamic_cast,reinterpret_cast,static_cast。
5.以下运算符只能重载为成员函数:=,(),[],->。
一个常用的运算符重载是通过友元函数将用于重载cout<<来输出一个对象,以及使用友元函数来实现一个常数+一个对象的情况,如果是成员函数的话系统将使用常数来调用这个重载函数,这显然是行不通的。
类的自动转换和强制类型转换
前面说过类的构造函数看起来就像是一个类型转换,能够将一个基本类型转换一个类类型。那么通过转换函数我们能够将一个类类型转换为一个基本类型。转换函数的形式如:
operator Typename()
没有参数和返回值且必须是类的成员函数,对你没有听错。如operator int() 定义了之后就能够将一个对象直接赋值给int型了。
在不发生二义性的情况下,C++是允许发生隐式转换的,例如在一个需要int型参数的地方可以直接将对象作为实参,或者需要对象作为参数的情况下,将int型作为实参。我们通常使用的string str = “hello”正是利用这种性质。可以通过在原型前加上explicit来限制隐式的转换,只接受显示的转换。例如:
explicit operator int(); 或者是 explicit bool operator !();