矿区网站建设,wordpress经典的主题,餐饮品牌设计网站,沈阳网站建设方案文章目录1. 泛型编程2. 函数模板2.1 函数模板的概念2.2 函数模板的使用2.3 函数模板的原理2.4 函数模板的实例化隐式实例化显式实例化2.5 模板参数的匹配原则3. 类模板1. 泛型编程
首先我们来思考一个问题#xff1a;如何实现一个通用的交换函数呢#xff1f; 即我们想交换两…
文章目录1. 泛型编程2. 函数模板2.1 函数模板的概念2.2 函数模板的使用2.3 函数模板的原理2.4 函数模板的实例化隐式实例化显式实例化2.5 模板参数的匹配原则3. 类模板1. 泛型编程
首先我们来思考一个问题如何实现一个通用的交换函数呢 即我们想交换两个变量这两个变量可以是整型也可以是浮点型或者其它内置类型然后它们的交换都可以用一个函数完成。 那在C语言中肯定是没法解决这个问题的不过我们之前学习过在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;
}
...这几个函数的函数名相同只是参数列表不同构成重载这样我们想交换不同类型的变量都是去调用Swap函数然后根据参数类型的不同会自动匹配去调用对应的交换函数。 这与C语言相比确实有了一点进步。 但是呢还是有一些不好的地方 使用函数重载虽然可以实现但是有一下几个不好的地方 重载的函数仅仅是类型不同代码复用率比较低只要有新类型出现时就需要用户自己增加对应的函数代码的可维护性比较低一个出错可能所有的重载均出错 这些重载的函数呢干的事情都是一样的只是处理的数据的类型不同。 那我们想 能否告诉编译器一个模子模板让编译器根据不同的类型利用该模子来生成不同的代码呢 就类似于这样。 那如果在C中也能够存在这样一个模具就好了 通过给这个模具中填充不同材料(类型)来获得不同材料的铸件(即生成具体类型的代码那将会节省许多头发。 巧的是前人早已将此树栽好我们只需在此乘凉 C引入了泛型编程就可以解决这个问题。 泛型编程编写与类型无关的通用代码是代码复用的一种手段。 模板是泛型编程的基础又分为函数模板和类模板。 借助模板我们就可以解决上面的问题。 2. 函数模板
那我们先来学习一下函数模板。
2.1 函数模板的概念 函数模板代表了一个函数家族该函数模板与类型无关在使用时被参数化根据实参类型产生特定类型版本的函数。 2.2 函数模板的使用
函数模板格式 templateclass T1, class T2,...,class Tn 返回值类型 函数名(参数列表) { } 注意class是用来定义模板参数的关键字也可以使用typename(切记不能使用struct代替class) 举个栗子上面的Swap函数有了模板我们就可以这样搞
templateclass T
void Swap(T left, T right)
{T temp left;left right;right temp;
}这里的T是我们定义的模板的类型名称是自己起的我们调用Swap时传的参数是什么类型T就会被替换成对应的类型然后Swap函数就对该类型的参数进行相应的处理。 那现在我们交换不同类型的变量还需要一种类型写一个嘛不需要了用这一个就够了 是不是就搞定了啊。 那现在问大家一个问题 我们上面的Swap(a, b)和Swap(c, d)调用的是同一个函数吗 我们调式去看的话会发现它们都进到Swap里面了。 但是 我们刚才写的是个啥是一个具体的函数吗 是不是一个函数模板啊并不是一个函数。 如果我们去观察汇编的话会发现它们两个去call的函数是不一样的并不是一个。 其实大家想一下函数要建立栈帧它们的参数类型都不一样那建立的栈帧都不一样大怎么可能是同一个嘛。 2.3 函数模板的原理
那这样的话大家再思考一下 函数模板的原理是什么呢 大家都知道瓦特改良蒸汽机人类开始了工业革命解放了生产力。机器生产淘汰掉了很多手工产品。 本质是什么重复的工作交给了机器去完成。 有人给出了论调懒人创造世界。 那函数模板的原理呢其实也是这样 函数模板是一个蓝图它本身并不是函数是编译器用来产生特定具体类型函数的模具。 所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做。 那具体是怎么做的呢 在编译器编译阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。 比如当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的代码对于其它类型也是如此。 另外再给大家提一个东西就是 其实swap这个函数C库里面是提供了的我们可以直接用 不过库里面的是小写我们自己刚才的写成大写区分一下所以以后我们再用swap就不用自己写了。 当然这里我们自己写是拿它来给大家举例子帮助我们理解知识的。 2.4 函数模板的实例化 用不同类型的参数使用函数模板时函数模板生成对应类型参数的具体函数称为函数模板的实例化。 模板参数实例化分为隐式实例化和显式实例化。 隐式实例化 让编译器根据实参推演模板参数的实际类型 我们来看这样一段代码
templateclass T
T Add(const T left, const T right)
{return left right;
}
int main()
{int a1 10, a2 20;double d1 10.0, d2 20.0;Add(a1, a2);Add(d1, d2);return 0;
}我们提供了一个加法函数的模板然后在main函数里分别加了两个整型和浮点型。 目前是没什么问题的。 那如果这样呢 这样就不行了为什么呢 因为这时候函数模板在推演实例化的时候会出现歧义 该语句不能通过编译因为在编译期间该函数模板实例化时需要推演其实参类型。这时通过实参a1将T推演为int通过实参d1将T推演为double类型但模板参数列表中只有一个T编译器无法确定此处到底该将T确定为int 或者 double类型而报错。 注意在模板中编译器一般不会进行类型转换操作因为一旦转化出问题编译器就需要背黑锅 那面对这种情况有两种解决方式 首先第一种方法就是我们自己去进行强制类型转换。 这样就没问题了。 那另一种方法呢
显式实例化 在函数名后的中指定模板参数的实际类型 这样也可以解决。 这种情况如果类型不匹配编译器会尝试进行隐式类型转换如果无法转换成功编译器将会报错。 2.5 模板参数的匹配原则
来看这两个函数可以同时存在吗
// 专门处理int的加法函数
int Add(int left, int right)
{return left right;
}
// 通用加法函数
templateclass T
T Add(T left, T right)
{return left right;
}是可以的 一个非模板函数可以和一个同名的函数模板同时存在 然后再看 这里会调用哪一个 我们通过调式可以看到它调的是第一个。 为什么会调第一个因为编译器在这个地方也会看调哪一个成本会更低一点第一个呢可以直接调但第二个的话是不是还要用模板实例化之后才能调啊。 所以在这里编译器选择了第一个。 那如果我们就想调函数模板生成的那个呢可以做到吗 当然可以我们只要显示实例化就行了 所以呢 一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数。 另外 对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而不会从该模板产生出一个实例。 但如果模板可以产生一个具有更好匹配的函数 那么将选择模板。 举个栗子在刚才的基础上我们再增加一个模板函数
templateclass T1, class T2
T1 Add(T1 left, T2 right)
{return left right;
}首先Add(a, b)默认调用非模板函数这个我们上面刚说过那大家思考一下Add(1, 2.0)会调用哪个 我们看到这里调用了两个参数的模板函数生成的更加匹配的Add函数。 首先大家要知道这里其实第一个非模板函数也是可以调的普通函数是可以进行自动类型转换的而模板函数是不会自动类型转换的。像我们刚才上面就是强制类型转换的。 但是当前这种情况要调非模板函数毕竟还得进行一个类型转换而我们得第二个函数模板有两个参数T1和T2那调用的时候模板是不是可以产生一个具有更好匹配的函数。 Add(1, 2.0)T1自动推演为intT2自动推演为double。 所以这里就选择调用模板生成的函数了。 那除了函数模板之外呢还有类模板。
3. 类模板
那学习了上面的内容相信类模板大家就能很容易理解了。
举个栗子 如果没有类模板的话在C里我们想写一个栈类一般是这样的 typedef int DataType;
class Stack
{
public://构造函数Stack(size_t capacity 4){_array (DataType*)malloc(sizeof(DataType) * capacity);if (NULL _array){perror(malloc申请空间失败!!!);return;}_capacity capacity;_size 0;}void Push(DataType data){// CheckCapacity();_array[_size] data;_size;}// 其他方法...
private:DataType* _array;int _capacity;int _size;
};一般我们会typedef一下这样如果我们想改变栈里存储数据的类型就比较方便了。 但是 如果我们在main函数里定义了2个或者多个栈想让它们分别存储不同类型的数据能不能做到呢 显然是没法做到的现在是int那它们两个里面就都只能存int如果我们改成double那就都只能存double。 如果想做到那就只能定义两个栈的类一个int的一个double的。但是这样它们除了数据类型不一样其它是不是都一样啊。 那这种没有什么技术含量的事情我们就可以交给编译器帮我们做。 怎么搞呢?用类模板就行了
templateclass T
class Stack
{
public:Stack(int capaicty 4){_a new T[capaicty];_top 0;_capacity capaicty;}~Stack(){delete[] _a;_capacity _top 0;}private:T* _a;size_t _top;size_t _capacity;
};要注意的是 类模板实例化与函数模板实例化有些不同类模板实例化只能显式实例化即需要在类模板名字后跟然后将实例化的类型放在中即可。 类模板不是真正的类其实例化的结果才是真正的类。 因为函数模板实例化可以根据参数类型去推演模板参数的类型但是我们拿一个类去创建对象就比如当前的栈不会直接传数据类型是什么所以要显式实例化 Stack是类名Stack才是类型 这样我们就可以让不同的栈对象里面存不同类型的数据了。 然后还需要注意的是 如果类模板里的成员函数声明和定义分离的话 正常我们是这样写的但是在类模板里这样不行。 注意类模板中成员函数放在类外进行定义时需要加模板参数列表 这样就可以了。 其次 我们定义一个类可能习惯头文件和源文件分开来那普通类这样搞是没问题的就像我们之前实现的日期类就是多文件管理的。 但是呢类模板不行类模板如果这样搞会链接错误的至于原因呢我们后面到模板进阶的时候会讲大家先了解一下。 那这篇文章就先到这里欢迎大家指正