面向对象之数据抽象_代码大全笔记(一)

代码大全里有句话:“在一种语言上编程”的程序员将他们的思想限制于“语言直接支持的那些构件”。如果语言工具是初级的,那么程序员的思想也是初级的。“深入一种语言去编程”的程序员首先决定他要表达的思想是什么,然后决定如何使用特定语言提供的工具来表达这些思想。

俗一点的说就是,软件开发,重要的是思想,语言仅是工具。有了好的创意,算法、方案和架构,可以选择任何语言来实现。

OOP离不开数据抽象,为了更好的进行数据抽象,需要掌握一些基本的技术,当所有最基本的技术都可信手拈来时就可以发挥强大的创造力。万变不离其宗,基础扎实才能游刃有余。

1、隐藏抽象的实现细节

一般情况下,我们都会在C++的头文件中声明类的所有成员(public,protected,private),对于这种情况,用户并不能访问私有和保护成员,但是能看到。有时候我们希望用户无法访问的还是不显示为好,尤其是库的提供者。--不能用你还让我看,调戏还是找骂?

用户在创建类的对象之前,C++的编译器需要知道类的所有成员的细节,以便为对象分配足够的内存,一般用户都仅仅只有头文件,因此在类的头文件中需要声明类的私有和保护成员。这样同时也会增加编译的成本,因为类的大小改变时,类的所有用户必须重新编译它们的程序。(类加入或删除数据成员时,类大小改变,而普通成员函数的增减需要重编,虚函数,若加在现存虚函数的末尾(现存函数在VTABLE中的偏移不变),可以不重编)

为了解决以上问题,可以通过增加一个单独的实现类来解决。(Qt中大量使用了这种方法,大多是由一个对应XXXPrivate后缀的类提供具体的实现)--其实就是外包出去

在导出的类中仅仅包含实现类的指针,而不是将所有的数据都保留在导出类的头文件中,这样XXXImpl的任何更改,都不会影响到XXX类。如下:

class XXXImpl;//此类中包含具体的数据成员,这里利用了前置声明,XXXImpl不需要导出给用户,在XXX的构造函数中创建XXXImpl并让m_pHandle指向它
calss XXX
{
   public:
         XXX();
   private:
         XXXImpl*    m_pHandle;
}
利用指针的好处:
  1. XXXImpl的改动不影响XXX,用户不需要重新编译,只需重新链接即可,极大的加快的编译程序的速度。--外包商换了,但我不告诉你
  2. XXX的用户看不到数据成员。--在外包商那里,你在我这里看瞎眼啊
利用指针的缺点:
  1. XXX的任何使用m_pHandle的成员函数不能是内联的。(因为内联会在用户代码展开,展开时需要知道细节)--缺点再多该用还得用啊
  2. XXX每个对象都增加了一个指针的大小(32位机器是4字节)
  3. 对XXXImpl成员的访问是通过m_pHandle进行的,增加了间接环节。
  4. 创建和删除XXXImpl也需要成本

大型工程重新编译的成本是非常高的,链接的主要任务是地址映射(重定位函数调用的地址等),和编译相比,链接所花费的时间相当少。当修改类的实现,而接口不变时,用户只需要重新链接。类中的任何下列改动都将导致用户重新编译代码:

  1. 添加数据成员
  2. 修改数据成员大小或类型
  3. 修改数据成员顺序
  4. 删除数据成员
  5. 添加成员函数
  6. 修改函数声明
  7. 修改函数顺序
  8. 删除函数
  9. 修改类内部任何声明(枚举、结构体等)
  10. 修改内联函数

仅仅修改函数实现(实现重编),用户只需重新连接(和新函数地址连接)。

2、指针、引用、值

按照按需创建的概念,即对象在需要的时候才创建。

使用指针作为数据成员的优点:
  1. 创建包含指针的对象时,不需要立即给指针绑定对象,可以给NULL,让对象创建速度更快。
  2. 在对象生存期内,指针可以指向不同对象,提供了更大的灵活性。
使用指针作为数据成员的缺点:
  1. 成员函数使用指针之前需要判空,否则崩溃。
  2. 若在成员函数中new创建了对象并赋给指针,则析构时需要删除指针所指对象,否则内存泄漏。

对比引用和值,使用指针作为数据成员只在需要灵活性的地方才有用,如在创建类对象时,就需要使用类对象的数据成员,那么使用引用和值会比指针容易。使用指针和引用时,可以使用多态。

3、控制对象的创建

只允许用new()创建对象

不让用户在栈上创建对象(局部),技巧是让析构函数成为私有。C++中用户在栈上创建对象时,编译器会查找匹配的构造和析构函数,如果其中一个无法访问,则出现编译错误。如此就只能通过new来创建对象了,但是问题是我们如何删除对象呢?(析构已经是私有的了),一个简单的办法是提供一个Delete成员函数(delete this)。--怎么看着像一损招?有用没用,先记着

防止用new()创建对象

实现一个不可访问new运算符。

private:
 void* operator new(size_t size);
 void operator delete(void* p);

4、避免大型数组作为局部变量或数据成员

由于局部变量是在栈上分配的,栈通常有预设值的大小,且依赖具体平台,在栈上创建大型数组可能导致程序崩溃,尤其是函数是递归的,问题更严重。数据成员同理。创建大型数组应该考虑使用堆。

使用数组作为数据成员时,考虑使用指针代替数组。
使用对象数组和指针数

我们通常避免创建对象数组,使用对象数组有一些缺点:

XXX  obArray[10]; //这样只能调用默认构造函数

希望调用不同的构造函数时,得使用下面的方式:

XXX obArray[10] = { 11, XXX("test")}--对于剩下的8个,还是调用了默认构造函数,这种方法也只适用于小型数组,如果是10000个XXX对象的数组,估计要自杀了

较好的方案是使用对象指针数组,可以自由使用特定构造函数。

XXX* pObArray[10];

for()

  pobArray[i] = 0;//初始化

pObArray[0] = new XXX("test");//保存

更灵活一点,可以创建一个指向指针数组的指针,即指针的指针。XXX ** ppArray; ppArray = new XXX*[size];但使用指针数组,需要判空,切记。-都用STL容器多一些

5、成员函数返回值,数据成员,首选对象,而不是简单类型的指针

有时候我们会设置类似 const char* GetName() const;这样的成员函数,返回简单类型的指针。这样做并不能防止用户将指针转为char*并修改它。这种时候,最好使用抽象数据类型代替char*来保存字符,如CString。--在可能的地方,使用对象代替原始指针类型

6、避免临时对象

如果对象做常量使用,则最好创建1次,然后重复使用。

void f(const XXX& x);

void g()
{
   f(XXX("test"));
}
//每次调用g的时候,都将创建临时对象
可以在全局
XXX Test("test");
g(){ f(Test); }
//或者在内部声明static对象
void g()
{
   static XXX test("Test");
   f(test);
}


7、使用复制构造函数初始化对象

很多时候,我们希望用现存对象创建新对象,此时,复制构造函数是最好的选择。

8、有效的使用代理对象

代理对象的作用:

  1. 用于安全共享的代理对象--写时复制,进程共享内存
  2. 为方便使用的代理对象
  3. 为远程对象替身的代理对象
  4. 提供其他功能的智能代理--智能指针
  5. 解决语法/语义问题的代理--操作符重载[]
  6. 通用下标代理

9、使用简单的抽象建立更复杂的抽象

给定一个Fish和Fly,则很容易实现一个FlyFish抽象。

10、抽象必须允许用户用各种不同的方式使用类

数据抽象和面向对象编程的威力在于创建简单的类,并用许多方法扩展它们,以创建更加强大有用的抽象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值