C++学习笔记(四)——泛型程序设计

 

目录

一、标准模板库STL

string 类

1.概述

2.赋值

3.连接

4.比较

5.子串与交换

6.寻找string中的字符

7.删除与替换

8.插入

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

10.字符串拷贝

11.字符串流处理

标准模板库STL概述

1.泛型程序设计

2.容器概述:

3.迭代器

4.算法

5.其他概念

vector

1.初始化

2.随机访问迭代器

3.插入、删除元素

deque

1.deque 与 vector 的关系:

list

1.看一个自己编写类放在list容器的例子:

2.插入、删除元素

3.对整个链表的操作

函数对象

1.简介

2.greater 函数对象类模板

set和multiset

map和multimap

容器适配器

算法

二、C++11新特性和C++高级主题

C++11新特性

强制类型转换

异常处理


一、标准模板库STL

  • string 类

1.概述

(1)正确的初始化方法:

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

(2)string 对象的长度用成员函数length()读取,用size()也可以。从效果来讲二者区别不大。
(3)string 支持流读取运算符:string s; cin >> s;
(4)string 支持getline函数:string s; getline(cin, s);

2.赋值

(1)用 = 赋值

  • string s1("cat"), s2; s2 = s1;
  • s1[2] = 'n';

(2)用assign成员函数复制

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

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

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

3.连接

(1)用 + 运算符连接字符串。

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

(2)用成员函数append连接字符串

  • s1.append(s2)   //把 s2 连接到 s1 后面;
  • s2.append(s1, 3, s1.size())   
    //把s1从下标为3开始的s1.size()个字符连在s2后面。如果字符串内没有足够的字符,则复制到字符串最后一个字符。

4.比较

(1)用关系运算符比较string的大小,就是 == , > , >= , < , <= , != ,比较的是string的字典序。
(2)返回值都是bool类型,成立返回true,否则返回false;

5.子串与交换

(1)成员函数substr()返回子串:s2 = s1.substr(4, 5);    //下标4开始的5个字符。
(2)交换swap:s1.swap(s2),把s1和s2换一下。

6.寻找string中的字符

(1)s1.find("lo"):从前往后找"lo"第一次出现的位置,如果找到,返回s1中 'l' 的下标,找不到返回 string::npos(string中定义的静态变量)。
(2)s2.rfind("lo"):从后往前找"lo"第一次出现的位置,如果找到,返回s1中'l'的下标,找不到返回 string::npos。
(3)s1.find_first_of(“abcd"):在s1中从前向后查找 “abcd” 中任何一个字符第一次出现的地方,如果找到,返回找到字母的位置,如果找不到,返回 string::npos。
(4)s1.find_last_of(“abcd"):在s1中查找 “abcd” 中任何一个字符最后一次出现的地方,如果找到,返回找到字母的位置,如果找不到,返回 string::npos。
(5)s1.find_first_not_of(“abcd"):在s1中从前向后查找不在 “abcd” 中的字母第一次出现的地方,如果找到,返回找到字母的位置,如果找不到,返回 string::npos。
(6)s1.find_last_not_of(“abcd"):在s1中从后向前查找不在 “abcd” 中的字母第一次出现的地方,如果找到,返回找到字母的位置,如果找不到,返回 string::npos。

7.删除与替换

(1)s1.erase(5):去掉下标 5 及之后的字符
(2)s1.replace(2,3, “haha"):将s1中下标2 开始的3个字符换成“haha”
(3)s1.replace(2,3, "haha", 1,2):将s1中下标2 开始的3个字符换成“haha” 中下标1开始的2个字符

8.插入

(1)s1.insert(5,s2):将s2插入s1下标5的位置
(2)s1.insert(2,s2,5,3):将s2中下标5开始的3个字符插入s1下标2的位置

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

(1)printf("%s\n", s1.c_str()):s1.c_str() 返回传统的const char * 类型字符串,且该字符串以‘\0’结尾。
(2)const char * p1=s1.data():s1.data() 返回一个char * 类型的字符串,对s1 的修改可能会使p1出错。

10.字符串拷贝

(1)s1.copy(p2,5,0):s1.copy(p2,5,0) 从s1的下标0的字符开始制作一个最长5个字符长度的字符串副本并将其赋值给p2。返回值表明实际复制字符串的长度。

11.字符串流处理

(1)可以从string进行输入输出,我们用 istringstream 和 ostringstream进行字符串上的输入输出,也称为内存输入输出。
(2)istringstream类用于执行C++风格的字符串流的输入操作。ostringstream类用于执行C++风格的字符串流的输出操作。strstream类同时可以支持C++风格的串流的输入输出操作。

#include<iostream>
#include<string>
#include<sstream>
using namespace std;
int main() {
	string S("Input test 123 4.7 A");
	istringstream is(S);
	string s1, s2;
	int a; double b; char c;
	is >> s1 >> s2 >> a >> b >> c;
	
	ostringstream os;
	int d = 10;
	os << "This " << d << "ok" << endl;
	cout << os.str();
	return 0;
}
/*
Output:
This 10ok
*/
  • 标准模板库STL概述

1.泛型程序设计

(1)C++ 语言的核心优势之一就是便于软件的重用。C++中有两个方面体现重用:

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

(2)将一些常用的数据结构(比如链表,数组,二叉树)和算法(比如排序,查找)写成模板,标准模板库 (Standard Template Library) 就是一些常用数据结构和算法的模板的集合。有了STL,不必再写大多的标准数据结构和算法,并且可获得非常高的性能。

2.容器概述:

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

  • 顺序容器:vector, deque,list
  • 关联容器:set, multiset, map, multimap
  • 容器适配器:stack, queue, priority_queue

(2)对象被插入容器中时,被插入的是对象的一个复制品。
(3)顺序容器简介:

  • vector 头文件 <vector>:动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能(大部分情况下是常数时间)。
  • deque 头文件 <deque>:双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成 (但次于vector)。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。
  • list 头文件 <list>:双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。

(4)关联容器简介:

  • 元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;在查找时具有非常好的性能;通常以平衡二叉树方式实现,插入和检索的时间都是 O(log(N))。
  • set/multiset 头文件 <set>:set 即集合。set中不允许相同元素,multiset中允许存在相同的元素。
  • map/multimap 头文件 <map>:map同multimap的不同在于是否允许相同first值的元素。

(5)容器适配器简介:

  • stack :头文件 <stack>
  • queue 头文件 <queue>
  • priority_queue 头文件 <queue>

(6)顺序容器和关联容器中都有的成员函数:

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

(7)顺序容器的常用成员函数:

  • front :返回容器中第一个元素的引用
  • back : 返回容器中最后一个元素的引用
  • push_back : 在容器末尾增加新元素
  • pop_back : 删除容器末尾的元素
  • erase :删除迭代器指向的元素(可能会使该迭代器失效),或删除一个区间,返回被删除元素后面的那个元素的迭代器。

3.迭代器

(1)用于指向顺序容器和关联容器中的元素,用法和指针很类似,有 const 和非 const 两种。可以通过迭代器修改其指向的元素,通过非 const 迭代器还能修改其指向的元素。迭代器上可以执行 ++ 操作, 以使其指向容器中的下一个元素。如果迭代器到达了容器中的最后一个元素的后面,此时再使用它,就会出错,类似于使用NULL或未初始化的指针一样。
(2)定义迭代器:

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

(3)访问一个迭代器指向的元素:*迭代器变量名;
(4)反向迭代器示例(输出:2 1):

#include<iostream>
#include<vector>
using namespace std;
int main() {
	vector<int> v;
	v.push_back(1), v.push_back(2);
	vector<int>::reverse_iterator r;
	for (r = v.rbegin(); r != v.rend(); r++) {
		cout << *r << ' ';
	}
	return 0;
}

(5)双向迭代器操作:

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

(6)随机访问迭代器操作

  • 双向迭代器的所有操作
  • 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
  • p – p1 : p1和p之间的元素个数

(7)容器上支持的迭代器汇总:

  • vector 随机访问
  • deque 随机访问
  • list 双向
  • set/multiset 双向
  • map/multimap 双向
  • stack 不支持迭代器
  • queue 不支持迭代器
  • priority_queue 不支持迭代器

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

4.算法

(1)简介:

  • 算法就是一个个函数模板, 大多数在<algorithm> 中定义
  • STL中提供能在各种容器中通用的算法,比如查找,排序等
  • 算法通过迭代器来操纵容器中的元素。许多算法可以对容器中的一个局部区间进行操作,因此需要两个参数,一个是起始元素的迭代器,一个是终止元素的后面一个元素的迭代器。比如,排序和查找。
  • 有的算法返回一个迭代器。比如 find() 算法,在容器中查找一个元素,并返回一个指向该元素的迭代器
  • 算法可以处理容器,也可以处理普通数组。

(2)算法示例:find() (看,find() 是可以是一个算法,并不一定是成员函数)。

  • template<class InIt, class T>
    InIt find(InIt first, InIt last, const T& val);
  • first 和 last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点[first,last)。区间的起点是位于查找范围之中的,而终点不是。find在[first,last)查找等于val的元素。
  • 用 == 运算符判断相等。
  • 函数返回值是一个迭代器。如果找到,则该迭代器指向被找到的元素。如果找不到,则该迭代器等于last。

5.其他概念

(1)STL中“大”“小” 的概念(使用STL时,在缺省的情况下,以下三个说法等价):

  • x比y小
  • 表达式“x<y”为真
  • y比x大

(2)记一下那些容器要求从小到大排序:

  • 容器内部的元素是从小到大排序的
  • 有些算法要求其操作的区间是从小到大排序的,称为“有序区间算法”。例:binary_search
  • 有些算法会对区间进行从小到大排序,称为“排序算法”。例: sort

(2)STL中“相等”的概念:

  • 有时,“x和y相等”等价于“x==y为真”。例:在未排序的区间上进行的算法,如顺序查找find()
  • 有时,“x和y相等”等价于“x小于y和y小于x同时为假”。例:有序区间算法,如binary_search;关联容器自身的成员函数find。
  • vector

1.初始化

  • 看一个初始化vector的方法:
int a = { 1, 2, 3, 4, 5 };
vector<int> v(a, a + 5);   // 把a中的元素放到v中
  • vector<T> v(int n, T e):v初始化为含有n个e的容器。

2.随机访问迭代器

  • v.begin() - v.end():vector上的迭代器是随机访问迭代器,可以进行相减操作。

3.插入、删除元素

  • v.insert(v<T>::iterator pos, T val):pos指向的位置插入val。
  • v2.insert(v2.begin(),v.begin()+ 1,v.begin()+3):将v的一段插入v2开头。
  • v.erase(v<T>::iterator pos):删除pos所指向的元素。
  • v.erase(v.begin() + 1, v.begin() + 3):删除 v 上的一个区间,即 2,3。
  • deque

1.deque 与 vector 的关系:

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

  • list

1.看一个自己编写类放在list容器的例子:

#include<iostream>
#include<list>
#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 lnlt, class T>
void PrintList(lnlt first, lnlt last, const list<T>& lst) {
	typename list<T>::const_iterator i;  
	//注意,const迭代器可以自增自减,只是不能修改指向的元素。常引用对应const迭代器。
	for (i = first; i != last; i++) {
		cout << *i << ' ';
	}
	cout << '\n';
}
int main() {
	list<A> lst;
	lst.push_back(3), lst.push_back(2), lst.push_back(1);
	cout << "(1) ";
	PrintList(lst.begin(), lst.end(), lst);
	lst.sort();
	cout << "(2) ";
	PrintList(lst.begin(), lst.end(), lst);

	list<A> lst2;
	lst2.push_back(10), lst2.push_back(11);
	list<A>::iterator p1, p2, p3;
	p1 = find(lst.begin(), lst.end(), 1);
	p2 = find(lst2.begin(), lst2.end(), 10);
	p3 = find(lst2.begin(), lst2.end(), 11);

	//将[p2,p3)插入p1之前,并从lst2中删除[p2,p3)
	lst.splice(p1, lst2, p2, p3);
	cout << "(3) ";
	PrintList(lst.begin(), lst.end(), lst);
	return 0;	
}
/*
Output:
(1) 3 2 1
(2) 1 2 3
(3) 10 1 2 3
*/

2.插入、删除元素

  • lst.push_front():在前面插入元素
  • lst.push_back():在后面插入元素
  • splice(iterator p1, lst2, iterator p2, iterator p3):在指定位置前面插入另一链表中的元素,并在另一链表中删除被插入的元素。即在p1所指向元素的前面插入lst2中 [p2, p3) 的元素,并删除 lst2 中 [p2, p3) 的元素。 
  • lst.pop_front():删除前面的元素
  • lst.pop_back():删除后面的元素
  • lst.remove(T a):删除和指定值a相等的所有元素

3.对整个链表的操作

  • lst.sort():排序 ( list 不支持 STL 的算法 sort)
  • lst.unique():删除所有和前一个元素相同的元素(要做到元素不重复,则 unique 之前还需要 sort)
  • lst.merge(lst2):合并两个链表,并清空被合并的那个,即 lst2。
  • lst.reverse():颠倒链表
  • 函数对象

1.简介

(1)若一个类重载了运算符"()",则该类的对象就成为了函数对象。
(2)举个栗子:

#include<iostream>
#include<vector>
#include<numeric>
using namespace std;
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;
		for (int i = 0; i < power - 1; i++) {
			v *= value;
		}
		return total + v;
	}
};
template<class T>
void Print(T first, T last) {
	for (; first != last; first++) cout << *first << ' ';
	cout << endl;
}
int main() {
	int a1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	vector<int> v(a1, a1 + 10);
	Print(v.begin(), v.end());
	//accumulate定义在<numeric>中
	int result = accumulate(v.begin(), v.end(), 0, SumPowers<int>(3));
	cout << result;
	return 0;
}

(3)dev C++提供的accumulate源代码:
注:调用 accumulate 时,和__binary_op对应的实参可以是个函数或函数对象。

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;
}

(4)例:写出自己的MyMax模板

#include<iostream>
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 first, T last, Pred my_less) {
	T tmpMax = first;
	for (; first != last; first++) {
		if (my_less(*tmpMax, *first)) tmpMax = first;
	}
	return tmpMax;
}
int main() {
	int a[] = { 35, 7, 13, 19, 12 };
	cout << *MyMax(a, a + 5, Myless()) << endl;
	cout << *MyMax(a, a + 5, MyCompare) << endl;
	return 0;
}
/*
Output:
19
12
*/

2.greater 函数对象类模板

(1)STL 的<functional> 里还有以下函数对象类模板:

  • equal_to
  • greater
  • less

(2)greater模板长这个样子:

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

 (3)举一个使用greater的例子:

  • list 有两个sort函数,前面例子中看到的是不带参数的sort函数,它将list中的元素按 '<' 规定的比较方法升序排列。
  • list还有另一个sort函数,可以用 op来比较大小,即 op(x,y) 为true则认为x应该排在前面。:
template <class T2>
void sort(T2 op);
#include<iostream>
#include<list>
using namespace std;
template<class T>
void Print(T first, T last) {
	for (; first != last; first++) cout << *first << ' ';
	cout << endl;
}
int main() {
	int a[] = { 5, 21, 14, 2, 3 };
	list<int> lst(a, a + 5);
	lst.sort(greater<int>());
	Print(lst.begin(), lst.end());
	return 0;
}
  • set和multiset

  • map和multimap

  • 容器适配器

  • 算法

二、C++11新特性和C++高级主题

  • C++11新特性

  • 强制类型转换

  • 异常处理

1.用try、catch进行异常处理

(1)一旦抛出异常,程序立刻停止运行,开始执行catch里面的内容。若没有抛出任何异常,则相应所有的catch都不会运行。
(2)注意:try块中定义的局部对象,发生异常时会析构!

#include<iostream>
using namespace std;
int main() {
	double m, n;
	while (cin >> m >> n) {
		try {
			cout << "before dividing." << endl;
			if (n == 0) throw - 1;
			else if (m == 0) throw - 1.0;
			else cout << m / n << endl;
			cout << "after dividing." << endl;
		}
		catch (double d) {
			cout << "catch(double)\n" << d << endl;
		}
		catch (int e) {
			cout << "catch(int)\n" << e << endl;
		}
		cout << "finished" << endl;
	}
	return 0;
}

2.异常的再抛出

(1)如果一个函数在执行的过程中,抛出的异常在本函数内就被catch块捕获并处理了,那么该异常就不会抛给这个函数的调用者(也称“上一层的函数”);如果异常在本函数中没被处理,就会被抛给上一层的函数。

#include<iostream>
#include<string>
using namespace std;
class CExeption {
public:
	string msg;  //messege
	CExeption(string s):msg(s){}
};
double Devide(double x, double y) {
	if (y == 0) throw CExeption("devided by zero");
	cout << "in Devide" << endl;
	return x / y;
}
int CountTax(int salary) {
	try {
		if (salary < 0) throw - 1;
		cout << "counting tax" << endl;
	}
	catch (int) {
		cout << "salary < 0" << endl;
	}
	cout << "tax counted" << endl;
	return salary * 0.15;
}
int main() {
	double f = 1.2;
	try {
		CountTax(-1);
		f = Devide(3, 0);
		cout << "end of try block" << endl;
	}
	catch (CExeption e) {
		cout << e.msg << endl;
	}
	cout << "f=" << f << endl;
	cout << "finished" << endl;
	return 0;
}
/*
Output:
salary < 0
tax counted
devided by zero
f=1.2
finished
*/

3.C++标准异常类

C++标准库中有一些类代表异常,这些类都是从exception类派生而来。异常抛出具体的文字信息,不同的编译器也可能不一样。

s

(1)bad_cast:在用 dynamic_cast进行从多态基类对象(或引用),到派生类的引用的强制类型转换时,如果转换是不安全的,则会抛出此异常。

#include<iostream>
#include<stdexcept>
#include<typeinfo>
using namespace std;
class Base {
	virtual void func(){}
};
class Derived :public Base {
public:
	void Print(){}
};
void PrintObj(Base& b) {
	try {
		Derived& rd = dynamic_cast<Derived&>(b);
		rd.Print();
	}
	catch (bad_cast& e) {
		cerr << e.what() << endl;
	}
}
int main() {
	Base b;
	PrintObj(b);
	return 0;
}
/*
Output:
Bad dynamic_cast!
*/

(2)bad_alloc:在用new运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。

#include<iostream>
#include<stdexcept>
using namespace std;
int main() {
	try {
		char* p = new char[0x7fffffff];
	}
	catch (bad_alloc& e) {
		cerr << e.what() << endl;
	}
	return 0;
}
/*
Output:
bad allocation
*/

(3)out_of_range:用vector或string的at成员函数根据下标访问元素时,如果下标越界,就会抛出此异常。

#include<iostream>
#include<stdexcept>
#include<vector>
#include<string>
using namespace std;
int main() {
	vector<int> v(10);
	//v里面有10个元素
	try {
		v.at(100) = 100;
		//访问下标为100的元素。
	}
	catch (out_of_range& e) {
		cerr << e.what() << endl;
	}
	string s = "hello";
	try {
		char c = s.at(100);
	}
	catch (out_of_range& e) {
		cerr << e.what() << endl;
	}
	return 0;
}
/*
Output:
invalid vector subscript
invalid string position
*/

猜你喜欢

转载自blog.csdn.net/qq_45812711/article/details/105593429