《C++编程思想》第三章 隐藏实现 (原书代码+习题+解答)

来源:互联网 发布:linux查看配置文件 编辑:程序博客网 时间:2024/04/30 15:02

一.相关知识点 

1.  在C++中,存取控制并不是面向对象的特征,但它为类的创建者提供了很有价值的访问控制。类的用户可以清楚地看到,什么可以用,什么应该忽略。更重要的是,它保证了类的用户不会依赖任何类的实现细节。有了这些,我们就能更改类的实现部分,没有人会因此而受到影响,因为他们并不能访问类的这一部分。一旦我们有了更改实现部分的自由,就可以在以后的时间里改进我们的设计,而且允许犯错误。要知道,无论我们如何小心地计划和设计,都可能犯错误。知道犯些错误也是相对安全的,这意味着我们会变得更有经验,会学得更快,就会更早完成项目。一个类的公共接口部分是用户能看到的。所以在分析设计阶段,保证接口的正确性更加重要。但这并不是说接口不能作修改。如果我们第一次没有正确地设计接口部分,我们可以再增加函数,这样就不需要删除那些已使用该类的程序代码。

2.可见的实现部分
    有些项目不可让最终用户看到其实现部分。例如可能在一个库的头文件中显示一些策略信息,但公司不想让这些信息被竞争对手获得。比如从事一个安全性很重要的系统 (如加密算法),我们不想在文件中暴露任何线索,以防有人破译我们的代码。或许我们把库放在了一个“有敌意”的环境中,在那里程序员会不顾一切地用指针和类型转换存取我们的私有成员。在所有这些情况下,就有必要把一个编译好的实际结构放在实现文件中,而不是让其暴露在头文件中。

3.句柄类( handle classes)
    C++中的存取控制允许将实现与接口部分分开,但实现的隐藏是不完全的。编译器必须知道一个对象的所有部分的声明,以便创建和管理它。我们可以想象一种只需声明一个对象的公共接口部分的编程语言,而将私有的实现部分隐藏起来。但 C + +在编译期间要尽可能多地做静态类型检查。这意味着尽早捕获错误,也意味着程序具有更高的效率。然而这对私有的实现部分来说带来两个影响:一是即使程序员不能轻易地访问实现部分,但他可以看到它;二是造成一些不必要的重复编译。

二.相关代码

1.

#include <iostream>using namespace std;/*public.cpp*/struct A{int i;char j;float f;void foo();};void A::foo(){}struct B{public:int i;char j;float f;void foo();};void B::foo(){}int main(){return 0;}

2.

#include <iostream>using namespace std;/*private.cpp*/struct B{private:char j;float f;public:int i;void foo();};void B::foo(){i = 0;j = '0';f = 0.0;}int main(){B b;b.i = 1;//OK public//b.j = '1';Illegal,private//b.f = 1.0;Illegal,privatereturn 0;}


3.保护(protected)
最后一种存取指定符是 protected。 protected与private基本相似,只有一点不同:继承的结构可以访问protected成员,但不能访问private成员


4.

#include <iostream>using namespace std;/*friend.cpp*/struct X;struct Y         //struct Y必须在它的成员Y :: f(X*)被声明为struct X的                 //一个友元之前声明 ,但Y :: f(X*)要被声明, struct X又必须先声明{void f(X*);};struct X{private:int i;public:void initialize();friend void g(X*, int);friend void Y::f(X*);friend struct Z;//friend struct Z是一个不完全的类型说明,并把整个 struct都当作一个友元。friend void h();};void X::initialize(){i = 0;}void g(X* x, int i){x->i = i;}void Y::f(X* x){x->i = 47;}struct Z{private:int j;public:void initialize();void g(X* x);};void Z::initialize(){j = 99;}void Z::g(X* x){x->i += j;}void h(){X x;x.i = 100;}int main(){X x;Z z;z.g(&x);return 0;}


5.

#include <iostream>using namespace std;/*nestfrnd.cpp*/#include <string.h>#define SZ 20/*struct holder包含一个整型数组和一个 p ointer,我们可以通过 pointer来存取这些整数。因为pointer与holder紧密相连,所以有必要将它作为 struct中的一个成员。一旦 pointer被定义,它就可以通过下面的声明来获得存取 holder的私有成员的权限:friend holder : :pointer ;注意,这里struct关键字并不是必须的,因为编译器已经知道 pointer是什么了*/struct holder{private:int a[SZ];public:void initialize();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);};friend holder::pointer;};void holder::initialize(){memset(a,0,SZ*sizeof(int));}void holder::pointer::initialize(holder* H){h = H;p = h->a;}void holder::pointer::next(){if(p < &(h->a[SZ-1])){p++;}}void holder::pointer::previous(){if(p > &(h->a[0])){p--;}}void holder::pointer::top(){p = &(h->a[0]);}void holder::pointer::end(){p = &(h->a[SZ-1]);}int holder::pointer::read(){return *p;}void holder::pointer::set(int i){*p = i;}int main(){holder h;holder::pointer hp,hp2;int i;h.initialize();hp.initialize(&h);hp2.initialize(&h);for(i = 0;i < SZ;++i){hp.set(i);hp.next();}hp.top();hp2.end();for(i = 0;i < SZ;++i){cout<<"hp = "<<hp.read()<<","<<"hp2 = "<<hp2.read()<<endl;hp.next();hp2.previous();}return 0;}


6.

#include <iostream>using namespace std;/*class.cpp*/struct A{private:int i,j,k;public:int f();void g();};int A::f(){return i + j + k;}void A::g(){i = j = k = 0;}class B//然而class在C + +中的使用逐渐变成了一个非必要的关键字。它和 struct       //的每个方面都是一样的,除了class中的成员缺省为私有的,而 struct中的   //成员缺省为public{int i,j,k;public:int f();void g();};int B::f(){return i + j + k;}void B::g(){i = j = k = 0;}class X//许多人喜欢用一种更像struct的风格去创建一个类,       //因为可以通过以 public开头来重载 “缺省为私有”的类行为{public:void interface_function();private:void private_function();int internal_representation;};int main(){return 0;}


7.

#include <iostream>using namespace std;/*stash.h*/#ifndef STASH_H_#define STASH_H_class stash{int size;                 //Size of each spaceint quantity;             //Number of storage spacesint next;                 //Next empty spaceunsigned char* storage;   //storage指针是一个unsigned char*。这是 C 编译器支持的最小的存储片,尽管在某些机器                          //上它可能与最大的一般大,这依赖于具体实现。 storage指向的内存从堆中分配void inflate(int increase);/*inflate()函数使用realloc()为stash得到更大的空间块。 realloc()把已经分配而又希望重分配的存储单元首地址作为它的第一个参数(如果这个参数为零,例如 initialize()刚刚被调用时,realloc()分配一个新块)。第二个参数是这个块新的长度,如果这个长度比原来的小,这个块将不需要作拷贝,简单地告诉堆管理器剩下的空间是空闲的。如果这个长度比原来的大,在堆中没有足够的相临空间,所以要分配新块,并且要拷贝内存。 assert()检查以确信这个操作成功。(如果这个堆用光了, malloc()、 calloc()和realloc()都返回零。)*/public:void initialize(int Size);//initialize()完成对 struct stash 的必要的设置,即设置内部变量为适当的值。最初,设置                          //storage指针为零,设置size 指示器也为零,表示初始存储未被分配。void cleanup();           //清除int add(void* element);   //add()函数在stash的下一个可用位子上插入一个元素。首先,它检查是否有可用空间,如                          //果没有,它就用后面介绍的 inflate() 函数扩展存储空间。void* fetch(int index);   //fetch()首先看索引是否越界,如果没有越界,返回所希望的变量地址,地址的计算采用与                          //add()中相同的方法int count();              //返回所存储空间大小};#endif


8.

#include <iostream>using namespace std;/*stack.h*/#ifndef STACK_H_#define STACK_H_class stack//这个嵌套 struct 称为 link,它包括指向这个表中的下一个 link 的指针和指向存放在 link 中的数据的指针,如果 next 指针是零,意味着表尾。{struct link{void* data;link* next;void initialize(void* Data, link* Next);}*head;public:void initialize();void push(void* Data);void* peek();void* pop();void cleanup();//cleanup 去除每个栈元素,并释放data 指针};#endif


9.

#include <iostream>using namespace std;/*减少重复编译handle.h句柄类*/#ifndef HANDLE_H_#define HANDLE_H_class handle{struct cheshire;//struct cheshire;是一个没有完全指定的类型说明或类声明(一个类的定义包含类的主体)cheshire* smile;public:void initialize();void cleanup();int read();void change(int);};#endif

#include "handle.h"#include <stdlib.h>#include <assert.h>struct handle::cheshire//cheshire 是一个嵌套结构,所以它必须用范围分解符定义struct handle::cheshire {//在handle::initialize()中,为cheshire struct分配存储空间在handle::cleanup()中这些空间被释放{int i;}void handle::initialize(){smile = (cheshire*)malloc(sizeof(cheshire));assert(smile);smile->i = 0;}void handle::cleanup(){free(smile);}int handle::read(){return smile->i;}void handle::change(int x){smile->i = x;}

#include "handle.h"/*客户程序员唯一能存取的就是公共的接口部分,因此,只是修改了在实现中的部分,这些文件就不须重新编译*/int main(){handle u;u.initialize();u.read();u.change(1);u.cleanup();return 0;}


三.习题+解答

1) 创建一个类,具有public、 private 和protected数据成员和函数成员。创建该类的一个对象,看看当试图存取所有的类成员时会得到一些什么编译信息。

#include <iostream>using namespace std;class people{string phonenumber;public:int age;string name;string sex;protected:string address;};int main(){people p;p.age = 19;p.name = "John";p.sex = "man";//p.address = "ShangHai";     protected错误无法访问//p.phonenumber = "123456789";private错误无法访问return 0;}


a.error C2248: 'address' : cannot access protected member declared in class 'people'

b.error C2248: 'phonenumber' : cannot access private member declared in class 'people'

2) 创建一个类和一个全局friend函数来处理类的私有数据。

#include <iostream>using namespace std;struct A{int i;char c;float f;public:void initialize();void g();friend void h();};void A::initialize(){i = 0;c = '0';f = 0.0;}void A::g(){cout<<i<<" "<<c<<" "<<f<<endl;}void h(){A a;a.i = 1;a.c = '1';a.f = 1.0;cout<<a.i<<" "<<a.c<<" "<<a.f<<endl;}int main(){A b;b.initialize();b.g();h();return 0;}


3) 修改 HANDLE.CPP中的cheshire,重新编译和连接这一文件,但不重新编译USEHANDL.CPP。

#include "handle.h"#include <stdlib.h>#include <assert.h>struct handle::cheshire//cheshire 是一个嵌套结构,所以它必须用范围分解符定义struct handle::cheshire {                       //在handle::initialize()中,为cheshire struct分配存储空间在handle::cleanup()中这些空间被释放{int i;int* p;};void handle::initialize(){smile = (cheshire*)malloc(sizeof(cheshire));assert(smile);smile->i = 1;smile->p = NULL;}void handle::cleanup(){free(smile);}int handle::read(){return smile->i;}void handle::change(int x){smile->i = x;}


上述答案仅供参考,如有错误希望大家指出,谢谢大家~

0 0
原创粉丝点击