白杨尚青头像
关注
C++入门篇(九):模板与STL初探——泛型编程的大门与标准库的宝库<过渡章节>封面图

C++入门篇(九):模板与STL初探——泛型编程的大门与标准库的宝库<过渡章节>

目录

0.1 概述&序言

一、泛型编程与函数模板

1.1 什么是泛型编程

1.2 函数模板的定义

1.3 隐式实例化与显式实例化

1.4 一道错题引入

1.5 原因与原理:模板参数的匹配原则

1.6 非类型模板参数

二、类模板

2.1 为什么需要类模板

2.2 类模板的定义与实例化

2.3 类模板的声明与定义分离

2.4 模板特化:给特殊类型"开小灶"(了解,未来会进行详细讲解)

三、STL初探

3.1 什么是STL

3.2 STL六大组件

3.3 第一把武器:string(下章详细讲解)

3.4 常用容器一句话概览

四、最终总结


0.1 概述&序言

   这里是白杨,上篇我们把内存管理的底裤扒干净了(没看过的同学请走传送门:C++入门篇(八):内存管理)。C++入门篇(八):C++内存管理详细精讲-CSDN博客

    本篇继续开新地图,聊两件事:

  • 模板:泛型编程的基础。一份代码,多种类型,这就是模板的魔法——它也是 STL 的"发动机"。
  • STL 初探:标准模板库,C++ 最伟大的财富之一。容器、算法、迭代器三大件先混个脸熟。

内容依旧干货满满,让我们开始吧。

一、泛型编程与函数模板

1.1 什么是泛型编程

先说结论:

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

想写一个交换函数:

void Swap(int& a, int& b)      { int t = a; a = b; b = t; }
void Swap(double& a, double& b){ double t = a; a = b; b = t; }
void Swap(char& a, char& b)    { char t = a; a = b; b = t; }

   逻辑一模一样,只是类型不同,重复代码泛滥成灾。C++ 的解法就是模板——把类型当成参数传。

口诀:模板是图纸,类型是参数;实例化之后,房子才盖好。

1.2 函数模板的定义

template<typename T>   // 也可以写 template<class T>
void Swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

   template<typename T> 声明了一个模板参数 T。编译器遇到 Swap(a, b) 时,会根据实参类型推导出 T,再生成一份对应的函数——这个过程叫模板实例化。模板本身不是函数,实例化出来的才是函数。

1.3 隐式实例化与显式实例化

int main()
{
    int a = 1, b = 2;
    Swap(a, b);        // 隐式实例化:推导出 T = int
    Swap<int>(a, b);   // 显式实例化:手动指定 T = int

    double d1 = 1.1, d2 = 2.2;
    Swap(d1, d2);      // T = double

    // Swap(a, d1);       // error:a是int、d1是double,T 推导不出来
    // Swap<int>(a, d1);  // error:指定T=int后,d1是double不能传给int&
    return 0;
}

1.4 一道错题引入

下面代码输出什么?

#include <iostream>
using namespace std;

template<class T>
void Swap(T& x, T& y)
{
    T t = x;
    x = y;
    y = t;
    cout << "模板" << endl;
}

void Swap(int& x, int& y)
{
    int t = x;
    x = y;
    y = t;
    cout << "普通函数" << endl;
}

int main()
{
    int a = 1, b = 2;
    Swap(a, b);            // ?
    Swap<int>(a, b);       // ?
    double d1 = 1.1, d2 = 2.2;
    Swap(d1, d2);          // ?
    return 0;
}

答案:普通函数 模板 模板。

1.5 原因与原理:模板参数的匹配原则

  1. 普通函数和函数模板同时存在、且匹配程度相同时,优先调用普通函数。
  2. 显式指定模板参数(Swap)时,强制走模板。
  3. 函数模板不允许自动类型转换(int 不会自动变 double);普通函数可以(int 可以隐式转 double)。
  4. 模板可以有多个类型参数:template<class T1, class T2>。

   原则:能一套逻辑吃遍所有类型的,写模板;只有一个特殊类型要特事特办的,写普通函数让它优先被调用。

1.6 非类型模板参数

模板参数不只是类型,还可以是常量——称为非类型模板参数:

template<class T, size_t N>
void PrintArray(T(&arr)[N])    // N 由编译器从数组大小推导
{
    for (size_t i = 0; i < N; ++i)
        cout << arr[i] << " ";
    cout << endl;
}

int main()
{
    int a[5] = { 1,2,3,4,5 };
    PrintArray(a);    // T=int, N=5
    double d[3] = { 1.1,2.2,3.3 };
    PrintArray(d);    // T=double, N=3
    return 0;
}

先说结论:

非类型模板参数必须是编译期就能确定的整型常量(size_t、int 等),不能传 double、string 和对象。

   它的妙处:换普通函数,传数组还得再传一个"数组大小"参数;换成模板,N 编译器帮你数好,一份代码通吃所有类型、所有大小的数组。

二、类模板

2.1 为什么需要类模板

函数模板解决"函数逻辑相同、类型不同",类模板解决"类的结构相同、类型不同"——比如 Stack<int> 和 Stack<char>。

2.2 类模板的定义与实例化

template<class T>
class Stack
{
public:
    Stack(size_t capacity = 4)
        : _arr(new T[capacity])
        , _capacity(capacity)
        , _top(0)
    {}

    ~Stack()
    {
        delete[] _arr;
        _arr = nullptr;
        _capacity = _top = 0;
    }

    void Push(const T& x)
    {
        _arr[_top++] = x;
    }

private:
    T* _arr;
    size_t _capacity;
    size_t _top;
};

int main()
{
    Stack<int> st1;    // 类模板必须显式实例化
    st1.Push(2026);

    Stack<char> st2;   // 编译器生成另一份独立的类
    st2.Push('A');
    return 0;
}

先说结论:

   类模板不是类,实例化出来的才是类。 Stack 只是模板(图纸),Stack<int> 才是真正的类型(房子)。所以类模板必须显式指定类型参数,编译器没法像函数模板那样自动推导。

2.3 类模板的声明与定义分离

函数模板的声明和定义可以分离,类模板理论上也可以,但强烈不建议:

// Stack.h
template<class T>
class Stack
{
public:
    void Push(const T& x);   // 只声明
    // ...
};

// Stack.cpp
template<class T>
void Stack<T>::Push(const T& x)
{
    _arr[_top++] = x;
}

如图虽然成功编译。

   坑在哪?模板的实例化发生在编译期,编译器必须"看见"函数的完整定义才能生成代码。 如果把定义放在 .cpp 里,使用它的文件只 include 了 .h、看不到定义,就实例化不出来——链接错误。

原则:类模板的声明和定义放在同一个文件(.h / .hpp)里。

2.4 模板特化:给特殊类型"开小灶"(了解,未来会进行详细讲解)

该内容未来在进阶内容会讲,此处不做讲解,只需了解:

   模板是"通用流水线",但有些类型天生特殊。比如比较两个字符串指针,通用版本比的是地址而不是内容,这时就要给 char* 单独写一份——这就是模板特化。

三、STL初探

3.1 什么是STL

先说结论:

STL(Standard Template Library,标准模板库),是 C++ 标准库的核心部分,是一套基于模板的通用数据结构和算法库。

   它由 Alexander Stepanov 等人在惠普实验室开发,1994 年正式纳入 C++ 标准。学了它意味着:别人帮你把轮子造好了,你要做的就是学会开车。

3.2 STL六大组件

组件作用入门必学
容器(Container)存数据的结构:vector、list、map…必学
算法(Algorithm)对数据操作:sort、find、reverse…必学
迭代器(Iterator)容器和算法之间的桥梁,像"指针"必学
仿函数(Functor)类似函数的对象,作为算法参数了解
适配器(Adapter)包装已有组件改变接口了解
空间配置器(Allocator)底层内存管理了解

一句话记忆:容器装数据,算法干脏活,迭代器当桥梁。

3.3 第一把武器:string(下章详细讲解)

   string 是动态字符数组,专治各种 char* 的苦:自动管理内存、自动扩容、支持 + 拼接,还自带一串好用的成员函数。先感受一下"容器:string":(仅举例,下章会对string详细讲解):

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string s1 = "hello";
    string s2 = "world";

    string s3 = s1 + " " + s2;    // 拼接:operator+
    cout << s3 << endl;           // hello world

    s3.push_back('!');            // 尾插一个字符
    cout << s3 << endl;           // hello world!

    cout << "长度:" << s3.size() << endl;   // 12

    if (s3.find("world") != string::npos)    // 找子串
        cout << "找到了" << endl;

    sort(s3.begin(), s3.end());   // 算法:排序(字符串也能排)
    cout << s3 << endl;           //  !dehllloorw(按ASCII序)
    return 0;
}

   注意对比:换成 C 语言,strcat 要手动管容量、找子串要 strstr 加判空——string 一个 + 一个 find 全搞定,而且永远不会越界。

   在学会string后你会发现以前C语言都是过的什么苦日子((*^_^*))。

3.4 常用容器一句话概览

  • string:字符串。动态字符数组,C++ 里永远别再手动管理 char*。
  • vector:动态数组。随机访问快,尾插尾删快,中间插入删除慢。
  • list:双向链表。任意位置插入删除快,不支持随机访问。
  • map:键值对,底层红黑树,有序去重。m["key"] = value 直接存。
  • unordered_map:键值对,底层哈希表,无序,查询平均更快。
  • set:集合,自动去重 + 排序。

四、最终总结

  1. 泛型编程:与类型无关的代码;模板是图纸,实例化才是房子
  2. 函数模板:隐式/显式实例化;普通函数优先,模板不自动类型转换
  3. 非类型模板参数必须是整型常量,N 可以让编译器帮数
  4. 类模板必须显式实例化;声明和定义放同一个文件
  5. STL 六大组件:容器装数据、算法干脏活、迭代器当桥梁

   好了本期内容就到这里,从下一篇开始,我们逐个拆解 STL 容器——第一站就是 string,欢迎继续围观。如果对你有帮助,不要忘记点赞三连一波哦,我是白杨,我们下期见。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Bai_YangSQ/article/details/166643289

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--