c++泛型编程入门与STL介绍
从泛型编程到STL
目录
(1)泛型编程和STL介绍
C++泛型编程利用模板(template)实现代码复用,支持类型无关性。通过模板函数(或者模板类)定义通用逻辑,编译时生成具体类型代码,提升效率与灵活性。
STL(标准模板库)是C++泛型编程的经典实现,其核心组件(如容器、算法、迭代器)均基于模板技术构建。泛型编程提供底层机制(如模板元编程、类型参数化),而STL通过封装这些机制,为开发者提供可直接使用的泛型工具集,实现高效、可复用的代码设计,如std::vector<T>等典型泛型容器。
(2)模板函数
1.函数模板介绍
C++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。
凡是函数体结构逻辑相同的函数都可以设计模板函数,而不必定义多个函数,只需在模板中设置相应的逻辑操作即可。
在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
函数模板的特点总结:
- 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
- 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
2.模板函数语法
函数模板泛型类型声明
template < 类型形式参数表 >
// 比如可以像下面这样
template < typename T1 , typename T2 , …… , typename Tn >
template < class T1 , class T2 , …… , class Tn >
在C++中,typename 和 class 在模板声明中通常可以互换使用。在C++模板的早期版本中用class来声明模板参数。当模板变得更加复杂,特别是涉及到嵌套依赖类型时(如typename X::Y),typename 关键字被引入以更明确地表明我们正在引用一个类型。尽管 typename 和 class 在模板声明中通常可以互换使用,但使用 typename 可以使代码更具可读性和明确性,特别是在涉及嵌套依赖类型时。
函数模板创建
注意前面必须紧跟着泛型类型声明,像下面这样
template <类型形式参数表>
类型 函数名(形式参数表)
{
语句序列;
}
函数模板之调用
myswap<float>(a, b); //显示类型调用
myswap(a, b); //自动数据类型推导
特别说明:
- 函数模板定义由泛型类型声明和函数定义组成
- 泛型类型声明的类属参数必须在函数定义中至少出现一次
- 函数参数表中可以使用类属类型参数,也可以使用一般类型参数
3.用法示例
例子1:写n个函数,作用分别是交换两个char类型变量的值、两个int类型之间、两个double类型变量之间的值。现在用函数模板实现。
#include <iostream>
using namespace std;
// 函数的业务逻辑 一样
// 函数的参数类型 不一样
void myswap01(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
void myswap02(char &a, char &b)
{
char c = 0;
c = a;
a = b;
b = c;
}
// 泛型编程
// 让类型参数化
// template 告诉C++编译器 我要开始泛型编程了,看到T, 不要随便报错
template <typename T>
void myswap(T &a, T &b)
{
T c = 0;
c = a;
a = b;
b = c;
}
void main1()
{
{
int x = 10;
int y = 20;
myswap01(x, y);
printf("x:%d y:%d \n", x, y);
}
{
char a = 'a';
char b = 'b';
myswap02(a, b);
printf("a:%c b:%c \n", a, b);
}
return ;
}
// 函数模板的调用,两种调用方式
// (1)显示类型 调用
// (2)自动类型 推导
void main2()
{
{
int x = 10;
int y = 20;
myswap<int>(x, y); //1 函数模板 显示类型 调用
printf("x:%d y:%d \n", x, y);
myswap(x, y); //2 自动类型 推导
printf("x:%d y:%d \n", x, y);
}
{
char a = 'a';
char b = 'b';
//myswap<char>(a, b); //1 函数模板 显示类型 调用
myswap(a, b);
printf("a:%c b:%c \n", a, b);
}
}
int main()
{
// 不使用函数模板
main1();
// 使用函数模板
main2();
return 0;
}
例子2:交换两种不同的类型
#include <iostream>
using namespace std;
template <typename T, typename T2>
void myswap(T &x, T2 &y)
{
T c = x;
T2 d = y;
x = (T)d;
y = (T2)c;
}
int main()
{
int a = 1;
char b = 12;
myswap(a, b);
printf("a=%d, b=%d", a, b);
return 0;
}
例子3:对不同类型的数组进行排序
#include <iostream>
using namespace std;
#include <string.h>
//让你对字符数组或者int数组都可以进行排序
template <typename T,typename T2 >
int mySort(T *array, T2 size)
{
T2 i, j;
T tmp;
if (array == NULL)
{
return -1;
}
//选择
for (i=0; i<size; i++)
{
for (j=i+1; j<size; j++)
{
if (array[i] < array[j])
{
tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
return 0;
}
template <typename T, typename T2>
int myPrint(T *array, T2 size)
{
T2 i = 0;
for (i=0; i<size; i++)
{
cout << array[i] << " ";
}
cout << endl;
return 0;
}
int main()
{
// int类型
{
int myarray[] = {11, 33, 44, 33,22, 2, 3, 6, 9};
int size = sizeof(myarray)/sizeof(*myarray);
mySort<int, int> (myarray, size);
printf("int: after sort\n");
myPrint(myarray, size);
}
//char 类型
{
char buf[] = "aff32ff2232fffffdssss";
int len = strlen(buf);
mySort<char, int>(buf, len);
printf("char: after sort\n");
myPrint<char , int>(buf, len);
}
return 0;
}
4.函数模板遇上函数重载
函数模板可以像普通函数一样被重载,函数模板和普通函数同名在一起时,调用规则:
- C++编译器优先考虑普通函数
- 普通函数不能完全匹配时,考虑函数模板,如果函数模板可以产生一个匹配,那么选择模板,注意函数模板不允许自动类型转化
- 如果函数模板还匹配不上,则会考虑普通函数进行进行自动类型转换,且选择转化次数最少的那个函数
- 另外,可以通过空模板实参列表的语法限定编译器只通过模板匹配
示例代码理解
#include <iostream>
// 函数模板
template <typename T>
void myswap(T &a, T &b)
{
std::cout << "This is template func !" << std::endl;
T c = 0;
c = a;
a = b;
b = c;
}
// 普通函数1
void myswap(int m, char n)
{
std::cout << "This is normal func: int + char" << std::endl;
}
// 普通函数2
void myswap(int x, int y)
{
std::cout << "This is normal func: int + int" << std::endl;
}
int main()
{
int a = 10;
char c = 'z';
{
// 1.优先考虑普通函数
myswap(a, c);
// This is normal func: int + char
}
{
// 2.普通函数找不到,发现没有可匹配的类型,考虑函数模板
// 函数模板函数的调用: 将严格的按照类型进行匹配,不会进行自动类型转换
myswap(c, c);
// This is template func !
}
{
// 3.优先考虑普通函数,发现没有可匹配的类型
// 于是考虑函数模板,但这里的函数模板须要求形参和实参的类型相同
// 于是再次调用普通函数,进行隐式的类型转换,且选择转换次数最少的那个函数
myswap(c, a);
// This is normal func: int + int
}
{
// 4.可以通过空参数列表来限定使用函数模板
myswap<>(a, a);
// This is template func !
myswap(a, a);
// This is normal func: int + int
}
return 0;
}
(3)模板类
1.类模板说明
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,则可以创建一个类模板。
语法
template <类型形式参数表>
类声明
例如
template<typename T>
class TClass
{
private:
T DateMember;
};
说明:
- 类模板用于实现类所需数据的类型参数化
- 类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响
- 类属参数必须至少在类说明中出现一次
- 类模板在实例化时必须声明类型,比如TClass<int> varName,而不能TClass varName;
- 类模板在做函数形参时也必须声明类型
2.单个模板类
类模板的定义和使用
创建对象初始化时必须进行类型指定,假设A是一个模板类,则A m_book(m) 会报错,要 A<int> m_book(m)这样才不会报错。
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T a)
{
this->x = a;
}
void printA()
{
cout << "x=" << x << endl;
}
protected:
private:
T x;
};
int main()
{
int m = 10;
// A m_book(m) // 会报错,类型不清楚,C++编译器不知道怎么分配内存
A<int> m_book(m); // 只有数据类型固定下来,才知道如何分配内存
m_book.printA();
return 0;
}
类模板做函数形参
void useA(A<int> &a),其中A为一个模板类
C++编译器 要求具体的类 所以所 要 A<int> &a
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T a)
{
this->x = a;
}
void printA()
{
cout << "x=" << x << endl;
}
protected:
private:
T x;
};
//参数,C++编译器 要求具体的类 所以所 要 A<int> &a
void useA(A<int> &a)
{
a.printA();
}
int main()
{
int m = 10;
A<int> m_book(m);
m_book.printA();
// 类模板做参数
useA(m_book);
return 0;
}
3.模板类的继承
从模板类派生普通类
从模板类 可以派生 普通类
模板类派生时, 需要具体化模板类,C++编译器需要知道 父类的数据类型具体是什么样子的
因为需要知道父类所占的内存大小是多少 只有数据类型固定下来,才知道如何分配内存
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t)
{
this->x = t;
}
void printA()
{
cout << "x=" << x << endl;
}
protected:
T x;
};
// 需要明确父类A的数据类型
class B: public A<int>
{
public:
B(int m = 10, int n = 20): A<int> (m)
{
this->y = m + n;
}
void printB()
{
cout << "x=" << x << ", y=" << y << endl;
}
private:
int y;
};
int main()
{
int m = 10;
A<int> m_book(m);
m_book.printA();
B m_math_book(40, 50);
m_math_book.printB();
return 0;
}
从模板类派生模板类
子类也是模板类,对父类用抽象类型声明就可。
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t)
{
this->x = t;
}
void printA()
{
cout << "x=" << x << endl;
}
protected:
T x;
};
template <typename T2>
class C: public A<T2>
{
public:
C(T2 c, T2 a): A<T2>(a)
{
this->c = c;
}
void printC()
{
cout << "c=" << c << endl;
}
protected:
T2 c;
};
int main()
{
C<int> c1(10, 20);
c1.printC();
return 0;
}
4.模板类的三种代码风格
①所有的类模板函数写在类的内部
#include <iostream>
using namespace std;
template <typename T>
class Complex
{
public:
// 友元函数
// 友元函数的正规写法是在类内部声明,在类外部具体实现,而这里都是在类内部完成了,也可以
friend Complex MySub(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
// 友元函数
friend ostream & operator<<(ostream &out, Complex &c3)
{
if (c3.b == 0)
{
out << c3.a << endl;
}
else if (c3.b > 0)
{
out << c3.a << "+" << c3.b << "i";
}
else
{
out << c3.a << "-" << -c3.b << "i";
}
return out;
}
public:
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
public:
Complex operator+(Complex &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
void printCom()
{
if (b == 0)
{
cout << a << endl;
}
else if (b > 0)
{
cout << a << "+" << b << "i" << endl;
}
else
{
cout << a << "-" << -b << "i" << endl;
}
}
private:
T a;
T b;
};
int main()
{
// 需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
// 运算符 + 已被重载
Complex<int> c3 = c1 + c2;
c3.printCom(); // 4+6i
// 运算符 << 已被重载
cout << c3 << endl; // 4+6i
// 滥用友元函数
// 为啥叫滥用?因为友元函数的声明和定义都在类的内部
{
Complex<int> c4 = MySub(c1, c2);
cout << c4 << endl; //-2-2i
}
return 0;
}
②所有的类模板函数的实现写在类的外部,在一个cpp中
注意:在声明时或者函数名时函数的参数值和返回值类型要具体化,即要加上<T>
#include <iostream>
using namespace std;
// 模板类必须要有前置声明
template <typename T>
class Complex;
// 模板函数作为友元函数必须要有前置声明
template <typename T>
// 在声明时或者函数名时函数的参数值和返回值类型要具体化,即要加上<T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2);
// 类的函数在内部只声明
template <typename T>
class Complex
{
// 友元函数,注意MySub<T>
friend Complex<T> MySub<T>(Complex<T> &c1, Complex<T> &c2);
// 友元函数对<<运算符重载 同时再叠加 模板参数,会出问题,这是c++编译器的bug
// friend ostream & operator<<(ostream &out, Complex<T> &c3);
// friend ostream & operator<< <T>(ostream &out, Complex<T> &c3);
// 所以这里还是在内部实现
friend ostream & operator<<(ostream &out, Complex &c3)
{
if (c3.b == 0)
{
out << c3.a << endl;
}
else if (c3.b > 0)
{
out << c3.a << "+" << c3.b << "i";
}
else
{
out << c3.a << "-" << -c3.b << "i";
}
return out;
}
public:
Complex(T a, T b);
void printCom();
Complex operator+(Complex &c2);
private:
T a;
T b;
};
// 类成员函数的具体实现
// 成员函数 构造函数
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
// 成员函数 printCom()
template <typename T>
void Complex<T>::printCom()
{
if (this->b == 0)
{
cout << this->a << endl;
}
else if (this->b > 0)
{
cout << this->a << "+" << this->b << "i" << endl;
}
else
{
cout << this->a << "-" << -this->b << "i" << endl;
}
}
// 成员函数 实现 + 运算符重载
template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
// 友元函数 MySub
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
int main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
// +运算符的重载
Complex<int> c3 = c1 + c2;
c3.printCom(); // 4+6i
// <<运算符的重载
cout << c3 << endl; // 4+6i
//调用友元函数
{
Complex<int> c4 = MySub<int>(c1, c2);
c4.printCom(); // -2-2i
cout << c4 << endl; // -2-2i
}
return 0;
}
③所有的类模板函数写在类的外部,在不同的.h和.cpp中
complex.h
// this file: complex.h
#pragma once
#include <iostream>
using namespace std;
// 类的前置声明
template <typename T>
class Complex;
// 友元函数前置声明
template <typename T>
// 在声明时或者函数名时函数的参数值和返回值类型要具体化,即要加上<T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2);
template <typename T>
class Complex
{
friend Complex<T> MySub<T>(Complex<T> &c1, Complex<T> &c2);
// friend ostream & operator<< <T>(ostream &out, Complex &c3);
friend ostream & operator<<(ostream &out, Complex &c3)
{
if (c3.b == 0)
{
out << c3.a << endl;
}
else if (c3.b > 0)
{
out << c3.a << "+" << c3.b << "i";
}
else
{
out << c3.a << "-" << -c3.b << "i";
}
return out;
}
public:
Complex(T a, T b);
void printCom();
Complex operator+(Complex &c2);
private:
T a;
T b;
};
complex.hpp
// this file: complex.hpp
#include <iostream>
using namespace std;
#include "complex.h"
//构造函数的实现 写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
if (this->b == 0)
{
cout << this->a << endl;
}
else if (this->b > 0)
{
cout << this->a << "+" << this->b << "i" << endl;
}
else
{
cout << this->a << "-" << -this->b << "i" << endl;
}
}
template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c2)
{
Complex tmp(a+c2.a, b+c2.b);
return tmp;
}
// 友元函数
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
main.cpp
#include <iostream>
using namespace std;
#include "complex.hpp"
int main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
c3.printCom(); // 4+6i
cout << c3 << endl; // 4+6i
//滥用友元函数
{
Complex<int> c4 = MySub<int>(c1, c2);
cout << c4 << endl; // -2-2i
}
cout<<"hello..."<<endl;
return 0;
}
5.模板类中的static成员
也需要在类的外部进行唯一的那次初始化
#include <iostream>
using namespace std;
// 普通类中的static成员
class AA1
{
public:
static int m_a;
protected:
private:
};
int AA1::m_a = 0;
class AA2
{
public:
static char m_a;
protected:
private:
};
char AA2::m_a = 0;
// 类模板中的static成员
template <typename T>
class AA
{
public:
static T m_a;
protected:
private:
};
template <typename T>
T AA<T>::m_a = 0;
int main()
{
AA<int> a1, a2, a3;
a1.m_a = 10;
a2.m_a ++;
a3.m_a ++;
cout << AA<int>::m_a << endl; // 12
AA<char> b1, b2, b3;
b1.m_a = 'a';
b2.m_a ++;
b2.m_a ++ ;
cout << AA<char>::m_a << endl; // c
cout << a1.m_a << endl; // 12
AA<int> c1, c2, c3;
c1.m_a ++;
cout << AA<int>::m_a << endl; // 13
cout << AA<char>::m_a << endl; // c
cout << c1.m_a << endl; // 13
// 结论:m_a 应该是 每一种类型的类 使用自己的m_a
return 0;
}
(4)模板编译机制
- 编译器并不是把模板函数处理成能够处理任意类型的函数
- 编译器会根据模板函数以及模板函数调用时的具体类型来生成不同的函数
- 编译器会对模板函数进行两次编译
- 在声明的地方对模板代码本身进行编译,会简单判断模板函数的是否存在语法错误;
- 调用的地方对参数替换后的代码进行编译。根据具体的调用参数类型生成相应的普通函数原型,多次调用时若类型不同,则会产生多个普通函数原型。
(5)STL基础概念
STL是什么
STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称。现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间。STL里几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。
STL有六大组件:容器、算法、迭代器、仿函数、适配器、分配器。其中algorithm(算法)、container(容器)和iterator(迭代器),几乎贯穿始终,容器和算法通过迭代器可以进行无缝地连接。
在C++标准中,STL被组织为下面的13个头文 件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>。
STL六大组件
–容器(Container)
–算法(Algorithm)
–迭代器(Iterator)
–仿函数(Function object)
–适配器(Adaptor)
–分配器(allocator)
①容器
C++标准模板库(STL)提供了多种容器,按数据结构可分为序列容器、关联容器和无序容器。
容器分为哪些种类?
①序列容器(顺序存储,支持动态调整)
|
序列容器 |
特点 |
典型场景 |
|
std::vector |
动态数组,连续内存,随机访问快(O(1)),尾部插入/删除高效(O(1)),中间插入/删除慢(O(n))。 |
需要频繁随机访问的场景(如矩阵运算)。 |
|
std::deque |
双端队列,分段连续内存,支持头部/尾部高效插入/删除(O(1)),随机访问较快(O(1))。 |
需要频繁在首尾操作的场景(如队列优化)。 |
|
std::list |
双向链表,非连续内存,插入/删除高效(O(1)),但随机访问慢(O(n)),不支持随机迭代器。 |
需要频繁在任意位置插入/删除的场景(如LRU缓存)。 |
|
std::forward_list |
单向链表,比list更节省内存,但仅支持单向遍历。 |
内存敏感且仅需单向遍历的场景。 |
|
std::array |
固定大小数组,编译时确定大小,支持随机访问(O(1)),无动态内存分配。 |
已知大小且需高效访问的场景(如小规模数据)。 |
②关联容器(基于红黑树,有序存储)
|
关联容器 |
特点 |
典型场景 |
|
std::set |
元素唯一且有序,插入/删除/查找复杂度均为O(log n),支持反向迭代器。 |
需要有序且去重的集合(如字典词库)。 |
|
std::multiset |
允许重复元素,其余同set。 |
需要有序但允许重复的集合(如分数统计)。 |
|
std::map |
键值对(key-value),键唯一且有序,查找复杂度O(log n)。 |
需要键值映射且有序的场景(如电话簿)。 |
|
std::multimap |
允许重复键,其余同map。 |
需要键值映射但允许重复的场景(如多值映射)。 |
③无序容器(基于哈希表,无序存储)
|
无序容器 |
特点 |
典型场景 |
|
std::unordered_set |
元素唯一且无序,插入/删除/查找平均复杂度O(1),最坏O(n)。 |
需要快速查找且无需排序的场景(如黑名单过滤)。 |
|
std::unordered_multiset |
允许重复元素,其余同unordered_set。 |
需要快速查找但允许重复的场景(如词频统计)。 |
|
std::unordered_map |
键值对(key-value),键唯一且无序,平均复杂度O(1)。 |
需要快速键值查找的场景(如缓存系统)。 |
|
std::unordered_multimap |
允许重复键,其余同unordered_map。 |
需要快速键值查找但允许重复的场景(如用户标签)。 |
④容器适配器(基于其他容器封装)
|
容器 |
底层容器 |
特点 |
典型场景 |
|
std::stack |
默认deque |
后进先出(LIFO),仅支持push/pop/top操作。 |
函数调用栈、表达式求值。 |
|
std::queue |
默认deque |
先进先出(FIFO),仅支持push/pop/front/back操作。 |
任务调度、广度优先搜索(BFS)。 |
|
std::priority_queue |
默认vector + 堆 |
最大堆(默认),支持push/pop/top,元素按优先级出队。 |
任务优先级调度、Dijkstra算法。 |
②迭代器
迭代器在STL中用来将算法和容器联系起来,起着一种黏和剂的作用。几乎STL提供的所有算法都是通 过迭代器存取元素序列进行工作的,每一个容器都定义了其本身所专有的迭代器,用以存取容器中的元素。
迭代器部分主要由头文件<utility>,<iterator>和<memory>组 成。<utility>是一个很小的头文件,它包括了贯穿使用在STL中的几个模板的声明,<iterator>中提供了迭代器 使用的许多方法,而对于<memory>的描述则十分的困难,它以不同寻常的方式为容器中的元素分配存储空间,同时也为某些算法执行期间产生 的临时对象提供机制,<memory>中的主要部分是模板类allocator,它负责产生所有容器中的默认分配器。
迭代器分为哪些种类吗?分别是输入迭代器(Input Iterator)、输出迭代器(Output Iterator)、前向迭代器(Forward Iterator)、双向迭代器(Bidirectional Iterator)和随机访问迭代器(Random Access Iterator)。其中输入和输出迭代器分别用于读取和写入数据,前向迭代器只能向前访问而不能向后访问(forward_list),双向迭代器既可向前也可向后(list),随机访问迭代器可以通过下标访问任何合法的位置(vector)。
③适配器
适配器是做什么的?
适配器是一种设计模式,主要起到将不同的接口统一起来形成一种更方便更通用更兼容的接口。STL中的适配器用于将一个类的接口转换成客户端所期望的另一种接口。
适配器分类?主要分为容器适配器、迭代器适配器和仿函数适配器。
- 容器适配器:通过改变底层容器的接口来提供不同的数据结构,包括std::stack(后进先出,默认基于std::deque实现)、std::queue(先进先出,默认基于std::deque实现)、std::priority_queue(优先队列,默认基于std::vector实现)。它们隐藏了底层容器的具体实现细节,只提供特定的接口。
- 迭代器适配器:提供了一种方式来转换和组合不同的迭代器类型,例如std::reverse_iterator(使得迭代器可以以相反的顺序进行迭代)、std::istream_iterator和std::ostream_iterator(用于将流与容器的输入输出操作适配起来)、插入迭代器(包括头插法迭代器、尾插法迭代器、中间插入法迭代器)。
- 仿函数适配器:在C++11及以后的版本中,std::bind、std::not1、std::mem_fn等函数对象适配器的使用场景有所变化,std::bind的使用场景减少,因为Lambda表达式提供了更简洁和灵活的替代方案,std::not1和std::not2也被Lambda表达式取代。
④分配器
分配器是什么?
分配器主要用于内存的分配与释放。一般容器都会自带默认分配器,很少会自己实现分配器。
⑤仿函数
仿函数是什么?
在C++中,仿函数(Functor,也称为函数对象)是一种通过重载operator()实现的对象,其行为类似于函数。但与普通函数相比,有更多的优点,比如仿函数可以将复杂逻辑封装为可调用的对象,不仅可以实现相关函数功能,还可以统计函数运行过程中的各种属性等,当然还有更多高级用法。
#include <iostream>
using namespace std;
// 仿函数:计算累加和(支持状态保存)
class Accumulator {
private:
int sum = 0;
public:
void operator()(int value) {
sum += value;
cout << "当前累加和: " << sum << endl;
}
};
int main() {
Accumulator acc;
acc(10); // 输出: 当前累加和: 10
acc(20); // 输出: 当前累加和: 30
return 0;
}
⑥算法
STL提供了大约100个实现算法的模版函数,比如算法for_each将为指定序列中的每一个元素调用指定的函数,stable_sort以 你所指定的规则对序列进行稳定性排序等等。这样一来,只要熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要 的功能并大大地提升效率。
算法部分主要由头文件<algorithm>,<numeric>和<functional>组 成。<algorithm>是所有STL头文件中最大的一个(尽管它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上 都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。<numeric>体积很 小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。<functional>中则定义了一些模板类, 用以声明函数对象。
end
更多推荐
所有评论(0)