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

一.相关知识点

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,private

	return 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 space
	int quantity;             //Number of storage spaces
	int next;                 //Next empty space
	unsigned 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;
}

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-15 05:02:01

《C++编程思想》第三章 隐藏实现 (习题+解答)的相关文章

java编程思想--第三章 操作符

额...继续搞些容易忽略的东西在下面,这章没打算精读,赶紧过,好戏应该在后面. 1.基本类型的赋值 比如 : int a =1; int b = a; 是进行值的复制,以后改变了a的值对b没有影响 引用类型的复制 比如: List<String> list1 = new  ArrayList<String>(); List<String> list2 = list1; 是进行的引用的复制,list1与list2 会同时指向 那个 ArrayList ,一个改变了它的状态

C++ 编程思想 第三章 3-2

#include <iostream> #include <cmath> // for sqrt() using namespace std; #define MAX 100 //质数 int main() { cout << "2"; int i; int j; for (i = 3; i < MAX + 1; i += 2) { for ( j = 3; j <= sqrt(i); j += 2) { if (i % j == 0)

java编程思想笔记(第一章)

Alan Kay 第一个定义了面向对象的语言 1.万物皆对象 2.程序是对象的集合,他们彼此通过发送消息来调用对方. 3.每个对象都拥有由其他对象所构成的存储 4.每个对象都拥有其类型(TYpe) 5.某一特定类型的所有对象都可以接收同样的消息. Booch提出一种更简洁的描述: 对象拥有状态(state) 行为(behavior) 和标识(identity) 每个对象都有一个接口 每个对象都属于定义了特性和行为的某个类(特性可以理解为属性的状态,行为可以理解为method) 在面向对象的程序设

Java编程思想笔记(第二章)

第二章  一切都是对象 尽管Java是基于C++的,但相比之下,Java是一种更纯粹的面向对象程序设计语言. c++和Java都是杂合型语言(hybird language) 用引用(reference)操作对象 类似遥控器(引用)来操作电视(对象) 在Java中你可以创建一个引用,但是没有与任何对象关联,比如: String s; 这个时候如果用则会报错.安全的做法是: 创建一个引用的同时并进行初始化 String s="1111"; 必须由你创建所有对象 New关键字的意思是给我一

Java编程思想——第17章 容器深入研究(two)

六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add 在尾部增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常 remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常 element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementExce

《Python核心编程》 第五章 数字 - 课后习题

课后习题  5-1 整形. 讲讲 Python 普通整型和长整型的区别. 答:普通整型是绝大多数现代系统都能识别的. Python的长整型类型能表达的数值仅仅与你机器支持的(虚拟)内存大小有关. 5-2 运算符 (a) 写一个函数,计算并返回两个数的乘积 (b) 写一段代码调用这个函数,并显示它的结果 答: def pro(a,b): p = a*b return p a = int(raw_input("a=")) b = int(raw_input("b="))

Java 编程思想 第五章 ----初始化与清理(1)

从今天开始每天一小时的java 编程思想的阅读和编码,其实就是把书上的代码抄下来. 5.5 清理:终结处理和垃圾回收 初始化和清理工作同等重要,但是清理工作却被常常忘记,但是在使用对象之后,对对象弃之不顾的做法并不是很安全.Java有自己的垃圾回收器负责回收无用的对象占据的内存资源.但也有特殊情况:假定你的内存区域不是用new获得的,这是无法用垃圾回收器释放所以java中允许在类中定义一个名为 finalize()的方法.       工作原理: 一旦垃圾回收器准备好释放对象占用的存储空间,将首

java 编程思想 一 第二章(对象)

上班之余发现有很多空闲时间,享受生活 又觉得有点空虚,而且对自己的基础知识总觉得掌握的不是很牢固,有点似懂非懂的感觉,近来刚好有时间,所以就考虑继续学习,然后再经过考虑和各大博主推荐选择了<java编程思想>这本书,在此分享学习心得跟大家共勉,也算是对自己的监督吧.(本内容需要有一定的基础才能看,类似于基础回顾,强化理解,新手可能有些地方不太能听懂) 一.什么是对象? 这并不是我们男女朋友那种对象哈哈. 简言之:万事万物皆对象. 个人理解:我们所要处理的事务或者建立的某种模型的抽象总结.具体就

Python编程入门-第三章 编写程序 -学习笔记

第三章 编写程序 1.编辑源程序.运行程序 可通过IDLE中File>New File新建一个文本以编辑源程序,编辑完成可通过Run>Run Module(或者F5快捷键)来运行程序.Python源文件都以.py格式存储. 2.从命令行运行程序 除了上述利用IDLE的集成功能运行程序的方式外,当然也可以通过命令行运行程序,命令格式为:python ‘源文件名称.py’. 3.编译源代码 当运行py格式文件时,Python会自动创建相应的.pyc文件,该文件包含编译后的代码即目标代码,目标代码基