模板的基本特征

目录

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++提供两种模板 -- 函数模板和类模板

模板特点:

  1. 模板不可以直接使用,它只是一个框架
  2. 模板的通用性不是万能的

2. 函数模板

2.1 函数模板语法

建立一个通用的函数,其返回类型和参数类型可以不指定,可以通过一个虚拟的类型来替代

语法:

template<typename T>

函数的声明和定义

说明:

template -- 声明创建的模板

typename -- 后面的字符表示为一个数据类型,可以被class替换

T -- 通用数据类型,可以被替换,但一般使用大写字母

举例:

//函数模板
//template<typename T>
template<class T>
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<typename T>
//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<typename T>
//2. 模板必须确定T的数据类型,才能够使用
template<class T>
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<typename 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.4 普通函数和模板函数的调用规则


1.如果函数模板和普通函数都可以实现,则优先调用普通函数
2.可以通过空模板的参数列表来强制调用模板函数

void MyPrint(int a, int b)
{
	cout << "调用普通函数" << endl;
}
template<typename T>
void MyPrint(T a, T b)
{
	cout << "调用函数模板" << endl;
}
int main()
{
	//调用普通函数
	MyPrint(10, 20);
	//调用函数模板
	MyPrint<>(10, 20); 
    MyPrint<int>(10,20);
	return 0;
}

当不显示指定类型调用时,调用的是普通函数,只有通过空模板或者显示类型才会调用模板函数 

3.函数模板可以发生函数重载
4.如果函数模板可以更好的进行匹配,优先调用函数模板 

void MyPrint(int a, int b)
{
	cout << "调用普通函数" << endl;
}
template<typename T>
void MyPrint(T a, T b)
{
	cout << "调用函数模板" << endl;
}
template <typename T>
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 模板的局限性

模板的通用性不是万能的

例如:

template<typename T>
bool 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<class T>
类的实现

 说明:

template -- 声明创建的模板

class -- 后面的字符表示为一个数据类型,可以被typename替换

T -- 通用数据类型,可以被替换,但一般使用大写字母

举例:

template<class NameType,class AgeType>
class 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<string, int> p1("张三", 15);
	p1.Show();
	return 0;
}

3.2 类模板和函数模板之间的区别

1. 类模板没有自动推导,需要显示指定类型

template<class NameType, class AgeType = int>
class 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<string,int> p1("张三", 15);
	p1.Show();
}
int main()
{
	test01();
	return 0;
}

2. 类模板中可以有缺省参数

template<class NameType = string, class AgeType = int>
class 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<string> 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 T>
class Myclass
{
public:

	T obj;
	void func1()
	{
		obj.ShowPerson1();
	}
	void func2()
	{
		obj.ShowPerson2();
	}
};
void test01()
{
	Myclass <Person1> 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 类模板做参数

类模板做参数有三种方式:

  1. 指定传入类型
  2. 将参数模板化
  3. 将整个类模板化

举例:

template <class T1,class T2>
class 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<string,int>&p)
{
	p.ShowPerson();
}
void test01()
{
	Person<string, int> p("张三", 18);
	PrintPerson(p);
}
//2.将参数模板化
template<class T1,class T2>
void PrintPerson2(Person<T1,T2>&p)
{
	p.ShowPerson();
	cout << "T1的类型:" <<typeid(T1).name() << endl;
	cout << "T2的类型:" << typeid(T2).name() << endl;
}
void test02()
{
	Person<string, int>p("李四", 18);
	PrintPerson2(p);
}
//3.整个类模板化
template <class T>
void PrintPerson3(T&p)
{
	p.ShowPerson();
	cout << "T的类型:" << typeid(T).name() << endl;
}
void test03()
{
	Person<string, int>p("王五", 20);
	PrintPerson3(p);
}
int main()
{
	test01();
	cout << "------------" << endl;
	test02();
	cout << "------------" << endl;
	test03();
	return 0;
}

打印出模板所代表的类型: 

 一般情况下我们如果要将模板类作为函数参数,我们选择第一种方法,因为其余两种将函数模板和类模板结合起来,代码更加复杂。

3.5 类模板和继承

1. 如果我们要继承的父类是一个模板类,我们应该怎么做?

template <class T>
class Base
{
public:
	T m;
};
//必须要知道父类中T的数据类型,才能够继承父类
class Son :public Base <int> 
{

};

我们必须要指定父类中T的数据类型,才能够继承类,因为如果不指明T的数据类型,那么系统不确定开多少字节的空间

2. 如果我们想让我们的父类T的数据类型灵活变化,而不是指定后就无法改变,应该怎么做?

这时我们就需要将子类也进行模板化

template <class T>
class Base
{
public:
	T m;
};
//如果想要灵活的指定父类的T类型,需要将子类也模板化
template<class T1,class T2>
class Son2:public Base<T1>
{
public:
	T2 obj;
};
void test02()
{
	Son2<int,char> 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;
}

在类外实现,需要加上该类的作用域来表示该函数属于该类

那么模板类的成员函数又是如何在类外实现呢?

template <class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void ShowPerson();
	T1 _name;
	T2 _age;
};

//构造函数类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{
	_name = name;
	_age = age;
}
template<class T1, class T2>
void Person<T1,T2>::ShowPerson()
{
	cout << "姓名:" << _name << endl;
	cout << "年龄:" << _age << endl;
}
void Test01()
{
	Person<string,int> s1("张三",25);
	s1.ShowPerson();
}
int main()
{
	Test01();
	return 0;
}

比起普通的类在类外实现,只是在每个类实现前加上了template<class T1,class T2>

 3.7 类模板和友元

1. 友元函数在类内实现

template<class T1,class T2>
class Person
{
public:
	friend void Show(Person<T1, T2>& 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<string, int> p("张三", 18);
	Show(p);
}
int main()
{
	test01();
	return 0;
}

2. 友元函数在类外实现

//需要让编译器提前得知Person类存在
template<class T1,class T2>
class Person;
//类外实现
template<class T1, class T2>
void Show(Person<T1, T2>& d)
{
	cout << "姓名:" << d._name << endl;
	cout << "年龄:" << d._age << endl;
}
template<class T1,class T2>
class Person
{
public:
	friend void Show<>(Person<T1, T2>& d);
	Person(T1 name,T2 age)
	{
		_name = name;
		_age = age;
	}
private:
	T1 _name;
	T2 _age;
};
void test01()
{
	Person<string, int> p("张三", 18);
	Show(p);
}
int main()
{
	test01();
	return 0;
}

如果要在类外进行实现,需要提前进行类模板声明函数的实现

一般情况下,我们选择类内实现代码 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值