C++函数模板与类模板
lhb2998658795 人气:01.模板
1.1何为模板
即模子,生成器。
1.2C++的模板的形式有两种
函数模板与类模板。
1.3如何定义一个函数模板
就像定义函数一样,定义一个函数模板,把函数中类型抽象出来,
同时告诉编译器下面的函数是一个函数模子或函数生成器。
1.4语法形式
template + <class T1, class T2 = double, class T3 = int ....> //模板头中的类型参数列表也可以有默认值。 T add(T a, T b) { return a + b; }
1.5模板的编译机制
1.编译器并不是把模板处理成能够处理任何类型的函数,而是一个函数或类的生成器。
2.函数模板通过具体类型产生不同的函数(产生了模板函数)
3.编译器会对函数模板进行两次编译,第一次在声明的地方对模板本身进行编译,在调用的地方对参数替换后的代码进行编译使用函数模板与真正的函数,谁的调用效率高呢?当然是真正的函数,因为函数模板还需要编译器进行翻译一遍才能调用。所以现开发中并不一定要把所有函数你都要定义成函数模板,所以类型替换之后的函数模板就成了一个函数实例了,这样才能调用。
代码实例:
#include <iostream> using namespace std; template <class T> T my_add(T a, T b) { return a + b; } int main() { my_add<int>(10,20); my_add<float>(3.14f,5.21f); my_add<double>(5.21,3.14); return 0; }
2.函数模板
2.1调用方式
显式调用:函数名后使用<>尖括号指定具体参数调用。
using namespace std; template <class T> T my_add(T a, T b) { return a + b; } int main() { my_add<int>(10,20); my_add<float>(3.14f,5.21f); my_add<double>(5.21,3.14); return 0; }
隐式调用:由编译器自动根据参数推导参数类型再调用。
#include <iostream> using namespace std; template <class T> T my_add(T a, T b) { return a + b; } int main() { my_add(10,20); my_add(3.14f,5.21f); my_add(5.21,3.14); return 0; }
2.2函数模板的特化与调用优先级
当只有基础模板和特化模板时。
#include <iostream> using namespace std; template <class T> T my_add(T a,T b) { cout<<"这是一个基础模板"<<endl; return a+b; } template <class T> T my_add(int a,int b) { cout<<"这是一个特化模板"<<endl; return a+b; } int main() { cout<<my_add(10,20)<<endl; return 0; }
结果图:
当只有基础模板和特化模板时,隐式调用基础模板。
当有基础模板和特化模板,还有实例时候。
#include <iostream> using namespace std; template <class T> T my_add(T a,T b) { cout<<"这是一个基础模板"<<endl; return a+b; } template <class T> T my_add(int a,int b) { cout<<"这是一个特化模板"<<endl; return a+b; } int my_add(int a,int b) { cout<<"这是一个实例"<<endl; return a+b; } int main() { cout<<my_add(10,20)<<endl; return 0; }
结果图:
当有基础模板和特化模板,还有实例时候,隐式调用用实例。
当有基础模板和特化模板,还有实例时候,显示调用
#include <iostream> using namespace std; template <class T> T my_add(T a,T b) { cout<<"这是一个基础模板"<<endl; return a+b; } template <class T> T my_add(int a,int b) { cout<<"这是一个特化模板"<<endl; return a+b; } int my_add(int a,int b) { cout<<"这是一个实例"<<endl; return a+b; } int main() { cout<<my_add<int>(10,20)<<endl; return 0; }
结果图:
当有基础模板和特化模板,还有实例时候,显示调用用特化模板。
总结:
当有函数实例时:隐式调用将直接调用函数实例。
如果没有函数实例时,隐式调用将直接调用函数模板的基础模板。
如果使用显示调用,当优先调用特化的与类型匹配的函数模板。
3.可变参函数模板
3.1概念
所谓的可变参模板是指类型参数为一一个可变是类型,这个类型使用class...来修饰。
3.2代码实现(实现一个c中的printf的函数)
#include <iostream> using namespace std; void printf() { } template <class Firstarg,class... Arg> void printf(Firstarg firstarg, Arg... arg) { cout<<firstarg; printf(arg...); } int main() { printf("lisi","cc"); return 0; }
结果图:
4.类模板
4.1类模板的定义形式
注意:在使用类模板时,不存在编译推导类型,必须手动指定具体类型。
template <class T1, class T2, class T3 ...> //class修饰符也可使用typename来修饰。 class + 类名 { //类模板的模板体。 private: //类模板中的属性。 public: //类中的方法 protected: };
4.2代码实例
#include <iostream> using namespace std; template <class T1,class T2> class A { T1 name; T2 age; public: A(T1 name,T2 age) { this->age=age; this->name=name; } void show_info() { cout<<"name="<<name<<",age="<<age<<endl; } }; int main() { // A<string,int> a("lisi",20); // a.show_info(); A<string,int>* a=new A<string,int>("lisi",20); a->show_info(); return 0; }
结果图:
5.类模板中的特殊属性的初始化方式及继承与多态
5.1代码实例
#include <iostream> using namespace std; template <class T1,class T2> class A { T1 name; T2 age; public: A(T1 name,T2 age) { this->name=name; this->age=age; } virtual void show_info() { cout<<"name="<<this->name<<",age"<<age<<endl; } void set_name(T1 name) { this->name=name; } T1 get_name() { return this->name; } void set_age(T2 age) { this->age=age; } T2 get_age() { return this->age; } }; template <class T1,class T2,class T3> class B:public A<T1,T2> { const int id; static int count; public: B(T1 name,T2 age,T3 _id):id(_id),A<T1,T2>(name,age) { } void show_info() { cout<<"id="<<this->id<<",name="<<this->get_name()<<",age"<<this->get_age()<<endl; } }; int main() { //1.栈上 B<string,int,int> b("lisi",20,1001); b.show_info(); //2.堆上 B<string,int,int>* b1=new B<string,int,int>("zhangsan",29,1002); b1->show_info(); //3.实现多态 A<string,int>* a=new B<string,int,int>("wangwu",50,1003); a->show_info(); return 0; }
结果图:
5.2使用类模板去实现一个数据结构
实现一个顺序栈模板
首先我们使用一下多文件编程,类似于c的那种,我们会发现问题如下:
main.cpp文件:
#include <iostream> #include "socket.h" using namespace std; int main() { socket<int> s(2); return 0; }
stack.h文件:
#ifndef SOCKET_H #define SOCKET_H using namespace std; #include <iostream> template <class T> class socket { T* m_date; int len; int max_size; public: //构造 socket(int _len); //析构 ~socket(); //入栈 void push(const socket& other); //出栈 void out(); //获取栈顶的值 T get_out(); //判断是否为空 bool is_empty(); }; #endif // SOCKET_H
stack.cpp文件:
#include "socket.h" template <class T> socket<T>::socket(int _max_size) { this->m_date=new T[len]; this->len=0; this->max_size=_max_size; }
结果图:
结果分析:如图所以,结果告诉我们无法连接到构造函数,这是由于我们使用的是模板类,模板类需要被编译两次,如果我们像这样把stack.cpp和stack.h分开写的话,stack.cpp里面的模板只被编译了一次,所以我们无法连接到构造函数。
使分文件编程的方式实现一个模板栈:
在C++分文件编程时,在业内常用的一种文件标准是后缀为.hpp的模板文件。
代码实现:
stack.cpp文件:
#ifndef STACK_HPP #define STACK_HPP using namespace std; #include <iostream> template <class T> class Stack { T* my_data; int len; int max_size; public: //构造函数 Stack(int _max_size); //析构函数 ~Stack(); //入栈 void push(const int& other); //出栈 void out_data(); //获取栈顶的值 T get_data(); //判断是否为空 bool is_empty(); }; #endif // STACK_HPP template <class T> Stack<T>::Stack(int _max_size) { this->my_data=new int[_max_size]; this->len=0; this->max_size=_max_size; } template <class T> Stack<T>::~Stack() { if(this->my_data!=nullptr){ delete this->my_data; } } template <class T> void Stack<T>::push(const int& other) { if(this->max_size<len){ return; } my_data[len]=other; ++(this->len); } template <class T> void Stack<T>::out_data() { if(len<=0){ return; } --(this->len); } template <class T> T Stack<T>::get_data() { return this->my_data[len-1]; } template <class T> bool Stack<T>::is_empty() { if(this->len==0){ return true; } return false; }
main.cpp文件:
#include <iostream> #include "stack.hpp" using namespace std; int main() { Stack<int> s(12); s.push(1); s.push(2); s.push(3); while(!s.is_empty()){ cout<<s.get_data()<<endl; s.out_data(); } return 0; }
结果图:
分析:因为这次我们把函数的实现放在了hpp文件里面,当我们调用头文件的时候问编译一次,还有就是当调用声明的时候也会编译一次,所以就达到了类模板的使用要求,所以这次我们就可以链接到。
5.3类模板的特化
#include <iostream> using namespace std; template<class T1> class A { public: A() { cout<<"A的基础模板"<<endl; } }; template <> class A <int> { public: A() { cout<<"A的特化模板"<<endl; } }; template <class T2,class T3> class B { public: B() { cout<<"B的基础模板"<<endl; } }; template <class T2> class B<T2,float> { public: B() { cout<<"B的偏化模板"<<endl; } }; int main() { A<float> a; A<int> a1; B<int ,int> b1; B<int ,float> b2; return 0; }
结果图:
分析:当使用类模板去定义对象时,因为具体指定使参数类型,所以他将优先调用与之指定类型相匹配的特化或偏特化版本。否则,将直接调用全特化。
5.4C++中类模板中的内嵌类
内嵌类一般情况下是为外围类而服务:比如说:STL容器中所提供的迭代器就是一种内嵌类。内嵌类并不是对外公开的,只做为外围类的一个辅助类。隐藏在外围类的内部,对外不可以见。只能通过::域名访问的形式,才能访问到。
代码实例:
#include <iostream> using namespace std; template <typename T> class A { public: class B { int a = 100; int b = 200; static B* c; }; }; template <class T> typename A<T>::B* A<T>::B::c = nullptr; int main() { A<int> a; cout << sizeof (a) << endl; A<float>::B b1; cout << sizeof(b1) <<endl; return 0; }
内嵌类需要注意的几点内容:
1.内嵌类可以访问定义在外围类(enclosing class)中的静态实例变量。外围类不可以访问嵌套类的成员.
2.不能从内嵌类中访问外部类的非静态成员.
3.可以在外部通过作用域限定符调用.
加载全部内容