【学习笔记】C++STL和泛型编程-侯捷

文章目录

  • C++标准库:体系结构与内核分析
  • 第一讲
    • 1.header、版本和重要资源
    • 2.STL体系结构介绍
      • 2.1 STL六大部件(Components)
      • 2.2 STL容器的前闭后开区间
      • 2.3 基于范围的for循环 ranged-base for
      • 2.4 auto关键字
    • 3.容器的结构与分类
    • 4.各种STL容器的测试
      • 4.1 vector容器的测试
      • 4.2 list和forward_list容器的测试
      • 4.3 deque容器的测试
      • 4.4 stack和queue的测试
      • 4.5 multiset和multimap的测试
      • 4.6 unordered_multiset和unordered_multimap的测试
    • 5.分配器allocator的测试
  • 第二讲
    • 6.STL源代码的分布(VC++ 6.0、g++ 4.9.2)
    • 7.面向对象编程(OOP) vs. 泛型编程(GP)
    • 8.STL源码学习基础:操作符重载、模板(泛化、全特化、偏特化)
      • 8.1 操作符重载
      • 8.2 模板(泛化、全特化、偏特化)
        • 8.2.1 类模板 class template
        • 8.2.2 函数模板 function template
        • 8.2.3 成员模板 member template
      • 8.3 特化 specialization
        • 8.3.1 类模板的全特化 template specialization
        • 8.3.2 类模板的偏特化/局部特化 partial specialization
          • 个数的偏特化
          • 范围的偏特化


学习笔记源自博览网侯捷大师的C++课程,在原视频及讲义的基础上填充注释。
如有侵权,请联系删除,抱歉。


C++标准库:体系结构与内核分析

C++ Standard Library —— architecure & sources

目标:根据源代码分析C++ STL的体系结构(STL是泛型编程GP的代表),以STL为目标深层次地探讨泛型编程。

  • Level 0 :使用C++标准库
  • Level 1 :深入认识C++标准库
  • Level 2 :良好使用C++标准库
  • Level 3 :扩充C++标准库

C标准库是功能相互独立的函数库;

C++标准库分为6个主要部件(容器、算法、迭代器、分配器、适配器、仿函数),彼此关系紧密


第一讲

C++ STL标准库与泛型编程
C++ Standard Template Library and Generic Programming

1.header、版本和重要资源

C++标准库(C++ Standard Library) vs. C++标准模板库(C++ Standard Template Library)

注:C++标准模板库主要为6大部件,C++标准库除6大部件外还包括其它内容。

C++标准库以头文件(header files)的形式呈现。

  • C++标准库的头文件不带后缀名.h,例如#include

  • 新式C的头文件不带后缀名.h,例如#include

  • 旧式C的头文件带后缀名.h仍然可以使用,例如#include

  • 新式头文件内的组件封装在命名空间std中:

    • using namespace std;
    • using std::cout;std::vector vec;
  • 旧式头文件内的组件封装在命名空间std中。


常用网站

http://www.cplusplus.com/

https://en.cppreference.com/w/

http://gcc.gnu.org/

常用书籍

《The C++ Standard Library》

《STL源码剖析》


2.STL体系结构介绍

2.1 STL六大部件(Components)

  • 容器(Containers)
  • 算法(Algorithms)
  • 分配器(Allocators)
  • 迭代器(Iterators)
  • 适配器(Adapters)
  • 仿函数(Functors)

【学习笔记】C++STL和泛型编程-侯捷_第1张图片

容器与分配器:分配器管理容器元素的内存

容器与算法、迭代器:算法是操作或处理容器元素的模板函数迭代器是容器和算法之间的桥梁,是泛化的指针

示例:STL六大组件的应用
【学习笔记】C++STL和泛型编程-侯捷_第2张图片


2.2 STL容器的前闭后开区间

[ ):迭代器位置包左不包右

begin():迭代器指向容器第1个元素的位置

end():迭代器指向容器最后1个元素的下一个位置

【学习笔记】C++STL和泛型编程-侯捷_第3张图片

注:迭代器相当于泛化的指针,可使用指针的操作:++--*->


2.3 基于范围的for循环 ranged-base for

语法

for( decl : coll ) {
	statement
}

for( 声明 : 容器 ) {
	语句
}

注:该写法比迭代器、for_each()简单。【语法糖】

示例1

for(int i : {1, 3, 5, 7, 9}) {
    std::cout << i << std::endl;
}

示例2

std::vector<int> vec;
...
    
/* 传值:pass by value */
// 拷贝容器中的元素并赋值给局部变量elem——不影响容器中的原有元素
for(auto elem : vec) {
    std::cout << elem << std::endl;
}

/* 传引用:pass by reference */
// 对容器中的元素进行操作——影响容器中的原有元素
for(auto& elem : vec) {
    elem *= 3;
}

2.4 auto关键字

使用auto关键字声明变量或对象时,编译器自动对其类型进行推导。【语法糖】

/* 正常写法 */
list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), targetStr);

/* 使用auto关键字 */
list<string> c;
...
auto ite = find(c.begin(), c.end(), targetStr);

注:使用auto关键字声明变量或对象的同时,必须进行定义(显式赋值操作)。

/* 错误写法 */
list<string> c;
...
// auto ite;
// ite = find(c.begin(), c.end(), targetStr);

3.容器的结构与分类

  • 序列式容器(Sequence Containers):按照元素的存储顺序依次排列。

    • 数组(Array):容量大小固定
    • 向量(Vector):单端进出,容量大小可动态扩展
    • 双端队列(Deque):双端可进可出
    • 双向链表(List):双向循环链表
    • 单向链表(Forward-List):内存占用少于双向链表【C++11新增】

      注:栈stack和队列queue底层依赖于deque容器,本质上属于容器适配器

  • 关联式容器(Associative Containers):包括键和值,适合快速查找

    • 集合(Set/Multiset):由红黑树(高度平衡二叉树)实现

    • 映射表(Map/Multimap):由红黑树(高度平衡二叉树)实现

      注1:红黑树可自动调整达到平衡,避免在最坏情况下退化为线性链表。
      注2:STL未规定set或map的具体实现,但编译器多采用红黑树实现。
      注3:set的键和值不作区分:键就是值,值就是键。
      注4:set的值不可重复,map的键不可重复;Multiset的值可重复,Multimap的键可重复

  • 不定序容器(Unordered Containers):底层使用哈希表实现。

    • 无序集合(Unordered Set/Multiset):由哈希表实现
    • 无序映射表(Unordered Map/Multimap):由哈希表实现

      注1:哈希表包括若干个bucket,每个bucket对应一个链表。
      哈希冲突时,应避免每个bucket对应的链表长度过长而导致元素搜索时间较长。
      注2:g++编译器提供非标准的hash_set/hash_multiset/hash_map/hash_multimap,与标准库提供的unordered_set/unordered_multiset/unordered_map/unordered_multimap基本一致,使用时需包含头文件#include

【学习笔记】C++STL和泛型编程-侯捷_第4张图片


4.各种STL容器的测试

4.1 vector容器的测试

注1:vector容器的容量支持动态扩容,且扩展后的容量是当前容量的2倍,并将扩容前旧容器的原有元素拷贝至扩容后的新容器中。
注2:STL算法中的::find()函数是全局函数,且是顺序查找
注3:vector容器扩容时容量成倍增长,可能会造成内存的浪费。


4.2 list和forward_list容器的测试

注1:当STL和容器类均提供用于排序的sort()函数时,优先使用容器类的sort()函数,效率更高。
注2:单向链表forward_list不存在成员函数back()size()
注3:g++编译器提供非标准的单向链表slist,与标准库提供的forward_list基本一致,使用时需包含头文件#include
注4:list容器扩容时每次新增1个节点,内存利用率较高,但查找效率较低。


4.3 deque容器的测试

deque容器包括中央控制器和若干分段连续缓冲区(buffer)。
当deque容器向前或向后填充元素时,先查看对应缓冲区是否还有剩余空间,当不存在剩余空间时,会申请额外的缓冲区且中央控制器的指针指向新的缓冲区。

注:deque容器扩容时,每次扩充1个缓冲区大小。
【学习笔记】C++STL和泛型编程-侯捷_第5张图片


4.4 stack和queue的测试

【学习笔记】C++STL和泛型编程-侯捷_第6张图片

【学习笔记】C++STL和泛型编程-侯捷_第7张图片

注:栈stack和队列queue底层依赖于deque容器,本质上属于容器适配器


4.5 multiset和multimap的测试

注1:当STL和容器类均提供用于查找的find()函数时,优先使用容器类的find()函数,效率更高。
注2:multimap插入元素时,不可使用[],例如mmp[0] = 1; mmp[0] = 2;,编译器不知道是插入2个键相同的键值对或修改键对应的值。


4.6 unordered_multiset和unordered_multimap的测试

哈希表包括若干个bucket,每个bucket对应一个链表。

哈希冲突时,应避免每个bucket对应的链表长度过长而导致元素搜索时间较长。

经验法则:当元素个数大于等于bucket个数时,bucket个数会扩充至约原先的2倍。

bucket数量一定多于元素数量


5.分配器allocator的测试

容器包含默认的分配器(模板参数使用默认值)。

示例:各类STL容器的默认分配器

template<typename _Tp, typename _Alloc = std::allocator<_Tp>>	// 使用默认参数
class vector : protected _Vector_base<_Tp, _Alloc>
    
template<typename _Tp, typename _Alloc = std::allocator<_Tp>>	// 使用默认参数
class list : protected _List_base<_Tp, _Alloc>
    
template<typename _Tp, typename _Alloc = std::allocator<_Tp>>	// 使用默认参数
class deque : protected _Deque_base<_Tp, _Alloc>
    
template<typename _Key, typename _Compare = std::less<_Key>,
			typename _Alloc = std::allocator<_Key>>
class set ...
    
template<typename _Key, typename _Tp, typename _Compare = std::less<_Key>,
			typename _Alloc = std::allocator<std::pair<_Key, _Tp>>
class map ...
    
template<class _Value, class _Hash = hash<_Value>, 
			class _Pred = std::equal_to<_Value>, class _Alloc = std::allocator<_Value>>
class unordered_set ...
    
template<class _Key, class _Tp, class _Hash = hash<_Key>, 
			class _Pred = std::equal_to<_Key>,
			class _Alloc = std::allocator<std::pair<_Key, _Tp>>
class unordered_map ...

g++不同类型的分配器

【学习笔记】C++STL和泛型编程-侯捷_第8张图片

示例:直接使用分配器管理内存(不建议)

注:不建议手动使用分配器直接管理内存,释放内存时需释放对应大小的内存。

/* 测试不同类型分配器的allocate()和deallocate()函数 */
#include 	// 包含各类分配器的头文件
int *p;

/* 默认分配器:allocator */
allocator<int> a1;
p = a1.allocate(1);		// 分配1个int大小的空间
a1.deallocate(p, 1);  	// 需要同时释放指针和对应的内存大小

/* malloc分配器:malloc_allocator */
__gnu_cxx::malloc_allocator<int> a2;
p = a2.allocate(2);		// 分配2个int大小的空间
a2.deallocate(p, 2);  	// 需要同时释放指针和对应的内存大小

/* new分配器:new_allocator */
__gnu_cxx::new_allocator<int> a3;
p = a3.allocate(3);		// 分配3个int大小的空间
a3.deallocate(p, 3);  	// 需要同时释放指针和对应的内存大小

/* 池分配器:__pool_alloc */
__gnu_cxx::__pool_alloc<int> a4;
p = a4.allocate(4);		// 分配4个int大小的空间
a4.deallocate(p, 4);  	// 需要同时释放指针和对应的内存大小

/* 多线程分配器:__mt_alloc */
__gnu_cxx::__mt_alloc<int> a5;
p = a5.allocate(5);		// 分配5个int大小的空间
a5.deallocate(p, 5);  	// 需要同时释放指针和对应的内存大小

/* bitmap分配器:bitmap_allocator */
__gnu_cxx::bitmap_allocator<int> a6;
p = a6.allocate(6);		// 分配6个int大小的空间
a6.deallocate(p, 6);  	// 需要同时释放指针和对应的内存大小

第二讲

6.STL源代码的分布(VC++ 6.0、g++ 4.9.2)

Visual C++编译器的STL源码路径:...\include

g++编译器的STL源码路径:

  • STL:...\include\c++\bits

  • 扩展:...\include\c++\ext


7.面向对象编程(OOP) vs. 泛型编程(GP)

面向对象编程(OOP,Object-Oriented Programming):将数据data操作method关联

// list容器的迭代器不支持随机访问
template <class T, class Alloc = alloc>
class list {
    ...
    void sort();	// list容器类的成员函数sort()
};

标准库提供的全局::sort()算法所使用的迭代器必须是随机访问迭代器(Random Access Iterator)

链表list的迭代器不支持随机访问,无法使用全局::sort()算法进行排序。

/* STL标准库提供的全局::sort() */
// ::sort(c.begin(), c.end())
template <class RandomAccessIterator>	// 随机访问迭代器
inline void sort(RandomAccessIterator first, RandomAccessIterator last) {
    __introsort_loop(first, value_type(first), __lg(last - first) * 2);
    __final_insertion_sort(first, last);
}
template <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessIterator first, RandomAccessIterator last,
                      T*, Size depth_limit) {
    ...
    // 只有随机访问迭代器,才能跳跃多个元素位置first + (last- first)/2
    // 非随机访问迭代器,只能向前向后移动1个位置
 	RandomAccessIterator cut = __unguarded_partition(
    	first, last, T(__median(*first, *(first + (last- first)/2), *(last -1))));   
    ...
}

泛型编程(GP,Generic Programming):将数据data操作method分离

容器类(Containers)

vector和deque的迭代器支持随机访问,可使用全局::sort()算法进行排序。

// vector容器的迭代器支持随机访问
template <class T, class Alloc = alloc>
class vector {
    ...
};

// deque容器的迭代器支持随机访问
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
    ...
};

算法类(Algorithms)

/* STL标准库提供的全局::sort() */
// ::sort(c.begin(), c.end())
template <class RandomAccessIterator>	// 随机访问迭代器
inline void sort(RandomAccessIterator first, RandomAccessIterator last) {
	...
}

template <class RandomAccessIterator, class Compare> // 比较规则
inline void sort(RandomAccessIterator first, RandomAccessIterator last, Compare comp) {
	...
}

采用泛型编程的优点

  • 容器(Containers)和算法(Algorithms)可各自独立实现,提供统一的对外接口即可,两者以迭代器(Iterator)作为桥梁。
  • 算法(Algorithms)通过迭代器(Iterator)确定操作范围,并通过迭代器(Iterator)获取和使用容器(Containers)元素。

所有的算法(Algorithms),涉及元素本身的操作时,本质是比较元素大小

如:查找是找到与指定值相等的元素;排序是比较元素间的大小顺序。

/* max函数的重载 */
template <class T>
inline const T& max(const T& a, const T& b) {
    return a < b ? b : a;
}

// Compare 自定义的比较规则
template <class T, class Compare>
inline const T& max(const T& a, const T& b, Compare comp) {
    return comp(a, b) ? b : a;
}
bool strLonger(const string& s1, const string& s2) {
    return s1.size() < s2.size();	// 比较字符串长度
}

int main(){
    // 按默认字典序比较
    cout << max(string("zoo"), string("hello")) << endl; // zoo
    // 按自定义的比较规则比较(字符串长度)
    cout << max(string("zoo"), string("hello"), strLonger) << endl; // hello
}

8.STL源码学习基础:操作符重载、模板(泛化、全特化、偏特化)

8.1 操作符重载

操作符重载规则

操作符重载在一定条件下,可作为成员函数或全局函数。

【学习笔记】C++STL和泛型编程-侯捷_第9张图片

示例:链表迭代器的操作符重载
【学习笔记】C++STL和泛型编程-侯捷_第10张图片


8.2 模板(泛化、全特化、偏特化)

模板:编码时将某些类型暂定为通用类型,在使用时再指定具体类型

8.2.1 类模板 class template

调用者在使用时再指定泛型的具体类型

注:类模板使用时,需显式指定具体的泛型类型。编译器不会对类模板进行类型推导。

template<typename T>	// 类模板
class complex{
private:
    T re, im;	// 模板
    friend complex& __doap1 (complex *ths, const complex& r); 	// 友元函数

public:
    complex(T r = 0, T i = 0) : re(r), im(i){}	// 默认参数、初始化列表
    complex& operate+=(const complex&);	// 重载运算符
    T real() const { return re; }		// 成员函数-常函数
    T imag() const { return im; }
};
int main(){
    complex<double> c1(1.1, 2.2);
    complex<int> c2(3, 4);
}

8.2.2 函数模板 function template

注1:关键字class可替换为typename
注2:函数模板使用时,不必显式指定具体的泛型类型。编译器会对函数模板进行实参推导/类型推导(argument deduction)。类模板使用时,需显式指定具体的泛型类型。

// 函数模板
template<class T>
inline const T& min(const T& a, const T& b){
    return b < a ? b : a;	// 运算符< 可被重载
}

class stone{
public:
    stone(int w, int h, int we) : _w(w), _h(h), _weight(we){}
    // 重载运算符<
    bool operator< (const stone& rhs){
        return this->_weight < rhs._weight;
    }
    
private:
    int _w, _h, _weight;
}

int main(){
    stone r1(2, 3), r2(3, 3), r3;
    // 调用模板函数min,类型推导
    // 类型推导T为stone类型,进一步调用stone::operator<()
    r3 = min(r1, r2);	
}

8.2.3 成员模板 member template

成员模板:类模板中的成员(函数/属性)也为模板。

template <class T1, class T2>
struct pair {
    typedef T1 first_type;
    typedef T2 second_type;
    
    // 成员变量
    T1 first;
    T2 second;
    
    // 构造函数
    pair() : first(T1()) , second(T2()) {}	// 匿名对象T1()、T2()
    pair(const T1& a, const T2& b) : first(a), second(b) {}	// 有参构造
    
    /* 成员模板 */
    template <class U1, class U2>
    pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}	// 拷贝构造
};

应用场景:STL中的拷贝构造函数有参构造函数常设计为成员模板。

成员模板的泛型类型类模板的泛型类型子类

注:成员模板的泛型类型U1/U2,继承自类模板的泛型类型T1/T2。反之不成立

class Base1 {};
class Derived1 : public Base1 {};

class Base2 {};
class Derived2 : public Base2 {};
pair</* U1 */Devired1, /* U2 */Devired2> p;
pair<Base1, Base2> p2(p);

/* 等价写法 */
// 使用成员模板创建匿名对象,用于类的拷贝构造函数
pair</*T1*/Base1, /*T2*/Base2> p2(pair</*U1*/Devired1, /*U2*/Devired2>());

示例:智能指针类成员模板的应用

智能指针子类shared_ptr有参构造函数的参数(被包装的指针),是智能指针父类__shared_ptr有参函数构造函数的参数(被包装的指针)的子类

template<typename _Tp>
class shared_ptr : public __shared_ptr<_Tp>
{
    ...
    /* 成员模板 */
    template<typename _Tp1>
    // 构造函数
    // 子类构造函数shared_ptr的参数__p,对父类构造函数__shared_ptr<_Tp>赋值
    explicit shared_ptr(/*Devired*/_Tp1* __p) : __shared_ptr</*Base*/_Tp>(__p) {}
    ...
};
/* 指针类的多态 */
Base1 *ptr = new Devired1;	// 向上转型up-cast
/* 智能指针类的多态 */
// 使用子类对象
shared_ptr<Base1*> sptr(new Devired1);	// 模拟向上转型up-cast

8.3 特化 specialization

8.3.1 类模板的全特化 template specialization

  • 泛化(generalization):使用时再指定模板参数的具体类型。
  • 特化(specialization):将模板泛化的部分进行局部的特征化。显式指定部分或全部模板参数的具体类型。
    • 全特化(full specialization):显式指定全部模板参数的具体类型。
    • 偏特化/局部特化(partial specialization):指定部分模板参数的具体类型。

注1:特化的优先级高于泛化。当存在合适的特化版本时,编译器优先调用相应的特化版本。

注2:全特化的所有模板参数均被指定,故模板参数列表为空,即template <>

示例:类模板的泛化和特化

/* 一般的泛化 */
template <class Key>
struct hash {};
/* 不同版本的特化 */
// 本案例只包含1个模板参数,特化后即全特化
template<>	// 全特化的所有参数均被指定,故模板参数列表为空。
struct hash<char> {
    size_t operator()(char x) const {
        return x;
    }
};

template<>
struct hash<int> {
    size_t operator()(int x) const {
        return x;
    }
};

template<>
struct hash<long> {
    size_t operator()(long x) const {
        return x;
    }
};
/* 测试案例 */
// 1.当未指定类型时,则使用泛化的类模板
// 2.当指定特定类型时,则使用相应特化版本的类模板
cout << hash<long>()(1000);	// 第1个()表示匿名对象;第2个()表示调用函数调用运算符

【学习笔记】C++STL和泛型编程-侯捷_第11张图片


8.3.2 类模板的偏特化/局部特化 partial specialization

偏特化/局部特化(partial specialization):指定部分模板参数的具体类型。

个数的偏特化

若干数量的模板参数进行特化(显式指定类型)

注1:偏特化的模板参数,必须从左到右连续指定(如偏特化第1/2/3个),而不能穿插指定(如偏特化第1/3/5个),类似于默认参数必须从参数列表的最右侧往左连续。

注2:偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表,否则报错。

/* 泛化的类模板 */
template<typename T, typename Alloc = .../*默认参数*/>	// 模板参数
class vector
{
	... 
};
/* 偏特化的类模板-个数的偏特化 */
// 通过偏特化/局部特化,模板参数的2个泛型类型变为1个
// 偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表
template<typename Alloc = .../*默认参数*/>
class vector<bool, Alloc>	// 偏特化/局部特化:使用指定的bool类型,绑定泛型类型T
{
	... 
};

范围的偏特化

将模板参数类型的表示范围缩小

  • 将泛型类型T缩小为对应的指针类型T*
  • 将泛型类型T缩小为const限定符修饰T const

示例:类模板的泛化和偏特化

/* 泛化的类模板 */
template<typename T>	// 模板参数
class C
{
	... 
};
/* 偏特化的类模板-范围的偏特化:指针类型偏特化 */
template<typename T>	// 模板参数
class C<T*>
{
	... 
};

// 等价写法
template<typename U>	// 模板参数
class C<U*>
{
	... 
};

/* 偏特化的类模板-范围的偏特化:const偏特化 */
template<typename T>	// 模板参数
class C<T const>
{
	... 
};
/* 测试案例 */
/* 使用泛化版本的类模板 */
C<string> obj1;

/* 使用偏特化版本的类模板-范围的偏:指针类型偏特化 */
C<string*> obj2;	// 任意类型T → 任意指针类型T*

/* 使用偏特化版本的类模板-范围的偏:const偏特化 */
C<string const> obj3;	// 任意类型T → const类型T const

示例:类模板的泛化、全特化及偏特化的调用顺序

#include 
using namespace std;

/* 泛化的类模板 */
template<typename T1, typename T2>
class Test {
public:
	Test(T1 a, T2 b) : _a(a), _b(b) {
		cout << "泛化的类模板" << endl;
	}
private:
	T1 _a;
	T2 _b;
};

/* 全特化的类模板 */
template<>  // 全特化的所有参数均被指定,故模板参数列表为空
class Test<int, double> {
public:
	Test(int a, double b) : _a(a), _b(b) {
		cout << "全特化的类模板" << endl;
	}
private:
	int _a;
	double _b;
};

/* 偏特化的类模板-个数的偏特化 */
template<typename T2>   // 偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表
class Test<int, T2> {
public:
	Test(int a, T2 b) : _a(a), _b(b) {
		cout << "偏特化的类模板-个数的偏特化" << endl;
	}
private:
	int _a;
	T2 _b;
};

/* 偏特化的类模板-范围的偏特化:指针类型偏特化 */
template<typename T1, typename T2>
class Test<T1*, T2*> {  // 将泛型类型 T 缩小为对应的指针类型 T*
public:
	Test(T1* a, T2* b) : _a(a), _b(b) {
		cout << "偏特化的类模板-范围的偏特化:指针类型偏特化" << endl;
	}
private:
	T1* _a;
	T2* _b;
};

/* 偏特化的类模板-范围的偏特化:const偏特化 */
template<typename T1, typename T2>
class Test<T1 const, T2 const> {    // 将泛型类型 T 缩小为const限定符修饰 T const
public:
	Test(T1 a, T2 b) : _a(a), _b(b) {
		cout << "偏特化的类模板-范围的偏特化:const偏特化" << endl;
	}
private:
	T1 _a;
	T2 _b;
};

int main()
{
	Test<string, double> test1("hello", 1.1);   // 泛化的类模板
	Test<int, double> test2(1, 2.2);            // 全特化的类模板
	Test<int, string> test3(2, "world");        // 偏特化的类模板-个数的偏特化
	Test<int*, int*> test4(nullptr, nullptr);   // 偏特化的类模板-范围的偏特化:指针类型偏特化
	Test<const int, const int> test5(1, 2);     // 偏特化的类模板-范围的偏特化:const偏特化
	return 0;
}

【学习笔记】C++STL和泛型编程-侯捷_第12张图片


你可能感兴趣的:(C++侯捷大师,c++,stl,标准模板库,泛型编程)