目录
一、标准模板库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类派生而来。异常抛出具体的文字信息,不同的编译器也可能不一样。
(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
*/