目录
- [一. STL](#一. STL)
-
- STL概论
- [1.1 STL基本概念](#1.1 STL基本概念)
- [1.2 STL六大组件简介](#1.2 STL六大组件简介)
-
- 1容器container:
- [2 算法algorithm:](#2 算法algorithm:)
- [3 迭代器iterator:](#3 迭代器iterator:)
- 4仿函数:
- 5适配器:
- [6 空间配置器:](#6 空间配置器:)
- [1.3 STL优点](#1.3 STL优点)
- [二. STL三大组件](#二. STL三大组件)
-
- [2.1 容器](#2.1 容器)
- [2.2 算法](#2.2 算法)
- [2.3 迭代器](#2.3 迭代器)
- [三. 常用容器](#三. 常用容器)
-
- [3.1 string容器](#3.1 string容器)
-
- [3.1.1 string容器基本概念](#3.1.1 string容器基本概念)
- [3.1.2 string容器常用操作](#3.1.2 string容器常用操作)
-
- [3.1.2.1 string 构造函数](#3.1.2.1 string 构造函数)
- [3.1.2.2 string基本赋值操作](#3.1.2.2 string基本赋值操作)
- [3.1.2.3 string存取字符操作](#3.1.2.3 string存取字符操作)
- [3.1.2.4 string拼接操作](#3.1.2.4 string拼接操作)
- [3.1.2.5 string查找和替换](#3.1.2.5 string查找和替换)
- [3.1.2.6 string比较操作](#3.1.2.6 string比较操作)
- [3.1.2.7 string子串](#3.1.2.7 string子串)
- [3.1.2.8 string插入和删除操作](#3.1.2.8 string插入和删除操作)
- [3.1.2.9 string和c-style字符串转换](#3.1.2.9 string和c-style字符串转换)
- [3.2 vector容器](#3.2 vector容器)
-
- [3.2.1 vector容器基本概念](#3.2.1 vector容器基本概念)
- [3.2.2 vector迭代器](#3.2.2 vector迭代器)
- [3.2.3 vector的数据结构](#3.2.3 vector的数据结构)
- [3.2.4 vector常用API操作](#3.2.4 vector常用API操作)
-
- [3.2.4.1 vector构造函数](#3.2.4.1 vector构造函数)
- [3.2.4.2 vector常用赋值操作](#3.2.4.2 vector常用赋值操作)
- [3.2.4.3 vector大小操作](#3.2.4.3 vector大小操作)
- [3.2.4.4 vector数据存取操作](#3.2.4.4 vector数据存取操作)
- [3.2.4.5 vector插入和删除操作](#3.2.4.5 vector插入和删除操作)
- [3.2.5 vector小案例](#3.2.5 vector小案例)
-
- 3.2.5.1巧用swap,收缩内存空间
- [3.2.5.2 reserve预留空间](#3.2.5.2 reserve预留空间)
- [3.9 STL容器元素深/浅拷贝问题](#3.9 STL容器元素深/浅拷贝问题)
- [3.10 STL容器使用时机](#3.10 STL容器使用时机)
- 四.常用算法
- 五.空间配置器
一. STL
STL概论
长久以来,软件界一直希望建立一种可重复利用的东西,以及一种得以制造出"可重复运用的东西"的方法,让程序员的心血不止于随时间的迁移,人事异动而烟消云散,从函数(functions),类别(classes),函数库(function libraries),类别库(class libraries)、各种组件,从模块化设计,到面向对象(object oriented ),到模式(pattern)的归纳整理,为的就是复用性的提升。
复用性必须建立在某种标准之上。但是在许多环境下,就连软件开发最基本的数据结构(data structures) 和算法(algorithm)都未能有一套标准。大量程序员被迫从事大量重复的工作,竟然是为了完成前人已经完成而自己手上并未拥有的程序代码,这不仅是人力资源的浪费,也是挫折与痛苦的来源。
为了建立数据结构和算法的一套标准,并且降低他们之间的耦合关系,以提升各自的独立性、弹性、交互操作性(相互合作性,interoperability),诞生了STL。
1.1 STL基本概念
STL(Standard Template Library,标准模板库),是惠普实验室开发的一系列软件的统称。现在主要出现在 c++中,但是在引入 c++之前该技术已经存在很长时间了。
STL 从广义上分为: 容器 (container) 算法 (algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL 几乎所有的代码都采用了模板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。STL(Standard Template Library)标准模板库,在我们 c++标准程序库中隶属于 STL 的占到了 80%以上。
1.2 STL六大组件简介
STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器、空间配置器。
1容器container:
各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。
2 算法algorithm:
各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte.
3 迭代器iterator:
扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator--等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
4仿函数:
行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template
5适配器:
一种用来修饰容器或者仿函数或迭代器接口的东西。
6 空间配置器:
负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.
STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。
1.3 STL优点
- 1.STL 是 C++的一部分,因此不用额外安装什么,它被内建在你的编译器之内。
- 2.STL 的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但是这种分离使得 STL 变得非常通用。例如:在 STL 的 vector 容器中,可以放入元素、基础数据类型变量、元素的地址;STL 的 sort() 排序函数可以用来操作 vector,list 等容器。
- 3.程序员可以不用思考 STL 具体的实现过程,只要能够熟练使用 STL 就 OK 了。这样他们就可以把精力放在程序开发的别的方面。
- 4.STL 具有高可重用性,高性能,高移植性,跨平台的优点。
高可重用性 :STL 中几乎所有的代码都采用了模板类和模版函数的方式实现,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。
高性能 :如 map 可以高效地从十万条记录里面查找出指定的记录,因为 map 是采用红黑树的变体实现的。(红黑树是平横二叉树的一种)
高移植性:如在项目 A 上用 STL 编写的模块,可以直接移植到项目 B 上。
二. STL三大组件
2.1 容器
容器,置物之所也。
研究数据的特定排列方式,以利于搜索或排序或其他特殊目的,这一门学科我们成为数据结构。大学信息类相关专业里面,与编程最有直接关系的学科,首推数据结构与算法。几乎可以说,任何特定的数据结构都是为了实现某种特定的算法。STL容器就是将运用最广泛的一些数据结构实现出来。
常用的数据结构不在乎,数组(array),链表(list),tree(树),栈(stack),队列(queue),集合(set),映射表(map),根据数据在容器中的排列特性,这些数据分为序列式容器 和关联式容器两种。
- 序列式容器就是容器元素在容器中的位置是由元素进入容器的时间和地点来决定。
- Vector容器、
- Deque容器、
- List容器、
- Stack容器、
- Queue容器。
- 关联式容器是指容器已经有了一定的规则,容器元素在容器中的位置由我的规则来决定。
- Set/multiset容器
- Map/multimap容器
2.2 算法
算法,问题之解法也。
以有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms).
广义而言,我们所编写的每个程序都是一个算法,其中的每个函数也都是一个算法,毕竟它们都是用来解决或大或小的逻辑问题或数学问题。STL收录的算法经过了数学上的效能分析与证明,是极具复用价值的,包括常用的排序,查找等等。特定的算法往往搭配特定的数据结构,数据结构是问题的载体,算法与数据结构相辅相成。
算法分为:质变算法 和非质变算法 。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
再好的编编程技巧,也无法让一个笨拙的算法起死回生。
2.3 迭代器
迭代器(iterator)是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物。在<<书Design Patterns>>一书中提供了23中设计模式的完整描述,其中iterator模式定义如下:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
迭代器的设计思维-STL的关键所在,STL的中心思想在于将数据容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。从技术角度来看,容器和算法的泛型化并不困难,c++的class template和function template可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题。
迭代器的种类:
输入迭代器 | 提供对数据的只读访问-- |
---|---|
输出迭代器 | 提供对数据的只写访问 |
前向迭代器 | 提供读写操作,并能向前推进迭代器 |
双向迭代器 | 提供读写操作,并能向前和向后操作 |
随机访问迭代器 | 提供读写操作,并能在数据中随机移动 |
输入迭代器 提供对数据的只读访问 只读,支持++、、!=
输出迭代器 提供对数据的只写访问 只写,支持++
前向迭代器 提供读写操作,并能向前推进迭代器 读写,支持++、、!=
双向迭代器 提供读写操作,并能向前和向后操作 读写,支持++、--,
随机访问迭代器 提供读写操作,并能在数据中随机移动 读写,支持++、--、[n]、-n、<、<=、>、>=
2.3 案例
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
usingnamespace std;
//STL 中的容器 算法 迭代器
void test01(){
vector<int> v;//STL 中的标准容器之一 :动态数组
v.push_back(1);//vector 容器提供的插入数据的方法
v.push_back(5);
v.push_back(3);
v.push_back(7);
//迭代器
vector<int>::iterator pStart = v.begin();//vector 容器提供了 begin()方法 返回指向第一个元素的迭代器
vector<int>::iterator pEnd = v.end();//vector 容器提供了 end()方法 返回指向最后一个元素下一个位置的迭代器
//通过迭代器遍历
while(pStart != pEnd){
cout <<*pStart <<" ";
pStart++;
}
cout << endl;
//算法 count 算法 用于统计元素的个数
int n = count(pStart, pEnd,5);
cout <<"n:"<< n << endl;
}
//STL 容器不单单可以存储基础数据类型,也可以存储类对象
class Teacher
{
public:
Teacher(int age):age(age){};
~Teacher(){};
public:
int age;
};
void test02(){
vector<Teacher> v;//存储 Teacher 类型数据的容器
Teacher t1(10), t2(20), t3(30);
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
vector<Teacher>::iterator pStart = v.begin();
vector<Teacher>::iterator pEnd = v.end();
//通过迭代器遍历
while(pStart != pEnd){
cout << pStart->age <<" ";
pStart++;
}
cout << endl;
}
//存储 Teacher 类型指针
void test03(){
vector<Teacher*> v;//存储 Teacher 类型指针
Teacher* t1 =new Teacher(10);
Teacher* t2 =new Teacher(20);
Teacher* t3 =new Teacher(30);
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
//拿到容器迭代器
vector<Teacher*>::iterator pStart = v.begin();
vector<Teacher*>::iterator pEnd = v.end();
//通过迭代器遍历
while(pStart != pEnd){
cout <<(*pStart)->age <<" ";
pStart++;
}
cout << endl;
}
//容器嵌套容器 难点(不理解,可以跳过)
void test04(){
vector<vector<int>> v;//容器中存储容器
vector<int> v1, v2, v3;
v1.push_back(1);
v1.push_back(2);
v2.push_back(10);
v3.push_back(100);
v3.push_back(200);
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
//拿到容器迭代器
vector<vector<int>>::iterator pStart = v.begin();
vector<vector<int>>::iterator pEnd = v.end();
//通过迭代器遍历
while(pStart != pEnd){
vector<int> vTemp =*pStart;//获得迭代器当前指向的容器
vector<int>::iterator tmpStart = vTemp.begin();
vector<int>::iterator tmpEnd = vTemp.end();
for(; tmpStart != tmpEnd; tmpStart++){
cout <<*tmpStart <<" ";
}
cout << endl;
pStart++;
}
}
int main(){
//test01();
//test02();
//test03();
test04();
system("pause");
return EXIT_SUCCESS;
}
三. 常用容器
3.1 string容器
3.1.1 string容器基本概念
C风格字符串 (以空字符结尾的字符数组 )太过复杂难于掌握,不适合大程序的开发,所以C++标准库定义了一种string类,定义在头文件。
String和c风格字符串对比:
- 1.Char是一个指针,String是一个类
string封装了char,管理这个字符串,是一个char*型的容器。 - 2.String封装了很多实用的成员方法
查找find,拷贝copy,删除delete 替换replace,插入insert - 3不用考虑内存释放和越界
string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。
3.1.2 string容器常用操作
3.1.2.1 string 构造函数
string();//创建一个空的字符串 例如: string str;
string(conststring& str);//使用一个string对象初始化另一个string对象
string(constchar* s);//使用字符串s初始化
string(int n, char c);//使用n个字符c初始化
3.1.2.2 string基本赋值操作
string&operator=(constchar* s);//char*类型字符串 赋值给当前的字符串
string&operator=(conststring&s);//把字符串s赋给当前的字符串
string&operator=(char c);//字符赋值给当前的字符串
string& assign(constchar *s);//把字符串s赋给当前的字符串
string& assign(constchar *s, int n);//把字符串s的前n个字符赋给当前的字符串
string& assign(conststring&s);//把字符串s赋给当前字符串
string& assign(int n, char c);//用n个字符c赋给当前字符串
string& assign(conststring&s, int start, int n);//将s从start开始n个字符赋值给字符串,如s=hello,那么n=3,start=1,那么是hel中从e开始赋值3-1个字符
3.1.2.3 string存取字符操作
char&operator[](int n);//通过[]方式取字符
char& at(int n);//通过at方法获取字符
3.1.2.4 string拼接操作
string&operator+=(conststring& str);//重载+=操作符
string&operator+=(constchar* str);//重载+=操作符
string&operator+=(constchar c);//重载+=操作符
string& append(constchar *s);//把字符串s连接到当前字符串结尾
string& append(constchar *s, int n);//把字符串s的前n个字符连接到当前字符串结尾
string& append(conststring&s);//同operator+=()
string& append(conststring&s, int pos, int n);//把字符串s中从pos开始的n个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加n个字符c
3.1.2.5 string查找和替换
int find(conststring& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
int find(constchar* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
int find(constchar* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
int find(constchar c, int pos = 0) const; //查找字符c第一次出现位置
int rfind(conststring& str, int pos = npos) const;//查找str最后一次位置,从pos开始查找
int rfind(constchar* s, int pos = npos) const;//查找s最后一次出现位置,从pos开始查找
int rfind(constchar* s, int pos, int n) const;//从pos查找s的前n个字符最后一次位置
int rfind(constchar c, int pos = 0) const; //查找字符c最后一次出现位置
string& replace(int pos, int n, conststring& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n, constchar* s); //替换从pos开始的n个字符为字符串s
3.1.2.6 string比较操作
/*
compare函数在>时返回 1,<时返回 -1,==时返回 0。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的A比小写的a小。
*/
int compare(conststring&s) const;//与字符串s比较
int compare(constchar *s) const;//与字符串s比较
3.1.2.7 string子串
string substr(int pos = 0, int n = npos) const;//返回由pos开始的n个字符组成的字符串
3.1.2.8 string插入和删除操作
string& insert(int pos, constchar* s); //插入字符串
string& insert(int pos, conststring& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入n个字符c
string& erase(int pos, int n = npos);//删除从Pos开始的n个字符
3.1.2.9 string和c-style字符串转换
//string 转 char*
string str = "itcast";
constchar* cstr = str.c_str();
//char* 转 string
char* s = "itcast";
string sstr(s);
提示:
在c++中存在一个从const char到string的隐式类型转换,却不存在从一个string对象到C_string的自动类型转换。对于string类型的字符串,可以通过c_str()函数返回string对象对应的C_string.
通常,程序员在整个程序中应坚持使用string类对象,直到必须将内容转化为char时才将其转换为C_string.
提示:
为了修改string字符串的内容,下标操作符[]和at都会返回字符的引用。但当字符串的内存被重新分配之后,可能发生错误.
string s ="abcdefg";
char& a = s[2];
char& b = s[3];
a ='1';
b ='2';
cout << s << endl;
cout <<(int*)s.c_str()<< endl;
s ="pppppppppppppppppppppppp";
//a = '1';
//b = '2';
cout << s << endl;
cout <<(int*)s.c_str()<< endl;
3.2 vector容器
3.2.1 vector容器基本概念
vector的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用的灵活性。
- Array是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。
- Vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的array了。
Vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦vector旧空间满了,如果客户每新增一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间(不论多大),一如刚所说,是"配置新空间-数据移动-释放旧空间"的大工程,时间成本很高,应该加入某种未雨绸缪的考虑,稍后我们便可以看到vector的空间配置策略。
3.2.2 vector迭代器
Vector维护一个线性空间,所以不论元素的型别如何,普通指针都可以作为vector的迭代器,因为vector迭代器所需要的操作行为,
如operaroe*, operator->, operator++, operator--, operator+, operator-, operator+=, operator-=,
普通指针天生具备。Vector支持随机存取 ,而普通指针正有着这样的能力。所以vector提供的是随机访问迭代器 (Random Access Iterators).
根据上述描述,如果我们写如下的代码:
Vector<int>::iterator it1;
Vector<Teacher>::iterator it2;
It1的型别其实就是Int*,it2的型别其实就是Teacher*.
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
usingnamespace std;
int main(){
vector<int> v;
for(int i =0; i <10;i ++){
v.push_back(i);
cout << v.capacity()<< endl;
}
int* start =&v[0];
int* end =&v[v.size()-1];
for(; start <= end; start++){
cout <<*start << endl;
}
system("pause");
return EXIT_SUCCESS;
}
3.2.3 vector的数据结构
Vector所采用的数据结构非常简单,线性连续空间 ,它以两个迭代器**_Myfirst和_Mylast**分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器_Myend指向整块连续内存空间的尾端。
为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这边是容量的概念。换句话说,一个vector的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下次再有新增元素,整个vector容器就得另觅居所。
注意 :
所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了。这是程序员容易犯的一个错误,务必小心。
3.2.4 vector常用API操作
3.2.4.1 vector构造函数
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将n个elem拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
//例子 使用第二个构造函数 我们可以...
int arr[] = {2,3,4,1,9};
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
3.2.4.2 vector常用赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
vector&operator=(const vector &vec);//重载等号操作符
swap(vec);// 将vec与本身的元素互换。
3.2.4.3 vector大小操作
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。
3.2.4.4 vector数据存取操作
at(int idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
operator[];//返回索引idx所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素
3.2.4.5 vector插入和删除操作
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素
3.2.5 vector小案例
3.2.5.1巧用swap,收缩内存空间
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
usingnamespace std;
int main(){
vector<int> v;
for(int i =0; i <100000;i ++){
v.push_back(i);
}
cout <<"capacity:"<< v.capacity()<< endl;
cout <<"size:"<< v.size()<< endl;
//此时 通过resize改变容器大小
v.resize(10);
cout <<"capacity:"<< v.capacity()<< endl;
cout <<"size:"<< v.size()<< endl;
//容量没有改变
vector<int>(v).swap(v);
cout <<"capacity:"<< v.capacity()<< endl;
cout <<"size:"<< v.size()<< endl;
system("pause");
return EXIT_SUCCESS;
}
3.2.5.2 reserve预留空间
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
usingnamespace std;
int main(){
vector<int> v;
//预先开辟空间
v.reserve(100000);
int* pStart =NULL;
int count =0;
for(int i =0; i <100000;i ++){
v.push_back(i);
if(pStart !=&v[0]){
pStart =&v[0];
count++;
}
}
cout <<"count:"<< count << endl;
system("pause");
return EXIT_SUCCESS;
}
3.9 STL容器元素深/浅拷贝问题
STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素直接放进容器中,也就是说我们提供的元素必须能够被拷贝。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
usingnamespace std;
class myclass{
public:
myclass(char* data){
int len = strlen(data)+1;//计算传进来的字符串长度
this->data =newchar[len];//在堆区分配了len字节内存
strcpy(this->data, data);//将数据拷贝到我们在堆分配的内存中
}
//增加拷贝构造函数
myclass(const myclass& mc){
int len = strlen(mc.data)+1;
this->data =newchar[len];
strcpy(this->data, mc.data);
}
//重载operator=操作符
myclass&operator=(const myclass& mc){
if (this->data != NULL){
delete[] this->data;
this->data = NULL;
}
int len = strlen(mc.data)+1;
this->data =newchar[len];
strcpy(this->data, mc.data);
return*this;
}
//既然我们在堆区分配了内存,需要在析构函数中释放内存
~myclass(){
if(NULL!=this->data){
delete[]this->data;
this->data =NULL;
}
}
private:
char* data;
};
void test_deep_copy(){
char* data ="abcd";
myclass mc(data);//创建myclass的实例 并用char*字符串data初始化对象
vector<myclass> v;//创建vector容器
v.push_back(mc);//将mc实例插入到vector容器尾部
}
int main(){
test_deep_copy();//调用测试函数
system("pause");
return0;
}
3.10 STL容器使用时机
- vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
- deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
vector与deque的比较:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置 却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。 - list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
- set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
- map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。
四.常用算法
4.1 函数对象
重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载"()"操作符,使得类对象可以像函数那样调用。
注意:
1.函数对象(仿函数)是一个类,不是一个函数。
2.函数对象(仿函数)重载了"() "操作符使得它可以像函数一样调用。
分类:假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为"一元仿函数"(unary functor);相反,如果重载的operator()要求获取两个参数,就将这个类称为"二元仿函数"(binary functor)。
函数对象的作用主要是什么?STL提供的算法往往都有两个版本,其中一个版本表现出最常用的某种运算,另一版本则允许用户通过template参数的形式来指定所要采取的策略。
//函数对象是重载了函数调用符号的类
struct MyPrint{
voidoperator()(int val){
cout << val << endl;
}
};
void test01(){
//如何使用
MyPrint print01;
print01(10);//重载了()操作符的类实例化的对象,可以像普通函数那样调用,可以有参数 ,可以有返回值
}
//函数对象超出了普通函数的概念,可以保存函数的调用状态
struct HePrint{
HePrint(){
mCount =0;
}
voidoperator()(int val){
cout << val << endl;
mCount++;
}
int mCount;
};
void test02(){
HePrint print;
print(10);
print(20);
print(30);
print(40);
cout << print.mCount << endl;
}
//函数对象可以做参数和返回值
struct OurPrint{
voidoperator()(int val){
cout << val << endl;
}
};
void doBusiness(OurPrint print){
print(20);
}
void test03(){
//函数对象做参数
doBusiness(OurPrint());
}
4.2 谓词
谓词是指普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可作为一个判断式。
struct GreaterThanFive{
booloperator()(int v){
return v >5;
}
};
//一元谓词
void test01(){
vector<int> v;
for(int i =0; i <10;i++){
v.push_back(i);
}
vector<int>::iterator ret = find_if(v.begin(), v.end(), GreaterThanFive());
if(ret == v.end()){
cout <<"没有找到!"<< endl;
}
else{
cout <<"找到:"<<*ret << endl;
}
}
//二元谓词
struct MyCompare{
booloperator()(int v1,int v2){
return v1 > v2;
}
};
void test02(){
vector<int> v;
srand((unsignedint)time(NULL));
for(int i =0; i <10; i++){
v.push_back(rand()%100);
}
for(vector<int>::iterator it = v.begin(); it != v.end(); it ++){
cout <<*it <<" ";
}
cout << endl;
//排序算法
sort(v.begin(), v.end(), MyCompare());
for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout <<*it <<" ";
}
cout << endl;
}
4.3 内建函数对象
STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要引入头文件 #include。
6个算数类函数对象,除了negate是一元运算,其他都是二元运算。
templateT plus//加法仿函数
template T minus//减法仿函数
template T multiplies//乘法仿函数
template T divides//除法仿函数
template T modulus//取模仿函数
template T negate//取反仿函数
6个关系运算类函数对象,每一种都是二元运算。
templatebool equal_to//等于
templatebool not_equal_to//不等于
templatebool greater//大于
templatebool greater_equal//大于等于
templatebool less//小于
templatebool less_equal//小于等于
逻辑运算类运算函数,not为一元运算,其余为二元运算。
templatebool logical_and//逻辑与
templatebool logical_or//逻辑或
templatebool logical_not//逻辑非
内建函数对象举例:
/*
template bool equal_to//等于
template bool not_equal_to//不等于
template bool greater//大于
template bool greater_equal//大于等于
template bool less//小于
template bool less_equal//小于等于
*/
void test01(){
equal_to<int> MyEqual;
plus<int> MyPlus;
if(MyEqual(10,20)){
cout <<"相等!"<< endl;
}
else{
cout <<"不相等!"<< endl;
}
cout <<"MyPlus:"<< MyPlus(10,20)<< endl;
}
void test02(){
vector<int> v;
srand((unsignedint)time(NULL));
for(int i =0; i <10; i++){
v.push_back(rand()%100);
}
for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout <<*it <<" ";
}
cout << endl;
sort(v.begin(),v.end(),greater<int>());
for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout <<*it <<" ";
}
cout << endl;
}
3.1.4 函数对象适配器
//函数适配器bind1st bind2nd
//现在我有这个需求 在遍历容器的时候,我希望将容器中的值全部加上100之后显示出来,怎么做哇?
struct myprint :public binary_function<int,int,void>{//二元函数对象 所以需要继承 binary_fucntion<参数类型,参数类型,返回值类型>
voidoperator()(int v1,int v2)const{
cout << v1 + v2 <<" ";
}
};
void test02(){
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//我们直接给函数对象绑定参数 编译阶段就会报错
//for_each(v.begin(), v.end(), bind2nd(myprint(),100));
//如果我们想使用绑定适配器,需要我们自己的函数对象继承binary_function 或者 unary_function
//根据我们函数对象是一元函数对象 还是二元函数对象
for_each(v.begin(), v.end(), bind2nd(myprint(),100));
cout << endl;
//总结: bind1st和bind2nd区别?
//bind1st : 将参数绑定为函数对象的第一个参数
//bind2nd : 将参数绑定为函数对象的第二个参数
//bind1st ,bind2nd将二元函数对象转为一元函数对象
}
//函数对象适配器 not1 not2
struct myprint02 {
voidoperator()(int v1)const{
cout << v1 <<" ";
}
};
void test03(){
vector<int> v;
v.push_back(2);
v.push_back(1);
v.push_back(5);
v.push_back(4);
vector<int>::iterator it = find_if(v.begin(), v.end(), not1(bind2nd(less_equal<int>(),2)));
cout <<"it:"<<*it << endl;
sort(v.begin(), v.end(), not2(greater<int>()));
for_each(v.begin(), v.end(), myprint02());
cout << endl;
//not1 对一元函数对象取反
//not2 对二元函数对象取反
}
//如何给一个普通函数使用绑定适配器(bind1st bind2nd)绑定一个参数?(拓展)
//ptr_fun
void myprint04(int v1,int v2){
cout << v1 + v2 <<" ";
}
void test04(){
vector<int> v;
v.push_back(2);
v.push_back(1);
v.push_back(5);
v.push_back(4);
//1 将普通函数适配成函数对象
//2 然后通过绑定器绑定参数
for_each(v.begin(), v.end(), bind2nd(ptr_fun(myprint04),100));
cout << endl;
//总结: ptr_fun 将普通函数转变为函数对象
}
//mem_fun mem_fun_ref
//如果我们容器中存储的是对象或者对象指针,如果能指定某个成员函数处理成员数据。
class student{
public:
student(string name,int age):name(name), age(age){}
void print(){
cout <<"name:"<< name <<" age:"<< age << endl;;
}
void print2(int a){
cout <<"name:"<< name <<" age:"<< age <<" a:"<< a << endl;
}
int age;
string name;
};
void test05(){
//mem_fun : 如果存储的是对象指针,需要使用mem_fun
vector<student*> v;
student* s1 =new student("zhaosi",10);
student* s2 =new student("liuneng",20);
student* s3 =new student("shenyang",30);
student* s4 =new student("xiaobao",40);
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
v.push_back(s4);
for_each(v.begin(), v.end(), mem_fun(&student::print));
cout <<"-----------------------------"<< endl;
//mem_fun_ref : 如果存储的是对象,需要使用mem_fun_ref
vector<student> v2;
v2.push_back(student("zhaosi",50));
v2.push_back(student("liuneng",60));
v2.push_back(student("shenyang",70));
v2.push_back(student("xiaobao",80));
for_each(v2.begin(), v2.end(), mem_fun_ref(&student::print));
}
五.空间配置器
4.1.1容器通过空间配置器取得数据存储空间,空间配置器管理容器的空间
4.1.2 STL空间配置器产生的缘由:
在软件开发,程序设计中,我们不免因为程序需求,使用很多的小块内存(基本类型以及小内存的自定义类型)。在程序中动态申请,释放。
这个过程过程并不是一定能够控制好的,于是乎,
问题1:就出现了内存碎片问题。
问题2:一直在因为小块内存而进行内存申请,调用malloc,系统调用产生性能问题。
策略:如果申请的内存大小超过128,那么空间配置器就自动调用一级空间配置器。反之调用二级空间配置器。
一级空间配置器,STL源码中的一级空间配置器命名为class __malloc_alloc_template ,它很简单,就是对malloc,free,realloc等系统分配函数的一层封装。
二级空间配置器,由一个内存池和自由链表配合实现的。
4.2 算法概述
算法主要是由头文件组成。
是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等...
体积很小,只包括在几个序列容器上进行的简单运算的模板函数.
定义了一些模板类,用以声明函数对象。
4.3 常用遍历算法
/*
遍历算法 遍历容器元素
@param beg 开始迭代器
@param end 结束迭代器
@param _callback 函数回调或者函数对象
@return 函数对象
/
for_each(iterator beg, iterator end, _callback);
/
transform算法 将指定容器区间元素搬运到另一容器中
注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
@param beg1 源容器开始迭代器
@param end1 源容器结束迭代器
@param beg2 目标容器开始迭代器
@param _cakkback 回调函数或者函数对象
@return 返回目标容器迭代器
*/
transform(iterator beg1, iterator end1, iterator beg2, _callbakc)
for_each:
/*
template<class _InIt,class _Fn1> inline
void for_each(_InIt _First, _InIt _Last, _Fn1 _Func)
{
for (; _First != _Last; ++_First)
_Func(*_First);
}
*/
//普通函数
void print01(int val){
cout << val <<" ";
}
//函数对象
struct print001{
voidoperator()(int val){
cout << val <<" ";
}
};
//for_each算法基本用法
void test01(){
vector<int> v;
for(int i =0; i <10;i++){
v.push_back(i);
}
//遍历算法
for_each(v.begin(), v.end(), print01);
cout << endl;
for_each(v.begin(), v.end(), print001());
cout << endl;
}
struct print02{
print02(){
mCount =0;
}
voidoperator()(int val){
cout << val <<" ";
mCount++;
}
int mCount;
};
//for_each返回值
void test02(){
vector<int> v;
for(int i =0; i <10; i++){
v.push_back(i);
}
print02& p = for_each(v.begin(), v.end(), print02());
cout << endl;
cout << p.mCount << endl;
}
struct print03 :public binary_function<int,int,void>{
voidoperator()(int val,int bindParam)const{
cout << val + bindParam <<" ";
}
};
//for_each绑定参数输出
void test03(){
vector<int> v;
for(int i =0; i <10; i++){
v.push_back(i);
}
for_each(v.begin(), v.end(), bind2nd(print03(),100));
}
transform:
//transform 将一个容器中的值搬运到另一个容器中
/*
template<class _InIt, class _OutIt, class _Fn1> inline
_OutIt _Transform(_InIt _First, _InIt _Last,_OutIt _Dest, _Fn1 _Func)
{
for (; _First != _Last; ++_First, ++_Dest)
*_Dest = _Func(*_First);
return (_Dest);
}
template<class _InIt1,class _InIt2,class _OutIt,class _Fn2> inline
_OutIt _Transform(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _OutIt _Dest, _Fn2 _Func)
{
for (; _First1 != _Last1; ++_First1, ++_First2, ++_Dest)
*_Dest = _Func(*_First1, *_First2);
return (_Dest);
}
*/
struct transformTest01{
intoperator()(int val){
return val +100;
}
};
struct print01{
voidoperator()(int val){
cout << val <<" ";
}
};
void test01(){
vector<int> vSource;
for(int i =0; i <10;i ++){
vSource.push_back(i +1);
}
//目标容器
vector<int> vTarget;
//给vTarget开辟空间
vTarget.resize(vSource.size());
//将vSource中的元素搬运到vTarget
vector<int>::iterator it = transform(vSource.begin(), vSource.end(), vTarget.begin(), transformTest01());
//打印
for_each(vTarget.begin(), vTarget.end(), print01()); cout << endl;
}
//将容器1和容器2中的元素相加放入到第三个容器中
struct transformTest02{
intoperator()(int v1,int v2){
return v1 + v2;
}
};
void test02(){
vector<int> vSource1;
vector<int> vSource2;
for(int i =0; i <10; i++){
vSource1.push_back(i +1);
}
//目标容器
vector<int> vTarget;
//给vTarget开辟空间
vTarget.resize(vSource1.size());
transform(vSource1.begin(), vSource1.end(), vSource2.begin(),vTarget.begin(), transformTest02());
//打印
for_each(vTarget.begin(), vTarget.end(), print01()); cout << endl;
}
4.4 常用查找算法
/*
find算法 查找元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return 返回查找元素的位置
/
find(iterator beg, iterator end, value)
/
adjacent_find算法 查找相邻重复元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 回调函数或者谓词(返回bool类型的函数对象)
@return 返回相邻元素的第一个位置的迭代器
/
adjacent_find(iterator beg, iterator end, _callback);
/
binary_search算法 二分查找法
注意: 在无序序列中不可用
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return bool 查找返回true 否则false
/
bool binary_search(iterator beg, iterator end, value);
/
find_if算法 条件查找
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回bool类型的函数对象)
@return bool 查找返回true 否则false
/
find_if(iterator beg, iterator end, _callback);
/
count算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value回调函数或者谓词(返回bool类型的函数对象)
@return int返回元素个数
/
count(iterator beg, iterator end, value);
/
count算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回bool类型的函数对象)
@return int返回元素个数
*/
count_if(iterator beg, iterator end, _callback);
STL示例代码\18 find
STL示例代码\19 find_if
STL示例代码\20 adjacent_find
STL示例代码\21 binary_search
STL示例代码\22 count和count_if
4.5 常用排序算法
/*
merge算法 容器元素合并,并存储到另一容器中
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
/
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
/
sort算法 容器元素排序
注意:两个容器必须是有序的
@param beg 容器1开始迭代器
@param end 容器1结束迭代器
@param _callback 回调函数或者谓词(返回bool类型的函数对象)
/
sort(iterator beg, iterator end, _callback)
/
random_shuffle算法 对指定范围内的元素随机调整次序
@param beg 容器开始迭代器
@param end 容器结束迭代器
/
random_shuffle(iterator beg, iterator end)
/
reverse算法 反转指定范围的元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/
reverse(iterator beg, iterator end)
STL示例代码\23 sort
STL示例代码\24 merge
STL示例代码\26 reverse和random_shuttle
4.6 常用拷贝和替换算法
/*
copy算法 将容器内指定范围的元素拷贝到另一容器中
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param dest 目标容器结束迭代器
/
copy(iterator beg, iterator end, iterator dest)
/
replace算法 将容器内指定范围的旧元素修改为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param oldvalue 旧元素
@param oldvalue 新元素
/
replace(iterator beg, iterator end, oldvalue, newvalue)
/
replace_if算法 将容器内指定范围满足条件的元素替换为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback函数回调或者谓词(返回Bool类型的函数对象)
@param oldvalue 新元素
/
replace_if(iterator beg, iterator end, _callback, newvalue)
/
swap算法 互换两个容器的元素
@param c1容器1
@param c2容器2
*/
swap(container c1, container c2)
STL示例代码\27 copy
STL示例代码\28 replace和replace_if
STL示例代码\29 swap
4.7 常用算数生成算法
/*
accumulate算法 计算容器元素累计总和
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value累加值
/
accumulate(iterator beg, iterator end, value)
/
fill算法 向容器中添加元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value t填充元素
*/
fill(iterator beg, iterator end, value)
STL示例代码\30 accumulate和fill
4.8 常用集合算法
/*
set_intersection算法 求两个set集合的交集
注意:两个集合必须是有序序列
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
/
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
/
set_union算法 求两个set集合的并集
注意:两个集合必须是有序序列
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
/
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
/
set_difference算法 求两个set集合的差集
注意:两个集合必须是有序序列
@param beg1 容器1开始迭代器
@param end1 容器1结束迭代器
@param beg2 容器2开始迭代器
@param end2 容器2结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
STL示例代码\31 set_union和set_difference和set_intersection