c++学习18模板

概念

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

特点

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

C++的另一种编程思想称为泛型编程,主要运用的技术就是模板
C++提供两种模板机制:函数模板和类模板

函数模板

语法

template <typename T>
函数声明或定义

解释:
template ---- 声明创建模板
typename ----表明其后面跟着的是一个数据类型,可以用class代替
T ----通用的数据类型,名称可以替换,通常为大写字母

作用

建立一个通用函数,其函数返回值类型和形参类型可以不做具体的制定,用一个虚拟的类型来代表

使用函数模板

关于函数模板的使用可以分为,自动推导(不传入数据类型,由编译器自动识别)和指定类型
关于指定类型的函数模板语法

函数名 <指定的数据类型>(参数列表);

案例代码:

#include<iostream>
using namespace std;

//声明要创建模板
template <typename T> //T表示任意的数据类型,可以替换成其他字母

void mySwap(T& a, T& b) {
	T & temp = a;
	a = b;
	b = temp;
}

void test01() {
	int a = 3;
	int c = 2;
	double b = 3.14;
	//1.自动类型推导
	//mySwap(a,c);
	//2.指定类型 <>里面指定的是T
	mySwap<int>(a, c);
	cout << "a=" << a << endl;
	cout << "c=" << c << endl;
}
int main() {

	system("pause");
	return 0;
}

目的:提高复用性,将类型参数化

函数模板使用的注意事项

  1. 自动类型推断必须推导至同一数据类型T才可以使用
  2. 模板必须要确定出T的数据类型才可以使用

如果使用的函数模板,并没有需要使用数据类型的地方,则可以直接指定T的数据类型,可以防止在声明是模板时却没有数据类型的参与导致的错误,不指定则直接在使用时报错。

函数模板案例

案件描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则同大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行测试
#include <iostream>
using namespace std;
//-利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
//- 排序规则同大到小,排序算法为选择排序
//- 分别利用char数组和int数组进行测试
template<typename T>
//交换模板
void mySwap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

//排序模板
template<typename T>
void mySort(T myarry[],int len) {
	for (int i = 0;i < len;i++) {
		int max = i;//认定最大值的下标
		for (int j = i + 1;j < len; j++) {
			//认定最大值的下标值更小,说明j下标的才是最大值
			if (myarry[max] < myarry[j]) {
				max = j;
			}
		}
		if (max != i) {
			//如果下标不相等,直接交换max和i下的元素,这样i的位置始终是剩余元素中最大的
			mySwap(myarry[max], myarry[i]);
		}
	}
}

//打印数组模板
template<typename T>
void printArry(T arry[],int len) {
	for (int i = 0; i < len;i++) {
		cout << arry[i] << " ";
	}
	cout << endl;
}

void test01() {
	char cArry[] = "abdfagwhu";
	int len1 = sizeof(cArry)/sizeof(char);
	mySort(cArry, len1);
	printArry(cArry,len1);
	int iArry[] = { 3,23,45,1,4,49,10,4 };
	int len2 = sizeof(iArry) /sizeof(int);
	mySort(iArry, len2);
	printArry(iArry, len2);

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

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

  • 普通函数调用时,可以自动发生类型转换,隐式类型转换
  • 函数模板调用时,如果使用自动类型推导,不会发生隐式类型转换
  • 函数模板调用时,如果用指定类型的方式,可以发生隐式类型转换

建议使用函数模板时,用显示指定函数类型的方式去调用

普通函数和函数模板调用规则
  1. 如果函数模板和普通函数都可以实现,优先调用普通函数
  2. 可以通过空模板参数列表,强制调用函数模板
  3. 函数模板可以发生函数重载
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板
模板的局限性

模板的通用性并不是万能的
有些特殊的数据类型,需要用具体化的方式实现

#include<iostream>
using namespace std;
//函数模板的局限性
//对比两个数据是否相等
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<> bool myCompare(Person& p1, Person& p2){
	if ((p1.m_Name == p2.m_Name) && (p1.m_Age == p2.m_Age)) {
		return true;
	}
	else {
		return false;
	}
}

void test01() {
	Person p1("张大", 17);
	Person p2("张大", 17);
	Person p3("张大", 18);
	cout << myCompare(p1, p2) << endl;
	cout << myCompare(p1, p3) << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

类模板

类模板作用

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

语法

template <typename T>

解释:
和函数模板一样

#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;
	}

	NameType m_Name;
	AgeType m_Age;
};

void test01() {
	Person<long,int> p1(12538, 14);
	Person<string,string> p2("肖晓", "二八年华");
	cout << "p1的姓名为:" << p1.m_Name << "年龄为 " << p1.m_Age << endl;
	cout << "p2的姓名为:" << p2.m_Name << "年龄为 " << p2.m_Age << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

类模板和函数模板的区别

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数
#include<iostream>
using namespace std;
//类模板
//类模板在函数参数列表中可以有默认参数
template<class NameType,class AgeType = int>
class Person {
public:
	Person(NameType name, AgeType  age ) {
		this->m_Name = name;
		this->m_Age = age;
	}
	NameType m_Name;
	AgeType m_Age;
};

void test01() {
	Person<long> p1(12538, 14);
	Person<string> p2("肖晓", 15);
	cout << "p1的姓名为:" << p1.m_Name << "年龄为 " << p1.m_Age << endl;
	cout << "p2的姓名为:" << p2.m_Name << "年龄为 " << p2.m_Age << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

类模板中成员函数创建实际

类模板中的成员函数,并不是一开始就创建的,而是在模板调用后生成的

#include<iostream>
using namespace std;

class Person1 {
public:
	void showPerson1() {
		cout<< "我是Person1的方法" << endl;
	}
};
class Person2 {
public:
	void showPerson2() {
		cout << "我是Person2的方法" << endl;
	}
};

template<class P>
class Person {
public:
	P person;
	//类模板中的成员函数
	void fun1() {
		person.showPerson1();
	}
	void fun2() {
		person.showPerson2();
	}

};
void test01() {
	Person<Person1> p;
	p.fun1();
	//p.fun2(); //报错 "showPerson2":不是"Person1"的成员
}
int main() {
	test01();
	system("pause");
	return 0;
}

在上述代码中,在Person类中,person.showPerson2();并没有报错,但在test01()中,通过类模板创建p对象,p对象去调用fun2时,由于p的数据类型Person1,而fun2中调用了一个Person1中没有的成员,虽然代码并没有报错,但编译的时候直接报错。
上述说明了类模板中的成员函数并不是一开始就创建的,而是在我们调用模板时才创建。

类模板对象做函数参数

类模板示例化出的对象,向函数传参的方式

  1. 指定传入的类型 ----直接显示对象的类型
  2. 参数模板化 -----将对象中的参数变为模板传递
  3. 整个类模板化 ----将这个对象模板化传递数据

类模板与继承

当类模板碰到继承时,需要注意以下几点:

  1. 当子类继承的父类是一个类模板时,子类再声明时,要指定父类T的数据类型
  2. 如果不指定,编译器无法给子类分配内存
  3. 如果想灵活指定出父类中T的类型,子类也需要变成类模板

类模板分文件书写

由于类模板中成员创建时期是在调用阶段,导致分文件时链接不到:

  • 解决方案1:
    普通类的分文件书写,只用分为.h和.cpp文件,在引用时,引用.h文件。
    类模板不同,类模板如果采取类似普通类的写法,则需在引用时,引用.cpp文件(且此方法不建议使用)
  • 解决方案2
    类模板分文件书写,将函数声明和实现写到一起,文件的后缀改为.hpp
    使用时,引用.hpp文件即可

类模板与友元

全局函数类内实现,直接类内声明友元即可
全局函数类外实现,需让编译器提前知道全局函数的存在,在声明友元时需要加上<>表示类模板的空实现,在函数定义时,需在其上方有模板函数的标识

类模板案例

案例说明

实现一个通用的数组类,要求如下:

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

MyArry.hpp

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

template<class T>
class MyArry
{

public:
	MyArry(int capacity) {
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->myAddress = new T[this->m_Capacity];

	}

	//拷贝构造
	MyArry(const MyArry& arr) {
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;

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

		//如果原有数组不为空,还需要将原本的数组元素拷贝过来
		if (arr.myAddress != NULL) {
			for (int i = 0;i < arr.m_Size;i++) {
				this->myAddress[i] = arr.myAddress[i];
			}
		}
	}

	//operator= 防止浅拷贝问题
	MyArry& operator=(const MyArry&arr) {
		//先判断原来的堆区是否有数组,有就释放
		if (this->myAddress != NULL) {
			delete[] this->myAddress;
			this->myAddress = NULL;
			this->m_Capacity = NULL;
			this->m_Size = 0;
		}
		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->myAddress = new T[arr.m_Capacity];
		for (int i=0;i < arr.m_Size;i++) {
			this->myAddress[i] = arr.myAddress[i];
		}
		return *this;
	}

	//尾插法
	void Push_Back(const T&val) {
		//判断数组是否还有空间进行插入,没有就直接返回
		if (this->m_Capacity == this->m_Size) {
			cout << "你的数组没有空间了" << endl;
			return;
		}
		this->myAddress[this->m_Size] = val; //让数组末尾加一个位置,并指向我们插入的val
		this->m_Size++; //更新数组元素个数

	}

	//尾删法
	void Pop_Back() {
		//让用户访问不到最后一个元素即为逻辑上的删除
		//如果数组大小为0,就直接返回
		if (this->m_Size == 0) {
			return;
		}
		//如果数组不为0,直接让数组元素个数-1即可
		this->m_Size--;
	}

	//通过下标的方式,访问数组的元素
	T& operator[](int index) {
		return this->myAddress[index];
	}
	//返回数组容量
	int getmCapacity() {
		return this->m_Capacity;
	}
	//返回数组大小
	int getmSize() {
		return this->m_Size;
	}
	~MyArry() {
		if (this->myAddress != NULL) {
			delete[] this->myAddress;
			this->myAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
	}

private:
	T* myAddress;//指针要指向堆区开辟的真实数组
	int m_Capacity; //数组容量
	int m_Size;   //数组大小

};

数组封装.cpp

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

//测试内置数据类型
void test01() {
	MyArry<int>arr1(5);
	
	/*MyArry<int>arr3(100);
	arr3 = arr1;*/

	for (int i = 0;i < 5;i++) {
		arr1.Push_Back(i);
	}

	for (int i = 0;i < arr1.getmSize();i++) {
		cout << arr1[i] << endl;
	}

	cout << "arr1的数组容量为" <<arr1.getmCapacity()<< endl;

	MyArry<int>arr2(arr1);
	cout << "尾删法之前" << endl;
	for (int i = 0;i < arr2.getmSize();i++) {
		cout << arr2[i] << endl;
	}
	cout << "尾删法之后" << endl;
	arr2.Pop_Back();
	for (int i = 0;i < arr2.getmSize();i++) {
		cout << arr2[i] << endl;
	}
}


//测试自定义数据类型
class Person {
	friend void printPArry(MyArry <Person>& pArry);
public:
	Person() {};
	Person(string name, int age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	

private:
	string m_Name;
	int m_Age;

};

void printPArry(MyArry <Person>& pArry) {
	for (int i = 0;i < pArry.getmSize();i++) {
		cout << "第" << i+1<< "个人的姓名为" << pArry[i].m_Name << " 的年龄为" << pArry[i].m_Age << endl;
	}
}

void test02() {
	Person p1("zz", 1);
	Person p2("aa", 2);
	Person p3("ez", 3);
	Person p4("tz", 4);
	Person p5("yz", 5);
	Person p6("vz", 6);

	MyArry <Person> pArry(10);
	pArry.Push_Back(p1);
	pArry.Push_Back(p2);
	pArry.Push_Back(p3);
	pArry.Push_Back(p4);
	pArry.Push_Back(p5);
	pArry.Push_Back(p6);

	cout << "尾删前" << endl;
	printPArry(pArry);
	cout << "pArry的容量" << pArry.getmCapacity() << endl;
	cout << "pArry的大小" << pArry.getmSize() << endl;
	cout << "尾删后" << endl;
	pArry.Pop_Back();
	printPArry(pArry);
	cout << "pArry的容量" << pArry.getmCapacity() << endl;
	cout << "pArry的大小" << pArry.getmSize() << endl;
}
int main() {
	//test01();
	test02();
	system("pause");
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值