目录
1. 什么是模板?
2. 函数模板
2.1 函数模板语法
2.2 函数模板的注意事项
2.3 普通函数和模板函数的区别
2.4 普通函数和模板函数的调用规则
2.5 模板的局限性
3. 类模板
3.1 类模板语法
3.2 类模板和函数模板之间的区别
3.3 类模板中成员函数创建时机
3.4 类模板做参数
3.5 类模板和继承
3.6 类模板成员函数在类外实现
3.7 类模板和友元
1. 什么是模板?
模板就是建立通用的模具,提升了代码的复用性
C++中的另一种编程思想就是泛型编程,主要的技术就是模板
C++提供两种模板 -- 函数模板和类模板
模板特点:
- 模板不可以直接使用,它只是一个框架
- 模板的通用性不是万能的
2. 函数模板
2.1 函数模板语法
建立一个通用的函数,其返回类型和参数类型可以不指定,可以通过一个虚拟的类型来替代
语法:
template函数的声明和定义
说明:
template -- 声明创建的模板
typename -- 后面的字符表示为一个数据类型,可以被class替换
T -- 通用数据类型,可以被替换,但一般使用大写字母
举例:
//函数模板 //templatetemplate void Swap(T& left, T& right) { T temp = left; left = right; right = temp; } int main() { int left = 10; int right = 20; cout << "交换前" << "left = " << left << ",right = " << right << endl; Swap(left, right); cout << "交换后" << "left = " << left << ",right = " << right << endl; return 0; }
2.2 函数模板的注意事项
函数模板分为两种推导方式 --- 自动推导和显示指定类型
1. 当推导方式为自动推导时,推导的类型必须是同一个类型才可以使用
template//1. 自动推导类型,T必须推导为同一个类型才可以使用 void Swap(T& a, T& b) { T temp = a; a = b; b = temp; } int main() { int a = 10; char b = 'c'; Swap(a,b); return 0; }
当我们传入两个不同数据类型的值,编译器则分辨不出T应该表示哪种数据类型,因此要避免写出这样的代码
解决方法:必须使用显示指定类型来确定T的数据类型
2. 模板必须确定T的数据类型,否则无法使用
template//2. 模板必须确定T的数据类型,才能够使用 template void func() { cout << "调用func()" << endl; } int main() { func(); return 0; }
如果无法推导T的数据类型,模板则无法使用
解决方法:必须使用显示指定类型来确定T的数据类型
2.3 普通函数和模板函数的区别
1. 普通函数可以进行隐式转换
2. 自动推导类型无法进行隐式转换,只有显示指定类型才可以进行隐式转换
void Swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
template
void Swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
char b = 'c';
Swap(a, b);
return 0;
}
如果是模板函数,如果传入的数据类型不同,则无法判读T的类型,也就无法进行隐式转换
必须先指定了T的数据类型,才可以进行转换
2.4 普通函数和模板函数的调用规则
1.如果函数模板和普通函数都可以实现,则优先调用普通函数
2.可以通过空模板的参数列表来强制调用模板函数
void MyPrint(int a, int b)
{
cout << "调用普通函数" << endl;
}
template
void MyPrint(T a, T b)
{
cout << "调用函数模板" << endl;
}
int main()
{
//调用普通函数
MyPrint(10, 20);
//调用函数模板
MyPrint<>(10, 20);
MyPrint(10,20);
return 0;
}
当不显示指定类型调用时,调用的是普通函数,只有通过空模板或者显示类型才会调用模板函数
3.函数模板可以发生函数重载
4.如果函数模板可以更好的进行匹配,优先调用函数模板
void MyPrint(int a, int b)
{
cout << "调用普通函数" << endl;
}
template
void MyPrint(T a, T b)
{
cout << "调用函数模板" << endl;
}
template
void MyPrint(T a, T b, T c)
{
cout << "调用重载的函数模板" << endl;
}
int main()
{
char c = 'a';
char d = 'b';
char e = 'c';
MyPrint(c,d);
MyPrint(c,d,e);
return 0;
}
当传入的数据类型是char类型时,在普通函数中也可以运行,为什么这里就调用模板函数了呢?
因为模板函数更匹配,只需要替换T为char即可,而普通函数中还需要隐式转换
2.5 模板的局限性
模板的通用性不是万能的
例如:
templatebool Judge(T a, T b) { if (a == b) { return true; } else { return false; } }
如果传入的是两个数组、两个类呢?
模板无法实现功能
因此对于某些特点的数据类型,我们需要用具体化的方式来特殊实现
如果传入的是两个类,我们应该怎么做呢?
class Person
{
public:
Person(string name, int age)
{
_name = name;
_age = age;
}
string _name;
int _age;
};
//利用具体化Person的版本实现代码,具体化优先调用
template<> bool Judge(Person a, Person b)
{
if (a._age == b._age&&a._name==b._name)
{
return true;
}
else
{
return false;
}
}
int main()
{
Person p1("张三", 15);
Person p2("李四", 15);
if (Judge(p1, p2))
{
cout << "p1==p2" << endl;
}
else
{
cout << "p1!=p2" << endl;
}
return 0;
}
这时我们就可以进行实现两个类之间的操作
3. 类模板
3.1 类模板语法
类模板的语法和函数模板的语法类似
template类的实现
说明:
template -- 声明创建的模板
class -- 后面的字符表示为一个数据类型,可以被typename替换
T -- 通用数据类型,可以被替换,但一般使用大写字母
举例:
templateclass Person { public: Person(NameType name, AgeType age) { _name = name; _age = age; } void Show() { cout << "name:" << _name << "nage:" << _age; } NameType _name; AgeType _age; }; int main() { Person p1("张三", 15); p1.Show(); return 0; }
3.2 类模板和函数模板之间的区别
1. 类模板没有自动推导,需要显示指定类型
templateclass Person { public: Person(NameType name, AgeType age) { _name = name; _age = age; } void Show() { cout << "name:" << _name << "nage:" << _age << endl; } NameType _name; AgeType _age; }; //1.类模板没有自动推导方式 void test01() { //Person p1("张三", 15);//无法推导出传入的数据类型,只能显示指定类型 Person p1("张三", 15); p1.Show(); } int main() { test01(); return 0; }
2. 类模板中可以有缺省参数
templateclass Person { public: Person(NameType name, AgeType age) { _name = name; _age = age; } void Show() { cout << "name:" << _name << "nage:" << _age << endl; } NameType _name; AgeType _age; }; //2.类模板中可以有缺省参数 void test02() { Person p2("王五", 15); p2.Show(); } int main() { test02(); return 0; }
其缺省参数也满足在函数上的缺省参数原理,必须从右向左给定缺省参数
3.3 类模板中成员函数创建时机
类模板中的成员函数和普通类中的成员函数有区别的
1.类模板中的成员函数只在调用时才被创建
class Person1
{
public:
void ShowPerson1()
{
cout << "Person1 Show()" << endl;
}
};
class Person2
{
public:
void Show()
{
cout << "Person2 Show()" << endl;
}
};
template
class Myclass
{
public:
T obj;
void func1()
{
obj.ShowPerson1();
}
void func2()
{
obj.ShowPerson2();
}
};
void test01()
{
Myclass m;
m.func1();
m.func2();
}
int main()
{
test01();
}
当指定类型为Person1时,如果不调用func2函数则不会报错,则说明类模板中的成员函数是在调用时被创建
2.普通类中的成员函数在类创建时就被创建
class Person1
{
public:
void ShowPerson1()
{
cout << "Person1 Show()" << endl;
}
};
class Person2
{
public:
void Show()
{
cout << "Person2 Show()" << endl;
}
};
class Myclass
{
public:
Person1 obj;
void func1()
{
obj.ShowPerson1();
}
void func2()
{
obj.ShowPerson2();
}
};
void test01()
{
Myclass m;
}
int main()
{
test01();
}
当没有调用func1和func2函数时,代码已经报错,则说明在普通类创建时,其成员函数就已经被创建了
3.4 类模板做参数
类模板做参数有三种方式:
- 指定传入类型
- 将参数模板化
- 将整个类模板化
举例:
templateclass Person { public: Person(T1 name, T2 age) { _name = name; _age = age; } void ShowPerson() { cout << "姓名:" << _name << "n年龄:" << _age << endl; } T1 _name; T2 _age; }; //1.指定传入类型 //是什么类型就完成的传入 void PrintPerson(Person &p) { p.ShowPerson(); } void test01() { Person p("张三", 18); PrintPerson(p); } //2.将参数模板化 template void PrintPerson2(Person &p) { p.ShowPerson(); cout << "T1的类型:" < p("李四", 18); PrintPerson2(p); } //3.整个类模板化 template void PrintPerson3(T&p) { p.ShowPerson(); cout << "T的类型:" << typeid(T).name() << endl; } void test03() { Person p("王五", 20); PrintPerson3(p); } int main() { test01(); cout << "------------" << endl; test02(); cout << "------------" << endl; test03(); return 0; }
打印出模板所代表的类型:
一般情况下我们如果要将模板类作为函数参数,我们选择第一种方法,因为其余两种将函数模板和类模板结合起来,代码更加复杂。
3.5 类模板和继承
1. 如果我们要继承的父类是一个模板类,我们应该怎么做?
templateclass Base { public: T m; }; //必须要知道父类中T的数据类型,才能够继承父类 class Son :public Base { };
我们必须要指定父类中T的数据类型,才能够继承类,因为如果不指明T的数据类型,那么系统不确定开多少字节的空间
2. 如果我们想让我们的父类T的数据类型灵活变化,而不是指定后就无法改变,应该怎么做?
这时我们就需要将子类也进行模板化
templateclass Base { public: T m; }; //如果想要灵活的指定父类的T类型,需要将子类也模板化 template class Son2:public Base { public: T2 obj; }; void test02() { Son2 s2; //T1是父类类型 T2是子类类型 cout << "父类的类型:" << typeid(s2.m).name() << endl; cout << "子类的类型:" << typeid(s2.obj).name() << endl; } int main() { test02(); return 0; }
这样就可以灵活的改变父类中的数据类型
3.6 类模板成员函数在类外实现
普通的成员函数在类外是如何实现的呢?
class Person
{
public:
Person(String name, int age);
void ShowPerson();
String _name;
int _age;
};
Person(String name, int age)
{
_name = name;
_age = age;
}
void Person::ShowPerson()
{
cout << "姓名:" << _name << endl;
cout << "年龄:" << _age << endl;
}
void Test01()
{
Person s1("张三",25);
s1.ShowPerson();
}
int main()
{
Test01();
return 0;
}
在类外实现,需要加上该类的作用域来表示该函数属于该类
那么模板类的成员函数又是如何在类外实现呢?
templateclass Person { public: Person(T1 name, T2 age); void ShowPerson(); T1 _name; T2 _age; }; //构造函数类外实现 template Person ::Person(T1 name, T2 age) { _name = name; _age = age; } template void Person ::ShowPerson() { cout << "姓名:" << _name << endl; cout << "年龄:" << _age << endl; } void Test01() { Person s1("张三",25); s1.ShowPerson(); } int main() { Test01(); return 0; }
比起普通的类在类外实现,只是在每个类实现前加上了template
3.7 类模板和友元
1. 友元函数在类内实现
templateclass Person { public: friend void Show(Person & d) { cout << "姓名:" << d._name << endl; cout << "年龄:" << d._age << endl; } Person(T1 name,T2 age) { _name = name; _age = age; } private: T1 _name; T2 _age; }; void test01() { Person p("张三", 18); Show(p); } int main() { test01(); return 0; }
2. 友元函数在类外实现
//需要让编译器提前得知Person类存在 templateclass Person; //类外实现 template void Show(Person & d) { cout << "姓名:" << d._name << endl; cout << "年龄:" << d._age << endl; } template class Person { public: friend void Show<>(Person & d); Person(T1 name,T2 age) { _name = name; _age = age; } private: T1 _name; T2 _age; }; void test01() { Person p("张三", 18); Show(p); } int main() { test01(); return 0; }
如果要在类外进行实现,需要提前进行类模板声明和函数的实现
一般情况下,我们选择类内实现代码



