第5章 隐藏实现
一、C++的访问控制符
public 在其后声明的所有成员可以被所有人访问。
private 表示除了该类型的创建者和类的内部成员函数之外,任何人都不能访问。
protected 继承的结构可以访问protected成员,但不能访问private结构
二、友元:朋友才能看到
C++是一种很“人性化”的语言,跟我不熟的人,都不能知道我家的家庭成员。朋友就可以。
可以声明为友元的函数类型:
1、全局函数(可以用本结构的指针作为参数)
2、另一个结构中的函数(可以用本结构的指针作为参数)
3、整个另一个结构
friend的双重声明作用:1) 声明一个函数
2) 声明这个函数为当前结构的友元
开个小差:书中有这样一个代码段
struct X;
struct Y{
void f(X*);
};
结构Y要声明其成员函数f(X*),必须知道X的定义,但X结构体又是在结构Y之后明确定义的。这种情况下可以在前面先声明一个struct X。函数要传递的是一个地址,不关X是什么类型,其类型的地址都是一个固定的值,因此编译器知道如何传递一个地址。编译是可以通过的。
但是如果声明这样一个函数void f(X);就不行了,因为编译器还不知道X的具体结构,编译会报错。
三、嵌套友元
嵌套的结构并不能自动获得访问private成员的权限。若遵循下列规则就可以
1、声明一个嵌套结构
2、声明该结构为全局范围的friend
3、定义这个结构
主要就是将声明结构与声明友元分开~!
struct Holder{
private:
int a[sz];
public:
void initialize();
struct Pointer;
friend Pointer;
struct Pointer{
private:
Holder* h;
int* p;
public:
void initialize(Holder* h);
void next();
void previous();
void top();
void end();
int read();
void set(int i);
};
};
这样,结构Pointer就可以直接访问数组a了。
再做个试验,在public区域定义一个变量int n; 如果并没有声明结构Pointer是结构Holder的友元,结构Pointer的函数仍然能够访问数据成员n。这说明,friend关键字是用来让外部函数访问类的私有成员的一种方法!!!
从第三点看出,C++并非一个纯面向对象的语言,friend关键字用来解决一些实际问题。
五、类
关键字class被用来描述一个新的数据类型。它和struct的每一个方面都是一样的,不同在于
class声明的类中的成员默认为private;
struct声明的结构体中的成员默认为public;
六、现在以C++的方式来实现一个Stash
#ifndef STASH_H
#define STASH_H
class Stash{
int size; //number of each spaces
int quantity; //number of storage spaces
int next; //Next empty space
//Dynamically allocated array of bytes:
unsigned char* storage;
void inflate(int increase);
public:
void initialize(int size);
void cleanup();
int add(void* element);
void* fetch(int index);
int count();
};
#endif//STASH_H
注意到函数void inflate(int increase);被加入到了private里面,因为它只在add(入栈时发现空间不够用了才调用),所以不能被轻易访问,事实上它也确实不需要成为一个外部接口。
七、句柄类
先说一个实际的问题。对于一个公司的核心技术,技术管理人员可能不想所有人都能看到其代码,因此交给其他程序员使用的时候,可能是交给他们一个库文件,一个头文件。只要在头文件里能找到的接口,都可以调用。另一个事实是,当一个文件被修改,或它所依赖的头文件被修改,都需要重复编译该文件。这意味着程序员无论何时修改了一个类,无论修改的是公共接口的部分还是私有成员的声明部分,他都必须编译包含这个头文件的所有文件。这就是通常所说的易碎的基类问题(fragile base-class problem)对于一个大项目而言,这是非常耗时耗力的。
解决办法是----句柄类(handle class)。有关实现的任何东西都消失了,只剩一个单指针“smile"。改指针指向一个结构,该结构的定义与其所有的成员函数的定义一同出现在实现文件中。这样,只要接口部分不改变,头文件就不需要变动。儿实现部分可以任意更改,编译的时候只需编译单独地一个文件就OK。
#ifndef HANDLE_H
#define HANDLE_H
class Handle{
struct Cheshire;
Cheshire *smile;
public:
void initialize();
void cleanup();
int read();
void change(int);
};
#endif
在头文件中并没有定义结构体Cheshire。只是声明了而已,但是这对于Cheshire类型的指针smile已经足够,编译器已经知道它是一个地址。
接下来你只需要在实现文件Handle.cpp里实现对结构Cheshire的定义即可。
#include<iostream>
#include "Handle.h"
using namespace std;
struct Handle::Cheshire{
int i;
};
void Handle::initialize()
{
smile = new Cheshire;
smile->i = 0;
}
void Handle::cleanup()
{
delete smile;
smile = NULL;
}
int Handle::read()
{
return smile->i;
}
void Handle::change(int i)
{
smile->i = i;
}
int main()
{
Handle h;
h.initialize();
cout << "smile->i = " << h.read() << endl;
h.change(5);
cout << "smile->i = " << h.read() << endl;
return 0;