一篇文章带你走进类模板的世界
!!!
前言
上一篇文章的链接:https://blog.csdn.net/hujiahangdewa/article/details/144630185
有了上一篇文章的铺垫,我们再来看看类模板。其实就是要看template 这段代码的后面跟的是什么,如果跟的是函数的定义,那么它就是一个函数模板,如果跟的是类,那么它就是一个类模板。
当然,类模板也有一些细节,需要我们注意。
一、如何定义类模板?
- 要定义一个类模板,你可以使用template关键字和一个或多个模板参数来定义一个通用的类。类模板的定义如示例:
#include <iostream>
#include <string>
using namespace std;
//如何定义一个类模板
template <typename T1,typename T2>//定义了两个模板参数T1和T2
class Student{
public:
Student(string name,T1 age,T2 score)//类的构造方法
{
this->name = name;
this->age = age;
this->score = score;
}
void showMe()
{
cout<<this->name<<" "<<this->age<<" "<<this->score<<endl;
}
~Student(){}//析构函数
private:
string name;
T1 age;
T2 score;
};
int main()
{
Student<int,int> obj("张三",18,100);//类的实例化
obj.showMe();
return 0;
}
在以上例子中,类的实例化Student<T1,T2> obj(“张三”,10,100);T1和T2是参数类型,可以根据需求来更改。
二、类模板和函数模板的区别
- 类模板的类型参数可以有默认值
template <typename T1,typename T2>
template <typename T1=int,typename T2> //会报错,因为如果给了一个参数默认值,其后的都必须给上默认值
template <typename T1,typename T2=double>//正确,参数T2后面没有其他的参数,所以是可以通过编译的
- 类模板没有自动推导的使用方式
我们在实例化类的时候,必须要指出我们定义类模板时的参数列表的实际参数类型,否则将会报错。
Student<int,int> obj("张三",18,100);//正确,成功编译
obj.showMe();
Student ock("张三",18,100);//错误,类模板没有自动推导参数类型的功能
三、类模板成员函数的实例化
- 延迟实例化
在函数被调用时才被实例化
我们用例子来说明,如下:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class MyClass{
public:
void AAA()
{
T obj_one;
obj_one.showme();//showme()这个函数并没有定义
}
};
int main()
{
MyClass<string > obj;
obj.AAA();
return 0;
}
在以上这个例子中,写完这一整段代码,不运行程序,它不会提示报错。但我们在实例化MyClass这个类,运行程序时会报错,因为找不到showme这个方法。
那我们新写一个类,然后在这个类模板中定义一个showme这个方法,再将这个类传给MyClass会怎么样呢?
class Me{
public:
void showme()
{
cout<<"I am a template class"<<endl;
}
};
int main()
{
MyClass<Me> obj;//将Me类当成参数传给MyClass
obj.AAA();//输出的结果就是类Me中的showme方法
return 0;
}
- 模板类外成员函数的定义
在定义类模板的成员函数时,如果你想要将函数的实现放在类的外部,你需要在函数定义之前加上模板参数的声明。具体来说,你需要在函数名称前面加上模板参数列表,并在函数名后面加上尖括号和模板参数名。然后,在编写函数的实现。
以下是代码示例:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Me{
public:
void showme(T info);//函数的声明
};
//以下是函数的实现
template <typename T>
void Me<T>::showme(T info) {
cout<<info<<endl;
}
- 类模板对象作为参数
在C++模板编程中,类模板对象作为参数是值将一个类模板的实例(对象)传递给函数或者其他类模板的构造函数、成员函数等。这种做法可以让我们在编写通用的代码时可以灵活处理不同类型的数据。
一般有三种解决方法:
例子如下:
#include <iostream>
#include <string>
using namespace std;
//定义一个模板类,模板参数T1,T2
template <typename T1,typename T2>
class student{
public:
student(T1 name,T2 score)
{
this->name = name;
this->score = score;
}
void showme()
{
cout<<"我是:"<<name<<"分数是:"<<score<<endl;
}
private:
T1 name;
T2 score;
};
//定义一个函数
//如果我的不是类模板,而是一个普通的类,这里不会报错
void func1(student &p)//会报错,因为没有给出参数列表
{
p.showme();
}
int main()
{
student<string,int> obj("张三",99);
func1(obj);
return 0;
}
对于以上的报错,我们有三种解决的方法。
//第一种方法,给出student的参数列表,确定具体的类型传入
void func1(student<string,int> &p)
{
p.showme();
}
//方法2:参数模板化
template <typename T1,typename T2>
void func2(student<T1,T2> &p)
{
p.showme();
}
//方法3:将整个类模板化
template <typename T>
void fun3(T &p)//理论上来说可以忽略参数类型,但是不这么写会报错
{
p.showme();//确保实例化p的类中必须要包含showme()这个函数,否则将会报错
}
四、类模板与继承
当谈及类模板和继承时,可以有几种不同的情况和用法
- 基类是类模板
如果基类是类模板,而派生类不是,派生类可以通过特定类型的继承来实现。派生类可以使用基类模板的特定实例化。
//父类是模板
template <typename T>
class Base{
public:
T var;
};
//子类继承父类,父类是模板类,子类是普通类
class Myclass:public Base{//出现了报错,如果父类是一个普通的类,这样写是不会报错的
};
//解决方法就是确定父类的泛型类型
//1.确定基类的类型
class Myclass:public Base<int>{//正确,编译通过
};
//当父类是模板类,子类也是模板类时
template <typename T,typename U>//T是给基类的类型,U是给子类的类型
class Myclass1:public Base<T>{
U age;
};
五、友元函数和模板类
分两种情况:在类内部实现的全局友元函数和在类外部实现的全局友元函数
- 在类内部实现的全局友元函数
#include <iostream>
#include <string>
using namespace std;
template <typename T1,typename T2>
class student{
//在这里定义一个友元函数,也就是全局函数,友元函数的内部实现
//如果去掉了friend,将会报错
//类内部实现
friend void prints(student<T1,T2>&p)
{
cout<<p.name<<" "<<p.score<<endl;//可以直接访问私有变量
p.showme(); //可以访问共有函数
}
public:
student(T1 name,T2 score)
{
this->name=name;
this->score=score;
}
void showme()
{
cout<<"我是"<<this->name<<"分数是:"<<this->score<<endl;
}
private:
T1 name;
T2 score;
};
int main()
{
student<string,int> s("张三",100);//实例化对象
prints(s);//调用全局函数,prints不属于类里面的方法,所以可以直接调用
return 0;
}
- 在类外实现的全局友元函数
#include <iostream>
#include <string>
using namespace std;
//提前让编译器知道类的存在
template <typename T1,typename T2>
class Student;
//提前让编译器知道这个全局函数的存在
template <typename T1,typename T2>
void Prints(Student<T1,T2> &p);
//定义一个模板类,含有两个自定义参数类型
template <typename T1,typename T2>
class Student{
//类内声明,类外实现
//必须加一个空模板参数,作用是告诉编译器这是一个模板方法
friend void Prints<>(Student<T1,T2> &p);
public:
Student(T1 name,T2 score)
{
this->name = name;
this->score = score;
}
void showme()
{
cout<<"我是"<<this->name<<",分数是"<<this->score<<endl;
}
private:
T1 name;
T2 score;
};
//类外的定义
template <typename T1,typename T2>
void Prints(Student<T1,T2> &p){
cout<<"我是"<<p.name<<",分数是"<<p.score<<endl;//直接访问私有变量
p.showme();//直接访问共有函数
}
int main()
{
Student<string,int> stu("张三",100);//类的实例化对象
Prints<>(stu);//调用全局函数,(在类内部实现)
return 0;
}
大多数情况下,我是推荐使用第一种写法,第二种写法会有点繁琐,有时还可能会忘记提前写出声明,导致发生错误。
六、类模板分文件编写
我们可以直接引用.cpp文件,也可以创建.hpp
.hpp文件时模板类定义的过程
残生hpp的根本原因:cpp的单独编译产生obj,链接阶段会出现错误。
所以才使用.hpp文件
我们用例子来解释:
当我们将所有的东西放在一个文件中时,是不会报错的,如下
#include <iostream>
#include <string>
using namespace std;
//模板类的定义
template <typename T1,typename T2>
class Student{
public:
Student(T1 name,T2 score);
void showme();
private:
T1 name;
T2 score;
};
//类成员的外部实现
template <typename T1,typename T2>
Student<T1,T2>::Student(T1 name, T2 score) {
this->name = name;
this->score = score;
}
template <typename T1,typename T2>
void Student<T1,T2>::showme() {
cout << "我是:" << this->name << " 分数是:" << this->score << endl;
}
int main()
{
Student<string,int> stu("小明",100);
stu.showme();
return 0;
}
接下来,我们使用普通的.h文件来声明这个类的定义
student.h
#pragma once
//模板类的定义
template <typename T1,typename T2>
class Student{
public:
Student(T1 name,T2 score);
void showme();
private:
T1 name;
T2 score;
};
student.cpp
#include "student.h"
//类成员的实现
template <typename T1,typename T2>
Student<T1,T2>::Student(T1 name, T2 score) {
this->name = name;
this->score = score;
}
template <typename T1,typename T2>
void Student<T1,T2>::showme() {
cout << "我是:" << this->name << " 分数是:" << this->score << endl;
}
main.cpp
#include <iostream>
#include <string>
#include "student.h"
using namespace std;
int main()
{
Student<string,int> stu("小明",100);
stu.showme();
return 0;
}
当我们运行main.cpp时,编译器会报错,因为链接时出现了问题。主要原因是因为我们的类模板有一个特点就是延迟实例化,只有被调用时才会被实例化,所以,当编译器在编译文件是,他没有检测到对函数的实例化,所以就没没有生成对应的.obj文件,当然最后的.exe文件中也不会出现链接这些文件。所以才出现了报错。(这是我的理解,有错误的地方,请在评论区指出)
解决方法就是将原来的student.h变更为student.hpp,然后在student.hpp中写入类的定义,以及方法的实现,再将main.cpp中的头文件重新编写即可解决这个错误。
如下:
student.hpp
#pragma once
#include "student.hpp"
#include <iostream>
//模板类的定义
template <typename T1,typename T2>
class Student{
public:
Student(T1 name,T2 score);
void showme();
private:
T1 name;
T2 score;
};
//类成员的实现
template <typename T1,typename T2>
Student<T1,T2>::Student(T1 name, T2 score) {
this->name = name;
this->score = score;
}
template <typename T1,typename T2>
void Student<T1,T2>::showme() {
std::cout << "我是:" << this->name << " 分数是:" << this->score << std::endl;
}
总结
文章全面地介绍了 C++ 类模板相关知识,涵盖类模板定义、与函数模板的区别、成员函数实例化(包括延迟实例化、类外定义、对象作为参数)、与继承的关系、友元函数的结合以及分文件编写等方面,通过丰富的代码示例详细解释了各类特性和应用场景,为 C++ 开发者深入理解和运用类模板提供了系统的指导,有助于提升代码的复用性和灵活性,在实际编程中能更好地利用类模板解决各类问题。
标签:name,--,C++,showme,score,Student,模板,template From: https://blog.csdn.net/hujiahangdewa/article/details/144645761