世间的道理,听没听说,知不知道是一回事,如何去做,又是另一回事。
书上书外的道理,如何落到实处,总是难上加难。
C++中主要通过模版来实现泛型思想,其中有两种模版机制函数模版和类模版。
函数模版主要作用是建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
1.函数模版语法
语法:template<typename T> 通用函数实现
template关键字声明函数模版<表明后面是一种数据类型(也可用class) 通常使用大写字母通用数据类型>
cpp
#include <iostream>
using namespace std;
template<typename T>//利用模板实现通用的交换函数
void mySwap(T& a, T& b)
{
T temp=a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
mySwap(a,b);//自动类型推导
cout << "a=" << a <<"," << "b=" << b << endl;
double c = 0.1;
double d = 0.2;
mySwap<double>(c,d);//模板指定类型
cout << "c=" << c <<"," << "d=" << d << endl;
system("pause");
return 0;
}
2.函数模版注意事项
(1)自动类型推导,必须推导出一致的数据类型T(即如果多个函数参数使用模版的通用数据类型,这些参数类型必须一致,才能使用自动类型推导),才可以使用。
(2)模板必须要确定出T的数据类型,才可以使用,例如:对于不需要传参的函数,必须指定数据类型(可以是空模板参数列表)。
3.普通函数和函数模版之间的区别
普通函数调用时可以发生自动类型转换(隐式类型转换)而 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换 ,如果利用显示指定类型的方式(即传入参数数据类型与指定的数据类型不一致时),可以发生隐式类型转换。
调用规则:
(1)如果函数模板和普通函数都可以实现,优先调用普通函数。
(2)可以通过空模板参数列表来强制调用函数模板。
(3) 函数模板也可以发生重载。
(4)如果函数模板可以产生更好的匹配(例如普通函数需要进行参数类型转换,而函数模版不需要),优先调用函数模板。
4.函数模版局限性
对于一些模版函数如果传入的是自定义类型,函数将无法运行,因此C++为了解决这种问题,提供模板的重载 ,可以为这些特定的类型提供具体化的模板。
cpp
#include <iostream>
using namespace std;
template<class T>
bool mycompare(T& a, T& b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
class Person{
public:
int M_age;
string M_name;
Person(int age, string name)
{
this->M_age = age;
this->M_name = name;
}
};
template<>bool mycompare(Person &a,Person &b)//具体化,template<>开头,并通过名称来指出类型,优先级高于常规模版
{
if (a.M_age == b.M_age && a.M_name == b.M_name)
{
return true;
}
else
{
return false;
}
}
int main()
{
Person p1(11, "张三三");
Person p2(12, "张三三");
int ret = mycompare(p1, p2);
if (ret)
{
cout << "p1等于p2" << endl;
}
else
{
cout << "p1不等于p2" << endl;
}
system("pause");
return 0;
}
5.类模版
语法:template<typename T> 通用类
类模板与函数模板区别主要有两点:
(1) 类模板没有自动类型推导的使用方式
(2)类模板在模板参数列表中可以有默认参数
cpp
#include <iostream>
#include <string>
using namespace std;
template<class nametype,class agetype = int>//agetype默认参数类型为int
class Person
{
public:
nametype m_name;
agetype m_age;
Person(nametype name, agetype age)
{
this->m_name = name;
this->m_age = age;
}
void showPerson()
{
cout << "年龄为" << this->m_age << "姓名为" << this->m_name << endl;
}
};
int main()
{
Person<string>p1("张三三",16);//无法自动推导必须指明参数模版参数,参数模版列表的第二项使用默认参数
system("pause");
return 0;
}
类模板中成员函数和普通类中成员函数创建时机是有区别的:
(1)普通类中的成员函数一开始就可以创建
(2)类模板中的成员函数在调用时才创建
cpp
#include <iostream>
#include <string>
using namespace std;
class Person1
{
public:
void showPerson1()
{
cout << "Person1" << endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout << "Person2" << endl;
}
};
template<class T>
void test01()
{
T p1;
p1.showPerson1();
//p1.showPerson2();//编译不报错,因为类模板中的成员函数未创建
}
int main()
{
test01<Person1>();//运行时报错,因为参数模版参数指定为Person1,无法调用showPerson2
system("pause");
return 0;
}
6.类模版对象做函数参数
一共有三种函数参数传入方式:
(1)指定传入的类型 --- 直接显示对象的数据类型
(2)参数模板化 --- 将对象中的参数变为模板进行传递
(3)整个类模板化 --- 将这个对象类型 模板化进行传递
cpp
#include <iostream>
#include <string>
using namespace std;
template<class nametype, class agetype >//agetype默认参数类型为int
class Person
{
public:
nametype m_name;
agetype m_age;
Person(nametype name, agetype age)
{
this->m_name = name;
this->m_age = age;
}
};
void showPerson1(Person<string, int> &p)//1.指定传入类型
{
cout << "年龄为" << p.m_age << " 姓名为" << p.m_name<< endl;
}
void test01()
{
Person<string, int>p1("张三三", 16);
showPerson1(p1);
}
template<class T1,class T2>//2.参数模版化,本质上是类模版化和参数模版化的结合
void showPerson2(Person<T1,T2> &p)
{
cout << "年龄为" << p.m_age << " 姓名为" << p.m_name << endl;
cout << "T1的数据类型" << typeid(T1).name()<< endl;
cout << "T2的数据类型" << typeid(T2).name() << endl;
}
void test02()
{
Person<string, int>p2("王一一", 14);
showPerson2(p2);
}
template<class T3>//2.整个类模版化,本质上也是类模版化和参数模版化的结合
void showPerson3( T3 p)
{
cout << "年龄为" << p.m_age << " 姓名为" << p.m_name << endl;
cout << "T3的数据类型" << typeid(T3).name() << endl;//查看模版数据类型
}
void test03()
{
Person<string, int>p3("周七七", 18);
showPerson3(p3);
}
int main()
{
test01();
test02();
test03();
Person<string,int>p1("张三三", 16);
system("pause");
return 0;
}
7.类模版与继承
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型 如果不指定,编译器无法给子类分配内存。
如果想灵活指定出父类中T的类型,子类也需变为类模板。
cpp
#include <iostream>
#include <string>
using namespace std;
template<class T>
class Base
{
public:
Base();
void showbase();
private:
T num;
};
template<class T>//成员函数类外实现
void Base<T>::showbase()
{
cout << "Base" << endl;
}
template<class T>//构造函数类外实现
Base<T>::Base()
{
cout <<typeid(T).name() << endl;
}
class Son:public Base<int>//为父类模版指定一个类型
{
};
template<class T1,class T2>
class Son1:public Base<T2>//使用类模版继承类模版
{
public:
Son1(){
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
};
};
void test01()
{
Son1<int,char> s1;
s1.showbase();
};
int main()
{
test01();
system("pause");
return 0;
}
8.类模版的分文件编写
如果将模版的头文件.h和实现文件.cpp分开编写,主文件中只包含.h文件,编译时会报错。类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。
解决方式有两种:(1)直接在主文件中包含.cpp文件(2)将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。
9.类模版和友元
全局函数类内实现 - 直接在类内声明友元即可 全局函数类外实现 - 需要提前让编译器知道全局函数的存在。
cpp
#include <iostream>
#include <string>
using namespace std;
//2、全局函数配合友元 类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
template<class T1, class T2> class Person;
//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
//template<class T1, class T2> void printPerson2(Person<T1, T2> & p);
template<class T1, class T2>
void printPerson2(Person<T1, T2> & p)
{
cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
}
template<class T1, class T2>
class Person
{
//1、全局函数配合友元 类内实现
friend void printPerson(Person<T1, T2> & p)
{
cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
}
//全局函数配合友元 类外实现
friend void printPerson2<>(Person<T1, T2> & p);
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
//1、全局函数在类内实现
void test01()
{
Person <string, int >p("Tom", 20);
printPerson(p);
}
//2、全局函数在类外实现
void test02()
{
Person <string, int >p("Jerry", 30);
printPerson2(p);
}
int main() {
//test01();
test02();
system("pause");
return 0;
}