(十三)C++学习 | string类 STL


1. string类

string类中预定义了大量关于字符串的操作,在使用该类时需要引入头文件#include<string>。几种常用的字符串初始化方法包括:

// 正确的初始化方法
string s1("Hello");
string s2 = "Hello";
string s3(3, 'a');	// s3初始化为3个连续字符'a'组成的字符串
// 错误的初始化方法
string s4 = 'a';
string s5('a');
string s6=11;
string s7(11);

除了以上列举的正确和错误的字符串初始化方法,还存在一种特殊的情况,即将字符赋值给string对象:

string s;
s = 'n';

最后列举出string类的特性以及常用函数:

  • 使用length函数获取string类对象的长度;
  • string支持流读取运算符,即cin >> stringObject;
  • string支持getline()函数,即getline(cin, str);
  • 两个string类之间可以使用等号赋值;
  • 可以使用成员函数assign()实现对象的整体或部分赋值;
  • string对象的访问类似于数组,即使用[]访问,同时也可以使用成员函数at()实现元素访问;
  • 可以仅使用加号将两个string类对象连接,也可以使用成员函数append()实现该功能;
  • 可以使用关系运算符比较两个string类对象的大小,比较依据是字典序,也可以使用成员函数compare()实现该功能;
  • 成员函数substr()可以取string类对象的子串;
  • 成员函数swap()可以交换两个string类对象的内容;
  • 成员函数find()用于查找string类对象中的子串,如果找到则返回所在位置的索引;
  • 成员函数rfind()的功能同find(),但二者的查找方向相反;
  • 成员函数erase()用于删除string类对象中的字符;
  • 成员函数replace()用于替换string类对象中的字符;
  • 成员函数insert()用于在string类对象中插入字符;
  • 成员函数c_str()用于将string类对象转化成char*

2. STL,标准模板库简介

标准模板库是 C + + {\rm C++} C中的重要内容,前面提到 C + + {\rm C++} C语言的核心优势之一是便于软件重用,这主要体现在两个重要的方面:

  1. 面向对象的思想,包括继承、多态,标准类库等;
  2. 泛型程序设计的思想,包括模板机制以及标准模板库等。

在标准模板库中, C + + {\rm C++} C将一些常用的数据结构,如链表数组二叉树等,以及一些常用的算法,如排序查找等写成模板以方便我们使用。而以后无论数据结构里存放的是什么类型的数据,算法作用的对象是什么,我们都不必去重写数据结构而可以直接使用标准模板库的内容。 S T L {\rm STL} STL中包含三个基本的概念,分别是容器迭代器算法

  • 容器 可以容纳各种数据类型的通用数据结构,是类模板。其中顺序容器包括vectordequelist,关联容器包括setmultisetmapmultimap,容器适配器包括stackqueuepriority_queue
  • 迭代器 可以用于依次存取容器中的元素,类似于指针;
  • 算法 用来操作容器中的元素的函数模板,如sort()函数和find()函数等。而算法本身与它们所操作的数据结构无关,因此它们可以在从简单数组到复杂容器的任何数据结构上使用。

3. 常用的STL

首先介绍顺序容器,它的元素存放位置是无序的

3.1 vector

vector包含在头文件#include<vector>中,它表示一种动态数组,元素在内存中连续存放。它具备数组的一般特性,即随机存取任何元素都能在常数时间完成。程序实例:

#include<iostream>
#include<vector>

using namespace std;
// 定义函数模板,将vector里的元素全部输出
template<class T> void PrintVector(T s, T e) {
	for (;s != e; ++s) {
		cout << *s << " ";
	}
}

int main() {
	int a[5] = {1, 2, 3, 4, 5};
	vector<int> v(a, a+5);	// 将数组a的元素赋值给vector v
	cout << v.end() - v.begin() << " ";	// 输出5
	v.insert(v.begin() + 2, 13);	// 将13插入第2个位置,vector变为{1, 2, 13, 3, 4, 5}
	v.erase(v.begin() + 2);	// 删除第2个位置处的元素,vector变为{1, 2, 3, 4, 5}
	vector<int> v2(4, 100);	// vector v2一共有四个元素,全部为100
	v2.insert(v2.begin(), v.begin() + 1, v.begin() + 3);	// 将v中的一段元素插入v2开头处
	v.erase(v.begin() + 1, v.begin() + 3);	// 删除v中的一段元素
	PrintVector(v.begin(), b.end());	// 打印
	return 0;
}

vector还可用于定义二维数组,即vector<vector<int>> v(3);表示定义了一个二维数组, 3 3 3表示定义了含有三个元素vector<int>vector,即二维向量。示例程序:

#include<iostream>
#include<vector>

using namespace std;

int main() {
	// 二维vector
	vector<vector<int>> v(3);
	// 往二维vector内添加元素
	for (int i = 0; i < v.size(); ++i) {
		for (int j = 0; j < 4; ++j) {
			v[i].push_back(j);
		}
	}
	// 输出四行的0 1 2 3
	for (int i = 0; i < v.size(); ++i) {
		for (int j = 0; j < v[i].size(); ++j) {
			cout << v[i][j] << " ";
		}
		cout << endl;
	}
}

3.2 deque

deque包含在头文件#include<deque>中,它表示一种双向队列,元素在内存中连续存放。随机存取任何元素都能在常数时间完成。这里,所有适用于vector的操作均适用于deque。除此之外,deque还有将元素插入到最前面以及删除最前面元素的操作,且时间复杂度是 O ( 1 ) O(1) O(1)

3.3 list

list包含在头文件#include<list>中,它表示一种双向链表,元素在内存中不连续存放。在任何位置增删元素都能在常数时间完成,不支持随机存取。除了具有所有顺序容器都有的成员函数外,还支持以下函数:

  • push_front 在链表的最前面插入;
  • pop_front 删除最前面的元素;
  • sort 排序(但不支持 S T L {\rm STL} STL中的排序函数);
  • remove 删除和指定值相等的所有元素;
  • unique 删除所有和前一个元素相同的元素(如果需要删除容器中的全部重复元素,应先执行排序操作);
  • 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) {
	// typename关键字
	typename list<T>::const_iterator i;
	i = lst.begin();
	for (; i != lst.end(); ++i) {
		cout << *i << ",";
	}
}

int main() {
	// 链表的元素均为A的对象
	list<A> lst1, lst2;
	// lst1: 1->3->2->4->2
	lst1.push_back(1); lst1.push_back(3);
	lst1.push_back(2); lst1.push_back(4);
	lst1.push_back(2);
	// lst2: 40->20->10->30->30->30->40
	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);
	// 排序,lst2: 10->20->30->30->30->40->40
	lst2.sort();
	// lst2: 20->30->30->30->40->40
	lst2.pop_front();
	// 删除所有和A(2)相等的元素,lst1: 1->3->4
	lst1.remove(2);
	// 删除重复元素,lst2: 20->30->40
	lst2.unique();
	// 合并,并清空lst2,lst1: 1->3->4->20->30->40
	lst1.merge(lst2);
	// 翻转链表,lst1: 40->30->20->4->3->1
	lst1.reverse();
	// 往lst2中添加元素
	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(lst1.begin(), lst1.end(), 200);
	p3 = find(lst1.begin(), lst1.end(), 400);
	// 在lst2中将[p2,p3)元素插入p1位置,lst1:lst1: 40->30->20->4->200->300->3->1
	lst1.splice(p1, lst2, p2, p3);
	return 0;
}

其次是关联容器,容器中的元素是排序的,插入任何元素都按相应的排序规则来确定其位置,在查找时具有非常好的性能。该类容器通常以平衡二叉树实现,插入和检索的时间复杂度都是O(logN)

3.4 set / multiset

set / multiset包含在头文件#include<set>中,set中不允许包含相同的元素,而multiset中允许存在相同的元素。关联容器除了各容器都有的函数外,还支持以下成员函数:

  • find 查找等于某个值的元素(x小于yy小于x同时不成立即为相等);
  • lower_bound:查找某个下界;
  • upper_bound:查找某个下界;
  • euqal_range:同时查找上界和下界;
  • count:计算等于某个值的元素,等于的定义同上;
  • insert:用以插入一个元素或一个区间。

multiset

template<class Key, class Pred=less<Key>, class A=allocator<Key>>

Pred类型的变量决定了multiset中的元素,即比较大小是怎么定义的。less的定义:

template<class T> struct less: public binary_function<T, T, bool> {
	bool operator()(const T& x, const T& y) {
		return x < y;	// 默认使用小于号比较大小
	}
	const;
};

multiset 的成员函数:

  • iterator find(const T& val); 在容器中查找值为val的元素,返回其迭代器;如果找不到,则返回end()
  • iterator insert(const T& val);val插入到容器中并返回其迭代器;
  • void insert(iterator first, iterator last); 将区间[first, last)插入容器;
  • int count(const T&val); 统计有多少个元素的值和val相等;
  • iterator lower_bound(const T& val); 查找一个最大的位置it,使得[begin(), it)中的所有元素均比val小;
  • iterator upper_bound(const T& val); 查找一个最小的位置it,使得[it, end())中的所有元素均比val大;
  • pair<iterator, iterator> equal_range(const T& val); 同时求得lower_boundupper_bound
  • iterator erase(iterator it); 删除it指向的元素,返回其后面元素的迭代器。

错误示例程序:

#include<set>

using namespace std;

class A {};
int main(){
	multiset<A> a;
	a.insert(A());	// 出错
}

在插入元素时,multiset会将被插入元素和已有元素进行比较。由于默认是用小于号比较的,所以这要求上述类A的对象能够使用小于号作比较,但是在该类中小于号没有被重载,所以会出错。示例程序:

#include<iostream>
#include<set>

using namespace std;
// 打印函数模板
template<class T> void Print(T first, T last) {
	for (; first != last; ++first) {
		cout << *first << " ";
	}
	cout << endl;
}
// 类A
class A {
private:
	int n;
public:
	// 构造函数
	A(int n_) {
		n = n_;
	}
	// 重载
	friend bool operator < (const A& a1, const A& a2) {
		return a1.n < a2.n;
	}
	friend ostream& operator<<(ostream& o, const A& a2) {
		o << a2.n;
		return o;
	}
	// 友元类
	friend class MyLess;
};
struct MyLess {
	// 类对象为函数对象
	bool operator()(const A& a1, const A& a2) const {
		// 个位较小者小
		return (a1.n % 10) < (a2.n % 10);
	}
};
typedef multiset<A> MSET1;	// MSET1用小于号比较大小
typedef multiset<A, MyLess> MSET2;	// MSET2用MyLess比较大小
int main() {
	const int SIZE = 6;
	A a[SIZE] = { 4,22,19,8,33,40 };
	MSET1 m1;
	// 4 8 19 22 33 40
	m1.insert(a, a + SIZE);
	// 4 8 19 22 22 33 40
	m1.insert(22);
	// 22
	cout << *m1.lower_bound(22) << " ";
	// 33
	cout << *m1.upper_bound(22) << " ";
	// 4 8 19 33 40
	m1.erase(m1.lower_bound(22), m1.upper_bound(22));
	// MSET2对象
	MSET2 m2;
	// 40 22 33 4 8 19
	m2.insert(a, a + SIZE);
	return 0;
}

set的示例程序:

#include<iostream>
#include<set>

using namespace std;

int main() {
	typedef set<int>::iterator IT;
	int a[5] = {3, 4, 6, 1, 2};
	// 1 2 3 4 6
	set<int> st(a, a + 5);
	pair<IT, bool> result;
	// 1 2 3 4 5 6
	result = st.inset(5);
	// 判断是否插入成功
	// 插入成功
	if (result.second) {
		// ...
	}
	// 插入失败
	else {
		// ...
	}
	pair<IT, IT> bounds = st.equal_range(4);
	// 45
	cout << *bounds.first << *bounds.second;
	return 0;
}

3.5 map / multimap

map / multimap包含在头文件#include<set>中,map中不允许包含相同的元素,而multimap中允许存在相同的元素。mapset的最大不同在于,前者存放的元素有且仅有两个成员变量,一个名为first,另一个名为secondmap会根据first的顺序对容器内的元素从小到大进行排序,并且可以通过first快速检索元素。map / multimap容器里存放的都是pair对象。

  • map / multimap中的元素由<关键字, 值>组成,每个元素都是一个pair对象,关键字是first成员变量,其类型是Key
  • multimap中允许存在多个元素的关键字相同,元素按照first成员变量从小到大排列。

示例程序:

#include<iostream>
#include<map>

using namespace std;

int main(){
	typedef multimap<int, double, less<int>> mmid;
	mmid pairs;
	// 0
	cout << pairs.count(15) << " ";
	pairs.insert(mmid::value_type(15, 2.7));
	pairs.insert(mmid::value_type(15, 99.3));
	// 2
	cout << pairs.count(15) << " ";
	pairs.insert(mmid::value_type(30, 111.11));
	pairs.insert(mmid::value_type(10, 22.22));
	pairs.insert(mmid::value_type(25, 33.333));
	pairs.insert(mmid::value_type(20, 9.3));
	// 10,22.22 15,2.7 15,99.3 20,9.3 25,33.333 30,111.11
	for (mmid::const_iterator i = pairs.begin(); i != pairs.end(); ++i) {
		cout << i->first << "," << i->second << " ";
	}
}

例题 学生成绩录入和查询系统,该系统的输入包含两种:第一种输入表示要添加一个学生的信息,碰到这种输入,就记下学生的姓名、学号和分数;第二种输入表示要查询,碰到这种输入,就输出已有记录中分数比输入分数低的最高分获得者的姓名、学号和分数。如果有多个学生满足条件,就输出学号最大的那个学生的信息。如果找不到满足条件的学生,则输出相应的提示信息。输入和输出样例:

// 输入
Add Jack 12 78	// 添加Jack,学号是12,成绩是78
Query 78	// 查询比78低的最高分获得者的姓名、学号和分数
Query 81
Add Percy 9 81
Add Marry 8 81
Query 82
Add Tom 11 79
Query 80
Query 81
// 输出
Nobody	// 查询结果为空
Jack 12 78
Percy 9 81
Tom 11 79
Tom 11 79

示例程序:

#include<map>
#include<string>
#include<iostream>

using namespace std;

class CStudent {
public:
	// 类的内部定义其他类
	struct CInfo {
		int id;	// 学号
		string name;	// 姓名
	};
	int score;	// 分数
	CInfo info;	// 学生的其他信息
};
// 第一个关键字是整型,第二个关键字是CInfo
typedef multimap<int, CStudent::CInfo> MAP_STD;

int main() {
	MAP_STD mp;
	CStudent st;
	string cmd;
	while (cin >> cmd)
	{
		// 添加信息
		if (cmd == "Add") {
			// 接收输入姓名、学号和分数
			cin >> st.info.name >> st.info.id >> st.score;
			// 插入
			mp.insert(MAP_STD::value_type(st.score, st.info));
		}
		// 查询
		else if (cmd == "Query") {
			// 输入分数
			int score;
			cin >> score;
			// 查询比score低的最大值
			MAP_STD::iterator p = mp.lower_bound(score);
			// 查找成功
			if (p != mp.begin()) {
				// 前一个元素
				--p;
				// 记录结果
				score = p->first;
				MAP_STD::iterator maxp = p;
				int maxId = p->second.id;
				// 循环查找具有最大学号的元素
				for (; p != mp.begin() && p->first == score; --p) {
					if (p->second.id > maxId) {
						maxp = p;
						maxId = p->second.id;
					}
				}
				// 上述循环以p == mp.begin()时结束额外处理
				if (p->first == score) {
					if (p->second.id > maxId) {
						maxp = p;
						maxId = p->second.id;
					}
				}
				// 输出
				cout << maxp->second.name << " " << maxp->second.id << " " << maxp->first << endl;
			}
			// 查找失败
			else
			{
				cout << "Nobody" << endl;
			}
		}
	}
	return 0;
}

multimap相比,map不能存在相同的元素外,还具有一个重要的特性,即通过[ ]取值,返回对关键字等于key的元素的值的引用。如果没有该关键字,则会自动往map里面插入该关键字,并返回其引用。示例程序:

#include<map>
#include<iostream>

using namespace std;
// 重载左移运算符,输出pair对象的值
template<class Key, class Value> ostream& operator<<(ostream& o, const pair<Key, Value>& p)	{
	o << p.first << "," << p.second << " ";
	return o;
}
int main() {
	typedef map<int, double, less<int>> mmid;
	mmid pairs;
	// 0
	cout << pairs.count(15) << " ";
	pairs.insert(mmid::value_type(15, 2.7));
	pairs.insert(make_pair(15, 99.4));
	// 1
	cout << pairs.count(15) << " ";
	pairs.insert(mmid::value_type(20, 9.3));	// set不允许出现重复元素,插入失败
	mmid::iterator i;
	// 15,2.7 20,9.3
	for (i = pairs.begin(); i !=pairs.end(); ++i) {
		cout << *i << ",";
	}
	cout << endl;
	// 没有40,插入该元素
	int n = pairs[40];
	// 15,2.7 20,9.3 40,0
	for (i = pairs.begin(); i != pairs.end(); ++i) {
		cout << *i << ",";
	}
	// 将关键字15对应的元素改成6.28
	// 15,6.28 20,9.3 40,0
	for (i = pairs.begin(); i != pairs.end(); ++i) {
		cout << *i << ",";
	}
	return 0;
}

最后是容器适配器

3.6 stack

stack包含在头文件#include<stack>中,它表示一种栈,是项的有限序列。并且满足序列中被删除、检索和修改的项只能是最近插入序列的项,是一种满足后进先出的数据结构。

  • push 插入元素;
  • pop 弹出元素;
  • top 返回栈顶元素的引用。

3.7 queue

queue包含在头文件#include<queue>中,它表示一种队列,插入只可以在尾部进行,删除、检索和修改只允许从头部进行,是一种满足先进先出的数据结构。队列的成员函数与栈类似,遵循先进先出的原则。

3.8 priority_queue

priority_queue包含在头文件#include<queue>中,它表示一种队列,最高优先级(通常是最大或最小)的元素总是位于队列头部。示例程序:

#include<queue>
#include<iostream>

using namespace std;

int main() {
	priority_queue<double> pq1;
	pq1.push(3.2); pq1.push(9.8); pq1.push(9.8); pq1.push(5.4);
	// 9.8 9.8 5.4 3.2
	while(!pq1.empty()) {
		cout << pq1.top() << " ";
		pq1.pop();
	}
	// 比较大小使用greater
	priority_queue<double, vector<double>, greater<double>> pq2;
	pq2.push(3.2); pq2.push(9.8); pq2.push(9.8); pq2.push(5.4);
	// 3.2 5.4 9.8 9.8
	while(!pq2.empty()) {
		cout << pq1.top() << " ";
		pq1.pop();
	}
	return 0;
}

3.9 顺序容器和关联容器中常用的成员函数

  • begin 返回指向容器中第一个元素的迭代器;
  • end 返回指向容器中最后一个元素后面的位置的迭代器;
  • rbegin 返回指向容器最后一个元素的迭代器;
  • rend 返回指向容器中第一个元素前面的位置的迭代器;
  • erase 从容器中删除一个或多个元素;
  • clear 从容器中删除所有元素。

3.10 顺序容器中常用的成员函数

  • front 返回容器中第一个元素的索引;
  • back 返回容器中最后一个元素的引用;
  • push_back 在容器末尾增加新元素;
  • pop_back 删除容器末尾的元素;
  • erase 删除迭代器指向的元素,或删除一个区间,返回被删除元素后面那个元素的迭代器。

4. 迭代器

  • 用于指向顺序容器和关联容器中的元素;
  • 迭代器用法和指针类似;
  • const和非const两种;
  • 通过迭代器可以读取它指向的元素;
  • 通过非const迭代器还能修改其指向的元素。

定义迭代的格式是容器类名::iterator 变量名;容器类名::const_iterator 变量名;,使用以下方式访问一个迭代器指向的元素*迭代器变量名;。此外,迭代上可以执行++操作,以使其指向容器中的下一个元素。如果迭代器到达了容器中的最后一个元素的后面,此时再使用它,就会出错,类似于使用NULL或未初始化的指针一样。

int main() {
	vector<int> v;
	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 << " ";	// 输出1 2 3 4
	}
	vector<int>::reverse_iterator r;	// 反向迭代器
	for(r= v.begin(); r != v.end(); ++r) {
		cout << *r<< " ";	// 输出4 3 2 1
	}
	vector<int>::iterator j;	// 非常量迭代器
	for(j= v.begin(); j != v.end(); ++j) {
		*j = 100;
	}
	for(j= v.begin(); j != v.end(); ++j) {
		cout << *j<< " ";	// 输出100 100 100 100
	}
	return 0;
}

迭代器主要分为双向迭代器随机访问迭代器。首先若pq均是双向迭代器,则可对pq作如下操作:

  • ++pp++,使p指向容器中的下一个元素;
  • --pp--,使p指向容器中的上一个元素;
  • *p,取p指向的元素;
  • p = q,赋值操作;
  • p == qp != q,判断是否相等/不相等。

对于随机访问迭代,它的功能更加强大,除具备双向迭代器的所有功能外,它还具备如下操作:

  • p += i,将p向后移动i个元素;
  • p -= i,将p向前移动i个元素;
  • p + i,指向p后面的第i个元素的迭代器;
  • p - i,指向p前面的第i个元素的迭代器;
  • p[i]p后面的第i个元素的引用;
  • p < qp <= qp > qp >= q,比较大小。

5. 算法简介

S T L {\rm STL} STL中内置许多算法,其基本特性如下:

  • 算法就是一个个函数模板,大多数都在头文件#include<algorithm>中定义;
  • S T L {\rm STL} STL中提供能在各种容器中通用的算法,比如查找、排序等;
  • 算法通过迭代器来操纵容器中的元素。许多算法可以对容器中的一个局部区间进行操作,因此通常需要两个参数,一个是起始元素的迭代器,一个是终止元素的后面一个元素的迭代器;
  • 有的算法返回一个迭代器。如查找算法在容器中查找一个元素,并返回一个指向该元素的迭代器;
  • 算法可以处理容器,也可以处理普通数组。
int main() {
	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 << " ";	// 查找成功,输出3
	}
	p = find(v.begin(), v.end(), 9);
	if (p == v.end()) {
		cout << "查找失败!" << " ";	// 查找失败,输出“查找失败!”
	}
	p = find(v.begin() + 1, v.end() - 2, 1);	// 在区间[2,3)内查找
	if (p != v.end()) {
		cout << *p << " ";	// 查找成功,输出3
	}
	int *pp = find(array, array + 4, 20);	// 对数组的查找
	cout << *pp << " ";	// 查找成功,输出20
	return 0;
}

6. STL中的大小比较

  • 关联容器内部的元素是从小到大排序的;
  • 有些算法要求其操作的区间是从小到大排序的,称为有序区间算法,如二分查找等;
  • 有些算法会对区间进行从小到大排序,称为排序算法,如排序等;
  • 还有一些算法会用到的概念。使用 S T L {\rm STL} STL时,在缺省的情况下,以下说法等价:x比y小表达式x<y为真y比x大
  • 有时,x和y相等等价于x==y为真,如顺序查找;
  • 有时,x和y相等等价于x<y和y<x同时为假,如二分查找。
class A {
	int v;
public:
	A (int n): v(n) {}	// 构造函数
	bool operator<(const A& a2) const {	// 重载小于符号,任何A对象不会存在一个小于另外一个的情况
		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));	// 折半查找,返回1。这里用到的是x<y和x>y均不成立,则判断x==y
	return 0;
}

7. 函数对象

函数对象的定义是:如果一个类重载了圆括号(),则该类的对象就成为函数对象。如:

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

示例程序:

#include<vector>
#include<iostream>
#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) {
		T v = value;
		// 计算value的power次方
		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);
	// 1 2 3 4 5 6 7 8 9 10
	PrintInterval(v.begin(), v.end());
	// v中的所有元素的平方和:385
	int a = accumulate(v.begin(), v.end(), 0, sumSquares);
	cout << a << endl;
	// v中的所有元素的立方和:3025
	int b = accumulate(v.begin(), v.end(), 0, SumPowers<int>(3));
	cout << b << endl;
	// v中的所有元素的四次方和:25333
	int c = accumulate(v.begin(), v.end(), 0, SumPowers<int>(4));
	cout << c << endl;
	return 0;
}

我们以第一条调用accumulate函数为例说明函数对象的作用,其他两条语句类似。

accumulate(v.begin(), v.end(), 0, sumSquares)

实例化出:

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操作即为我们传入的参数sumSquares,然后在循环中完成相关操作。此外, S T L {\rm STL} STL还给我们提供了专门用于生成函数对象的函数对象类模板,我们可以使用这些模板来生成函数对象,如equal_togreaterless等等,均包含在头文件#include<functional>中。如:

template<class T> struct greate:public binary_function<T, T, bool> {
	bool operator()(const T& x, const T& y) const {
		return x > y;
	}
};

对于greater的应用,在list中存在两个排序函数sort

  • void sort();list中的元素按照小于号规定的比较方法升序排列;
  • template<class Compare> void sort(Compare op);list中的元素按照指定的比较方法升序排列。

示例程序:

#include<list>
#include<iostream>

using namespace std;

class A {
public:
	bool operator()(const int& c1, const int& c2){
		// 两个数中个位较大者更大
		return (c1 % 10) < (c2 % 10);
	}
};
template<class T> void Print(T first, T last) {
	// 输出[first,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(A());
	// 21 2 3 14 5
	Print(lst.begin(), lst.end());
	// 排序函数使用函数对象作为参数,使用默认排序规则
	lst.sort(greater<int>());
	// 21 14 5 3 2
	Print(lst.begin(), lst.end());
	return 0;
}

总结

S T L {\rm STL} STL C + + {\rm C++} C中的重要组成,本文介绍其基本用法,比使用几个示例辅以解析。总的来说, S T L {\rm STL} STL为我们提供了一套快速实现功能的机制,如果想要更多地了解其机制及实现策略,可以阅读其源码


参考

  1. 北京大学公开课:程序设计与算法(三)C++面向对象程序设计.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值