目录
🤔前提:
📖此处我们讲的是用户自定义模板,但是在c++实际应用中,我们实际上应用更多的是C++的STL模板库来进行实际操作,但学习自主设计模板也是关键的。
🤔模板:
📖C++中的模板是一种通用的编程工具,允许通过定义通用的函数或类来编写通用的代码。模板可以使程序员编写出与类型无关的代码,从而使程序更具可重用性和灵活性。
📖在C++中,有两种类型的模板:函数模板和类模板。函数模板可以定义一个通用的函数,使它适用于多种不同的数据类型。类模板可以定义一个通用的类,使它适用于多种不同的对象类型。在使用模板时,需要使用模板参数来指定类型,然后编译器会根据类型实例化模板,生成具体的代码来处理相应的数据类型。
🤔函数模板语法:
template <typename T>
函数返回类型 函数名 (参数)
//此处的参数类型就可以用T代替
{
函数内容
}
📖我们来解释上述代码:
📖1. template :声明创建模板,告知编译器下面的函数是模板函数。
📖2. typename:数据类型,可以用class代替,二者作用一样。
📖3. T : 通用数据类型,名称可以替换,通常为大写字母。
🤔实例:
#include<iostream>
using namespace std;
//创建模板,通用变量名字叫做T;
template <typename T>
void Swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 20;
int b = 10;
//自动类型推导
Swap(a, b);
cout << a <<" "<<b<<endl;
char c='q';
char d='p';
//显示指定类型:
Swap<char>(c, d);
cout << c << " " << d;
}
📖 建立了一个Swap模板函数来完成将两个变量值交换的功能,可以看到在Swap函数的参数列表里,变量a和变量b的类型都是 T,也就是不限制交换类型。而使用这个函数的时候我们可以发现我们使用了两种方法:
📖1.自动类型推导:不主动告知编译器Swap传入的参数是什么类型,让编译器自己识别。
📖2.显示指定类型:在函数Swap使用的时候在< >里自己填写上传入的参数类型。
📖二者并无差异,可根据需求自主选择。
🤔模板函数特点:
📖 1. 模板必须要确定出T的数据类型,才可以正常使用。
📖 2.模板参数也可以发生重载。
🤔普通函数与函数模板区别:
📖1.普通函数调用的时候可以发生自动类型转换。
📖2.函数模板调用的时候如果可以利用自动类型推导,就不会发生隐式类型转换。
📖3.如果利用显示指定类型的方式,可以发生隐式类型转换。
🤔模板函数与普通函数的调用规则:
📖1.如果函数模板和普通函数都可以实现,优先使用普通函数。
📖2.可以通过空模板参数列表来强制调用函数模板。
📖3.函数模板也可以发生重载。
📖4.如果函数模板可以产生更好的匹配,优先调用函数模板。
🤔验证代码如下:
#include<iostream>
using namespace std;
void myprintf(int a, int b)
{
cout << "调用的普通函数"<<endl;
}
template <typename T>
void myprintf(T a,T b)
{
cout << "调用的双参数函数模板"<<endl;
}
template <typename T>
void myprintf(T a)
{
cout << "调用的单参数函数模板" << endl;
}
int main()
{
int a = 10;
int b = 20;
myprintf(a, b);
//第一次出现“调用的普通函数”,因为如果普通函数与模板函数重名,优先调用普通函数;
//即使我们把普通函数的定义注释掉,只留下声明,此时也不会调用模板函数
//因为有普通函数的声明,编译器是默认普通函数存在的,此时优先调用普通函数,编译器内部却找不到,就会报错!
//空模板参数列表可以强制调用模板函数
myprintf<>(a, b);
//模板函数可以发生重载
myprintf<>(a);
}
🤔以下为运行结果:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
🤔类模板语法:
template <typename T,typename B>
class 类名
{
T a;
B c;
}
📖由此我们可以看出类模板建立方法跟函数模板的建立方法有很多相似的地方,此时的T和B可以作为通用变量类型,也就是说:模板的通用类型可以有多个!
🤔向类模板传入参数方式:
📖1.指定传入的类型-----直接显示对象的数据类型
📖2.参数模板化 -----将对象中的参数变为模板进行传递
📖3.整个类模板化 -----将这个对象类型模板化进行传递
🤔实例:
#include<iostream>
using namespace std;
template<class T1,class T2>
class person
{
public:
person(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
void showperson()
{
cout << "姓名:" << this->name << " 年龄:" << this->age<<endl;
}
T1 name;
T2 age;
};
//1.指定传入类型
void printfperson01(person<string, int>&p)
{
p.showperson();
}
void test01()
{
person<string, int>p("孙孔和",100);
printfperson01(p);
}
//2.将参数模板化
template<class T1, class T2>
void printfperson02(person<T1, T2>&p)
{
p.showperson();
//cout << "T1的类型为:" << typeid(T1).name;
//通过这种操作就可以看到t到底被实例化为什么变量
}
void test02()
{
person<string, int>p("孙悟空", 999);
printfperson02(p);
}
//3.整个类模板化
template<class T>
void printfperson03(T &p)
{
p.showperson();
}
void test03()
{
person<string, int>p("悟空", 999);
printfperson03(p);
}
int main()
{
//test01();
//test02();
//test03();
}
🤔以下为运行结果:
🤔类模板的特点:
📖1.类模板没有自动推导类型
📖2.类模板在模板参数列表中可以有默认参数
📖3.类模板中的成员函数在调用的时候才创建
🤔类模板的继承问题:
📖1.当子类继承的父类是一个类模板的时候,子类在声明的时候,要指定出父类中T的类型
📖2.如果不指定,编译器无法给子类分配内存空间
📖3.想灵活指出父类中T的类型,子类也需要变为类模板
#include<iostream>
using namespace std;
template <class T>
//父类:
class base
{
T m;
};
//子类:
//class son public:base//这样写是会直接报错的,因为c++编译的时候需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son :public base<int >//必须给父类指定一个类型
{
int son;
};
//也可以通过将子类再次模板化来灵活的指出父类中T的类型
template <class T,class B>
class Son :public base<T >//必须给父类指定一个类型
{
B son;
};
🤔类模板的友元问题:
📖1.全局函数类内实现,直接声明友元就可以
📖2.全局函数类外实现,需要提前让编译器知道全局函数的存在
#include<iostream>
using namespace std;
//提前让编译器知道这个类的存在
template<class T1, class T2>
class person;
//提前让编译器知道这个函数的存在,因为这个函数调用到person类了,而person类在下面,因此我们还需要让编译器提前知道person类的存在。
template<class T1, class T2>
void printfperson1(person<T1, T2>p)
{
cout << "姓名:" << p.name << " 年龄:" << p.age;
}
template<class T1,class T2>
class person
{
//友元全局函数类内实现
friend void printfperson(person <T1,T2>p)
{
cout << "姓名:" << p.name << " 年龄:" << p.age;
}
//友元全局函数类外实现
//加空函数的参数列表:
//如果全局函数是 类外实现 ,那麽我们需要让编译器提前知道这个函数的存在
friend void printfperson1<>(person <T1, T2>p);
public:
person(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
private:
T1 name;
T2 age;
};
void test01()
{
person<string, int>p1("孙悟空", 99);
printfperson(p1);
}
int main()
{
test01();
}