C++——详解类模板

news2024/9/24 19:17:12

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

💬文章目录

    • 一.类模板
      • ①类模板的定义与实例化
      • ②类模板的实例化
      • ③类模板的具体化(特化、特例化)
        • 1️⃣完全具体化
        • 2️⃣部分具体化
        • 3️⃣调用规则
      • ④类模板的成员函数类内类外实现
      • ⑤模板类作为函数参数
      • ⑥类模板的派生
        • 1️⃣类模板派生普通类
        • 2️⃣类模板派生类模板
        • 3️⃣普通类派生类模板
      • ⑦类模板与友元函数
        • 1️⃣非模板友元函数
        • 2️⃣约束模板友元函数
        • 3️⃣非约束模板友元函数
      • ⑧模板类的成员模板

一.类模板

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

①类模板的定义与实例化

函数可以定义函数模板,同样地,对于类来说,也可以定义一个类模板。类模板是针对成员数据类型不同的类的抽象,它不是一个具体实际的类,而是一个类型的类,一个类模板可以生成多种具体的类。类模板的定义格式如下所示:
在这里插入图片描述
其中,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;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/710348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

xxl-job源码改造集成:适配opengauss数据、适配单点登录等

目录 一、摘要 二、集成方案 三、集成步骤 3.1 springboot集成xxl-job 3.2 适配高斯数据库(postgresql) 3.3 页面集成 3.4 登录集成 3.5 接口集成 四、部署 一、摘要 公司现在打算重构产品&#xff0c;将原来的quartz替换成xxl-job&#xff0c;主要因为quartz不能动态…

多通道分离与合并

1、分离 2、合并 Mat img imread("F:/testMap/plan.png");Mat imgs[3];split(img,imgs);//分离Mat img0,img1,img2;img0 imgs[0];img1 imgs[1]; img2 imgs[2];Mat img_H;merge(imgs,3,img_H);//合并vector<Mat> imgsV; imgsV.push_back(img0);imgsV.push_b…

5.8.3 TCP连接管理(一)TCP连接建立

5.8.3 TCP连接管理&#xff08;一&#xff09;TCP连接建立 我们知道TCP是面向连接的传输协议&#xff0c;在传输连接的建立和释放是每一次面向连接通信必不可少的过程&#xff0c;因此传输连接的管理使得传输连接的建立和释放的过程都能够正常的进行。 一、使用Wireshark查看…

UE4/5用GeneratedDynamicMeshActor网格细分静态网格体【用的是ue5建模模式的box,其他的没有作用】

目录 制作 逻辑&#xff1a; 效果&#xff1a; ​编辑 代码&#xff1a; 制作 前面和之前的流程一样&#xff0c;打开插件和继承GeneratedDynamicMeshActor创建一个蓝图&#xff1a; 逻辑&#xff1a; 两个函数对应了两种细分方法 上面的细分模式是&#xff1a;Loop细…

JupyterNotebook基本操作

目录 Jupyter notebook文件操作 创建文件 修改文件名 复制文件 移动文件 删除文件 上传文件 下载文件 Jupyter notebook单元格操作 内容类型 编辑模式 快捷键 对照表 命令模式 编辑模式 查看快捷键 进入Jupyter Notebook主界面“File”中 VSCode配置Jupyter…

Bootstrap 表单

文章目录 Bootstrap 表单表单布局垂直或基本表单内联表单水平表单支持的表单控件输入框&#xff08;Input&#xff09;文本框&#xff08;Textarea&#xff09;复选框&#xff08;Checkbox&#xff09;和单选框&#xff08;Radio&#xff09;选择框&#xff08;Select&#xff…

【Go】Go 语言教程--介绍(一)

文章目录 Go 语言特色Go 语言用途第一个 Go 程序Go 语言环境安装UNIX/Linux/Mac OS X, 和 FreeBSD 安装Windows 系统下安装安装测试 Go 是一个开源的编程语言&#xff0c;它能让构造简单、可靠且高效的软件变得容易。 Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompso…

文生图技术stable diffusion入门实战

文章目录 0. 环境搭建0.1 Windows0.1.1 git环境安装0.1.2 python 环境搭建0.1.2.1 配置pip国内镜像源 0.1.3 stable diffusion环境搭建0.1.3.1 远程访问Stable diffusion 1. 基础知识1.1 Stable Diffusion Webui及基础参数1.2 参数说明 0. 环境搭建 0.1 Windows 0.1.1 git环境…

电子时钟制作(瑞萨RA)(3)----使用J-Link烧写程序到瑞萨芯片

概述 这一节主要讲解如何使用J-Link对瑞萨RA芯片进行烧录。 硬件准备 首先需要准备一个开发板&#xff0c;这里我准备的是芯片型号R7FA2E1A72DFL的开发板&#xff1a; 视频教程 https://www.bilibili.com/video/BV1kX4y1v7tL/ 电子时钟制作(瑞萨RA)----(2)使用串口进行程序…

初学spring5(八)整合MyBatis

学习回顾&#xff1a;初学spring5&#xff08;七&#xff09;AOP就这么简单 一、步骤 1、导入相关jar包 junit <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version> </dependency>m…

如何使用RobotFramework编写好的测试用例

目录 概述 命名 测试套件命名 测试用例命名 关键字命名 setup和teardown的命名 文档 测试套件文档 测试用例文档 用户关键字文档 测试套件结构 测试用例结构 工作流测试 数据驱动测试 用户关键字 变量 变量的命名 传参和返回值 避免使用Sleep关键字 我们AT…

算法设计与分析 课程期末复习简记(更新中)

网络流 下面是本章需要掌握的知识 • 流量⽹络的相关概念 • 最⼤流的概念 • 最⼩割集合的概念 • Dinic有效算法的步骤 • 会⼿推⼀个流量⽹络的最⼤流 下面对此依次进行复习 首先看流量网络的相关概念 上面是课程PPT中的定义&#xff0c;真是抽象 实际上&#xff0c;我们直接…

Maynor的博客专家成长之路——暨2023年中复盘

文章目录 博客专家成长之路——暨2023年中复盘前言念念不忘的博客专家每天只做三件事敲代码写博客健健身 我的感悟 不足之处未来&#xff1a;和CSDN共同成长最后 博客专家成长之路——暨2023年中复盘 前言 ​ 2023年不知不觉已经过去了半年有余&#xff0c;也是时候作年中复盘…

WEB界面测试

目录 前言&#xff1a; 摘要: WEB界面测试&#xff0c;最大的难度之一可能就是兼容性测试了 WEB界面测试&#xff0c;注重用户体验 WEB界面测试&#xff0c;注意用户的使用习惯 前言&#xff1a; Web界面测试是一种通过模拟用户与Web应用程序的交互来验证其功能和用户体验…

基于自然语言处理的多模态模型_综述

A Survey on Multimodal Large Language Models&#xff1b; 论文链接&#xff1a;https://arxiv.org/pdf/2306.13549.pdf 项目链接(实时更新最新论文&#xff0c;已获1.8K Stars)&#xff1a;https://github.com/BradyFU/Awesome-Multimodal-Large-Language-Models 研究背景 …

线程安全问题之原因及解决方案

线程安全问题 根本原因代码结构原子性解决方案&#xff1a;synchronized 内存可见性问题解决方案 volatile 指令重排序问题wait和notify判定一个代码是否线程安全&#xff0c;一定要具体问题具体分析!!! 根本原因 根本原因&#xff1a;多线程抢占式执行&#xff0c;随机调度。 …

ESP8266-NodeMCU搭建Arduino IDE开发环境

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、准备工作二、搭建步骤1.打开Arduino IDE 首选项2.打开Arduino IDE的“开发板管理器”3.在Arduino IDE的开发板菜单中找到“NodeMCU开发板”4.设置Arduino IDE的…

实验二:子程序设计实验

一、实验目的闻明找强的的掌握于程府的定又和调用方法掌握子程布的程库设计与调试方法 实验要求. 说明实现本实验需要掌握的知识及本实验害要的实验环境 二、实验要求了解萄单汇师培长程产没计与调武了解江编语子能店定义了解汇编语着子程序设计 实验内容 阐明实验具体内容及实…

3. Linux组件之内存池的实现

文章目录 一、为什么需要内存池二、内存池的工作流程三、内存池的实现3.1 数据结构3.2 接口设计3.2.1 创建内存池3.2.2 内存池销毁3.2.3 内存分配1. 分配小块内存2. 分配大块内存 3.2.4 内存池的释放3.2.5 内存池重置 3.3 完整代码 一、为什么需要内存池 应用程序使用内存&…

【我们一起60天准备考研算法面试(大全)-第一天 1/60(排序、进制)】【每天40分钟,我们一起用60天准备 考研408-数据结构(笔试)】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…