当前位置: 首页 > article >正文

C++——模板

一、模板的概念

模板就是建立通用的模具,大大提高复用性 。是c++泛型编程思想和STL技术的主要实现。

二、函数模板

(一)概念:

函数模板是一种通用函数,用到关键字template和typename(或class),我们可以用类型参数代替具体的数据类型,从而使函数可以处理不同的数据,大大提高了代码复用性。例如:

template <typename T>
void myCompare(T &a, T &b) {
	if (a == b) {
		cout << "a == b" << endl;
	}
	else if (a > b) {
		cout << "a > b" << endl;
	}
	else {
		cout << "a < b" << endl;
	}
}
void test() {
	int a = 5, b = 10;
	myCompare(a, b);
	double c = 0.9, d = 0.5;
	myCompare(c, d);
}

代码中模板的定义从template开始,<typename T>声明了参数列表,void myCompare(T &a, T &b){……}就是函数定义,T代替了参数类型。该函数模板可以用来比较两个int、double、long等内置类型的大小。和函数重载相比,函数模板更简洁,减少了代码量。

(二)用法

函数模板用法有两种:1、自动类型推导;2、显示指定类型

template<typename T>
T myAdd(T a, T b) {
	return a + b;
}
void test() {
	int a = 5, b = 10;
	double c = 3.14, d = 6.28;
	cout << myAdd(a, b) << endl;            //自动类型推导
	cout << myAdd<double>(c, d) << endl;    //显示指定类型
}

函数模板的参数列表可以指定默认参数类型,和函数默认参数类型一样,需要从后向前指定。 

(三)注意事项

函数模板的使用中,传入的参数,需要与函数定义中参数列表一致。例如:

template <typename T,typename K=int>
void myFunc(T &a, T &b,K &c) {
	cout << "重载模板调用" << endl;
}
void test() {
	int a = 5;
	int b = 10;
	char c;
	myFunc(a, b, c);  //正确
    //myFunc(a, c, b);  //错误
}

(四)函数模板实例

实现一个排序函数模板,能够满足整形、浮点型、字符型等多种内置类型数据数组的排序

template<typename T>
void mySort(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		int maxID = i;
		for (int j = i + 1; j < len; j++) {
			if (arr[maxID] < arr[j]) {
				maxID = j;
			}
		}
		if (maxID != i) {
			T temp = arr[maxID];
			arr[maxID] = arr[i];
			arr[i] = temp;
		}
	}
}
template<typename T>
void printArr(T* arr, int len) {
	for (int i = 0; i < len; i++) {
		cout << setw(2) << setiosflags(ios::left) << arr[i];
	}
	cout << endl;
}
void test01() {
	int intArr[] = { 1,3,5,6,4,7,2,9,8 };
	int len = sizeof(intArr) / sizeof(int);
	mySort(intArr, len);
	printArr(intArr, len);
	cout << "…………………………………………" << endl;
	char charArr[] = "asdfghjklqwertyuiopzxcvbnm";
	len = sizeof(charArr) / sizeof(char);
	mySort(charArr, len);
	printArr(charArr, len);

}

(五)普通函数和函数模板对比

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

普通函数支持隐式类型转换;函数模板采用自动类型推导时,不支持参数的隐式类型转换;函数模板使用显示指定类型的方式,可以发生隐式类型转换

template <typename T>
T myAdd(T a, T b) {
	return a + b;
}
void test() {
	int a = 5, b = 10;
	char c = 'a';
	cout << myAdd(a, b) << endl;
	//cout << myAdd(a, c) << endl;//错误,采用自动类型推导,不能发生隐式类型转换
	cout << myAdd<int>(a, c) << endl;//正确,采用显示指定类型,能够发生隐式类型转换
}

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

(1)当普通函数和函数模板同时存在时,优先调用普通函数;

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

(3)函数模板可发生重载;

(4)如果函数模板可以产生更好的匹配,优先调用函数模板。

template <typename T>
T myAdd(T a, T b) {
	cout << "函数模板调用" << endl;
	return a + b;
}

int myAdd(int a, int b) {
	cout << "普通函数调用" << endl;
	return a + b;
}
template <typename T>
T myAdd(T a, T b) {
	cout << "函数模板调用" << endl;
	return a + b;
}

template <typename T>
T myAdd(T a, T b, T c) {
	cout << "函数模板的重载调用" << endl;
	return a + b + c;
}
void test() {
	int a = 5, b = 10;
	cout << myAdd(a, b) << endl;	//优先调用普通函数
	cout << myAdd<>(a, b) << endl;	//通过空模板参数列表强制调用函数模板
	cout << myAdd(a, b, 10) << endl;//函数模板的重载
	char c = 'c', d = 'd';
	cout << (int)myAdd(c, d) << endl;	//函数模板比普通函数更加适配,调用函数模板
}

输出结果:普通函数调用

                  函数模板调用

                  函数模板的重载调用

                  函数模板调用

例1:当普通函数和函数模板同时存在,且函数原型完全相同,优先调用普通函数。

例2:通过空模板参数列表,强制调用函数模板。

例3:函数模板发生了重载,通过对传入参数的数量控制调用模板

例4:例子中普通函数调用会发生隐式类型转换,函数模板更加适配,则调用函数模板。

TIP:为了避免普通函数和普板函数出现二义性,一般提供了函数模板就不再提供普通函数

三、模板的局限性

函数模板并不是万能的,当参数为数组类型,或者自定义类型(类)时,无法进行部分运算。例如:

template <typename T>
void myfunc(T &a, T &b) {
	a = b;
}

c++提供了函数模板的重载,为特殊类型参数提供具体化模板。语法如下:

template <typename T>
void myfunc(T &a, T &b) {
	a = b;
}
class Person {
public:
	string m_name;
	int m_age;
};
template<> void myfunc(Person &a, Person &b) {
	a.m_name = b.m_name;
	a.m_age = b.m_age;
}

特殊类型参数的函数模板具体化后,传入该类型参数会直接调用具体化类型参数的函数模板,不再调用通用函数模板。

四、类模板

类模板和函数模板相似,在template<class T>后面紧跟类定义

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

(一)类模板和函数模板的比较:

1、类模板没有自动类型推导的使用方式,只有显式指定类型实例化对象。

2、类模板和函数模板的参数列表中均可以指定默认类型。

class Person {
public:
	Person(){}
	Person(NameType name=string, AgeType age=int) {
		m_name = name;
		m_age = age;
	}
	NameType m_name;
	AgeType m_age;
};
//Person p("孙悟空", 999);	//错误,缺少类模板Person的参数列表
Person <string, int>p("猪八戒", 888);//正确,类模板只能使用显示指定类型实例化对象

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

1、普通类中成员函数在声明时即可创建

2、类模板中成员函数在调用时创建,当模板中T类型不确定时,func1()函数和func2()函数无法创建,只有当实例化Myclass  p,确定了T的类型为Person1后调用p.func1()时才会创建。

class Person1 {
public:
	void show1() {
		cout << "Person1 show" << endl;
	}
};
class Person2 {
public:
	void show2() {
		cout << "Person2 show" << endl;
	}
};
template<class T>
class MyClass {
public:
	T obj;
	void func1() {
		obj.show1();
	}
	void func2() {
		obj.show2();
	}
};
void test() {
	MyClass<Person1> p;
	p.func1();
	//p.func2();
}

int main(int argc, char const *argv[]) {
	test();
	return 0;
}

(三)类模板作为参数传入函数的三种方式

1、指定参数类型传参(常用)

2、利用函数模板,声明类模板中参数

3、利用函数模板,声明函数参数后自动推导

template<class T,class K>
class Person {
public:
	Person(T name, K age) {
		this->m_name = name;
		this->m_age = age;
	}
	T m_name;
	K m_age;
	void show() {
		cout << "姓名:" << this->m_name << endl;
		cout << "年龄:" << this->m_age << endl;
	}
};
//第一种传参方式:指定参数类型传参
void printPerson1(Person<string, int> &p) {
	p.show();
	cout << typeid(p).name() << endl;
}
//第二种传参方式:利用函数模板,声明类中参数类型
template<class T,class K>
void printPerson2(Person<T, K> &p) {
	p.show();
	cout << typeid(p).name() << endl;
}
//第三种传参方式:利用函数模板,声明函数参数类型后自动推导
template<class T>
void printPerson3(T &p) {
	p.show();
	cout << typeid(p).name() << endl;
}
void test() {
	Person<string, int> p("唐僧", 30);
	printPerson1(p);
	printPerson2(p);
	printPerson3(p);
}

(四)类模板与继承

当子类继承的父类为模板时,声明子类时需要指定父类中的参数类型;

或者子类也作为模板去继承父类模板,用子类模板中的参数类型来指定父类模板参数类型。

template<class T>
class Base {
public:
	T m;
};
//子类继承类模板,需指定父类参数类型
class Son1 :public Base<char> {


};
//子类继承类模板也可声明为类模板
//此时相当于指定了父类模板中参数类型为T
template<class T,class K>
class Son2 :public Base<T> {
public:
	K obj;
};

void test01() {
	Base<char> b;		//类模板实例化对象时,需指定参数类型
	Son1 s1;			//子类声明时已指定父类参数类型为字符型
	Son2<int,char> s2;	//子类为模板时,先指定了父类参数类型为T,也就是实例化时指定的int
}

(五)类模板成员函数的类外实现和分文件编写

1、类模板中成员函数的类外实现,每次要重申模板和模板的参数列表

template<class TypeName,class TypeAge>
class Person {
public:
	TypeName m_name;
	TypeAge m_age;

	Person(TypeName name,TypeAge age);
	void show();
};

template<class TypeName,class TypeAge>
Person<TypeName, TypeAge>::Person(TypeName name, TypeAge age) {
	this->m_name = name;
	this->m_age = age;
}
template<class TypeName,class TypeAge>
void Person<TypeName, TypeAge>::show() {
	cout << this->m_name << endl;
	cout << this->m_age << endl;
}

2、分文件编写:因类模板函数是在调用阶段创建,并不是在编译阶段创建,则编译器进行到调用语句时发生错误,无法识别类模板函数。解决办法:在编译阶段将函数体提供给编译器,代替函数原型。可以在编译阶段将函数体所在的cpp文件包含进去,也可以将函数体写进头文件,命名为.hpp文件(约定俗成,不强制),一般用第二种处理方法。

Person.hpp

#pragma once
template<class TypeName,class TypeAge>
class Person
{
public:
	TypeName m_name;
	TypeAge m_age;

	Person(TypeName name, TypeAge age);
	void show();
};
template<class TypeName, class TypeAge>
Person<TypeName, TypeAge>::Person(TypeName name, TypeAge age) {
	this->m_name = name;
	this->m_age = age;
}
template<class TypeName, class TypeAge>
void Person<TypeName, TypeAge>::show() {
	cout << this->m_name << endl;
	cout << this->m_age << endl;
}

main.cpp

#include<iostream>
using namespace std;
#include<string>
#include"Person.hpp"
void test() {
	Person<string, int> p("张三", 20);
	p.show();
}
int main(int argc, char const *argv[]) {
	test();
	return 0;
}

(六)类模板——友元

类模板的全局友元函数可以分为内部实现和外部实现两种方式

1、内部实现:直接在类内部实现函数体,函数前加上friend友元关键字即可。

2、外部实现:因传入参数不确定类型,则外部全局函数也需要用模板对应类模板

(1)声明类模板

(2)声明全局函数模板(模板参数列表是类模板参数列表)

(3)定义类模板

(4)类内部将全局函数模板声明为友元函数模板

(5)实现函数模板

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

//声明类模板,目的是声明全局函数模板
template<class TypeName,class TypeAge>
class Person;
//声明全局函数模板
template<class TypeName,class TypeAge>
void printPerson(Person<TypeName, TypeAge> &p);
//定义类模板
template<class TypeName,class TypeAge>
class Person {
private:
	TypeName m_Name;
	TypeAge m_Age;
public:
	Person(TypeName name,TypeAge age);
	//外部实现全局友元函数模板
	friend void printPerson<>(Person<TypeName, TypeAge> &p);
	//内部实现全局友元函数
	friend void printPerson1(Person<TypeName, TypeAge> &p) {
		cout << p.m_Name << endl;
		cout << p.m_Age << endl;
	}
};
//构造函数的外部实现
template<class TypeName,class TypeAge>
Person<TypeName, TypeAge>::Person(TypeName name, TypeAge age) {
	this->m_Name = name;
	this->m_Age = age;
}
//全局友元函数模板的外部实现
template<class TypeName,class TypeAge>
void printPerson(Person<TypeName, TypeAge> &p) {
	cout << p.m_Name << endl;
	cout << p.m_Age << endl;
}

五、模板实例

用模板实现数组,能够实现对内置数据类型和自定义数据类型的储存和基本操作。通过独立写代码的过程对类的三种权限复习一下;对运算符的重载复习一下;对new的用法理解加深了一些。

MyArray.hpp

#pragma once
#include<iostream>
using namespace std;
//定义数组类模板
template<class T>
class MyArray {
private:
	int m_size = 0;					//元素数量
	int m_capacity ;				//数组容量
	T *m_array = NULL;				//数组指针
public:
	MyArray(int capacity);
	MyArray(const MyArray& array);
	void add(const T &value);		//添加元素
	void del(const T &value);		//删除元素
	int search(const T &value);		//搜索元素
	void print();					//打印数组
	T& operator[](int index);		//[]重载
	MyArray<T>& operator=(const MyArray& array);//=重载
	bool isfull();					//判定数组是否已满
	~MyArray();
};
//普通构造函数
template<class T>
MyArray<T>::MyArray(int capacity) {
	this->m_size = 0;
	this->m_capacity = capacity;
	this->m_array = new T[capacity];
}
//拷贝构造函数,利用=重载避免浅拷贝
template<class T>
MyArray<T>::MyArray(const MyArray& array) {
	*this = array;
}
//=号重载,返回对象本身实现连续复制
template<class T>
MyArray<T>& MyArray<T>::operator=(const MyArray& array) {
	this->m_capacity = array.m_capacity;
	this->m_size = array.m_size;
	if (this->m_array) {
		delete this->m_array;
	}
	this->m_array = new T[m_capacity];
	
	for (int i = 0; i < this->m_size; i++) {
		this->m_array[i] = array.m_array[i];
	}
	return *this;
}
//[]重载,取当前index下标元素
template<class T>
T& MyArray<T>::operator[](int index) {
	return m_array[index];
}
//判定数组是否已满
template<class T>
bool MyArray<T>::isfull() {
	return this->m_size == this->m_capacity;
}
//添加元素
template<class T>
void MyArray<T>::add(const T &value) {
	if (!this->isfull()) {
		this->m_array[this->m_size++] = value;
	}
}
//删除元素
template<class T>
void MyArray<T>::del(const T &value) {
	int index = search(value);
	while (index != -1) {
		for (int i = index; i < m_size - 1; i++) {
			m_array[i] = m_array[i + 1];
		}
		m_size--;
		index = search(value);
	}
}
//查找元素
template<class T>
int MyArray<T>::search(const T &value) {
	int index = -1;
	for (int i = 0; i < m_size; i++) {
		if (this->m_array[i] == value) {
			index = i;
			break;
		}
	}
	return index;
}
//打印元素
template<class T>
void MyArray<T>::print() {
	for (int i = 0; i < m_size; i++) {
		cout << m_array[i] ;
		if (i != m_size - 1) {
			cout << " ";
		}else{
			cout << endl;
		}
	}
	cout << endl;
}
template<class T>
MyArray<T>::~MyArray() {
	if (m_array) {
		delete m_array;
		m_array = NULL;
	}
}

main.cpp

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

void test() {
	MyArray<int> *pA_int = new MyArray<int>(10);
	pA_int->add(3);
	pA_int->add(5);
	pA_int->add(7);
	pA_int->add(8);
	pA_int->add(8);
	cout << (*pA_int)[1] << endl;
	pA_int->print();

	MyArray<int> pA_int2(*pA_int);
	pA_int2.print();
	pA_int2.del(8);
	pA_int2.print();
}

int main(int argc, char const *argv[]) {
	test();
	return 0;
}


http://www.kler.cn/a/135414.html

相关文章:

  • 24.11.13 Javascript3
  • Ubuntu安装MySQL8
  • 应用程序部署(IIS的相关使用,sql server的相关使用)
  • 【LeetCode】【算法】55. 跳跃游戏
  • 3.5【数据库系统】ER图
  • 【Qt】在 Qt Creator 中使用图片资源方法(含素材网站推荐)
  • Java中异常的捕获与处理
  • 你听说过“消费多少返利多少的”模式吗?
  • 基于Vue+SpringBoot的医院门诊预约挂号系统 开源项目
  • 【C/PTA】函数专项练习(二)
  • C语言幂级数求近似值
  • JC/T 2339-2015 地暖用相变储能材料及构件检测
  • 游戏报错d3dcompiler_47.dll缺失怎么修复,总结多种修复方法
  • Linux文件目录以及文件类型
  • Iceberg学习笔记(1)—— 基础知识
  • Linux:zip包的压缩与解压
  • C#开发的OpenRA游戏之属性QuantizeFacingsFromSequence(7)
  • 测试用例的设计方法(全):正交实验设计方法|功能图分析方法|场景设计方发
  • 代码随想录 11.16 || 动态规划 LeetCode 583.两个字符串的删除操作、72.编辑距离
  • 网工内推 | 国企、港企网工,年底双薪,NA以上认证即可
  • CentOS 安装etcd集群 —— 筑梦之路
  • SpringCloud -Token传递之Feign
  • 【数据结构与算法】Kadane‘s算法(动态规划、最大子数组和)
  • 趣学python编程 (四、数据结构和算法介绍)
  • 【10套模拟】【7】
  • 【C++上层应用】1. 异常处理