第四部分—C++核心编程_5. 模板

发布于:2023-01-17 ⋅ 阅读:(614) ⋅ 点赞:(0)

5.1C++模板

5.1.1 模板概论

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

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

 总结:

1.模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。

2.模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

5.1.2 函数模板

5.1.2.1 什么是函数模板?

#include <iostream>
using namespace std;

template<class T>
void MySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
void test01()
{
	int a = 10;
	int b = 20;
	cout << "交换前" << a << " " << b << endl;
	MySwap(a, b);
	cout << "交换后" << a << " " << b << endl;

	char c = 'a';
	char d = 'b';
	MySwap(c, d);
	cout << "交换前" << c << " " << d << endl;
	MySwap(c, d);
	cout << "交换后" << c << " " << d << endl;

	char e = 'x';
	char f = 'y';
	cout << "交换前" << e << " " << f << endl;
	MySwap<char>(e, f);                            //函数模板可以自动类型推导,也可以显式指定类型                         
	cout << "交换后" << e << " " << f << endl;
}

int main()
{
	test01();
	return 0;
}

输出结果

交换前10 20
交换后20 10
交换前b a
交换后a b
交换前x y
交换后y x

用模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。

5.1.2.2 使用函数模板对数组进行排序

template <class T1>
void PrintArray(T1 arr[],int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

template <class T2>
void MySort(T2 arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		for (int j = len - 1; j > i; j--)
		{
			if (arr[j] > arr[j - 1])
			{
				T2 temp = arr[j - 1];
				arr[j - 1] = arr[j];
				arr[j] = temp;
			}
		}
	}
}


void test()
{
	//char数组
	char tempChar[] = "abcdefg";
	int charLen = strlen(tempChar);
	cout << "排序前" << endl;
	PrintArray(tempChar, charLen);

	//排序
	MySort(tempChar, charLen);
	cout << "排序后" << endl;
	PrintArray(tempChar, charLen);


	//int数组
	int tempInt[] = { 1,2,3,4,5,6,7 };
	int intLen = sizeof(tempInt) / sizeof(tempInt[0]);
	cout << "排序前" << endl;
	PrintArray(tempInt, intLen);

	//排序
	MySort(tempInt, intLen);
	cout << "排序后" << endl;
	PrintArray(tempInt, intLen);
}

int main()
{
	test();
	return 0;
}

输出结果

排序前
a b c d e f g
排序后
g f e d c b a
排序前
1 2 3 4 5 6 7
排序后
7 6 5 4 3 2 1

5.1.3 函数模板和普通函数区别

  1. 函数模板不允许自动类型转化
  2. 普通函数能够自动进行类型转化
template <class T>
void MyPlus(T a, T b)
{
	cout << "调用模板函数" << endl;
}

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

void test()
{
	int a = 0;
	char b = 'a';

	//调用函数模板要严格匹配类型
	MyPlus(a, a);
	MyPlus(b, b);

	//调用普通函数,普通函数可以隐式类型转换
	MyPlus(a, b);

	MyPlus<int>(a, b);
}


int main()
{
	test();
	return 0;
}

输出结果

调用普通函数
调用模板函数
调用普通函数
调用模板函数

5.1.4 函数模板和普通函数在一起调用规则

  1. c++编译器优先考虑普通函数
  2. 可以通过空模板实参列表的语法限定编译器只能通过模板匹配
  3. 函数模板可以像普通函数那样可以被重载
  4. 如果函数模板可以产生一个更好的匹配,那么选择模板
template <class T>
void MyPlus(T a, T b)
{
	cout << "调用模板函数" << endl;
}

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

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

void test()
{
	int a = 10;
	int b = 20;
	char c = 'x';
	char d = 'y';
	//1.如果函数模板和普通函数都能匹配,c++编译器优先考虑普通函数
	MyPlus(a, b);
	//2.如果我必须要调用函数模板,则加上实参列表强制调用函数模板
	MyPlus<>(a, b);
	//3.相对于普通函数不需要类型转换,那么选择模板函数
	MyPlus(c, d);
	//4.函数模板可以重载
	MyPlus<int>(a, b, c);
}

int main()
{
	test();
	return 0;
}

输出结果

调用普通函数
调用模板函数
调用模板函数
调用重载的模板函数

5.1.5 模板机制剖析

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

5.1.5.1编译过程

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

预处理(Pre-processing) -> 编译(Compiling) ->汇编(Assembling) -> 链接(Linking)

5.1.5.2 模板实现机制

函数模板机制结论:

  1. 编译器并不是把函数模板处理成能够处理任何类型的函数
  2. 函数模板通过具体类型产生不同的函数
  3. 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

5.1.6模板的局限性

假设有如下模板函数:

template<class T>
void f(T a, T b)
{
      …  
}

如果代码实现时定义了赋值操作 a = b,但是T为数组,这种假设就不成立了同样,如果里面的语句为判断语句 if(a>b),但T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。

总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。

class Person
{
public:
	Person(const string name, int age)
	{
		this->Name = name;
		this->Age = age;
	}
	string Name;
	int Age;
};

template<class T> void mySwap(T& p1, T& p2)
{
	string nameTemp;
	int ageTemp;
	nameTemp = p1.Name;
	p1.Name = p2.Name;
	p2.Name = nameTemp;

	ageTemp = p1.Age;
	p1.Age = p2.Age;
	p2.Age = ageTemp;
}

void test()
{
	Person p1("孙悟空", 999);
	Person p2("猪八戒", 888);

	cout << p1.Name << " " << p1.Age << endl;
	cout << p2.Name << " " << p2.Age << endl;

	mySwap(p1, p2);
	cout << p1.Name << " " << p1.Age << endl;
	cout << p2.Name << " " << p2.Age << endl;
}
int main()
{
	test();
	return 0;
}

输出结果

孙悟空 999
猪八戒 888
猪八戒 888
孙悟空 999

5.1.7 类模板

5.1.7.1 类模板基本概念

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

类模板用于实现类所需数据的类型参数化

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

	void ShowPerson()
	{
		cout << this->Name << " " << this->Age << endl;
	}

public:
	NameType Name;
	AgeType Age;
};

void test()
{
  //Person p1("孙悟空",999);               //类模板不能进行类型自动堆导
	Person<string, int> p1("孙悟空", 999); //需要指定类型
	p1.ShowPerson();
}
int main()
{
	test();
	return 0;
}

输出结果

孙悟空 999

5.1.7.2 类模板做函数参数

//类模板
template <class NameType,class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->Name = name;
		this->Age = age;
	}

	void PrintPerson()
	{
		cout << this->Name << " " << this->Age << endl;
	}

public:
	NameType Name;
	AgeType Age;
};

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

void test()
{
	Person<string, int>p("Tom", 30);
	p.PrintPerson();
	DoBussiness(p);
}

int main()
{
	test();
	return 0;
}

输出结果

Tom 30
Tom vip 50

5.1.7.3 类模板派生普通类

//类模板
template<class T>
class Myclass 
{
public:
	Myclass(T property)
	{
		this->Property = property;
	}

public:
	T Property;
};

//子类实例化的时候需要具体化的父类,子类需要知道父类的具体类型是什么样的
//这样c++编译器才能知道给子类分配多少内存
//普通派生类
class Subclass :public  Myclass<int>
{
public:
	Subclass(int b) :Myclass<int>(20)    //给父类初始化
	{
		this->B = b;
	}

	void Print()
	{
		cout << this->B << " " << this->Property << endl;
	}

public:
	int B;
};

void test()
{
	Subclass b(10);
	b.Print();
}

int main()
{
	test();
	return 0;
}

输出结果

10 20

5.1.7.4 类模板派生类模板

//父类模板
template <class T1>
class Base
{
public:
	Base(T1 m)
	{
		this->M = m;
	}
public:
	T1 M;
};

//继承类模板的时候,必须要确定父类的大小
template<class T2>
class Child :public Base<double>
{
public:
	Child(T2 param) :Base<double>(20.00)
	{
		this->Param = param;
	}
	void Print()
	{
		cout << this->Param << " " << this->M << endl;
    }
public:
	T2 Param;
};

void test()
{
	Child<int> d1(10);
	d1.Print();
}

int main()
{
	test();
	return 0;
}

输出结果

10 20

5.1.7.5 类模板类内实现

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

	void ShowPerson()
	{
		cout << this->Name << " " << this->Age << endl;
	}

public:
	NameType Name;
	AgeType Age;
};


void test()
{
	Person<string, int>p1("孙悟空", 999);
	p1.ShowPerson();
}

int main()
{
	test();
	return 0;
}

输出结果

孙悟空 999

5.1.7.6 类模板类外实现

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

public:
	T1 Name;
	T2 Age;
};

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

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

void test()
{
	Person<string, int>p1("孙悟空", 999);
	p1.Print();
}

int main()
{
	test();
	return 0;
}

输出结果

孙悟空 999

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

//1.Person.hpp 部分
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <stdbool.h>
#include <iostream>
#include <string>
using namespace std;


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

public:
	T1 Name;
	T2 Age;
};

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

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







//2. main.cpp 部分
#include"Person.hpp"
int main()
{
	Person<string, int>p1("孙悟空", 999);
	p1.Print();
	return 0;
}

输出结果

孙悟空 999

结论: 案例代码在qt编译器顺利通过编译并执行,但是在Linux和vs编辑器下如果只包含头文件,那么会报错链接错误,需要包含cpp文件,但是如果类模板中有友元类,那么编译失败!

解决方案: 类模板的声明和实现放到一个文件中,我们把这个文件命名为.hpp(这个是个约定的规则,并不是标准,必须这么写).

原因:

  1. 类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
  2. C++编译规则为独立编译。

5.1.7.8模板类碰到友元函数

//告诉编译器Person这个类模板是存在的
template <class T1, class T2>class Person;

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

//友元函数在类内实现
template <class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age)
	{
		this->Name = name;
		this->Age = age;
	}
	//1.友元函数在类内实现
	friend void Print1(Person<T1, T2>& p)
	{
		cout << p.Name << " " << p.Age << endl;
	}
	
	//2.友元函数在类外实现
	//友元函数类外实现  加上<>空参数列表,告诉编译去匹配函数模板
	friend void Print2(Person<T1, T2>& p);

	//3.类模板碰到友元函数模板
	template <class T3, class T4>
	friend void Print3(Person<T3, T4>& p)
	{
		cout << p.Name << " " << p.Age << endl;
	}

public:
	T1 Name;
	T2 Age;
};

template <class T1, class T2>
void Print2(Person<T1, T2>& p)
{
	cout << p.Name << " " << p.Age << endl;
}


void test01()
{
	Person<string, int>p1("孙悟空", 999);
	Print1(p1);
	Print2<string, int>(p1);
	Print3(p1);
}


int main()
{
	test01();
	return 0;
}

输出结果

孙悟空 999
孙悟空 999
孙悟空 999

5.1.8类模板的应用

设计一个数组模板类(MyArray),完成对不同类型元素的管理

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <stdbool.h>
#include <iostream>
#include <string>
using namespace std;


template <class T>
class MyArray
{
public:

	//有参构造
	MyArray(int capacity)
	{
		this->Capacity = capacity;
		this->Size = 0;
		this->pAddress = new T[this->Capacity];
	}

	//拷贝构造
	MyArray(const MyArray& arr)
	{
		this->Capacity = arr.Capacity;
		this->Size = arr.Size;
		this->pAddress = arr.pAddress;
		for (int i = 0; i < this->Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//重载 [] 操作符
	T operator[](int index)
	{
		return this->pAddress[index];
	}

	//尾插
	void Push_Back(const T& val)
	{
		if (this->Capacity == this->Size)
		{
			return;
		}
		this->pAddress[this->Size] = val;
		this->Size++;
	}

	//尾删
	void Pop_back()
	{
		if(this->Size==0)
		{
			return;
		}
		this->Size--;
	}

	int getSize()
	{
		return this->Size;
	}

	~MyArray()
	{
		if (this->pAddress != NULL)
		{
			delete[]this->pAddress;
			this->pAddress = NULL;
			this->Capacity = 0;
			this->Size = 0;
		}
	}

private:
	T* pAddress;
	int Capacity;
	int Size;
};


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

public:
	string Name;
	int Age;
};


void PrintMyArrayInt(MyArray<int>& arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}


void PrintMyArrayPerson(MyArray<Person>& personArr)
{
	for (int i = 0; i < personArr.getSize(); i++)
	{
		cout << personArr[i].Name << " " << personArr[i].Age << endl;
	}
	cout << endl;
}


int main()
{
	MyArray<int>MyArrayInt(10);
	for (int i = 0; i < 9; i++)
	{
		MyArrayInt.Push_Back(i);
	}
	PrintMyArrayInt(MyArrayInt);

	MyArray<Person>MyArrayPerson(10);
	Person p1("德玛西亚", 30);
	Person p2("孙悟空", 20);
	Person p3("猪八戒", 10);
	MyArrayPerson.Push_Back(p1);
	MyArrayPerson.Push_Back(p2);
	MyArrayPerson.Push_Back(p3);
	PrintMyArrayPerson(MyArrayPerson);

	return 0;
}

输出结果

0 1 2 3 4 5 6 7 8
德玛西亚 30
孙悟空 20
猪八戒 10

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到