STL快速入门(一)

STL简介

  • 1.STL历史
  • 2. STL组件
  • 3. STL基本结构
  • 4. STL使用方法
  • 参考

1.STL历史

C 语言是 1972 年由美国的 Dennis Ritchie 设计发明的,并首次在 UNIX 操作系统的计算机上使用。C 语言由早期的汇编语言 BCPL 发展演变而来。随着微型计算机的日益普及,C 语言出现了许多其他版本,由于没有统一的标准,各版本之间出现了不一致之处。ANSI 因此为 C 语言制定一套 ANSI 标准,后来成为现行的 C 语言标准。

早期的 C 语言主要用于 UNIX 系统。因其强大的功能和各方面的优点逐渐被人们认识。20 世纪 80 年代,C 语言开始应用于其他操作系统,并很快在各种计算机上得到广泛应用,成为当代最优秀的程序设计语言之一。

C 语言的表现能力和处理能力极强。它不仅具有丰富的运算符和数据类型,便于实现各类复杂的数据结构,还可以直接访问内存物理地址,甚至进行位操作。此外,C 语言还可实现对硬件的编程操作,十分便捷方便。

1983 年,贝尔实验室的 BjameStrou-strup 推出了 C++。C++ 进一步扩充和完善了 C 语言,成为面向对象的程序设计语言。最初 C++ 主要用于小型计算机系统。1988 年,出现了第一个用于 PC 的 Z0RTECH C++ 2.0 编译系统;1989 年,出现了 Turbo C++2.0 编译器。

1991 年开始,Borland 公司陆续推出了 Borland C++ 2.0/3.0/4.0 系统。而微软公司直到 1992 年,才推出基于 DOS 的 MS C/C++ 7.0 系统。

1993 年,微软推出了面向 Windows 的 Visual C++ 1. 0 系统,并于 1998 年推出 了 Visual C++6. 0。

C 语言提供了具有可适应性的、强大的抽象机制,用于对问题进行抽象。这种语言结构允许程序员创建和使用新的类型,而这些新的类型则可以与实际应用中所包含的概念相适应。在 C++ 的最新发展过程中,C++ 新增了模板新特性。通过使用模板,程序具备更好的代码重用性能。

1994 年 7 月,美国国家标准与技术研究院通过投票决定,将 STL 纳入 C++ 标准,使之成为 C++ 库的重要组成部分。1997 年,C++ 标准完成了最近一次的修改,官方名称为 ISO/IEC 14882。

STL 从根本上讲是“容器”的集合,也是组件的集合。容器包括 list、vector、set、map 等;组件包括迭代器等。STL 的目的是标准化组件,与 Visual C++ 中的 ATL 相似。

STL 是 C++ 的一部分,不用额外安装,被内建在支持 C++ 的编译器中。

STL 的算法是标准算法,其实现了将已经定义好的算法应用在容器的对象上。

2. STL组件

STL 是 C++ 标准程序库的核心。STL 内的所有组件都由模板构成,其元素可以是任意型别。程序员通过选用恰当的群集类别调用其成员函数和算法中的数据即可,但代价是 STL 晦涩难懂。

STL 组件主要包括容器迭代器算法仿函数
容器
容器即用来存储并管理某类对象的集合。例如鱼缸是用来盛放金鱼的容器。

每一种容器都有其优点和缺点。为满足程序的各种需求,STL 准备了多种容器类型,容器可以是 arrays 或是 linked lists,或者每个元素有特别的键值。
迭代器
迭代器用于在一个对象群集的元素上进行遍历动作。对象群集可能是容器,也可能是容器的一部分。

迭代器的主要用途是为容器提供一组很小的公共接口。利用这个接口,某项操作可以行进至群集内的下一个元素。

每种容器都提供了各自的迭代器。迭代器了解该容器的内部结构,所以能够正确行进。迭代器的接口和一般指针类似。
算法
算法用来处理群集内的元素,可以出于不同目的搜寻、排序、修改、使用那些元素。所有容器的迭代器都提供一致的接口,通过迭代器的协助,算法程序可以用于任意容器。

STL 的一个特性是将数据和操作分离。数据由容器类别加以管理,操作则由可定制的算法定义。迭代器在两者之间充当“粘合剂”,以使算法可以和容器交互运作。

STL 的另一个特性即组件可以针对任意型别运作。“标准模板库”这一名称即表示“可接受任意型别”的模板,并且这些型别均可执行必要操作。

在 STL 中,容器又分为序列式容器和关联式容器两大类,而迭代器的功能主要是遍历容器内全部或部分元素的对象。迭代器可划分为 5 种类属,这 5 种类属归属两种类型:双向迭代器和随机存取迭代器。

SIL 中提供的算法包括搜寻、排序、复制、重新排序、修改、数值运算等。
仿函数
STL中大量运用了仿函数。仿函数具有泛型编程强大的威力,是纯粹抽象概念的例证。

3. STL基本结构

STL 是 C++ 通用库,由迭代器、算法、容器、仿函数、配接器和配置器(即内存配置器)组成。

容器
STL 包含诸多容器类。容器类是可以包含其他对象的类,就像数组和队列堆栈等数据结构包含整数、小数、类等数据成员一样。STL 可以包含常见的向量类、链表类、双向队列类、集合类、图类等,每个类都是一种模板,这些模板可以包含各种类型的对象。

目前,STL 中已经提供的容器主要如下:

  • array< T,N >:数组容器,是一个长度固定的序列,有 N 个 T 类型的对象,不能增加或删除元素。
  • vector< T >:向量容器。是一个长度可变的序列,用来存放T类型的对象。必要时,可以自动增加容量,但只能在序列的末尾高效地增加或删除元素。
  • list < T >:双向链表容器,完成了标准 C++ 数据结构中链表的所有功能。是一个长度可变的、由 T 类型对象组成的序列,它以双向链表的形式组织元素,在这个序列的任何地方都可以高效地增加或删除元素。
  • forward list< T >:正向链表容器。是一个长度可变的、由 T 类型对象组成的序列,它以单链表的形式组织元素,是一类比链表容器快、更节省内存的容器,但是它内部的元素只能从第一个元素开始访问。
  • queue < T >:队列容器,完成了标准 C++ 数据结构中队列的所有功能。
  • stack < T >:栈容器,完成了标准 C++ 数据结构中栈的所有功能。
  • deque < T >:双端队列容器,完成了标准 C++ 数据结构中栈的所有功能。是一个长度可变的、可以自动增长的序列,在序列的两端都不能高效地增加或删除元素。
  • priority_queue < T >:一种按值排序的队列容器。
  • set < T >:一种集合容器。
  • multiset < T >:一种允许出现重复元素的集合容器。
  • map < key, val >:一种关联数组容器。
  • multimap < key, val >:一种允许出现重复 key 值的关联数组容器。

以上容器设计高效,还提供了接口。程序员可以在任何适当的地方使用它们。
容器可以分为序列式容器和关联式容器两大类。序列式容器主要有 vector、list 和 deque;关联式容器包括 set、map、multiset 和 multimap 等容器模板类。

算法
STL 提供了非常多的数据结构算法。这些算法在命名空间 std 的范围内定义,通过包含头文件 < algorithm > 来获得使用权。
常见的部分算法如下:

  • for_each();
  • find();
  • find_if();
  • count();
  • count_if();
  • replace();
  • replace_if();
  • copy();
  • unique_copy();
  • sort();
  • equal_range();
  • merge();

STL 中的所有算法都是基于模板实现的。

迭代器
通俗来讲,迭代器就是指示器。迭代器技术能够使程序非常快捷地实现对 STL 容器中内容的反复访问。反复访问意味着一次可以访问一个或多个元素。

迭代器为访问容器提供了通用的方法,类似于 C++ 的指针。当参数化类型是 C++ 内部类型时,迭代器即 C++ 指针。
STL 定义了 5 种类型的指示器,并根据其使用方法予以命名。每种容器都支持某种类别的迭代器。常见的迭代器包括输入、输出、前向、双向和随机接入等类别:

  • 输入迭代器主要用于为程序中需要的数据源提供输入接口,此处的数据源一般指容器、数据流等。输入迭代器只能从一个序列中读取数值。该迭代器可以被修改和被引用。
  • 输出迭代器主要用于输出程序中已经得到的数据结果(容器,数据流)。输出迭代器只能向一个序列写入数据。该迭代器也可以被修改和被引用。
  • 双向迭代器既可以用来读又可以用来写,它与前向迭代器相类似。双向迭代器可以同时进行前向和后向元素操作。所有 STL 容器都提供了双向迭代器功能,这既有利于数据的写入和读出,又有利于提供更加灵活的数据操作。
  • 有的容器甚至提供了随机接入迭代器。随机接人迭代器可以通过跳跃的方式访问容器中的任意数据,使数据的访问非常灵活。随机访问迭代器具有双向迭代器的所有功能,是功能最强大的迭代器类型。

迭代器的诞生使算法和容器分离成为可能。算法是模板,其类型依赖于迭代器,不会局限于单一容器。不同的 STL 算法需要不同类型的迭代器来实现相应的功能。因为不同类型的 STL 容器支持不同类型的迭代器,所以不能对所有容器使用相同的算法。

仿函数
STL 包含了大量仿函数。仿函数可以理解为函数的一般形式。对于编程来说,仿函数非常重要,并有几种约束。在 C++ 标准中,函数调用一般使用指针,当需要调用函数时,只需要提供函数的地址即可。例如:

#include 
using namespace std;
//计数出这个数组中大于 10 的数字的数量
int RecallFunc(int *start, int *end, bool (*pf)(int))
{
     
    int count=0;
    for(int *i=start;i!=end+1;i++)
    {
     
    	count = pf(*i) ? count+1 : count;
    }
    return count;
}

bool IsGreaterThanTen(int num)
{
     
	return num>10 ? true : false;
}

int main()
{
     	int a[5] = {
     10,100,11,5,19};
    int result = RecallFunc(a,a+4,IsGreaterThanTen);
    cout<<result<<endl;
    return 0;
}

此方法的最大缺陷是效率低,不具备一般性。为提高效率,STL 定义了仿函数这种概念。

仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

如果编程者要将某种“操作”当做算法的参数,一般有两种方法:
(1)一个办法就是先将该“操作”设计为一个函数,再将函数指针当做算法的一个参数。上面的实例就是该做法;
(2)将该“操作”设计为一个仿函数(就语言层面而言是个 class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。

很明显第二种方法会更优秀,因为第一种方法扩展性较差,当函数参数有所变化,则无法兼容旧的代码,具体在第一小节已经阐述。正如上面的例子,在我们写代码时有时会发现有些功能代码,会不断地被使用。为了复用这些代码,实现为一个公共的函数是一个解决方法。不过函数用到的一些变量,可能是公共的全局变量。引入全局变量,容易出现同名冲突,不方便维护。

这时就可以使用仿函数了,写一个简单类,除了维护类的基本成员函数外,只需要重载 operator() 运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。而且相对于函数更优秀的性质,仿函数还可以进行依赖、组合与继承等,这样有利于资源的管理。如果再配合模板技术和 Policy 编程思想,则更加威力无穷,大家可以慢慢体会。Policy 表述了泛型函数和泛型类的一些可配置行为(通常都具有被经常使用的缺省值)。

STL 中也大量涉及到仿函数,有时仿函数的使用是为了函数拥有类的性质,以达到安全传递函数指针、依据函数生成对象、甚至是让函数之间有继承关系、对函数进行运算和操作的效果。比如 STL 中的容器 set 就使用了仿函数 less ,而 less 继承的 binary_function,就可以看作是对于一类函数的总体声明,这是函数做不到的。

//less的定义
template<typename _Tp> struct less : public binary_function<_Tp, _Tp, bool>
{
     
      bool operator()(const _Tp& __x, const _Tp& __y) const
      {
      return __x < __y; }
};
 
//set的申明
template<typename _Key, typename _Compare = std::less<_Key>,typename _Alloc = std::allocator<_Key>> class set;

仿函数中的变量可以是 static 的,同时仿函数还给出了 static 的替代方案,仿函数内的静态变量可以改成类的私有成员,这样可以明确地在析构函数中清除所用的内容,如果用到了指针,那么这个是不错的选择。有人说这样的类已经不是仿函数了,但其实,封装后从外界观察,可以明显地发现,它依然有函数的性质。
下面是一个仿函数的例子:

class StringAppend
{
     
public:
    explicit StringAppend(const string& str) : ss(str){
     }
    void operator() (const string& str) const
    {
     
         cout<<str<<' '<<ss<<endl;
    }
private:
    const string ss;
};

int main()
{
     
    StringAppend myFunctor2("and world!");
    myFunctor2("Hello");
}

编译运行输出:

Hello and world!

这个例子应该可以让您体会到仿函数的一些作用:它既能像普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。于是仿函数提供了第四种解决方案:成员变量。成员函数可以很自然地访问成员变量。

内存配置器和配接器
STL 包括底层的内存分配和释放。配接器可以实现不同类之间的数据转换。最常用的配接器有 istream_temtor,它提供了函数复制的接口。配接器对于 STL 技术来说非常重要。
STL 提供了 3 种容器配接器,分别是:

stack ;
queue ;
deque ;
#include 
#include 
using namespace std;
int main ()
{
     
    stack <int> st; //定义堆栈对象
    for (int i = 0;i <10;i ++ )
        st.push (i); //将数据压入堆钱
    while (!st.empty())
    {
     
        cout << st.top() << " "; //弹出堆找的第一个元素,并输出
        st.pop(); //弹出堆栈元素
    }
    cout<< endl;
    cin.get();    //任意键退出
    return 0;
}

程序执行结果为:9 8 7 6 5 4 3 2 1 0

4. STL使用方法

STL 作为 C++ 通用库,主要由迭代器、算法、容器、仿函数、内存配置器和配接器等六大部分组成。程序员使用 STL 容器能够实现多种标准类型且操作便捷的容器。

对于编程人员,标准化组件意味着直接使用现成的组件,不用重复开发。使用 STL 最重要的是掌握基本理论和编程方法,了解 STL 编程技术,必须深刻掌握 STL 容器技术和 STL 迭代器技术。

STL 提供了一组表示容器、迭代器、仿函数和算法的模板:

  • 容器是类似数组的单元,可存储 若干个值,且STL容器是同质的,即存储的值类型相同;
  • 算法是完成特定任务的处方;
  • 迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针;
  • 仿函数是类似于函数的对象,可以是类对象或函数指针。

STL 使程序员能够构造各种容器和执行各种操作。

下面以矢量为例,简要讲述矢量模板的使用。
在数学计算和 STL 模板中,vector 对应数组,提供与 valarray 和 ArrayTP 类似的操作。而 STL 为使 vector 矢量具备通用性,在头文件 中定义了 vector 模板。具体方法为:创建 vector 模板对象,使用通常的 表示法指出要使用的类型;然后使用初始化参数决定矢量的大小,并定义矢量动态内存。例如:

#include 
using namespace std;    //使用命名空间 std
vector <int> ratings (5);    //定义矢量对象 int n;
cin >> n;    //输入矢量大小
vector <double> scores (n);    //定义矢量动态内存

内存分配器是用来管理对象内存的。在 STL 容器模板中,一般都有一个可选的模板参数。例如:

template <class T, class Allocator = allocator <T>>//矢量模板
class vector {
     
    ...
}

若省略该模板参数的值,则容器模板将默认使用 allocator 类。类 allocator 以标准形式使用 new 和 delete 内存管理方式。
下面举例说明,创建两个 vector 对象:一个是 int 规范;另一个是 string 规范:

#include 
#include 
#include 
using namespace std;
const int NUM = 5;
int main ()
{
     
    vector <string>names(NUM);    //定义矢量对象
    vector <int> sexs (NUM);    //同上
    cout<<"Please Do Exactly As Told You Will enter \n"<<NUM<<" Personal Name and Their Sex.\n";
    int i =0;
    for (i = 0;i <NUM;i++)    //输入信息
    {
     
        cout << "Enter title # " << i +1 << ": ";
        getline (cin, names[i]) ; //获取输入信息
        cout << "Enter sex (0/1) #";
        cin >> sexs [i];    //获取输入信息
        cin.get ();    //等待
    }
    cout << "Thank you. You entered the following:    \n"<< "name/sex" << endl;
    for (i = 0; i <NUM; i++ )    //输出信息
    {
     
        cout <<names[i] << "\t" << sexs[i] << endl;
    }
    return 0;
}

程序执行结果为:

Please Do Exactly As Told You Will enter
5 Personal Name and Their Sex.
Enter title # 1: A
Enter sex (0/1) #1
Enter title # 2: B
Enter sex (0/1) #1
Enter title # 3: D
Enter sex (0/1) #0
Enter title # 4: E
Enter sex (0/1) #1
Enter title # 5: E
Enter sex (0/1) #1
Thank you. You entered the following:
name/sex
A       1
B       1
D       0
E       1
E       1

参考

  • http://c.biancheng.net/view/1436.html
  • https://blog.csdn.net/u010710458/article/details/79734558
  • https://blog.csdn.net/K346K346/article/details/82818801
  • https://choubin.site/2020/02/13/STL4-IteratorAndInitTool/

你可能感兴趣的:(STL快速入门(一))