HP STL
HP STL 是 Alexandar Stepanov(STL 标准模板库之父,文章后续简称 Stepanov)在惠普 Palo Alto 实验室工作时,与 Meng Lee 合作完成的。HP STL 是开放源码的,即任何人都可以免费使用、复制、修改、发布和销售该软件以及相关文档,但前提是必须在相关文档中,加入 HP STL 版本信息和授权信息。
HP STL 是 C++ STL 的第一个实现版本,其它版本的 C++ STL 一般是以 HP STL 为蓝本实现出来的。不过,现在已经很少直接使用此版本的 STL 了。
SGI STL
Stepanov 在离开 HP 之后,就加入到了 SGI 公司,并和 Matt Austern 等人开发了 SGI STL。严格意义上来说,它是 HP STL 的一个继承版本。和 HP STL 一样,SGI STL 也是开源的,其源代码的可读性可非常好,并且任何人都可以修改和销售它。
注意,和 STL 官方版本来说,SGI STL 只能算是一个“民间”版本,因此并不是所有支持 C++ 的编译器都支持使用 SGI STL 模板库,唯一能确定的是,GCC(Linux 下的 C++ 编译器)是支持的,所以 SGI STL 在 Linux 平台上的性能非常出色。
STLport
为了使 SGI STL 的基本代码都适用于 VC++ 和 C++ Builder 等多种编译器,俄国人 Boris Fomitchev 建立了一个 free 项目来开发 STLport,此版本 STL 是开放源码的。
PJ STL
PJ STL(全称为 P.J. Plauger STL)是由 P.J.Plauger(美国人,1965 年毕业于普林斯顿大学,物理专业学士)参照 HP STL 实现出来的,也是 HP STL 的一个继承版本,因此该头文件中不仅含有 HP STL 的相关授权信息,同时还有 P.J.Plauger 本人的版权信息。
其实 PJ STL 是 P.J.Plauger 公司的产品,尽管该公司当时只有 3 个人。
PJ STL 被 Visual C++ 编译器所采用,但和 PH STL、SGI STL 不同的是,PJ STL 并不是开源。
Rouge Wave STL
该版本的 STL 是由 Rouge Wave 公司开发的,也是继承 HP STL 的一个版本,它也不是开源的。
Rouge Wave STL 用于 Borland C++ Builder 编译器中,我们可以在 C++ Builder 的 Inculde 子目录中找到该 STL 的所有头文件。
值得一提的是,尽管 Rouge Wave STL 的性能不是很好,但 C++ Builder 对 C++ 语言标准的支持还算不错,所以在一定程度上使 Rouge Wave STL 的表现得以改善。
遗憾的是,由于 Rouge Wave STL 长期没有更新且不完全符合标准,因此 Rouge Wave STL 在 6.0 版本时改用了 STLport 版本(之后的版本也都采用了 STLport),不过考虑到和之前版本的兼容,6.0 版本中依旧保留了 Rouge Wave STL。
Rouge Wave 公司在 C++ 程序库领域应该说是鼎鼎大名,对 C++ 标准化的过程出力甚多。不过 Rouge Wave STL 版本不仅更新频率慢,费用还高,基于这两个原因,Borland 在 6.0 版本决定弃用 Rouge Wave STL 而改用 STLport。
熟练使用STL标准库是每个C++程序员的必备技能!
C++ 标准程序库发展至今,几乎所有内容都被设计为了模板的形式,STL 已经成为 C++ 程序库的重要组成部分。可以这么说,如果 C++ 不支持 STL 标准模板库,就无法使用程序库。那么,C++ 为什么要引入 STL 呢?
在大多数人看来,计算机既神秘有能干,但在程序员的眼中,计算机又蠢又笨,唯一的优点就是运算速度比人快,不给指令什么都干不了,就是给指令,计算机也不能灵活运用。
比如说,在 C++ 中,同样一个加法,不同的数据类型,要给出不同的运行代码:
#include<iostream>
using namespace std;
//处理整形之间的加法
int addInt(int m, int n) {
return m + n;
}
//处理浮点类型值之间的加法
double addDouble(double i, double j) {
return i + j;
}
//......
int main()
{
cout << addInt(1, 2) << endl << addDouble(1.2, 2.1);
return 0;
}
运行结果为:
3
3.3
像这样,对于每一种数据类型,我们都必须给计算机设计一个单独的函数,实在太繁琐了。由此可以感受到,计算机并不具备人类的基本思维,处理问题不灵活。
为了让计算机不断接近人类的认知能力,科学家们想了很多办法,比如使用面向对象开发技术,通过类的封装和函数重载,可以部分解决上面的问题:
#include<iostream>
using namespace std;
class calc {
public:
//处理整形之间的加法
int add(int m, int n) {
return m + n;
}
//处理浮点类型值之间的加法
double add(double i, double j) {
return i + j;
}
//......
};
int main()
{
calc a;
cout << a.add(1, 2) << endl << a.add(1.2, 2.1);
return 0;
}
运行结果为:
3
3.3
有关 C++ 函数重载,可阅读《C++函数重载详解》一节。
创建这样的类之后,当通过类对象调用 add 方法时,就无需考虑参数的具体数据类型了。但从某种程序上来说,这也仅是让计算机聪明了一点点。
为了让程序更加智能、人性化,经过科学家们持续的努力,C++ 引入了模板这个功能。模板可以认为是针对一个或多个尚未明确的类型而编写的一个个函数,是 C++ 的一个新特性。
通过引入模板,C++ 引申出了泛型编程技术。简单的理解泛型编程,即使用该技术编写的代码,可以支持多种数据类型。也就是说,通过泛型编程,能编写出可重复利用的程序代码,并且其运行效率和针对某特定数据类型而设计的代码相同。由此可见,C++ 很需要泛型这种新的编程模式,可以减轻编程的工作量,增强代码的重用性。
有关泛型及泛型编程,读者可阅读《泛型是什么,C++泛型编程又是什么?》一文做详细了解。
在 C++ 支持模板功能,引入了泛型编程思想的基础上,C++ 程序员们想编写出很多通用的针对不同数据类型的算法,其中 STL 脱颖而出成为 C++ 标准,并被引入 C++ 标准程序库。
STL 是一个具有高度可用性、高效的模板库,该库包含了诸多在计算机科学领域中常用的基础数据结构和算法,掌握了 STL 标准,很多功能就无需自己费心费力的去实现了(不用重复的造轮子),直接拿来用即可。
总的来说,STL 模板库是 C++ 标准程序库的重要组成部分,为 C++ 程序员提供了大量的可扩展的程序框架,高度实现了代码的可重用性,并且它是内置的,不需要额外安装,使用非常方便。
泛型是什么,C++泛型编程又是什么?
在计算机程序设计领域,为了避免因数据类型的不同,而被迫重复编写大量相同业务逻辑的代码,人们发展的泛型及泛型编程技术。
那么,什么是泛型呢?本节就带领读者深度剖析一下这个问题。
所以泛型,实质上就是不使用具体数据类型(例如 int、double、float 等),而是使用一种通用类型来进行程序设计的方法,该方法可以大规模的减少程序代码的编写量,让程序员可以集中精力用于业务逻辑的实现。
为了更好地说明使用具体数据类型有多么麻烦,这里先举个例子,假设客户需要一个函数,功能是返回两个 int 类型数据中较大的那个,很多读者自然而然会编写如下代码:
int maxt(int x, int y) {
return (x > y) ? x : y;
}
可是没过几天,该用户又提出需要编写一个返回两个 double 类型数据中较大的那个,于是我们需要之前的代码进行修改:
double maxt(double x, double y) {
return (x > y) ? x : y;
}
之后,该用户又提出需要再编写一个能返回两个 char 类型数据中较大的那个…。可以看到,只是因为数据类型不同,就迫使我们不得不把具有相同功能的代码写了若干遍,这样的实现方法简直令人崩溃。
为了解决类似的问题,有聪明的人将代码修改成如下的样子:
T maxt(T x, T y) {
return (x > y) ? x : y;
}
如此,当用户需要某个数据类型的 maxt 函数时,我们只需要把其中的 T 替换成用户需要的实际数据类型就行了。
那么,代码中的 T 是什么呢?很明显,这是一个占位符,更确切的说是一个类型占位符。也就是说,将来在 T 这个位置上的是一个真实、具体的数据类型,至于到底是哪个类型,完全取决于用户的需求。
当然,如果硬要给 T 这种类型占位符也叫做一种数据类型,提供这种想法的发明者称它为泛型(generic type),而使用这种类型占位符的编程方式就被称为泛型编程。
值得一提的是,既然泛型并不是真实的数据类型,那么使用泛型编写的代码也就不是真正的程序实体,只能算是一个程序实体的样板。故此,通常形象的将这种使用了泛型的代码称为模板,由模板生成实际代码的过程称为模板的具体实现。
注意,类型占位符的替换工作,不需要人为操控,可以完全交由计算机来完成,更准确的说,是交由编译器在编译阶段来完成模板的具体实现。
总之一句话,泛型也是一种数据类型,只不过它是一种用来代替所有类型的“通用类型”。在 C++ 中,用以支持泛型应用的就是标准模板库 STL,它提供了 C++ 泛型设计常用的类模板和函数模板。
有关 STL 模板库,由于不是本节重点,这里不再赘述,后续章节会做详细介绍。
C++ STL基本组成(6大组件+13个头文件)
通常认为,STL 是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成,其中后面 4 部分是为前 2 部分服务的,它们各自的含义如表 1 所示。
STL的组成 | 含义 |
---|---|
容器 | 一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。 |
算法 | STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 中,少部分位于头文件 中。 |
迭代器 | 在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,扮演着容器和算法之间的胶合剂。 |
函数对象 | 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。 |
适配器 | 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。 |
内存分配器 | 为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。 |
关于表 1 中罗列的 STL 的构成,初学者简单了解即可,后续章节将专门对它们做系统的深入讲解。
另外,在惠普实验室最初发行的版本中,STL 被组织成 48 个头文件;但在 C++ 标准中,它们被重新组织为 13 个头文件,如表 2 所示。
关于这些头文件的作用和用法,本节不做过多赘述,后续章节会做详细介绍。
按照 C++ 标准库的规定,所有标准头文件都不再有扩展名。以 为例,此为无扩展名的形式,而 <vector.h> 为有扩展名的形式。
但是,或许是为了向下兼容,或许是为了内部组织规划,某些 STL 版本同时存储具备扩展名和无扩展名的两份文件(例如 Visual C++ 支持的 Dinkumware 版本同时具备 <vector.h> 和 );甚至有些 STL 版本同时拥有 3 种形式的头文件(例如 SGI 版本同时拥有 、<vector.h> 和 <stl_vector.h>);但也有个别的 STL 版本只存在包含扩展名的头文件(例如 C++ Builder 的 RaugeWare 版本只有 <vector.h>)。
建议读者养成良好的习惯,遵照 C++ 规范,使用无扩展名的头文件。
如何衡量一个算法的执行效率?
学习 C++ 标准库,特别是 STL,经常需要考量算法和成员函数的效能(也就是运行效率,又称复杂度),因此每个学习 STL 的读者都需要掌握一种衡量算法(或成员函数)复杂度的方法,目前最常用的方法称为大 O 表示法(注意,不是数字 0,而是字母 O)。
使用大 O 表示法衡量某个算法的复杂度,其实就是将该算法的运行时间用输入量为 n 的函数表示出来。这里的输入量 n 在 STL 中通常指的是算法操作的元素个数。
举个例子,当算法运行时间随元素个数成线性增长时(即如果元素个数呈倍数增长,运行时间也呈倍数增长),该算法的复杂度用 O(n) 来表示;反之,如果算法的运行时间和输入量 n 无关,则该算法的复杂度就用 O(1) 来表示。
表 1 列出了常见的算法复杂度种类,以及使用大 O 表示法表示的复杂度。
算法复杂度种类 | 含义 | 大 O 表示法 |
---|---|---|
常数阶 | 算法运行时间和所操作的元素个数无关 | O(1) |
对数阶 | 算法运行时间随所操作的元素个数呈对数增长 | O(log(n)) |
线性阶 | 算法运行时间随所操作的元素个数呈线性增长 | O(n) |
指数阶(m次方,m为数字) | 算法运行时间随所操作的元素个数呈 m 次方增长 O(nm) | 常见的有 O(n2)、O(n3) 等 |
值得注意的是,大 O 表示法并不关心算法运行所消耗的具体时间,换句话说,对于那些影响算法运行效率较小的因素,使用大 O 表示法表示时会直接将其忽略。例如,某个算法运行的复杂度为 O(n),呈线性增长,但至于线性增长的具体程度(是 100n 还是 2n),在大 O 表示法看来,它们是一样的。也就是说,采用这种测量法则,任何两个线性算法都将被视为具有相同的复杂度。
采用大 O 表示法甚至会出现这种一种情况,即带有巨大常量的线性算法,很有可能会比小常量的指数算法更受欢迎,因为该方法无法显示出真实的运行时间。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0
采用大 O 表示法甚至会出现这种一种情况,即带有巨大常量的线性算法,很有可能会比小常量的指数算法更受欢迎,因为该方法无法显示出真实的运行时间。
[外链图片转存中…(img-ZUtH38PL-1725734598508)]
[外链图片转存中…(img-Weuv3F81-1725734598508)]
[外链图片转存中…(img-py02yPIi-1725734598509)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0