【C】泛型编程为了让函数或者类有更好的复用性C引入了模板的技术。让不同的数据类型能使用到相同的函数或者类中去这种编程的思想也叫做泛型编程。一、模板void Swap(int left,int right) { int temp left; left right; right temp; }在学习C语言的时候写一个交换变量数据的函数会发现一个问题就是这个函数只能用来交换int类型的变量那么我要交换其他类型的变量要怎么办呢多写几个对应的函数吗这种方法其实不是很好因为会造成代码冗余。这里使用C的模板可以轻松的解决templatetypename T void Swap(T left, T right) { T templeft; leftright; righttemp; } int i1,j2; double x1.1,y2.2; Swap(i,j); Swap(x,y);模板格式template typename(也可以用class) 后面的类型名字T是随便取的 Ty,K,V,一般是大写字母或者单词首字母大写。T 代表是一个模板类型(当成变量的类型就可以了int、char这些也可以是自定义类型)。PS这里交换变量使用了一个中间变量那么可以不可以直接使用异或来交换呢其实不行因为异或交换要 int 类型才可以使用。PS这里int和double类型的两个调用实际上调用的不是同一个函数。templateclass T T Add(const T left,const T right) { return leftright; } //编译器自动推演隐式实例化 Add(1.1,2); //这里推演实例化出错 //强制类型转换后可以继续使用 Add((int)1.1,2); Add(1.1,(double)2);模板有个隐式类型的转换直接输入数据编译器就能根据数据判断是什么类型的变量但是要注意如果两个变量类型不同那么它就不会知道你到底想用哪个类型的变量。//或者定义两个参数 templateclass T1class T2 T1 Add(const T1 left,const T2 right) { return leftright; } //这里就可以编译器自动推导。 Add(1.1,2); Add(1,2.2); //这里会出警告double到int会损失精度模板中定义两个类型的参数这里编译器就会帮你自动强制转换数据类型这里double类型和int类型计算int类型就会被提升为double类型。//显示实例化 Addint(1.1,2);使用这种方法也可以解决明确的告知编译器我就是想用 int 类型的变量。templateclass T T* func(int n) { T* a new T[n]; return a; } //所以这里必需显示实例化 funcint(20);这种模板函数没法自动推演编译器不知道返回值的类型所以使用的时候必须明确告知要返回变量的类型。int Add(int left,int right) { } templateclass T T Add(T left,T right) { return leftright; } Add(1,2);模板函数和普通函数可以同时存在调用时会先去检查有没有专门处理int类型的函数如果没有就调用模板。二、类模板typedef char STDateType; class Stack { private: STDateType *a; int top; int capacity; }; Stack st1; Stack st2;在C语言中为了方便使用不同类型的变量这里一般用宏定义。但是并没有解决在两个对象中一个存储变量int一个存储double。也就是说定义的所有对象只能存储一种类型的数据。但是实际使用的时候是需要灵活变通的要多种类型一起使用包括自定义类型。所以类也可以使模板。PS函数能根据传入的数据推导出数据类型但是类模板不能所以使用模板类实例化对象的时候必须显示实例化。templatetypename T class Stack { public: Stack(size_t capacity 4) :_a(nullptr) ,_capacity(0) ,_top(0); { if(capacity 0) { _a new T[capacity]; _capacity capacity; _top 0; } } ~Stack() { delete[] _a; _a nullptr; _capacity _top 0; } void Push(const T x) { //如果插入满了开辟新空间 if(_top _capacity){ size_t NewCapacity _capatcity 0 ? 4 : _capacity*2; T* tmp new T[NewCapacity]; if(_a){ memcpy(tmp,_a,sizeof(T)*_top); delete[] _a; } _a tmp; _capacity NewCapacity; } _a[_top]x; _top; } void Pop() { assert(_top 0); --_top; } bool Empty() { return _top 0; } T Top() //这边使用了引用那么可以修改,不想修改要加const。 { assert(_top 0); return _a[_top-1]; } private: T *_a; int _top; int _capacity; }; Stackint st1; Stackchar st2; st1.Top();这里两个Stack类型的变量就可以存储多种类型的数据了包括自定义类型而模板其实就是为了能更好的支持自定义类型而创建出来的。PS模板不支持分离编译声明放在.h 定义放在 .cpp 但是可以在当前文件分开写所以可以创建一个.hpp的文件这个是约定俗称的方便人一看到就知道是什么意思。//声明定义在本文件分离。 templateclass T class Stack { public: Stack(size_t capacity 4) :_a(nullptr) ,_capacity(0) ,_top(0); { if(capacity 0) { _a new T[capacity]; _capacity capacity; _top 0; } } ~Stack() { delete[] _a; _a nullptr; _capacity _top 0; } void Push(const T x); void Pop(); bool Empty(); T Top(); private: T *_a; int _top; int _capacity; }; //注意这里的写法 templateclass T void StackT::Push(const T x) { if(_top _capacity){ size_t NewCapacity _capatcity 0 ? 4 : _capacity*2; T* tmp new T[NewCapacity]; if(_a){ memcpy(tmp,_a,sizeof(T)*_top); delete[] _a; } _a tmp; _capacity NewCapacity; } _a[_top]x; _top; }三、STL标准模板库Standard Template LibrarySTL是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。虽说它主要出现到C中但在被引入C之前该技术就已经存在了很长时间。STL的代码从广义上讲分为三类algorithm算法、container容器和iterator迭代器几乎所有的代码都采用了模板类和模板函数的方式这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。STL六大组件容器数据结构算法迭代器配接器仿函数空间配置器。sting#includestring //string是一个宏定义 typedef basic_stringchar string; string str; //等价于 basic_stringchar str使用string容器要加载头文件并且其中的很多函数都在std的命名空间中string是 basic/_string 的宏其中 basic/_string 就是类名了。basic/_string根据不同的模板参数宏定义了多个不同的容器定义多个是为了匹配对应的编码因为不同的编码大小不一样。这里先学习string容器和其中常用函数的使用这个是一通百通的因为其他的容器相似度非常高也是编写库的人特意设置的学会这一个其他的也会使用了。构造函数string可以用同样的string对象来进行初始化也可以使用字符串(char/*)来进行初始化(隐式类型装)。还可以指定要初始化的数据段指定开始的位置和要初始化的长度。如果给与的长度大于了字符串后面的长度那么把后面全部复制下来。其中如果不指定长度其默认的缺省值为一个静态变量npos值为-1。PSnpos的类型为size/_t是一个无符号的整形给值-1会变成一个非常大的整数。那么其意思就很明确了不输入指针的长度则从开始位置往后全部给要初始的对象。这种对象的初始化的方法在STL里面是通用的其他容器大差不差。重载运算符[ ]:我们知道字符串其实就是一个字符数组可以通过[ ]访问每个单个的字符所以string里面重载了运算符[ ]给我们用来访问字符。[ ]返回的是字符的引用所以可以通过这个运算符修改单个的字符。迭代器迭代器每个容器因为其特性不一样实现的方法也不一样在string里面因为其本质还是使用数组来存储数据的所以这里的迭代器就是指针。既然[ ]、指针就能遍历string为何要定义一个迭代器出来因为并不是所有的容器都是使用指针来实现迭代器的。但是迭代器使用的方法都是一样的是STL遍历容器的通用方法。使用string里面的一个宏定义iterator来定义一个变量itit就是string容器的迭代器其使用方法和指针相似。it!s.end(); its.end();这里迭代器判断是不是到了容器尾部的时候最好使用!而不是使用符号虽然两个效果在这里是一样的(因为string底层迭代器就是指针)。但是在其容器中会出错(比如list)。for (auto ch : s) { cout ch endl; } //想修改用使用引用 for(auto ch : it) { ch; }范围for的底层就是迭代器。PS范围for里面的迭代器能自动遍历自动迭代自动判断结束 。反向迭代器其实看名字就大概能明白其是什么意思了就是从容器尾部往开始位置遍历。PS要注意这里迭代器是往容器前面走(左边)–是往容器后面走(右边)。常量迭代器常量反向迭代器PS常量迭代器是不能修改对象中的数据的。push/_back():push/_back()这个函数只能在string尾部插入一个字符。append():在string尾部插入一个字符串并且可以指定插入字符串的开始位置和长度。重载符号用法跟append()差不多只不过看起来更容易理解了用起来也方便。insert():在指定位置插入一个字符串同样可以指定插入字符串的开始位置和长度。reserve()和resize():在插入数据的时候函数内部就已经帮我们判断了如果容器满了则会重新扩容空间。但是我们如果知道要输入数据大小那么就可以提前开辟空间。reserve()开辟空间resize()则是开辟空间并初始化。PSstring内置了一个函数capacity()可以得知现在string的容量。c/_str:有时候需要兼容C语言所以里面包含了兼容C语言的函数c/_str()函数返回的是const char/*的类型。string filename(test.cpp); FILE* fp fopen(filename.c_str,r); char chgetc(fp); while(ch ! EoF){ coutch; char chgetc(fp); }这里filename和filename.c/_str是有区别的filename 是以容器的size()为准。filename.c/_str是常量字符串以“/0”为结束标识。find():查找字符串查找到了返回匹配字符串第一个字符的位置没找到返回npos。find()在查找字符串的时候可以指定开始查找的位置也可以指定查找的字符串长度。但是要注意如果有多个字符串可以匹配上这里只返回第一个匹配上的位置。find/_first/_of():这个函数比较难理解它不是要把完全匹配的字符串位置给找出来。而是匹配的字符串的字符谁第一个在主串出现就返回该处匹配的位置。这里e是最先在主串里面出现的所以这里返回1。匹配失败返回npos。四、模拟实现stringnamespace STR { class string { public: //构造函数 使用初始化列表 string(const char* str ) //这里的缺省值是 /0 :_str(new char[strlen(str)1]) //加1 是加上 /0的空间 ,_size(strlen(str)) ,_capacity(strlen(str)) //capacity不包括 /0 { strcpy(_str, str); } //构造函数 另一种写法 string(const char* str ) //这里的缺省值是 /0 { //这样写可以复用变量如果写在初始列表变量初始顺序要严格匹配声明顺序。 _size strlen(str); _capacity _size; _str new char[_capacity 1]; //加1 是加上 /0的空间 strcpy(_str, str); } //析构函数 ~string() { delete[] _str; _str nullptr; _capacity _size 0; } private: char* _str; size_t _size; //字符串的长度 不包括/0 size_t _capacity; //数组的容量 不统计/0 static size_t nops_t; }; } //静态变量 类外面定义 size_t STR::string::nops_t -1;PS这里为了能方便与库里面的string进行区分这里自定义了一个命名空间STR。这里开始写拷贝构造函数的时候就要注意了成员变量中定义了一个数组那么就要注意深浅拷贝的问题为了避免浅拷贝这里初始化的时候要为 /_str 重新开辟一段空间。传统写法//拷贝构造函数 传统写法 string(const string str) :_str(new char[str._capacity 1]) //新开辟一段空间 , _size(str._size) , _capacity(str._capacity) { strcpy(_str, str._str); }现代写法const char* c_str() const //const string* const this { return _str; } void swap(string str) { std::swap(_str, str._str); std::swap(_size, str._size); std::swap(_capacity, str._capacity); } //拷贝构造函数 现代写法 string(const string str) :_str(nullptr) , _size(0) , _capacity(0) { //注意这里是str._str是char*类型的字符串,这里复用了构造函数 string tmp(str._str); swap(tmp); }拷贝构造函数的现代写法非常巧妙、简洁并且借用了构造函数来进行深拷贝。这里定义了一个交换函数swap()里面复用了库里面的swap()来交换数据。库里面的swap()被模板化了可以直接进行自定义类型的交换但是这里会多次调用拷贝构造浪费效率。所以这里在string内部又重载了一个 swap()函数其本身只是进行变量数据的交换所以效率比较高。现代写法的思路就是把传进来的字符串重新生成一个对象再把对象的数据跟自己交换而且由于tmp是函数内部的局部变量出了函数就会调用析构函数销毁不用手动释放地址。这种写法也可以在重载赋值符号上重载赋值符号的时候也要考虑深浅拷贝的问题。//传统写法 string operator(const string str) { if (this!str) { delete[] _str; _str new char[str._capacity 1]; //1是给/0留的空间 strcpy(_str, str._str); _capacity str._capacity; _size str._size; } return *this; } //现代写法 string operator(const string str) { if (this ! str) { string tmp(str._str); swap(tmp); } return *this; }套路一样把传进来的字符串重新生成一个对象再把对象的数据跟自己交换。string operator(const string str) { if(this!str){ string tmp(str); std::swap(*this,tmp); //这里使用库里面的swap,会造成错误 } return *this; }还有一点 如果重载赋值符号使用库里面的swap()进行交换swap/*thistmp函数内部还是使用的赋值符号会再去调用赋值所以这里会死循环造成栈溢出。//重载[] 返回pos位置的字符的引用 //常量的调用 只能读不能写 const char operator[](size_t pos) const { assert(pos _size); return _str[pos]; } //普遍调用 可以读可以写 char operator[](size_t pos) { assert(pos _size); return _str[pos]; }重载[ ]非常简单直接复用数组的[ ]即可。//扩容 void reserve(size_t n) { if (n _capacity) { char* tmp new char[n 1]; strcpy(tmp, _str); delete[] _str; _str tmp; _capacity n; } } //开空间并初始化 void resize(size_t n,char ch/0) { //1.比当前的空间大 if (n_size) { reserve(n); for (size_t i _size; i n; i) { _str[i] ch; } _str[n] /0; _size n; } else { //2.比当前的空间小 _str[n] /0; _size n; } }扩容函数如果string内部已经有内容的话要把数据拷贝到新开辟的空间里面去。开辟空间的时候要多留一个字节给/0。//插入一个字符串 void push_back(char cn) { if (_size _capacity) { //如果空间满了要扩容 //如果string容器里面没有数据开4个字节的空间。 reserve(_capacity 0 ? 4 : _capacity * 2); } _str[_size] cn; _size; _str[_size] /0; } //插入一个字符串 void append(const char* str) { size_t len strlen(str); if (_size len _capacity) { reserve(_size len); } strcpy(_str _size, str); //strcat(_str,str) 也可以使用这个函数追加不过要找/0效率低 _size len; } void append(const string str) { append(str._str); } void append(size_t n, char ch) { for (size_t i 0; i n; i) { push_back(ch); } } string operator(char ch) { push_back(ch); return *this; } string operator(const char* str) { append(str); return *this; }插入字符或者字符串实现其实比较简单主要是注意判断如果数组的容量占满了要重新开辟空间。//插入字符 string insert(size_t pos, char ch) { assert(pos _size); if (_size _capacity) { reserve(_capacity 0 ? 4 : _capacity * 2); } size_t end _size 1; while (end pos) { _str[end] _str[end - 1]; end--; } _str[pos] ch; _size; return *this; } //插入字符串 string insert(size_t pos, const char* str) { assert(pos _size); size_t len strlen(str); if (_size len _capacity) { reserve(_size len); } size_t end _size len; while (end pos len) { _str[end] _str[end - len]; end--; } strncpy(_str pos, str, len); _size len; return *this; }在指定位置插入时要把插入位置后面的数据往后挪动。这里不要用strcpy()会把/0也拷贝进去所以这里使用了strncay()size_t end _size; while (end pos) { //这里pos0,end减到最后变为-1的时候会变得非常大造成越界。 _str[end 1] _str[end]; end--; } size_t end _size1; while (end pos) { _str[end] _str[end-1]; end--; }PS这里挪动数据有两种写法最好使用下面的写法因为上面在开头插入数据时最后end会变为-1而这里end是size/_t类型的数据会变得非常大从而出错。void push_back(char ch) { insert(_size,ch); } void append(const char* str) { intsert(_size,str); }其实也可以先实现insert()然后复用实现push/_back()和append()。//删除 void erase(size_t pos, size_t n nops_t) //如果不写那么全删除了 { assert(pos _size); //如果没给参数或者给的参数过大则pos后面的全部删除 if (n nops_t || n pos _size) { _str[0] /0; _size 0; } else { //把要删除的数据用后面的数据覆盖 strcpy(_str pos, _str pos n); _size - n; } } void clear() { _str[0] /0; _size 0; }删除函数比较简单没什么要注意的地方。//查找字符 size_t find(char ch, size_t pos 0) { assert(pos _size); for (size_t i pos; i _size;i) { if (ch_str[i]) { return i; } } return nops_t; } //查找字符串 size_t find(const char* sub, size_t pos 0) { assert(pos _size); const char* ret strstr(_str pos,sub); if (ret nullptr) { return nops_t; } else { return ret - _str; } }这里使用了库里面的串匹配函数这里也可以自己写个KMP算法来查找不过其实效率也差不多。这里返回查找到字符串位置的写法比较巧妙使用的是指针减指针的知识点获得的是中间元素的个数而/_str又是数组的起始地址所以这里就返回的就是查找到的位置。bool operator(const string str)const { //大于0就是真 return strcmp(_str, str._str) 0; } bool operator(const string str)const { return strcmp(_str, str._str) 0; } bool operator(const string str)const { return !(*this str); } bool operator(const string str)const { return *this str || *this str; } bool operator(const string str)const { return !(*this str); } bool operator!(const string str)const { return !(*this str); }C语言里面字符串是可以比较大小的所以这里也要重载一下这里直接复用strcmp()以后比较字符串的大小的时候就可以直接使用运算符很方便也容易理解。string substr(size_t pos, size_t len nops_t)const { assert(pos _size); size_t realLen len; //如果没给参数或者给的参数过大则pos后面的全部返回为子串 if (len nops_t || pos len _size) { realLen _size - pos; } string sub; for (size_t i 0; i realLen;i) { sub _str[pos i]; } return sub; }把主串中的一段数据作子串返回。分割字符串的时候用的比较多。//重载流提取和流插入 //实现成全局函数避免和this指针的位置问题。 std::ostream operator(std::ostream out, const string str); std::istream operator(std::istream in, string str); std::ostream STR::operator(std::ostream out, const string str) { for (size_t i 0; i str.size(); i) { out str[i]; } return out; } //这种写法多次扩容多次 std::istream STR::operator(std::istream in, string str) { char ch; //inch 不能使用这个这个无法检查到空格 ch in.get(); while (ch ! ch ! /n) { str ch; ch in.get(); } return in; } std::istream STR::operator(std::istream in, string str) { str.clear(); char ch; const size_t N 32; char buff[N]{0}; size_t i 0; ch in.get(); //其思路是buff满了再追加到str里面 while (ch ! ch ! /n) { buff[i] ch; if (i N-1) { buff[i] /0; str buff; i 0; } ch in.get(); } buff[i] /0; str buff; return in; }这里流提取比较好理解但是流插入要注意一下流插入是可以连续对多个对象进行插入的。但是使用cinch这种写法不能接收到空格和换行符因为它本身检查到空格和换行就是输入的间隔会造成这里退出不了循环所以这里使用了cin里面自带的get()函数。如果输入的内容很长使用的效率很低。所以这里使用一个数组先接收一定的数据数组的数据满了才添加到对象里面(和缓冲区的概念相似)。//迭代器 typedef char* iterator; //const 迭代器 typedef const char* const_iterator; iterator begin() { return _str; } iterator end() { return _size _str; }迭代器的实现也比较简单因为string就是用数组来存储的字符串用指针也可以访问这里只不过是把指针进行了宏定义但是这种写法也是可以使用范围for的。学习资源如果你是也准备转行学习网络安全黑客或者正在学习这里开源一份360智榜样学习中心独家出品《网络攻防知识库》,希望能够帮助到你**读者福利 |**CSDN大礼包《网络安全入门进阶学习资源包》免费分享**安全链接放心点击**![](https://i-blog.csdnimg.cn/img_convert/a6502ab41b1a86132b9ebb5aab9a2cdc.jpeg)知识库由360智榜样学习中心独家打造出品旨在帮助网络安全从业者或兴趣爱好者零基础快速入门提升实战能力熟练掌握基础攻防到深度对抗。1、知识库价值深度 本知识库超越常规工具手册深入剖析攻击技术的底层原理与高级防御策略并对业内挑战巨大的APT攻击链分析、隐蔽信道建立等提供了独到的技术视角和实战验证过的对抗方案。广度 面向企业安全建设的核心场景渗透测试、红蓝对抗、威胁狩猎、应急响应、安全运营本知识库覆盖了从攻击发起、路径突破、权限维持、横向移动到防御检测、响应处置、溯源反制的全生命周期关键节点是应对复杂攻防挑战的实用指南。实战性 知识库内容源于真实攻防对抗和大型演练实践通过详尽的攻击复现案例、防御配置实例、自动化脚本代码来传递核心思路与落地方法。2、 部分核心内容展示360智榜样学习中心独家《网络攻防知识库》采用由浅入深、攻防结合的讲述方式既夯实基础技能更深入高阶对抗技术。360智榜样学习中心独家《网络攻防知识库》采用由浅入深、攻防结合的讲述方式既夯实基础技能更深入高阶对抗技术。内容组织紧密结合攻防场景辅以大量真实环境复现案例、自动化工具脚本及配置解析。通过策略讲解、原理剖析、实战演示相结合是你学习过程中好帮手。1、网络安全意识2、Linux操作系统3、WEB架构基础与HTTP协议4、Web渗透测试5、渗透测试案例分享6、渗透测试实战技巧7、攻防对战实战8、CTF之MISC实战讲解3、适合学习的人群‌一、基础适配人群‌‌零基础转型者‌适合计算机零基础但愿意系统学习的人群资料覆盖从网络协议、操作系统到渗透测试的完整知识链‌‌开发/运维人员‌具备编程或运维基础者可通过资料快速掌握安全防护与漏洞修复技能实现职业方向拓展‌或者转行就业‌应届毕业生‌计算机相关专业学生可通过资料构建完整的网络安全知识体系缩短企业用人适应期‌‌二、能力提升适配‌1、‌技术爱好者‌适合对攻防技术有强烈兴趣希望掌握漏洞挖掘、渗透测试等实战技能的学习者‌2、安全从业者‌帮助初级安全工程师系统化提升Web安全、逆向工程等专项能力‌3、‌合规需求者‌包含等保规范、安全策略制定等内容适合需要应对合规审计的企业人员‌因篇幅有限仅展示部分资料完整版的网络安全学习资料已经上传CSDN朋友们如果需要可以在下方CSDN官方认证二维码免费领取【保证100%免费】5、渗透测试案例分享*6、渗透测试实战技巧7、攻防对战实战8、CTF之MISC实战讲解3、适合学习的人群‌一、基础适配人群‌‌零基础转型者‌适合计算机零基础但愿意系统学习的人群资料覆盖从网络协议、操作系统到渗透测试的完整知识链‌‌开发/运维人员‌具备编程或运维基础者可通过资料快速掌握安全防护与漏洞修复技能实现职业方向拓展‌或者转行就业‌应届毕业生‌计算机相关专业学生可通过资料构建完整的网络安全知识体系缩短企业用人适应期‌‌二、能力提升适配‌1、‌技术爱好者‌适合对攻防技术有强烈兴趣希望掌握漏洞挖掘、渗透测试等实战技能的学习者‌2、安全从业者‌帮助初级安全工程师系统化提升Web安全、逆向工程等专项能力‌3、‌合规需求者‌包含等保规范、安全策略制定等内容适合需要应对合规审计的企业人员‌因篇幅有限仅展示部分资料完整版的网络安全学习资料已经上传CSDN朋友们如果需要可以在下方CSDN官方认证二维码免费领取【保证100%免费】