五、STL编程
1. 模(mu)板 template(掌握)
C++中使用模板可以实现“参数化多态”,可以让类或函数声明一种通用数据类型,使类中或函数中编写与类型无关的代码。
通常模板有两种实现形式:
● 函数模板
● 类模板
1.1 函数模板
函数模板可以使函数的参数或返回值支持任意数据类型,需要注意的是函数中对任意数据类型的处理要考虑到算法的通用性问题。
#include <iostream>using namespace std;class Dog{public: Dog(int){} // 重载加法运算符 int operator +(Dog&) { return 666; } int get_msg() { return 666; }};// 函数模板template <class T>T add(T a,T b){ return a+b;}int main(){ // 具体使用的时候可以是任意类型 cout << add(1,2) << endl; // 3 cout << add(1.1,2.2) << endl; // 3.3 string s1 = "ABC"; string s2 = "123"; cout << add(s1,s2) << endl; // ABC123// cout << add("ABC","123") << endl; 函数内部计算错误 Dog d1(1); Dog d2(2); // 注意算法的通用性问题,add返回一个Dog对象 cout << add(d1,d2).get_msg() << endl; // 666 return 0;}
1.2 类模板
类模板可以使一个类支持通用数据类型。
#include <iostream>using namespace std;template <typename T> // class与typename都可以class Demo{public: Demo(T value):value(value){} void set_value(T value) { this->value = value; } T get_value() const { return value; }private: T value;};int main(){ Demo<int> d1(10); d1.set_value(11); cout << d1.get_value() << endl; // 11 Demo<bool> d2(false); cout << d2.get_value() << endl; // 0 d2.set_value(true); cout << d2.get_value() << endl; // 1 return 0;}
下面把上述代码的Demo类的函数,改为声明定义分离:
#include <iostream>using namespace std;template <typename T> // class与typename都可以class Demo{public: // 类内声明 Demo(T value); void set_value(T value); T get_value() const;private: T value;};template <typename T>Demo<T>::Demo(T value):value(value){}template <typename T>void Demo<T>::set_value(T value){ this->value = value;}template <typename T>T Demo<T>::get_value() const{ return value;}int main(){ Demo<int> d1(10); d1.set_value(11); cout << d1.get_value() << endl; // 11 Demo<bool> d2(false); cout << d2.get_value() << endl; // 0 d2.set_value(true); cout << d2.get_value() << endl; // 1 return 0;}
2. 容器
2.1 泛型编程与STL(熟悉)
泛型编程提出的目的是:发明一种语言机制,能够实现一个标准的容器库(标准模板库 STL),标准容器库可以做到编写一般化的算法,来支持不同的数据类型。
标准模板库(STL)是美国的惠普实验室开发的一系列软件的统称,后来被引入到C++语言中,主要包括:
● 算法
● 容器
● 迭代器
在STL中,几乎所有的代码都采用了模板来实现,相比于传统的库,具有更好的适用性和重用性。
2.2 容器类型(掌握)
C++中的STL容器类都是std名字空间中的类型,因此后面不再赘述名字空间。
所有的容器类的使用都要引入头文件,string的头文件包含在iostream中,可以不写。
容器类型的对象统一使用栈内存。
2.3 顺序容器
顺序容器是一种各元素之间具有顺序关系的线性表,是一种线性结构的可序群集,每个元素都有固定的位置,通过下标可以访问,除非使用插入或删除等操作改变元素的位置。
2.3.1 string 字符串
详见四、运算符重载
2.3.2 array 数组(熟悉)
array是C++11中新增的容器类型,相比传统数组更加安全和易于使用。
#include <iostream>// 头文件#include <array>using namespace std;int main(){ // 创建一个长度为5的int数组 array<int,5> arr1; // 取出元素值 cout << arr1.at(0) << endl; // ? cout << arr1[1] << endl; // ? // 可以在创建时赋予初始值 array<int,5> arr2 = {1,2,3}; // 1,2,3,?,? // 修改 arr2[0] = 888; // for each遍历,也支持for循环和迭代器 for(int i:arr2) cout << i << " "; return 0;}
2.3.3 vector 向量(掌握)
vector内部使用数组实现,能高效地进行随机存取,但是插入和删除的效率较低。
#include <iostream>// 头文件#include <vector>using namespace std;int main(){ // 创建一个长度为5的向量对象 vector<int> vec(5); // 取出元素值 cout << vec[0] << endl; cout << vec.at(1) << endl; // 创建一个长度为0的向量对象 vector<int> vec2; // 是否为空 cout << vec2.empty() << endl; // 1 // 向后追加 vec2.push_back(123); vec2.push_back(44); // 在第一个位置插入666 vec2.insert(vec2.begin(),666); // 在第二个位置插入222 vec2.insert(vec2.begin()+1,222); // 在倒数第一个位置插入元素999 // 注:end指向最后一个元素的后面 vec2.insert(vec2.end(),999); // 在倒数第三个位置插入元素333 vec2.insert(vec2.end()-2,333); // 删除第二个元素 vec2.erase(vec2.begin()+1); // 删除倒数第二个元素 vec2.erase(vec2.end()-2); // 删除最后一个元素 vec2.pop_back(); // 修改第二个元素 vec2.at(1) = 789; // 遍历(for、for each、iterator迭代器) for(int i:vec2) cout << i << " "; cout << endl; // 清空元素 vec2.clear(); cout << vec2.size() << endl; // 0 return 0;}
2.3.4 list 列表(掌握)
list内部使用双向链表实现,能高效地进行插入和删除操作,但是随机存取的效率较低。另外,list不支持使用下标操作元素,需要使用迭代器指针操作元素。
#include <iostream>// 头文件#include <list>using namespace std;int main(){ // 创建一个空列表对象 list<string> lis; // 是否为空 cout << lis.empty() << endl; // 1 // 向后追加元素 lis.push_back("Bye"); // 向前追加元素 lis.push_front("Hello"); // 在第二个位置插入元素 lis.insert(++lis.begin(),"BBB"); // 保存迭代器指针 list<string>::iterator iter = lis.begin(); // 移动到第三个位置 advance(iter,2); // 在第三个位置插入元素"TTT" lis.insert(iter,"TTT"); // 修改迭代器指针所在位置的元素 *iter = "UUU"; // 遍历(for each、iterator迭代器) for(string i:lis) cout << i << " "; cout << endl; // 删除倒数第一个元素 lis.pop_back(); // 删除第一个元素 lis.pop_front(); // 删除第二个元素 lis.erase(++lis.begin()); // 遍历(for each、iterator迭代器) for(string i:lis) cout << i << " "; cout << endl; return 0;}
2.3.5 deque 队列(熟悉)
API基本兼顾vector和list,性能均衡。
2.4 关联容器(掌握)
关联容器的各元素之间没有严格的物理上的顺序关系,但是在内部仍然具有排序的特点,因此可以使用迭代器进行遍历。
常见的关联容器有:
map、muitimap等
以map(键值对映射)为例,进行管理容器的讲解。
#include <iostream>// 头文件#include <map>using namespace std;int main(){ // 创建一个内容为空的键值对对象 map<string,int> map1; // 是否为空 cout << map1.empty() << endl; // 1 // 先判断要增加的键有否没有 if(map1.find("age")==map1.end()) map1["age"] = 24; // 增加元素 // 增加元素 map1.insert(pair<string,int>("salary",5000)); map1.insert(pair<string,int>("height",175)); map1.insert(pair<string,int>("weight",75)); map1.insert(pair<string,int>("age",25)); // 先判断要更新的键是否有 if(map1.find("age")!=map1.end()) map1["age"] = 25; // age已有数据,更新为25 // 删除元素 cout << "删除weight键是否成功:" << map1.erase("weight"); cout << endl; cout << "删除name键是否成功:" << map1.erase("name"); cout << endl; // 元素(键值对)数量 cout << map1.size() << endl; // 是否存在 if(map1.find("height") != map1.end()) { // 取出元素 cout << map1["height"] << endl; // 175 cout << map1.at("age") << endl; // 25 } // 遍历支持迭代器 return 0;}
3. 迭代器(掌握)
迭代器是一种特殊的指针,通常用于遍历所有的容器类型,且遍历效率高,推荐使用迭代器进行遍历。
不同的容器类型使用迭代器遍历的方式基本一致,需要注意的是const_iterator是只读迭代器,iterator是读写迭代器。
#include <iostream>// 头文件#include <string>#include <array>#include <vector>#include <list>#include <deque>#include <map>using namespace std;int main(){ string str = "gjkdsjkgdhs"; for(string::const_iterator iter = str.begin(); iter != str.end(); iter++) { cout << *iter << " "; } cout << endl; array<string,4> arr = {"fds","gf","er","bdf"}; array<string,4>::iterator arr_iter = arr.begin(); while(arr_iter != arr.end()) { cout << *arr_iter << " "; arr_iter++; } cout << endl; vector<string> vec(5,"hello"); for(vector<string>::iterator iter= vec.begin(); iter != vec.end();iter++) { cout << *iter << " "; } cout << endl; list<string> lis(5,"hello"); for(list<string>::iterator iter= lis.begin(); iter != lis.end();iter++) { cout << *iter << " "; } cout << endl; deque<string> deq(5,"hello"); for(deque<string>::iterator iter= deq.begin(); iter != deq.end();iter++) { cout << *iter << " "; } cout << endl; map<string,int> map1; map1.insert(pair<string,int>("salary",5000)); map1.insert(pair<string,int>("height",175)); map1.insert(pair<string,int>("weight",75)); map1.insert(pair<string,int>("age",25)); for(map<string,int>::const_iterator iter = map1.begin(); iter != map1.end();iter++) { // first取键 // second取值 cout << iter->first << ":" << iter->second << endl; } return 0;}