【泛型编程】

100 阅读3分钟

1. 函数模板

函数模板是指函数逻辑结构相同,但是参数类型不同的一类函数的抽象,即类型参数化。引用官方描述如下:

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

模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

下面写一个函数模板的实例,通过实例分析函数模板的语法和使用规则

#include <iostream>
using namespace std;

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

void my_print(int a, int b)
{
	cout << "普通函数 (int a, int b) " << a << " " << b << endl;
}

void my_print(int a, char b)
{
	cout << "普通函数 (int a, char b) " << a << " " << b << endl;
}

int main()
{
	my_print(1, 2); //优先匹配普通函数

	my_print('a', 4); //调用 my_print(int a, int b) 并会自动进行类型转换,把 'a' 转换为 int 型

	my_print('a', 'b'); //调用模板函数,模板函数严格进行参数类型匹配,并会自动推导参数类型
	//只有调用时两个参数类型完全一致且没有普通函数匹配的时候才会调用模板函数(参数更佳匹配)

	my_print<char>('a', 'b'); //显式参数类型

	my_print<>(1, 2); //强制使用模板函数,不会在调用 my_print(int a, int b)

	//my_print<>(1, 'a'); //错误	C2672	“my_print” : 未找到匹配的重载函数	
	//函数模板不会进行参数类型转换

	
	system("pause");
	return 0;
}

编译并执行上面的程序可以看到打印的结果和我们在程序中分析的规则一致

另外,如果把上面注释掉的语句放开

my_print<>(1, 'a');

也就是强制使用模板函数,并传入两个类型不同的参数,再次编译可以看到编译器报错

这证明了我们所分析的,模板函数不会进行参数自动类型转换。

最后函数模板的实现并不是提前把所有可能的参数类型都列举出来并生成相应的函数,而是二次编译理论。函数模板的实现机制实际是两次编译,第一次编译是在模板函数声明的地方进行编译;第二次编译是在模板函数调用的地方,对参数替换后的函数进行编译。

2. 类模板实现vector容器

首先创建一个类的.h和.cpp文件,分别如下

UserVector.h文件

#pragma once //只包含一次

#include <iostream>
using namespace std;

template<typename user_t>
class UserVector
{
public:
	//构造函数与析构函数
	UserVector(int len = 0); //有了默认初始参数,不需要在写无参构造函数 UserVector();
	UserVector(UserVector<user_t>& v); //UserVector<user_t> 必须指明类型来告诉编译器如何分配内存
	~UserVector();
public:
	//重载函数
	user_t& operator[](int index);
	friend ostream& operator<< <user_t> (ostream& out, UserVector<user_t>& u);
	UserVector<user_t>& operator=(UserVector<user_t>& u);
private:
	int		len;
	user_t* p;
};

其中

#pragma once 

表示只包含一次该头文件,防止重复包含导致的重定义,相当于C语言中的

#ifndef _USER_VECTOR_H
#define _USER_VECTOR_H

/*
程序代码
*/

#endif

UserVector.cpp文件

#include "UserVector.h"

template<typename user_t>
UserVector<user_t>::UserVector(int len)
{
	this->len = len;
	this->p = new user_t[this->len];
}

template<typename user_t>
UserVector<user_t>::UserVector(UserVector<user_t>& v)
{
	this->len = v.len;
	this->p = new user_t[this->len];
	for (int i = 0; i < this->len; i++)
	{
		this->p[i] = v.p[i];
	}
}

template<typename user_t>
UserVector<user_t>::~UserVector()
{
	if (this->p != NULL)
	{
		delete[] this->p;
	}
	this->p = NULL;
	this->len = 0;
}

template<typename user_t>
user_t& UserVector<user_t>::operator[](int index)
{
	return this->p[index];
}

template<typename user_t>
//std::ostream& operator<< <user_t> (std::ostream& out, UserVector<user_t>& u)
//错误	C2768	“operator << ”: 非法使用显式模板参数	
std::ostream& operator<<(std::ostream& out, UserVector<user_t>& u)
{
	for (int i = 0; i < u.len; i++)
	{
		out << u.p[i] << " ";
	}
	return out;
}

template<typename user_t>
UserVector<user_t>& UserVector<user_t>::operator=(UserVector<user_t>& u)
{
	if (this->p != NULL)
	{
		delete[] this->p;
		//无需置NULL,因为下面立马要修改
	}

	this->len = u.len;
	this->p = new user_t[this->len];

	for (int i = 0; i < this->len; i++)
	{
		this->p[i] = u.p[i];
	}

	return *this;
}

这里注意,在类模板中重载运算符的时候一定不能乱用友元函数,一般只有重载左移右移运算符时,才能使用友元函数,否则会报各种错误,并且在重载左移右移运算符的时候,函数声明一定要这样声明

friend ostream& operator<< <user_t> (ostream& out, UserVector<user_t>& u);

主测试函数函数如下

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;

//#include "UserVector.h" //报一堆错
#include "UserVector.cpp"

void FuncTest1()
{
	//1. int 类型的容器
	UserVector<int> i1(10);
	for (int i = 0; i < 10; i++)
	{
		i1[i] = i;
	}
	cout << "i1: " << i1 << endl;

	UserVector<int> i2 = i1;
	cout << "i2: " << i2 << endl;

	UserVector<int> i3;
	i3 = i2;
	cout << "i3: " << i3 << endl;

	//2. char
	UserVector<char> c1(3);
	c1[0] = 'a';
	c1[1] = 'b';
	c1[2] = 'c';
	cout << "c1: " << c1 << endl;

	UserVector<char> c2;
	c2 = c1;
	cout << "c2: " << c2 << endl;
}

class People
{
public:
	People()
	{
		this->age = 0;
		this->name = NULL;
	}
	People(int age, const char* p) //装入容器时,必须要有拷贝构造函数
	{
		this->age = age;
		this->name = new char[strlen(p) + 1];
		strcpy(this->name, p);
	}
	People(People& p)
	{
		this->age = p.age;
		this->name = new char[strlen(p.name) + 1];
		strcpy(this->name, p.name);
	}
	~People()
	{
		if (this->name != NULL)
		{
			delete[] this->name;
		}
		this->name = NULL;
		this->age = 0;
	}
public:
	People& operator=(People& p)
	{
		if (this->name != NULL)
		{
			delete[] this->name;
		}

		this->name = new char[strlen(p.name) + 1];
		this->age = p.age;
		strcpy(this->name, p.name);

		return *this;
	}
	friend ostream& operator<<(ostream& out, People& p)
	{
		out << p.name << ": " << p.age << endl;
		return out;
	}
private:
	int		age;
	char*	name;
};

void FuncTest2()
{
	People p1(16, "p1"), p2(17, "p2"), p3(18, "p3");
	UserVector<People> V(3);
	V[0] = p1;
	V[1] = p2;
	V[2] = p3;

	cout << V[0] << V[1] << V[2] << endl;
}

int main()
{
	//装入普通类型
	FuncTest1();

	//装入 类对象
	FuncTest2();

	system("pause");
	return 0;
}

在向容器中装入自己定义的类对象时,一定要自己实现拷贝构造函数,否则可能出现浅拷贝的问题。

3. 模板类中的静态成员

首先我们知道,在类中,static关键字修饰的成员属于整个类,即类定义的所有关键字共享同一个static成员,那么在模板类中的静态成员是否是属于该模板所能代表的所有类型的类呢?下面通过一个程序测试一下:

#include <iostream>
using namespace std;

template<typename user_t>
class MyClass
{
public:
	static user_t a;
};

template<typename user_t>
user_t MyClass<user_t>::a = 0;

int main()
{
	MyClass<int> m1, m2;
	m1.a++;
	m2.a++;
	cout << "m1.a: " << m1.a << endl;
	cout << "m2.a: " << m2.a << endl;

	MyClass<char> m3, m4;
	m3.a = 'a';
	m4.a++;
	cout << "m1.a: " << m1.a << endl;
	cout << "m2.a: " << m2.a << endl;
	cout << "m3.a: " << m3.a << endl;
	cout << "m4.a: " << m4.a << endl;
	
	system("pause");
	return 0;
}

通过程序运行结果可以看到并不是所有类型的类都共享同一个静态成员。实际上模板类中的静态成员,同一个类型的类共享一个静态成员的副本。也就是说 MyClass 定义的对象共享一个静态成员,MyClass 也有自己的一个静态成员,以此类推,每个类型定义的对象都共享自己的静态成员。