设为首页 加入收藏

TOP

C++面向对象高效编程:数据抽象(一)
2016-12-06 20:24:45 】 浏览:575
Tags:面向 对象 高效 编程 数据 抽象

数据抽象

数据抽象是一种依赖于接口和实现分离的编程(以及设计)技术。即定义抽象数据类型以及一组操作并隐藏实现的过程

接口和实现的分离

接口的含义

类的接口包括用户所能执行的操作 用户观察到的对象视图 告诉Client“可以做什么”

实现的含义

类的实现包括类的数据成员、 负责接口实现的函数体以及 定义类所需要的各种私有函数。 负责“如何做”

保护实现——数据封装

1.通过数据封装实现了类的接口和实现的分离,封装后的类隐藏了它的实现细节。

2.数据被封装后,客户无法直接访问,更不能修改,只有接口函数才可以访问和修改封装的信息。接口用户完全不知道描述该接口的函数如何使用被封装的信息,而且类的用户也对此毫无兴趣。

3.封装另外一个优点是实现独立,由于类用户无法查看封装的数据,他们也不会注意到封装数据的存在,因此改动封装的数据和信息不会影响用户缩减的接口。即客户使用的接口与支持接口的实现彼此独立。

需要封装的内容

某项对用户理解类毫无帮助,或者从接口中移除该项根本不会减少类的作用 某项包含有敏感数据,为了不让用户直接访问的 某项有着潜在的危险,并且要求用户掌握特殊技能才能操作的 类为了自我管理而使用的某些元素,且对接口意义不大

抽象数据类型

为了实现数据抽象和封装,首先需要先定义一个抽象数据类型。定义抽象类型将直接应用数据抽象的概念.

Cpp 与数据抽象

C++中,抽象的基本单元是类,类只不过是功能增强的C结构。

下面以一个整数栈TintStack类为例

class TintStack{
    public:
        //member functions
        TintStack(unsigned int StackSize = DEFAULT_SIZE);   //defualt constructor
        TintStack(const TintStack& that);                   //copy constructor
        TintStack& operator= (const TintStack &that);       //assignment operators
        ~TintStack();                                      //destructor

        void push(int thisValue);
        int pop();
        unsigned int howmany() const;
        //more...
    private:
        int * _sp;
        unsigned _count;
        unsigned _size;
};

访问区域

访问说明符控制派生类基类继承而来的成员是否对派生类用户可见。

public区域:
该区域看做是通用公共的接口,没有任何的保护,是类限制最少的区域。 private区域:
成员函数的实现可以访问在此区域的所有成员。但是类的用户无法操控private区域。 protected区域
protected区域的限制比private区域要宽松一点,但比public要严格。 该区域用户派生类(通过继承)使用。派生类的 成员函数友元可以访问该区域内的所有成员,但是派生类的用户不能访问。 派生类的 成员函数友元只能通过派生类对象访问基类 protected区域的成员。派生类对于一个基类对象中的受保护成员没有任何访问特权

构造函数

考虑下面的创建对象实例

TintStack mystack;
TintStack s1(100);
TintStack s2 = s1;
TintStack *dsp = new TintStack(200);
TintStack s3 = TintStack(250);

对象只能用构造函数创建。

(1)TintStack mystack;

这里我们创建了一个TinStack类的对象mystack,因为该声明没有带有任何的参数,所以这个语句将调用默认构造函数。不带任何参数调用的构造函数即是默认构造函数

在mystack 类中TintStack(unsigned int StackSize = DEFAULT_SIZE);就是一个默认构造函数,接受一个unsigned int 参数,并且有一个默认值DEFAULT_SIZE。

(2)TintStack s1(100)

这条语句创建TintStack类的对象s1,其栈的大小为100个元素。这类的语法看起来像是函数调用。这里调用了TinStack类的构造函数。

(3)TinStack s2 = s1;

从语法上来说,这条语句将通过s1创建s2,编译器此时会调用复制构造函数

(4)TinStack *dsp = new TintStack (200)

在前面(1)创建对象中,编译器负责运行栈对象mystack分配内存,分配的内存包括有:数据成员、编译器需要的其他内部信息。此时编译器控制这种对象的分配和生存期。

而在这条语句中,使用了new()操作符,表明dsp所指向的对象通过动态分配内存,在运行堆上创建,这种动态分配对象的生存期应该由程序员控制,而非编译器。

(5)TinStack s3 = TinStack(250)

该语句等同于 TinStack temp (250); TinStack s3 = temp;,但是编译器优化可能会改变这个声明的实现,原来的声明表明我们正在请求的创建一个对象s3,而且为了初始化s3,必须创建一个包含250个元素的临时temp类对象,临时对象将在s3创建之后消失。

因此,我们不需要创建这样一个临时对象,并把它赋值给s3,这样很浪费时间。大多数编译器会选择直接创建一个大小为250的对象的s3.

创建对象时发生了以下三个步骤:

编译器需要获得对象所需的内存数量。

获得的原始内存被转换成一个对象,涉及将对象的数据成员防止在正确的位置,还有可能建立成员函数指针表等

最后,在前两部完成后,编译器通过新创建的对象调用构造函数。

动态分配相关与析构函数

(1)对于下列的函数

void printStack(TintStack thisOne)
{
    unsigned i =thisOne.howmany();
    for (unsigned j = 0; j < i; j++)
        cout << thisOne.pop() << " ";
    cout << endl;
}

当调用printStack()函数的时候,该函数接受一个对象的副本,编译器就会调用对象所属类的复制构造函数。从而使得函数获得一个原始对象的副本,用来初始化形参thisOne。

离开printStack()函数的时候,对象thisOne会发生什么情况?在运行时栈分配的一切都被清楚,而且编译器将回收它们所占用的内存。所有语言定义类型都是这样。

因此,对于局部变量i和j不在作用域内,编译器将回收之前被它们所占用的空间。与此类似,局部对象thisOne不在作用域内,因此也应该回收它所占用的内存空间。

对于对象的本身的大小,编译器很清楚。但是它并不知道对象中的指针_sp所指向的某些动态分配的内存。这时,析构函数就会排上用场。

无论何时对象离开作用域,编译器真正回收该对象所占用的内存之前,都会通过调用对象的析构函数,释放对象获得的任何资源。

换句话说,只有当对象在作用域中即将不再可见时,在函数返回之前或离开代码块之前,编译器才会调用析构函数。

(2)当我们使用动态分配时,就必须在使用之后手动使用delete释放资源。
调用delete操作符时,发生以下两个步骤:

若指针是一个指向类的指针,则通过调用该类的析构函

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇C++入门学习笔记 下一篇C++编程入门(44)多态性:多态的..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目