目录
3.2 STL(standard template library,标准模板库)
三、提高编程
3.1 模板(范式编程)
3.1.1 函数模板
3.1.1.1 基本语法
template<typename T> //T可以匹配任何类型int/float...
void mySwap(T &a, T&b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
//第1种写法:系统自动判断类型
mySwap(a, b);
//第2种写法:显示指定类型
mySwap<int>(a, b);
cout << b << ' ' << a << endl;
}
3.1.1.2 普通函数与模板函数
3.1.1.2.1 区别
普通函数会发生隐式类型转换,模板函数(依赖系统自动判断类型)不会发生隐式类型转换,模板函数(显示指定类型)会发生隐式类型转换,因此推荐使用后者方法2。
3.1.1.2.2. 调用规则
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配,优先调用函数模板
3.1.2 类模板
3.1.2.1 基本语法
template<class NAMETYPE, class AGETYPE>
class Person
{
public:
Person(NAMETYPE name, AGETYPE age)
{
this->m_name = name;
this->m_age = age;
}
void showPerson()
{
cout << "name: " << this->m_name << " age: " << this->m_age << endl;
}
private:
NAMETYPE m_name;
AGETYPE m_age;
};
int main()
{
Person<string, int> p1 ("Yeah", 20);
p1.showPerson();
}
3.1.2.2 类模板与函数模板区别
1、类模板无法自动推导类型,只支持显示的参数类型输入;
2、类模板在模板参数列表中可以有默认参数
3.1.2.3 类模板中成员函数创建时机
类模板中成员函数在调用时才去创建;而普通类中的成员函数构造时就创建。
3.1.2.4 类模板对象做函数参数
建议:指定传入类型
template<class NAMETYPE, class AGETYPE>
class Person
{
public:
Person(NAMETYPE name, AGETYPE age)
{
this->m_name = name;
this->m_age = age;
}
void showPerson()
{
cout << "name: " << this->m_name << " age: " << this->m_age << endl;
}
private:
NAMETYPE m_name;
AGETYPE m_age;
};
void test(Person<string, int> &p) //如左所示
{
p.showPerson();
}
int main()
{
Person<string, int> p1 ("Yeah", 20);
test(p1);
}
3.1.2.5 类模板与继承
如果父类是类模板,子类需要指定出父类中T的数据类型。
template<class NAMETYPE, class AGETYPE>
class Person
{
public:
void showPerson()
{
cout << "name: " << this->m_name << " age: " << this->m_age << endl;
}
NAMETYPE m_name;
AGETYPE m_age;
};
class Man: public Person<string, int>
{
};
void test()
{
Man m1;
m1.m_name = "Yeah";
m1.m_age = 20;
m1.showPerson();
}
int main()
{
test();
}
3.1.2.6 类模板成员函数类外实现
类模板中成员函数类外实现时,需要加上模板参数列表。
template<class NAMETYPE, class AGETYPE>
class Person
{
public:
Person(NAMETYPE name, AGETYPE age);
void showPerson();
NAMETYPE m_name;
AGETYPE m_age;
};
template<class NAMETYPE, class AGETYPE> //注意加上类模板函数声明
Person<NAMETYPE, AGETYPE>::Person(NAMETYPE name, AGETYPE age) //注意加上类模板函数作用域
{
this->m_name = name;
this->m_age = age;
}
template<class NAMETYPE, class AGETYPE> //注意加上类模板函数声明
void Person<NAMETYPE, AGETYPE>::showPerson() //注意加上类模板函数作用域
{
cout << "name: " << this->m_name << " age: " << this->m_age << endl;
}
void test()
{
Person <string, int> m1 ("Yeah", 20);
m1.showPerson();
}
int main()
{
test();
}
3.1.2.7 类模板分文件编写
将声明(.h头文件)和实现(.cpp源文件)写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称。
3.2 STL(standard template library,标准模板库)
广义上分为容器、算法、迭代器,容器和算法通过迭代器进行连接。
细分为六大组件,
1.容器:各种数据结构,如vector、list、deque、set、map
2.算法:各种常用算法,如sort、find、copy、for_each
3.迭代器:
4.仿函数:
5.适配器:
6.空间配置器:
3.2.1 容器
3.2.1.1 string 字符串
int main()
{
//string初始化/赋值
string str1("ab cd");
string str2;
str2 = "fdsf";
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
//拼接:+= 或者 append
str1 += "1234";
str2.append("6854");
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
//find:查找
cout << "pos: " << str1.find("cd") << endl; //如果查找不到,返回-1
cout << "pos: " << str2.find("f") << endl;
cout << "pos: " << str2.rfind("f") << endl; //从右侧开始查找第一个目标字符
//replace
str1.replace(5,7,"4321"); //begin_index,end_index,可能会超出end_index以插入字符长度为准
cout << "str1: " << str1 << endl;
//compare
if (str1.compare(str2) == 0) //比较两个字符串大小,相等为0,大于为1,小于-1
cout << "same!" << endl;
else
cout << "diff!" << endl;
//modify //直接修改
str2[0] = '?';
cout << "str2: " << str2 << endl;
//insert //插入,start_index
str2.insert(1,"!!");
cout << "str2: " << str2 << endl;
//erase //删除,begin_index, erase_length
str2.erase(1,2);
cout << "str2: " << str2 << endl;
//substr //切片,begin_index, erase_length
cout << "substr: " << str1.substr(0,4) << endl;
}
3.2.1.2 vector
void printVector(vector<int> &v)
{
for (vector<int>::iterator it = v.begin(); it < v.end(); it ++)
cout << *it << " ";
cout << endl;
}
int main()
{
//初始化赋值:1-手动赋值
vector<int> v1;
v1.push_back(1); //压入尾端
v1.push_back(6);
v1.push_back(3);
v1.push_back(8);
v1.push_back(5);
printVector(v1);
//初始化赋值:2-拷贝赋值
vector<int>v2(v1);
printVector(v2);
//初始化赋值:3-切片赋值
vector<int>v3(v1.begin()+1, v1.end()-1);
printVector(v3);
cout << "vector capacity: " << v1.capacity() << endl;
cout << "vector size: " << v1.size() << endl;
//修改vector的大小
v1.resize(10,0); //如果扩充大小,默认后序空间用0填充,也可自行指定
printVector(v1);
v1.resize(4); //如果是缩减大小,相当于切片
printVector(v1);
//尾端删除
v1.pop_back();
printVector(v1);
//任意位置插入
v1.insert(v1.begin(), 0);
printVector(v1);
//任意位置删除,也可以是区间
v1.erase(v1.begin());
printVector(v1);
//直接清空
v1.clear();
printVector(v1);
//判断是否为空
cout << "v1 empty? : " << v1.empty() << "v2 empty? : " << v2.empty() << endl;
cout << "first elem: " << v2.front() << endl;
cout << "second elem: " << v2[1] << endl;
cout << "last elem: " << v2.back() << endl;
//交换两个vector
v2.swap(v3);
printVector(v2);
printVector(v3);
//可以减少内存占用消耗
cout << "capacity: " << v2.capacity() << " size: " << v2.size() << endl;
v2.resize(1);
cout << "capacity: " << v2.capacity() << " size: " << v2.size() << endl;
vector<int>(v2).swap(v2); //注意
cout << "capacity: " << v2.capacity() << " size: " << v2.size() << endl;
//预留空间:如果事先知道vector后序会用到的空间,可以避免动态扩展过程中的时间浪费
vector<int>v;
v.reserve(100000);
//排序
printVector(v2);
sort(v1.begin(), v2.end());
printVector(v2);
}
3.2.1.3 deque 双端数组
双端数组,可以对头端/尾端进行插入删除操作。
deque与vector区别:
1、vector对于头部的插入删除效率低(需要移动数据),数据量越大,效率越低;deque相对速度快;
2、vector访问元素速度比deque快。
具体操作过程中的差异:
1.deque没有容量capacity;
2.deque可以对头部进行操作push_front、pop_front;
3.2.1.4 stack 栈
栈容器,现金后出,不允许有遍历行为。
栈可以为空,也可以统计元素个数。
int main()
{
stack<int> stk;
stk.push(1);
stk.push(2);
stk.push(3);
stk.push(4);
cout << "size: " << stk.size() << endl;
while (!stk.empty())
{
cout << stk.top() << endl;
stk.pop();
}
cout << "size: " << stk.size() << endl;
}
3.2.1.5 queue 队列
符合先进先出FIFO,有两个出口,不允许遍历行为。
操作和stack类似:push、pop、front、back、empty、size。
3.2.1.6 list 链表
优点:可以对任意位置进行快速插入或删除元素,可以灵活利用空间
缺点:遍历速度较慢、占用的空间也较大
注意:不是连续存储空间,链表遍历只能逐个遍历(it++),不能跳着遍历/随机访问(it+=2)
交换swap
大小size、empty、resize(没有容量capacity)
插入删除push_back/push_front、pop_back/pop_front、insert、clear、erase、remove
读取front、back(不支持[]、at)
反转reverse、排序sort(lst.sort(),默认从小到大)
所有不支持随机访问迭代器的容器,不可以用标准算法;
不支持随机访问迭代器的容器,内部会提供对应的一些算法。
自定义类型中,按照特定元素进行排序:
class Person
{
public:
Person(string name, int age, int height)
{
this->m_age = age;
this->m_name = name;
this->m_height = height;
}
string m_name;
int m_age;
int m_height;
};
void printList(const list<Person> &lst)
{
for (list<Person>::const_iterator it = lst.begin(); it != lst.end(); it ++)
cout << "name: " << (*it).m_name << " age: " << (*it).m_age << " height: " << (*it).m_height << endl;;
}
bool myCompare(Person &p1, Person &p2)
{
return p1.m_age > p2.m_age; //此处的顺序即为需要的顺序,从大到小
}
int main()
{
list<Person> lst;
Person p1("zhangsan", 32, 170);
Person p2("lisi", 35, 175);
Person p3("wangwu", 28, 178);
lst.push_back(p1);
lst.push_back(p2);
lst.push_back(p3);
printList(lst);
lst.sort(myCompare); //注意此函数中的写法,并且myCompare无参数输入
printList(lst);
}
3.2.1.7 set
所有元素在插入时自动被排序,底层结构是二叉树。
set不允许容器中有重复的元素,multiset允许重复元素。
set添加元素用insert,
大小和交换:size、empty、swap(不允许使用resize)
插入删除:insert、clear、erase(类似于list中的remove)
查找:find(查找是否存在,存在返回该元素的迭代器;若不存在,返回set.end())
统计:count(统计元素的个数)
查找的使用方法如下:
int main()
{
multiset<int> ss;
ss.insert(1);
ss.insert(99);
ss.insert(9);
ss.insert(5);
ss.insert(9);
printSet(ss);
multiset<int>::iterator pos = ss.find(999);
if (pos == ss.end())
cout << "not find!" << endl;
else
cout << "find!" << endl;
}
利用仿函数让set按照从大到小的顺序保存:
class myCompare //仿函数
{
public:
bool operator()(int v1, int v2)
{
return v1 > v2;
}
};
void printSet(const set<int, myCompare> &ss)
{
for (set<int, myCompare>::const_iterator it = ss.begin(); it != ss.end(); it++)
cout << *it << " " ;
cout << endl;
}
int main()
{
set<int, myCompare> ss;
ss.insert(1);
ss.insert(99);
ss.insert(9);
ss.insert(5);
printSet(ss);
}
利用仿函数对自定义类型的数据进行排序:
class comparePerson
{
public:
bool operator()(const Person &p1, const Person &p2)
{
return p1.m_age > p2.m_age;
}
};
void printSet(const set<Person, comparePerson> &ss)
{
for (set<Person, comparePerson>::const_iterator it = ss.begin(); it != ss.end(); it++)
cout << "name: " << (*it).m_name << " age: " << (*it).m_age << endl;
}
int main()
{
Person p1("zhangsan", 25);
Person p2("lisi", 35);
Person p3("wangwu", 30);
set<Person, comparePerson> ss;
ss.insert(p1);
ss.insert(p2);
ss.insert(p3);
printSet(ss);
}
3.2.1.8 pair
成对出现的数据,利用对组可以返回两个数据。
//方法1
pair<string, int> p1 ("zhangsan", 20);
cout << "name: " << p1.first << " age: " << p1.second << endl;
//方法2
pair<string, int> p2 = make_pair("zhangsan", 20);
cout << "name: " << p2.first << " age: " << p2.second << endl;
3.2.1.9 map
vector,list,map是最常用的三种容器
map中所有元素都是pair,pair中第一个元素为key,第二个元素为value,会根据key自动排序。
底层用二叉树实现。
map/multimap区别是不允许/允许重复key值。
插入:insert
删除:erase(可以按照m.begin()、区间、key值删除)、clear
查找:find
统计:count
void printMap(const map<int, int> &mm)
{
for (map<int, int>::const_iterator it = mm.begin(); it != mm.end(); it++)
{
cout << "first: " << (*it).first << " second: " << (*it).second << endl;
}
}
int main()
{
map<int, int> mm;
mm.insert(pair<int, int>(1,10));
mm.insert(pair<int, int>(3,55));
mm.insert(pair<int, int>(2,28));
printMap(mm);
}
利用仿函数,修改排序:
class myCompare
{
public:
bool operator()(int p1, int p2)
{
return p1 > p2;
}
};
void printMap(const map<int, int, myCompare> &mm)
{
for (map<int, int, myCompare>::const_iterator it = mm.begin(); it != mm.end(); it++)
{
cout << "first: " << (*it).first << " second: " << (*it).second << endl;
}
}
int main()
{
map<int, int, myCompare> mm;
mm.insert(pair<int, int>(1,10));
mm.insert(pair<int, int>(3,55));
mm.insert(pair<int, int>(2,28));
printMap(mm);
}
3.2.2 函数对象(仿函数)
1、函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值;
2、函数对象超出普通函数的概念,函数对象可以有自己的状态;
3、函数对象可以作为参数传递。
返回bool类型的仿函数称为谓词;
如果operator()接受一个参数,就叫做一元谓词;接受两个参数,就叫做二元谓词。
内建函数对象需要包含头文件#incude <function>。
3.2.3 算法 algorithm
3.2.3.1 遍历算法 for_each
在实际开发中是最常用的遍历算法,用法如下:
void printv(int x)
{
cout << x << " ";
}
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(22);
v.push_back(3);
v.push_back(44);
for_each(v.begin(), v.end(), printv); //printv不带()
}
3.2.3.2 查找算法
3.2.3.2.1 find
内置数据类型和自定义类的查找,find的返回值是迭代器:
class Person
{
public:
Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
bool operator==(const Person &p)
{
if (this->m_name == p.m_name && this->m_age == p.m_age)
return true;
else
return false;
}
string m_name;
int m_age;
};
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(22);
v.push_back(3);
v.push_back(44);
vector<int>::iterator it = find(v.begin(), v.end(), 23);
if (it == v.end())
cout << "not find!" << endl;
else
cout << "find!" << endl;
vector<Person> vp;
Person p1("aaa", 24);
Person p2("bbb", 63);
Person p3("ccc", 34);
Person p4("ddd", 35);
vp.push_back(p1);
vp.push_back(p2);
vp.push_back(p3);
vector<Person>::iterator it2 = find(vp.begin(), vp.end(), p4);
if (it2 == vp.end())
cout << "not find!" << endl;
else
cout << "find!" << endl;
}
3.2.3.2.2 binary_search
二分法查找数据,要求为排序数据,返回的是bool类型数据
int main()
{
set<int> v;
v.insert(1);
v.insert(22);
v.insert(3);
v.insert(44);
bool ret = binary_search(v.begin(), v.end(), 22);
cout << ret << endl;
}
3.2.3.2.3 count
统计数据出现次数,返回整形int。
3.2.3.3 排序算法
3.2.3.3.1 sort
默认从小到大,如果要从大到小需要加谓词。
sort(v.begin(), v.end());
sort(v.begin(), v.end(), greater<int>());
3.2.3.3.2 random_shuffle 随机打乱
使用时记得加随机种子
rand_shuffle(v.begin(), v.end());
3.2.3.3.3 merge 合并后排序
int main()
{
set<int> v1; //合并的两个数组需要为排序数组
set<int> v2; //合并的两个数组需要为排序数组
for (int i = 0; i<5; i++)
{
v1.insert(i);
v2.insert(i+1);
}
vector<int> vTarget; //定义目标接受的vector
vTarget.resize(v1.size()+v2.size()); //记得需要预先分配空间
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
}
3.2.3.3.4 reverse 反转
reverse(v.begin(), v.end());
3.2.3.4 拷贝和替换
3.2.3.4.1 copy
copy(v.begin(), v.end(), vTarget.begin());
3.2.3.4.2 replace
replace(v.begin(), v.end(), oldvalue, newvalue);
3.2.3.4.3 swap
同种类型容器内容互换:
swap(container c1, container c2);
3.2.3.5 算术生成算法
头文件为<numeric>
3.2.3.5.1 accumulate 累加
accumulate(v.begin(), v.end(), init_sum_value);
3.2.3.5.2 fill 后期重新填充
vector<int>v;
v.resize(10);
fill(v.begin(), v.end(), 100);
3.2.3.6 集合算法
要求原有的两个容器内都是按序排列数据
3.2.3.6.1 set_intersection 交集
目标容器开辟空间需要从两个容器中取较小值,set_intersection返回值是交集中最后一个元素的位置。
set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTraget.begin());
3.2.3.6.2 set_union 并集
目标容器开辟空间需要取两个容器大小的和,set_union返回值是交集中最后一个元素的位置。
set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTraget.begin());
3.2.3.6.3 set_difference 差集
目标容器开辟空间需要从两个容器中取较大值(实际上可以取被比较容量的大小),set_difference返回值是交集中最后一个元素的位置。
set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTraget.begin());