类似+wordpress+建站,联享品牌网站建设,网站的积分系统怎么做,动漫制作专业好找工作吗相关代码gitee自取#xff1a;
C语言学习日记: 加油努力 (gitee.com)
接上期#xff1a;
【C初阶】七、内存管理 #xff08;C/C内存分布、C内存管理方式、operator new / delete 函数、定位new表达式#xff09; -CSDN博客 目录 一 . 泛型编程 二 . 函数模板 函数模板…
相关代码gitee自取
C语言学习日记: 加油努力 (gitee.com) 接上期
【C初阶】七、内存管理 C/C内存分布、C内存管理方式、operator new / delete 函数、定位new表达式 -CSDN博客 目录 一 . 泛型编程 二 . 函数模板 函数模板的概念 函数模板的格式 函数模板的原理 函数模板的实例化 隐式实例化 显式实例化 模板参数的匹配原则 三 . 类模板 类模板的定义格式 类模板的实例化 图示 -- 以栈类为例 本篇博客相关代码 Test.cpp文件 -- C文件 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 一 . 泛型编程 我们以前写的函数一般都是针对某种类型的如实现两值交换Swap函数 如果交换的两值是int类型那就要将Swap函数的参数设置为int类型 如果交换的两值是double类型那就要将Swap函数的参数设置为double类型…… 通过函数重载实现 对于函数虽然函数重载可以实现函数参数多类型的问题但也有一些不好的地方1、重载的函数仅仅是类型不同而已具体实现和实现逻辑都是很类似的当接收的函数参数类型不同时就需要用户自己增加对应的重载函数2、代码的可维护性比较低其中一个重载函数出错可能所有的重载函数都会出错 那能不能实现一个通用的Swap函数呢实现泛型编程呢 泛型编程 -- 编写与类型无关的通用代码是代码复用的一种手段C中为解决这个问题有了模板的概念模板是泛型编程的基础 有了模板相当于告诉编译器一个模子让编译器能够根据不同的类型利用该模子来生成对应类型的代码模板分为函数模板和类模板 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 二 . 函数模板 函数模板的概念 函数模板代表了一个函数家族该函数模板与类型无关 在使用时被参数化根据实参类型产生函数的特定类型版本 函数模板的格式 注意typename是用来定义模板参数的关键字上面的T1、T2、Tn就是模板参数除了可以使用typename来定义还可以使用class来定义 //函数模板格式
templatetypename T1, typename T2, ……, typename Tn
函数返回值类型 函数名(参数列表)
{// 函数体
} 图示 函数模板的原理 函数模板是一个蓝图它本身并不是函数是编译器使用后能产生特定具体类型函数的摸具。所以模板就是将本来应该由我们完成的重复的事情交给了编译器完成 在编译器编译阶段对于函数模板的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如当使用double类型调用函数模板时编译器通过对实参类型的推演将模板参数T确定为double类型然后产生一份专门处理double类型的代码 图示 函数模板的实例化 用不同类型的参数调用模板时称为函数模板的实例化。模板参数实例化分为隐式示例化和显式实例化 隐式实例化 让编译器根据实参推演模板参数的实际类型 上面的图示中的模板参数实例化都是隐式实例化 图示 隐式实例化中如果只设置了一个模板参数但实参中却有多种类型这时将不能通过编译此时有两种处理方式1、用户自己来强制转化 2、使用显式实例化 图示 --------------------------------------------------------------------------------------------- 显式实例化 不通过模板参数推演识别出实参的类型而是自己显式设置模板参数的类型在函数名后的中指定模板参数的实际类型即可 显式实例化时如果类型不匹配编译器会尝试进行隐式类型转换如果无法转换成功编译器将会报错 显式实例化的真正用法设置了一个模板但函数参数中并没有设置模板参数而函数体中却使用了模板参数类型或者返回值是模板参数类型这种情况就需要显式实例化来确定实参类型 图示 模板参数的匹配原则 一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数 图示 对于非模板函数和同名函数模板如果其它条件都相同在调动时会优先调用非模板函数而不会从该模板产生出一个示例。如果模板可以产生一个具有更好匹配的函数那么将选择模板 图示 模板函数不允许自动类型转换但普通函数可以进行自动类型转换 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 三 . 类模板 类模板的定义格式 类模板定义和函数模板定义类似定义时将函数的位置换成类即可模板参数类型在类中定义成员类型时进行使用 //类模板定义格式
templateclass T1, class T2, ……, class Tn
class 类模板名
{ // 类内成员定义
} 类模板的实例化 类模板实例化和函数模板实例化不同类模板实例化需要在类模板名字后跟然后将实例化的类型放在中即可 类模板名字不是真正的类名显式实例化后的结果才是真正的类名 同一个类模板显式实例化出的不同类这些类的类型是不一样的以栈类模板为例Stackint st1 和 Stackdouble st2st1 的类型是 Stackint 是用于存储int类型数据的栈st2 的类型是 Stackdouble 是用于存储double类型数据的栈st1 和 st2 的类型是不一样的 图示 -- 以栈类为例 注意类模板成员函数的声明和实现分离不能分离到两个文件中分离时通常都写在一个.h文件中。而且分离后的成员函数实现部分需要设置对应的函数模板分离后不指定成员函数的类域而是指定其“类模板类型” 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本篇博客相关代码 Test.cpp文件 -- C文件 #define _CRT_SECURE_NO_WARNINGS 1//包含IO流
#include iostream;
//完全展开std命名空间
using namespace std;//Swap函数 -- 交换两个int类型数据
void Swap(int left, int right)
{int temp left;left right;right temp;
}//Swap函数 -- 交换两个double类型数据
void Swap(double left, double right)
{double temp left;left right;right temp;
}//Swap函数 -- 交换两个char类型数据
void Swap(char left, char right)
{char temp left;left right;right temp;
}/*
* 这里实现了三个Swap函数
* 分别交换了三种不同的类型
* 但实现的逻辑都是相同的就只有交换类型不同
* 所以就造成了某种程度的“冗余”
*
* 上面的函数都需要针对具体的类型
* 那能不能让一个代码能够针对广泛的类型呢
* C中就有了泛型编程
*///函数模板
templatetypename T
//tyename 也可以写成 class
//templateclass T
//Swap函数 -- 交换两类型数据泛型编程
void Swap(T left, T right)
{char temp left;left right;right temp;
}
/*
* 使用函数模板即可实现泛型编程
* 让函数能够针对广泛的类型
* 而不只能针对一种类型
*
* 通过关键字template即可定义一个模板
* Swap函数的参数设置为模板参数
*///主函数
int main()
{int a 0; //int类型变量int b 1; //int类型变量double c 1.1; //double类型变量double d 2.2; //double类型变量//调用设置了模板参数的Swap函数Swap(a, b); //int类型 -- 模板参数TSwap(c, d); //double类型 -- 模板参数T/** 这里调用的两个Swap函数实际不是同一个* 两个Swap函数的函数地址不同* 不同类型调用的Swap函数不同是由模板参数导致的* * 模板的原理* 模板参数接受参数如果是int类型* 需要调用到int类型的函数* T 模板参数就会推演成 int类型模板参数推演* 然后就会实例化出具体的函数* T 是int类型的对应函数。模板实例化* * 如果接收的是double类型数据* T 模板参数就会推演成 double类型模板参数推演* 然后就会示例化出具体的函数* T 是double类型的对应函数。模板实例化*/return 0;
}//如果一个函数需要接收不同类型的参数
templateclass T1, class T2
/*
* 如果需要接收不同类型的参数
* 直接在模板中设置多个模板参数即可
*模板参数名可以随便取但一般会取为T -- type
*
* 模板参数 和 函数参数 类似
* 但是 函数参数 定义的是 形参对象
* 而 模板参数 定义的则是 类型
*/
void func(const T1 t1, const T2 t2)
//模板参数T1接收一种类型T2接收另一种类型
{cout t1 endl;cout t2 endl;/** 设置了模板参数的函数* 如果要进行输入或输出* 就必须使用 cin/cout 进行 输入/输出 了* 因为设置了模板参数* 不知道实际传进来的数据是什么数据* 因为使用 scanf/printf 必须要指定数据类型* 所以这里使用 scanf/printf 来 输入/输出*/
}//通用泛型加法函数
templateclass T
T Add(T left, T right)
//接收 T 模板参数类型
{return left right;//返回值也是 T 模板类型
}templateclass T
T* f()
{//开辟T类型的动态空间T* p new T[10];//没设置模板参数T却使用了T//返回T类型指针return p;/** 该函数没有设置模板参数T* (设置的参数不是模板参数)* 但返回值却返回模板指针类型(T*)* * 没设置模板参数就无法进行类型推演*/
}//主函数
int main()
{/** 推演实例化* 函数参数传递推演出模板参数的类型* 再生成实例化对应的函数*///隐式实例化//T1推演为intT2推演为intfunc(1, 2);//T1推演为doubleT2推演为doublefunc(1.1, 2.2);//T1推演为doubleT2推演为intfunc(1.1, 2);//调用通用泛型加法函数cout Add(1, 2.2) endl;/** 该语句不能通过编译因为在编译期间* 当编译器看到该实例化时需要推演其实参类型* 通过实参a1将T推演为int通过实参d1将T推演为double类型* 但模板参数列表中只有一个T* 编译器无法确定此处到底该将T确定为int 或者 double类型而报错* * 注意在模板中编译器一般不会进行类型转换操作* 因为一旦转化出问题编译器就需要背黑锅* * 此时有两种处理方式1. 用户自己来强制转化 2. 使用显式实例化*/ cout Add(1, (int)2.2) endl;//显式实例化// 2.2 隐式转换为int类型cout Addint(1, 2.2) endl; // 1 隐式转换为double类型cout Adddouble(1, 2.2) endl;/** 直接显式实例化指定将参数实例化为某种类型* 而不通过模板参数的类型推演*///函数没有设置模板参数double* p fdouble();/** 函数参数没设置模板参数* 但却使用了模板参数* 编译器没法进行类型推演* 所以此时就需要显式实例化来确定类型* 显式实例化的真正用法*/return 0;
}//使用栈解决多类型问题
typedef int STDataType;
/*
* 想让栈存储int类型数据
* 就在这里设置类型为int
* 想让栈存储double类型数据
* 就在这里设置类型为double
* ……
*/类模板
//templateclass T
//
类模板 -- 栈类
//class Stack
//{
//public: //公有成员函数
//
// //构造函数
// Stack(int capacity 4)
// {
// //调用了构造函数则打印
// cout Stack(int capacity 4) endl;
//
// //使用new开辟栈容量大小的空间
//
// // typedef 设置多类型
// //_a new STDataType[capacity];
//
// // 类模板 设置多类型
// _a new T[capacity]; //使用模板T类型
//
// _top 0; //栈顶值默认为0
// _capacity capacity; //设置栈容量
// }
//
// //析构函数
// ~Stack()
// {
// //调用了析构函数则打印
// cout ~Stack() endl;
//
// //使用delete释放new开辟的空间
// delete[] _a;
//
// _a nullptr; //置为空指针
// _top 0; //栈顶值置为0
// _capacity 0; //栈容量置为0
// }
//
//private: //私有成员变量
//
// T* _a; //栈指针 -- 使用模板T类型
// int _top; //栈顶值
// int _capacity; //栈容量
//
//};//类模板
templateclass T//类模板 -- 栈类
class Stack
{
public: //公有成员函数//构造函数 -- 类模板成员函数声明和定义分离Stack(int capacity 4);//析构函数~Stack(){//调用了析构函数则打印cout ~Stack() endl;//使用delete释放new开辟的空间delete[] _a;_a nullptr; //置为空指针_top 0; //栈顶值置为0_capacity 0; //栈容量置为0}private: //私有成员变量T* _a; //栈指针 -- 使用模板T类型int _top; //栈顶值int _capacity; //栈容量 };//类模板成员函数的声明和实现分离
templateclass T
StackT::Stack(int capacity)
/*
* 实现时指定的不是Stack成员函数的类名类域
* 而是Stack成员函数的类型
* (不是Stack::而是StackT)
* 需要把模板参数写出来
*
* 注
* 类模板不允许声明和定义分离到两个文件
* 分离时都写在一个.h文件中
*/
{//调用了构造函数则打印cout Stack(int capacity 4) endl;//使用new开辟栈容量大小的空间// typedef 设置多类型//_a new STDataType[capacity];// 类模板 设置多类型_a new T[capacity]; //使用模板T类型_top 0; //栈顶值默认为0_capacity capacity; //设置栈容量
}//主函数
int main()
{/** typedef 可以解决多类型问题* 那 typedef 可以代替 模板 吗* * 答案是不能typedef设置一个类的类型后* 该类的类型就只能是typedef设置的那一种了* * 如果用类模板的话设置一个类的话* 该类的一个对象就可以是int类型* 而该类的另一个对象还可以是double类型*///显式实例化//让这个栈类型对象存储int类型Stackint st1; //int//让这个栈类型对象存储double类型Stackdouble st2; //double/** 函数模板可以显式实例化也可以让它自己推演* 函数模板大多数情况让它自己推演出类型* 不进行显式实例化* * 类模板能让一个类的对象是不同类型的* 如都是栈类对象但一个栈对象存储int类型数据* 另一个栈对象存储double类型数据* 只需要在创建对象时显式实例化需要的类型即可。* 这时我们实现的数据结构* 就跟具体的存储类型是无关的想要哪种类型* 在创建对象时就显式实例化哪种类型*/return 0;
}// 专门处理int的加法函数 -- 非模板函数
int Add(int left, int right)
{return left right;
}// 通用加法函数 -- 模板函数
templateclass T
T Add(T left, T right)
{return left right;
}//测试函数
void Test()
{// 与非模板函数匹配编译器不需要特化Add(1, 2); // 调用编译器特化的Add版本Addint(1, 2); //函数模板Add被实例化为非模板函数Add
}//专门处理int的加法函数 -- 非模板函数
int Add(int left, int right)
{return left right;
}//通用加法函数 -- 模板函数
templateclass T1, class T2
T1 Add(T1 left, T2 right)
{return left right;
}//测试函数
void Test()
{//Add(int, int)Add(1, 2);/** 与非函数模板类型完全匹配* 不需要函数模板实例化*///Add(int, double)Add(1, 2.0);/** 模板函数可以生成更加匹配的版本* 编译器根据实参生成更加匹配的Add函数*/
}