C++ STL 体系结构、编程方法及存在的问题.pdf
《C++ STL 体系结构、编程方法及存在的问题.pdf》由会员分享,可在线阅读,更多相关《C++ STL 体系结构、编程方法及存在的问题.pdf(20页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、C+STL 体系结构、编程方法及存在的问题体系结构、编程方法及存在的问题 10448236 李健 一、一、STL 概述概述 1.1 C+标准库标准库 高级程序设计语言希望尽可能减少程序员的重复工作,因此提供了各种抽象机制降低程序复杂性。在程序设计实践中积累了许多经验和代码,充分利用这些经验和代码是降低程序复杂性的有效途径。程序设计语言必须提供代码重用的机制。一般而言有源代码级别的重用和二进制代码级别的重用两种机制,源代码级别的重用非常简单,只需要将源代码一起编译即可。但是许多时候源代码丢失或者厂商不愿意公开源代码,只有二进制代码可用,此时程序设计语言应该提供重用二进制代码的机制。许多程序设计语
2、言提供了标准库和相应的库管理机制,通过标准库用户可以使用常用的算法和数据结构,通过库管理机制用户可以使用第三方的库,从而扩充标准库。现代编程语言倾向于将程序设计语言理解为程序设计环境。除了核心的语言成分外,还包括编程实践中经常用到的算法和数据结构,作为核心语言的支持。例如 Java 规范中就明确提到语言提供的标准库 java.lang.*将自动加载,C 语言规范中对标准库也有相应的定义。Pascal 因为没有定义标准库和提供库管理机制被许多 C 程序员诟病。C+在许多方面类似 C,例如采用 C 中的虚拟机观点,具有指针,类型结构的内存布局于 C 相同,但是 C+在更多的方面与 C 不同。C+中
3、引入了更高级的面向对象抽象机制,提供了构造大型程序的名空间机制,具有比 C 复杂的类型机制,具有编译时模板机制,具有更多的运行时机制。因此需要设计体现 C+特色的标准库。C+的特色在于提供灵活的机制,执行效率高。标准库作为语言的支持成分,需要大量的重复使用,因此 C+标准库应该体现效率。这也是 C 标准库的特征之一。其次 C+提供了高级抽象机制,因此标准库应该通用。通用意味着标准库对用户定义的类型有良好的支持。C+中引入面向对象机制,强调信息封装,用户定义类型千变万化,因此标准库应当具有操作任意类型的能力。同时对这些用户定义类型不能有任何假定,否则不方便用户使用标准库。最后标准库的组织应该结构
4、化,包含程序设计中最有用最核心的工具。C+标准库有如下设计目标:结构化良好、完整全面、包含最有用的程序组件;程序组件在理论上尽可能抽象,但是执行效率尽可能接近手工编写的 C 代码。1.2 标准模板库标准模板库 STL STL 是 Standard Template Library 的简称。STL 不仅是可重用的组件库,而且是一个包括算法与数据结构的软件体系结构。STL 整体设计庞大、稳定、完整且可扩展、注重效率,体现了泛型编程的精髓。STL 中广泛使用模板技术获取通用性,模板技术的本质是参数化的类型声明和使用。C+提供的模板机制体现了 C+的许多考虑:注重效率,记法简洁。STL 中广泛使用模板
5、和重载技术,采用泛型编程技术,STL 中的算法和数据结构的效率有着严格的保证,采用算法分析中的渐进复杂度表示。使得标准库非常通用。早期的 STL实现由 Stepanov 和 Austern 完成。下表是 STL 在数值计算方面的效率。Data type Qsort in C Hand coded Numerical recipeSTL Int 5.90-5.92 1.54-1.65 1.46-1.50 1.11-1.14 Short 9.03-9.03 1.73-1.80 1.58-1.59 1.17-1.19 Byte 7.87-7.89 0.98-1.02 0.98-1.00 0.70-0
6、.73 Float 7.08-7.10 2.38-2.50 2.48-2.55 1.97-2.02 Double 16.4-16.4 2.70-2.93 2.72-2.83 2.28-2.37 http:/theory.standford.edu/amitp/rants/c+-vs-c/库是一系列程序组件的集合,它们可以在不同的程序中重复使用。库函数遵照以下的规则:接受一些符合预先指定类型的参数,返回一个特定类型的值或改变一些已有的值。设计一个能被广泛使用的 C 或 C+库的一个重要组成部分就是,猜测出这些函数中最有可能出现的参数组合情况。设计一个能被广泛使用的 C+库的另一个重要组成部分就是
7、猜测类中最有可能出现的成员对象类型的组合情况。通用容器的需求通用容器的需求 程序员不希望重复设计链表、队列、树这些常用的数据结构,但是 C 不提供抽象操作类型的机制。因此 C 程序员利用宏和其它编程技巧实现了通用的链表操作,下面是从 Linux下 gcc 源代码中摘录的关于通用链表操作的实现。/*List declarations.*/#define LIST_HEAD(name,type)struct name struct type*lh_first;/*first element*/#define LIST_HEAD_INITIALIZER(head)NULL#define LIST_E
8、NTRY(type)struct struct type*le_next;/*next element*/struct type*le_prev;/*address of previous next element*/*List functions.*/#define LIST_EMPTY(head)(head)-lh_first=NULL)#define LIST_FIRST(head)(head)-lh_first)#define LIST_FOREACH(var,head,field)for(var)=LIST_FIRST(head);(var);(var)=LIST_NEXT(var)
9、,field)#define LIST_INIT(head)do LIST_FIRST(head)=NULL;while(0)#define LIST_INSERT_AFTER(listelm,elm,field)do if(LIST_NEXT(elm),field)=LIST_NEXT(listelm),field)!=NULL)LIST_NEXT(listelm),field)-field.le_prev=&LIST_NEXT(elm),field);LIST_NEXT(listelm),field)=(elm);(elm)-field.le_prev=&LIST_NEXT(listelm
10、),field);while(0)#define LIST_INSERT_BEFORE(listelm,elm,field)do (elm)-field.le_prev=(listelm)-field.le_prev;LIST_NEXT(elm),field)=(listelm);*(listelm)-field.le_prev=(elm);(listelm)-field.le_prev=&LIST_NEXT(elm),field);while(0)#define LIST_INSERT_HEAD(head,elm,field)do if(LIST_NEXT(elm),field)=LIST_
11、FIRST(head)!=NULL)LIST_FIRST(head)-field.le_prev=&LIST_NEXT(elm),field);LIST_FIRST(head)=(elm);(elm)-field.le_prev=&LIST_FIRST(head);while(0)#define LIST_NEXT(elm,field)(elm)-field.le_next)#define LIST_REMOVE(elm,field)do if(LIST_NEXT(elm),field)!=NULL)LIST_NEXT(elm),field)-field.le_prev=(elm)-field
12、.le_prev;*(elm)-field.le_prev=LIST_NEXT(elm),field);while(0)该代码的主要意图是定义一个链表结构,该结构可以内嵌到用户定义的类型中,通过链表结构实现链表操作。从代码可以看出这样的实现很难读懂;采用宏实现无法进行类型检查,加大了调试难度;使用不自然,容易出错。象上面C中的链表操作要求用户定义的类型中嵌入链表结构,这违反了信息封装的原则,同时使用不方便,因此程序员希望 C+能提供自然、通用的容器,这些容器能容纳用户定义的类型,并提供各种操作,而不需要强制用户定义的类型具有某种结构。例如向量、链表、队列都属于容器。这些容器提供的操作不依赖于容
13、器包含的类型。通用算法的需求通用算法的需求 好的算法是一笔重要的财富,许多算法其实对参数只有很少的要求。程序员希望能够重用这些算法。不同算法对参数有不同的要求,C+标准库必须对用户类型进行抽象,得出算法所需的最低要求。迭代器的需求迭代器的需求 Pascal 语言的设计者 N.Wirth 提出过一个著名的公式:算法数据结构程序。数据结构即通用容器,算法还需要操作这些通用容器,但是如何操纵这些容器呢?迭代器提供了一种简洁的记法解决这些问题。迭代器扩展了指针的概念,使得存取容器变得异常容易,同时迭代器也具有非常好的可扩展性。与迭代器相关的是如何表示迭代器界定的元素集合,为此引入区间概念。区间是指元素
14、集合,其中的元素具有“第一”和“最后”的概念,每个元素(除了最后一个元素)都有后继,只要以“一个接一个”的方式,便能遍历整个集合,取得从头至尾的所有元素。函数对象的需求函数对象的需求 C 中提供了函数指针,但是函数指针不适合在 C+中工作,因为函数指针不能很好的与C+的其它语言成分结合在一起,例如函数指针无法重载,函数指针无法建立模板,函数指针没有作用域规则。因此 C+扩展了函数指针的概念提供了函数对象。其实函数对象就是提供了 operator()重载的对象,但是经过这样处理之后将函数指针具有的功能与 C+中其它的语言成分完全结合在一起,获得比函数指针更强大的能力。模板的需求模板的需求 模板概
15、念根植于对描述参数化容器类的愿望,扩展静态类型检查所能处理的问题。C+中的解决方法之一是采用宏机制,但是宏机制有许多缺点:根本不遵守作用域规则和类型规则,不能与其它语言特性友好相处(构造函数和析构函数)。从前面的代码也可看出宏机制不自然、难于理解,不适合构造大型程序。因此必须采用新的机制使得记法方便、运行效率高、类型安全。通用容器类的特点是对不同类型的容器元素,操作也不同。例如通用链表操作加入要取表头的元素,如果是 int 型链表那么该操作返回 int 值,如果是 double 型链表,同样的操作要返回 double 值。也就是说通用容器类能够被参数化。C+是静态编译型语言,要想对实现参数化的
16、类,必须在编译时完成。同时 C+也很强调类型匹配,因此参数化类型必须经过静态类型检查。参数化类型不能对参数有任何假定,因此必须与语言其它特性友好相处。C+中的模板基于源代码正文,占用的是编译时间。原因是希望能有宏机制的效率。模板与其它语言成分的关系模板与其它语言成分的关系 模板概念是 C+中非常有效的概念,原因在于模板可以与其它语言成分协同工作,而宏机制却不行。例如模板函数和模板类可以向普通函数和类一样,用在同样的上下文中,模板类中也可以定义虚函数,类声明中也可以定义模板成员函数,以至于模板类可以继承,模板函数可以重载。总之,模板概念已经完全与其它语言成分融合在一起,构成 C+语言强大的语言特
17、性之一。模板的威力在于把握了约束的时机,首先将模板作为预处理的文本,然后将约束检查推迟到使用模板的时候,兼顾了效率和代码尺寸。1.3 C+语言机制概述语言机制概述 正是 C+语言提供的机制使得 STL 取得了成功。STL 中用到的 C+语言特性主要有模板和函数重载。1.3.1 模板机制模板机制 声明模板时 C+设计者希望函数的参数传递机制也能适合模板参数传递,这也体现 C+的风格,尽可能将机制贯彻到底,减少程序员的学习成本。因此模板参数传递具有 C+中函数传参的特性,如缺省参数值、模板类重载、模板参数的类型推导等等。模板参数的限制模板参数的限制 模板参数没有任何限制,在这一点上模板与宏机制完全
18、一样。没有对模板参数进行限制使得模板可以提供像宏机制一样的灵活性,避免增加编译器的负担。实现时完全可以将模板当作预处理,编译器的其它部分不用改变。不限制模板参数使得所有的类型检查推迟到模板使用时,因此可能出现莫名其妙的出错信息。如果限制模板参数,那么模板在很大程度上称为语言的内部机制,与宏机制有很大差别,一方面增加了编译器的复杂度,很难实现,不利于模板的使用。另一方面使得针对模板的调试会带来很大的方便。模板参数检查只能放到模板实例化时进行,因为只有在实例化时模板才接受已经定义好的类型。这样做有一个缺点,如果一个模板没有被实例化,那么模板中的错误将无法检测。有时候关于模板的出错信息表现为链接错误
19、,这样的错误会使用户感到迷惑。解决方法是构造更强大的编译器。限制模板参数的一种方法是采用继承描述模板参数。这种方式也不尽人意。因为许多时候继承并不能完全描述类型之间的关系。例如 int 和 complex 都属于数值类型,但这两种类型并没有继承关系,但一般的数值计算函数都接受这两种类型作为参数。还有导致过度使用继承同时引入一些垃圾代码,如引入一个完全抽象的基类,然后增加基类的限制。比较上面两种看法可以看出不限制模板参数更有利。因此从工程实际的角度看,增强编译器对模板出错信息的诊断是当务之急。引入非类型参数的必要引入非类型参数的必要 引入非类型参数的最主要目的是代替数组,提供具有安全检查的替代品
20、。例如如下代码 template class Buffer T vi;int sz;public:Buffer():sz(i)int size()return sz;/可以提供类似于 Java 数组 length()的功能。采用这种做法可以减少数组越界的情况。参数类型匹配参数类型匹配 所有的参数检查都在实例化模板时完成。作用域作用域 C+支持分别编译,因此需要仔细考察引入模板后对分别编译产生的影响。传统上 C+采用.cpp 文件和.hpp 文件作为源文件。其中.hpp 是接口部分,.cpp 是实现部分。模板定义无论放入接口部分还是放入实现部分,都会出现问题。模板放入接口部分将导致编译单位将包含
21、所有的模板定义,会导致编译性能下降。模板放入实现部分将涉及如何找到模板定义,实现起来特别复杂,因为编译单元需要保存模板定义。解决方法是构造功能更强的编译器,该编译器能够记录已经遇到的模板定义,以及模板的出处。并将这些信息记录目标文件中,在模板实例化时查找这些相应的符号表。有了更强的编译器之后,模板作为语言成分可以纳入到已有的作用域规则之中。避免代码膨胀避免代码膨胀 使用模板的一个代价是实例化模板时总要重新生成类声明,如果程序中多处使用模板将会生成非常多的冗余代码,从而增加编译时间和占用内存。实际上可以避免重复生成代码,注意到类模板和函数模板的声明由类型参数唯一确定,实际类型参数相同的模板实例实
22、际上可以共享同一份类定义。实例化实例化?显式实例化 显式实例化非常有必要。因为 C+只在实例化时才真正展开模板同时进行类型检查,但此时有关模板的错误往往表现为链接错误。因此需要提供一种机制使程序员能够强制编译器生成模板实例,并进行类型检查,这相当于使模板的类型检查提前到编译时刻,因此出错信息表现为编译错误,有利于调试。显示实例化可能使得分别编译时产生冗余代码。?模板特殊化 模板对任意模板参数都描述了一个函数或者类的定义。但是出于效率考虑,有许多特殊情况可以单独设计。简单例子是一般类都有构造函数和析构函数,但是 C+的基本类型并不需要这些函数,因此对于做内存管理的模板而言,初始化这样的类型可以直
23、接操作内存而不必遵循对一般类的操作流程。因为模板参数类型可能匹配一般模板也可能匹配特殊模板,因此需要定义规则使编译器能够知道使用哪个模板定义。对此 C+采用使用之前先声明的方法,如果在作用域中声明了特殊化,则使用特殊化模板,否则使用一般化模板。注重效率:减少编译时间(实例化时机和实例化动作),减少运行时间(模板特殊化)指向类模板成员函数的指针指向类模板成员函数的指针 显然 C+在设计时也考虑到这些问题,因此提供了相应的机制,如 C+标准中定义了指向类成员函数的指针。另一方面利用 typedef 可以简化和包装类型定义,C+标准允许在typedef 使用模板声明。有了以上的语言特性,加上 tra
24、its 技术,即可实现指向类模板成员函数的指针。示意代码如下:template class A public:T f(T t)return t;/定义模板类的成员函数;template struct A_traits typedef T(A:*f_ptr)(T);/定义指向 A 类的成员函数的指针类型;int main(void)A a;A_traits:f_ptr fp;fp=&A:f;/指向具体对象的成员函数 (a.*fp)(3);/调用该成员函数 代码在 g+2.4.3 Linux 平台下编译通过。1.3.2 函数重载函数重载 函数重载是 C+中的另一种抽象机制,也是基于类型的程序设计的
25、重要手段。函数重载是指在作用域中具有相同名字的函数,这些函数的参数类型或参数个数不同,因而被认为不同的函数,当发生函数调用是编译器根据实参个数和类型匹配相应的函数。函数重载使得程序员将概念上相似的操作抽象为一个函数名,减少了程序员的概念复杂性,例如加法操作可以用于实数、复数和向量,程序员只需面对抽象的加法操作,而由编译器选择相应类型的法操作。函数重载的关键之处在于参数匹配。当发生函数调用时,编译器如何找到对应的重载函数呢?这依赖与以下规则:精确匹配和类型转换匹配。精确匹配指实参类型与某个重载函数的形参精确匹配,当然这个重载函数就是所要的函数。类型转换匹配是指实参类型经过某种转换后可与形参精确匹
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ STL 体系结构、编程方法及存在的问题 体系结构 编程 方法 存在 问题
限制150内