第十六章 模板与泛型编程

C++模板

模板概论

c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。

  • c++提供两种模板机制:函数模板和类模板
  • 类属 - 类型参数化,又称参数模板

总结:

  • 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
  • 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

函数模板

函数模板基本使用:

  1. template 或者 template,告诉编译器,紧跟的代码里出现T不要报错
  2. mySwap(T &a,T &B)类型也需要传入,类型参数化
  3. mySwap(a,b) 自动类型推导,按照a,b的类型来推导、替换T,此时,要确保,a,b的类型是一致的,不会出现二义性。
  4. mySwap(a,b) 显式指定的类型

函数模板和普通函数区别

函数模板不允许自动类型转化,普通函数能够自动进行类型转化

普通函数和函数模板的调用规则

1. 如果出现普通函数与模板重载,优先使用普通函数调用,如果没有实现,就会报错
2. 如果像强制调用模板,那么可以使用空参列表myPrint<>(a, b);
3. 函数模板也可以发生重载
4. 如果函数模板可以产生更好的匹配,那么优先使用函数模板

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

//1.普通函数与函数模板的区别
template<class T>
T myPlus(T a, T b)
{
	return a + b;
}

int myPlus2(int a, int b)
{
	return a + b;
}


//2.普通函数和函数模板的调用规则
template<class T>
void myPrint(T a, T b)
{
	cout << "模板函数调用的myPrint(a,b)" << endl;
}

//3.函数模板的重载
template<class T>
void myPrint(T a, T b,T c)
{
	cout << "模板函数调用的myPrint(a,b,c)" << endl;
}

void myPrint(int a, int b)
{
	cout << "普通函数调用的myPrint" << endl;
}


void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';

	//myPlus(a, c);//类型推导不出来 ,模板函数不可以进行类型转换
	cout << myPlus2(a, c) << endl;//可以 109 a = 10,c = 99
}

void test02()
{
	int a = 10;
	int b = 20;
	//1.如果出现普通函数与模板重载,优先使用普通函数调用,如果没有实现,就会报错
	myPrint(a, b);

	//2.如果像强制调用模板,那么可以使用空参列表
	myPrint<>(a, b); //模板函数调用的myPrint

	//3.函数模板也可以发生重载
	myPrint<>(a, b);

	//4.如果函数模板可以产生更好的匹配,那么优先使用函数模板
	char c = 'c';
	char d = 'd';
	myPrint(c, d);
}


int main()
{
	//test01();
	test02();//普通函数调用的myPrint

	system("pause");
	return 0;
}

image

模板机制剖析

思考:

1. 为什么函数模板可以和普通函数放在一起?
2. c++编译器是如何实现函数模板机制的?

C/C++语言程序的编译过程:

hello.cpp程序是高级c语言程序,这种程序易于被人读懂。为了在系统上运行hello.c程序,每一条c语句都必须转化为低级的机器指令。
然后将这些机器指令打包成可执行目标文件格式,并以二进制形式存储于磁盘中。
预处理(Pre-processing) -> 编译(Compiling) ->汇编(Assembling) -> 链接(Linking)

image

函数模板机制结论:

  • 编译器并不是把函数模板处理成能够处理任何类型的函数
  • 函数模板通过具体类型产生不同的函数
  • 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
    • 第一次编译:检查有没有语法错误
    • 第二次编译:生成类型替换后的代码,检查有没有错误

函数模板的局限性

  1. 模板不能解决所有类型
  2. 如果出现不能解决的类型,可以通过第三代具体化来解决问题
  3. 第三代具体化语法:
  4. template<> 返回值 函数名<具体类型>(参数)
  5. 返回值和函数名要与具体化之前的一致,不然会报错

假设有如下模板函数:

template<class T>
void f(T a, T b)
{
	…
}
  • 如果代码实现时定义了赋值操作 a = b,但是T为数组,这种假设就不成立了
  • 同样,如果里面的语句为判断语句 if (a>b), 但T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。
  • 总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。
class Person
{
public:
	Person(string name, int age)
	{
		this->mName = name;
		this->mAge = age;
	}
	string mName;
	int mAge;
};

//普通交换函数
template <class T>
void mySwap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}
//第三代具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
//具体化优先于常规模板
template<>void  mySwap<Person>(Person &p1, Person &p2)
{
	string nameTemp;
	int ageTemp;

	nameTemp = p1.mName;
	p1.mName = p2.mName;
	p2.mName = nameTemp;

	ageTemp = p1.mAge;
	p1.mAge = p2.mAge;
	p2.mAge = ageTemp;

}

void test()
{
	Person P1("Tom", 10);
	Person P2("Jerry", 20);

	cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl;
	cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl;
	mySwap(P1, P2);
	cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl;
	cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl;
}
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;


class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};

template<class T>
bool myCompare(T &a, T &b)
{
	if (a == b)
	{
		return true;
	}
	return false;
}

//通过具体化,来自定义数据类型,解决上述问题
//如果两个都能匹配,优先匹配具体化的
//如果具体的匹配不到了,就去找通用的,再不行就error
//语法:template<> 返回值 函数名<具体类型>(参数)
template<> bool myCompare<Person>(Person &a, Person &b)
{
	if (a.m_Age == b.m_Age)
	{
		return true;
	}
	return false;
}

void test01()
{
	int a = 10;
	int b = 20;

	int ret = myCompare(a, b);

	cout << "ret = " << ret << endl;//0

	Person p1("tom",10);
	Person p2("jerry",10);

	//ret = myCompare(p1.m_Age, p2.m_Age);//0
	//如果两个都能匹配,优先匹配具体化的
	//如果具体的匹配不到了,就去找通用的,再不行就error
	ret = myCompare(p1, p2);//exception
	cout << "ret = " << ret << endl;//0
}

int main()
{
	test01();

	system("pause");
	return 0;
}

类模板

类模板基本概念

类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。
类模板用于实现类所需数据的类型参数化

类模板的基本使用

写法:template<T...>紧接着是类

函数模板可以进行自动类型推导,而类模板不可以
函数模板不可以有默认数据类型,而类模板可以

template<class NameType, class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

void test01()
{
	//Person P1("德玛西亚",18); // 类模板不能进行类型自动推导 
	Person<string, int>P1("德玛西亚", 18);
	P1.showPerson();
}
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<string>
#include<iostream>
using namespace std;

//类模板
//类模板可以有默认类型,函数模板不可以有
template<class NameType, class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson()
	{
		cout << "name:" << this->m_Name << " age:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

void test01()
{
	//类模板,不支持自动类型推导
	//Person p("monkey",100);

	//显式指定类型
	Person<string, int> p("monkey", 100);
	p.showPerson();

}


int main()
{
	test01();

	system("pause");
	return 0;
}

成员函数的创建时机

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<string>
#include<iostream>
using namespace std;

//类模板
//类模板可以有默认类型,函数模板不可以有
template<class NameType, class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson()
	{
		cout << "name:" << this->m_Name << " age:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

class Person1
{
public:
	void showPerson1()
	{
		cout << "showPerson1调用"  << endl;
	}
};

class Person2
{
public:
	void showPerson2()
	{
		cout << "showPerson2调用" << endl;
	}
};

template<class T>
class myClass
{
public:
	T obj;
	void func1()
	{
		obj.showPerson1();
	}

	void func2()
	{
		obj.showPerson2();
	}
};

//成员函数一开始不会创建出来,而是在运行时才会创建
//如果不调用func1(),那它根本就不会去调用showPerson()

void test01()
{
	//类模板,不支持自动类型推导
	//Person p("monkey",100);

	//显式指定类型
	Person<string, int> p("monkey", 100);
	p.showPerson();

}


void test02()
{
	myClass<Person1> p1;
	p1.func1();
	p1.func2();

}

int main()
{
	//test01();
	test02();

	system("pause");
	return 0;
}

类模板做函数的参数:

1.显示指定类型
2.参数模板化
3.整体模板化

//如何查看类型
cout << "方式三:" << typeid(T).name() << endl;

//类模板
template<class NameType, class AgeType>
class Person{
public:
	Person(NameType name, AgeType age){
		this->mName = name;
		this->mAge = age;
	}
	void PrintPerson(){
		cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

//类模板做函数参数
void DoBussiness(Person<string,int>& p){
	p.mAge += 20;
	p.mName += "_vip";
	p.PrintPerson();
}

int main(){

	Person<string, int> p("John", 30);
	DoBussiness(p);

	system("pause");
	return EXIT_SUCCESS;
}

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<string>
#include<iostream>
using namespace std;

template<class NameType, class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson()
	{
		cout << "name:" << this->m_Name << " age:" << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

//第一种传参方式:
//1.指定传入类型
void doWork(Person<string, int> &p)//
{
	p.showPerson();
}

void test01()
{
	Person <string, int>p("tom", 10);
	doWork(p);
}

//2.参数模板化
template<class T1,class T2>
void doWork2(Person<T1, T2> &p)
{
	//如何查看类型
	cout << "方式二:" << typeid(T1).name() << endl;
	cout << "方式二:" << typeid(T2).name() << endl;
	p.showPerson();
}

void test02()
{
	Person <string, int>p("jerry", 10);
	doWork2(p);
}

//3.整体类型化
template<class T>
void doWork3(T &p)
{
	//如何查看类型
	cout << "方式三:" << typeid(T).name() << endl;
	p.showPerson();
}

void test03()
{
	Person<string, int> p("dog", 12);
	doWork3(p);
}



int main()
{
	test01();
	test02();
	test03();

	system("pause");
	return 0;
}

image

类模板碰到的继承问题

当类模板碰到继承:

  • 基类如果是模板类,必须让子类告诉编译器,基类中的 T 到底是什么类型,
  • 如果不告诉,那么无法分配内存,编译不过
  • 利用参数列表 class Child : public Base
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<string>
#include<iostream>
using namespace std;

template<class T>
class Base
{
public:


	T m_A;
};

//在构造Child的时候,也会调用Base里的构造,Base里的构造有可能会对m_A初始化,
//那就要对 T 分配内存,而此时还不确定 T 的类型,因此不知道 T 的内存
//也即,child继承于base,必须告诉base中的T的数据类型,否则无法给T分配内存

//class Child : public Base;//error,应该为如下:
class Child : public Base<int>
{

};

//child也是模板类
template<class T1,class T2>
class Child2 : public Base<T2>
{
public:
	Child2()
	{
		cout << typeid(T1).name() << endl;//int
		cout << typeid(T2).name() << endl;//double
	}
public:
	T1 m_B;
};



void test01()
{

	Child2<int, double>child;//T1 int类型 , T2是double类型 //让用户指定类型
}


int main()
{
	test01();
	system("pause");
	return 0;
}

类模板类外实现成员函数

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<string>
#include<iostream>
using namespace std;

template<class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age);
		/*{
			this->m_Name = name;//类内实现形式
			this->m_Age = age;
		}*/

		void showPerson();
	//{
	//	cout << "姓名:" << this->m_Name << "年龄" << this->m_Age << endl;
	//}
	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>::showPerson()
{
	cout << "姓名:" << this->m_Name << "年龄" << this->m_Age << endl;
}


void test01()
{
	Person<string,int> p1("Mt", 100);
	p1.showPerson();
}


int main()
{
	test01();
	system("pause");
	return 0;
}

image

类模板头文件和源文件分离问题

问题:

  • .h 、.cpp 分别声明和实现
  • 但是由于类模板的成员函数在运行阶段才去创建,导致包含 .h 头文件不会创建函数的实现,就无法解析外部命令

解决方案:

  • 方案一:包含.cpp文件(不推荐)
  • 方案二:不要分文件编写,将声明和实现写到同一个文件中,后缀名改为.hpp(约定俗成)

Person.hpp

#pragma once

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

template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
	this->mName = name;
	this->mAge = age;
}

template<class T1, class T2>
void Person<T1, T2>::ShowPerson(){
	cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}

main.cpp

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
#include"Person.hpp"

//模板二次编译
//编译器编译源码 逐个编译单元编译的

int main(){

	Person<string, int> p("Obama", 20);
	p.ShowPerson();


	system("pause");
	return EXIT_SUCCESS;
}

**结论: ** 案例代码在qt编译器顺利通过编译并执行,但是在Linux和vs编辑器下如果只包含头文件,那么会报错链接错误,需要包含cpp文件,但是如果类模板中有友元类,那么编译失败!
**解决方案: **类模板的声明和实现放到一个文件中,我们把这个文件命名为.hpp(这个是个约定的规则,并不是标准,必须这么写).
原因:
类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
C++编译规则为独立编译。

Person.hpp

点击查看代码
#pragma once
#include<iostream>
using namespace std;

template<class T1,class T2>
class Person
{
public:
	Person(T1 name,T2 age);
	void showPerson();

	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>::showPerson()
{
	cout << "姓名:" << this->m_Name << " 年龄 " << this->m_Age << endl;
}

main

#include<iostream>
#include<string>
#include"Person.hpp"


using namespace std;


//建议,模板不要做份文件编写,写到一个类中即可,类内进行声明和实现,最后把这个后缀名改为 .hpp
//这是约定俗称的,“.hpp”文件一般都是写模板用的
int main()
{
	Person<string, int> p("pig", 18);
	p.showPerson();

	//如何解决?
	//#include<Person.cpp>

	system("pause");
	return 0;
}

当模板类碰到友元函数

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include <string>

template<class T1, class T2> class Person;
//告诉编译器这个函数模板是存在
template<class T1, class T2> void PrintPerson2(Person<T1, T2>& p);

//友元函数在类内实现
template<class T1, class T2>
class Person{
	//1. 友元函数在类内实现
	friend void PrintPerson(Person<T1, T2>& p){
		cout << "Name:" << p.mName << " Age:" << p.mAge << endl;
	}

	//2.友元函数类外实现
	//告诉编译器这个函数模板是存在
	friend void PrintPerson2<>(Person<T1, T2>& p);

	//3. 类模板碰到友元函数模板
	template<class U1, class U2>
	friend void PrintPerson(Person<U1, U2>& p);

public:
	Person(T1 name, T2 age){
		this->mName = name;
		this->mAge = age;
	}
	void showPerson(){
		cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
	}
private:
	T1 mName;
	T2 mAge;
};

void test01()
{
	Person <string, int>p("Jerry", 20);
	PrintPerson(p);
}


// 类模板碰到友元函数
//友元函数类外实现  加上<>空参数列表,告诉编译去匹配函数模板
template<class T1, class T2>
void PrintPerson2(Person<T1, T2>& p)
{
	cout << "Name2:" << p.mName << " Age2:" << p.mAge << endl;
}

void  test02()
{
	Person <string, int>p("Jerry", 20);
	PrintPerson2(p);   //不写可以编译通过,写了之后,会找PrintPerson2的普通函数调用,因为写了普通函数PrintPerson2的声明	
}

int main(){

	//test01();
	test02();
	system("pause");
	return EXIT_SUCCESS;
}
posted @ 2023-02-17 21:26  nullptrException  阅读(51)  评论(0)    收藏  举报