1.模板
1.1·C++中除了有面向对象的思想外还有一种编程思想为泛型编程,主要利用技术便是模板。
1.2基本概念:template称为被参数化的类型,称其参数化是因为把类型相关的信息剥离出模板;称其类型是因为模板会根据它所作用的类型而有性质上的变化。
1.3特点:提高复用性,模板不可直接使用,且模板的通用性不是万能的。
1.4 模板分为两种:函数模板、类模板
1.5 模板参数并不非得是某个类型,也可以是常量表达式等等,暂时先知道即可,等后面遇到了再补充,本文全是当作某种类型来看。
1.6建议:将模板的类型参数视作类类型,根据实际使用情况能用常引用就不用值传递,对于类模板,能用初始化列表就用初始化列表。
2.函数模板
2.1函数模板:建立一个通用模板,其函数的返回类型与参数类型不具体制定,用一个虚拟的类型来表示。
2.1语法:
template<typename T>
//函数声明与定义
template:声明创建模板,后面跟着模板参数列表
typename:表示后面的符号是一种数据类型,可以用class代替,效果一样
T:通用的数据类型,名称可以替换,通常为大写字母
示例:
template<typename T>
void func(T &a, T &b) //交换函数
{
T temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
char c = 'x';
char d = 'y';
cout << "a:" << a << "\tb:" << b << endl;
func(a, b); //模板第一种使用方法:自动数据推导,实参推断
cout << "a:" << a << "\tb:" << b << endl;
cout << "c:" << c << "\td:" << d << endl;
func<char>(c, d); //模板第二种使用方法:显示指定类型,显示实参
cout << "c:" << c << "\td:" << d << endl;
return 0;
}
注意:
1>其实模板有类型参数化的味道,上面实例给出了使用模板的两种方法。
2>使用自动数据推导时,必须推导出一致的数据类型才可以使用,简单说就是数据类型要合法,而且并不是说只能有一个类型参数(如:template<typename T, class T1>)
3>模板必须要确定出T的数据类型才可以使用(即使没有使用到T)
template<class T>
void func2()
{
cout << "hello!" << endl;
}
int main()
{
//func2(); //错误
func2<int>(); //正确
return 0;
}
2.3函数模版与一般函数区别:
1>普通函数可以发生隐式类型转换
2>使用函数模板显示指定类型也可以发生隐式类型转换
3>使用函数模板自动类型推导则不可发生隐式类型转换
int Add1(int a,int b)
{
return a+b;
}
template<class T>
T Add2(T a,T b)
{
return a+b;
}
void test()
{
int a=10;
int b=20;
char c='c';
cout<<Add1(a, b)<<endl;
cout<<Add1(a, c)<<endl; //发生隐式类型转换,c转换成ascii码99
//cout<<Add2(a, c)<<endl; //发生错误,可见自动数据推导不可隐式转换
cout<<Add2<int>(a, c)<<endl; //显示指定类型可以发生隐式转换
}
int main()
{
test();
return 0;
}
2.4普通函数与函数模板的调用规则
1>普通函数与函数模板都可以实现,优先调用普通函数
2>可以通过空模板参数列表来强制调用函数模板
3>函数模板也可以发生重载
4>如果函数模板可以产生更好的匹配,那优先调用函数模板
void myPrintf(int a, int b)
{
cout<<"调用普通函数!"<<endl;
}
template<class T>
void myPrintf(T a, T b)
{
cout<<"调用模板!"<<endl;
}
template<typename T> //函数模板可发生重载
void myPrintf(T a, T b, T c)
{
cout<<"调用重载模板!"<<endl;
}
void test()
{
int a = 10;
int b = 20;
char c = 'c';
char d = 'd';
//当普通函数与函数模板都可以实现时调用普通函数
myPrintf(a, b); //输出为调用普通函数!
//虽然c、d可以通过隐式转换调用普通函数但函数模板更加适合,所以调用函数模板
myPrintf(c, d); //输出为调用模板!
//可以通过空模板参数列表来调用函数模板
myPrintf<>(a, b); //输出为调用模板!
//函数模板可以发生重载
myPrintf(a, b, 100); //输出为调用重载模板!
}
int main()
{
test();
return 0;
}
注意:既然提供了函数模板,那就最好不要再提供普通函数来,否则容易产生二义性!
2.5函数模板的局限性
模板不是万能的,比如使用自定义数据类型就不太适合
class People
{
public:
People(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
template<class T>
void Compare(T a, T b)
{
if (a == b) //此处有解决方法使得p1\p2比较,用运算符重载
cout << "相等!" << endl;
else
cout << "不相等!" << endl;
}
//特例化比较函数
template<>
void Compare(People a, People b)
{
if (a.m_age == b.m_age&&a.m_name == b.m_name)
cout << "相等!" << endl;
else
cout << "不相等!" << endl;
}
void test()
{
People p1("张三", 19);
People p2("张三", 9);
int a = 10;
int b = 10;
Compare(a, b);
Compare(p1, p2); //如果不重载则报错,因为编译器知道T类型是People但在比较中无法比较
}
int main()
{
test();
return 0;
}
所以,可以使用模板特例化来解决模板使用自定义类型的问题。注意,模板特例化是一个实例而不是重载!!!
3.类模板
3.1 类模板是建立一个通用类,类中的成员数据类型不确定,用一个虚拟类型表示
3.2类模板语法
template<typename T>
//类
template:声明创建模板
typename:表示后面的符号是一种数据类型,可以用class代替,效果一样
T:通用的数据类型,名称可以替换,通常为大写字母
3.3类模板与函数模板的区别
1>类模板没有自动类型推导的使用方式(类型推断)只有显示实参
2>类模板在模板参数列表中可以有默认参数(规则还是与之前一样)
template<class TypeName, class TypeAge>
class People
{
public:
People(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
TypeName m_name;
TypeAge m_age;
};
template<class TypeName, class TypeAge=int>
class Animal
{
public:
Animal(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
TypeName m_name;
TypeAge m_age;
};
void test()
{
//函数模板可以是因为可以根据实参的类型、顺序、个数来匹配,
//但类模板就不行,因为这传入的是用于初始化成员变量的参数,简单说构造函数只负责初始化没有这样的功能
//People p1("张三", 10); //报错
People<string, int> p1("张三", 10);
Animal<string> a1("猫", 10); //类模板可以有默认参数
}
int main()
{
test();
return 0;
}
3.4 类模板的成员函数创建时机:并不是一开始就创建,而是在调用时才创建。
class People1
{
public:
void Show1()
{
cout << "调用People1中的Show1!" << endl;
}
};
class People2
{
public:
void Show2()
{
cout << "调用People2中的Show2!" << endl;
}
};
template<class T>
class Animal
{
public:
T t;
void Show3()
{
t.Show1();
}
void Show4()
{
t.Show2();
}
};
void test()
{
Animal<People1> animal;
animal.Show3();
//animal.Show4(); //报错!
}
int main()
{
test();
return 0;
}
3.5类模板对象做函数参数
一共有三种传入方式:
1> 指定传入的类型——直接显示对象的数据类型(推荐)
2>参数模板化——将对象中的参数变为模板进行传递
3>整个类模板化——将这个对象类型模板化进行传递
template<class T1, class T2>
class People
{
public:
People(T1 name,T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void show()
{
cout << "姓名为:" << m_Name << " 年龄为:" << m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//第一种:指定传入数据类型,直接把类模板的数据类型确定
void myPrintf1(People<string, int> p)
{
p.show();
}
void test1()
{
People<string, int> p1("张三", 10);
myPrintf1(p1);
}
//第二种:参数模板化
template<class T1,class T2>
void myPrintf2(People<T1, T2> p)
{
p.show();
}
void test2()
{
People<string, int> p2("张三", 20);
myPrintf2(p2);
}
//第三种:整个类模板化
template<class T>
void myPrintf3(T p)
{
p.show();
}
void test3()
{
People<string, int> p3("张三", 30);
myPrintf3(p3);
}
int main()
{
test1();
test2();
test3();
return 0;
}
推荐使用第一种!
3.6类模板与继承
注意事项:
1> 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类的数据类型。
2> 如果不指定,编译器将无法给子类分配内存。(因为数据类型不确定根本就不知道占多大内存空间)
3> 如果想灵活指定出父类中T的类型,子类也需要变为类模板。
template<class T>
class Base
{
public:
T m_B;
};
//第一种,指定父类数据类型
class Son :public Base<int>{
};
void test1()
{
Son s;
cout << typeid(s.m_B).name() << endl;
}
//第二种:不指定父类具体数据类型,但子类也要是模板
template<class T1, class T2>
class Son1 :public Base<T1>
{
public:
T2 m_S;
};
void test2()
{
Son1<int, char> s1;
cout << typeid(s1.m_B).name() << endl;
cout << typeid(s1.m_S).name() << endl;
}
int main()
{
test1();
test2();
return 0;
}
3.7类模板成员函数的类外实现
template<class T1,class T2>
class Person
{
public:
Person(T1 name, T2 age);
void Show();
T1 m_name;
T2 m_age;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
template<class T1, class T2>
void Person<T1, T2>::Show()
{
cout << m_name << " " << m_age << endl;
}
void test()
{
Person<string, int> p1("张三",15);
p1.Show();
}
int main()
{
test();
return 0;
}
如何记忆:先按一般类外实现去写,然后,这是类模板所以要在类名与域作用符之间加上<T1……>,最后既然有模板参数列表,那么前面肯定要加template关键字加模板参数列表。
3.8类模板分文件编写
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。(各个文件独立编译,如果在某.cpp文件中出现了函数调用,但是在此.cpp文件并没有对应函数的实现。此时就会在函数调用出生成特定的符号,在之后的链接过程完成函数调用)
解决方案:
1>直接包含源文件(之前包含的是头文件,把头文件换成.cpp文件)
2>将声明和实现都写在同一文件中,头文件.h后缀更改为.hpp,.hpp是约定名称,不强制。
3.9类模板与友元
全局函数作友元类内实现:直接在类内声明友元即可(推荐)
全局函数作友元类外实现:需要提前让编译器知道全局函数的存在
//因为友元提前了,里面Person肯定要先声明,而Person又是一个类模板,所以还要加上template……
template<class T1, class T2>
class Person;
//这里之所以放在最前面,是因为如果全局函数作为友元类外实现的话,必须要让编译器提前知道,函数本质发生变化,变成函数模板
template<class T1, class T2>
void Show2(Person<T1, T2> p)
{
cout << "类外实现——姓名为:" << p.m_Name << "\t" << "年龄为:" << p.m_Age << endl;
}
template<class T1,class T2>
class Person
{
public:
//第一种:直接在类内实现,参数模板化实现
friend void Show1(Person<T1,T2> p)
{
cout << "姓名为:" << p.m_Name << "\t" << "年龄为:" << p.m_Age << endl;
}
//第二种:内外实现
friend void Show2<>(Person<T1, T2> p); //注意:这里要加<>是为了表明是一个函数模板
Person(T1 name, T2 age);
private:
T1 m_Name;
T2 m_Age;
};
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void test1()
{
Person<string, int> p1("张三", 10);
Show1(p1);
Show2(p1);
}
int main()
{
test1();
return 0;
}