c++模板与泛型编程完整笔记 | STL前置基础

c++模板与泛型编程

前言

1.泛型编程:思想

不针对int/double/string写死代码,写一套通用逻辑,适配所有类型。

2.模板:c++唯一实现泛型的语法工具之一

c++没有"泛型关键字",模板=c++泛型编程的唯一落地方式。

3.STL容器=官方写好的大量模板类

vector,map,set,string 全部都是类模板,如果你不懂模板,只会调用STL,永远看不懂底层。

一、为什么必要要有模板&泛型编程

1.传统代码的巨大缺陷(重复冗余)

如果你要写一个“取最大值”函数:

  • 写一份int版

  • 写一份double版

  • 写一份float版

  • 未来自定义结构体,类还要再写

代码几乎一模一样,只有类型不同,极度冗余,难维护。

2.泛型编程的核心宗旨

算法逻辑不变,数据类型可变。编辑只写一次,自动适配任意类型,这就是泛型

3.模板的作用

给类型填占位符,编译时自动生成对应代码。

二、模板两大核心分类

模板只有两种,没有第三种:

1.函数模板 -> 通用函数

作用:写一份函数逻辑,给int、double、string都能用。

语法格式:
template<typename T>
返回值 函数名(参数)
{
//里面可以用T当作类型
}
示例代码:
#include<iostream>
using namespace std;
//模板头, T是类型占位符
template<typename T>
T getMax(T a,T b)
{
    return a > b ? a : b;
}
int main()
{
    //编译器自动看你传入什么值,自己推导出T是什么类型
    cout<<getMax(10,20)<<endl;//T变成int
    cout<<getMax(1.5,3.2)<<endl;//T变成double
    //也可以手动强制写类型
    cout<<getMax<int>(5,9)<<endl;
    return 0;
}
函数模板重点

1.template必须写在函数最上面

2.可以自动推导类型,调用的时候很多时候不用写

3.模板本身不是真正函数,编译的时候编译器生成一份真实的函数代码。

2.类模板 ->通用类(STL容器全是这个)

作用:写一个通用的类,可以存int,可以存string,可以存自定义结构体。vector就是类模板的典型。

语法格式
template<typename T>
class 类名
{
    //成员变量,成员函数,T就代表将来的类型
};
示例代码
#include<iostream>
#include<string>
using namespace std;

template<typename T>
class MyBox
{
    private:
    T data;//这个变量是什么类型,等到创建对象才知道
    public:
    void set(T val)
    {
        data = val;
    }
    T get()
    {
        return data;
    }
};
int main()
{
    //类模板:不能自动推导!必须写<类型>
    MyBox<int> box1;
    box1.set(100);
    cout<<box1.get()<<endl;
    MyBox<string> box2;
    box2.set("hello");
    cout<<box2.get()<<endl;
    return 0;
}
剥离分配器、调试宏之后的可读简化版vector容器源码
// vector 是类模板!T是存储的元素类型,Alloc是内存分配器(模板默认参数)
template<typename T, typename Alloc = std::allocator<T>>
class vector
{
public:
    // 类型别名,STL到处在用
    typedef T           value_type;
    typedef T*          iterator;   //迭代器本质就是裸指针!
    typedef const T*    const_iterator;
private:
    iterator _Start;         // 起始
    iterator _Finish;        //有效数据结尾
    iterator _EndOfStorage;  //整块内存的结尾

    //分配器对象,负责申请释放堆内存
    Alloc _alloc;
public:
    //构造函数
    vector()
    {
        _Start = nullptr;
        _Finish = nullptr;
        _EndOfStorage = nullptr;
    }
    //析构:销毁元素,释放堆内存
    ~vector()
    {
        clear();
        //释放整块堆内存
        if(_Start != nullptr)
        {
            _alloc.deallocate(_Start, capacity());
        }
    }
    // size:有效元素数量
    size_t size() const
    {
        return static_cast<size_t>(_Finish - _Start);
    }
    // capacity总容量
    size_t capacity() const
    {
        return static_cast<size_t>(_EndOfStorage - _Start);
    }
    bool empty() const
    {
        return _Start == _Finish;
    }
    // operator[] 下标访问
    value_type& operator[](size_t n)
    {
        return *(_Start + n);
    }
    // begin迭代器
    iterator begin()
    {
        return _Start;
    }
    iterator end()
    {
        return _Finish;
    }
    // clear:调用对象析构,不释放内存!capacity不变
    void clear()
    {
        while(_Finish != _Start)
        {
            --_Finish;
            _alloc.destroy(_Finish);
        }
    }
    // push_back 核心!
    void push_back(const T& val)
    {
        // 判断内存满了:有效结尾 == 内存总结尾 → 需要扩容
        if(_Finish != _EndOfStorage)
        {
            //还有剩余空间,直接构造对象
            _alloc.construct(_Finish, val);
            ++_Finish;
        }
        else
        {
            //扩容!开辟新空间,拷贝旧元素,释放旧空间
            reallocate();
            _alloc.construct(_Finish, val);
            ++_Finish;
        }
    }
private:
    //扩容函数,原版vector一般扩大2倍
    void reallocate()
    {
        size_t old_cap = capacity();
        // 扩容策略:空的就给1;否则翻倍
        size_t new_cap = old_cap == 0 ? 1 : old_cap * 2;

        //申请一块更大堆内存
        iterator new_start = _alloc.allocate(new_cap);
        //把旧内存全部拷贝到新内存
        iterator p_old = _Start;
        iterator p_new = new_start;
        for(; p_old != _Finish; ++p_old,++p_new)
        {
            _alloc.construct(p_new, *p_old);
            _alloc.destroy(p_old);
        }
        //释放旧内存
        _alloc.deallocate(_Start, old_cap);
        //更新三个指针指向新内存
        _Start = new_start;
        _Finish = p_new;
        _EndOfStorage = new_start + new_cap;
    }
};
类模板重点

1.template写在class上面

2.创建对象必须手动写<类型>,编译器不会自动推导

3.如果成员函数写在类外面,外面也要重新写一遍template

4.STL的vector、map<k,v>全部都是类模板。

简单对比,一眼分清

1.函数模板
  • 产出:通用函数

  • 使用:大多自动推导类型,不用写<>

  • STL对应:sort、max、swap这些算法

2.类模板
  • 产出:通用类、通用数据结构
  • 使用:创建对象必须写<类型>
  • STL对应:vector、map、set容器

三、模板关键概念

1.模板参数 = 类型参数

普通函数参数:传数值

模板参数:传类型

2.模板实例化

  • 隐式实例化:编译器自动推导,写代码时不用手动指定类型

  • 函数模板可以隐式实例化;类模板不能完全靠隐式实例化
        template<typename T>
        T max(T a, T b)
    {
        return a > b ? a : b;
    }
         max(1,2);//隐式实例化,编译器推导出T = int
    
  • 显式实例化:手动写<类型>,强制告诉编译器生成该类型的代码

  • 函数模板:max<int>(10,20);
    类模板:vector<int> v;,STL所有容器使用过程,全部是[类模板实例化]
    
  • 补充:显式实例化声明(extern)

  • //强制编译器生成int版本的函数模板案例
    template int max<int>(int a,int b);
    //extern显式实例化声明:告诉编译器别处已经生成实例,本文件不要重复生成
    extern template int max<int>(int a,int b);
    

3.模板不能分离编译

模板代码必须写在头文件.h中

原因:

编译时需要看到完整模板源码才能生成对应类型代码,如果拆分cpp,编译找不到模板实现,直接报错

四、泛型编程的真正定义

1.不聚焦数据类型,只聚焦算法逻辑

2.用模板实现与类型无关的通用代码

3.一套代码,复用所有数据类型

对比:

面向对象:聚焦[对象、继承、多态]

泛型编程:聚焦[通用、复用、类型无关]

STL = 泛型编程思想的工业级1成品库

  • 容器:通用数据结构(类模板)
  • 算法:通用算法(函数模板)
  • 迭代器:通用指针适配

五、模板特化

1.什么是特化?

通用模板对某些特殊类型不适用,单独写一份定制版

2.全特化

//通用<typename T>
void show(T a)
{
    cout<<"通用模板"<<endl;
}
//专门为string<>
<string>(string a)
{
    cout<"字符串特化版本"<endl;
}

优先级:特化版本 > 通用模板

STL大量用特化优化不同类型的存储结构

六、模板默认参数

可以给模板参数设置默认类型

template<typename T>
class Test
{
    public:
    T val;
};
//不写类型默认int
Test<> t1;
Test<double> t2;

vector第二参数内存分配器就是模板默认参数

这就是我看不懂<T,Alloc>的来源

posted @ 2026-08-30 16:23  烟雨楼10  阅读(10)  评论(0)    收藏  举报