【C++】函数模板&类模板

本文深入探讨了C++中的模板概念,包括函数模板和类模板。函数模板允许参数化函数的数据类型,通过模板实例化生成特定类型的函数。类模板则使类的成员属性可以使用通用数据类型。文章详细阐述了模板的定义、注意事项、调用规则、局限性以及成员函数的实现等问题,并举例说明了如何处理模板的隐式转换、重载和局限性。此外,还讨论了类模板的继承、友元函数以及在分文件编写时的处理方法。
摘要由CSDN通过智能技术生成

目录

 

模板的概念:

函数模板:

函数模板定义:

函数模板的注意事项:

1.不能不指定模板的通用数据类型

2.函数模板中的通用数据类型要唯一

3.函数模板的隐式转换问题

4.函数模板和普通函数的调用规则

5.函数模板的局限

类模板:

类模板定义:

类模板的注意事项:

1.类模板不能选择使用让编译器自动推导数据类型的方式

2.类模板的参数列表可以有默认参数......

3.函数模板成员函数的创建时机

4.类模板的对象作为形参

5.类模板的继承问题:

6.函数模板中的成员函数类外实现问题:

7.类模板分文件编写问题:

8.类模板与全局函数做友元:


模板的概念:

C++除了面向对象编程的编程思想,还有一种编程思想就是泛型编程,泛型编程的具体例子就是模板。所谓模板,就是一个通用的类型,我们可以向模板传递不同的数据类型,从而得到不同的具体类型。C++中提供的模板有函数模板类模板

函数模板:

函数模板定义:

函数模板的作用就是将函数的数据类型参数化,什么意思,就是我们平常写的函数都是具体有形参类型,返回类型,里面具体数据类型的。类型参数化可以在我们写函数时将这些数据类型变成未知的变量,从而得到一个函数模板,我们可以根据传入不同的数据类型得到不同的结果和实现。

函数模板的语法:

template<typename T>

上面的黑色加粗体template就是语法关键字,表示要声明一个模板;而黑色加粗体typename语法关键字表示后面紧跟一个虚拟数据类型;绿色的粗体就是一个通用类型。所以我们可以写一个模板函数 :

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

这个函数的两个形参是一个通用数据类型T,我们可以根据传进去的数据不同得到不同的实现。例如我们如果将T定义为char类型,那么这个函数就变成交换两个char类型;如果我们将T定义为int类型,那么这个函数的实现就变成两个int交换。

那么我们怎么利用这个模板得到一个具体的函数呢?下面有两种具体定义一个函数的方法:

1.让编译器自动类型推导:

    Swap(a, b);

上面表示让编译器根据我们传进去的数据自己推导T应该成为什么数据类型。

2.显式指定数据类型:

   Swap<int>(a, b);

 上面表示显式指定数据类型,我们可以在实参列表前加上声明指定T的数据类型。

函数模板的注意事项:

1.不能不指定模板的通用数据类型

就是模板中的通用数据类型T,我们在调用模板时一定要显式说明T的数据类型,即使我们不要用到T,不然编译器会报错。

template<typename T>
void Swap()
{
	;
}
int main()
{
	//Swap();//错误
	Swap<char>();
	return 0;
}

2.函数模板中的通用数据类型要唯一

即我们不能模板传递不同的数据类型,这样编译器会不知道选择哪一种数据类型。

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

int main()
{
	char a = 'a';
	int b = 20;
	func(a,b);
	return 0;
}

3.函数模板的隐式转换问题

我们知道,在普通函数中,如果我们传递实参与形参不同,编译器可能会自动帮我们发生隐式的类型转换。而在函数模板中,如果我们使用的不是显式数据指定的方式创建函数模板,那么编译器会不知道T的具体数据类型,所以不会发生隐式的数据转换。而如果我们使用的是显式地函数模板,那么编译器也可能会自动帮我们进行隐式数据转换。

4.函数模板和普通函数的调用规则

规则1:函数模板和普通函数都可以调用,优先调用普通函数;

规则2:我们可以加上空的模板参数列表强制调用函数模板,即函数调用时后面加上尖括号:

func < >(...);

规则3:函数模板可以发生重载,即不同模板函数名可以一样;

规则4:如果有更好的匹配,编译器也会根据更好匹配优先调用函数模板; 

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

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

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


int main()
{
	func(100);//调用普通函数,规则1
	func<>(100);//调用函数模板,规则2
	func(100, 200);//传进去两个数据,优先匹配调用函数模板2,模板函数发生重载,规则3,4
	return 0;
}

5.函数模板的局限

函数模板也不是万能的,对于一些不可以比较的数据,函数模板也无法进行对比。例如如果传进去两个不同的类,那么函数模板就无法对比了。

当然,为了解决这个问题,我们也可以有两种方法:

方法一:具体实例化一个对比函数,明确告知里面的对比方式和数据类型,它的语法是:

template<> 返回值类型+函数名+(参数列表) 

注意,参数列表里面的参数需要指定确定的数据类型!

class Info
{
public:
	Info(int a, char b)
	{
		this->a = a;
		this->b = b;
	}
	int a;
	char b;
};

template<typename T>

bool Compare(T a, T b)
{
	if (a == b)
		return true;
	else
		return false;
}

template<> bool Compare(Info a, Info b)//具体化一个函数模板!
{
	if (a.a == b.a && a.b == b.b)
		return true;
	else
		return false;
}
int main()
{
	Info A(2,'a');
	Info B(2,'a');
	if (!Compare(A, B))
	{
		cout << "不相等" << endl;
	}
	else
		cout<<"相等" << endl;
	return 0;
}

方法二:重载“==”运算符:

class Info
{
public:
	Info(int a, int b)
	{
		this->a = a;
		this->b = b;
	}
	bool operator== (Info &A)
	{
		if (A.a == this->a && A.b == this->b)
			return true;
		else
			return false;
	}
	int a;
	int b;
};

template<typename T>
bool Compare(T a, T b)
{
	if (a == b)
		return true;
	else
		return false;
}


int main()
{
	Info A(2, 1);
	Info B(2, 1);
	if (!Compare(A, B))
		cout << "不相等" << endl;
	else
		cout<<"相等" << endl;
	return 0;
}

类模板:

类模板定义:

类模板与函数模板的作用类似,类模板可以将类中的成员属性的类型设置为通用数据类型。以此可以让类应用更加广泛。

下面是一个简单的类模板的创建:

template<class G,class T>
class Info
{
public:
	Info(T a, G b)
	{
		this->a = a;
		this->b = b;
	}
	T a;
	G b;

};

int main()
{
	Info<int, int> A(2,3);
	return 0;
}

类模板的注意事项:

1.类模板不能选择使用让编译器自动推导数据类型的方式

2.类模板的参数列表可以有默认参数......

//template<class T,class G=char>
//模板的参数列表可以有默认参数,即可以提早定义一个数据类型....
template<class T, class G>
class Info
{
public:
	Info(T a,G b)
	{
		this->m_a = a;
		this->m_b = b;
	}
	T m_a;
	G m_b;
};
int main()
{
	//Info A(2, 5);//不能让编译器自动推导数据类型,只能够显式声明模板的数据类型
	Info<int, int> A(2,5);
	return 0;
}

3.函数模板成员函数的创建时机

函数模板中的成员函数只会在调用模板时创建,它不同于普通的类,一个普通的类会在创建类时就创建成员函数。所以我们可以利用这个特性,大大扩展模板函数的应用范围。

class C1
{
public:
	int mem1=2;
};

class C2
{
public:
	int mem2=1;
};

template<typename T>
class Info
{
public:
	T m_class;
	int func1()
	{
		return m_class.mem1;
	}
	int func2()
	{
		m_class.mem2;
	}
};
int main()
{
	Info<C1>A;
	cout << A.func1() << endl;
	return 0;
}

4.类模板的对象作为形参

我们往往有时候会将用类模板创建出来的类对象传递到某个函数中,但是函数的形参应该写为什么数据类型呢?因为我们的对象是用类模板创建的,所以我们不得不考虑这个问题,为了解决这个问题,C++支持函数形参的数据类型有三种接收的方法:

我们先创建一个类模板并且写一个对象:

template<class T>
class Info
{
public:
	Info()
	{
		a = 20;
	}
	void Show()
	{
		cout<<this->a << endl;
	}
	T a;
};


int main()
{
	Info<int>A;
	return 0;
}

1.我们可以将形参指定具体数据类型

void print01(Info<int> A)
{
	A.Show();
}

2.我们可以将函数形参的模板类里的数据类型也模板化:

template<class T>
void print02(Info<T> A)
{
	A.Show();
}

3.我们可以直接将整个形参的数据类型模板化:

template<class T>
void print03(T A)
{
	A.Show();
}

5.类模板的继承问题:

首先我们创建一个父类模板,要是我们想要再让一个子类去继承这个父类,我们就要注意对于父类模板中的通用数据类型的处理问题:

1.子类继承时直接指明父类模板中通用数据类型的具体类型

template<typename T>
class Fath
{
public:
	T a;
};

class Son2 :public Fath<int>
{
};

2.子类也变成一个类模板

template<typename T>
class Fath
{
public:
	T a;
};


template<class T,class G>
class Son:public Fath<T>
{
public:
	G b;
};

!!!查看数据类型的方法:

头文件:<typeinfo>

语法:typeid(数据类型).name();

cout<<"char的数据类型:" << typeid(char).name() << endl;
cout <<"int的数据类型:" << typeid(int).name() << endl;

6.函数模板中的成员函数类外实现问题:

我们知道,普通的类成员函数要在类外实现,只需要在类内写个函数声明,并且在类外实现函数时加上作用域即可。而类模板的成员函数类外实现不同,它需要在作用域的 “ :: ”前加上参数列表。

template<class T>
class Info
{
public:
	void func();
	//{
	//	cout<<"this is Info" << endl;
	//}
};
template<class T>//类外实现
void Info<T>::func()
{
	cout << "this is Info" << endl;
}

int main()
{
	Info<int> A;
	A.func();
	return 0;
}

7.类模板分文件编写问题:

我们知道,类模板的成员函数是在调用函数的时候创建。而如果我们将一个类模板的函数声明放在一个头文件,而将成员函数的类外实现放在另一个源文件中,这样会带来问题。因为此时类模板的成员函数没有创建。

主函数文件:

 类模板头文件:

模板成员函数实现源文件:

 而这样运行会带来问题:

 就是因为类模板里面的成员函数没有创建。而解决这个问题,有两种方法:

方法1:直接将类模板和类模板里成员函数的实现放在同一个头文件中,并且将这个头文件后缀改为为.hpp。这是一个约定俗成的习惯,说明里面是个类模板。

方法二:直接包含类模板成员函数的源文件。因为类模板无法找到创建成员函数,所以直接包含源文件。

8.类模板与全局函数做友元:

全局函数在类模板中做友元,全局函数的实现可以在类模板中或者类模板外部。

1.全局函数实现在类模板中:

template<class T>
class Info
{
public:
	friend void func(Info A)
	{
		cout<<A.m_name<<endl;
	}
	Info(T a)
	{
		m_name = a;
	}
private:
	T m_name;
};

int main()
{
	Info<string> A("梁");
	func(A);
	return 0;
}

2.全局函数实现在类模板外:

全局函数做友元类外实现的几点注意事项:

1.类内全局函数声明要在函数名后加“ < > ”表面这个是个函数模板;

2.函数模板实现要在类创造前;

3.因为全局友元函数创建在类之前,而我们传进去了类模板。所以要提早声明类模板的存在;

4.全局函数不是类模板作用于下,不用加作用域;

template<class T>
class Info;

template<class T>
void func(Info<T> A)
{
	cout<<A.m_name<<endl;
}


template<class T>
class Info
{
public:
	friend void func<>(Info A);
	Info(T a)
	{
		m_name = a;
	}
private:
	T m_name;
};

int main()
{
	Info<string> A("梁");
	func(A);
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值