学习笔记九:类和数据抽象

1.inline成员函数的定义必须在调用该函数的每个源文件中是可见的。不在类定义体内定义的inline成员函数,其定义通常应放在有类定义的头文件中。
2.一旦定义了类,那么我们就知道了所有类成员,以及存储该类的对象所需的存储空间。
3.在创建类的对象之前,必须完整地定义该类,而不只是声明类。这样,编译器就会给类的对象预定相应的存储空间。同样,在使用引用或指针访问类的成员之前,必须定义类。
4.只有当类定义已经在前面出现过,数据成员才能被指定为该类类型。如果该类型是不完全类型,那么数据成员是指向该类类型的指针或引用。类的前向声明一般用来编写相互依赖的类。如,两类之间的交叉定义以及在类定义内部需要定义该类的指针或引用。

class LinkScreen{
    Screen window;
    LinkScreen *prev;
    LinkScreen *next;
}

5.(一般而言)定义类时不进行存储分配;定义对象时,将为其分配存储空间。
6.每个对象具有自己的类数据成员的副本(若类定义中含有static类型数据时除外,因为静态数据成员和静态成员函数是为类而非对象服务的,所以对象不具有静态数据的副本)
7.我们一般定义类如下面第一行所示:
当然第二行定义也是合法的(不过不建议这么做),因为它在定义类的同时,也定义了对象。这样做,会使得所发生的操作难以理解。对读者而言,将2个不同实体(类和变量)组合在一个语句中,会让人疑惑不解。

class Sales_item{/* ... */};
class Sales_item{/* ... */}accum,trans;

8.必须显示使用this指针的情况:当我们需要将一个对象作为一个整体引用而不是引用对象的一个成员时。最常见的情况是函数返回对调用该函数的对象的引用。示例:
#
我们定义两个操作:move和set
set:将特定字符或光标指向的字符设置为定值;
move:给定两个index的值,将光标移至新位置。
理想情况下,希望程序员能够将这些操作的序列连接成一个单独的表达式:

myScreen.move(4.0).set('#');
//上语句等价于
myScreen.move(4.0);
myScreen.set('#');

而要能连成一句的条件就是myScreen.move(4.0)返回的是对象整体。
在这两个操作中,必须要返回一个引用,该引用指向指向操作的那个对象。
#

//Screen.h
class Screen{
public:
    //interface member functions
    Screen& move(index r,index c);
    Screen& set(char);
    Screen& set(index,index,char);
    // other member as before
}

//Screen.cpp
Screen& Screen::set(char c)
{
    contents[cursor] = c;
    return *this;
}
Screen& Screen::move(index r,index c)
{
    index row = r*width;
    cursor = row + c;
    return *this;
}

9.从const成员函数返回*this,该类型的this是一个指向const类类型对象的const指针。既不能改变this所指向的对象值,也不能改变this所保存的地址。(c++ primer 377 中举出来关于display函数的例子相当有意思,之后可以好好琢磨)
10.成员函数重载和普通函数应用相同的规则:两个重载成员的形参数量和类型不能完全相同。
这里,关于重载,需要考虑一种现象:const重载

//首先分析const修饰成员函数和返回值的情况:
class A
{
public:
    void f(int i){ std::cout<<"1";}; //函数1
    void f(int i) const { std::cout<<"2" ;};//函数2

};
//这个地方的重载是没有问题的,说明const 修饰函数能够区分重载

class B
{
    void f(int i);
    const void f(int i);

};
//这次编译器会报出 'B::f' : redefinition; 的错误,说明const作为修饰返回值不能够区分重载
//再者分析const修饰值传递和指针(引用)传递的情况:
class C
{
    void f(int i);
    void f(const  int i);
};
//这个是错误的,编译通不过。那么是不是说明内部参数的const不予重载呢?

class D
{
public:
    void f(int &i) { std::cout<<"3";}; //函数3;
    void f(const  int &i){ std::cout<<"4" ;};//函数4
};
// 这个程序是正确的,看来上面的结论是错误的。

//分析:为什么会这样呢?这要涉及到接口的透明度问题。按值传递时,对用户
//而言,这是透明的,用户不知道函数对形参做了什么手脚,在这种情况下进行
//重载是没有意义的,所以规定不能重载!当指针或引用被引入时,用户就会对
//函数的操作有了一定的了解,不再是透明的了,这时重载是有意义的,所以规
//定可以重载。

//这里要注意的一点:返回值不能作为区分重载的条件。

11.全局作用域符::
如果我们需要使用某个全局对象,但因为类中存在同名变量,将全局变量屏蔽了。我们可以采用全局作用域符来限定名字。
如:

int height;
class Screen
{
public:
    void dummy_fcn(index height)
    {
        cursor = width * ::height; //::限定height为全局变量
    }
private:
    index height;
    index width;
}

12.构造函数初始化式:有些成员必须在构造函数初始列表中进行初始化。对于这样的成员,在构造函数体重对它们赋值不起作用。这样的成员指的是没有默认构造函数的类类型成员,以及const或引用类型的成员。

  class ConstRef {
     public:
         ConstRef(int ii);
     private:
         int i;
         const int ci;
         int &ri;
     };
     // no explicit constructor initializer: error ri is uninitialized
     ConstRef::ConstRef(int ii)
     {              // assignments:
          i = ii;   // ok
          ci = ii;  // error: cannot assign to a const
          ri = i;   // assigns to ri which was not bound to an object
     }

初始化const或引用类型数据成员的唯一机会是在构造函数初始化列表中。

 // ok: explicitly initialize reference and const members
 ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }

13.构造函数初始化类别仅指定用于初始化成员的值,并不知道这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。

     class X {
         int i;
         int j;
     public:
         // run-time error: i is initialized before j
         X(int val): j(val), i(j) { }
     };

上述代码段中,由于i的初始化次序早于j,所以用j来初始化i是非法的。
按照与成员声明一致的次序编写构造函数初始化列表是个好主意。此外,尽可能避免使用成员来初始化其他成员。
14.使用默认实参,可以减少代码重复。(详情见C++ Primer Page 391)
15.由于指针和数组,只对定义在全局作用域中的对象才初始化,当对象定义在局部作用域中时,内置或符合类型的成员不进行初始化。所以如果类包含内置或符合类型的成员,则该类不应该依赖于默认构造函数。它应该定义自己的构造函数来初始化这些成员。
16.类通常应定义一个默认构造函数。且通常,在默认构造函数在给成员提供的初始值应该指出该对象是“空”的。
17.通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显式构造对象。(详情见C++ Primer Page394隐式类类型转换,这部分较为抽象,需要反复研读
18.定义和使用构造函数几乎总是好的。当我们为自己定义的类型提供一个默认构造函数时,允许编译器自动允许那个构造函数,以保证每个类对象在初次使用之前正确地初始化。
19.友元声明和友元定义之间相互依赖:

     class Screen {
         // Window_Mgr members can access private parts of class Screen
         friend class Window_Mgr;
         // ...restofthe Screen class
     };
   Window_Mgr&
     Window_Mgr::relocate(Screen::index r, Screen::index c,
                          Screen& s)
     {
          // ok to refer to height and width
          s.height += r;
          s.width += c;

          return *this;
     }

在这个例子中,类Window_Mgr必须先定义。否则,Screen类就不能将Window_Mgr函数指定为友元。然而relocate函数中由于使用了类Screen而且访问了类Screen成员,所以函数relocate的定义要在类Screen定义之后。
20.每个static数据成员是与类关联的,并不与该类的对象关联。
21.static成员是类的组成部分但不是任何对象的组成部分,因此,static成员函数没有this指针。因为static成员不是任何对象的组成部分,所以static**成员函数**不能被声明为const(但是static数据成员可以定义为const)。毕竟,将成员函数声明为const就是承诺不会修改该函数所属的对象。最后,static成员函数不能声明为虚函数。
22.static数据成员的类型可以是该成员所属的类类型。非static成员被限定声明为其自身类对象的指针或引用:

class Bar {
     public:
         // ...
     private:
         static Bar mem1; // ok
         Bar *mem2;       // ok
         Bar mem3;        // error
     };

23.一些知识点的总结:
(1)空类的大小为1字节(为什么不是0字节?主要是因为每个对象在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含地加一个字节,这样空类在实例化后在内存能够得到了独一无二的地址,所以空类所占的内存大小是1个字节)。这里还要注意的是如果类不为空,那么编译器不会再隐含地添加这一字节。
(2)类内部的成员变量:
普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
static变量:不占用内存,原因是编译器将其放在全局变量区。
类内部的成员函数:
普通函数:不占用内存。
虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的。
24.同一个类的多个对象共享函数代码。而我们访问类的成员函数是通过类里面的一个指针实现,而这个指针指向的是一个table,table里面记录的各个成员函数的地址。(可参考《c++类的成员函数存储方式(是否属于类的对象)—一道面试题引发的思考》http://blog.csdn.net/richerg85/article/details/19540437和《C++中的类所占内存空间总结》http://blog.sina.com.cn/s/blog_69c189bf0100mkeu.html这两篇博客)
25.可用第24条知识点解释在类的复制构造函数和赋值构造函数中,为什么该类的另一个对象可以使用私有成员。

class Message
{
public:
    Message(const std::string str = ""):contents(str){}
    Message(const Message&);
    Message& operator=(const Message&);
private:
    std::string contents;
};
Message::Message(const Message &m)
{
    contents = m.contents;
}
Message& Message::operator=(const Message &m)
{
    contents = m.contents;
    return *this;
}

从上述代码段中,我们看到contents是类Message下的一个私有成员,在一般函数体内我们是不能直接使用m.contents取值。那么在上述两个构造函数体内为什么能够使用呢?
这要从类的私有成员说起,我们知道私有成员(包括数据和成员函数)只能被该类的成员函数和友元函数访问。那么同一类的不同对象的成员函数能否相互访问之间的私有成员呢?答案是肯定的。从第24个知识点我们知道该类的所有对象共享函数代码——也就是说,在该类的成员函数内部,类的不同对象的私有成员都是可见的,这也就是为什么在上述构造函数中m.contents合法的原因。其实不止构造函数,普通的成员函数也是一样的。比如,我们在类Message定义一个copy(Message &m)函数

void Message::copy(const Message &m)
{
    contents = m.contents;
}

这个也是合法的。
其实,在C++ Primer中也提到过每个成员函数具有一个附加的隐含指针this,它的目的就是为了告诉类的成员函数操作的对象是哪个(即当前对象),这样从侧面证实了并不是每个对象都含有一套成员函数的副本。根据这一知识点,我们可以将上述代码段改写为:

void Message::copy(const Message &m)
{
    this->contents = m.contents;
}

也就是说“=”号左边的contents其实也是取自当前对象的私有成员,只不过我们一般把this省略。
所以做个总结:私有成员(包括数据和成员函数)只能被该类的成员函数和友元函数访问。不过类的成员函数访问的私有成员可以取自该类的不同对象。(比如上述copy函数中的contents可以取自当前对象也可以取自对象m)
25.好的类设计者会定义直观易用的类接口,而使用者只关心类中影响他们使用的那部分实现。在简单的应用程序中,类的使用者和设计者往往是同一个人。即使在这种情况下,保持角色区分也是有益的。设计类的接口时,应该考虑的是如何方便类的使用;使用类时,设计者就不应该考虑类如何工作——扮演不同的角色,要明确自己身处这个角色的目的。
26.形参表和函数体处于类作用域中,而函数返回类型不一定在类作用域中。(如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外)——类定义体到右括号结束
example

//类定义
class A
{
public:

private:
    int ival;
    double dval;
};

//类声明
class A;

27.构造函数分两个阶段执行:(1)初始化阶段;(2)普通计算阶段。计算阶段由构造函数体中的所有语句组成,而初始化阶段一般指利用<构造函数初始化列表>进行。
不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段之前。如果没有为类成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数。如果那个类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
28.可以初始化const对象或引用类型的对象,但不能对它赋值。
29.构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。
30.定义构造函数,使用默认实参,减少代码重复。
31.如果类包含内置或者自己定义的复合类型成员,则该类不应该依赖于合成的默认构造函数。它应该定义自己的构造函数来初始化这些成员。
32.抑制由构造函数定义的隐式转换:explicit
explicit只能用在类内部的构造函数声明上。在类定义体外部所做的定义上不再重复它,否则会出错。
33.除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit
34.友元不是授予友元关系那个类的成员,所以它们不受其声明出现部分的访问控制影响。不过,通常,将友元声明成组地放在类定义的开始或结尾较好。一般来讲,必须先定义包含成员函数的类,才能将成员函数设为友元。
35.类可以共享static数据成员,也能共享static成员函数。static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员。static成员函数也不能被声明为虚函数。static数据成员必须在类定义体外部定义。但const static数据成员可以在类定义体内定义,但该数据成员还必须在类的定义体之外进行定义。
example

class Account
{
public:
    static double rate();
    static void rate(double);
private:
    static const int period = 30;
};

const int Account::period //在类定义体外,不需要static关键字 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值