类模板几种常见的情况
类模板的定义我在上一篇博客模板小结中已经介绍过,大家还不懂的可以去看下。
1 #include<iostream> 2 using namespace std; 3 template<class T1,class T2> //模板类 4 class Person 5 { 6 public: 7 Person(T1 age,T2 name) 8 { 9 this->age = age; 10 this->name = name; 11 } 12 void show_infor() //用来访问模板类的私有变量,输出变量信息 13 { 14 cout<<age<<" "<<name<<endl; 15 } 16 private: 17 T1 age; //两个属性,年龄和名字 18 T2 name; 19 }; 20 21 int main(int argc, char const *argv[]) 22 { 23 //Person p(20,"stronger"); //这里实例化对象时,必须告诉编译器模板类的类型 24 Person<int,string> p(20,"stronger"); 25 return 0; 26 }
上面的代码我们创建了一个模板类,有属性和方法,接下来我们写一个全局函数,来访问模板类。
1 #include<iostream> 2 using namespace std; 3 template<class T1,class T2> //模板类 4 class Person 5 { 6 public: 7 Person(T1 age,T2 name) 8 { 9 this->age = age; 10 this->name = name; 11 } 12 void show_infor() //用来访问模板类的私有变量,输出变量信息 13 { 14 cout<<age<<" "<<name<<endl; 15 } 16 private: 17 T1 age; //两个属性,年龄和名字 18 T2 name; 19 }; 20 void show(Person &p) //这样写不告诉编译器类型,编译器无法识别 21 { 22 p.show_infor(); 23 } 24 int main(int argc, char const *argv[]) 25 { 26 //Person p(20,"stronger"); //这里实例化对象时,必须告诉编译器模板类的类型 27 Person<int,string> p(20,"stronger"); 28 show(p); //调用全局函数 29 return 0; 30 }
看第20行,这样写行吗?肯定不行,我们没告诉编译器模板类的类型,编译器无法识别,所以我们得按照下面的格式写。
1 #include<iostream> 2 using namespace std; 3 template<class T1,class T2> //模板类 4 class Person 5 { 6 public: 7 Person(T1 age,T2 name) 8 { 9 this->age = age; 10 this->name = name; 11 } 12 void show_infor() //用来访问模板类的私有变量,输出变量信息 13 { 14 cout<<age<<" "<<name<<endl; 15 } 16 private: 17 T1 age; //两个属性,年龄和名字 18 T2 name; 19 }; 20 /*void show(Person &p) //这样写不告诉编译器类型,编译器无法识别 21 { 22 p.show_infor(); 23 }*/ 24 void show(Person<int,string> &p) //告诉编译器类型 25 { 26 p.show_infor(); 27 } 28 int main(int argc, char const *argv[]) 29 { 30 //Person p(20,"stronger"); //这里实例化对象时,必须告诉编译器模板类的类型 31 Person<int,string> p(20,"stronger"); 32 show(p); //调用全局函数 33 return 0; 34 }
运行结果:
看运行结果,我们输出了对象的信息,但这样的全局函数是我们要的吗?能满足其他情况吗?很明显不可以,加入我们再实例化一个两个属性都是int型的怎么办呢?所以这个全局函数,我们还得想办法重写,很简单,既然我们也不确定这个对象的两个属性是什么类型,那我们就也写成函数模板不就可以了。
1 #include<iostream> 2 using namespace std; 3 template<class T1,class T2> //模板类 4 class Person 5 { 6 public: 7 Person(T1 age,T2 name) 8 { 9 this->age = age; 10 this->name = name; 11 } 12 void show_infor() //用来访问模板类的私有变量,输出变量信息 13 { 14 cout<<age<<" "<<name<<endl; 15 } 16 private: 17 T1 age; //两个属性,年龄和名字 18 T2 name; 19 }; 20 /*void show(Person &p) //这样写不告诉编译器类型,编译器无法识别 21 { 22 p.show_infor(); 23 }*/ 24 /*void show(Person<int,string> &p) //告诉编译器类型 25 { 26 p.show_infor(); 27 }*/ 28 template<class T1,class T2> 29 void show(Person<T1,T2> &p) 30 { 31 p.show_infor(); 32 } 33 int main(int argc, char const *argv[]) 34 { 35 //Person p(20,"stronger"); //这里实例化对象时,必须告诉编译器模板类的类型 36 Person<int,string> p(20,"stronger"); 37 show(p); //调用全局函数 38 Person<int,int> p2(20,100); //这里实例化出两个对象都为int型的 39 show(p2); 40 return 0; 41 }
运行结果:
看运行结果,这样写全局函数就可以实现所有类型的对象的访问。这种用函数模板去写的方法还有一种,代码如下:
1 #include<iostream> 2 using namespace std; 3 template<class T1,class T2> //模板类 4 class Person 5 { 6 public: 7 Person(T1 age,T2 name) 8 { 9 this->age = age; 10 this->name = name; 11 } 12 void show_infor() //用来访问模板类的私有变量,输出变量信息 13 { 14 cout<<age<<" "<<name<<endl; 15 } 16 private: 17 T1 age; //两个属性,年龄和名字 18 T2 name; 19 }; 20 /*void show(Person &p) //这样写不告诉编译器类型,编译器无法识别 21 { 22 p.show_infor(); 23 }*/ 24 /*void show(Person<int,string> &p) //告诉编译器类型 25 { 26 p.show_infor(); 27 }*/ 28 /*template<class T1,class T2> 29 void show(Person<T1,T2> &p) 30 { 31 p.show_infor(); 32 }*/ 33 template<class T> 34 void show(T &p) 35 { 36 p.show_infor(); 37 } 38 int main(int argc, char const *argv[]) 39 { 40 //Person p(20,"stronger"); //这里实例化对象时,必须告诉编译器模板类的类型 41 Person<int,string> p(20,"stronger"); 42 show(p); //调用全局函数 43 Person<int,int> p2(20,100); //这里实例化出两个对象都为int型的 44 show(p2); 45 return 0; 46 }
运行结果:
这样写也可以,但只是可能传入的对象是别的就不行了,推荐大家使用第一种函数模板的方法。
2.类模板遇到继承
这里顺带讲一下在继承上一些常见的情况:
构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。
如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。
构造原则如下:
1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。
4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式。
情况1:如果子类没有定义构造方法,则调用父类的无参数的构造方法。
1 #include<iostream> 2 using namespace std; 3 template<class T1> //模板类,也是基类 4 class Base 5 { 6 public: 7 T1 a1; 8 }; 9 class Son1:public Base<int> //注意这里也是必须指定类型,否则会报错 10 { 11 public: 12 void set_infor(int x,int a) 13 { 14 x1 = x; 15 a1 = a; 16 } 17 void show_infor() 18 { 19 cout<<"派生类:"<<x1<<" 基类:"<<a1<<endl; 20 } 21 private: 22 int x1; 23 }; 24 int main(int argc, char const *argv[]) 25 { 26 Son1 s1; 27 s1.set_infor(12,24); 28 s1.show_infor(); 29 }
运行结果:
情况2:如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
1 #include<iostream> 2 using namespace std; 3 template<class T1> //模板类,也是基类 4 class Base 5 { 6 public: 7 T1 a1; 8 }; 9 class Son1:public Base<int> //注意这里也是必须指定类型,否则会报错 10 { 11 public: 12 Son1() 13 { 14 cout<<"派生类无参构造"<<endl; 15 } 16 Son1(int x,int a) 17 { 18 x1 = x; 19 a1 = a; 20 cout<<"派生类有参构造"<<endl; 21 } 22 void set_infor(int x,int a) 23 { 24 x1 = x; 25 a1 = a; 26 } 27 void show_infor() 28 { 29 cout<<"派生类:"<<x1<<" 基类:"<<a1<<endl; 30 } 31 private: 32 int x1; 33 }; 34 int main(int argc, char const *argv[]) 35 { 36 Son1 s1; 37 s1.set_infor(12,24); 38 s1.show_infor(); 39 Son1 s2(11,22); 40 s2.show_infor(); 41 }
运行结果:
情况3:如果子类没有定义构造方法,则调用父类的无参数的构造方法。
1 #include<iostream> 2 using namespace std; 3 template<class T1> //模板类,也是基类 4 class Base 5 { 6 public: 7 T1 a1; 8 }; 9 class Son1:public Base<int> //注意这里也是必须指定类型,否则会报错 10 { 11 public: 12 void set_infor(int x,int a) 13 { 14 x1 = x; 15 a1 = a; 16 } 17 void show_infor() 18 { 19 cout<<"派生类:"<<x1<<" 基类:"<<a1<<endl; 20 } 21 private: 22 int x1; 23 }; 24 int main(int argc, char const *argv[]) 25 { 26 Son1 s1; 27 s1.set_infor(12,24); 28 s1.show_infor(); 29 }
运行结果:
情况4:在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
1 #include<iostream> 2 using namespace std; 3 template<class T1> //模板类,也是基类 4 class Base 5 { 6 public: 7 Base() 8 { 9 cout<<"基类无参构造"<<endl; 10 } 11 T1 a1; 12 }; 13 class Son1:public Base<int> //注意这里也是必须指定类型,否则会报错 14 { 15 public: 16 Son1() 17 { 18 cout<<"派生类无参构造"<<endl; 19 } 20 Son1(int x,int a) 21 { 22 x1 = x; 23 a1 = a; 24 } 25 void set_infor(int x,int a) 26 { 27 x1 = x; 28 a1 = a; 29 cout<<"派生类有参构造"<<endl; 30 } 31 void show_infor() 32 { 33 cout<<"派生类:"<<x1<<" 基类:"<<a1<<endl; 34 } 35 private: 36 int x1; 37 }; 38 int main(int argc, char const *argv[]) 39 { 40 Son1 s1; 41 s1.set_infor(12,24); 42 s1.show_infor(); 43 Son1 s2(11,22); 44 s2.show_infor(); 45 }
运行结果:
情况5和情况6:在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。需要用初始化父类成员对象的方式。
1 #include<iostream> 2 using namespace std; 3 template<class T1> //模板类,也是基类 4 class Base 5 { 6 public: 7 Base() 8 { 9 cout<<"无参构造"<<endl; 10 } 11 Base(T1 a) 12 { 13 a1 = a; 14 cout<<"基类有参构造"<<endl; 15 } 16 T1 a1; 17 }; 18 class Son1:public Base<int> //注意这里也是必须指定类型,否则会报错 19 { 20 public: 21 Son1() 22 { 23 cout<<"派生类无参构造"<<endl; 24 } 25 /*Son1(int x,int a) 26 { 27 x1 = x; 28 a1 = a; 29 }*/ 30 Son1(int x,int a):Base(a),x1(x) //显式调用基类的无参构造 31 { 32 cout<<"派生类有参构造"<<endl; 33 } 34 void set_infor(int x,int a) 35 { 36 x1 = x; 37 a1 = a; 38 cout<<"派生类有参构造"<<endl; 39 } 40 void show_infor() 41 { 42 cout<<"派生类:"<<x1<<" 基类:"<<a1<<endl; 43 } 44 private: 45 int x1; 46 }; 47 int main(int argc, char const *argv[]) 48 { 49 Son1 s2(11,22); 50 s2.show_infor(); 51 }
运行结果:
3.类模板成员函数类外实现
其实还有类内实现,但其实我们前面所讲的都是类模板的成员函数类内实现。如果在类外实现的话,我们也要用函数模板的形式来实现。
1 #include<iostream> 2 using namespace std; 3 template<class T1,class T2> 4 class Person 5 { 6 public: 7 Person(T1 age,T2 name); 8 void show(); 9 void speak(); 10 private: 11 T1 age; 12 T2 name; 13 }; 14 template<class T1,class T2> 15 Person<T1,T2>::Person(T1 age,T2 name) 16 { 17 this->age = age; 18 this->name = name; 19 } 20 template<class T1,class T2> 21 void Person<T1,T2>::show() 22 { 23 cout<<age<<" "<<name<<endl; 24 } 25 template<class T1,class T2> 26 void Person<T1,T2>::speak() 27 { 28 cout<<name<<"在说话"<<endl; 29 } 30 int main(int argc, char const *argv[]) 31 { 32 Person<int,string> p1(12,"stronger"); 33 p1.show(); 34 p1.speak(); 35 }
运行结果:
4.类模板成员函数的创建时机
假如说有下面这样一段程序:
1 #include<iostream> 2 using namespace std; 3 class A 4 { 5 public: 6 void showA() 7 { 8 cout<<"A"<<endl; 9 } 10 }; 11 class B 12 { 13 public: 14 void showB() 15 { 16 cout<<"B"<<endl; 17 } 18 }; 19 template<class T> 20 class C 21 { 22 public: 23 T obj; 24 void foo1() 25 { 26 obj.showA(); 27 } 28 void foo2() 29 { 30 obj.showB(); 31 } 32 }; 33 int main(int argc, char const *argv[]) 34 { 35 C<A> p1; 36 C<B> p2; 37 }
大家看C类里面,起始两个成员函数分别使用了A类和B类的成员函数,这要是在普通函数里面肯定报错了,因为如果C类的对象的类型是A类的,那么它一定无法调用B类的,那为什么这里编译能通过呢?那是因为类模板成员函数的创建时机是在调用阶段。也就是说如果在上面的程序中,用p1去使用foo2();就会出错。
5.类模板遇上友元
1 #include<iostream> 2 using namespace std; 3 template<class T> //先声明类模版 4 class Myclass; 5 template<class T> 6 void visit(Myclass<T> &obj) 7 { 8 cout<<obj.data<<endl; 9 } 10 template<class T> 11 class Myclass 12 { 13 friend void visit<>(Myclass<T> &obj); 14 public: 15 Myclass(T data) 16 { 17 this->data = data; 18 } 19 private: 20 T data; 21 }; 22 int main(int argc, char const *argv[]) 23 { 24 Myclass<int> p(12); 25 visit(p); 26 }
当友元函数在类模板之后实现的话,一定要提前声明。
1 #include<iostream> 2 using namespace std; 3 template<class T> 4 class Myclass; 5 template<class T> 6 void visit(Myclass<T> &obj); 7 template<class T> 8 class Myclass 9 { 10 friend void visit<>(Myclass<T> &obj); 11 public: 12 Myclass(T data) 13 { 14 this->data = data; 15 } 16 private: 17 T data; 18 }; 19 template<class T> 20 void visit(Myclass<T> &obj) 21 { 22 cout<<obj.data<<endl; 23 } 24 int main(int argc, char const *argv[]) 25 { 26 Myclass<int> p(12); 27 visit(p); 28 Myclass<char> p1('A'); 29 visit(p1); 30 }
或者我们可以在类内声明友元时,直接将函数体也写在类内,这样也可以。
1 #include<iostream> 2 using namespace std; 3 template<class T> 4 class Myclass 5 { 6 friend void visit(Myclass<T> &obj) 7 { 8 cout<<obj.data<<endl; 9 } 10 public: 11 Myclass(T data) 12 { 13 this->data = data; 14 } 15 private: 16 T data; 17 }; 18 int main(int argc, char const *argv[]) 19 { 20 Myclass<int> p(12); 21 visit(p); 22 }