ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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 什么是STL3.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 函数模板的定义templatetypename T // 也可以写 templateclass T void Swap(T a, T b) { T tmp a; a b; b tmp; }templatetypename T声明了一个模板参数 T。编译器遇到Swap(a, b)时会根据实参类型推导出 T再生成一份对应的函数——这个过程叫模板实例化。模板本身不是函数实例化出来的才是函数。1.3 隐式实例化与显式实例化int main() { int a 1, b 2; Swap(a, b); // 隐式实例化推导出 T int Swapint(a, b); // 显式实例化手动指定 T int double d1 1.1, d2 2.2; Swap(d1, d2); // T double // Swap(a, d1); // errora是int、d1是doubleT 推导不出来 // Swapint(a, d1); // error指定Tint后d1是double不能传给int return 0; }1.4 一道错题引入下面代码输出什么#include iostream using namespace std; templateclass 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); // ? Swapint(a, b); // ? double d1 1.1, d2 2.2; Swap(d1, d2); // ? return 0; }答案普通函数 模板 模板。1.5 原因与原理模板参数的匹配原则下面这张流程图直观展示了编译器在遇到Swap(a, b)时的匹配决策过程flowchart TD A[调用 Swap 函数] -- B{是否显式指定模板参数?} B -- 是 -- C[强制走函数模板] B -- 否 -- D{是否存在匹配的普通函数?} D -- 是 -- E[优先调用普通函数] D -- 否 -- F{模板能否推导出 T?} F -- 能 -- G[走函数模板] F -- 不能 -- H[编译错误]可以看到只要显式指定了模板参数编译器就会强制走模板否则在普通函数与模板匹配程度相同时普通函数优先被调用。此外函数模板不允许自动类型转换因此当实参类型无法推导出统一的 T 时会直接编译报错。普通函数和函数模板同时存在、且匹配程度相同时优先调用普通函数。显式指定模板参数Swap时强制走模板。函数模板不允许自动类型转换int 不会自动变 double普通函数可以int 可以隐式转 double。模板可以有多个类型参数templateclass T1, class T2。原则能一套逻辑吃遍所有类型的写模板只有一个特殊类型要特事特办的写普通函数让它优先被调用。1.6 非类型模板参数模板参数不只是类型还可以是常量——称为非类型模板参数templateclass 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); // Tint, N5 double d[3] { 1.1,2.2,3.3 }; PrintArray(d); // Tdouble, N3 return 0; }先说结论非类型模板参数必须是编译期就能确定的整型常量size_t、int 等不能传 double、string 和对象。它的妙处换普通函数传数组还得再传一个数组大小参数换成模板N 编译器帮你数好一份代码通吃所有类型、所有大小的数组。二、类模板2.1 为什么需要类模板函数模板解决函数逻辑相同、类型不同类模板解决类的结构相同、类型不同——比如Stackint和Stackchar。2.2 类模板的定义与实例化templateclass 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() { Stackint st1; // 类模板必须显式实例化 st1.Push(2026); Stackchar st2; // 编译器生成另一份独立的类 st2.Push(A); return 0; }先说结论类模板不是类实例化出来的才是类。Stack只是模板图纸Stackint才是真正的类型房子。所以类模板必须显式指定类型参数编译器没法像函数模板那样自动推导。2.3 类模板的声明与定义分离函数模板的声明和定义可以分离类模板理论上也可以但强烈不建议// Stack.h templateclass T class Stack { public: void Push(const T x); // 只声明 // ... }; // Stack.cpp templateclass T void StackT::Push(const T x) { _arr[_top] x; }如图虽然成功编译。坑在哪模板的实例化发生在编译期编译器必须看见函数的完整定义才能生成代码。如果把定义放在 .cpp 里使用它的文件只 include 了 .h、看不到定义就实例化不出来——链接错误。原则类模板的声明和定义放在同一个文件.h / .hpp里。2.4 模板特化给特殊类型开小灶了解未来会进行详细讲解该内容未来会在进阶内容中讲解此处不做展开只需了解模板是通用流水线但有些类型天生特殊。比如比较两个字符串指针通用版本比的是地址而不是内容这时就要给 char* 单独写一份——这就是模板特化。三、STL初探3.1 什么是STL先说结论STLStandard 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集合自动去重 排序。四、最终总结泛型编程与类型无关的代码模板是图纸实例化才是房子函数模板隐式/显式实例化普通函数优先模板不自动类型转换非类型模板参数必须是整型常量N 可以让编译器帮数类模板必须显式实例化声明和定义放同一个文件STL 六大组件容器装数据、算法干脏活、迭代器当桥梁好了本期内容就到这里从下一篇开始我们逐个拆解 STL 容器——第一站就是string欢迎继续围观。如果对你有帮助不要忘记点赞三连一波哦我是白杨我们下期见。
返回列表