目录
1. 模板
1.1 泛型编程
- 当我们要实现两个元素交换的时候,由于不知道要交换什么类型的两个值,可能要写多个参数不同的重载函数,例如:
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
- 即使我们使用函数的重载能够实现函数的功能,但是这样写代码会显得比较冗余,那我们能不能写一个模板来让编译器帮我们生成代码呢? 答案是可以的。
1.2 函数模板
1.2.1 函数模板概念
- 函数模板与函数参数类型无关,编译器不会直接调用模板,而是利用模板实例化出一个函数,然后在调用。
1.2.2 函数模板格式
- 函数模板通常采用 template<typename T1, typename T2......>的形式,这里的 typename 可以换成 class,例如:
template <typename T>
void Swap(T& x, T& y)
{
T t = x;
x = y;
y = t;
}
int main()
{
int a1 = 10, a2 = 20;
Swap(a1, a2);
return 0;
}
1.2.3 函数模板的原理
- 函数模板实际上是编译器检测你所传入的参数的类型,然后自动帮你生成一份函数,之后再调用。
1.2.4 显式实例化
-
那如果我们传入的参数类型不同那该怎么办呢?比如:
template <class T>
T Add(T& left, T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 1.1, d2 = 2.2;
Add(a1, d1);
return 0;
}
这个时候编译器会编译不通过,因为当你传入a1 的 时候,编译器自动会把 T 推演成 int 类型, 但是看到 double 又会把 T 推演成 double 类型,编译器不知道该听谁的,所以会报错,那该如何处理呢? 这里给出三种方法:
- 强制类型转换:
template <class T> // 注意这里的第二个参数要加上const T Add(T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 1.1, d2 = 2.2; Add(a1, (int)d1); return 0; }
由于在强制类型转换的过程中会产生临时变量,并且临时变量具有常性,不可被修改,而且传的是引用,因此要加上const。
- 多加一个模板参数类型
template <class T1, class T2> T2 Add(T1& left, T2& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 1.1, d2 = 2.2; Add(a1, d1); return 0; }
- 显示实例化
template <class T> // 这边的const也是类似的道理 T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 1.1, d2 = 2.2; Add<double>(a1, d1); return 0; }
显示实例化会强制编译器把 T 推演成某个类型,格式就例如:Add<double>(a1, d1);
1.2.5 模板参数的匹配原则
-
一个模板和一个与模板同名的函数可以同时存在,并且模板也可以生成与这个函数参数类型相同的函数,但是如果存在同名函数,且参数类型和要传入的参数类型相同,会首先调用这个函数,如果想让编译器自己生成,就可以用显示实例化,例如:
template <class T>
T Add(const T& left, T& right)
{
return left + right;
}
int Add(int left, int right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
Add(a1, a2);
Add<int>(a1, a2);
return 0;
}
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板,例如:
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换:
// 因为普通函数可以进行自动类型转换,因此不需要加上const
int Add(int left, int right)
{
return left + right;
}
int main()
{
int a1 = 10;
double d2 = 20;
Add(a1, d2);
return 0;
}
1.3 类模板
1.3.1 类模板定义格式
- template<class T1, class T2, ..., class Tn>
如下是实现简单的栈类型个类模板:
template<class T>
class Stack
{
public:
Stack(int capacity)
:_a(new T[capacity])
,_top(0)
,_capacity(capacity)
{}
void PushBack(const T& val)
{
//CheckCapacity()
_a[_top++] = val;
}
~Stack()
{
delete[] _a;
_top = _capacity = 0;
}
private:
T* _a;
int _top;
int _capacity;
};
- 如果要声明定义分离的话,要加入模板参数列表
template<class T>
void Stack<T>::PushBack(const T& val)
{
//CheckCapacity()
_a[_top++] = val;
}
template<class T>
Stack<T>::Stack(int capacity)
:_a(new T[capacity])
, _top(0)
, _capacity(capacity)
{}
1.3.2 类模板的实例化
int main()
{
Stack<int> st1(8);
Stack<double> st2(4);
return 0;
}
2. STL —— string类
2.1 STL 简介
STL(Standard Template Library)是C++标准库中的一个重要组成部分,提供了丰富的通用数据结构和算法模板。STL的设计目标是提供可复用、高效和类型安全的组件,以便开发人员可以方便地处理各种常见的数据结构和算法问题。
STL主要由以下几个组件组成:
容器(Containers):STL提供了一系列的容器类模板,如
vector
、list
、deque
、set
、map
等。这些容器类模板封装了常见的数据结构,如数组、链表、双向队列、集合和映射等,开发人员可以根据需要选择合适的容器来存储和组织数据。算法(Algorithms):STL提供了一组常用的算法模板,如排序、查找、合并、删除、遍历等。这些算法可以用于不同的容器,并且具有高度的可组合性,开发人员可以将不同的算法组合在一起以实现复杂的操作。
迭代器(Iterators):迭代器是STL中用于遍历容器元素的对象,它提供了一种通用的访问方式,使得算法可以独立于容器进行操作。STL提供了多种类型的迭代器,包括输入迭代器、输出迭代器、正向迭代器、双向迭代器和随机访问迭代器,每种迭代器都具有不同的特性和功能。
函数对象(Function Objects):函数对象是可调用对象,可以像函数一样使用。STL中的算法可以接受函数对象作为参数,以实现不同的操作。STL提供了一些标准的函数对象,如谓词(Predicate)、函数适配器(Function Adapters)等,同时也支持自定义的函数对象。
分配器(Allocators):STL中的容器使用分配器来管理内存的分配和释放。分配器允许开发人员自定义内存管理策略,以满足特定的需求,如内存池分配器、定制的内存分配器等。
STL的设计理念是以泛型编程为核心,通过使用模板和参数化类型,提供通用的、可复用的组件。STL组件之间的协调和配合使得开发人员能够以更高的抽象层次进行编程,从而提高开发效率并降低代码复杂性。
使用STL可以使得C++开发人员能够更加专注于解决问题本身,而不必过多关注底层的数据结构和算法实现。STL已经成为C++编程中的重要工具和标准实践,广泛应用于各个领域的软件开发。
2.2 标准库中的string类
- cplusplus 网站给出的 string 底层的介绍:
string - C++ Reference (cplusplus.com)https://legacy.cplusplus.com/reference/string/string/
2.2.1 string类的构造函数
- cplusplus 网站给出的string类构造函数的介绍:
- 对应的构造函数的用法:
- 注意这里的 "<<" 和 ">>" 重载库中已经给出,所以可以直接用:
int main()
{
string s0; // (1)
string s1("hello world"); // (4)
string s2(s1); // (2)
string s3(s1, 5); // (3)
string s4("hello world", 5); // (5)
string s5(6, '#'); // (6)
cout << s0 << endl;
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
return 0;
}
2.3 string类容量操作
size返回字符串有效长度。
2.4 遍历string类
2.4.1 operator[]
- string类中已经把[]重载了,可以获取pos位置的元素。
- string类中的容量操作有一个size,可以返回字符串有效字符长度。
- begin() 和 end() 在迭代器中,begin获取第一个字符的迭代器,end获取最后一个字符下一个位置的迭代器。
int main()
{
string s0; // (1)
string s1("hello world"); // (4)
string s2(s1); // (2)
for (size_t i = 0; i < s2.size(); ++i)
{
cout << s2[i] << " ";
}
cout << endl;
for (size_t i = 0; i < s2.size(); ++i)
{
s2[i]++;
}
cout << endl;
for (size_t i = 0; i < s2.size(); ++i)
{
cout << s2[i] << " ";
}
cout << endl;
return 0;
}
2.4.2 迭代器iterator
- 迭代器的用法类似于指针,在string中可能使用[]比较方便,但是在 链表,二叉树等链式结构就得用迭代器了。
int main()
{
string s0; // (1)
string s1("hello world"); // (4)
string s2(s1); // (2)
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
it = s2.begin();
while (it != s2.end())
{
*it += 5;
++it;
}
cout << endl;
it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
return 0;
}
2.4.3 范围for
int main()
{
string s0; // (1)
string s1("hello world"); // (4)
string s2(s1); // (2)
for (auto e : s2)
{
cout << e << " ";
}
return 0;
}