C++ 提高编程
主要针对C++泛型编程和STL技术
一、 模板
1、 概念
模板就是建立通用的模具,大大提高代码的复用性
模板特点
模板不可以直接使用,它只是一个框架
模板的通用并不是万能的
2、 函数模板
C++ 另一种编程思想为泛型编程,主要利用的技术就是模板
C++ 提供两种模板机制:函数模板 和 类模板
2.1 函数模板语法
函数模板的作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体确定,用一个虚拟的类型来代表
语法
template<typename T>
函数声明或定义
参数
template:声明创建模板
typename:表明其后面的符号是一种数据类型,可以用class来代替
T:通用的数据类型,名称可以替换,通常为大写字母
// 两个整型交换函数
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
// 交换浮点型的函数
void swap(double& a, double& b)
{
double temp = a;
a = b;
b = temp;
}
// 函数模板
template <typename T> // 声明模板,告诉编译器后面代码紧跟着T,不要报错,T是一个通用的数据类型
void m_swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test()
{
int a = 1;
int b = 3;
double a1 = 4;
double b1 = 5;
/* swap(a, b);
cout << a << b << endl;
swap(a1, b1);
cout << a1 << b1 << endl; */
// 使用函数模板
// 1、 自动推导
m_swap(a, b);
cout << a << b << endl;
// 2、 显示指定类型
m_swap<int>(a, b);
cout << a << b << endl;
}
模板可以将数据类型参数化
模板的使用方法
自动推导
显示指定类型
2.2 注意事项
注意事项
自动推导数据类型,必须推导出一致的数据类型 T,才可以使用
模板必须要确定出 T 的数据类型,才可以使用
2.3 普通函数和函数模板的区别
普通函数调用时可以发生自动类型转换(隐式类型装换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型装换
如果利用显示指定类型的方法,可以发生隐式类型转换
2.4 普通函数和函数模板的调用规则
调用规则如下
如果函数模板和普通函数都可以实现,优先调用普通函数
可以通过空模板参数列表强制调用函数模板
void myPrint(int a, int b)
{
cout << a << b << endl;
cout << "普通函数" << endl;
}
template<typename T>
void myPrint(T a, T b)
{
cout << a << b << endl;
cout << "模板函数" << endl;
}
void test()
{
int a = 10;
int b = 20;
myPrint<>(a, b); // 空模板参数列表调用模板函数
}
函数模板也可以发生重载
如果函数模板可以产生更好的匹配模式,优先调用函数模板
void myPrint(int a, int b)
{
cout << a << b << endl;
cout << "普通函数" << endl;
}
template<typename T>
void myPrint(T a, T b)
{
cout << a << b << endl;
cout << "模板函数" << endl;
}
void test()
{
char a = 'a';
char b = 'b';
myPrint(a, b); // 函数模板可以产生更好的匹配
}
既然提供了函数模板,最好不要提供普通函数,否则容易出现二义性
2.5 模板的局限性
模板的通用性并不是万能的
如果传入的是一个元组以及自定义数据类型,就无法实现了
因此,C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化模板
// 模板重载
// 对比两个数据是否相等
class Person
{
public:
Person(string name, int age)
{
m_Age = age;
m_Name = name;
}
string m_Name;
int m_Age;
};
template<class T>
bool myCompare(T& a, T& b) // 如果传入的是一个自定义数据类型呢
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
// 利用具体化Person的版本实现代码,具体化优先调用
// 也可以使用运算符重载
template<>bool myCompare(Person& p1, Person& p2)
{
if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
{
return true;
}
else
{
return false;
}
}
void test()
{
Person p1("Tom", 10);
Person p2("Tom", 10);
cout << myCompare(p1, p2) << endl;
}
学习模板并不是为了写模板,而是在STL中能够运用系统提供的模板
3、 类模板
3.1 类模板语法
类模板作用
建立一个通用类,类中成员数据类型可以不具体制定,用一个虚拟的类型代表
语法
template<typename T>
参数
template:声明创建模板
typename:表明其后面的符号是一种数据类型,可以用class来代替
T:通用的数据类型,名称可以替换,通常为大写字母
template<typename NameT, typename AgeT>
class Person
{
public:
Person(NameT name, AgeT age)
{
m_Age = age;
m_Name = name;
}
NameT m_Name;
AgeT m_Age;
};
void test()
{
Person<string, int>("Tom", 30); // 调用-只有一种调用方式
}
3.2 类模板和函数模板的区别
类模板与函数模板区别主要有两点
类模板没有自动类型推导的使用方式
类模板在模板参数列表中可以有默认参数
template<typename NameT, typename AgeT = int> // 默认参数
class Person
{
public:
Person(NameT name, AgeT age)
{
m_Age = age;
m_Name = name;
}
NameT m_Name;
AgeT m_Age;
};
void test()
{
Person<string>("Tom", 30);
}
3.3 使用时机
类模板中成员函数和普通类中成员函数创建时机是有区别的
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建
class Person1
{
public:
void show()
{
cout << "Person1" << endl;
}
};
template<typename T>
class Person
{
public:
// 没调用,其不会编译,因为无法确定T的数据类型
T p1;
void func1()
{
p1.show();
}
};
void test()
{
Person<Person1> p;
p.func1();
}
3.4 类模板对象函数做参数
类模板实例出的对象,向函数传参
一共有三种传入方式
指定传入的数据类型:直接显示对象的数据类型
// 类模板做函数的参数
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
m_Name = name;
m_Age = age;
}
T1 m_Name;
T2 m_Age;
void showPerson()
{
cout << "name:" << m_Name << " age:" << m_Age << endl;
}
};
// 指定传入类型
void printPerson1(Person<string, int> &p)
{
p.showPerson();
}
// 参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
p.showPerson();
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
// 整个类模板化
template<class T>
void printPerson3(T &p)
{
p.showPerson();
}
void test()
{
Person<string, int> p("Tom", 12);
printPerson1(p);
printPerson2(p);
printPerson3(p);
}
二、 STL 初识
1、 基本概念
STL 基本模板库
STL 从广义上分为容器、算法和迭代器
容器和算法事件通过迭代器无缝连接
STL 几乎所有的代码都采用了模板类或模板函数
2、 STL 六大组件
STL 大体分为六大组件:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
容器:各种数据结构:vector、list、deque、set、map等,用来存放数据
算法:各种常用的算法,如sort、find、copy、for_each等
迭代器:扮演了容器和算法之间的胶合剂
仿函数:行为类似的函数,可作为算法的某种策略
适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
空间配置器:负责空间的配置和管理
2.1 容器、算法、迭代器
容器:置物之所也
STL 容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组、列表、树、栈、队列、集合、映射表等
这些容器分为序列式容器和关联式容器两种
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
算法:问题之解也
有限的步骤,解决逻辑或数学上的问题,这叫做算法
算法分为:质变算法和非质变算法
质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
迭代器:容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含有的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针
迭代器种类
常用的容器中迭代器种类为双向迭代器和随机访问迭代器
3、 迭代器初始
3.1 vector 存放内置数据类型
容器:vector
算法:for_each
迭代器:vector::iterator
#include <vector> // vector 头文件
#include <algorithm> // 标准算法头文件
void printVector(int value)
{
cout << value << endl;
}
// vector 存放内置数据类型
void test()
{
// 创建一个 vector 容器——数组
vector<int> v;
// 向容器中插入数据
v.push_back(10); // 尾插数据
v.push_back(11);
v.push_back(12);
// 通过迭代器访问容器中的数据
vector<int>::iterator itBegin = v.begin(); // 起始迭代器,指向容器中第一个元素,当做指针使用
vector<int>::iterator itEnd = v.end(); // 结束迭代器,指向容器最后一个元素的下一个位置
// 第一种遍历方式
while (itBegin != itEnd)
{
cout << *itBegin << endl;
itBegin++;
}
// 第二种遍历方式
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << endl;
}
// 第三种遍历方式
for_each(v.begin(), v.end(), printVector); // 回调函数
}