将接口与实现分离
我们上篇讲的Point类仅在单个源文件的上下文中完全使用该类时才有用(也就是说在其他app中它是不能被使用的)。所以通常在库中导出类定义通常更有用,从而使这些定义可用于更广泛的应用程序。在这种情况下,我们要做的是创建一个point.h文件,作为类的接口和一个单独的point.cpp文件,其中包含相应的实现。
从前面的接口系列可以看出,接口通常仅包含其函数的原型,而不是完整的实现。类中的方法也是如此。 界面中的类定义仅包括方法原型,而将这些方法的代码放到实现的cpp中。 因此,头文件看起来与看到的其他库的头文件类似,(除了原型出现在类定义中)。然而,实现文件的结构有些不同。
在C++中,将类的接口与其实现分开时,类定义本身只存在于.h文件中。相应的代码作为独立的方法定义出现在.cpp文件中,它们不是以原型的方式嵌套在类定义中。因此,方法定义需要以不同的方式指定它们所属的类。
在C++中,这个标识是通过在类名之前添加类名作为限定词来实现的,用两个冒号分隔两个名字。因此,Point类中的getX方法的完全限定名称是Point :: getX。
下面的代码为 Point类提供了一个完整的接口:
头文件
Point.h
#ifndef _Point_h
#define _Point_h
#include <string>
class Point {
public:
/*
*构造函数: Point();
*用法:Point p
* Point pt(xc,yc)
* ------------------------
*创建一个Point类的对象,默认构造函数将默认初始的坐标为(0,0)
*第二种形式是用户自行确定x,y的取值
*/
Point();
Point(int xc, int yc);
/*
*方法:getX,getY
*用法: int x = pt.getX()
* int y = pt.getY()
*------------------------
*用途: 返回Point类中的,x 与 y的值
*/
int getX();
int getY();
/*
*方法:toString
*用法:string str = pt.tostring()
*------------------------
*用途: 以“(x,y)”形式返回Point的字符串表示形式。
*/
std::string toString();
private:
int x;
int y;
};
#endif
相对应的实现如下:
实现文件
Point.cpp
#include <string>
#include "Point.h"
#include "strlib.h"
using namespace std;
Point::Point(){
x = 0;
y = 0;
}
Point::Point(int xc, int yc) {
x = xc;
y = yc;
}
int Point::getX(){
return x;
}
int Point::getY(){
return y;
}
string Point::toString(){
return "(" + integerToString(x) + "," + integerToString(y) + ")";
}
integerToString参考我的博客C++抽象编程——字符串与整数之间的相互转换详解,在这里我直接把它写进来了我的“strlib.h”文件中,我直接导入就好了。关于“strlib.h”文件,可以参考我的文章(内容很丰富):
strlib.h,一些常见的字符串处理函数
虽说到现在为止还挺好。但是point.h文件仍然指定客户端不感兴趣的某些细节。实例变量x和y实际上是实现的一部分,在接口中就可以让每个人看到,即使客户端没有直接访问这些变量。即使这些声明出现在标记为私有的部分,如果客户端根本看不到这些细节,那就更好了。
不幸的是,C++的规则使得从客户端隐藏实例变量的声明变得非常棘手。在语法上,类的公共和私有部分必须包含在类体内。鉴于编译器必须能够看到整个类的定义,因此无法阻止客户端查看私有部分的内容。 即使如此,我们仍然避免将所有信息出现在客户端的脸上。