网站管理工具,网站建设的应对措施,南宁法拍房源信息,急招一对夫妻门卫6500元文章目录 1. 泛型编程2. 函数模板2. 1 函数模板概念2. 2 函数模板格式2. 3 函数模板的原理2. 4 函数模板的实例化2. 5 模板参数的匹配原则2. 6 补充#xff1a;使用调试功能观察函数调用 3. 类模板3 .1 类模板的定义格式3. 2 类模板的实例化 1. 泛型编程
在C语言中#xff0… 文章目录 1. 泛型编程2. 函数模板2. 1 函数模板概念2. 2 函数模板格式2. 3 函数模板的原理2. 4 函数模板的实例化2. 5 模板参数的匹配原则2. 6 补充使用调试功能观察函数调用 3. 类模板3 .1 类模板的定义格式3. 2 类模板的实例化 1. 泛型编程
在C语言中如果我们要实现交换函数swap为了让这个函数能兼容所有的类型我们需要swap_intswap_double等等为每个类型单独实现一个对应的交换函数还要起不同的名称防止冲突。 而在C中我们可以使用函数重载不再需要使用其他函数名但是似乎仍然需要为每个类型单独实现一个函数
void Swap(int left, int right)
{int temp left;left right;right temp;
}
void Swap(double left, double right)
{double temp left;left right;right temp;
}
void Swap(char left, char right)
{char temp left;left right;right temp;
}
//....使用函数重载虽然可以实现需求但是依然有一下几个不好的地方:
重载的函数仅仅是类型不同代码复用率比较低只要有新类型出现时就需要用户自己增加对应的函数。代码的可维护性比较低一个出错可能所有的重载均出错。
那能否告诉编译器一个模子让编译器根据不同的类型利用该模子来生成代码呢
工业上生产不同颜色的同一种金属制品会先制作一个模具然后通过往模具中加入实现准备好的不同颜色的液态金属就可以制作出不同的成品。
如果在C中也能够存在这样一个模具通过给这个模具中填充不同材料(类型)来获得不同材料的铸件(即生成具体类型的代码)那将会节省许多不必要的代码。 巧的是前人早已将树栽好我们只需在此乘凉这便是泛型编程和模板。
泛型编程编写与类型无关的通用代码是代码复用的一种手段。 模板是泛型编程的基础。
模板有两种 我们来一一介绍。
2. 函数模板
2. 1 函数模板概念
函数模板代表了一个函数家族该函数模板与类型无关在使用时被参数化根据实参类型产生函数的特定类型版本。
2. 2 函数模板格式
templatetypename T1, typename T2,..,typename Tn
返回值类型 函数名(参数列表)
{}以swap函数为例
templatetypename T
void swap(T a, T b)
{T temp a;a b;a temp;
}注意typename是用来定义模板参数的关键字也可以使用class不能使用struct。
2. 3 函数模板的原理
函数模板是一个蓝图它本身并不是函数是编译器用使用方式产生特定具体类型函数的模具。 所以其实模板就是将本来应该我们做的重复的事情交给了编译器。 在编译器编译阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的代码对于字符类型也是如此。
2. 4 函数模板的实例化
用不同类型的参数使用函数模板时称为函数模板的实例化。板参数实例化分为隐式实例化和显式实例化。
隐式实例化让编译器根据实参推演模板参数的实际类型。
#includeiostream
using namespace std;
templateclass T
T Add(T A, T B)
{return A B;
}int main()
{int a 10;char b a;cout Add(a, b) endl;return 0;
}该语句不能通过编译因为在编译期间当编译器看到该实例化时需要推演其实参类型通过实参a将T推演为int通过实参b将T推演为char类型但模板参数列表中只有一个T编译器无法确定此处到底该将T确定为int 还是char类型而报错。 注意在模板中编译器一般不会进行类型转换操作。 这时候有两种解决办法
手动进行强制类型转换
Add(a, (int)b);这样就可以了形参接收到的是强制类型转换后的数值。
显式实例化在函数名后的中指定模板参数的实际类型。
int main()
{int a 10;char b a;cout Addint(a, b) endl;return 0;
}这个写法可以先给Add的模板的T传int使其生成对应的函数后再接收参数那么如果类型不匹配编译器会尝试进行隐式类型转换如果无法转换成功编译器将会报错。
但是要注意对于swap函数来说上面的两种做法都不能使swap的两个参数不同第一个做法中强制类型转换产生的是一个临时对象具有常性不可修改第二个中隐式类型转换产生的也是一个临时对象不可修改。除非写一个两个实参类型不同的函数模板比如
#includeiostream
using namespace std;templateclass T,class Y
void swap(T a, Y b)
{//交换函数的实现
}因为这样的函数模板没有什么实际意义所以就不实现了。 不过顺带一提对于这样的函数如果我们想在调用时指定其两个参数的类型可以这么写
#includeiostream
//using namespace std; //这里注意std命名空间中有一个swap函数模版templateclass T,class Y
void swap(T a, Y b)
{//交换函数的实现
}int main()
{int a 10;char b a;swapint, char(a, b); //中不同的类型名用 , 隔开std::cout a b std::endl;return 0;
}代码中也提到了全局的swap函数模板所以在实践中我们不需要手动去实现swap函数模板直接使用库里的就行了库函数的swap不支持不同类型变量的交换。
2. 5 模板参数的匹配原则
一个非模板函数可以和一个同名的函数模板同时存在即使该函数模板可以被实例化为这个非模板函数。
#includeiostream
using namespace std;// 显式写出 int 的Add函数
int Add(const int a, const int b)
{return a b;
}// 这个函数模板也可以实例化出 int 的Add函数
templateclass T
T Add(const T a, const T b)
{return a b;
}int main()
{cout Add(1, 2) Add(10.2, 15.0) endl;return 0;
}对于这种情况编译器会优先使用显式写出来的那个int类型的Add函数而不会使用函数模板去重新生成一个。
对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数那么将选择模板。
#includeiostream
using namespace std;// 显式写出 int 的 Add 函数
int Add(const int a, const int b)
{return a b;
}//这个函数模板也能实例化出 int 的 Add 函数
templateclass T,class Y
T Add(const T a, const Y b)
{return a b;
}int main()
{cout Add(1, 2.0) endl;return 0;
}这种情况下编译器就不会使用上面显式写出来的函数而是使用函数模板实例化出来一个新的函数。
模板函数不允许自动类型转换但普通函数可以进行自动类型转换。 这一条实际上在2.4中已经介绍过了不再赘述。
2. 6 补充使用调试功能观察函数调用
我们怎么能知道函数调用的是哪个模板或者说哪个显式写出来的函数呢 虽然可以通过在函数内部写一条输出语句输出不同的内容实现但这在实际开发中显得有些麻烦要给所有的可能的模板都加上输出所以一般不采用。 我们可以使用调试功能来观察这里以VS2022的调试功能为例
当程序运行到函数调用的这一行时按F11逐语句就可以进入调用的函数内部 可以发现这个函数调用的就是这个函数模板实例化出来的函数如果是调用的其他模板或函数程序就会执行到相应的位置。
3. 类模板
3 .1 类模板的定义格式
templateclass T1, class T2, ..., class Tn
class 类模板名
{// 类内成员定义
};举例
#includeiostream
using namespace std;
templatetypename T
class Stack
{
public:Stack(size_t capacity 4):_size(0),_capacity(capacity)//,_data(new T[capacity]) //注意不能这么写详情请看类和对象下{_data new T[_capacity];}// 析构函数略void push_back(const T temp){if (_size _capacity){T* newdata new int[2 * _capacity];for (int i 0; i _size; i){newdata[i] _data[i];}delete[] _data;_data newdata;}_data[_size] temp;}void Print(){for (int i 0; i _size; i){cout _data[i] ;}cout endl;}private:T* _data;size_t _size;size_t _capacity;
};int main()
{Stackint a;a.push_back(1);a.push_back(2);a.push_back(3);a.push_back(4);a.push_back(5);a.Print();return 0;
}对于Stack类中的_data数组它的类型完全是由模板参数控制的在创建时可以根据需要自由选择种类。 同时对Print函数来说cout这一非格式化输出也是很有意义的因为它不可能使用printf函数去输出。
另外对于类模板不建议把成员函数的声明和定义拆分到不同的文件.h和.cpp中会导致编译错误。 原因有二
多重定义错误 如果将模板的定义放在.cpp文件中并且不在头文件中声明这些成员函数则在每个包含该头文件的编译单元中都需要重新定义这些成员函数。这会导致链接错误因为每个翻译单元都会看到一个不同的定义。编译时实例化需求 当编译器遇到模板类的使用时它需要知道整个模板类的定义以便它可以为特定的模板参数实例化模板。如果成员函数的定义不在同一个文件中编译器就无法生成正确的代码。
3. 2 类模板的实例化
类模板实例化与函数模板实例化不同类模板实例化需要在类模板名字后跟然后将实例化的类型放在中即可类模板名字不是真正的类而实例化的结果才是真正的类。
也就是说
Stackint a;类模板创建类的时候int是必须的而模板函数在一些情况下不是必须的。
谢谢你的阅读喜欢的话来个点赞收藏评论关注吧 我会持续更新更多优质文章