《C和C++代码精粹》——2.14 封装和不完全类型

2.14 封装和不完全类型

C和C++代码精粹
好的编程习惯能隐藏用户不必知道的执行细节。例如,要执行一个整数栈,可以为用户提供如下的头文件:

// mystack.h
class StackOfInt ( )
{
public:
    StackOfInt ( ) ;
    void push ( int );
    int pop ( ) ;
    int top ( ) const;
    int size ( ) const;
private:
    enum { MAX_STACK = 100 };
    int data [ MAX_STACK];
    int stkPtr;
};

因为下面几行的数组和栈指针是私有的,因此用户必须通过你提供的公共接口按照你的方式进行操作(程序清单2.20就是一个例子)。即使成员函数的实现对于用户是隐藏的,但他(或她)可以看一下头文件就很容易地推断出与程序清单2.21相似的内容。如果隐藏所有的实现细节对你来说很重要,你可以通过使用不完全类型增加一个额外的保护层。

不完全类型的大小不能在编译期的时候确定。下面的声明就是一个例子:

extern int a [];

这个声明定义a是一个未知长度的数组,所以不能使用sizeof,否则将得到一个错误信息。这个定义完全不同于:

extern int  *a;

这是一个有大小的指针。可以通过在另一个类中隐藏它的实现细节来加强上面栈类型的封装性。在程序清单2.22中,只有一个指向 StackImp的指针出现在StackOfInt的私有部分中。语句

class  StackImp;

是StackImp的一个不完全声明,它仅仅表明了类的存在。一旦只有一个StackImp的指针或引用出现在 stack2.h中,用户就不需要stkimp.h。StackOfInt的成员函数目前只是传递请求到StackImp(见程序清单2.23和程序清单2.24)。

程序清单2.20 StackOfInt 的定义

// tstack.cpp: 使用StackOfInt 类
#include <iostream>
#include "mystack.h"
using namespace std;  

main()
{
    StackOfInt s;  

    s.push(10);
    s.push(20);
    s.push(30);
    while (s.size())
        cout << s.pop() << endl;
}  

//输出:
30
20
10

程序清单2.21 StackOfInt 的实现

// stack.cpp: StackOfInt 类的实现
#include "stack.h"  

StackOfInt::StackOfInt()
{
    stkPtr = 0;
}
void StackOfInt::push(int i)
{
    if (stkPtr == MAX_STACK)
        throw "overflow";
    data[stkPtr++] = i;
}  

int StackOfInt::pop()
{
    if (stkPtr == 0)
        throw "underflow";
    return data[--stkPtr];
}  

int StackOfInt::top() const
{
    if (stkPtr == 0)
        throw "underflow";
    return data[stkPtr - 1];
}  

int StackOfInt::size() const
{
    return stkPtr;
}

程序清单2.22 为了更好地封装而使用不完全类型

// stack2.h:    隐藏堆栈的实现
class StackImp;  

class StackOfInt
{
public:
    StackOfInt();
    ~StackOfInt();
    void push(int);
    int pop();
    int top() const;
    int size() const;  

private:
    StackImp* imp;
};

程序清单2.23 StackOfInt类的实现

// stack2.cpp
#include "stack2.h"
#include "stkimp.h"  

StackOfInt::StackOfInt()
          : imp(new StackImp)
{}  

StackOfInt::~StackOfInt()
{
    delete imp;
}  

void StackOfInt::push(int i)
{
    imp->push(i);
}  

int StackOfInt::pop()
{
    return imp->pop();
}  

int StackOfInt::top() const
{
    return imp->top();
}  

int StackOfInt::size() const
{
    return imp->size();
}

程序清单2.24 堆栈的实现

// stkimp.cpp
#include "stkimp.h"  

StackImp::StackImp()
{
    stkPtr = 0;
}  

void StackImp::push(int i)
{
    if (stkPtr == MAX_STACK)
        throw "overflow";
    data[stkPtr++] = i;
}  

int StackImp::pop()
{
    if (stkPtr == 0)
        throw "underflow";
    return data[--stkPtr];
}  

int StackImp::top() const
{
    if (stkPtr == 0)
        throw "underflow";
    return data[stkPtr - 1];
}  

int StackImp::size() const
{
    return stkPtr;
}

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-10-30 17:05:00

《C和C++代码精粹》——2.14 封装和不完全类型的相关文章

《C和C++代码精粹》——1.14 默认参数

1.14 默认参数 C和C++代码精粹在一个函数声明中的默认参数用于指示该函数从它的原型中取值.在程序清单1.16中有一个具有原型的函数: int minutes(int hrs ,int min=0); 最后一个参数后面的"=0"指示编译器给第二个参数提供值0.当调用minutes函数时,可以省略了该参数.这种机制对定义相应的重载函数来说本质上是一种速记的方法.在这种情况下,前面的语句等价于: int minutes (int hrs,int min); int minutes (i

《C和C++代码精粹》导读

前言 C和C++代码精粹 本书适合于那些C和C++的职业程序员.假如你已熟悉这两种语言的语法和基本结构,这本书能够为你创建有效的.实用的程序提供实践性的指导.每一个代码范例或程序范例均标明行之有效的用法和技术,这些用法和技术对C/C++这两种重要编程语言的性能发挥起着重要的作用. 对于那些希望在工作中加强自身技术和提高效率的人来说,本书可以算是一本经验之谈.尽管目前人们对面向对象模式的推崇到了白热状态(本书也包括这方面的丰富内容),可是我没有理由不对C++的基础-C表示尊崇.我发现太多的程序开发

《C和C++代码精粹》——2.9 字符串数组

2.9 字符串数组 C和C++代码精粹有两种方式来描述C风格的字符串数组:(1)指针数组:(2)二维数组.程序清单2.13中的程序说明了第一种方式.内存分布如下: 程序清单2.13 用指向字符的指针数组来实现字符串 // array6.cpp:粗糙的数组 #include <iostream> #include <cstring> using namespace std; main() { char* strings[] = {"now","is&qu

《C和C++代码精粹》——1.12 运算符重载

1.12 运算符重载 C和C++代码精粹在C++中你可以重载运算符,例如,定义一个复数的数据类型如下: struct complex { double real, imag; }; 假如能使用中缀符号用于复数加法,那将会相当方便.如: complex c1,c2; - complex c3=c1+c2; 当编译器遇到如c1+c2这样的表达式时,将查找下边两个函数中的一个(只须其中的一个存在): operator+(const complex&,const complex &); //全局函

《C和C++代码精粹》——2.3 指针运算

2.3 指针运算 C和C++代码精粹当一个指针指向一个数组元素时,可以在指针上加一个整数或减一个整数来使它指向同一数组的元素.在这样的指针上加1是通过它所引用类型的数据的字节数来增加它的值,因此,它就指向下一个数组元素.程序清单2.3中的程序实现了在一个浮点型数组内完成整数的运算.数组看起来像: 在我的系统平台上,浮点型占4个字节.在p上加1实际上等于在它的值上加4.对两个指向数组元素的指针进行相减可以得到在两个地址之间数组元素个数.换句话说,如果p和q是指向相同类型的指针,那么语句q=p+n意

《C和C++代码精粹》——2.11 更高深的内容

2.11 更高深的内容 C和C++代码精粹 我们可以很自然地得出以下结论:一个三维数组是二维数组的集合. int a[2] [3] [4]={{{0,1,2,3},{4,5,6,7},{8,9,0,1}}, {{2,3,4,5},{6,7,8,9},{0,1,2,3}}}; 这个数组的第一个元素是一个"二维数组" a[0](从技术上来说,a[0]是一个由3个含有4个整数的数组的数组),为了使指针与数组名a一致,定义: int (*p) [3] [4] = a; 程序清单2.16是一个应

《C和C++代码精粹》——2.7 指针和一维数组

2.7 指针和一维数组 C和C++代码精粹 在程序清单2.7中,会注意到在传递数组 s 时并没有使用它的地址,这是因为C和C++在大多数表达式中把数组名转换成指向它第一个元素的指针.自1984年以来,我已经向成百上千的学生讲授了C和C++,我注意到了指针和数组,特别是指针和多维数组之间的关系造成很多迷惑. 这样说似乎很奇怪,但是C++确实不支持数组,至少C++不像支持第一类数据类型如整型或者甚至结构体那样支持数组.考虑以下的语句: int i=1,j; int a[4]={0,1,2,3},b[

《C和C++代码精粹》——1.10 操纵器

1.10 操纵器 C和C++代码精粹 当标识符 endl出现在一个输出流中时,一个换行字符就被插入并且流被刷新.标识符endl是操纵器的一个例子,即为了副效应而插入到流的一个对象.在〈iostream〉中被声明的系统预定义的操纵器列于表 1.3中.程序清单1.11里的程序在功能上与程序清单1.10的程序等价,但它是用操纵器来代替显式调用setf函数.操纵器经常可以使代码更为高效. 表1.3 简单的操纵器(〈iostream〉) 程序清单1.11 用操纵器改变数据基数 // base2.cpp:

《C和C++代码精粹》——2.5 普通指针

2.5 普通指针 C和C++代码精粹 通常编写能接收指向任意类型参数的函数是很方便的.这是很有必要的,例如,用标准的库函数memcpy,能够从一个地址向另一个地址拷贝一块内存.你也可能想调用memcpy来拷贝自己创建的结构: struct mystruct a,b; /.../ memcpy(&a,&b,sizeof(struct mystruct)); 为了操作任意类型的指针,memcpy把它头两个参数声明为void型指针.可以不需要强制类型转换将任何类型的指针赋予void类型.也可以在