分享

C++...

 喜欢站在山上 2022-04-11

泛型编程

模板就是建立通用的模具,大大提高复用性。

  • 特点:模板不可以直接使用,它只是一个框架,它的通用并不是万能的
  • 分类:C++提供两种模板机制函数模板类模板

基础模板的语法

函数模板的作用:建立一个通用函数,其函数返回值类型形参类型可以不具体指定,用一个虚拟类型代表

#include <iostream>
using namespace std;

//针对如下情形
void chang_1(int &a,int &b)
{
	int temp = a;
	a = b;
	b = temp;
}
void chang_2(double &a, double &b)
{
	double temp = a;
	a = b;
	b = temp;
}

//采用模板完成上面的数据交换操作
template<typename T>//声明一个模板,告诉编译器紧跟着的T不要报错。typname可以替换成class
//template<class T>
void mychang(T &a,T &b)
{
	T temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a = 10;
	int b = 100;

	//模板:自动推导方式,编译器自己推导出这个类型
	mychang(a, b);//
	//模板:直接告诉方式,直接告诉编译器这个T是什么类型
	mychang<int>(a, b);

	//模板:
	system("pause");
	return 0;
}

其中:typname 可以替换成 class
template 会直接影响这句话紧跟着的函数,使其编程模板

注意事项

(1) 自动类型推导,必须推导出一致的数据类型T,才能使用;
(2) 模板必须要确定出 T 的数据类型,才可以使用;

template<class T>
void fun()
{
	cout <<"模板输出" << endl;
}
int main()
{
	//fun();//错误
	fun<int>();//随便指定T为一个类型
}

模板案例——数组排序

//模板案例——排序
//1、利用函数模板封装一个排序函数,可以对不同数据类型数组进行排序
//2、排序规则从大到小,排序算法为选择排序
//3、分别利用的 char 数组 与 int 数组进行测试
namespace test
{
	//交互函数模板
	template<typename T>
	void change_(T &a,T &b)
	{
		T temp = a;
		a = b;
		b = temp;
	}
	//打印数组函数模板
	template<typename T> 
	void printArray(T arr[],int len)
	{
		for (size_t i = 0; i < len; i++)
		{
			cout << arr[i]<< " ";
		}
		cout << endl;
	}

	//排序模板
	template<typename T>
	void follow(T arr[],int len)
	{
		for (size_t i = 0; i < len-1; i++)
		{
			int max = i;
			for (size_t j = i+1; j < len; j++)
			{
				if (arr[max]<=arr[j])
				{
					change_(arr[max] , arr[j]);
				}
			}
		}
	}
}

int main()
{
	//模板:
	char charArray[] = "abcidu";
	int intArray[] = {1,23,34,56,7,8,98};

	int num_char = sizeof(charArray) / sizeof(char);
	int num_int = sizeof(intArray) / sizeof(int);

	test::follow(charArray, num_char);
	test::printArray(charArray, num_char);

	test::follow(intArray, num_int);
	test::printArray(intArray, num_int);

	system("pause");
	return 0;
}

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

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 函数模板调用时,如果利用显示指定类型方式,可以发生隐式类型转换
    所谓隐式类型转换,即编译器自动将char类型变量使用ASCII码形式进行操作
template<typename T>
void xxx(T a,T b){ }
  • (普通函数 和 xxx(a,c)可以,xxx(a,c)自动推导就不行)

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

  • (优先级)如果函数模板和普通函数都可以实现,优先调用普通函数
namespace test_1
{
	void printmy(int a)
	{
		cout << "普通函数" << endl;
	}
	template<typename T>
	void printmy(T a)
	{
		cout << "模板函数" << endl;
	}
}
int main()
{
	int a = 10;
	test_1::printmy(a);//优先代用普通函数
	system("pause");
	return 0;
}

输出:普通函数

  • (强制调用)可以通过空模板参数列表强制调用函数模板
//承接上面,强制调用模板函数的方法
test_1::printmy<>(a);
  • (模板函数重载)函数模板也可以发生重载
namespace test_1
{
	void printmy(int a)
	{
		cout << "普通函数" << endl;
	}

	template<typename T>
	void printmy(T a)
	{
		cout << "模板函数" << endl;
	}

	template<typename T>
	void printmy(T a ,T b)
	{
		cout << "重载模板函数" << endl;
	}
}
int main()
{
	int a = 10;
	
	test_1::printmy(a);//默认调用普通函数
	test_1::printmy<>(a);//强制调用模板函数
	test_1::printmy(a, 10);//调用重载函数

	system("pause");
	return 0;
}

  • 如果函数模板可以产生更好的匹配,优先调用函数模板
//承接上面
char b = 'x';
test_1::printmy(b);//调用模板,因为普通函数需要隐式数据转换

模板的局限性

模板并非万能:

  • 如果在给模板传入的参数是数组,就无法实现了
  • 如果传入的数据类型为自定义的数据类型(如自定义类),模板也无法实现

针对上述问题,模板提供的解决方法是:

  • 提供模板重载,可以为这些特定的类型提供具体化的模板
namespace test_2
{
	class Animal
	{
	public:
		Animal(string name, int age) 
		{
			this->age_M = age;
			this->name_M = name;
		}
		string name_M;
		int age_M;
	};
	
	template<typename T>
	void campare_animal(T &a,T &b)
	{
		if (a == b)
		{
			cout << "岁数相等" << endl;
		}
		else
		{
			cout<<"岁数不等"<<endl;
		}
	}

	template<> void campare_animal(Animal &a1, Animal &b1)
	{
		if (a1.age_M==b1.age_M)
		{
			cout << "岁数相等" << endl;
		}
		else
		{
			cout << "岁数不等" << endl;
		}
		
	}

	void Teee()
	{
		Animal a("cat", 10);
		Animal b("dog", 100);

		campare_animal(a, b);//这样做编译器默认重载函数

	}

}

int main()
{
	int a = 10;
	test_2::Teee();
	system("pause");
	return 0;
}

类模板的基本语法

类模板的作用:
即:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表

template<typename T>
类
namespace test_05//类模板语句
{
	template<typename NameType,typename AgeType>//在类中用到了多少种不同类型的变量,就定义多少个模板
	class Animal
	{
	public:
		Animal(NameType a, AgeType b)
		{
			this->age_A = b;
			this->name_A = a;
		}
		NameType name_A;
		AgeType age_A;
	
		void test()
		{
			cout << "姓名:" << this->name_A << " 年龄:" << this->age_A << endl;
		}
	
	};

	void test_1()
	{
		Animal<string, int>A_M("cat", 10);
		A_M.test();
	}
}
int main()
{
	test_05::test_1();
	system("pause");
	return 0;
}

类模板与函数模板的区别

  • 类模板没有自动类型推导的使用方式
template<typename NameType,typename AgeType>//在类中用到了多少种不同类型的变量,就定义多少个模板
	class Animal
	{
	public:
		Animal(NameType a, AgeType b)
		{
			this->age_A = b;
			this->name_A = a;
		}
		NameType name_A;
		AgeType age_A;
	
		void test()
		{
			cout << "姓名:" << this->name_A << " 年龄:" << this->age_A << endl;
		}
	
	};

	void test_1()
	{
		//Animal A_M("cat",10);错误!!!!!!!
		Animal<string, int>A_M("cat", 10);
		A_M.test();
	}
  • 类模板在模板参数列表中可以有默认参数,(注意函数模板不能用,只有类模板才能用)
template<typename NameType,typename AgeType = int>// 默认整形int
	class Animal
	{
	public:
		Animal(NameType a, AgeType b)
		{
			this->age_A = b;
			this->name_A = a;
		}
		NameType name_A;
		AgeType age_A;
	
		void test()
		{
			cout << "姓名:" << this->name_A << " 年龄:" << this->age_A << endl;
		}
	
	};

	void test_1()
	{
		Animal<string>A_M("cat", 10);//此处的模板列表就可以不用再写 int 了 
		A_M.test();
	}

类模板中成员函数创建时机

  • 普通类中的成员函数一开始就可以创建;
  • 类模板中的成员函数在调用的时候才会被创建;
class Person1
	{
	public:
		void P_test1()
		{
			cout <<"person1" <<endl;
		}
	};

class Person2
	{
	public:
		void P_test2()
		{
			cout << "person2" << endl;
		}
	};

	template<typename T>//在写代码时是不报错
	class test_class
	{
	public:
		T obj;
		void test_1()
		{
			obj.P_test1();
		}
		void test_2()
		{
			obj.P_test2();
		}
	};

	void test_A()
	{
		test_class<Person1>d;//规定了T的类型
		d.test_1();
		//d.test_2();//Person1中没有test_2的成员函数故报错
	}
int main()
{
	test_A();
	system("pause");
	return 0;
}

如上图所示,在编写程序时编译器是不会报错的,即调用之前不会创建,只会在调用时进行创建

类模板对象作函数参数

即:类模板实例化出的对象,向函数传参的方式
三种:

  • 指定传入的类型 —— 直接显示对象的数据类型(最常用)
void printAnimal(Animal<string, int> &D)
	{
		cout << "指定传入类型" << D.name_A << endl;
	}
  • 参数模板化 —— 将对象中的参数变为模板进行传递
template<typename T1,typename T2>
	void printAnimal2(Animal<T1,T2> &D)
	{
		cout <<"参数模板化" <<D.name_A<<endl;
		cout << "T1类型" << typeid(T1).name() << endl;//查看模板代表的类型
		cout << "T2类型" << typeid(T2).name() << endl;
	}
  • 整个类模板化 —— 将这个对象类型 模板化 进行传递
template<typename T>
	void printAnimal3(T &p)
	{
		cout << "整个类模板化"<< endl;
		cout << "T" << typeid(T).name() << endl;
	}

整体测试:

namespace test_06
{
	//类模板对象做函数参数
	template<typename T1,typename T2>
	class Animal
	{
	public:
		Animal(T1 a, T2 b)
		{
			this->name_A = a;
			this->age_A = b;
		}
		T1 name_A;
		T2 age_A;
	};
	//1、指定传入类型
	void printAnimal(Animal<string, int> &D)
	{
		cout << "指定传入类型" << D.name_A << endl;
		
	}
	//2、参数模板化
	template<typename T1,typename T2>
	void printAnimal2(Animal<T1,T2> &D)
	{
		cout <<"参数模板化" <<D.name_A<<endl;
		cout << "T1类型" << typeid(T1).name() << endl;//查看模板代表的类型
		cout << "T2类型" << typeid(T2).name() << endl;
	}

	//3、整个类模板化
	template<typename T>
	void printAnimal3(T &p)
	{
		cout << "整个类模板化"<< endl;
		cout << "T" << typeid(T).name() << endl;
	}

	void test_AAA()
	{
		Animal<string, int> A("cat",10);
		printAnimal(A);
		printAnimal2(A);
		printAnimal3(A);
	}
}

int main()
{
	test_06::test_AAA(); 
	system("pause");
	return 0;
}

输出
指定传入类型cat
参数模板化cat
T1类型class std::basic_string<char,struct std::char_traits,class std::allocator >
T2类型int
整个类模板化
Tclass test_06::Animal<class std::basic_string<char,struct std::char_traits,class std::allocator >,int>

类模板的继承

  • 当子类继承的父类是一类模板时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
template<typename T>
	class father
	{
	public:
		T M_A;
	};
	//class Son :public father//错误,不指定T类型,子类无法知道父类中的T应该分配多少内存
	//{
	//};
	class Son :public father<int>
	{

	};
  • 如果想灵活指定出父类中 T 的类型,子类也需变为模板
	template<typename T>
	class father
	{
	public:
		T M_A;
	};

	template<typename T1,typename T2>//使用模板继承,这样就在实例化时才进行内存分配
	class Son2 :public father<T2>
	{
	public:
		T1 M_B;
	};

	void test()
	{
		Son2<string, int>X;//指定了T2为int
	}

类模板成员函数的类外实现

template<typename T1,typename T2>
	class Animal
	{
	public:
		Animal(T1 &a,T2 &b);

		void test1();

		T1 M_A;
		T2 M_B;

	};

	//构造函数模板的类外实现
	template<typename T1,typename T2>
	Animal<T1, T2>::Animal(T1 &a, T2 &b)
	{
		this->M_A = a;
		this->M_B = b;
	}
	//成员函数模板的类外实现
	template<typename T1,typename T2>
	void Animal<T1, T2>::test1()
	{
	
	}
	

类模板分文件编写(跨文件编写)

  • 问题:类模板中成员函数创建时机是在调用阶段,但是分别文件编写时链接不到原来的类
  • 解决 —1:直接包含.cpp源文件
    Animal.h
#pragma once
#include <iostream>
using namespace std;
template<typename TA1,typename TA2>
class Animal
{
public:
	Animal(TA1 &a, TA2 &b);
	void test_A();
	TA1 M_A;
	TA2 M_B;
};

Animal.cpp

#include"Animal.h"
template<typename TA1,typename TA2>
Animal<TA1, TA2>::Animal(TA1 &a,TA2 &b)
{
	this->M_A = a;
	this->M_B = b;
}

template<typename TA1, typename TA2>
void Animal<TA1, TA2>::test_A()
{
	cout << this->M_A << "  " << this->M_B << endl;
}

泛型编程_模板.cpp

#include "Animal.cpp"//模板需要包含.cpp
int main()
{
	int a = 10;
	string name_A = "AAA";
	Animal<string, int>A(name_A, a);
	A.test_A();
	system("pause");
	return 0;
}
  • 解决 — 2(更常用):将声明和实现写在同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,不是强制
    创建一个.hpp文件专门用于存储类模板及其成员函数定义,在.hpp
#pragma once
#include <iostream>
using namespace std;
template<typename TA1,typename TA2>
class Animal
{
public:
	Animal(TA1 &a, TA2 &b);

	void test_A();

	TA1 M_A;
	TA2 M_B;

};

template<typename TA1, typename TA2>
Animal<TA1, TA2>::Animal(TA1 &a, TA2 &b)
{
	this->M_A = a;
	this->M_B = b;
}

template<typename TA1, typename TA2>
void Animal<TA1, TA2>::test_A()
{
	cout << this->M_A << "  " << this->M_B << endl;
}

泛型编程_模板.cpp

#include "Animal.hpp"//模板需要包含.hpp
int main()
{
	int a = 10;
	string name_A = "AAA";
	Animal<string, int>A(name_A, a);
	A.test_A();
	system("pause");
	return 0;
}

类模板与友元(类外与类内)

  • 全局函数类内实现:直接在类中声明友元(相对简单)
namespace test_08
{
	template<typename T1,typename T2>
	class Animal
	{
		friend void printAnimal(Animal<T1, T2> &a) //全局函数友元函数的类内实现
		{
			cout << " " << a.M_A<< " " <<a.M_B << endl;
		}

	public:
		Animal(T1 a, T2 b)
		{
			this->M_A = a;
			this->M_B = b;
		}
		T1 M_A;
		T2 M_B;

	};

	void test()
	{
		Animal<string, int>A("Derek", 10);
		printAnimal(A);

	}
	
}

int main()
{
	test_08::test();
	system("pause");
	return 0;
}
  • 全局函数类外实现:需要提前是让编译器知道全局函数的存在(很复杂)
namespace test_09
{
	template<typename T1,typename T2>//先告诉编译器有Animal模板存在
	class Animal;

	template<typename T1,typename T2>
	void printAnimal(Animal<T1, T2> a)//再告诉编译器全局函数的定义,这个必须放在友元声明前
	{
		cout << " " << a.M_A << " " << a.M_B << endl;
	}

	template<typename T1, typename T2>//再进行类模板的内部定义
	class Animal
	{
		//全局函数内外实现,需要加空模板列表
		friend void printAnimal<>(Animal<T1, T2> a);//在内部说明友元声明,
		
	public:
		Animal(T1 a, T2 b)
		{
			this->M_A = a;
			this->M_B = b;
		}
		T1 M_A;
		T2 M_B;

	};

	void test()
	{
		Animal<string, int>A("Derek", 10);
		printAnimal(A);

	}
}

int main()
{
	test_09::test();
	system("pause");
	return 0;
}

案例——数组类封装:

在这里插入图片描述
功能:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储;
  • 将数组中的数据存储到堆区;
  • 构造函数中可以传入数组的容量;
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝;
  • 提供尾插法和尾删法对数据组中的数据进行增加与删除;
  • 可以通过下标的方式访问数组中的元素;
  • 可以获取数组中当前元素个数和数组的容量;

Animal.hpp (预定俗成的用于写类模板)

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

//1、初始化数组类
//2、可以进行类的拷贝;(防止浅拷贝(即堆区拷贝释放时出现重复释放的情况))
//3、可以使用=进行拷贝;(防止浅拷贝)

template<typename T>
class _Array_
{
	friend ostream& operator<<(ostream &cout, _Array_<int> &X);

public:
	_Array_(const int &capacity)//初始化构造
	{
		this->m_Capacity = capacity;//初始化时 确定数组容量
		this->m_Size = 0;//初始化数组内元素个数为0
		this->m_address = new T[this->m_Capacity];//容量来决定堆区,如果在使用自定义类型初始化模板时,这里必须使用默认构造进行初始化

		cout << "普通构造" << endl;
	}

	_Array_(const _Array_ &Array)//拷贝构造
	{
		this->m_Size = Array.m_Size;
		this->m_Capacity = Array.m_Capacity;
		this->m_address = new T[Array.m_Capacity];//深拷贝操作

		for (size_t i = 0; i < Array.m_Size; i++)
		{
			this->m_address[i] = Array.m_address[i];//拷贝每一个元素
		}
		
		cout << "拷贝构造" << endl;
	}
	
	_Array_& operator=(const _Array_ &Array)//_Array_ a ;_Array_ b ; b = a
	{
		if (this->m_address != NULL)//堆区不为空
		{
			delete[] m_address;
			this->m_address = NULL;
		}

		this->m_Capacity = Array.m_Capacity;
		this->m_Size = Array.m_Size;
		this->m_address = new T[Array.m_Capacity];//深拷贝操作

		for (size_t i = 0; i < Array.m_Size; i++)
		{
			this->m_address[i] = Array.m_address[i];
		}

		cout << "重载=" << endl;

		return *this;
	}

	~_Array_()
	{
		if (this->m_address!=NULL)//堆区不为空
		{
			delete[] this->m_address;
			this->m_address = NULL;
		}
	}

	//下标访问
	T& operator[](const int index)//传入的是下标
	{
		return this->m_address[index];
	}

	//尾插法
	void push_back(const T &someone)
	{
		if (this->m_Size < this->m_Capacity)//大小还没超过容量大小的
		{
			this->m_address[this->m_Size] = someone;
		}
		else
		{
			cout << "容量已满"<<endl;
			return;
		}
		this->m_Size++;
	}
	//尾删法
	void pop_back()//逻辑删除
	{
		if (this->m_Size == 0)
		{
			return;
		}
		else
		{
			this->m_Size--;
		}
	}
	//获取大小
	int getSize()
	{
		return this->m_Size;
	}
	//获取容量
	int getCapacity()
	{
		return this->m_Capacity;
	}


private:
	T *m_address;//用这个来管理数组堆区

	int m_Size;//用于存储数组当前的大小

	int m_Capacity;//用于存储数组的整个容量

};

泛型编程_模板.cpp
注意:如果在使用自定义类型初始化模板时,默认构造必须要

#include"_Array_.hpp"
#include<iostream>
#include<string>
using namespace std;

ostream& operator<<(ostream &cout ,_Array_<int> &X)
{
	for (size_t i = 0; i < X.getSize(); i++)
	{
		cout << X.m_address[i]<< endl;
	}
	return cout;
}

class Animal
{
public:
	Animal(){};//如果在使用自定义类型初始化模板时,默认构造必须要,
	Animal(string n, int a)
	{
		this->name_A = n;
		this->age_A = a;
	}
	string name_A;
	int age_A;
};

void test()
{
	_Array_<Animal> A(3);

	Animal A1("cat", 10);

	Animal A2("dog", 10);

	Animal A3("bird", 10);

	A.push_back(A1);
	A.push_back(A2);
	A.push_back(A3);

	for (size_t i = 0; i < A.getSize(); i++)
	{
		cout << A[i].name_A<<" : "<<A[i].age_A<< endl;//这里使用重载[]进行操作
	}
}

int main()
{
	//_Array_<string>P(10);//初始化

	//_Array_<string> P2(P);//拷贝构造

	//P2.operator=(P);

	//_Array_<int> P3(10);

	//cout << "尾插法:" << endl;

	//for (int i = 0; i < 10; i++)
	//{
	//	P3.push_back(i);
	//}
	//cout << P3 << endl;;
	//cout << "大小:" << P3.getSize() << endl;
	//cout << "容量:" << P3.getCapacity() << endl;

	//cout << "尾删法:" << endl;
	//P3.pop_back();
	//cout << P3 << endl;

	//cout << "大小:" << P3.getSize() << endl;
	//cout << "容量:" << P3.getCapacity() << endl;

	test();//自定义类型测试

	system("pause");
	return 0;
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多