文章目录
一、模板
本阶段主要针对 C++ 泛型编程和 STL 技术 做详细讲解,探讨C++ 更深层次的使用
模板就是建立通用的模具, 大大提高复用性
不能直接使用,只是一个框架
函数模板
- C++ 另一种编程思想称为 泛型编程,主要利用的技术就是模板
- C++ 提供两种模板机制 函数模板 和 类模板
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
语法:
template
函数声明或定义
解释:
template 声明创建模板
typename 表明其后面的符号是一种数据类型,可以用class 代替
T 通用的数据类型, 名称可以替换,通常为大写字母
我一般函数模板用 typename , 类模板用 class
template<typename T>
void func(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
template<typename T>
void func1()
{
cout << "func1 调用" << endl;
}
int main()
{
//自动类型推导
int a = 20;
int b = 10;
func(a, b);
// 显示指定类型
func<int>(a, b);
func1<int>();//必须指定类型,无法进行推导
return 0;
}
函数模板案例-数组排序
//交换值
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
//数组排序
template<typename T>
void mySort(T arr[] , int iLen)
{
for (int i = 0; i < iLen; ++i)
{
int iMax = i;
for (int j = i + 1; j < iLen; ++j)
{
if (arr[iMax] < arr[j])
{
iMax = j;
}
}
mySwap(arr[iMax], arr[i]);
}
}
void test03()
{
char charArr[] = "badcfe";
int ilen = sizeof(charArr) / sizeof(char);
mySort<char>(charArr, ilen);
cout << charArr << endl;
system("pause");
}
普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
类模板
建立一个通用类,类中的成员 数据类型可以不具体指定,用一个虚拟的类型来代表
template<class NameType, class AgeType = int>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
NameType m_Name;
AgeType m_Age;
};
Person<string, int> p("孙悟空", 1000);
二、STL的初识
- 长久以来,软件界一直希望建立一种可重复利用的东西
- C++ 的面向对象 和 泛型编程思想,目的就是复用性的提升
- 大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
- 为了建立数据结构和算法的一套标准,诞生了STL
1、STL 的诞生
面向对象三大特性:封装,继承,多态==
封装:把属性,行为类似的东西抽象出来,作为一个整体,来实现这些事或物
继承:子类将父类所有的属性,行为拿到手
多态:一个函数名称,有多个接口;由于对象创建的不同,父类指针指向子类对象,调用同一个接口,会产生不同的形态
泛型编程:模板,更通用化
2、STL 的基本概念
STL ---- Standard Template Library, 标准模板库
STL 从广义上分为:容器(container)、算法(algorithm)、迭代器(iterator)
容器和算法之间通过迭代器进行无缝连接
STL 几乎所有的代码都采用了模板类和模板函数
3、STL六大组件
STL大体分为六大组件:分别是容器、算法、迭代器、仿函数、适配器(配接器)、空间适配器
- 容器:各种数据结构,如vector, list, deque, set, map等,用来存放数据。
- 算法:各种常用的算法,如sort,find, copy, for_each 等。
- 迭代器:扮演容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可作为算法的某种策略。
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
4、STL 中容器、算法、迭代器
容器:置物指所也
STL 容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表等
这些容器分为 序列式容器 和 关联式容器两种:
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置;
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)
算法分为:质变算法 和 非质变算法
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找,计数,遍历,寻找极值等等
迭代器:容器与算法之间的粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表达方式。
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
三、常用容器
1、vector 存放内置数据类型
容器:vector
算法:for_each
迭代器:vector::iterator
#include <iostream>
#include <algorithm>//标准算法头文件
#include <vector>
using namespace std;
void myPrint(int val)
{
cout << val << endl;
}
int main()
{
vector<int> vctiTemp;
vctiTemp.push_back(10);
vctiTemp.push_back(20);
vctiTemp.push_back(30);
vctiTemp.push_back(40);
vector<int>::iterator itBegin = vctiTemp.begin();//执行容器中第一个元素
vector<int>::iterator itEnd = vctiTemp.end();//指向容器中最后一个元素的下一个位置
for(;itBegin != itEnd; ++itBegin )
{
cout << *itBegin << endl;
}
for_each(vctiTemp.begin(), vctiTemp.end(), myPrint)//回调
{
;
}
}
2、vector 存放自定义数据类型
学习目标:vector 中存放自定义数据类型,并打印输出
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Person
{
public:
Person(string strName, int iAge):m_strName(strName), m_iAge(iAge)
{
}
string m_strName;
int m_iAge;
};
void test01()
{
vector<Person> v;
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
vector<Person>::iterator it = v.begin();
for (; it != v.end(); ++it)
{
cout << (*it).m_strName << "\t" << (*it).m_iAge << endl;
}
return;
}
int main()
{
test01();
return 0;
}
3、vector 容器嵌套容器
void test02()
{
vector< vector<int>> vviTemp;
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
for (int i = 0; i < 4; ++i)
{
v1.push_back(i + 1);
v2.push_back(i + 2);
v3.push_back(i + 3);
v4.push_back(i + 4);
}
vviTemp.push_back(v1);
vviTemp.push_back(v2);
vviTemp.push_back(v3);
vviTemp.push_back(v4);
vector< vector<int>>::iterator it = vviTemp.begin();
for (; it != vviTemp.end(); ++it)
{
vector<int>::iterator it1 = (*it).begin();
for (; it1 != (*it).end(); ++it1)
{
cout << *it1 << "\t";
}
cout << endl;
}
}
4、string 容器-构造函数
string 是 C++ 风格的字符串,本质是一个类
与 char* 区别:
- char* 是一个指针;
- string 是一个类,类内部封装了 char* , 管理这个字符串,是一个char* 型的容器
构造函数:
string();
=>string str;
string(const char* s);
=>const char* str = “hello world”;
string s2(str);
string(const string& str);
=>string s3(s2);
string(int n, char c);
=>string s4(10, ‘a’);
5、string 容器-字符串查找,替换,比较,存取,插入,删除,子串
注意:下标都是从0 号位置开始的
//rfind 和 find区别
//rfind 从右往左查找,find 从左往右查找
string str1 = "abcdefgde";
int ipos = str1.find("de");
if(-1 == ipos)
{
cout << "未找到字符串" << endl;
}
else
{
cout << "找到字符串位置" << ipos << endl;
}
//源文:string& replace(int pos, int n, const string& str);//替换从pos 开始n 个字符为字符串str
string str2 = "abcdefg";
str.replace(1, 3, "1111");
//比较 compare 返回值 -1, 0 , 1
字符串1大于字符串2 ,返回值为 1, eg: str1.compare(str2) == 0
字符串1等于字符串2 ,返回值为 0
字符串1小于字符串2 ,返回值为-1
//string 中单个字符存取方式有两种
char& operator[](int n);// 通过[]方式取字符
char& at(int n);//通过at 方式获取字符
//对string 字符串进行插入和删除字符操作
string& insert(int pos, const char* s);//插入字符
string& erase(int pos, int n = npos);//删除从Pos 开始的n个字符
//从字符串中获取想要的子串
string substr(int pos = 0, int n = npos) const;//返回由pos 开始的n个字符组成的字符串
6、set 容器
判断是否插入成功
#include <iostream>
#include <set>
using namespace std;
void test01()
{
set<int> s;
pair<set<int>::iterator, bool> ret = s.insert(10);
if(ret.second)
{
cout << "插入成功" << endl;
}
else
{
cout << "插入失败" << endl;
}
}
set容器排序
#include <set>
class MyCompare
{
bool operator()(int v1, int v2)
{
return v1 > v2;
}
};
void test02()
{
set<int, MyCompare> s1;
s1.insert(10);
s1.insert(40);
}
7、map 容器
map 中所有元素都是pair(关联式容器,底层结构是二叉树实现的)
pair 中第一个元素为key (键值),不允许重复,起到索引作用,第二个元素为value (实值)
所有元素都会根据元素的键值自动排序
map<string, string> mapTemp;
mapTemp.insert(make_pair("12", "34"));
if(mapTemp.find("12") != mapTemp.end())//查找容器中字符串是否存在
{
cout << "找到了" << endl;
}
四、STL-仿函数
又名函数对象
总结:
1、函数对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用的运行时问题
2、函数对象超出普通函数的概念,函数对象可以有自己的状态
3、函数对象可内联编译,性能好,用函数指针几乎不可能
4、模板函数对象使函数对象具有通用性,这也是他的优势之一。
谓词
是指普通函数或 重载的 operation() 返回值 是bool 类型的函数对象(仿函数),如果operator 接受一个参数,那么叫做一元谓词,如果接受两个参数,那就叫二元谓词。
重载函数调用操作符的类,其对象称为 函数对象
函数对象使用重载的() 时,行为类似函数调用,也叫仿函数
本质是一个类,不是一个函数
class MyPrint
{
public:
MyPrint()
{
iCount = 0;
}
void Operator()(string strText)
{
cout << strText << end;
this->iCount++;
}
private:
int iCount;
}
//查找容器中 有没有大于5的数字
class GreateFive
{
void Operator()(int iNum)
{
return iNum > 5;
}
};
void test01()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
{
v.push_back(i);
}
vector<int>::iterator it = find_if(v.begin(), v.end(), GreateFive());
if(it != v.end())
{
cout << "没找到" << endl;
}
}
五、STL-常用算法
1、常用遍历算法
常用头文件:
是所有STL头文件中最大的一个,范围涉及到比较,交换,查找,遍历操作,复制,修改等等
体积很小,只包括几个在序列上面进行简单教学运算的模板函数
定义了一些模板类,用以声明函数对象
for_each(iterator begin, iterator end, _func) //遍历容器
//普通函数
void Print01(int var)
{
cout << var << " ";
}
//仿函数
class myPrint02
{
void operator() (int val)
{
cout << val << " ";
}
};
vector<int> vctIntVar;
//插入数值
for_each(vctIntVar.begin(), vctIntVar.end(), Print01);
for_each(vctIntVar.begin(), vctIntVar.end(), myPrint02());
transform //搬运容器到另一个容器中
函数原型:
transform(iterator beg1, iterator end1, iterator beg2, _func);
//beg1 源容器开始迭代器
//end1 源容器结束迭代器
//beg2 目标容器开始迭代器
//_func 函数或者函数对象
class Transform
{
public:
int operator()(int v)
{
return v;
}
};
void test01()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
{
v.push_back(i);
}
vector<int> vctTarget;
vctTarget.resize(v.size());//注意:目标容器需要提前开辟空间
transform(v.begin(), v.end(), vctTarget.begin(), Transform());
}
2、常用查找算法
find //查找元素
find_if //按条件查找元素
adjacent_find //查找相邻重复元素
binary_search // 二分法查找
count //统计元素个数
count_if //按照条件统计元素个数
函数原型:
find(iterator beg, iterator end, value);
find_if(iterator beg, iterator end, _func());
adjacent_find(iterator beg, iterator end);//用迭代器接收位置 eg:vector::iterator pos
bool binary_search(iterator beg, iterator end, value);//查找指定元素是否存在,注意:容器必须是有序的序列
int count(iterator beg, iterator end, value);//计数
若是自定义类型,需要重载 == 底层 find 知道如何对比Person 类型数据
bool operator == (const Person& p)
{
if (this->m_Name = p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
//内置数据类型
void test04()
{
vector<int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
//查找 容器中 是否有 5 这个元素
vector<int>::iterator it = find(v.begin(), v.end(), 5);
if (it == v.end())
{
cout << "没有找到" << endl;
}
else
{
cout << "找到:" << *it << endl;
}
}
3、常用排序算法
sort //对容器内元素进行排序
random_shuffle //洗牌 指定范围内的元素随机调整次序
merge //容器元素合并,并存储到另一容器中
reverse //反转指定范围的元素
函数原型:
sort(iterator beg, iteratro end, _Pred);
默认升序
sort(v.begin(), v.end());
//改变为降序
sort(iterator beg1, iterator end1, greater());//包含头 #include
random_shuffle(v.begin(), v.end());//随机种子 srand((unsigned int)time(NULL)); 包含头#include
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);//将两个有序的容器,合并到另一个容器中,还是有序的
reverse(iterator beg1, iterator end1);//反转容器顺序
4、常用拷贝和替换算法
copy //容器内指定范围的元素拷贝到另一容器中
replace //将容器内指定范围的旧元素修改为新元素
replace_if //容器内指定范围满足条件的元素替换为新元素
swap // 互换两个容器的元素
copy(iterator beg1, iterator end1, iterator dest);
replace(iterator beg1, iterator end1, oldvalue, newvalue);
replace_if(iterator beg1, iterator end1, _pred, newvalue);// -pred 谓词
swap(container c1, container c2);
5、常用算术生成算法
accumulate //计算容器元素累计总和
fill //向容器中添加元素
函数原型:
int total = accumulate(iterator beg, iterator end, int value);// 头文件 #include, value: 起始累计值
fill(iterator beg, iterator end, int value);//填充
6、常用集合算法
set_intersection //求两个容器的交集
set_union //求两个容器的并集
set_difference //求两个容器的差集
函数原型:
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
拓展
取容器最小值
vector v1;
vector v2;
min(v1.size(), v2.size());