C++的数据抽象及类的由来

《c++编程思想》阅读笔记(2)

与c相比,为什么会有一堆类的概念呢 ?
第2章 数据抽象


1)c++是一个能提高效率的工具。为什么我们还要努力使我们从已经熟悉且效率高的语言(c)转到另一种新的语言上?而且使用这种新语言,我们会在确实掌握它之前的一段时间内降低效率。这归因于我们确信通过使用新工具将会得到更大的好处。

2)
极大提高效率的唯一办法是使用其他人的代码,即使用库。库,简单地说就是一些人已经写的代码,按照某种方式包装在一起。所以,库大概是改进效率的最重要的方法,c++的主要设计目标之一就是使库容易使用。这意味着,在c中使用库有困难


3)声明与定义
“声明”向计算机介绍名字,它说,“这个名字什么意思”。而“定义”为这个名字分配内存空间。无论涉及到变量时还是函数时都一样。无论在哪种情况下,编译器都在“定义”处分配储存空间。对于变量,编译器确定这个变量占多少储存单元,并在内存中产生存放这它们的空间。对于函数,编译器产生代码,并为之分配储存空间。函数的储存空间中有一个由使用不带参数表活带地址操作符的函数名产生的指针

定义也可以是声明。如果该编译器还没有看到过名字A,程序员定义int A。则编译器马上为这个名字分配储存地址。

声明常常使用extern关键字。如果我们只是声明变量而不是定义它,则要求用extern。对于函数声明,extern是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明

函数原型包括关于参数类型和返回值的全部信息, int f(float, char);是一个函数原型。c++要求必须写出函数原型,因为它增加了一个重要的安全层。举个例子

extern int i; //Declaration without definition
extern float f(float); // function declaration

float b; // Declaration & definition
float f(float a) { // Definition
    return a + 1;
}

int i; //definition
int h(int x) { // Declaration &definition
    return x + 1;
}

在函数声明是,参数名可给出也可不给出。而在定义时,它们是必须的。这在c语言中确实如此,但在c++中却不一定


4)在c中,使用库的最大的障碍是名字冲突的问题,无论哪种情况,都不允许使用包含具有相同函数的两个库,所以c++开始把struct中的变量名不会与全局变量名冲突的这一优点扩展到函数名上,也就是,让函数是struct的成员。这样以后,函数不再需要传入结构体的指针进行操作,编译器已经帮你做好了这个问题,这些函数现在仅有的参数与这些函数所做的事情有关,而不是与这些函数的运算的机制有关

5)在这个struct里的init()函数完全不用担心与 其他struct的init()函数相抵触,于是乎,定义这个函数是,需要完全指定它是哪一个,为了完成这个指定任务,c++有一个新的运算符::,即范围分解运算符,例如,如果希望指定init()属于stash,就写stash::init(int size, int quantity)

6)结构的声明必须要在它们定义和使用之前,而放置结构定义的最习惯的为之是在头文件中,除非我们有意把它藏在代码中。

7)这里有一个关键字,称为this,它产生这个struct的地址


8)c++的编译比c的编译更加严格,在c中,我们常常发现能使程序通过编译,然后我们必须再花力气使它工作。在c++中,程序编译正确了,它就能工作了。这是因为该与眼堆类型要求更加严格的缘故。

9)什么是对象
把函数放进结构是c++中的根本改变,并且这引起我们将结构作为新概念去思考。在c中,结构是数据的凝聚,它将数据捆绑在一起,使得我们可以将它们看作一个包。但这除了能使程序设计方便之外,别无其他好处。这些结构上的运算可以用在别处。然而将函数也放在这个包内,结构就变成了新的创造物,它既能描述属性(就像c中的结构体能做的一样),又能描述行为,这就形成了对象的概念。对象时一个独立的有约束的试题,投自己的记忆和活动

在c++中,对象只是一个变量,最纯的定义是“存储的一个区域”。它是能存放数据的空间,并隐含着还有在这些数据上的运算。将数据连同函数捆绑在一起,这一点就允许创建新的类型。这常常被称作封装。
stash(书中举例的一个类)也是一个新的数据类型,可以add()、fetch()和inflate()。由说明stash S创建一个stash就像由说明float f创建一个float一样。一个stash也有属性和行为,甚至它的活动就像一个实数——一个内建的数据类型。我们成stash为抽象数据类型,也许这是因为它能允许我们从问题空间吧概念抽象到解空间。另外,c++编译器把它看做一个新的数据类型,并且如果说一个函数需要一个stash,编译器就确保传递了一个stash给这个函数。对抽象数据类型的类型检查就像对内建类型的类型检查一样严格。

然而,我们会看到在对象上完成运算的方法有所不同。object.member_function(arglist)是对一个对象“调用一个成员函数”。而在面向对象的语法中,也称之为“想一个对象发送消息”。这样,对于stash S,语句S.add(&i) “发送消息给S”,也就是说,“对自己add()”实际上,面向对象程序设计可以终结为一句话“向对象发送消息”。需要做的所有事情就是创建一束对象并且给它们发送消息。当然,问题是勾画出我们的对象和消息是什么,但如果完成了这些,c++的实现就直接了当了。

10)在c++中的结构长度和c中等价版本的长度相同。c++尽力不增加任何花费。

11)头文件的形式
当我第一次学习用c语言时,头文件对我是神秘的。许多有关c语言的数似乎不强调它,并且编译器也不强调函数声明,所以它在大部分时间内似乎是可要可不要的,除非要声明结构式。在c++中,头文件的使用变得非常清楚。它们对于每个程序开发是强制的,在它们中放入非常特殊的信息:声明。头文件告诉编译器在我们的库中那些事可用的。因为对于cpp文件能够不要源代码而使用库(只需要对象文件或库文件),所以头文件是存放借口规范的唯一地方
头文件是库的开发者与它的用户之间的合同。它说:“这里描述的是库能做什么”,它不说如何做,因为如何做存放在cpp文件中,开发者不需要分发这些描述“如何做”的源代码给用户。
该合同描述数据结构,并说明函数调用的参数和返回值。用户需要这些信息来开发应用程序,编译器需要它们来产生相应的代码。(联系自己学c的时候的stdio.h头文件,就可以知道我们知道如何用printf函数,却不知道内部是如何实现的,就是上述过程的一个具体体现)
编译器强迫执行这一合同,也就是要求所有的结构和函数在它们使用之前被声明,当它们是成员函数时,在它们被定义之前被声明。这样,就强制把声明放在头文件中并把这个头文件包含在定义成员函数的文件(date.cpp)和使用它们的头文件(client.cpp)中。 因为描述库的单个头文件被包括在整个系统中,所以编译器能保证一致和避免错误。
为了恰当地组织代码和写有效的头文件,有一些问题必须知道。第一个问题是将什么放进头文件中,基本规则是“只声明”,也就是说,对于编译器只需要一些信息以产生代码或创建变量分配内存。这样可以防止头文件包含在几个处理单元中时,如果内存分配不止一个地方,避免连接器产生多重定义错误。
第二个问题是重复声明,c和c++都允许对函数重复声明,只要这些重复声明匹配,但绝不允许对结构重复声明。


12)嵌套结构
在全局名字空间之外为数据和函数取名字是有好处的。在类里面再加一个类就是一个所谓的嵌套结构,举个例子

class stack {
  class link {
    void* data;
    link* next;
    void init(void* Data, link* Next);
  } *head;

我们可以这样来定义嵌套结构的成员,简单地两次使用范围分解运算符,以指明这个嵌套class的名字,即

void stack::link::init (void* Data, link* Next) {
  blabla ....
}

13) 希望用范围分解指定一个全局名字(而不是成员与之同名的变量时),应当使用前面不带任何东西的运算符。 for example :

 void S::f () {
    ::A++;  // select the global A
    A--;   //  the A at the class
 }

小结:在这一章中,我们已经学会了使用c++的基本方法,也就是在结构的内部放入函数。这种新类型被称为抽象数据类型,用这种结构创建的变量被称为这个类型的对象或实例。想对象调用成员函数被称为想这个对象发消息。面向对象的程序设计中的主要活动就是向对象发消息。

(不定期更新:))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这本经典、畅销的数据结构教材详细介绍了数据抽象的基础知识,强调作为面向对象方法基础原理的规范和实施之间的区别。书中使用的软件工程原则和概念以及UML图便于增强学生的理解。 ◆ 详细介绍了数据抽象,强调规范和实现之间的区别 ◆ 广泛介绍了各种面向对象的编程技术 ◆ 重点是核心的数据结构,而不是非必要的C++语言语法 ◆ 说明了和ADT在问题解决过程中的作用 ◆ 诠释了ADT的主要应用,如查找航班图、事件驱动的模拟和八皇后问题 ◆ 大部分章节中的例子都使用了标准模板库(STL) ◆ 介绍了递归 ◆ 附录中提供了基本的C++语法,以帮助学生从其他语言转换为C++ 第1章 数据抽象:墙 1 1.1 面向对象的概念 2 1.1.1 面向对象分析与设计 2 1.1.2 面向对象解决方案的特征 3 1.2 获得更好的解决方案 4 1.2.1 内聚 5 1.2.2 耦合 5 1.3 规范 6 1.3.1 操作契约 7 1.3.2 特殊情况 8 1.3.3 抽象 9 1.3.4 信息隐藏 10 1.3.5 最小且完整的接口 11 1.4 抽象数据型 12 1.4.1 设计ADT 14 1.4.2 涉及其他ADT的ADT 17 1.5 ADT包 18 1.5.1 确定行为 18 1.5.2 指定数据和操作 19 1.5.3 ADT的模板接口 22 1.5.4 使用ADT包 24 C++片段1 C++ 29 C1.1 待解决的问题 30 C1.1.1 私有数据字段 31 C1.1.2 构造函数和析构函数 32 C1.1.3 方法 32 C1.1.4 防止编译错误 33 C1.2 实现解决方案 34 C1.3 模板 35 C1.4 继承 37 C1.4.1 基和派生 38 C1.4.2 重写基方法 40 C1.5 虚方法和抽象 42 C1.5.1 虚方法 42 C1.5.2 抽象 43 第2章 递归:镜子 45 2.1 递归解决方案 46 2.2 返回值的递归 48 2.2.1 递归值函数:n的阶乘 49 2.2.2 箱式跟踪 52 2.3 执行动作的递归 55 2.4 递归与数组 62 2.4.1 逆置数组项 63 2.4.2 折半查找 64 2.4.3 查找数组中的最大值 68 2.4.4 查找数组中第k个最小值 69 2.5 组织数据 71 2.6 更多示例 75 2.6.1 Fibonacci数列(兔子繁殖) 75 2.6.2 组织游行队伍 78 2.6.3 从n个事物中选出k个 79 2.7 递归和效率 81 第3章 基于数组的实现 91 3.1 办法 92 3.1.1 核心方法 93 3.1.2 使用大小固定的数组 93 3.2 ADT包的基于数组的实现 94 3.2.1 头文件 95 3.2.2 定义核心方法 96 3.2.3 测试核心方法 98 3.2.4 实现更多方法 101 3.2.5 删除项的方法 103 3.2.6 测试 106 3.3 在实现中使用递归 107 3.3.1 getIndexOf方法 107 3.3.2 getFrequencyOf方法 108 C++片段2 指针、多态和内存分配 113 C2.1 变量的内存分配和方法的前期绑定 114 C2.2 需要解决的问题 115 C2.3 指针与程序的自由存储 116 C2.3.1 释放内存 118 C2.3.2 避免内存泄漏 119 C2.3.3 避免悬挂指针 122 C2.4 虚方法和多态 124 C2.5 数组的动态分配 126 第4章 基于链表的实现 129 4.1 预备知识 130 4.2 ADT包的基于链表的实现 133 4.2.1 头文件 134 4.2.2 定义核心方法 135 4.2.3 实现更多方法 138 4.3 在基于链表的实现中使用递归 143 4.4 测试多个ADT实现 145 4.5 比较基于数组的实现和基于链表的实现 148 第5章 作为问题求解技术的递归 155 5.1 定义语言 156 5.1.1 语法知识基础 156 5.1.2 两种简单的语言 158 5.2 代数表达式 160 5.2.1 代数表达式的型 160 5.2.2 前缀表达式 162 5.2.3 后缀表达式 166 5.2.4 完全括号化表达式 168 5.3 回溯 168 5.3.1 查找航线 168 5.3.2 八皇后问题 173 5.4 递归和数学归纳法的关系 179 5.4.1 递归阶乘函数的正确性 179 5.4.2 Hanoi塔的工作量 180 第6章 栈 189 6.1 ADT栈 190 6.1.1 在设计解决方案期间开发ADT 190 6.1.2 ADT栈的规范 192 6.2 栈的简单应用 197 6.2.1 检查括号匹配 197 6.2.2 识别语言中的字符串 199 6.3 栈在代数表达式中的应用 200 6.3.1 计算后缀表达式 201 6.3.2 中缀表达式与后缀表达式的等价转换 202 6.4 使用栈查找航班图 205 6.5 栈和递归的关系 212 C++片段3 异常 221 C3.1 背景知识 222 C3.2 断言 223 C3.3 抛出异常 224 C3.4 处理异常 227 C3.4.1 多个catch块 228 C3.4.2 未捕获的异常 229 C3.5 程序员定义的异常 232 第7章 实现ADT栈 235 7.1 基于数组的实现 236 7.2 基于链表的实现 239 7.3 在实现中使用异常 243 第8章 列表 247 8.1 指定ADT列表 248 8.2 使用列表操作 252 8.3 ADT列表的模板接口 255 第9章 实现列表 259 9.1 基于数组的ADT列表实现 260 9.1.1 头文件 261 9.1.2 实现文件 262 9.2 基于链表的ADT列表实现 266 9.2.1 头文件 266 9.2.2 实现文件 268 9.2.3 在LinkedList的方法中使用递归 275 9.3 两种实现的比较 279 第10章 算法的效率 283 10.1 什么是好的解决方案 284 10.2 测量算法的效率 285 10.2.1 算法的执行时间 286 10.2.2 算法增长率 287 10.2.3 分析与大O表示法 288 10.2.4 正确分析问题 291 10.2.5 查找算法的效率 293 第11章 排序算法及其效率 299 11.1 基本排序算法 300 11.1.1 选择排序 300 11.1.2 起泡排序 303 11.1.3 插入排序 305 11.2 较快排序算法 307 11.2.1 归并排序 307 11.2.2 快速排序 312 11.2.3 基数排序 319 11.3 各种排序算法的比较 321 C++片段4 关系和重用 325 C4.1 回顾继承 326 C4.1.1 的公有、私有和受保护部分 331 C4.1.2 公有、私有和受保护继承 332 C4.1.3 is-a和as-a关系 333 C4.2 包含:has-a关系 334 C4.3 回顾抽象 335 第12章 有序表及其实现 339 12.1 指定ADT有序表 340 12.1.1 ADT有序表的模板接口 342 12.1.2 使用有序表的操作 343 12.2 基于链表的实现 344 12.2.1 头文件 344 12.2.2 实现文件 345 12.2.3 基于链表的实现的效率 348 12.3 使用ADT列表的实现 348 12.3.1 包含 349 12.3.2 公有继承 352 12.3.3 私有继承 356 第13章 队列和优先队列 363 13.1 ADT队列 364 13.2 ADT队列的简单应用 367 13.2.1 读取字符串 367 13.2.2 识别回文 368 13.3 ADT优先队列 369 13.4 应用:模拟 371 13.5 面向位置和面向值的ADT 379 第14章 队列和优先队列的实现 387 14.1 ADT队列的实现 388 14.1.1 使用ADT列表的实现 388 14.1.2 基于链表的实现 390 14.1.3 基于数组的实现 394 14.1.4 比较实现 399 14.2 ADT优先队列的实现 400 C++片段5 运算符重载和友元访问 405 C5.1 重载运算符 406 C5.1.1 重载=进行赋值 408 C5.1.2 重载+进行连接 410 C5.2 友元访问和<<的重载 411 第15章 树 415 15.1 术语 416 15.1.1 树的型 417 15.1.2 树的高度 419 15.1.3 满二叉树、完全二叉树和平衡二叉树 421 15.1.4 二叉树的最大和最小高度 422 15.2 ADT二叉树 425 15.2.1 二叉树的遍历 425 15.2.2 二叉树的操作 428 15.2.3 ADT二叉树的模板接口 430 15.3 ADT二叉查找树 432 15.3.1 二叉查找树的操作 433 15.3.2 查找二叉查找树 434 15.3.3 创建二叉查找树 435 15.3.4 遍历二叉查找树 437 15.3.5 二叉查找树操作的效率 437 第16章 树的实现 443 16.1 二叉树中的节点 444 16.1.1 基于数组的表示 444 16.1.2 基于链表的表示 446 16.2 ADT二叉树基于链表的实现 447 16.2.1 头文件 447 16.2.2 实现 450 16.3 ADT二叉查找树基于链表的实现 458 16.3.1 ADT二叉查找树操作的算法 458 16.3.2 BinarySearchTree 469 16.4 在文件中保存二叉查找树 471 16.5 树排序 474 16.6 一般树 474 C++片段6 迭代器 479 C6.1 迭代器 480 C6.1.1 常见的迭代器操作 481 C6.1.2 使用迭代器操作 482 C6.1.3 实现迭代器 483 C6.2 迭代器的高级功能 485 第17章 堆 489 17.1 ADT堆 490 17.2 堆的基于数组的实现 493 17.2.1 基于数组的堆操作的算法 494 17.2.2 实现 498 17.3 ADT优先队列的堆实现 502 17.4 堆排序 504 第18章 字典及其实现 511 18.1 ADT字典 512 18.2 可能的实现 517 18.2.1 ADT字典的基于数组的有序实现 519 18.2.2 ADT字典的二叉查找树实现 521 18.3 选择实现 523 18.4 散列 529 18.4.1 散列函数 532 18.4.2 解决冲突 534 18.4.3 散列的效率 539 18.4.4 如何确立散列函数 542 18.4.5 字典遍历:散列的低效操作 543 18.4.6 使用散列和分离链实现ADT字典 544 第19章 平衡查找树 551 19.1 平衡查找树 552 19.2 2-3树 553 19.2.1 遍历2-3树 555 19.2.2 查找2-3树 556 19.2.3 在2-3树中插入数据 558 19.2.4 从2-3树中删除数据 562 19.3 2-3-4树 567 19.3.1 查找和遍历2-3-4树 569 19.3.2 在2-3-4树中插入数据 569 19.3.3 从2-3-4树中删除数据 572 19.4 红-黑树 573 19.4.1 查找和遍历红-黑树 575 19.4.2 红-黑树的插入和删除 575 19.5 AVL树 577 第20章 图 583 20.1 术语 584 20.2 将图作为ADT 587 20.3 图的遍历 591 20.3.1 深度优先查找 592 20.3.2 广度优先查找 593 20.4 图的应用 595 20.4.1 拓扑排序 595 20.4.2 生成树 598 20.4.3 最小生成树 600 20.4.4 最短路径 603 20.4.5 回路 606 20.4.6 一些复杂问题 608 第21章 外部存储中的数据处理 615 21.1 了解外部存储 616 21.2 排序外部文件的数据 618 21.3 外部字典 624 21.3.1 确定外部文件的索引 626 21.3.2 外部散列 629 21.3.3 B-树 632 21.3.4 遍历 639 21.3.5 多索引 640 C++片段7 标准模板库 647 C7.1 STL容器 648 C7.1.1 STL容器适配器 649 C7.1.2 顺序容器 650 C7.1.3 关联容器 654 C7.2 STL算法 657 附录A 回顾C++基础 659 附录B 编程中的重要主题 697 附录C 统一建模语言 719 附录D 软件生命周期 727 附录E 数学归纳法 733 附录F 算法验证 737 附录G C++文件基础 741 附录H C++头文件和标准函数 751 附录I C++文档系统 755 附录J ASCII字符代码 757 附录K 针对Java编程人员的C++知识 759 附录L 针对Python编程人员的C++知识 767
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值