类模板的常见用法

class_template

类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。类模板用于实现类所需数据的类型参数化

template<class NameType, class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};
void test1() {
	Person<string, int>p1("regina", 23);
	p1.showPerson();
	Person<char, float>p2('i', 23.1);
	p2.showPerson();
}

image-20240421223604510

类模板做函数参数

void param(Person<string, int>& p) {
	p.mAge += 10;
	p.mName += "regina";
	p.showPerson();
}
int main() {
	Person<string, int> p("love", 20);
	param(p);
	return 0;
}

image-20240425171013806

类模板派生普通类

template<class NameType, class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};
class Subclass : public Person<string, int> {
public:
	Subclass(string name, int age):Person<string, int>(name,age) {
		this->mName = name;
		this->mAge = age;
	}
};

int main() {
	Subclass p("regina", 17);
	p.showPerson();
	return 0;
}

Subclass(string name, int age):Person<string, int>(name,age)这句话首先声明带参数的构造函数里面的类型。这个类型要和后面使用类模板的父类的构造函数参数类型一样。但是当你在定义一个类时,如果该类是模板类的实例化,你可以直接使用类名而不需要指定模板参数,因为编译器可以根据构造函数参数的类型来推断模板参数的类型

类模板派生类模板

派生类不仅继承了基类的成员和行为,还继承了基类的模板参数,并可以在派生类中添加额外的模板参数或覆盖基类模板参数。这样可以使得派生类在使用时更加灵活,并且可以根据需要进行定制化。

template<class MoneyType>
class Subclass : public Person<string, int> {
public:
	Subclass(string name, int age, MoneyType have_money):Person<string, int>(name,age), havemoney(have_money) { /*这里的继承就是多一个对于自己的模板的默认构造函数的声明*/
		this->mName = name;
		this->mAge = age;
	}
	void showPerson() {
		Person<string,int>::showPerson();
		cout << "have_money: " << (havemoney ? "true" : "false") << endl;
	}
private:
	MoneyType havemoney;
};
int main() {
	Subclass<bool> p("regina", 17,true);
    /*在 main 函数中只传递了一个 bool 类型的参数,因为 Subclass 类模板只接受一个模板参数 MoneyType*/
	p.showPerson();
	return 0;
}

image-20240425175021765

类模板类内实现和类外实现

类模板类内实现指的是在类模板的定义中直接实现成员函数的方法。与普通类相似,类模板也可以在类内部实现成员函数,这意味着成员函数的定义可以直接放在类模板的声明中,而不需要在类外部再单独定义。这种方式使得代码更加简洁,并且可以避免在类外部定义时重复书写模板的参数列表。


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

template<class NameType, class AgeType>这个一共声明了三次是为什么呢?

  1. 模板类声明: 在C++中,模板类的成员函数可以直接在类的定义中进行声明和实现,但也可以在类定义之外单独实现。在类定义之外实现时,必须再次使用 template<class NameType, class AgeType> 来指明这是一个模板类的成员函数。
    • 在类定义中进行声明的情况下,模板参数已经被识别,所以无需再次声明。
  2. 类定义外的实现: 如果你在模板类的定义之外实现成员函数,则需要重新指明该成员函数属于特定的模板类。这是因为C++编译器在进行模板解析时需要知道这些函数与哪个模板类相关联。

类模板头文件和源文件分离问题

类模板的头文件和源文件分离是指将类模板的声明和定义分别放置在不同的文件中的做法。通常,类模板的声明(包括模板类的成员函数声明)放在头文件(通常是 .hpp.h 文件),而类模板的实现(包括成员函数的定义)则放在源文件(通常是 .cpp 文件)中。

这种分离的做法有几个好处:

  1. 模块化: 将类模板的声明和定义分开可以更好地组织代码,使得代码结构更清晰,易于维护和理解。
  2. 编译时间: 如果类模板的实现放在源文件中,在使用该类模板的地方只需要包含头文件即可,这样可以减少编译时间,因为编译器只需要编译一次模板的实现,而不是每次包含头文件时都重新编译一次。
  3. 隐藏实现细节: 将类模板的实现放在源文件中可以隐藏模板的具体实现细节,只向用户暴露接口,提高了代码的封装性和安全性。
#pragma once
/*是一种预处理器指令,用于确保在编译过程中头文件只被包含一次,以避免多重包含问题。

当编译器遇到 #pragma once 时,它会在编译过程中记住这个头文件的路径和文件名,
并在后续的 #include 指令中检查是否已经包含了相同的文件。如果已经包含过了,
那么该头文件将被忽略,不会再次包含。这样可以防止由于多重包含而导致的编译错误
或者重复定义的问题。

使用 #pragma once 可以简化头文件的管理,使得头文件的包含更加简洁和高效。*/

template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age);
	void ShowPerson();
public:
	T1 mName;
	T2 mAge;
};

template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->mName = name;
	this->mAge = age;
}

template<class T1, class T2>
void Person<T1, T2>::ShowPerson() {
	cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}

模板类碰到友元函数

template<class T1, class T2> class Person;
template<class T1, class T2> void PrintPerson2(Person<T1, T2>& p);

template<class T1, class T2>
class Person {
	//1. 友元函数在类内实现
	friend void PrintPerson(Person<T1, T2>& p) {
		cout << "Name:" << p.mName << " Age:" << p.mAge << endl;
	}
	//2.友元函数类外实现
	//告诉编译器这个函数模板是存在
	friend void PrintPerson2<>(Person<T1, T2>& p);//<> 是用来指示编译器,PrintPerson2 是一个函数模板的声明

	//3. 类模板碰到友元函数模板
	template<class U1, class U2>
	friend void PrintPerson(Person<U1, U2>& p);
public:
	Person(T1 name, T2 age) {
		this->mName = name;
		this->mAge = age;
	}
	void showPerson() {
		cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
	}
private:
	T1 mName;
	T2 mAge;

};
template<class T1, class T2>
void PrintPerson2(Person<T1, T2>& p)
{
	cout << "Name2:" << p.mName << " Age2:" << p.mAge << endl;
}
void test1() {
	Person<string, int> p("regina", 25);
	PrintPerson(p);
	PrintPerson2(p);
}

以下是为什么需要在类外部声明友元模板函数的原因:

  1. 预声明模板函数:当你在类内使用 friend 关键字声明一个友元模板函数时,你实际上告诉编译器这个函数会访问类的私有成员。但是这并不等同于定义了这个模板函数。因此,你仍然需要在类外部定义并实现这个模板函数。如果没有外部的声明和实现,编译器将无法找到这个函数模板,导致链接错误。
  2. 函数模板的明确声明:在使用友元模板函数时,编译器需要明确知道它是一个模板函数,并且知道它如何被实例化。如果你不在类外部声明这个函数模板,编译器可能不会正确处理其实例化,导致意外的编译错误。
  3. 类内部声明只是告诉编译器:在类内部通过 friend 声明友元模板函数只是告诉编译器该模板函数能够访问类的私有成员,并没有定义该函数的具体实现。因此,必须在类外部提供该函数模板的声明和实现,以确保编译器知道它的具体实现位置。
posted @ 2024-04-25 22:25  ivanlee717  阅读(75)  评论(0编辑  收藏  举报