程序设计与算法(三)C++面向对象程序设计笔记 第八周 标准模板库STL(一)

笔记按照中国大学MOOC上北京大学郭炜老师主讲的程序设计与算法(三)C++面向对象程序设计所作,B站上也有资源。原课程链接如下:

程序设计与算法(三)C++面向对象程序设计

其他各章节链接如下:

程序设计与算法(三)C++面向对象程序设计笔记 第一周 从C到C++

程序设计与算法(三)C++面向对象程序设计笔记 第二周 类和对象基础

程序设计与算法(三)C++面向对象程序设计笔记 第三周 类和对象提高

程序设计与算法(三)C++面向对象程序设计笔记 第四周 运算符重载

程序设计与算法(三)C++面向对象程序设计笔记 第五周 继承

程序设计与算法(三)C++面向对象程序设计笔记 第六周 多态

程序设计与算法(三)C++面向对象程序设计笔记 第七周 输入输出和模板

程序设计与算法(三)C++面向对象程序设计笔记 第八周 标准模板库STL(一)

程序设计与算法(三)C++面向对象程序设计笔记 第九周 标准模板库STL(二)

程序设计与算法(三)C++面向对象程序设计笔记 第十周 C++11新特性和C++高级主题

其他各科笔记汇总

标准模板库 STL(一)

String 类

string 类

string 类是模板类,是从 STL 模板 basic_string 实例化出来的,basic_string<char> 这个类的名字就是 string

typedef basic_string<char> string;

 

使用 string 类要包含头文件 <string>

 

 

string 类有多种构造函数,string 类对象的初始化:

string s1("Hello");
string month = "March";
string s2(8,'x');   

 

错误的初始化方法:

string error1 = 'c';  //错
string error2('u');   //错
string error3 = 22;   //错
string error4(8);     //错

 

可以将字符赋值给 string 对象

string s;
s = 'n';

 

 

string 类程序样例

#include <iostream>
#include <string>
using namespace std;
int main(int argc, char* argv[ ]){
    string s1("Hello");
    cout << s1 << endl;
    string s2(8,'x');
    cout << s2 << endl;
    string month = "March";
    cout << month << endl;
    string s;
    s = 'n';
    cout << s << endl;
    return 0;
}

输出:
Hello
xxxxxxxx
March
n

 

 

string 对象的长度用成员函数 length() 读取;

string s("hello");
cout << s.length() << endl;

输出:
5

 

string 支持流读取运算符,读到” “、”\n“、”\t“就停下

string stringObject;
cin >> stringObject;

 

string 支持 getline 函数

string s;
getline(cin ,s);
string 的赋值和连接

用 = 赋值

string s1("cat"), s2;
s2 = s1;

s1 和 s2 互相并不相关

 

用 assign 成员函数复制

string s1("cat"), s3;
s3.assign(s1);

 

用 assign 成员函数部分复制

string s1("catpig"), s3;
s3.assign(s1, 1, 3);     
//从s1中下标为1的字符开始复制3个字符给s3

 

单个字符复制

s2[5] = s1[3] = ‘a’;

 

逐个访问 string 对象中的字符

string s1("Hello");
for(int i = 0;i < s1.length();i++)
    cout << s1.at(i) << endl;

at 返回下标为 i 的那个字符的引用

成员函数 at 会做范围检查,如果超出范围,会抛出 out_of_range 异常,而**下标运算符[]**不做范围检查

 

 

用 + 运算符连接字符串

string s1("good "), s2("morning!");
s1 += s2;
cout << s1;

 

用成员函数 append 连接字符串

string s1("good "), s2("morning!");
s1.append(s2);
cout << s1;
s2.append(s1, 3, s1.size());  //s1.size(),s1字符数
cout << s2;  
//下标为3开始,s1.size()个字符,如果字符串内没有足够字符,则复制到字符串最后一个字符   
比较 string

用关系运算符 ==,>,>=,<,<=,!= 比较 string 的大小

返回值都是 bool 类型,成立返回 true,否则返回 false

string s1("hello"),s2("hello"),s3("hell");
bool b = (s1 == s2);
cout << b << endl;
b = (s1 == s3);
cout << b << endl;
b = (s1 > s3);
cout << b << endl;

输出:
1
0
1

 

用成员函数 compare 比较 string 的大小

string s1("hello"),s2("hello"),s3("hell");
int f1 = s1.compare(s2);
int f2 = s1.compare(s3);
int f3 = s3.compare(s1);
int f4 = s1.compare(1,2,s3,0,3);           // s1 1-2; s3 0-3
int f5 = s1.compare(0,s1.size(),s3);       // s1 0-end
cout << f1 << endl << f2 << endl << f3 << endl;
cout << f4 << endl << f5 << endl;
输出:
0   // hello == hello
1   // hello > hell
-1  // hell < hello
-1  // el < hell
1   // hello > hell 
子串

成员函数 substr

string s1("hello world"), s2;
s2 = s1.substr(4,5);         // 下标4开始5个字符
cout << s2 << endl;

输出:
o wor

 

返回一个新的字符串。如果往后数几个字符超过了字符串的长度就数到最后一个字符为止

交换 string

成员函数 swap

string s1("hello world"), s2("really");
s1.swap(s2);
cout << s1 << endl;
cout << s2 << endl;

输出:
really
hello world

寻找 string 中的字符

成员函数 find()

string s1("hello world");
s1.find("lo");

在 s1 中从前向后查找”lo“第一次出现的地方,如果找到返回”lo“开始的位置,即 l 所在的位置下标。如果找不到返回常数 string::npos(string 类中定义的静态常量)

 

string s1("hello worlld");
cout << s1.find("ll",1) << endl;
cout << s1.find("ll",2) << endl;
cout << s1.find("ll",3) << endl;  // 分别从下标1,2,3开始查找“ll”

输出:
2
2
9

 

 

成员函数 rfind()

string s1("hello world");
s1.rfind("lo");

在 s1 中从后向前查找“lo”第一次出现的地方。如果找到返回“lo”开始的位置,即 l 所在的位置下标。如果找不到返回 string::npos

 

成员函数 find_first_of()

string s1("hello world");
s1.find_first_of("abcd");

从 s1 中从前往后查找”abcd“中任何一个字符第一次出现的地方,如果找到返回找到字母的位置,如果找不到返回 string::npos

 

成员函数 find_last_of()

string s1("hello world");
s1.find_last_of("abcd");

在 s1 中查找”abcd“中任何一个字符最后一次出现的地方,如果找到返回找到字母的位置,如果找不到返回 string::npos

 

成员函数 find_first_not_of()

string s1("hello world");
s1.find_first_not_of("abcd");

在 s1 中从前向后查找不在”abcd“中的字母第一次出现的地方,如果找到返回找到字母的位置,如果找不到返回 string::npos

 

成员函数 find_last_not_of()

string s1("hello world");
s1.find_last_not_of("abcd");

在 s1 中从后向前查找不在”abcd“中的字母第一次出现的地方,如果找到返回找到字母的位置,如果找不到返回 string::npos

 

 

string s1("hello worlld");
cout << s1.find("ll") << endl;             
cout << s1.find("abc") << endl;            
cout << s1.rfind("ll") << endl;            
cout << s1.rfind("abc") << endl;           
cout << s1.find_first_of("abcde") << endl; 
cout << s1.find_first_of("abc") << endl;   
cout << s1.find_last_of("abcde") << endl;  
cout << s1.find_last_of("abc") << endl;    
cout << s1.find_first_not_of("abcde") << endl;   
cout << s1.find_first_not_of("hello world") << endl;
cout << s1.find_last_not_of("abcde") << endl;    
cout << s1.find_last_not_of("hello world") << endl;   

输出:
2
4294967295
9
4294967295
1
4294967295
11
4294967295
0
4294967295
10
4294967295

 

string::npos 输出结果就是一个比较大的整数

删除 string 中的字符

成员函数 erase()

string s1("hello worlld");
s1.erase(5);
cout << s1;
cout << s1.length();
cout << s1.size();
//去掉下标 5 及之后的字符

输出:
hello55

替换 string 中的字符

成员函数 replace()

string s1("hello world");
s1.replace(2, 3, "haha");
cout << s1;
//将s1中下标2开始的3个字符换成 "haha"

输出:
hehaha world

 

string s1("hello world");
s1.replace(2, 3, "haha", 1,2);
cout << s1;
//将s1中下标2开始的3个字符换成 "haha" 中下标1开始的2个字符

输出:
heah world

在 string 中插入字符

成员函数 insert()

string s1("hello world");
string s2("show insert");
s1.insert(5,s2); //将s2插入s1下标5的位置
cout << s1 << endl;
s1.insert(2,s2,5,3);
//将s2中下标5开始的3个字符插入s1下标2的位置
cout << s1 << endl;

输出:
helloshow insert world
heinslloshow insert world

*转换成C语言式char 字符串

成员函数 c_str()

string s1("hello world");
printf("%s\n",s1.c_str());
//s1.c_str() 返回传统的const char * 类型字符串,且该字符串以 '\0' 结尾

输出:
hello world

 

成员函数 data()

string s1("hello world");
const char * p1 = s1.data();
for(int i = 0;i < s1.length();i++)
    printf("%c",*(p1+i));
//s1.data() 返回一个char * 类型的字符串,对s1的修改可能会使p1 出错

输出:
hello world

字符串拷贝

成员函数 copy()

string s1("hello world");
int len = s1.length();
char * p2 = new char[len+1];
s1.copy(p2,5,0);
p2[5] = 0;
cout << p2 << endl;
//s1.copy(p2,5,0) 从s1的下标0的字符开始制作一个最长5个字符长度的字符串副本并将其赋值给p2。返回值表明实际复制字符串的长度

输出:
hello

字符串流处理

就像 C 语言的库函数 sscanf 和 sprintf 一样,输出输出的源都可以是字符串

除了标准流和文件流输入输出外还可以从 string 进行输入输出

类似 istream 和 osteram 进行标准流输入输出,我们用 istringstream 和 ostringstream 进行字符串上的输入输出,也称为内存输入输出

#include<string>
#include<iostream>
#include<sstream>

 

字符串输入流 istringstream

string input("Input test 123 4.7 A");
istringstream inputString(input);
string string1, string2;
int i;
double d;
char c;
inputString >> string1 >> string2 >> i >> d >> c;
cout << string1 << endl << string2 << endl;
cout << i << endl << d << endl << c << endl;
long L;
if(inputString >> L) cout << "long\n";
else cout << "empty\n";

输出:
Input
test
123
4.7
A
empty

 

字符串输出流 ostringstream

ostringstream outputString;
int a = 10;
outputString << "This " << a << "ok" << endl;
cout << outputString.str(); //返回 string 对象

输出:
This 10ok

STL 基本概念

里面是一堆函数模板和类模板

泛型程序设计

C++ 语言的核心优势之一就是便于软件的重用

C++中有两个方面体现重用:

  • 面向对象的思想:继承和多态,标准类库
  • 泛型程序设计(generic programming) 的思想: 模板机制,以及标准模板库 STL

 

泛型程序设计简单地说就是使用模板的程序设计法

将一些常用的数据结构(比如链表,数组,二叉树)和算法(比如排序,查找)写成函数模板和类模板,以后则不论数据结构里放的是什么对象,算法针对什么样的对象,则都不必重新实现数据结构,重新编写算法

标准模板库 (Standard Template Library)就是一些常用数据结构和算法的模板的集合

有了STL,不必再写大多的标准数据结构和算法,并且可获得非常高的性能

STL中的基本概念

容器:可容纳各种数据类型的通用数据结构,是类模板

迭代器:可用于依次存取容器中元素,类似于指针

算法:用来操作容器中的元素的函数模板

  • sort() 来对一个 vector 中的数据进行排序
  • find() 来搜索一个 list中的对象

算法本身与他们操作的数据的类型无关,因此他们可以在从简单数组到高度复杂容器的任何数据结构上使用

 

int array[100];

该数组就是容器,而 int * 类型的指针变量就可以作为迭代器,sort 算法可以作用于该容器上,对其进行排序,如:

sort(array,array+70); //将前70个元素排序

array 和 array + 70 都是迭代器

容器概述

可以用于存放各种类型的数据(基本类型的变量,对象等)的数据结构,都是类模板,分为三种:

  1. 顺序容器 vector、deque、list
  2. 关联容器 set、multiset、map、multimap
  3. 容器适配器 stack、queue、priority_queue

 

对象被插入容器中时,被插入的是对象的一个复制品。许多算法,比如排序,查找,要求对容器中的元素进行比较,有的容器本身就是排序的,所以,放入容器的对象所属的类,往往还应该重载 ==< 运算符

顺序容器简介

容器并非排序的,元素的插入位置同元素的值无关

有 vector、deque、list 三种

 

  • vector 头文件 <vector>

动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在不需要重新分配存储空间的情况下,往尾端增删元素能在常数时间完成。如果需要重新分配存储空间就要拷贝旧内容,时间复杂度就是 O ( n ) O(n) O(n)

由于 vector 总是提前多分配一些存储空间,因此大部分情况下往尾部增删元素不需要重新分配存储空间,时间复杂度就是常数

vector 在中间或者头部插入或者删除一个元素需要移动已有元素,时间复杂度是 O ( n ) O(n) O(n)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9p57C3H1-1666238673783)(C++ 面向对象程序设计.assets/image-20221016145800802.png)]

  • deque 头文件 <deque>

双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成(但次于 vector)。在两端增删元素具有较佳的性能(大部分情况下是常数时间,只有重新分配存储空间时才会变成 O ( n ) O(n) O(n)

在这里插入图片描述

下图这种情况下要随机存取任何元素,也就是给一个下标算出元素的地址,需要花时间判断下标加 head 之后是否超过了存储空间的末尾减去一个整个存储空间的长度把位置倒回到前面,而 vector 没有这种操作,因此 deque 在进行随机存取元素时会比 vector 慢

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vuXsfm8N-1666238673789)(C++ 面向对象程序设计.assets/image-20221016145952426.png)]

  • list 头文件 <list>

双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成,前提是已经找到了需要增删元素的位置。不支持随机存取

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lc6UwQzm-1666238673791)(C++ 面向对象程序设计.assets/image-20221016150012935.png)]

关联容器简介

元素是排序

插入任何元素,都按相应的排序规则来确定其位置

在查找时具有非常好的性能

通常以平衡二叉树方式实现,插入和检索的时间都是 O ( l o g ( N ) ) O(log(N)) O(log(N))

 

  • set/multiset 头文件 <set>

set 即集合。set 中不允许相同元素,multiset 中允许存在相同的元素

  • map/multimap 头文件 <map>

map 与 set 的不同在于 map 中存放的元素只能放对象,并且有且仅有两个成员变量,一个名为 first,另一个名为 second,map 根据 first 值对元素进行从小到大排序,并可快速地根据 first 来检索元素

map 同 multimap 的不同在于是否允许相同 first 值的元素

容器适配器简介
  • stack 头文件 <stack>

栈。是项的有限序列,并满足序列中被删除,检索和修改的项只能是最近插入序列的项(栈顶的项)。后进先出

  • queue 头文件 <queue>

队列。插入只可以在尾部进行,删除,检索和修改只允许从头部进行。先进先出

 

  • priority_queue 头文件 <queue>

优先级队列。最高优先级元素总是位于队列的头部第一个出列

顺序容器和关联容器中都有的成员函数

begin:返回指向容器中第一个元素的迭代器

end:返回指向容器中最后一个元素后面的位置的迭代器

rbegin:返回指向容器中最后一个元素的迭代器

rend:返回指向容器中第一个元素前面的位置的迭代器

erase:从容器中删除一个或几个元素

clear:从容器中删除所有元素

顺序容器的常用成员函数

front:返回容器中第一个元素的引用

back:返回容器中最后一个元素的引用

push_back:在容器末尾增加新元素

pop_back:删除容器末尾的元素

erase:删除迭代器指向的元素(可能会使该迭代器失效),或删除一个区间,返回被删除元素后面的那个元素的迭代器

迭代器

基本概念

用于指向顺序容器和关联容器中的元素。容器适配器不能访问中间的元素,不能遍历整个容器,没有迭代器

迭代器用法和指针类似

有 const 和非 const 两种

通过迭代器可以读取它指向的元素

通过非 const 迭代器还能修改其指向的元素

 

 

从容器模板实例化出来的类就叫做容器类,有了迭代器以后就可以访问这个容器类的对象里面的元素

定义一个容器类的迭代器的方法:

容器类名::iterator 变量名;
容器类名::const_iterator 变量名;

 

访问一个迭代器指向的元素:

* 迭代器变量名

 

 

迭代器上可以执行 ++ 操作, 以使其指向容器中的下一个元素。如果迭代器到达了容器中的最后一个元素的后面,此时再使用它,就会出错,类似于使用 NULL 或未初始化的指针一样

迭代器示例
#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<int> v;  //一个存放int元素的数组,一开始里面没有元素
    v.push_back(1); v.push_back(2);
    v.push_back(3); v.push_back(4);
    
    vector<int>::const_iterator i;   //常量迭代器
    for( i = v.begin();i != v.end();++i )
        cout << * i << ",";
    cout << endl;
    vector<int>::reverse_iterator r; //反向迭代器
    for( r = v.rbegin();r != v.rend();r++ )
        cout << * r << ",";
    cout << endl;
    vector<int>::iterator j;         //非常量迭代器
    for( j = v.begin();j != v.end();j++ )
        * j = 100;
    for( i = v.begin();i != v.end();i++ )
        cout << * i << ",";
}

输出结果:
1,2,3,4,
4,3,2,1,
100,100,100,100

 

vector<int> v 定义 vector<int> 容器类对象 v

通过常量迭代器 i 只能访问不能修改它所指向的内容

反向迭代器 reverse_iterator 和正向迭代器 iterator 名字不一样,类型不兼容。对于反向迭代器执行 ++ 操作会指向容器里的前一个元素

双向迭代器

若 p 和 p1 都是双向迭代器,则可对 p、p1 可进行以下操作:

++p, p++               //使p指向容器中下一个元素
--p, p--               //使p指向容器中上一个元素
* p                    //取p指向的元素
p = p1                 //赋值
p == p1 , p!= p1       //判断是否相等、不等

* p 表达式返回值是 p 指向的元素的引用

随机访问迭代器

若 p 和 p1 都是随机访问迭代器,则可对 p、p1 可进行以下操作:

//双向迭代器的所有操作
p += i             //将p向后移动 i 个元素
p -= i             //将p向前移动 i 个元素
p + i              //值为: 指向p后面的第i个元素的迭代器
p - i              //值为: 指向p前面的第i个元素的迭代器
p[i]               //值为: p后面的第i个元素的引用
p < p1, p <= p1, p > p1, p >= p1

 

 

容器容器上的迭代器类别
vector随机访问
deque随机访问
list双向
set/multiset双向
map/multimap双向
stack不支持迭代器
queue不支持迭代器
priority_queue不支持迭代器

vector 和 deque 上的迭代器是随机访问迭代器,支持随机访问,给一个下标就能够取到对应元素

list 和关联容器上的迭代器都是双向迭代器,不支持随机访问,要访问第 i 个元素要从头遍历

有的算法,例如 sort,binary_search 需要通过随机访问迭代器来访问容器中的元素,那么 list 以及关联容器就不支持该算法

 

 

vector 的迭代器是随机迭代器,遍历 vector 可以有以下几种做法(deque 亦然):

vector<int> v(100);
int i;
for( i = 0;i < v.size();i++ )
    cout << v[i];           //根据下标随机访问
vector<int>::const_iterator ii;
for( ii = v.begin();ii != v.end();++ii )
    cout << * ii;
for( ii = v.begin();ii < v.end();++ii )
    cout << * ii;

//间隔一个输出:
ii = v.begin();
while( ii < v.end()) {
    cout << * ii;
    ii = ii + 2;
}

v 是一个整型数组,一开始里面有100个元素

list 的迭代器是双向迭代器,正确的遍历 list 的方法:

list<int> v;
list<int>::const_iterator ii;
for( ii = v.begin();ii != v.end();++ii )
cout << * ii;

错误的做法:

for ( ii = v.begin(); ii < v.end(); ++ii )
    cout << * ii;
//双向迭代器不支持 <,list没有 [] 成员函数
for(int i = 0; i < v.size() ; i++)
    cout << v[i];

算法简介

算法简介

算法就是一个个函数模板, 大多数在 <algorithm> 中定义

STL 中提供能在各种容器中通用的算法,比如查找,排序等

算法通过迭代器来操纵容器中的元素。许多算法可以对容器中的一个局部区间进行操作,因此需要两个参数,一个是起始元素的迭代器,一个是终止元素的后面一个元素的迭代器。比如,排序和查找

有的算法返回一个迭代器。比如 find() 算法,在容器中查找一个元素,并返回一个指向该元素的迭代器

算法可以处理容器,也可以处理普通数组

算法示例:find()

不同编译器的头文件里面列出的顺序查找 find 函数模板写法会略有不同,这里是 Dev C++ 里面 find 的声明:

template <class InIt, class T>
InIt find( InIt first, InIt last, const T& val );

这里的 InIt 实际上就暗示类型参数是一个迭代器

first 和 last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点 [first,last) 。区间的起点是位于查找范围之中的,而终点不是。find 在 [first,last) 查找等于 val 的元素

== 运算符判断相等

函数返回值是一个迭代器。如果找到,则该迭代器指向被找到的元素。如果找不到,则该迭代器等于 last

顺序查找的时间复杂度是 O ( n ) O(n) O(n)

 

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {  //find算法示例
    int array[10] = {10,20,30,40};
    vector<int> v;
    v.push_back(1); v.push_back(2);
    v.push_back(3); v.push_back(4);
    vector<int>::iterator p;
    p = find(v.begin(),v.end(),3);
    if( p != v.end() ) 
          cout << * p << endl; //输出3
    p = find(v.begin(),v.end(),9);
    if( p == v.end() )
          cout << "not found " << endl;
    p = find(v.begin()+1,v.end()-2,1); 
    //整个容器:[1,2,3,4],查找区间:[2,3)
    if( p != v.end() )
          cout << * p << endl;
    int * pp = find(array,array+4,20); //数组名是迭代器
    cout << * pp << endl;
}

输出:
3
not found
3
20

 

实际上在 STL 的各种算法里面,只要提到了区间都是左闭右开的

STL中的“大”、“小”和“相等”

STL中“大”“小” 的概念

关联容器内部的元素是从小到大排序的

有些算法要求其操作的区间是从小到大排序的,称为“有序区间算法”,如 binary_search

有些算法会对区间进行从小到大排序,称为“排序算法”,如 sort

还有一些其他算法会用到“大”,“小”的概念

”大“和”小“的定义程序员可以自己设定

 

使用 STL 时,在缺省的情况下,以下三个说法等价:

  1. x 比 y 小
  2. 表达式“x < y”为真
  3. y 比 x 大
STL中“相等”的概念

有时,“x 和 y 相等”等价于“ x == y 为真”,如在未排序的区间上进行的算法,如顺序查找 find

有时,“x 和 y 相等”等价于“x 小于 y 和 y 小于 x 同时为假”,如有序区间算法 binary_search , 关联容器自身的成员函数 find,这里的小于也是可以自定义的

不能用 find 和 binary_search 算法查找关联容器,关联容器自身带有成员函数 find 可以用来进行查找

STL 中”相等“概念演示
#include <iostream>
#include <algorithm>
using namespace std;
class A {
    int v;
    public:
           A(int n):v(n) { }
           bool operator< ( const A & a2 ) const {
               cout << v << "<" << a2.v << "?" << endl;
               return false;
           }
           bool operator ==(const A & a2) const {
               cout << v << "==" << a2.v << "?" << endl;
               return v == a2.v;
           }
};

int main() {
    A a [] = { A(1),A(2),A(3),A(4),A(5) };
    cout << binary_search(a,a+4,A(9));
    return 0;
}

输出结果:
3<9?
2<9?
1<9?
9<1?
1

 

operator < 返回 false 意味着任何两个 class A 对象,其中一个都不会小于另外一个,即 x < y 和 y < x 都总不成立

operator == 用来判断两个 class A 对象是否相等,两个对象的 v 相等就相等

 

用二分查找 binary_search 在数组里面查找 A(9) 元素,函数调用的返回值是1而不是0,表明找到了 A(9),为什么?

从输出结果可以发现程序运行的过程中 operator == 并没有被调用,而 operator < 被多次调用,也就是说 binary_search 在执行的过程中判断数组里面有没有哪个值和 A(9) 相等用的是 operator <

binary_search 依次看 3<9、2<9、1<9是否成立,都不成立。由于不能再向前到前面一半找,binary_search 就判断9<1是否成立,也不成立。x < y 和 y < x 都不成立就认为 x 和 y 相等,和 x== y 没有关系,这就是 binary_search 工作的原理,1<9和9<1都不成立,binary_search 就认为1和9相等,找到了 A(9),返回1

vector 和 deque

vector 示例程序

vector 是动态可变长数组,根据下标访问元素和尾部增加删除元素的时间复杂度是 O ( 1 ) O(1) O(1),在中间插入删除元素的元素的时间复杂度是 O ( n ) O(n) O(n)

#include <iostream>
#include <vector>
using namespace std;
template<class T>
void PrintVector(T s, T e)
{
      for(; s != e; ++s)
            cout << * s << " ";
      cout << endl;
}

int main() {
    int a[5] = { 1,2,3,4,5 };
    vector<int> v(a,a+5); //将数组a的内容放入v
    cout << "1) " << v.end() - v.begin() << endl;
    //两个随机迭代器可以相减,输出 1) 5
    cout << "2) "; PrintVector(v.begin(),v.end());
    //2) 1 2 3 4 5
    v.insert(v.begin()+2,13); //在 begin()+2 位置插入13
    cout << "3) "; PrintVector(v.begin(),v.end());
    //3) 1 2 13 3 4 5
    v.erase(v.begin()+2);     //删除位于 begin()+2 的元素
    cout << "4) "; PrintVector(v.begin(),v.end());
    //4) 1 2 3 4 5
    vector<int> v2(4,100);    //v2有4个元素,都是100
    v2.insert(v2.begin(),v.begin()+ 1,v.begin()+3);
    //将v的一段插入v2开头
    cout << "5) v2: "; PrintVector(v2.begin(),v2.end());
    //5) v2: 2 3 100 100 100 100
    v.erase(v.begin() + 1, v.begin() + 3);
    //删除v上的一个区间,即 2,3
    cout << "6) "; PrintVector(v.begin(),v.end());
    //6) 1 4 5
    return 0;
}
用 vector 实现二维数组
#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<vector<int> > v(3);
    //v有3个元素,每个元素都是空的vector<int> 容器
    for(int i = 0;i < v.size(); ++i)
           for(int j = 0; j < 4; ++j)
                  v[i].push_back(j);
    for(int i = 0;i < v.size(); ++i) {
           for(int j = 0; j < v[i].size(); ++j)
                  cout << v[i][j] << " ";
           cout << endl;
    }
    return 0;
}

程序输出结果:
0 1 2 3
0 1 2 3
0 1 2 3

 

v 是一个 vector,vector 里面的每一个元素是一个 vector<int>。注意 vector<vector<int> > v(3) 不要忘了加空格,不加有的编译器会把两个”>“连在一起看做一个右移运算符导致编译出错

deque

所有适用于 vector 的操作都适用于 deque

deque 还有 push_front(将元素插入到前面) 和 pop_front(删除最前面的元素)操作,复杂度是 O ( 1 ) O(1) O(1)

双向链表 list

在任何位置插入删除都是常数时间,前提是已经有一个迭代器指向了那个位置,不支持随机存取

除了具有所有顺序容器都有的成员函数以外,还支持 8 个成员函数:

push_front:在前面插入, O ( 1 ) O(1) O(1)

pop_front:删除前面的元素, O ( 1 ) O(1) O(1)

sort:排序 ( list 不是随机迭代器,不支持 STL 的算法 sort )

remove:删除和指定值相等的所有元素

unique:删除所有和前一个元素相同的元素(要做到元素不重复,则 unique 之前还需要 sort)

merge:合并两个链表,并清空被合并的那个

reverse:颠倒链表

splice:在指定位置前面插入另一链表中的一个或多个元素,并在另一链表中删除被插入的元素

#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
class A {
    private:
        int n;
    public:
        A( int n_ ) { n = n_; }
        friend bool operator< ( const A & a1, const A & a2 );
        friend bool operator== ( const A & a1, const A & a2 );
        friend ostream & operator<< ( ostream & o, const A & a );
};

bool operator< ( const A & a1, const A & a2 ) {
    return a1.n < a2.n;
}
bool operator== ( const A & a1, const A & a2 ) {
    return a1.n == a2.n;
}
ostream & operator<< ( ostream & o, const A & a ) {
    o << a.n;
    return o;
}

template <class T>
void PrintList( const list<T> & lst ) {
//不推荐的写法,还是用两个迭代器作为参数更好
    int tmp = lst.size();
    if( tmp > 0 ) {
        typename list<T>::const_iterator i;
        i = lst.begin();
        for( i = lst.begin();i != lst.end(); i ++)
              cout << * i << ",";
}
    
}
//typename用来说明 list<T>::const_iterator 是个类型
//在vs中不写也可以

int main() {
    list<A> lst1,lst2;
    lst1.push_back(1);lst1.push_back(3);
    lst1.push_back(2);lst1.push_back(4);
    lst1.push_back(2);
    lst2.push_back(10);lst2.push_front(20);
    lst2.push_back(30);lst2.push_back(30);
    lst2.push_back(30);lst2.push_front(40);
    lst2.push_back(40);
    cout << "1) "; PrintList(lst1); cout << endl;
    // 1) 1,3,2,4,2,
    cout << "2) "; PrintList(lst2); cout << endl;
    // 2) 40,20,10,30,30,30,40,
    lst2.sort();
    cout << "3) "; PrintList(lst2); cout << endl;
    // 3) 10,20,30,30,30,40,40,
    lst2.pop_front(); 
    cout << "4) "; PrintList(lst2); cout << endl;
    // 4) 20,30,30,30,40,40,
    lst1.remove(2); //删除所有和A(2)相等的元素
    cout << "5) "; PrintList(lst1); cout << endl;
    // 5) 1,3,4,
    lst2.unique();  //删除所有和前一个元素相等的元素
    cout << "6) "; PrintList(lst2); cout << endl;
    // 6) 20,30,40,
    lst1.merge(lst2); //合并lst2到lst1并清空lst2
    cout << "7) "; PrintList(lst1); cout << endl;
    // 7) 1,3,4,20,30,40,
    cout << "8) "; PrintList(lst2); cout << endl;
    // 8)
    lst1.reverse();
    cout << "9) "; PrintList(lst1); cout << endl;
    // 9) 40,30,20,4,3,1,
    lst2.push_back(100);lst2.push_back(200);
    lst2.push_back(300);lst2.push_back(400);
    list<A>::iterator p1,p2,p3;
    p1 = find(lst1.begin(),lst1.end(),3);
    p2 = find(lst2.begin(),lst2.end(),200);
    p3 = find(lst2.begin(),lst2.end(),400);
    lst1.splice(p1,lst2,p2,p3);
    //将[p2,p3)插入p1之前,并从lst2中删除[p2,p3)
    cout << "10) "; PrintList(lst1); cout << endl;
    // 10) 40,30,20,4,200,300,3,1,
    cout << "11) "; PrintList(lst2); cout << endl;
    // 11) 100,400,
    return 0;
}

Dev C++ 在模板里面定义这种类型不确定的容器上面的迭代器前面要加 typename

class A 里面写了类型转换构造函数,整数都会被自动转换成一个 class A 的对象再 push 进 lst1 和 lst2,这个对象是一个临时对象,被放到 lst1 和 lst2 里面的元素是临时对象的拷贝

函数对象

函数对象

若一个类重载了运算符“()”,则该类的对象就成为函数对象

class CMyAverage { //函数对象类
    public:
           double operator()( int a1, int a2, int a3 ) { 
               return (double)(a1 + a2+a3) / 3;
           }
}; 

CMyAverage average; //函数对象
cout << average(3,2,3); 
//average.operator()(3,2,3) 输出 2.66667
函数对象的应用

Dev C++ 中的 Accumulate 函数模板源代码1:

template<typename _InputIterator, typename _Tp>
_Tp accumulate( _InputIterator __first, _InputIterator __last, _Tp __init )
{
    for ( ; __first != __last; ++__first )
        __init = __init + *__first;
    return __init;
}
//typename等效于class

_InputIterator 暗示这个类型参数应该是迭代器类型的

 

Dev C++ 中的 Accumulate 源代码2:

template<typename _InputIterator, typename _Tp, typename _BinaryOperation> 
_Tp accumulate( _InputIterator __first, _InputIterator __last, _Tp __init, _BinaryOperation __binary_op )
{
    for ( ; __first != __last; ++__first )
        __init = __binary_op(__init, *__first);
    return __init;
}

__init = __binary_op(__init, *__first) 对区间 [__first, __last) 里的每一个元素都和 __init 做运算然后把运算结果又赋值给 __init

调用 accumulate 函数模板时,为了使这个表达式编译通过,__binary_op 参数可以是一个函数的名字,可以是一个函数指针,也可以是一个函数对象。如果这个 __binary_op 是一个函数对象,__binary_op(__init, *__first) 表达式实际上就是在函数对象的 operator () 成员函数

函数对象的应用示例
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>
using namespace std;

int sumSquares( int total, int value )
{ return total + value * value; }

template <class T>
void PrintInterval(T first, T last)
{ //输出区间[first,last)中的元素
       for( ; first != last; ++ first)
              cout << * first << " ";
       cout << endl;
}

template<class T>
class SumPowers
{
       private:
           int power;
       public:
           SumPowers(int p):power(p) { }
           const T operator() ( const T & total, const T & value )
          { //计算value的power次方,加到total上
               T v = value;
               for( int i = 0;i < power - 1; ++ i)
                   v = v * value;
               return total + v;
           }
}

int main()
{
    const int SIZE = 10;
    int a1[] = { 1,2,3,4,5,6,7,8,9,10 };
    vector<int> v(a1,a1+SIZE);
    cout << "1) "; PrintInterval(v.begin(),v.end());
    int result = accumulate(v.begin(),v.end(),0,SumSquares);
    cout << "2) 平方和:" << result << endl;
    result = accumulate(v.begin(),v.end(),0,SumPowers<int>(3));
    cout << "3) 立方和:" << result << endl;
    result = accumulate(v.begin(),v.end(),0,SumPowers<int>(4));
    cout << "4) 4次方和:" << result;
    return 0;
}

输出:
1) 1 2 3 4 5 6 7 8 9 10
2) 平方和: 385
3) 立方和: 3025
4) 4次方和: 25333

 

SumPowers 是一个类模板,SumPowers<int> 是一个模板类,SumPowers<int>(3) 是一个临时函数类

下面看一下 accumulate 具体是怎么工作的

int result = accumulate(v.begin(),v.end(),0,SumSquares);

编译器经由 accumulate 模板从这条调用语句实例化出 accumulate 函数:

int accumulate(vector<int>::iterator first,vector<int>::iterator last,int init,int ( * op)(int,int))
{
    for ( ; first != last; ++first)
         init = op(init, *first);
    return init;
}

什么样的形参可以和函数名字匹配?函数指针可以跟函数名字相匹配

函数指针 op 指向的函数返回值类型是 int,有两个 int 类型参数

 

accumulate(v.begin(),v.end(),0,SumPowers<int>(3));

实例化出:

int accumulate(vector<int>::iterator first,vector<int>::iterator last,int init, SumPowers<int> op)
{
    for ( ; first != last; ++first)
           init = op(init, *first);
    return init;
}

最后一个参数 op 是一个函数对象 SumPowers<int>(3),所属的类是 SumPowers<int>,调用 op 的 operator () 成员函数

STL中的函数对象类模板

STL 的 <functional> 头文件里还有以下模板:

equal_to

greater

less

这些模板实现了”()“成员函数,所以称之为函数对象类模板,可以用来生成函数对象

greater 函数对象类模板
template <class T>
struct greater : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const {
        return x > y;
    }
};

如果哪一个关联容器或者算法使用了 greater 模板作为比较器,这些容器或者算法需要比较两个元素 x, y 大小时就会调用 operator() 成员函数并且把 x, y 作为参数传进来

operator() 函数返回值是 true,这些容器或者算法就会认为 x 小于 y,但是实际上这里面 x > y 表达式为 true 时则 operator() 返回 true,也就是说 greater 定义的比大小的规则跟传统方式是相反的,传统意义上的大反而意味着小

greater 的应用

list 有两个 sort 函数

void sort()

将 list 中的元素按 ”<“ 规定的比较方法升序排列

 

template <class Compare>
void sort(Compare op);

将 list 中的元素按 op 规定的比较方法升序排列。即要比较 x, y 大小时,看 op(x, y) 表达式的返回值,为 true 则认为 x 小于 y,也就是 x 需要排在 y 的前面

  

 

#include <list>
#include <iostream>
#include <iterator>
using namespace std;
class MyLess {
    public:
           bool operator()( const int & c1, const int & c2 )
          {
                      return (c1 % 10) < (c2 % 10);
          }
};
template <class T>
void Print( T first, T last){
    for( ; first != last; ++first) cout << *first << ",";
}

int main()
{ 
    const int SIZE = 5;
    int a[SIZE] = {5,21,14,2,3};
    list<int> lst(a,a+SIZE);
    lst.sort(MyLess());
    Print(lst.begin(),ls.end()); cout << endl;
    lst.sort(greater<int>()); //greater<int>()是个对象
    Print(lst.begin(),ls.end()); cout << endl;
    return 0;
}

输出:
21,2,3,14,5,
21,14,5,3,2,

 

Myless 是一个类的名字,MyLess() 是一个用无参构造函数初始化的临时函数对象。调用 lst.sort(MyLess()) 时指明排序比大小的规则由 MyLess() 函数对象决定,更准确地说是用 MyLess 类里面的 operator() 决定的,哪个元素的个位数小哪个元素就小

greater 是类模板,greater<int> 是模板类,great<int>() 生成临时函数对象,如果用 great<int>() 函数对象作为比大小的规则,哪个整数数学上大就把它算小排在前面,导致 lst 被降序排列

引入函数对象后,STL 中的“大”,“小”关系

关联容器和 STL 中许多算法,都是可以用函数或函数对象自定义比较器的。在自定义了比较器 op 的情况下,以下三种说法是等价的:

1.x 小于 y

2.op(x,y) 返回值为 true

3.y 大于 x

例题:写出 MyMax 模板
#include <iostream>
#include <iterator>
using namespace std;

class MyLess
{
    public:
    bool operator() (int a1,int a2)
   {
        if( (a1%10) < (a2%10) )
            return true;
        else
            return false;
   }
};

bool MyCompare(int a1,int a2)
{
    if( (a1%10) < (a2%10) )
        return false;
    else
        return true;
}

template <class T, class Pred>
T MyMax(T fist, T last, Pred myless)
{
    T tmpmax = first;
    for( ; fist != last; ++first )
        if( myless( * tmpmax, * first) )
            tmpmax = first;
    return tmpmax;
};

int main()
{
    int a[] = {35,7,13,19,12};
    cout << * MyMax(a,5,MyLess()) << endl;
    cout << * MyMax(a,5,MyCompare) << endl;
    return 0;
}

输出:
19
12

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值