【Essential C++学习笔记】第三章 泛型编程风格

文章目录

第三章 泛型编程风格

C++标准模板库 (STL)主要由两种组件构成:

  • 容器(container):vector, list,set, map等类
  • 泛型算法(generic algorithm):主要是操作容器,包括 find() ,sort(),replacet() ,merge()等等。

容器(container):

  • 序列式容器:vector、list。我们在序列式容器身上主要进行所谓的迭代( iterate)操作
  • 关联式容器:map、set。可以快速寻找容器中的元素值

关于STL,可以主要查阅下文进行详细了解:
https://blog.csdn.net/qq_50285142/article/details/114026148

3.1 指针的算术运算

/*********************************************************************
    程序名:ch3.1
    版权:
    作者:
    日期: 2023-08-10 10:19
    说明:给定一个存储整数的 vector,以及一个整数值.
	如果此值存在于vector 内,我们必须返回一个指针指向该值;
	反之则返回O,表示此值并不在vector内。
*********************************************************************/
#include <iostream>
#include <vector>

using namespace std;
int *find_elem(vector<int> &vec, int value);
void display_elems(vector<int> vec, ostream &os = cout);

int main() {
	vector<int> vec{1, 2, 3, 4, 5};
	int *p = find_elem(vec, 3);
	cout << *p << endl;
	*p = 8;
	display_elems(vec);
	return 0;
}

int *find_elem(vector<int> &vec, int value) {
	for (int i = 0; i < vec.size(); ++i) {
		if (vec[i] == value) {
			return &vec[i];
		}
	}
	return 0;
};

void display_elems(vector<int> vec, ostream &os) {
	os << "\nPentagonal Numeric Series\n\t";
	for (int ix = 0; ix < vec.size(); ++ix)
		os << vec[ix] << ' ';
	os << endl;
};
/*********************************************************************
    程序名:ch3.1.2
    版权:
    作者:
    日期: 2023-08-10 11:15
    说明:基于ch3.1,想办法让这个函数不仅可以处理整数,
	也可以处理任何型别——前提是该型别定义有 equality (相等)运算符。
*********************************************************************/
#include <iostream>
#include <vector>

using namespace std;

template<typename T1, typename T2>

T1 *find_elem(vector<T1> &vec, T2 value) {
	for (int i = 0; i < vec.size(); ++i) {
		if (vec[i] == value) {
			return &vec[i];
		}
	}
	return 0;
};

template<typename T3>

void display_elems(vector<T3> vec, ostream &os = cout) {
	os << "\nPentagonal Numeric Series\n\t";
	for (int ix = 0; ix < vec.size(); ++ix)
		os << vec[ix] << ' ';
	os << "\n\n" << endl;
};


int main() {
	vector<int> ivec{1, 2, 3, 4, 5};
	cout << "int :" << endl;
	int *p1 = find_elem(ivec, 3);
	cout << *p1 << endl;
	*p1 = 8;
	display_elems(ivec);


	cout << "float :" << endl;
	vector<float> fvec{ 2.5, 24.8, 18.7, 4.1, 23.9 };

	float *p2 = find_elem(fvec, 2.5);
	cout << *p2 << endl;
	*p2 = 3.1;
	display_elems(fvec);

	cout << "string :" << endl;
	vector<string> svec{ "we", "were", "her", "pride", "of", "ten" };

	string *p3 = find_elem(svec, "her");
	cout << *p3 << endl;
	*p3 = "majin";
	display_elems(svec);
	return 0;
}

template参数不明确:

在定义**find_elem()**时,如果只定义一个template会出现报错:

template<typename T1>
T1 *find_elem(vector<T1> &vec, T1 value) {
// ...
}

抛出问题:

以上都是将vector整体当参数传入find_elem(),如果数据结构是array,并且不考虑函数重载的实现方式,该怎么做?

(1)将array的元素传人find_elem(),而非指明该array

/*********************************************************************
    程序名:ch3.1.3
    版权:
    作者:
    日期: 2023-08-10 11:56
    说明:array下标的相关操作
*********************************************************************/
#include <iostream>
using namespace std;

template<typename T1>

T1 *find_elem(T1 *array, int size, T1 &value) {
	if (!array || size < 1) {
		return 0;
	}
	for (int i = 0; i < size; ++i) {
		if (array[i] == value) {
			return &array[i]; // 返回的是指针,所以需要&
		}
	}
	return 0;
}

int main() {
	int iarray[] = { 12, 70, 2, 169, 1, 5, 29 };
	int value = 12;
	int *p = find_elem(iarray, 6, value);
	cout << *p << endl;

	return 0;
}

因为事实上所谓下标操作就是将array 的起始地址加上索引值,产生出某个元素的地址、然后该地址再被提领(dereference)以返回元素值.

[ ] : subscript(下标):下标操作就是将array 的起始地址加上索引值,产生出某个元素的地址。然后该地址再被提领 ***** 以返回元素值.例如:

array  =  &array =  array 的起始地址
//返回array的第3个元素:
array[2]  =  *(array+2)

如何将array的声明从参数表中完全移除?

/*********************************************************************
    程序名: ch3.1.4
    版权:
    作者:
    日期: 2023-08-10 14:53
    说明:我们使用第二个指针来取代参数size。
	此指针扮演哨兵的角色。
	这个版本可以让我们将 array的声明从参数表中完全移除:
*********************************************************************/
#include <iostream>
using namespace std;

template<typename T1>

T1 *find_elem(T1 *first, T1 *last, T1 &value) {
	if (!first || !last) {
		return 0;
	}
	//当first不等于last时,就把value拿来和 first所指的元素比较
	//如果两者相等,便返回first,否则将first累加1,令它指向下一个元素
	for (; first != last; ++first) {  //这里是用地址进行比较、自增
		if (*first == value) {
			return first; // 返回的是指针,所以需要&
		}
	}
	return 0;
}

int main() {
	int ia[] = { 12, 70, 2, 169, 1, 5, 29 };
	int *pi = find_elem(ia, ia + 8, ia[3]);
	cout << *pi << endl;

	string sa[] = { "we", "were", "her", "pride", "of", "ten"  };
	string *ps = find_elem(sa, sa + 6, sa[3]);
	cout << *ps << endl;
	return 0;
}

(2)将vector的元素传人find_elem(),而不指明该vector。

取vector的第一个元素、最后一个元素的两种实现方法:

	cout << "int :" << ivec[0] << endl;
	cout << "int :" << &ivec.front() << endl;// .front返回 v 中第一个元素的引用
	cout << "int :" << ivec[ivec.size()-1] << endl;
	cout << "int :" << &ivec.back() << endl; // .back() :返回 v 中最后一个元素的引用

具体实现方法

/*********************************************************************
    程序名: ch3.1.5
    版权:
    作者:
    日期: 2023-08-10 15:10
    说明: 将vector的元素传人**find_elem()**,而不指明该vector。
*********************************************************************/
#include <iostream>
#include <vector>
using namespace std;

template<typename T1>

// 取用第一个元素的地址
inline T1 *begin(vector<T1> &vec) {
	return vec.empty() ? 0 : &vec[0];
}

template<typename T2>

// 取用最后一个元素的地址
inline T2 *end(vector<T2> &vec) {
	return vec.empty() ? 0 : &vec[vec.size()] - 1;
}

template<typename T3>

T3 *find_elem(T3 *first, T3 *last, T3 &value) {
	if (!first || !last) {
		return 0;
	}
	//当first不等于last时,就把value拿来和 first所指的元素比较
	//如果两者相等,便返回first,否则将first累加1,令它指向下一个元素
	for (; first != last; ++first) {  //这里是用地址进行比较、自增
		if (*first == value) {
			return first; // 返回的是指针,所以需要&
		}
	}
	return 0;
}

int main() {
	vector<int> ivec{ 12, 70, 2, 169, 1, 5, 29};
	int *pi = find_elem(begin(ivec), end(ivec) + 8, ivec[6]);
	cout << *pi << endl;
	return 0;
}

(3)将list的元素传人find_elem(),而不指明该list。

list 也是一个容器,不同的是,list 的元素以一组指针相互链接 (linked)前向 (forward)指针用来寻址下一个(next)元素,回向 (backward)指针用来寻址上一个(preceding)元素
因此,指针的算术运算并不适用于 list,因为指针的算术运算必须首先假设所有元都存储在连续空间里,然后才能根据当前的指针,加上元素大小之后,指向下一个元素

解决这个问题的办法是,在底层指针的行为之上提供一层抽象化机制,取代程序原本的**“指针直接操作”**方式,我们把底层指针的处理通通置于此抽象层中,让用户不需直接面对指针的操作,这个技巧使得我们只需提供一份 find()函数,便可以处理标准程序所提供的所有容器类,3.2节将会详细说明

3.2 了解泛型指针(Iterators)

首次,在说泛型指针之前,先说说特定指针,特定指针,顾名思义,就是有明确的类型的指针,如:int * ,char * 等等。

泛型指针,则为没有数据类型的地址,即 void *。许多库函数中也有此类型的泛型指针,如:malloc( ),memset( )等等。

***为什么学习泛型指针,原因就是*泛型指针可以让我们免去定义一个针对vector的指针、定义一个针对list的指针、针对array的指针,省工作量。

(1)常见的泛型算法:

  • 搜索(search):find(), count(), adjacent_find()(搜寻第一组相邻且值重复的元素)find_if(), count_if(), binary_search(), find_first_of()(搜索某些元素首次出现的位置)
  • 排序(sort)和次序整理(ordering):merge(), partial_sort(), partition()(切割)random_shuffle(), reverse(), rotate(), sort()
  • 复制(copy)、删除(deletion)、替换(substitution):copy(), remove(), remove_if(), replace(), replace_if(), swap(), unique()(去除重复)
  • 关系(relational):equal(), includes(), mismatch()(比较两个序列是否匹配)
  • 生成(generation)与质变(mutation):fill(), for_each(), generate(), transform()
  • 数值(numeric):accmulate(), adjacent_difference()(相邻差)partial_sum(), inner_product()(内积)
  • 集合(set):set_union()(并集),set_difference()(差集)

(2)::作用域运算符 解析

在C ++中,作用域运算符为::。它用于以下目的。

1)当存在具有相同名称的局部变量时,要访问全局变量:
#include <iostream>
using namespace std;
int x = 5;
int main() {
	int x = 10;
	cout << "Value of global x is " << ::x;
	cout << "\nValue of local x is " << x;
	return 0;
}

输出:

全局x的值为0
本地x的值为10

2)在类之外定义函数。
#include <iostream>
using namespace std;

class A {
	public:
		// 仅声明
		void fun();
};

//类外的定义使用
void A::fun() {
	cout << "outside fun() called";
}

int main() {
	A a;
	a.fun();
	return 0;
}
3)访问一个类的静态变量。
/*********************************************************************
    程序名:
    版权:
    作者:
    日期: 2023-07-21 15:53
    说明:
*********************************************************************/

#include <iostream>
using namespace std;

class Test {
		static int x;
	public:
		static int y;
		void func(int x) {
			// 我们可以通关::访问类的静态变量
			// 即使存在一个局部变量
			cout << "Value of static x is " << Test::x;
			cout << "\nValue of local x is " << x;
		}
};

//在c++中,静态成员必须显式定义
int Test::x = 1;
int Test::y = 2;

int main() {
	Test obj;
	int x = 3 ;
	obj.func(x);

	cout << "\nTest::y = " << Test::y;

	return 0;
}
4)如果有多个继承:

如果两个祖先类中存在相同的变量名,则可以使用作用域运算符进行区分。

#include <iostream>
using namespace std;

class A {
	protected:
		int x;
	public:
		A() {
			x = 10;
		};
};

class B {
	protected:
		int x;
	public:
		B() {
			x = 20;
		};
};

class C: public A, public B { // 继承?
	public:
		void fun() {
			cout << "A's x is " << A::x;
			cout << "\nB's x is " << B::x;
		}
};

int main() {
	C c;
	c.fun();
	return 0;
}
5)对于命名空间

如果两个命名空间中都存在一个具有相同名称的类,则可以将名称空间名称与作用域解析运算符一起使用,以引用该类而不会发生任何冲突

#include<iostream> 
int main(){ 
    std::cout << "Hello" << std::endl;
} 
6)在另一个类中引用一个类:

如果另一个类中存在一个类,我们可以使用嵌套类使用作用域运算符来引用嵌套的类

#include <iostream>
using namespace std;

class outside {
	public:
		int x;
		class inside {
			public:
				int x;
				static int y;
				int foo();

		};
};
// 类静态成员变量需要显示定义
int outside::inside::y = 5;

int main() {
	outside A;
	outside::inside B;
	A.x = 10;
	cout << A.x << endl;
	cout << B.y << endl;
}

(3) 拓展find_elem()

/*********************************************************************
    说明:用翻修过的find_elem ()来处理array.vector 和list
*********************************************************************/
#include <iostream>
#include <vector>
#include <list> //这个头文件得写,因为你用到了list,书上没说。。。。

using namespace std;
template<typename elemtype>

void display(const vector<elemtype> &vec, ostream &os = cout) {
	typename vector<elemtype>::const_iterator iter = vec.begin(); //原书中未在vector前加typename,你不加,编译器给你报错。(去掉试试)
	typename vector<elemtype>::const_iterator end_it = vec.end(); //如果是面对const vector,则用const_iterator来进行操作
	//如果面对vector,就用iterator即可
	//若vec是空vector,则iter=end_it,for循环不会被执行
	for (; iter != end_it; ++iter) {
		os << *iter << ' ';
	}
	os << endl;
}

//重新实现find()函数
template<typename IteratorType, typename elemType>IteratorType find(IteratorType first, IteratorType last,const elemType &value) {
	for (; first != last; ++first) {
		if (value == *first) {
			return first;
		}
	}
	return last;//走到这里证明没找到和value一样的元素,直接返回last指针。。。
}

int main() {
	const int asize = 8;
	int ia[asize] = {1, 1, 2, 3, 5, 8, 13, 21};

	int *pia = find_elem(ia, ia + asize, 3);
	if (pia != ia + asize) {//这里对应着return last,在传参的时候last=ia+asize
		cout << "*pia:" << *pia << endl;//走进了if,证明在容器里找到了指定元素
	}

	vector<int> ivec(ia, ia + asize);
	vector<int>::iterator it;  //定义泛型指针
	it = find_elem(ivec.begin(), ivec.end(), 3);
	if (it != ivec.end()) {
		cout << *it << endl;
	}

	list<int> ilist(ia, ia + asize);
	list<int>::iterator iter;  //定义泛型指针
	iter = find_elem(ilist.begin(), ilist.end(), 3);
	if (iter != ilist.end()) {
		cout << *iter << endl;
	}
	return 0;
}

仔细看看这段代码:

void display(const vector<elemtype> &vec, ostream &os = cout) {
	typename vector<elemtype>::const_iterator iter = vec.begin(); //原书中未在vector前加typename,你不加,编译器给你报错。(去掉试试)
	typename vector<elemtype>::const_iterator end_it = vec.end(); //如果是面对const vector,则用const_iterator来进行操作
	//如果面对vector,就用iterator即可
	//若vec是空vector,则iter=end_it,for循环不会被执行
	for (; iter != end_it; ++iter) {
		os << *iter << ' ';
	}
	os << endl;
}

iter被定义为一个iterator,iter指向一个vector,vector元素类型为elemType(任意类型)。iter的初值指向vector对象的首元素。双冒号(::)表示此iterator(泛型指针)是位于elemType类型的vector定义内的嵌套类型

(4)通过泛型指针获取元素值

1) 获取值:

采用一般指针的提领方式,即iter即课提领iter指针来取得iter指针指向的对象的元素值。

2) 获取底部对象方法:

如果想用泛型指针调用底部的string元素(类型的对象)所提供的操作(比如.size(),.length()等)
iter->箭头运算符.

#include <iostream>
#include <vector>
using namespace std;

int main() {
	string str[] = {"hello", "world", "this", "find", "gank", "pink", "that", "when", "how", "cpp"};
	vector<string> cs_vec(str, str + 10);

	vector<string>::const_iterator iter = cs_vec.begin(); //泛型指针定义并初始化为指向string类型vector对象的首元素
	cout << "string value of elemnt:" << *iter; //泛型指针提领操作获得其指向的对象的值
	cout << "(" << iter->size() << "):" << *iter
	     << endl; //相当于cs_vec[0].size()(打个比方),并且cs_vec[0]是个string类型的字符串。
}

3.3 所有容器 的共通操作

  • equality (==)和 nequality (!=) 运算符,返回truefalse.

  • assignment ()运算符,将某个容器复制给另一个容器

  • empty()会在容器无任何元素时返 true,否则返回 false

  • size()容器当前含有元素的数目

  • clear()删除所有元素

  • begin()返回一个 iterator,指向容器的第一个元素

  • end()返回一个 iterator,指向容器的最后一个元亲的下一个位置。

  • 通常我们在容器身上进行的送代操作都是始于 begin()而终于 end()所有容器都提供insert()用以安插元素,以及提供 erase() 用以删除元素

    • insert()将单一或某个范围内的安插到容器内
    • 将容器内的单一元案或某个范围内的元素删除erase()
void comp(vector<int> &v1, vector<int> &v2) {
	if (v1 == v2) {
		return;
	}
	if (v1.empty() || v2.empty() ) {
		return;
	}

	vector<int> temp;

	//将较大的 vector 值给 temp
	temp = v1.size() > v2.size() ? v1 : v2;
}

3.4 使用序列式容器(vector、 list、dequa、set、map)

(1) STL 各容器简单介绍

参考文档

特征优点缺点场景头文件
vector内存空间是连续的,可拓展的数组(动态数组)全端查询效率高中部的插入、删除效率低数列型,对象简单,变化较小,并且频繁随机访问#include
deque一段一段的定量连续空间构成,按页或块来分配存储器的,每页包含固定数目的元素查询效率高,两端操作不适合中间插入删除操作;占用内存多适用于既要频繁随机存取,又要关心两端数据的插入与删除的场景。#include
list双向链表,每个元素都是放在一块内存中,他的内存空间可以是不连续的,通过指针来进行数据的访问。访问开始和最后两个元素最快,其他元素的访问时间一样内存不连续,动态操作,可在任意位置插入或删除且效率高。不支持随机访问[]。查询效率低适用于经常进行插入和删除操作并且不经常随机访问的场景。#include
set红黑树实现,其内部元素依据有序和不重复原则删除效率高,不需要做内存拷贝和内存移动。每次插入值的时候,都需要调整红黑树,效率有一定影响。适用于经常查找一个元素是否在某群集中且需要排序的场景。#include
map红黑树实现,元素都是 Key - value 所形成的一个对组,每个元素有一个键,每一个键只能出现一次元素查找,且能把一个值映射成另一个值,可以创建字典。每次插入值的时候,都需要调整红黑树,效率有一定影响。适用于需要存储一个数据字典,并要求方便地根据key找value的场景。#include

使用那个容器?

1、如果需要高效的随机存取,不在乎插入和删除的效率,使用 vector。

2、如果需要大量的插入和删除元素,不关心随机存取的效率,使用 list。

3、如果需要随机存取,并且关心两端数据的插入和删除效率,使用 deque。

4、如果打算存储数据字典,并且要求方便地根据 key 找到 value,一对一的情况使用 map,一对多的情况使用 multimap。

5、如果打算查找一个元素是否存在于某集合中,唯一存在的情况使用 set,不唯一存在的情况使用 multiset。

各容器的时间复杂度分析

  • vector 在头部和中间位置插入和删除的时间复杂度为 O(N),在尾部插入和删除的时间复杂度为 O(1),查找的时间复杂度为 O(1)
  • deque 在中间位置插入和删除的时间复杂度为 O(N),在头部和尾部插入和删除的时间复杂度为 O(1),查找的时间复杂度为 O(1)
  • list 在任意位置插入和删除的时间复杂度都为 O(1),查找的时间复杂度为 O(N)
  • setmap 都是通过红黑树实现,因此插入、删除和查找操作的时间复杂度都是 O(log N)
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <set>
#include <map>
using namespace std;

void display_vector() {
	vector<int> vecTemp;
	for (int i = 0; i < 6; i++)
		vecTemp.push_back(i);

	cout << "vector display : \n";

	for (int i = 0; i < vecTemp.size(); i++)
		cout << vecTemp[i] << " "; // 输出:0 1 2 3 4 5
	cout << endl;
}

void display_deque() {
	deque<float> dequeTemp;
	for (int i = 0; i < 6; i++)
		dequeTemp.push_back(i);

	cout << "deque display : \n";

	for (int i = 0; i < dequeTemp.size(); i++)
		cout << dequeTemp[i] << " "; // 输出:0 1 2 3 4 5
	cout << endl;
}

void display_list() {
	list<char> listTemp;

	for (char c = 'a'; c <= 'z'; ++c)
		listTemp.push_back(c);

	cout << "list display : \n";
	while (!listTemp.empty()) {
		cout << listTemp.front() << " ";
		listTemp.pop_front();
	}
	cout << endl;
}

void display_set() {
	set<int> setTemp;

	setTemp.insert(3);
	setTemp.insert(1);
	setTemp.insert(2);
	setTemp.insert(1);

	cout << "set display : \n";
	set<int>::iterator it;
	for (it = setTemp.begin(); it != setTemp.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

void display_map() {
	map<int, string> mapTemp;

	mapTemp.insert({ 5, "张三" });
	mapTemp.insert({ 3, "李四"});
	mapTemp.insert({ 4, "隔壁老王" });

	map<int, string>::iterator it;
	for (it = mapTemp.begin(); it != mapTemp.end(); it++) {
		printf("学号:%d 姓名:%s\n", (*it).first, (*it).second.c_str());
	};
	cout << endl;
}

int main() {
	display_vector();
	display_deque();
	display_list();
	display_set();
	display_map();
	return 0;
}

(2)定义容器对象

	list<string> slist;  //产生空容器
	list<int> ilist(32); // 产生特定大小的容器
	
    vector<int> ivec(10, -1);//产生特定大小的容器并为每个元素指定初值(10个-1)

	int ia[3] = {
		1, 2, 3
	};
	vector<int> ivec(ia, ia + 3); // 通过array产生容器

	list<string> slist;
	list<string> slist2(slsit);// 复制容器产生心容器

3.5 使用泛型算法

使用泛型算法必须包含头文件#include <algorithm>

vector为例子,构建一个函数is_elem,若给定的值在vector中,返回true,反之返回false

有以下4中可能被采用的泛型算法

  • find()用于搜索无序集合中是否存在某值,搜索范围由iterator[first,last)标出。若找到就返回一个iterator指向该值,否则返回一个iterator指向last,属于linear search。
  • binary_search()用于搜索有序集合,属于binary search,效率比find()高。
  • count()返回数值相符的元素数目。
  • search()比对某个容器内是否存在某个子序列,存在则返回一个iterator指向子序列起始处,否则指向容器末尾。如给定数序[1,3,5,7,2,9],需要搜寻[5,7,2],则返回一个iter,指向下标[2]

由于我们的 Fibonacci vector必定以增顺序储其值,因此,binary_search()是我们的佳选择

  • 泛型算法max_element()可以返回一对iterator所表示的数列范围内最大的元素值。
  • copy()接受两个iterator标识出复制范围,第三个iterator指向复制行为的目的地(也是个容器)的第一个元素,后续元素会被依次填入。

3.6 如何设计一个泛型算法

1)vector取小于10的元素

开始我写了一个函数,它可以找出 vector 内小于 10 的所有元素。然而函数过于死板,没有弹性。

vector less_than_10(const vector<int> &vec) {
	vector<int> nvec;
	for (int ix = 0; ix < vec.size(); ++ix) {
		if (vec[ix] < 10) {
			nvec.push_back(vec[ix]);
		}
	}
	return nvec;
}
2)vector取小于某值的元素

接下来我为函数加上一个数值参数,让用户得以指定某个数值,以此和 vector 中的元素做比较

vector<int> less_than(const vector<int> &vec,int less_than_val)
3)vector取"指定比较方式"某值 的元素

后来,我又加上一个新参数:一个函数指针,让用户得以指定比较方式(大于小于或者等于)。
实现方法:加第三个参数 pred,用它来指定一个函数指针,其参数表有两个整数,返回值为 bool.

#include <iostream>
#include <vector>
using namespace std;

void display ( vector<int> vec, ostream &os = cout) {
	for (int i = 0; i < vec.size(); ++i) {
		os << vec[i] << ' ';
	}
	cout << endl;
}
bool less_than(int v1, int v2) {  // <
	return v1 < v2 ? true : false;
}

bool greater_than(int v1, int v2) { // >
	return v1 > v2 ? true : false;
}

vector<int> filter(const vector<int> &vec, int filter_value, bool (*pred)(int, int)) {
	vector<int> nvec;
	for (int ix  = 0; ix < vec.size(); ++ix) {
		// 调用 pred 所指函数
		// 比较 vec[ix]  filter_value
		if (pred(vec[ix], filter_value)) {
			nvec.push_back((vec[ix]));
		}
	}
	return nvec;
}

int main() {
	int ia[8] = {
		11, 2, 3, 34, 5, 99, 10, 8,
	};
	vector<int> ivec(ia, ia + 8); // 通过array产生容器

	vector<int> rvec = filter(ivec, 10, less_than);
	display(rvec);
	return 0;
}
4)使用泛型算法find()找相同的元素

find(first,last,val) : [first, last] 用于指定该函数的查找范围;val 为要查找的目标元素。
find() 函数查找成功时,其指向的是在 [first, last] 区域内查找到的第一个目标元素;如果查找失败,则该迭代器的指向和 last 相同。

#include <iostream>
#include <vector>
#include <algorithm>

// 找vector含有相同数的数量
int count_occurs(const vector<int> &vec, int val) {
	vector<int>::const_iterator iter = vec.begin(); // 定义一个泛型指针iter,指向vector的第一个元素
	int occurs_count = 0;
	while ((iter = find(iter, vec.end(), val)) != vec.end()) {
		// 找到了
		++ occurs_count;
		++iter;
	};
	return occurs_count;
}

int main() {
	int ia[8] = {
		11, 2, 3, 34, 5, 10, 10, 8,
	};
	vector<int> ivec(ia, ia + 8); // 通过array产生容器

	int count = count_occurs(ivec, 10);
	cout << count << endl;
	return 0;
}
5)引入function object的概念,得以将某组行为传给函数,比函数指针效率更高

必须含入头文件:#include<functional>

然后,我引人function object 的概念,使我们得以将某组行为传给函数,此法比函数指针的做法效率更高。我也带领各位简短的了解标准程序库提供的 function obiects
所谓function objects,是某种 class 的实体对象,这类 class function call 运算符进行了重载操作,如此一来,可使 function obiect 被当成一般函数来使用

  • 6个算术运算

    • plus <T> = return x + y
    • minus <T> = return x - y
    • negate <T> = return - x;
    • multiplies <T> = return x * y
    • divides <T> = return x / y;
    • modulus <T> = return x % y
  • 6个关系

    • less <T> = return x < y
    • less_equal <T> = return x <= y
    • greater <T> = return x > y
    • greater_equal <T> = return x > = y
    • equal_to <T> = return x == y
    • not_equal_to <T> = return x! = y
  • 3个逻辑运算

    • logical_and <T> = return x && y
    • logical_or <T> = return x || y
    • logical_not <T> = return ! x

举个例子: 对vector进行降序

#include <iostream>
#include <vector>
#include <functional>// 使用greater必须含入
#include <algorithm>  // 使用sort必须含入
using namespace std;

void display ( vector<int> vec, ostream &os = cout) {
	for (int i = 0; i < vec.size(); ++i) {
		os << vec[i] << ' ';
	}
	cout << endl;
}

int main() {

	int ia[8] = {
		11, 2, 3, 34, 5, 10, 10, 8,
	};
	vector<int> ivec(ia, ia + 8); // 通过array产生容器
	sort(ivec.begin(), ivec.end(), greater<int>()); 
	display(ivec);
}
bind1st、bind2nd

function object adapter会对function object进行修改操作。绑定适配器(binder adapter)会将function object的参数绑定到某个特定值,使二元函数对象转化为一元。bind1st将指定值绑定到第一操作数,bind2nd则绑定在第二操作数上。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void display ( vector<int> vec, ostream &os = cout) {
	for (int i = 0; i < vec.size(); ++i) {
		os << vec[i] << ' ';
	}
	cout << endl;
}

// 使用适配器bind2nd()
// find_if() : 前两个参数与find()一致,最后一个参数是传入能返回true or false 的适配器
vector<int> filter_bind(const vector<int> &vec, int val) {
	vector<int> nvec;
	vector<int>::const_iterator iter = vec.begin();
	// bind2nd( less<int>, val 1;
	//会把 va1绑定于 lesg<int>的第二个参数身上
	//于是,less<int> 会将每个元素拿来和 val比较
	while ((iter = find_if(iter, vec.end(), bind2nd(less<int>(), val))) != vec.end()) {
		nvec.push_back(*iter);
		iter++;
	}
	return nvec;
}

int main() {
	int ia[8] = {
		11, 2, 3, 34, 5, 10, 10, 8,
	};
	vector<int> ivec(ia, ia + 8); // 通过array产生容器

	vector<int> rvec = filter_bind(ivec, 9);
	display(rvec);
	return 0;
}

C++ STL bind1st bind2nd bind 的使用

6)以function template的方式重新实现函数。

最后,我将函数以 function template 的方式重新实现。为了支持多种容器,我传人一对 iterators,标示出一组元素范围:为了支持多种元素型别,我将元素型别参数化,也将施用于元素身上的“比较操作”参数化,以便得以同时支持函数指针和 function obiect 两种方式。现在,我们的函数和元素型别无关,也和比较操作无关,更和容器型别无关。简单地说,我们已经将最初的函数转化为一个泛型算法

7)完整代码与注释
/*********************************************************************
    说明:目的:找出小于/大于某指定值的元素的值。
*********************************************************************/
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

//找出比某特定元素大或者小的元素扔进新的容器的函数泛型化,此(模板)函数支持不同的容器,不同的元素类型,支持函数指针和function object两种方式(比较操作参数化)
//在容器的[first,last)范围(first,last均为泛型指针)中,找出比某特定元素大或者小的元素扔进新的容器,扔进新容器的什么位置由at这个泛型指针决定。
//特定元素是val,类型自拟,然后你可以传入function object或者函数指针来决定比较操作,是大于特定元素的扔进新容器还是小于特定元素的扔进新容器
template<typename T1, typename T2, typename T3, typename comp>

T1 filter(T1 first, T1 last, T2 at, const T3 &ElemType, comp pred) {
	//find_if为泛型算法,需要algorithm头文件支持,它会返回一个泛型指针给first
	//传入的参数一和二是标定查找范围,参数三是一个函数指针或者function object也行。bind2nd也需要functional头文件支持,目的是把第二个参数绑定(固定)
	while ((first = find_if(first, last, bind2nd(pred, ElemType))) != last) {
		cout << "found value:" << *first << endl;
		//找到了,肯定要复制给新容器,就让at指向接收复制的元素的容器的某个位置(一般是首个元素的位置),
		//然后解除引用让找到的元素赋值给at指向的元素(或者说把找到的元素复制到at指向的位置上),
		//然后,复制成功后at往下挪一个,以便接收后续复制过来的元素,first也往后挪一个,
		//是要缩小范围去寻找是否还有满足规则的元素,并扔给at指向的位置,也是防止某个元素被访问2次以减慢效率
		*at++ = *first++; // 先赋值后自加
	}
	return at;//最后返回at指向的位置的地址即可
}

int main() {

	const int elem_size = 8;
	int ia[elem_size] = {12, 8, 43, 0, 6, 21, 3, 7};
	int ia2[elem_size];
	cout << "filtering integer array for values less than 8\n";
	filter(ia, ia + elem_size, ia2, elem_size,
	       less<int>()); //对应上面的filter函数定义来看,这里at就指向了存储过滤好的元素的array的首地址(首个位置)
	//注意function object的类型是否和vector、array所匹配。function object的()不要少!!!!!

	vector<int>ivec(ia, ia + elem_size);
	vector<int>ivec2(elem_size);
	cout << "filtering integer vector for values greater than 8\n";
	filter(ivec.begin(), ivec.end(), ivec2.begin(), elem_size,
	       greater<int>()); //这里at就指向了存储过滤好的元素的vector的首地址(首个位置),注意function object的类型
	//是否和vector、array所匹配。vector、list等容器,不要和array混为一谈,划定查找范围时,.begin(),.end()等不要写成vector的名字及名字+容器容量!!!!

	return 0;
}

3.7 使用Map

map(#include <map>)被定义为一对(pair)数值,key通常是一个字符串,扮演索引角色,另一个数值是value

map对象有一个名为first的member,对应于key,另有一个名为second的member对应于value。

1)定义map,给初值,动态输入
    //对文内每个字的出现次数加以分析的程序
	//1 直接赋值
	map<string, int> words;
	words["vermeer"] = 1;
	words["majin"] = 2;
	//2 动态输入
	string tword;
	while (cin >> tword)
		words[tword]++;
2)查询map
  1. words["majin"],这种方式有一个缺点:如果我们用来索引的那个 key 并不存在于 map 内,则那个 key 会自动被加入 map 中,而其相应的 value 会被设为所属型别默认值。

  2. words.find("majin"),如果 key 已置于其中,find()会返回一个 iterator,指向 key/vaue 形成的一个 pair 。反之则返 end()

    //对文内每个字的出现次数加以分析的程序
    #include <iostream>
    #include <map>
    using namespace std;
    int main() {
    	map<string, int> words;
    	words["vermeer"] = 1;
    	words["majin"] = 2;
    	int count = 0;
    	map<string, int>::iterator it;
    	it = words.find("majin");
    	if (it != words.end()) {
    		count = it->second;
    	}
    	cout << count;
    	return 0;
    }
    
  3. map.count()会返某特定项在 map 内的个数

    //对文内每个字的出现次数加以分析的程序
    #include <iostream>
    #include <map>
    using namespace std;
    
    int main() {
    	map<string, int> words;
    	words["vermeer"] = 1;
    	words["majin"] = 2;
    	int count = 0;
    	if (words.count("majin"))
    		count = words["majin"];
    	cout << count;
    	return 0;
    }
    

3.8 使用set

set(#include <set>)由一群key组合而成。如果想知道某值是否存在于某个集合内,可以用set。任何一个key值在set内也最多只会有一份。

  • 默认情形下,set元素皆依据其所属类型默认的less_than运算符进行排列。
  • 若某值存在某集合内,第一次出现吸收进set,第二次及以后出现就不吸收进来。
1)定义set,给初值
	set<string> word_ex; // 空set

	int ia[10] = {1,3,5,8,5,3,1,5,8,1};
	vector<int> vec(ia, ia+10);
	set<int> iset(vec.begin(), vec.end());//{1,3,5,8} 
2)set的常用方法
  • insert()  插入set容器
  • count() 查找set中某个某个键值出现的次数
  • begin()  返回set容器的第一个元素
  • end()   返回set容器的最后一个元素
  • clear()   删除set容器中的所有的元素
  • empty()    ,判断set容器是否为空
  • max_size()   ,返回set容器可能包含的元素最大个数
  • size()      ,返回当前set容器中的元素个数
int ia[10]={1,3,5,8,5,3,1,5,8,1};
vector<int>vec(ia,ia+10);
set<int>iset(vec.begin(),vec.end());
iset.insert(ival);//给set加入单一元素,用单参数的insert():
iset.insert(vec.begin(),vec.end());//给set加入多个元素,用双参数的insert():
3)set 的迭代
set<int>::iterator it=iset.begin();
for(;it!=iset.end();++it)
{
	cout<<*it<<' ';
}
cout<<end;

3.9 如何使用Iterator Inserter

(当函数是将元素赋值到某个已存在的容器上时,程序员要考虑目的端容器不够大的问题,所以)标准库提供了insertion adapter以避免使用容器的赋值运算符#include <iterator>

3.6中的filter()、泛型算法如copy(),每个算法都接受一个 iterator标示出复制的起始位置每当复制一个元索时,其值会被赋值,iterator 则会递增至下个位置,我们必须保证在每一次复制操作中,目的端容器都具有够大的容量以存储这些被值进来的元素

Iterator Inserter就是为了不用再规定固定大小的目标(接收复制元素的)容器

  • back_inserter(),以容器的push_back()来执行插入(复制)操作,适合vector,传入b_i的参数为(目标)容器本身。

    	int ia[8] = {12, 8, 43, 0, 6, 21, 3, 7};
    	vector <int> ivec(ia, ia + 8);
    	vector<int>result_vec;
    	unique_copy(ivec.begin(), ivec.end(), back_inserter(result_vec));// 把ivec这个容器的元素从首到尾依次插进result_vec这个容器里
    	display(result_vec);
    	return 0; 
    
  • inserter(),以容器的insert()函数执行插入(复制)操作,inserter函数接收两个参数,一个(目标)容器,一个指向(目标)容器内的插入操作的起点的泛型指针

	int ia[8] = {12, 8, 43, 0, 6, 21, 3, 7};
	vector <int> svec(ia, ia + 8);
	vector<int>svec_res;
	unique_copy(svec.begin(), svec.end(), inserter(svec_res, svec_res.begin()));
	return 0;
  • front_inserter(),以容器的push_front()函数执行插入(复制)操作。适用于list和deque

    list<int>ilist_clone;
    copy(ilist.begin(),ilist.end(),front_inserter(ilist_clone));
    //front_inserter()只接受一个参数,即目标容器
    

如3.6节,ivec2定义时不直接设定大小,在赋值操作*at++ = *first++时会报错,直接设定大小,又容易产生内存泄漏。得到的ivec2 = {12,43,21,0,0,0,0,0}

但是一且将 ivec2 传给insertion adapte,元素的赋值操作 即被替换为安插操作,如果只在 vector 的末端安插元素,效率会比较高,因此我们选用 back inserter.

#include <iterator>

...
    
filter(ivec.begin(), ivec.end(), back_inserter(ivec2), elem_size,
	       greater<int>())

3.10 使用iostream Iterator

标准库定义了供输入输出使用的iostream iterator类,称为istream_iteratorostream_iterator,分别支持单一类型的元素读取和写入(#include <iterator>)。

如果你想从标准输入设备读取数据,并可能存在某个地方(比如容器里),然后操作一番数据(或容器),然后再输出(写回)到标准输出设备中,那么你需要iostream Iterator

1)标准库输入输出:

输入是istream_iterator,输出是ostream_iterator

// 输入:
//first
istream_iterator<string> is(cin);//绑至标准输入设备
//last标示要读取的最后一个元素的下一个位置
istream_iterator<string> eof;//只要定义istream_iterator时不为其指定istream对象,它便代表了end-of-file

// 输出:
//绑至标准输出设备
//第二个参数用来表示各个元素被输出时的分隔符,
ostream_iterator<string> os(cout," "); 

//用法举例: copy将存储在text中的每个元素一一写入os所标示的ostream上,每个元素之间一个空格。
copy(is,eof,back_inserter(text));//采用insertion adapter里的back_inserter()函数更有效率
copy(text.begin(),text.end(),os);
2)文件中读写
...
#include <fstream>
int main()
{
	ifstream in_file("input_file.txt");
	ofstream out_file("output_file.txt");
	
	if(!in_file || !out_file)
	{
		cerr << "!!unable to open the necessary files.\n";
		return -1;
	}
	
	//将 istream_iterator 绑定到 ifstream 对象 
	istream_iterator<string> is(in_file);
	istream_iterator<string> eof;
	//将 ostream_iterator 绑定到 ofstream 对象 
	ostream_iterator<string> os(out_file, " ");
	//...
}
3)完整示例程序
#include <iostream>
#include <iterator>
#include <string>
#include <algorithm>
#include <vector>
#include <fstream>
using namespace std;

//标准库输入输出:屏幕输入屏幕输出实例(ctrl+z结束输入)
void test_iostream() {
	istream_iterator<string> is(cin);
	istream_iterator<string> eof;

	vector<string> text;
	copy(is, eof, back_inserter(text));

	sort(text.begin(), text.end());//给text里的元素排个序,(string容器按第一个元素的ASCII码排序)
	ostream_iterator<string> os(cout, " ");
	copy(text.begin(), text.end(), os);
}

//文件中读写:先读入文件里的数据给一个容器(这里为string型),然后给容器里的元素排序,然后写入到另一个文件中。
void test_fstream() {
	ifstream in_file("3.10Input.txt");
	ofstream out_file("3.10Output.txt");
	if (!in_file || !out_file) {
		cerr << "!!unable to open the necessary files.\n"; //输出错误信息并清空输出缓存区
		return -1;
	}
	istream_iterator<string>is(in_file);
	istream_iterator<string>eof;
	vector<string> text;

	copy(is, eof, back_inserter(text)); //复制操作,把文件里读取的数据扔给text这个容器
	sort(text.begin(), text.end()); //给text里的元素排个序
	ostream_iterator<string>os(out_file, " "); //定义os这个ostream_iterator并将其绑定至out_file这个文件输出(指针)
	copy(text.begin(), text.end(), os); //复制操作,把text这个容器里的输入写入到文件中,每个写入的元素间有一个空格。
}


int main() {
//	test_iostream();
	test_fstream();
	return 0;
}

要点:

  • 当数组被传给函数或从函数中返回时,仅有第一个元素的地址会被传递。

  • 下标操作[]就是将起始地址加上索引值,产生出某个元素的地址,然后再提领该地址以返回元素值。

  • vectorarray相同,都是以一块连续内存储存所有元素,但vector可以为空,array却不可以。所以操作vector要先确定不为空。

  • typedef机制(见第四章)让我们得以为任何一个类型提供另一个等价名称,通常用来简化那些声明起来十分麻烦的类型

    typedef vector<string> vstring;
    
  • 每个标准容器都有一个begin()操作函数,可返回一个泛型指针iterator,指向第一个元素。end()则指向最后一个元素的下一位置。

  • const_iterator允许我们读取vector元素,但不允许任何写入操作。

  • 所有容器的共通操作:

    • equality(==)和inequality(!=)运算符,返回truefalse
    • assignment(=)运算符,将某个容器复制给另一个容器
    • empty()会在容器无任何元素时返回true,否则返回false
    • size()返回容器内目前持有的元素个数
    • clear()删除所有元素
    • begin()返回一个iterator,指向容器的第一个元素
    • end()返回一个iterator,指向容器的最后一个元素的下一个位置
    • insert()将单一或某个范围内的元素插入容器内
    • erase()将容器内的单一元素或某个范围内的元素删除
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值