1、模板
模板可以让类或者函数支持一种通用类型,这种通用类型在实际运行过程中可以使用任何数据类型。因此程序员可以写出一些与类型无关的代码。这种编程方式也叫“泛型编程”。
通常有两种形式:
- 函数模板
- 类模板
1.1 函数模板
//模板类型声明
template<class T>//T 模板类型
是让一个函数支持模板编程,可以使函数支持通用数据类型。
#include <iostream>
#include <string.h>
using namespace std;
// 模板函数
template<class T>//T 模板类型
T add(T a,T b)
{
return a+b;
}
int main()
{
cout << add(1,2) << endl; // 3
string s1 = "hello";
string s2 = "world";
cout << add(s1,s2) << endl; // helloworld
cout << add(2.3,23.12) << endl;
// cout << add(1,23.3) << endl;
//编译器自动识别数据类型
//错误,无法推导,局限性:只能用同一种类型
//错误,两个指针无法相加,如果想要实现这个功能,想要将+运算符重载
//cout << add("hello","world") << endl;
return 0;
}
1.2 类模板
使一个类支持模板编程,可以使类支持通用数据类型。
作用域:只有一个花括号大小
#include <iostream>
#include <string.h>
using namespace std;
// 模板函数
//template<typename T>
//T add(T a,T b)
//{
// return a+b;
//}
template<class T> // 可以class 也可以typename
class Test
{
private:
T val;
public:
Test(T v)
{
val = v;
}
T get_val()
{
return val;
}
void set_val(T v)
{
val = v;
}
};
int main()
{
// 类模板需要执行模板类型,通过<>。只要在之后的程序中看到<>,肯定是个模板类
Test<int> t1(20);
cout << t1.get_val() << endl;
t1.set_val(10);
cout << t1.get_val() << endl;
t1.set_val(2.34);
// cout << t1.get_val() << endl; // 数据窄化 输出2
Test<double> t2(2.14); // 指定类模板为double
cout << t2.get_val() << endl;
t2.set_val(3);
cout << t2.get_val() << endl;
return 0;
}
类内定义,类外声明
上面的写法可以改为类内声明类外定义的形式:
#include <iostream>
#include <string.h>
using namespace std;
template<class T> // 可以class 也可以typename
class Test
{
private:
T val;
public:
Test(T v)
{
val = v;
}
T get_val();//类内定义 类外声明
void set_val(T v);
};
template<class T>
T Test<T>::get_val()//类内定义 类外声明
{
return val;
}
template<class T>
//返回值类型 模板类型作用域 函数名(传参)
void Test<T>::set_val(T v)
{
val = v;
}
int main()
{
Test<int> t1(20);
cout << t1.get_val() << endl;
t1.set_val(10);
cout << t1.get_val() << endl;
t1.set_val(2.34);
// cout << t1.get_val() << endl; // 数据窄化输出2
Test<double> t2(2.14);
cout << t2.get_val() << endl;
t2.set_val(3);
cout << t2.get_val() << endl;
return 0;
}
2、容器
2.1 标准模板库STL
标准模板库(Standard Template Library,STL)是惠普实验室开发的一系列软件的统称。虽然说它主要出现在C++中,但是它在被引入C++之前,该技术就已经存在很长时间了。
STL的代码从广义上讲分为三部分,algorithm(算法)、container(容器)、iterator(迭代器)。几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用的机会。
2.2 概念
容器是用来存储数据的集合,数据元素可以是任何类型(因为是使用模板实现的)
容器类的使用,都要引入对应的头文件。
2.3 顺序容器
顺序容器中每个元素均有固定的位置并呈线性排布。除非使用删除或者是插入操作改变元素的位置。
2.3.1 array数组
array是C++11新增的容器类型,与传统数组相比更加安全,易于使用。array数组是定长的。没法方便的伸缩。array创建数组必须是定长的,且不能用变量定长。
QMAKE_CXXFLAGS += -std=c++11
array数组常用函数
#include <array> // 头文件
array<int,5> arr = {1,2,3}; // 后面两位补零
#include <iostream>
#include <array> // 头文件
using namespace std;
int main()
{
// 创建一个长度为5的int数组
array<int,5> arr = {1,2,3}; // 后面两位补零
cout << arr[0] << endl; // 1
cout << arr[4] << endl; // 0
cout << arr.at(2) << endl; // 3 推荐使用at
arr[3] = 200;
// for
for(int i = 0; i < arr.size(); ++i)
{
cout << arr.at(i) << " ";
}
cout << endl;
// for each
for(int i:arr)
{
cout << i << " ";
}
cout << endl;
//迭代器遍历,后面讲
// TODO
return 0;
}
2.3.2 vector 向量
vector向量内部是由数组实现的,比较适合进行随机的存取操作,不擅长插入删除操作。
不是定长,方便伸缩。(大量数据存取时且不经常插入删除)
vector向量常用函数
#include <vector> // 头文件
vector<int> vec(5,0);//创建一个大小为5类型为int,默认值为0的vector向量数组
vec.push_back(222);//在最后面追加新元素
vec.insert(vec.begin()+2,333);//begin()指向第一个元素位置
vec.pop_back(); // 删除最后一个元素
vec.erase(vec.end()-2);//end()指向最后一个元素后面,删除了倒数第二个元素
vec.empty()// 判断是否为空,0非空,1空
vec.clear();// 清空
#include <iostream>
#include <vector> // 头文件
using namespace std;
int main()
{
// vector<int> vec(5,0);//创建一个大小为5类型为int,默认值为0的vector向量数组
// cout << vec.size() << endl;
// for(int i = 0; i < vec.size(); ++i)
// {
// cout << vec[i] << endl;
// }
vector<int> vec = {1,2,3};
// 增
vec.push_back(222);//在最后面追加新元素
for(int i :vec)
{
cout << i <<" ";
}
cout << endl;
cout<<"--------------------"<<endl;
cout <<"向量大小:"<< vec.size() << endl;
// 插入操作,定向插入
// begin()可以返回指向第一个元素的迭代器指针,+2是在第三个位置上插入333
vec.insert(vec.begin()+2,333);//begin()指向第一个元素位置
cout<<"定向插入:"<<endl;
for(int i :vec)
{
cout << i <<" ";
}
cout << endl;
cout<<"--------------------"<<endl;
// 删
// 删除最后一个元素
cout<<"删除最后一个元素:"<<endl;
vec.pop_back();
for(int i :vec)
{
cout << i <<" ";
}
cout << endl;
cout<<"--------------------"<<endl;
// 删除了第二个元素
cout<<"删除了第二个元素:"<<endl;
vec.erase(vec.begin()+1);
for(int i :vec)
{
cout << i <<" ";
}
cout << endl;
cout<<"--------------------"<<endl;
// 删除了倒数第二个元素
cout<<"删除了倒数第二个元素:"<<endl;
vec.erase(vec.end()-2);//end()指向最后一个元素后面
for(int i :vec)
{
cout << i <<" ";
}
cout << endl;
cout<<"--------------------"<<endl;
// 改
cout<<"改:"<<endl;
vec[1] = 666;
vec.at(0) = 888;
for(int i :vec)
{
cout << i <<" ";
}
cout << endl;
cout<<"--------------------"<<endl;
// 查
cout << vec[1] << endl;
for(int i :vec)
{
cout << i <<" ";
}
cout << endl;
cout << "---------------------" << endl;
for(int i = 0; i < vec.size(); ++i)
{
cout << vec[i] << " ";
}
cout << endl;
// 判断是否为空,0非空,1空
cout << vec.empty() << endl;
// 清空
vec.clear();
cout << vec.empty() << endl;
// 迭代器遍历先省略
return 0;
}
2.3.3 list列表
list内部是由双向循环链表实现的。不支持下标。优点:可以高效的删除和插入操作。但不适合随机的存取操作。
list列表常用函数
#include <list> // 头文件
list<string> lis2{"hello","world"};
//创建了一个长度为2的列表,第一个元素就是hello,第二个world
list<string> lis(5,"hello");
// 创建一个长度为5的列表,每个元素都是“hello”
lis.push_back("world"); // 向后追加一个单元素
lis.push_front("hahaha"); // 向前追加一个单元素
lis.insert(++lis.begin(),"2222"); // 在第二个位置上插入“2222”,lis.begin()只能自增,不能加数值
lis.pop_back(); // 删最后一个元素
lis.pop_front(); // 删除第一个元素
lis.push_back("world"); // 向后追加一个单元素
list<string>::iterator iter = lis.begin();// 保存迭代器指针
advance(iter,1); // 移动迭代器指针
#include <iostream>
#include <list> // 头文件
using namespace std;
int main()
{
// 创建了一个默认无数值的list
list<string> lis1;
//创建了一个长度为2的列表,第一个元素就是hello,第二个world
list<string> lis2{"hello","world"};
for(string s:lis2)
{
cout << s << " ";
}
cout <<endl;
// 创建一个长度为5的列表,每个元素都是“hello”
list<string> lis(5,"hello");
cout<<"创建一个长度为5的列表,每个元素都是“hello”"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
// 增
lis.push_back("world"); // 向后追加一个单元素
cout<<"向后追加一个单元素"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
lis.push_front("hahaha"); // 向前追加一个单元素
cout<<"向前追加一个单元素"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
lis.insert(++lis.begin(),"2222"); // 在第二个位置上插入“2222”,lis.begin()只能自增,不能加数值
cout<<"在第二个位置上插入“2222”"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
// 删
lis.pop_back(); // 删最后一个元素
cout<<"删最后一个元素"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
lis.pop_front(); // 删除第一个元素
cout<<"删除第一个元素"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
lis.push_back("world"); // 向后追加一个单元素
cout<<"删除第一个元素"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
// 保存迭代器指针
list<string>::iterator iter = lis.begin();
advance(iter,1); // 移动迭代器指针
lis.insert(iter,"3333"); // 插入3333
cout<<"通过移动迭代器指针定向插入,插入3333"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
// 删除最后一个元素
iter = lis.end();
iter--;
lis.erase(iter);//删除iter指针指向的元素
cout<<"通过移动迭代器指针定向删除"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
// 返回第一个元素的引用
cout << "打印第一个元素* = " << lis.front() << endl;
// 返回最后一个元素
cout << "打印最后一个元素* = " << lis.back() << endl;
// 改
iter = lis.end();
advance(iter,2);//将迭代器指针移动到第二个位置
*iter = "200";
cout<<"通过移动迭代器指针定向修改元素"<<endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
// 查
cout<<"迭代器指针查看"<<endl;
cout << *iter << endl;
for(string s:lis)
{
cout << s << " ";
}
cout<<endl;
cout<<"___________________"<<endl;
// 迭代器遍历
cout << lis.size() << endl;
// 清空
lis.clear();
cout << lis.size() << endl;
return 0;
}
2.3.4 deque队列
deque几乎支持所有vector的API。性能位于vector与list二者之间。最擅长两端存取的顺序容器。
#include <iostream>
#include <deque> // 头文件
using namespace std;
int main()
{
// deque<int> deq(5);
// cout << deq.size() << endl;
// for(int i = 0; i < deq.size(); ++i)
// {
// cout << deq[i] << endl;
// }
deque<int> deq = {1,2,3};
// 增
deq.push_back(222);
cout << deq.size() << endl;
// 插入操作
// begin()可以返回指向第一个元素的迭代器指针,+2是在第三个位置上插入333
deq.insert(deq.begin()+2,333);
// 删
// 删除最后一个元素
deq.pop_back();
// 删除了第二个元素
deq.erase(deq.begin()+1);
// 删除了倒数第二个元素
deq.erase(deq.end()-2);
// 改
deq[1] = 666;
deq.at(0) = 888;
// 查
cout << deq[1] << endl;
for(int i :deq)
{
cout << i <<" ";
}
cout << endl;
cout << "---------------------" << endl;
for(int i = 0; i < deq.size(); ++i)
{
cout << deq[i] << " ";
}
cout << endl;
// 判断是否为空,0非空,1空
cout << deq.empty() << endl;
// 清空
deq.clear();
cout << deq.empty() << endl;
// 迭代器遍历先省略
return 0;
}
2.4 关联容器
关联容器的各个元素之间没有严格顺序,虽然内部具有排序特点。但是在使用时没有任何顺序相关的接口。底层是由树构成,(红黑树)
最常见的关联容器就是map-键值对映射
对于map而言,键具有唯一性,键通常使用字符串类型,值任何类型。通过键找到对应的值。
关联容器常用函数
#include <map> // 头文件
map<string,int> ma;//创建一个value为int类型的关联容器
map<string,int> ma1 = {{"年龄",99},{"身高",250}};
ma.insert(pair<string,int>("体重",70)); // 插入元
if(ma.find("身高") == ma.end())
{
cout << "没有身高元素" << endl;
}
int re = ma.erase("身高"); // 删除元素,返回值1成功,0失败
#include <iostream>
#include <map> // 头文件
using namespace std;
int main()
{
// map创建初始化 C++11支持
map<string,int> ma1 = {{"年龄",99},{"身高",250}};
cout << ma1.size() << endl; // 2
map<string,int> ma;//创建一个value为int类型的关联容器
cout << ma.size() << endl;//0:容器大小
// 增
cout << "增操作:"<< endl;
ma["身高"] = 180;
ma.insert(pair<string,int>("体重",70)); // 插入元素
cout <<"身高"<< ma["身高"] << endl;
cout <<"体重"<<ma["体重"] << endl;
cout << "--------------------" << endl;
// 改
cout << "改操作:"<< endl;
ma["身高"] = 165;
cout << "身高"<<ma["身高"] << endl;
cout << "--------------------" << endl;
// 查
cout << "查操作:" << endl;
cout << "身高"<<ma["身高"] << endl;
if(ma.find("身高") == ma.end())
{
cout << "没有身高元素" << endl;
}
else
{
cout << "身高"<<ma["身高"] << endl;
}
cout << "--------------------" << endl;
// 删
cout << "删除操作前 "<<ma.size() << endl;
cout << "删除操作:" << endl;
int re = ma.erase("身高"); // 删除元素,返回值1成功,0失败
cout << "身高删除返回值:" << re << endl;
if(ma.find("身高") == ma.end())
{
cout << "没有身高元素" << endl;
}
else
{
cout << "身高"<<ma["身高"] << endl;
}
cout <<"删除操作后 "<< ma.size() << endl;
cout << "--------------------" << endl;
//清空操作
cout << "清空操作" << endl;
ma.clear();
cout << ma.size() << endl;
cout << "--------------------" << endl;
return 0;
}
2.5 迭代器
迭代器是一个特殊的指针,主要用于容器的元素读写以及遍历。
如果迭代器不进行修改,建议使用只读迭代器,const_iterator,反之使用iterator.
#include <array>
#include <vector>
#include <list>
#include <deque>
#include <map> // 头文件
// 迭代器遍历 string
for(string::const_iterator iter = s.begin();iter != s.end(); ++iter)
// 迭代器遍历array
for(array<int,5>::const_iterator iter = arr.begin();iter != arr.end();iter++)
// vector 迭代器遍历
for(vector<string>::const_iterator iter = vec.begin();iter != vec.end();iter++)
// 迭代器遍历list
for(list<string>::const_iterator iter = lis.begin();iter != lis.end();++iter)
// 迭代器遍历deque
for(deque<string>::const_iterator iter = de.begin();iter != de.end();iter++)
// 迭代器遍历map
for(map<string,int>::const_iterator iter = ma.begin();iter != ma.end(); iter++)
{
// first 是键 second 是值
cout << iter->first << " " << iter->second << endl;
}
#include <iostream>
#include <array>
#include <vector>
#include <list>
#include <deque>
#include <map> // 头文件
using namespace std;
int main()
{
string s = "abdcdfg";
// 迭代器遍历 string
cout<<"迭代器遍历 string:"<<endl;
for(string::const_iterator iter = s.begin();
iter != s.end(); ++iter)
{
cout << *iter << " ";
}
cout << endl;
cout << "--------------" << endl;
// 迭代器遍历array
cout<<"迭代器遍历array:"<<endl;
array<int,5> arr = {23,2,5,87,2};
for(array<int,5>::const_iterator iter = arr.begin();
iter != arr.end();iter++)
{
cout << *iter << " ";
}
cout << endl;
cout << "--------------" << endl;
// vector 迭代器遍历
cout<<"vector 迭代器遍历:"<<endl;
vector<string> vec(6,"hello");
for(vector<string>::const_iterator iter = vec.begin();
iter != vec.end();iter++)
{
cout << *iter << " ";
}
cout << endl;
cout << "--------------" << endl;
// 迭代器遍历list
cout<<"迭代器遍历list:"<<endl;
list<string> lis(6,"world");
for(list<string>::const_iterator iter = lis.begin();
iter != lis.end();++iter)
{
cout << *iter << " ";
}
cout << endl;
cout << "--------------" << endl;
// 迭代器遍历deque
cout<<"迭代器遍历deque:"<<endl;
deque<string> de(6,"nihao");
for(deque<string>::const_iterator iter = de.begin();
iter != de.end();iter++)
{
cout << *iter << " ";
}
cout << endl;
cout << "--------------" << endl;
// 迭代器遍历map
cout<<"迭代器遍历map:"<<endl;
map<string,int> ma;
ma["年龄"] = 23;
ma["身高"] = 180;
ma["体重"] = 65;
for(map<string,int>::const_iterator iter = ma.begin();
iter != ma.end(); iter++)
{
// first 是键 second 是值
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
练习:
写一个函数show_array,要求传入两个参数。
void show_array(T t,int len)
第一个参数为数组默认值,第二个参数为数组的长度。
此函数的功能为创建一个长度为len类型是T,默认值为t的数组并输出。
尽量不用使用c语言的数组。
#include <iostream>
#include <vector>
using namespace std;
template<class T>
void show_array(T t,int len)
{
vector<T> vec(len,t);
for(T i:vec)
{
cout << i << " ";
}
}
int main()
{
show_array("a",5);
return 0;
}