C++——详解类模板

在这里插入图片描述
纵有疾风起,人生不言弃。本文篇幅较长,如有错误请不吝赐教,感谢支持。

一.类模板

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

①类模板的定义与实例化

函数可以定义函数模板,同样地,对于类来说,也可以定义一个类模板。类模板是针对成员数据类型不同的类的抽象,它不是一个具体实际的类,而是一个类型的类,一个类模板可以生成多种具体的类。类模板的定义格式如下所示:
在这里插入图片描述
其中,template是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数,也可以是非类型参数。类型参数由关键字class或typename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。
举例:

template<class T,int a>
class Maker
{};

该例中,T为类型参数,a为非类型参数。
对类模板的说明如下:

  • 1.如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。
  • 2.模板参数名不能被当作类模板定义中类成员的名字。
  • 3.同一个模板参数名在模板参数表中只能出现一次。
  • 4.在不同的类模板或声明中,模板参数名可以被重复使用。
  • 5.类模板参数可以有缺省实参
typedef string T;
template<class T, int a>
class Maker
{
    T name;   //1.name不是string类型,全局的重命名为T的string类型已被屏蔽
    typedef double T;//2.错误:成员名不能与模板参数名同名
};
template<class T, class T>//3.错误:重复使用名为T的参数
class A;
template<class T>//4.模板参数名在不同模板间可以重复使用
class B;
//5.类模板参数可以有缺省参数
template<class T, int size = 1024>
class Myclass;
template <class T= char, int size>
class Myclass;

②类模板的实例化

template<class T>
class Image
{
  T value;
};
Image<int> a;//显示实例化,告诉编译器模板参数是int
//生成int类型的类定义
Image<double> a;//以下同理。
Image<string> a;

在这里插入图片描述
与函数模板不同的是,函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化则必须由程序员在程序中显式地指定。

⚠️注意:

①类模板本身不是类型、对象或任何其他实体,它只是一个模具,仅包含模板定义的源文件不会生成任何代码。为了出现任何代码,必须实例化模板,即必须提供具体的数据类型,以便编译器可以生成实际的具体的类的定义,实例化仅仅完成了类的定义(模板参数被具体的数据类型替换),产生的是模板类,此时还没有产生对象,你需要拿这个显示实例化后的模板类去创建对象。

你可以把这个实例化后的模板类想象成一个你自定义的数据类型(类),只是后边多了个参数列表而已。

②函数模版可以根据实参来隐式推导数据类型,类模版不会自动推导数据类型,不存在实参推演过程,要显示的告诉编译器是模板参数是什么类型。
所以我们要创建模板类要使用显示实例化的方法。

📝例如:

//1.类模版是把类中的数据类型参数化
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)
{
	this->name = name;
	this->age = age;
}
template<class NameType, class AgeType>
void Maker<NameType, AgeType>::printMaker()
{
	cout << "Name:" << this->name << " Age:" << this->age << endl;
}
void test()
{
	//1.类模版不会自动推导数据类型,要显示的告诉编译器是什么类型
	//Maker<string,int>显示实例化类模板,NameType被string替换,AgeName被int替换,
	//产生具体的类定义,即模板类,拿这个模板类去创建m这个对象。
	//可以把Maker<string,int>想象成一个自定义数据类型。
	Maker<string, int>m("感谢支持!强风吹拂king的博客", 20);
	m.printMaker();

	//2.注意传入的参数,传入参数类型要程序员自己把握
	Maker<int, int>m2(20, 21);
	m2.printMaker();
	//Maker<> m3("强风吹拂king",18);错误,必须通过参数列表告诉编译器是什么类型
}

在这里插入图片描述

💬注意:
类模板在实例化时,带有模板参数的成员函数并不会跟着实例化,这些成员函数只有在被调用时才会被实例化。

③类模板的具体化(特化、特例化)

实例化和具体化都是用于生成具体的定义,实例化是编译器借助你提供的数据类型来显示替换模板中的模板参数来实例化出类定义,而具体化则是你专门为一个具体的数据类型写类定义。
模板类具体化(特化、特例化)有两种:完全具体化和部分具体化,这是和函数模板不同。
📝示例代码:模板类AA

include <iostream>
using namespace std;
// 类模板AA
template<class T1, class T2>
class AA 
{
public:
    T1 m_x;
    T2 m_y;
    AA(const T1 x, const T2 y) :m_x(x), m_y(y) 
    {
      cout << "类模板:构造函数。\n"; 
     }
     void show() const
     {
       cout << "类模板:x = " << m_x << ", y = " << m_y << endl;
     }
};

Maker是模板类,有两个模板参数T1,T2,在模板Maker类中用T1和T2分别定义了m_x,和m_y,构造函数使用初始化列表给m_x,和m_y赋值。

1️⃣完全具体化

下例类模板AA有两个模板参数T1和T2,完全具体化的意思是为这两个模板参数指定具体的数据类型,第一个指定为int,第二个指定为string,然后为这个具体的数据类型AA<int,string>单独写类的定义。

// 类模板AA完全具体化
template<>
class AA<int, string> 
{
public:
    int m_x;
    string m_y;
    AA(const int x, const string y) :m_x(x), m_y(y) { cout << "完全具体化:构造函数。\n"; }
    void show() const;
};
void AA<int, string>::show() const 
{// 成员函数类外实现。
     cout << "完全具体化:x = " << m_x << ", y = " << m_y << endl;
}
2️⃣部分具体化

函数模板没有部分具体化的版本,这是类模板才有的。
部分具体化的意思是为多个模板参数的部分参数指定具体的数据类型。
下例模板类AA有两个模板参数T1和T2,T1继续用通用的数据类型,T2指定为string,部分具体化就是为这个部分具体化版本单独写类定义。

//类模板AA部分具体化版本
template<class T1>
class AA<T1, string>
{
public:
    T1 m_x;
    string m_y;
AA(const T1 x, const string y) :m_x(x), m_y(y) 
{
    cout << "部分具体化:构造函数。\n"; }
    void show() const;
};
template<class T1>
void AA<T1, string>::show() const
{ // 成员函数类外实现。
    cout << "部分具体化:x = " << m_x << ", y = " << m_y << endl;
}
3️⃣调用规则

具体化程度高的类优先于具体化程度低的类,具体化的类优先于没有具体化的类。

int main()
{
    AA<int, string> a1(66,"感谢支持强风吹拂king的博客");//调用完全具体化版本
    AA<char, string> a2('k', "感谢支持强风吹拂king的博客");//调用部分具体化的版本
    AA<char, char> a3('k','k');//调用类模板AA
}

🖲运行结果如下:
在这里插入图片描述

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

类外实现成员函数,我们要把成员函数写成函数模板形式,成员函数属于哪个类模板,我们要在成员函数名前加类模板的作用域。
注意:
普通类的类外实现,类名就是一种具体的数据类型,所以成员函数的作用域就是类名::
,但类模板名不是一种具体的数据类型,只有加上了参数列表才算是具体的数据类型,所以类模板作用域类名<模板参数>::

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)
{
	this->name = name;
	this->age = age;
}
template<class NameType, class AgeType>
void Maker<NameType, AgeType>::printMaker()
{
	cout << "Name:" << this->name << " Age:" << this->age << endl;
}
void test()
{
	Maker<string, int>m("强风吹拂king", 18);
	m.printMaker();
}

在这里插入图片描述

⑤模板类作为函数参数

类模板经过显示实例化形成模板类,模板类可以用于函数的参数和返回值,有三种形式:

  • (1)普通函数,参数和返回值只适用于类模板的一种实例化版本。
  • (2)函数模板,参数和返回值适用于类模板的所有实例化版本,相当于函数模板将模板类参数的显示实例化的类型给模板化。
  • (3)函数模板,参数和返回值是任意类型(支持普通类和模板类和其它类型)。相当于函数模板将整个模板类参数给模板化。

💬举例(1):
普通函数,参数和返回值只适用于某类模板的一种实例化版本。

#include <iostream>
#include <string>
using namespace std;
template<class T1, class T2>
class AA // 模板类AA。
{
public:
    T1 m_x;
    T2 m_y;
    AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
    void show() const
    {
        cout << "show() x = " << m_x << ", y = " << m_y << endl;
    }
};
// 采用普通函数,参数和返回值只适用于类模板AA的一种实例化版本AA<int,string>。
AA<int, string> func(AA<int, string>& aa)
{
    aa.show();
    cout << "调用了func(AA<int, string> &aa)函数" << endl;
    return aa;
}

类模板AA有无数个实例化版本,例如AA<string,int>,AA<string,double>等。大家可以把类名<数据类型>当做一个自定义数据类型,可以传值,也可以传引用。
上例中的func()函数只能处理AA类模板实例化出的众多模板类的一种AA<int,string>。实参也只能是模板类AA<int,string>创建的对象。

int main()
{
    AA<int, string> a1(6,"感谢支持强风吹拂king的博客");
    func(a1);
    return 0;
}

放图

func可以用于类模板的其他实例化版本吗?

AA<double, string> a1(3.14,"感谢支持强风吹拂king的博客");

不可以,🙅🏻‍♀️
在这里插入图片描述

举例(2):
函数模板,参数和返回值适用于类模板的所有实例化版本,相当于函数模板将模板类参数的显示实例化的类型给模板化。
如果想让func()函数支持类模板AA的所以实例化版本,我们需要将func(函数)写成函数模板形式,将模板类参数的显示实例化的类型给模板化,那么实参可以是所有实例化的模板类创建的对象。

// 函数模板,参数和返回值适用于类模板AA的所有实例化版本。
template <typename T1,typename T2>
AA<T1, T2> func(AA<T1, T2>& aa)
{
    aa.show();
    cout << "调用了func(AA<T1, T2> &aa)函数。"<<endl;
    return aa;
}
int main()
{
    AA<int, string> a1(6,"感谢支持强风吹拂king的博客");
    AA<double, char> a2(3.14,'k');
    func(a1);
    func(a2);
    return 0;
}

在这里插入图片描述

这时的func()函数,适用于类模板AA的所以实例化版本,那么适用于其他类模板吗?

template<class T1, class T2>
class BB // 模板类BB。
{
public:
    T1 m_x;
    T2 m_y;
    BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
    void show() const
    {
        cout << "show() x = " << m_x << ", y = " << m_y << endl;
    }
};
int main()
{
    BB<int, string> b1(6, "感谢支持强风吹拂king的博客");
    BB<double, char> b2(3.14, 'k');
    func(b1);
    func(b2);
    return 0;
}

在这里插入图片描述

这时候就要用到第三个版本了。
举例(3):
函数模板,参数和返回值是任意类型(支持普通类和模板类和其它类型)。相当于函数模板将整个模板类参数给模板化。

//函数模板,参数和返回值是任意类型。
template <typename T>
T func(T& aa)
{
    aa.show();
    cout << "调用了T func(T& aa)函数。\n";
    return aa;
}
int main()
{
    BB<int, string> b1(6, "感谢支持强风吹拂king的博客");
    BB<double, char> b2(3.14, 'k');
    func(b1);
    func(b2);
    return 0;
}

在这里插入图片描述

⑥类模板的派生

类模板和普通类一样也可以继承和派生,以实现代码复用。类模板的派生一般有三种情况:````

  • 类模板派生普通类
  • 类模板派生类模板
  • 普通类派生类模板

这三种派生关系可以解决很多实际问题。

1️⃣类模板派生普通类

在C++中,可以从任意一个类模板派生一个普通类。在派生过程中,类模板先实例化出一个模板类,这个模板类作为基类派生出普通类。类模板派生普通类的示例代码如下所示:

//类模板派生普通类
//类模板
template<class T>
class Father
{
public:
	Father();
	Father(T x,T y);
	~Father();
private:
	T x;
	T y;
};
//普通类 继承 类模版
//普通类Son公有继承类模板Father
class Son :public Father<int>//要告诉编译器父类的泛型数据类型具体是什么类型
{
public:
    Son(int x);
    ~Son;
private:
  int x;
};

📝解释:

在上述代码中,类模板Father派生出了普通类Son,其实在这个派生过程中类模板Father先实例化出了一个int类型的模板类,然后由这个模板类派生出普通类Son,因此在派生过程中需要指定模板参数类型。

2️⃣类模板派生类模板

类模板也可以派生出一个新的类模板,它和普通类之间的派生几乎完全相同。但是,派生类模板的模板参数受基类模板的模板参数影响。例如,由类模板Father派生出一个类模板Son。
📝示例代码如下:

//类模版 派生 类模版
template<class T>
class Father
{
public:
	Father();
	Father(T x,T y);
	~Father();
private:
	T x;
	T y;
};
template<class T1,class T2>
class Son :public Father<T2>//要告诉编译器父类的泛型数据类型具体是什么类型
//可以用T2代替,在使用Son模板在告诉确定具体的类型
{
public:
  Son(T1 m)
  {
    this->Name=m;
  }
  T1 Name;
};
void test()
{
    Son<string,int>s("盲僧");
    cout<<s.Name<<endl;
}

上述代码中,类模板Son由类模板Base派生,Father的部分成员变量和成员函数类型由类模板BSon的参数U确定,因此Son仍然是一个模板。类模板派生类模板技术可以用来构建类模板的层次结构。

3️⃣普通类派生类模板

普通类也可以派生类模板,普通类派生类模板可以把现存类库中的类转换为通用的类模板,但在实际编程中,这种派生方式并不常用。
📝例如:

class Father
{
public:
	Father();
	Father(int x);
	~Father();
private:
	int x;
};
template<class T>
class Son:public Father
{
public:
	Father();
	Father(T x,T y);
	~Father();
private:
	T x;
	T y;
};

在上述代码中,类Father是普通类,类模板Son继承了普通类Father。利用这种技术,程序设计者能够从现存类中创建类模板,由此可以创建基于非类模板库的类模板。

⑦类模板与友元函数

模板类的友元函数有三类:

  • 1)非模板友元函数:友元函数不是模板函数,而是利用模板类参数生成的函数。
  • 2)约束模板友元函数:模板类实例化时,每个实例化的类对应一个友元函数。
  • 3)非约束模板友元函数:模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。
1️⃣非模板友元函数

非模板友元函数就是将一个普通函数声明为友元函数。我们先来看一段代码:

#include <iostream> 
#include<string> 
using namespace std;
template<class T1,class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
Maker<string,int> m1("感谢支持强风吹拂king的博客",666);
void show()
{
    cout << "x = " << m1.m_x << ", y = " << m1.m_y << endl;
}
int main()
{
    show();
}

📝解释:

Maker是模板类,有两个模板参数T1,T2,在模板Maker类中用T1和T2分别定义了m_x,和m_y,构造函数使用初始化列表给m_x,和m_y赋值。
用模板类Maker定义全局对象m1,再定义全局函数show(),在全局函数show()中访问模板类Maker的私有成员,看下面运行结果(报错):
在这里插入图片描述
如果我们把全局函数show(),给声明成模板Maker的友元函数可以访问Maker的私有成员吗?

friend void show();

🖲执行结果如下:
在这里插入图片描述

在类模板Maker中,将普通函数show()函数声明为友元函数,则show()函数是类模板Maker所有实例的友元函数。例如,它是Maker<string,int>,Maker<string,int>,Maker<string,double>等的友元函数。

这种为类模板设置友元函数的方法在语法上没有任何错误,但是要用模板类去创建全局对象,代码这么写实在是太笨了,在学习友元的时候,如果Maker是普通类,我们可以把Maker普通类的引用传给友元函数而不必创建全局对象
那么Maker是类模板,可以这么写吗?答案是不可以。Maker只是类的通用描述,根本不存在类名叫Maker的类,但是你具体化函数模板,也就是后面加上参数列表<具体的数据类型>,这就可以当做一个具体的类,就可以像普通类一样使用。

#include <iostream> 
#include<string> 
using namespace std;
template<class T1,class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
    friend void show(const Maker<string, int>& a);
};
void show(const Maker<string, int>& a)
{
   cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
    Maker<string,int> m1("感谢支持强风吹拂king的博客",777);
    show(m1);
    return 0;
}

🖲执行结果如下:
在这里插入图片描述

如果main函数内部我们这样写,编译器会报错吗?

int main()
{
    Maker<char,int> m1('k',666);
    show(m1);
    return 0;
}

错误如下:
在这里插入图片描述那么我们重载show函数,为Maker<char,int> m1(“k”,666);单独写一个show函数,并声明为Maker的友元函数。

void show(const Maker<char, int>& a)
{
   cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}

🖲运行结果如下:
在这里插入图片描述

编译通过了,但是问题来了,现在这种方法解决友元函数的问题,但是很麻烦,要为模板类的每一个实例化版本都要写一个友元函数,所以,C++提供了一个解决方案,利用模板参数,自动生成友元函数,这个友元函数也叫作非模板友元。
非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数,只能在类内实现。

#include <iostream> 
#include<string> 
using namespace std;
template<class T1, class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
    //友元函数的形参用模板的通用类型
    friend void show(const Maker<T1, T2>& a)
    {
        cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
    }
};
int main()
{
    Maker<string, int> m1("感谢支持强风吹拂king的博客", 666);
    show(m1);
    Maker<char, int> m2('k', 666);
    show(m2);
    return 0;
}

🖲运行结果如下:
在这里插入图片描述

这种方法的本质是编译器利用模板参数为我们生成友元函数,但有一点我们要注意⚠️:
编译器利用模板参数生成友元函数,但是这个友元函数不是函数模板。

📝我举个例子证明“友元函数不是函数模板”一下:

#include <iostream> 
#include<string> 
using namespace std;
template<class T1, class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
    //友元函数的形参用模板的通用类型
    friend void show(const Maker<T1, T2>& a)
    {
        cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
    }
    friend void show(const Maker<string, int>& a);
    friend void show(const Maker<char, int>& a);
};
void show(const Maker<char, int>& a)
{
    cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
void show(const Maker<string, int>& a)
{
    cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
    Maker<string, int> m1("感谢支持强风吹拂king的博客", 666);
    show(m1);
    Maker<char, int> m2('k', 666);
    show(m2);
    return 0;
}

这两个有具体类型的函数和模板参数的函数,它们之间是什么关系?
如果上面带模板参数的是函数模板,那两个带具体类型的就是函数模板的具体化版本。在模板技术中,普通函数,具体化版本和函数模板是可以共存的,编译器会优先使用普通函数和具体化版本,现在这三个函数之间的关系也是这样的吗?
在这里插入图片描述
在这里插入图片描述

函数出现了重载,为什么编译器不是优先选择一个函数调用,而是报重定义的错误呢?原因很简单,void show(const Maker<T1, T2>& a)不是函数模板,只是借用模板参数,编译器创建模板类实例的时候,会用具体的数据类型去替代模板参数,生成友元函数的实体,与下面定义的具体类型的函数发生代码冲突。报重定义的错误。

💬小结一下:
从上面的程序中我们可以看出,非模板友元的使用特别得呆板。

  • 如果我们想为某种具体化的类模板单独写友元函数,这是做不到的,会报重定义的错误。(例如给Maker<string,int>单独写个友元函数,是不行🙅🏻‍♂️的)
  • 非模板友元函数,在哪个模板类定义,就只能用于这个类,不能用于其他类,因为非模板友元借助的是本类模板参数,并且还只能在本类的类内定义。
2️⃣约束模板友元函数

约束模板友元函数是将一个函数模板声明为类的友元函数。函数模板的实例化类型取决于类模板被实例化时的类型,类模板实例化时会产生与之匹配的具体化友元函数。

📝使用约束模板友元的步骤:

  • 第一步,在模板类的定义前,声明友元函数模板,为的是让模板类知道友元函数模板的存在。
  • 第二步再次声明友元函数模板,在类模板中将函数模板声明为友元函数。在声明友元函数时,函数模板要实现具体化,即函数模板的模板参数要与类模板的模板参数保持一致,目的是让编译器知道需要实例化的友元函数模板,类模板和函数模板本来没什么关系的,正是第二步让它们产生了关系,编译器在实例化某种数据类型的类模板时,也会实例化这种数据类型的模板函数。
  • 第三步,友元函数模板的定义,放在类模板下,因为友元函数是函数模板,所以可以和具体化的函数模板共存,不会发生代码冲突,报重定义的错误。
#include <iostream>
#include<string>
using namespace std;
// 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
template <typename T>
void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
template<class T1, class T2>
class AA // 模板类AA。
{
    friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
    T1 m_x;
    T2 m_y;
public:
    AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> // 第三步:友元函数模板的定义。
void show(T& a)
{
	cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(AA<int, string>& a)
{
	cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
    AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
    show(a1); // 将使用具体化的版本。
    AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
    show(a2);// 将使用通用的版本。
}

🖲运行结果如下:
在这里插入图片描述
解释:
第一次在声明时,show()函数与AA类模板没有关系,只是让编译器知道函数模板的存在,第二步,函数模板再次声明,并完成显示实例化,模板参数变为了AA类,当生成AA< char,string>模板类时,会生成与之匹配的show< char,string>()函数和作为友元函数。
这种友元函数模板可以应用于多个模板类,只是第二步,函数模板在类内具体化不一样。
把类模板AA的代码复制一遍,将类名改为BB。
📝代码举例:

#include <iostream> // 包含头文件
#include<string>
using namespace std;
// 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
template <typename T>
void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
template<class T1, class T2>
class AA // 模板类AA。
{
	friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
	T1 m_x;
	T2 m_y;
public:
	AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template<class T1, class T2>
class BB // 模板类BB。
{
	friend void show<>(BB<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
	T1 m_x;
	T2 m_y;
public:
	BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> // 第三步:友元函数模板的定义。
void show(T& a)
{
	cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(AA<int, string>& a)
{
	cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(BB<int, string>& a)
{
	cout << "具体BB<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
	AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
	show(a1); // 将使用具体化的版本。
	AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
	show(a2); // 将使用通用的版本。
	BB<int, string> b1(66, "感谢支持强风吹拂king的博客");
	show(b1); // 将使用具体化的版本。
	BB<char, string> b2(66, "感谢支持强风吹拂king的博客");
	show(b2); // 将使用通用的版本。
}

🖲运行结果如下:
在这里插入图片描述
关于友元函数的声明,多说一点:

template<typename U>//类模板的定义 
class A 
{
  ……//其他成员
  friend void func<U>();//声明无参友元函数func() 
  friend void show<>(A<U>& a);//声明有参友元函数show()
  ……//其他成员
  };

在上述代码中,将函数模板func()与show()声明为类的友元函数
func()是无参的函数模板,我们在学习函数模板的时候,必须显示或隐式的让编译器知道模板参数将要被哪个具体的数据类型替换,以便生成具体的函数定义,但这是无参的函数模板,隐式实例化不行(没参数),所以只能借助参数列表显示实例化。
show()函数模板有一个模板类参数,编译器可以根据函数参数隐式推导出模板参数,生成函数定义,因此show()函数模板具体化中<>可以为空。

💬小结一下:
两个要点:

  • 可以某种具体化的类模板单独写友元函数,例如上面代码中为AA<int, string> a1(66, “感谢支持强风吹拂king的博客”);提供单独具体化的友元函数
  • 可以支持多个类模板。

这种友元方案更有价值,就是语法稍微麻烦一点。

3️⃣非约束模板友元函数

模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。
这其实不科学,按理说每个实例化的类只需要一个友元函数即可,不需要n个,其他数据类型的友元函数和自己没关系,其实原因就是没有约束模板友元函数的第二步,没有将函数模板具体化,函数模板和类模板没有绑定起来。
声明非约束模板友元函数示例代码如下所示:
在这里插入图片描述
📝案例:

#include <iostream> // 包含头文件。
using namespace std; 
// 非类模板约束的友元函数,实例化后,每个函数都是每个每个类的友元。
template<class T1, class T2>
class AA
{
	template <typename T> friend void show(T& a); // 把函数模板设置为友元。
	T1 m_x;
	T2 m_y;
public:
	AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> void show(T& a) // 通用的函数模板。
{
	cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <>void show(AA<int, string>& a) // 函数模板的具体版本。
{
	cout << "具体<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
	AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
	show(a1); // 将使用具体化的版本。
	AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
	show(a2);// 将使用通用的版本。
}

🖲运行结果如下:
在这里插入图片描述
虽然说运行结果和约束模板友元函数一致,但还是推荐使用约束模板友元函数。

⑧模板类的成员模板

在实际开发中,类模板中有类模板和函数模板很常见。我们接下来就了解一下。
📝示例代码:

#include <iostream> 
#include<string> 
using namespace std;
template<class T1,class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) { }
    void show()
    {
        cout << "x = " << m1.m_x << ", y = " << m1.m_y << endl;
    }
};
int main()
{
    Maker<string,int> m1("感谢支持强风吹拂king的博客",666);
    m1.show();
}

Maker是模板类,有两个模板参数T1,T2,在模板Maker类中用T1和T2分别定义了m_x,和m_y,构造函数使用初始化列表给m_x,和m_y赋值。

现在,在Maker模板中,加入BB模板类,和show函数模板。那么BB类模板和show函数模板都是是类Maker的成员模板。用成员类模板可以创建成员对象,函数模板可以访问私有成员。

#include <iostream> 
#include<string> 
using namespace std;
template<class T1, class T2>
class Maker
{
private:
    T1 m_x;
    T2 m_y;
public:
    Maker(const T1 x, const T2 y) : m_x(x), m_y(y) { }//Maker的构造函数,借用初始化列表初始化成员变量
    void show()
    {
        cout << "类模板Maker的普通show函数调用" << endl;
        cout << "m_x: " << m_x << ", m_y : " << m_y << endl;
    }
    //成员模板BB
    template<class T>
    class BB
    {
    public:
        T m_a;
        T1 m_b;//借用Maker的模板参数创建变量
        void show()
        {
            cout << "成员模板BB的普通show函数调用" << endl;
            cout << "成员模板BB对象的:m_a=" << m_a << ",m_b:" << m_b << endl;
        }
    };
    BB<string>  b;//用成员模板可以创建成员对象
    template<typename T>
    void show(T tt)
    {
        cout << "类模板Maker的show函数模板调用" << endl;
        cout << "tt=" << tt << endl;
        cout << "Maker对象的m_x:" << m_x << ",m_y:" << m_y << endl;
    }
};
int main()
{
    Maker<string, int> a("感谢支持强风吹拂king的博客",666);
    a.show();//调用类模板Maker的普通show函数
    a.b.m_a = ("纵有疾风起,人生不言弃");//初始化成员模板BB类的成员变量m_a
    a.b.show();//调用成员模板BB的普通show函数
    a.show("2023.6.25");//调用Maker的show函数模板
}

在这里插入图片描述

还有一个问题:如果我想把成员模板类外实现怎么办呢?
将鼠标放到要类外实现的成员模板上,就会出现该成员模板的作用域。
在这里插入图片描述

在这里插入图片描述

template<class T1, class T2>template<class T>void Maker<T1, T2>::BB<T>::show()
{
    cout << "类外实现成员模板BB的普通show函数调用" << endl;
    cout << "成员模板BB对象的:m_a=" << m_a << ",m_b:" << m_b << endl;
}
template<class T1, class T2>template<typename T>void Maker<T1, T2>::show(T tt)
{
    cout << "类外实现类模板Maker的show函数模板调用" << endl;
    cout << "tt=" << tt << endl;
    cout << "Maker对象的m_x:" << m_x << ",m_y:" << m_y << endl;
}

在这里插入图片描述

  • 31
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 41
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿标de杂货铺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值