C++基础知识(八)--- 模板

目录

一. 模板的概念

二. 函数模板

1. 使用函数模板:

2. 隐式转换 

3. 普通函数与函数模板

(1)区别

 (2)调用规则

 4. 模板实现机制

5. 模板的局限性(了解)

三. 类模板

1. 类模板的默认类型

2. 复数的模板类(了解)

(1)通过成员函数实现

(2)通过重载运算符实现(重载 “+”)

3. 类模板作为函数参数

四. 类模板的继承

 五. 类模板的成员函数类内类外实现

1. 类内实现

2. 类外实现

六. 类模板分文件编写

七. 类模板与友元

1. 类内实现(简单)

2. 类外实现

八. 类模板实现数组

arr.hpp

arr.cpp


一. 模板的概念

函数或类是通用的,但是里面的数据类型是多种多样的。

C++提供两种模板机制:函数模板类模板

类属:类型参数化,又称参数模板。

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

二. 函数模板

所谓的函数模板,就是建立一个通用函数,其函数类型和形参类型不具体确定,用一个虚拟的类型来代表。

//交换int数据
void SwapInt(int& a,int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

//交换char数据
void SwapChar(char& a,char& b)
{
	char temp = a;
	a = b;
	b = temp;
}

如上所示,如果我还要交换double类型的数据,那么还需要定义一个double类型数据交换的函数,这样就显得代码臃肿且不好维护。

上述代码中,函数体是一模一样的,只是数据类型不一样而已。如果能把类型作为参数传进来,实现类型的参数化,即函数模板。

1. 使用函数模板:

调用时,必须让编译器知道泛型 T 的类型。

编译器会对函数模板或类模板进行二次编译。

2. 隐式转换 

eg:使用函数模板实现对 char 和 int 类型数组进行排序。

//模板打印函数
template<class T>
void myPrint(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}
//模板排序函数
template<class T>
void mySort(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i;
		for (int j = i + 1; j < len; j++)
		{
			if (arr[max] < arr[j])
			{
				max = j;
			}
		}
		if (max != i)
		{
			mySwap(arr[max], arr[i]);
		}
	}
}

void test()
{
	char arrchar[] = "hello world";
	int len = sizeof(arrchar) / sizeof(char);
	mySort(arrchar, len);
	myPrint(arrchar, len);

	int arrint[] = {1,4,2,7,9,6};
	len = sizeof(arrint) / sizeof(int);
	mySort(arrint, len);
	myPrint(arrint, len);
}

3. 普通函数与函数模板

(1)区别

 (2)调用规则

普通函数
函数模板1
函数模板1

 4. 模板实现机制

编译器并不是把函数模板处理成能够处理任何类型的函数;

而是函数模板通过具体类型产生不同的函数;

编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的时候对参数替换后的代码进行编译。

5. 模板的局限性(了解)

编写的模板可能无法处理某些类型。

 为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。

eg:

class Maker
{
public:
	Maker(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
public:
	string name;
	int age;
};
template<class T>
void myfunc(T& a, T& b)
{
	if (a > b)
		cout << "a>b" << endl;
	else
		cout << "a<b" << endl;
}
//具体化函数模板,注意,要先有函数模板,才能具体化
template<>void myfunc<Maker>(Maker& a, Maker& b)
{
	cout << "函数模板的具体化" << endl;
	if (a.age > b.age)
		cout << "a>b" << endl;
	else
		cout << "a<b" << endl;
}

void test()
{
	Maker m1("ss",10);
	Maker m2("bb", 20);
	myfunc(m1, m2);
}
函数模板的具体化
a<b

不建议具体化函数模板,没有通用性。如上例子,可以进行比较运算符的重载。

三. 类模板

类模板就是把类中的数据类型参数化。

类模板不会自动推导数据类型,要显式地告诉编译器是什么类型;

而函数模板可以根据实参来推导数据类型。

1. 类模板的默认类型

 和函数的默认参数一样,默认类型后面的泛型类型都必须有默认类型。

template<class NameType, class AgeType=int, class T = int>

2. 复数的模板类(了解)

例题:对象 c1(3,4) ,c2(5,-3), c3=c1.Maker_add(c2)。实现它。

(1)通过成员函数实现

template<class T>
class Maker
{
public:
	Maker()
	{
		a = 0;
		b = 0;
	}
	Maker(T r, T i)
	{
		a = r;
		b = i;
	}
	Maker Maker_add(Maker& c)
	{
		Maker<T> m;
		m.a = this->a + c.a;
		m.b = this->b + c.b;
		return m;
	}
	void printMaker()
	{
		cout << "(" << a << "," << b << ")" << endl;
	}
private:
	T a;
	T b;
};

void test()
{
	Maker<int> m1(3, 4);
	Maker<int> m2(5, -3);
	Maker<int> m3;
	m3 = m1.Maker_add(m2);
}

(2)通过重载运算符实现(重载 “+”)

	Maker operator+(Maker& c)
	{
		Maker tmp(this->a + c.a, this->b + c.b);
		return tmp;
	}

void test()
{
	Maker<int> m1(3, 4);
	Maker<int> m2(5, -3);
	Maker<int> m3;
	m3 = m1 + m2;
}

3. 类模板作为函数参数

  三种方法:

  • 指定传入的数据类型;
  • 类的参数模板化;(常用)
  • 整个类模板化。
template<class NameType,class AgeType>
class Maker
{
public:
	Maker(NameType name, AgeType age)
	{
		this->name = name;
		this->age = age;
	}
	void printMaker()
	{
		cout << "Name:" << this->name << "Age:" << this->age << endl;
	}
public:
	NameType name;
	AgeType age;
};

//1. 指定传入的数据类型
void func(Maker<string, int>& m)
{
	m.printMaker();
}
//2.类的参数模板化(常用)
template<class T1,class T2>
void func2(Maker<T1, T2>& m)
{
	m.printMaker();
}
//3.整个类 模板化
template<class T>
void func3(T& m)
{
	m.printMaker();
}

void test()
{
	Maker<string, int> m1("sun",18);
	func(m1);

	func2(m1); //调用时进行二次编译,替换参数类型

	func3(m1);
}

四. 类模板的继承

不管是普通类继承类模板,还是类模板继承类模板,都必须告诉编译器父类的泛型数据类型是什么类型。

 五. 类模板的成员函数类内类外实现

1. 类内实现

直接写。

template<class NameType, class AgeType>
class Maker
{
public:
	Maker(NameType name, AgeType age)
	{
		this->name = name;
		this->age = age;
	}
	void printMaker()
	{
		cout << "Name:" << this->name << "Age" << this->age << endl;
	}
public:
	NameType name;
	AgeType age;
};

2. 类外实现

template<class NameType, class AgeType>
Maker<NameType, AgeType>::Maker(NameType name, AgeType age)
{
	cout << "构造函数" << endl;
}
template<class NameType, class AgeType>
void Maker<NameType, AgeType>::printMaker()
{

}

六. 类模板分文件编写

调用类模板时,要进行第二次编译,要把泛型的数据类型替换为具体的类型。这时需要知道函数体,但是函数的实现在.cpp中,那么调用类模板的.cpp中没有引入包含函数实现的.cpp文件,而是只引入了包含函数声明的 .h头文件,所以报错。

解决方法:

把成员函数的实现放到.h中,然后把.h改为.hpp,把最初放置成员函数实现的.cpp文件(如下例为Maker.cpp)中的函数实现屏蔽掉,然后在调用成员函数的地方引入.hpp

问题:

为什么.hpp中多了成员的实现,但是在调用类模板的地方引入.hpp时不会报重定义呢?

答:类的成员函数默认申请为内联函数,在链接时,链接器会对重复的成员函数进行处理,只保留一份成员函数,所以不会报错。

Maker.hpp(里面有类模板)

template<class NameType,class AgeType>
class Maker
{
public:
	Maker(NameType name, AgeType age);
	void printMaker();
public:
	NameType name;
	AgeType age;
};

template<class NameType, class AgeType>
Maker<NameType, AgeType>::Maker(NameType name, AgeType age)
{
	cout << "构造函数" << endl;
}
template<class NameType, class AgeType>
void Maker<NameType, AgeType>::printMaker()
{

}

Maker.cpp(最初包含函数实现的地方,当原先的.h头文件改名为.hpp,并且其中引入了函数实现后,这里的全部屏蔽掉,即不使用该.cpp文件了)

#include "Maker.h"

template<class NameType, class AgeType>
Maker<NameType, AgeType>::Maker(NameType name, AgeType age)
{
	cout << "构造函数" << endl;
}
template<class NameType, class AgeType>
void Maker<NameType, AgeType>::printMaker()
{

}

test.cpp(调用类模板的地方)

#include "Maker.hpp"
int main()
{
	Maker<string, int> m("sun",20);
	m.printMaker();

	system("pause");
	return EXIT_SUCCESS;
}

七. 类模板与友元

1. 类内实现(简单)

template<class NameType,class AgeType>
class Maker
{
    //注意,该函数不是成员函数
	friend void printMaker(Maker<NameType, AgeType>& p)
	{
		cout << "类内实现" << p.name << " " << p.age << endl;
	}
public:
	Maker(NameType name, AgeType age)
	{
		this->name = name;
		this->age = age;
	}
public:
	NameType name;
	AgeType age;
};

void test()
{
	Maker<string, int> m("sun", 18);
	printMaker(m);
}

2. 类外实现

  • (1)声明类模板;
  • (2)声明函数模板,告诉编译器下面有printMaker的实现;
  • (3)在函数名和()之间加上<>;
  • (4)友元在类外实现要写成函数模板。
//1. 声明类模板
template<class NameType, class AgeType>
class Maker;
//2. 声明函数模板,告诉编译器下面有printMaker的实现。
template<class NameType, class AgeType>
void printMaker(Maker<NameType, AgeType>& p);
template<class NameType,class AgeType>
class Maker
{
	//3. 在函数名和()之间加上<>。
	friend void printMaker<>(Maker<NameType, AgeType>& p);
public:
	Maker(NameType name, AgeType age)
	{
		this->name = name;
		this->age = age;
	}
private:
	NameType name;
	AgeType age;
};
//4. 友元在类外实现要写成函数模板
template<class NameType,class AgeType>
void printMaker(Maker<NameType, AgeType>& p)
{
	cout << "类外实现的友元函数" << p.name << " " << p.age << endl;
}

void test()
{
	Maker<string, int> m("sun", 18);
	printMaker(m);
}

八. 类模板实现数组

arr.hpp

#pragma once

template<class T>
class MyArray
{
public:
	MyArray(int capacity)
	{
		this->mCapacity = capacity;
		this->mSize = 0;
		//这里的T如果是Maker类型,则需要调用Maker的无参构造函数
		p = new T[this->mCapacity];
	}
	//拷贝构造
	MyArray(const MyArray& arr)
	{
		this->mCapacity = arr.mCapacity;
		this->mSize = arr.mSize;
		p = new T[arr.mCapacity];

		for (int i = 0; i < this->mSize; i++)
		{
			p[i] = arr.p[i];
		}
	}
	//赋值函数
	MyArray& operator=(const MyArray& arr)
	{
		if (this->p != NULL)
		{
			delete[] this->p;
			this->p = NULL;
		}
		p = new T[arr.mCapacity];
		this->mSize = arr.mSize;
		this->mCapacity = arr.mCapacity;
		for (int i = 0; i < this->mSize; i++)
		{
			p[i] = arr.p[i];
		}
		return *this;
	}
	//重载[]
	T& operator[](int index)
	{
		return this->p[index];
	}
	//尾插
	void PushBack(const T& val)
	{
		if (this->mSize == this->mCapacity)
		{
			return;
		}
		this->p[this->mSize] = val;
		this->mSize++;
	}
	//尾删
	void PopBack()
	{
		if (this->mSize == 0)
		{
			return;
		}
		this->mSize--;
	}
	//析构
	~MyArray()
	{
		if (this->p != NULL)
		{
			delete[] p;
			p = NULL;
		}
	}
	int getSize()
	{
		return this->mSize;
	}
private:
	T* p;
	int mCapacity;
	int mSize;
};

arr.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
using namespace std;
#include "arr.hpp"


class Maker
{
	friend void printfMaker(MyArray<Maker>& arr);
public:
	Maker(){}
	Maker(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
private:
	string name;
	int age;
};

void printfMaker(MyArray<Maker>& arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << "Name:" << arr[i].name << "  Age:" << arr[i].age << endl;
	}
}

void test()
{
	MyArray<Maker> arr(3);
	Maker m1("sun", 18);
	Maker m2("yu", 19);

	arr.PushBack(m1);
	arr.PushBack(m2);
	printfMaker(arr);

	MyArray<int> myint(5);
	for (int i = 0; i < 5; i++)
	{
		myint.PushBack(i + 1);
	}
	for (int i = 0; i < 5; i++)
	{
		cout << myint[i] << " ";
	}
	cout << endl;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值