C++ 模板

目录

首先:让我们思考为什么要使用函数模板哪?

函数模板的基本语法:

例如我们通过函数模板实现数据的交换:

其中对于自动类型推导有以下注意事项:

函数模板案例—数组排序

 普通函数与函数模板的区别:

1、普通函数的调用可以发生隐式类型转换

​2、函数模板 当使用自动类型推导时 不可以发生隐式类型转换

​3、函数模板 当使用显示指定类型时 可以发生隐式类型转换

 普通函数与函数模板的调用规则:

1、如果函数模板和普通函数都可以实现 优先使用普通函数

 2、可以通过空模板参数列表来强制调用函数模板

 3、函数模板也可以发生重载

 4、如果函数模板可以更好的匹配 优先调用函数模板

模板的局限性

模板并不是万能的 有些特定的数据类型 需要具体化方式做特殊实现 例如我们在对两个类进行对比时

对比两个数据是否相等

类模板

类模板的使用规则

类模板与函数模板的区别

1、类模板没有自动类型推导使用方式

 2、类模板在模板参数列表中可以有默认参数

类模板中成员函数的调用时机

结论:类模板中的成员函数并不是一开始就创建的 而是在调用的时候才去创建

类模板对象做函数参数

1、指定传入类型

2、参数模板化

 3、整个类模板化

 类模板与继承

1、当子类继承的父类是一个模板时 子类在声明的时候 要指定出父类中T的了类型

2、如果不指定 编译器无法给予子类分配内存

3、如果想灵活指出父类中T的类型 子类也需变成类模板

类模板成员函数类外实现

类模板分文件编写

类模板与友元

全局函数 类内实现

全局函数 类外实现

类模板案列--数组类封装



 

首先:让我们思考为什么要使用函数模板哪?

例如一个简单的交换函数,在大的程序中一般会比较常用,如果你不使用函数模版,便要声明多个不同类型的交换函数。而使用一个模版函数就可以解决问题了,既免了功夫,也减少了代码的字数。

函数模板的基本语法:

template<typename T>

其中typname可以替换为class

该声明的意义在于声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型。

例如我们通过函数模板实现数据的交换:

void MySwap(T &a, T &b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

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

	//1、自动类型推导
	MySwap(a, b);
	cout << "a = " << a << endl;//20
	cout << "b = " << b << endl;//10

	//2、显示指定类型
	MySwap<int>(a, b);
	cout << "a = " << a << endl;//10
	cout << "b = " << b << endl;//20

}

其中对于自动类型推导有以下注意事项:

 a、自动类型推导 必须推导出一致的数据类型T才可以使用
 b、模板必须要确定出T的数据类型 才可以使用

函数模板案例—数组排序

实现对通用数组的排序
规则 从大到小
算法 选择排序
测试 char int 

#include<iostream>
using namespace std;

//利用函数模板实现对数据的交换
template<class T>
void MySwap(T&a, T&b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

template<class T>
void MySort(T Arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		int max = i;//假设最大值的下标为i
		for (int j = i + 1; j < sz; j++)
		{
			if (Arr[max] < Arr[j])//结果发现 遍历后下表j的数值比i的数值大
			{
				max = j;//更新最大值的下标
			}
		}
		if (max != i)
		{
			//交换max下标和i下标的元素
			MySwap(Arr[max], Arr[i]);
		}
	}
}

//char数组测试
void test1()
{
	char arr[] = "bcdage";
	int sz = sizeof(arr) / sizeof(arr[0]);
	MySort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		cout << " " << arr[i];

	}
}

//int数组测试
void test2()
{
	int arr[] = { 1, 2, 5, 6, 3, 7 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	MySort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		cout << " " << arr[i];

	}
}


int main()
{
	test1();
	test2();
	return 0;
}

 运行结果如下:

 

 普通函数与函数模板的区别:

1、普通函数的调用可以发生隐式类型转换

//普通函数
int Add01(int a, int b)
{
	return a + b;
}

void test01()
{
	int a = 10;
	int b = 20;
	char ch = 'a';
	cout << "a+b=" << Add01(a, b) << endl;
	cout << "a+ch=" << Add01(a, ch) << endl;
}


2、函数模板 当使用自动类型推导时 不可以发生隐式类型转换

//函数模板
template<class T>
T MyAdd(T a, T b)//这里不可以用引用的方式进行值传递
{
	return a + b;
}

//1、自动类型推导
void test02()
{
	int a = 10;
	int b = 20;
	char ch = 'a';
	cout << "a+b=" << MyAdd(a, b) << endl;
	cout << "a+ch=" << MyAdd(a, ch) << endl;//显示出现错误
}


3、函数模板 当使用显示指定类型时 可以发生隐式类型转换

void test03()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << "a+b=" << MyAdd<int>(a, b) << endl;
	cout << "a+ch=" << MyAdd<int>(a, c) << endl;
}

 普通函数与函数模板的调用规则:

1、如果函数模板和普通函数都可以实现 优先使用普通函数

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

template<class T>
void Print1(T a, T b)
{
	cout << "函数模板的调用"
}

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

 2、可以通过空模板参数列表来强制调用函数模板

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

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

void test02()
{
	int a = 10;
	int b = 20;
	Print2<>(a, b);
}

 3、函数模板也可以发生重载

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

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

void test03()
{
	int a = 10;
	int b = 20;
	Print3(a, b,10);
}

 4、如果函数模板可以更好的匹配 优先调用函数模板

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

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

void test04()
{
	char a = 'a';
	char b = 'b';
	Print4(a, b);

模板的局限性

模板并不是万能的 有些特定的数据类型 需要具体化方式做特殊实现 例如我们在对两个类进行对比时

对比两个数据是否相等

1、对比两个整数

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

void test01()
{
	int a = 10;
	int b = 20;
	bool ret = MyCompare(a, b);
	if (ret)
	{
		cout << "a == b" << endl;

	}
	else
	{
		cout << "a != b" << endl;
	}
}

2、对比两个Person类时

class Person
{
public:
	Person(string name, int age)
	{
		m_name = name; 
		m_age = age;
	}

	string m_name;
	int m_age;
};

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

 出现了以上问题,这是因为在对特定的数据类型 需要具体化方式做特殊实现

class Person
{
public:
	Person(string name, int age)
	{
		m_name = name; 
		m_age = age;
	}

	string m_name;
	int m_age;
};

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

//利用具体化Person的版本实现代码 具体化优先调用
template<> bool MyCompare(Person& P1, Person& P2)
{
	if (P1.m_age == P2.m_age && P1.m_name == P2.m_name)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//对比两个Person类
void test02()
{
	Person P1("Tom", 23);
	Person P2("Tom", 24);

	bool ret = MyCompare(P1, P2);
	if (ret)
	{
		cout << "P1 == P2" << endl;
	}
	else
	{
		cout << "P1 != P2" << endl;
	}
}

类模板

类模板的使用规则

template < class NameType, class AgeType >
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		m_name = name;
		m_age = age;
	}

	void Show()
	{
		cout << "name:" << this->m_name << " age:" << this->m_age << endl;
	}
	NameType m_name;
	AgeType m_age;
};

void test()
{
	Person<string, int> P1("Tom", 19);
	P1.Show();
}

int main()
{
	test();

	return 0;
}

类模板与函数模板的区别

1、类模板没有自动类型推导使用方式

 

 2、类模板在模板参数列表中可以有默认参数

类模板中成员函数的调用时机

结论:类模板中的成员函数并不是一开始就创建的 而是在调用的时候才去创建

类模板对象做函数参数

1、指定传入类型

template < class NameType, class AgeType >
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		m_name = name;
		m_age = age;
	}

	void Show()
	{
		cout << "name:" << this->m_name << " age:" << this->m_age << endl;
	}
	NameType m_name;
	AgeType m_age;
};
//1、指定传入类型
void PriPerson1(Person<string, int>&P)
{
	P.Show();
}

void test01()
{
	Person<string, int>P("张三", 23);
	PriPerson1(P);
}

2、参数模板化

template < class NameType, class AgeType >
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		m_name = name;
		m_age = age;
	}

	void Show()
	{
		cout << "name:" << this->m_name << " age:" << this->m_age << endl;
	}
	NameType m_name;
	AgeType m_age;
};


template < class NameType, class AgeType >
void PriPerson2(Person<NameType, AgeType>&P)
{
	P.Show();
	cout << "NameType的类型为:" << typeid(NameType).name() << endl;
	cout << "AgeType的类型为:" << typeid(AgeType).name() << endl;
}

void test02()
{
	Person<string, int>P("李四", 24);
	PriPerson2(P);
}

 3、整个类模板化

template < class NameType, class AgeType >
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		m_name = name;
		m_age = age;
	}

	void Show()
	{
		cout << "name:" << this->m_name << " age:" << this->m_age << endl;
	}
	NameType m_name;
	AgeType m_age;
};

//3、整个类模板化
template <class T>
void PriPerson3( T &P)
{
	P.Show();
	cout << "T的类型为:" << typeid(T).name() << endl;
}

void test03()
{
	Person<string, int>P("王五", 25);
	PriPerson3(P);
}

 类模板与继承

当类模板碰到继承时 需要注意一下三点:

1、当子类继承的父类是一个模板时 子类在声明的时候 要指定出父类中T的了类型

2、如果不指定 编译器无法给予子类分配内存

3、如果想灵活指出父类中T的类型 子类也需变成类模板

template<class T>
class Base
{
public:
	T t;
};

template <class T1,class T2>
class Son :public Base<T1>
{
public:
	Son()
	{
			cout << "T1的类型为:" << typeid(T1).name() << endl;
			cout << "T2的类型为:" << typeid(T2).name() << endl;
	}
	T2 t;
};

void test02()
{
	Son<int, char>s;
}

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

 

 

 

类模板成员函数类外实现

#include<iostream>
#include<string>
using namespace std;

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

	void show();

	T1 m_name;
	T2 m_age;
};

//构造函数的类外实现
template <class T1, class T2>
Person<T1, T2>::Person(string name, int age)
{
	this->m_age = age;
	this->m_name = name;
}

//成员函数的类外实现
template <class T1, class T2>
void Person<T1, T2>::show()
{
	cout << "姓名:" << this->m_name << endl;
	cout << "年龄:" << this->m_age << endl;
}

void test()
{
	Person<string,int> P1("张三", 23);
	P1.show();

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

类模板分文件编写

Person.h

#pragma once 
#include<iostream>
#include<string>
using namespace std;

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

	void show();

	T1 m_name;
	T2 m_age;
};

Person.cpp

#include "Person.h"

//构造函数的类外实现
template <class T1, class T2>
Person<T1, T2>::Person(string name, int age)
{
	this->m_age = age;
	this->m_name = name;
}

//成员函数的类外实现
template <class T1, class T2>
void Person<T1, T2>::show()
{
	cout << "姓名:" << this->m_name << endl;
	cout << "年龄:" << this->m_age << endl;
}

test.c

#include "Person.cpp"
	
void test()
{
	Person<string,int> P1("张三", 23);
	P1.show();

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

这时我们通过运行发现有两处错误:

 解决该问题的第一种方法:

1、在test.c中引用Person.cpp

 2、将函数的实现与声明统一放在Person.hpp中

Person.hpp

#pragma once 
#include<iostream>
#include<string>
using namespace std;

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

	void show();

	T1 m_name;
	T2 m_age;
};


//构造函数的类外实现
template <class T1, class T2>
Person<T1, T2>::Person(string name, int age)
{
	this->m_age = age;
	this->m_name = name;
}

//成员函数的类外实现
template <class T1, class T2>
void Person<T1, T2>::show()
{
	cout << "姓名:" << this->m_name << endl;
	cout << "年龄:" << this->m_age << endl;

在test.c中添加:

类模板与友元

全局函数 类内实现

通过添加关键字:friend

template <class T1, class T2>
class Person
{
	//1、全局函数 类内实现
	friend void show(Person<T1, T2>P)
	{
		cout << " 姓名: " << P.m_name << " 年龄:" << P.m_age << endl;
	}


public:
	Person(T1 name, T2 age)
	{
		this->m_age = age;
		this->m_name = name;
	}

private:
	T1 m_name;
	T2 m_age;
};



void test()
{
	Person<string,int> P1("张三", 23);
	show(P1);

}

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

全局函数 类外实现

1、加空模板参数列表

2、如果全局函数 是类外实现 需要让编译器提前知道这个函数的存在

//提前让编译器知道Person类存在
template <class T1, class T2>
class Person;

//类外实现
template <class T1, class T2>
void show(Person<T1, T2>P)
{
	cout << " 姓名: " << P.m_name << " 年龄:" << P.m_age << endl;
}

template <class T1, class T2>
class Person
{

	friend void show<>(Person<T1, T2>P);

public:
	Person(T1 name, T2 age)
	{
		this->m_age = age;
		this->m_name = name;
	}

private:
	T1 m_name;
	T2 m_age;
};



void test()
{
	Person<string,int> P1("张三", 23);
	show(P1);

}

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

类模板案列--数组类封装

1、可以对内置数据类型以及自定义数据类型的数据进行存储
2、将数组中的数据存储到堆区
3、构造函数中可以传入数组的容量

//有参构造 参数 容量
	MyArray(int capacity)
	{
		/*cout << "有参构造函数的调用" << endl;*/
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
	}


4、提供对应的拷贝构造函数以及operator=防止浅拷贝问题

//拷贝构造
	MyArray(const MyArray& arr)
	{
		/*cout << "拷贝构造函数的调用" << endl;*/
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;

		//深拷贝
		this->pAddress = new T[arr.m_Capacity];

		//将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = this->pAddress[i];
		}
	}

	//operator = 防止浅拷贝问题
	MyArray& operator=(const MyArray& arr)
	{
		/*cout << "operator = 函数的调用" << endl;*/
		//先判断原来堆区是否有数据 如果有显示放
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}

		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];

		//将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = this->pAddress[i];
		}

		return *this;
	}


5、提供尾插法和尾删法对数组中的数据进行增加和删除

//尾插法
	void Push_Back(const T& val)
	{
		//判断容量是否满了
		if (this->m_Capacity == this->m_Size)
		{
			return;
		}
		this->pAddress[this->m_Size] = val;
		this->m_Size++;//更新数组的大小
	}

	//尾删法
	void Pop_Back()
	{
		if (this->m_Size == 0)
		{
			return;
		}
		this->m_Size--;//让用户访问不到最后一个元素 逻辑删除
	}


6、可以通过下标的方式访问数组中的元素

//通过下标方式访问数组中的元素 
	T& operator[](int index)
	{
		return this->pAddress[index];
	}


7、可以获取数组中当前元素个数和数组的容量

//返回数组的容量
	int GetCapacity()
	{
		return this->m_Capacity;
	}

	//返回数组的大小
	int GetSize()
	{
		return this->m_Size;
	}

析构函数:

~MyArray()
	{
		/*cout << "析构函数的调用" << endl;*/
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
		}
	}

完整MyArray.h

#pragma once
#include<iostream>
#include<string>
using namespace std;

template<class T>
class MyArray
{
public:
	//有参构造 参数 容量
	MyArray(int capacity)
	{
		/*cout << "有参构造函数的调用" << endl;*/
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
	}
	
	//拷贝构造
	MyArray(const MyArray& arr)
	{
		/*cout << "拷贝构造函数的调用" << endl;*/
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;

		//深拷贝
		this->pAddress = new T[arr.m_Capacity];

		//将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = this->pAddress[i];
		}
	}

	//operator = 防止浅拷贝问题
	MyArray& operator=(const MyArray& arr)
	{
		/*cout << "operator = 函数的调用" << endl;*/
		//先判断原来堆区是否有数据 如果有显示放
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}

		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];

		//将arr中的数据都拷贝过来
		for (int i = 0; i < this->m_Size; i++)
		{
			this->pAddress[i] = this->pAddress[i];
		}

		return *this;
	}

	//尾插法
	void Push_Back(const T& val)
	{
		//判断容量是否满了
		if (this->m_Capacity == this->m_Size)
		{
			return;
		}
		this->pAddress[this->m_Size] = val;
		this->m_Size++;//更新数组的大小
	}

	//尾删法
	void Pop_Back()
	{
		if (this->m_Size == 0)
		{
			return;
		}
		this->m_Size--;//让用户访问不到最后一个元素 逻辑删除
	}

	//通过下标方式访问数组中的元素 
	T& operator[](int index)
	{
		return this->pAddress[index];
	}

	//返回数组的容量
	int GetCapacity()
	{
		return this->m_Capacity;
	}

	//返回数组的大小
	int GetSize()
	{
		return this->m_Size;
	}

	//析构函数
	~MyArray()
	{
		/*cout << "析构函数的调用" << endl;*/
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
		}
	}
private:

	T* pAddress;//指针指向堆区开辟的真实数组

	int m_Capacity;//数组容量

	int m_Size;//数组大小
};

test.c

#include <iostream>
#include"MyArray.h"
using namespace std;


void test()
{
	MyArray<int> arr1(6);
	MyArray<int> arr2(arr1);
	MyArray<int> arr3(100);
	arr3 = arr1;

}

int main()
{
	test();

	return 0;
}

测试运行结果: 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学代码的小呆鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值