C++模板详解

目录

1,什么是c++的模板

2,模板的概念

3,函数模板

(1)什么是函数模板

(2)函数模板的定义格式

(3)案例

(4)函数模板的实例化

(1)隐式实例化

(2)显式实例化

(5)函数模板的重载

(6)函数模板与普通函数小结

(7)使用函数模板要注意的问题

4,类模板

1,定义类模板的格式

2,类与类模板的关系

3,类模板的实例化

(1)使用类模板创建对象时,必须指明具体的数据类型。

(2)当类模板有两个模板形参时,创建对象时,类型之间要用逗号分隔开。

(3)可以使用对象指针的方式来实例化

(4)案例

(5)模板声明或定义的作用域

(6)在类模板外部定义成员函数

5,类模板与友元函数

1,非模板友元函数

2,约束模板友元函数

(1)在类定义的前面声明函数模板

(2)在类模板中将函数模板声明为类的友元函数

(3)为友元函数模板提供定义

3、非约束模板友元函数


1,什么是c++的模板

程序设计中经常会用到一些程序实体:它们的实现和所完成的功能基本相同,不同的仅 仅是所涉及的数据类型不同。而模板正是一种专门处理不同数据类型的机制。

模板是泛型程序设计的基础(泛型generic type——通用类型之意)。

函数、类以及类继承为程序的代码复用提供了基本手段,还有一种代码复用途径——类属类型(泛型),利用它可以给一段代码设置一些取值为类型的参数(注意:这些参数 的值是类型,而不是某类型的数据),通过给这些参数提供一些类型来得到针对不同类 型的代码。

下面可以看一个例子

#include <iostream>
using namespace std;
void swapint(int* a, int* b)
{
	int p = *a;
	*a = *b;
	*b = p;
}
void showint(int a, int b)
{
	cout << "a=" << a << "," << "b=" << b << endl;
}
void swapfloat(float* a, float* b)
{
	float p = *a;
	*a = *b;
	*b = p;
}
void showfloat(float a, float b)
{
	cout << "a=" << a << "," << "b=" << b << endl;
}
int main()
{
	int a1 = 3, b1 = 12;
	swapint(&a1, &b1);
	showint(a1, b1);
	float a2 = 12.3, b2 = 45.1;
	swapfloat(&a2, &b2);
	showfloat(a2, b2);
	return 0;
}

输出结果

a=12,b=3

a=45.1,b=12.3

这个例子就是实现了两种不同类型的两个数之间的交换,我们这里只是实现了两种,如 果要实现很多种,那代码量就非常大。如果可以只用一个函数和类来描述,那将会大大 减少代码量,并能实现程序代码的复用性,所以这里就要用到模板了

2,模板的概念

值和类型是数据的两个主要特征,它们在C++中都可以被参数化。

数据的值可以通过函数参数传递,在函数定义时数据的值是未知的,只有等到函数调用 时接收了实参才能确定其值。——这就是值的参数化。

数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当函数 调用时,编译器根据传入的实参自动推断数据类型。——这就是类型的参数化(把类型 定义为参数)

带有类型参数的一段代码,实际上代表了一类代码,可以实现代码的复用,常用模板来 实现。

模板——是一段带有类型参数的程序代码,可以通过给这些参数提供一些类型来得到针 对不同类型的具体代码。

模板是C++中支持泛型编程的重要方法,是实现代码复用的工具,是实现参数化多态(类 型参数化)的手段,减轻了编程及维护的工作量和难度。

一个模板并非一个实实在在的类或函数,仅仅是一个类或函数的描述。模板是规则,通 过模板可以演化出多个具体的类或函数。

在C++中,能带有类型参数的代码可以是函数和类,所以模板一般分为函数模板和类模 板。

3,函数模板

(1)什么是函数模板

例如:我们求两个数相加的函数,实现int、float、double等多种类型数据相加

如果不使用模板的话,我们可以利用重载函数,定义多个同名函数实现函数重载, 但是采用函数重载,代码重复率高,可维护性极差。

这里可以采用函数模板,定义一个add函数模板,借助它实现多种类型数据相加

函数模板——实际上是定义一个通用函数,它所用到的数据的类型(包括返回值类 型、形参类型、局部变量类型)均被作为参数:不指定具体类型,而是用一个虚拟 的类型来代替(实际上是用一个标识符来占位)。凡是函数体相同的函数都可以用 这个模板来代替,在函数调用时根据传入的实参来逆推出真正的类型。这个通用函 数就称为函数模板。

通过函数模板实现类型参数化,即把类型定义为参数,从而实现代码复用。

可见:函数模板并不是一个可以直接使用的函数,它是可以产生多个函数的模板。

(2)函数模板的定义格式

template <typename 形参名, typename 形参名...>     //模板头(模板说明)

返回值类型  函数名(参数列表)                   //函数定义

{

        函数体;

}

说明:

1,template是声明模板的关键字,告诉编译器开始泛型编程。

2,尖括号<>中的typename是定义形参的关键字,用来说明其后的形参名为类型 参数,(模板形参)。Typename(建议用)可以用class关键字代替,两者没有区别。

3,模板形参(类属参数)不能为空(俗成约定用一个大写英文字母表示),且在函 数定义部分的参数列表中至少出现一次。与函数形参类似,可以用在函数定义的各 个位置:返回值、形参列表和函数体。

4,函数定义部分:与普通函数定义方式相同,只是参数列表中的数据类型要使用 尖括,号<>中的模板形参名来说明。当然也可以使用一般的类型参数。

(3)案例

用函数模板实现多种类型数据的相加

#include <iostream>
using namespace std;
template<typename T>//模板头,template关键字告诉编译器开始泛型编程
T add(T t1, T t2)//类型参数化为T
{
	return t1 + t2;
}
int main()
{
	cout << add(12, 34) << endl;
	cout << add(12.2,45.6) << endl;
	return 0;
}

输出结果

46

57.8

当调用add()函数传入int型参数12和参数34时,形参T被替换成int,得到结果 为46

当传入float型参数12.2和参数45.6时,形参T被替换成float,得到结果为57.8

避免了为int型定义一个求和函数,再为float型定义一个求和函数,实现了代码 复用。

利用模板实现了不同类型的变量相加,可以发现利用模板的话大大减少了代码量, 提高了代码复用性

注意:对函数模板的调用应使用实参推演来进行。

add(2,3)  或 int a=2,b=3;  add(a,b)

//对,编译器会根据传入的实际参数2、3推演出传入的是int类型参数,则T为int 类型

add(int, int)   //错,不能在函数调用的参数中指定模板形参的类型来直接将类型传 入

(4)函数模板的实例化

定义好的函数模板不可以直接使用,只相当于一个模具、规则,可使用不同类型的 参数来调用,可减少代码的书写,提高代码复用性。但使用函数模板不会减少最终 可执行程序的大小,因为在调用函数模板时,编译器会根据调用时的参数类型进行 相应的实例化。

不能直接使用函数模板实现具体操作,必须对模板进行实例化,即将模板参数实例 化,就是用具体的类型参数去替换函数模板中的模板参数,生成一个确定的具体类 型的真正函数,才能实现运算操作。

函数模板实例化的方法有两种:

隐式实例化:根据具体的函数调用形式,推演出模板参数类型。

显式实例化:通过显式声明形式指定模板参数类型

(1)隐式实例化

根据函数调用时传入的数据类型,推演出模板形参类型。模板形参的类型是隐 式确定的。

比如上面的多种类型相加

第一次调用add()函数模板:add(12,34),

①编译器根据传入的实参(12和34—>int型),推演出模板形参类型是int

②会将函数模板实例化出一个int类型的函数:

int add(int t1, int t2)

{

        return t1 + t2;

}

编译器生成具体类型函数的过程称为实例化,生成的函数称为模板函数。

③生成int类型的函数后,再将实参12和34传入进行运算。

第二次调用add()函数模板:add(12.2, 45.6)

实参为float类型的数据,编译器先将模板实例化为如下模板函数后,再将实 参12.2和45.6传入进行运算:

float add(float t1, float t2)

{

        return t1 + t2;

}

可见:每次调用都会根据不同的类型实例化出不同类型的函数,所以最终可执 行程序的大小并不会减少,只是提高了程序员对代码的复用。

存在的问题:隐式实例化不能为同一个模板形参指定两种不同的类型。例如: add(1, 1.2),这样调用,两种形参类型不同,编译器会出错。

解决方法:采用显式实例化。

(2)显式实例化

通过显式声明形式指定模板参数类型。

显式实例化语法格式:

template 函数返回值类型 函数名<实例化的类型>(参数列表);

注意:这是声明语句,要以分号结束,<>中是显式实例的数据类型,即要实例 化出一个什么类型的函数。如:显示实例化为int,则在调用时,不是int 类 型的数据会 转换为int类型进行计算。

例如:将例5-1中的add()函数模板显式实例化为int类型

template int add<int>(int t1, int t2);

代码:

#include <iostream>
using namespace std;
template<typename T>
T add(T t1, T t2)
{
		return t1 + t2;
}
template int add<int>(int t1, int t2);//显示实例化为int类型
int main()
{
		cout << add<int>(12, 'A') << endl;//函数模板调用:A-65
		cout << add(1.4, 5.7) << endl;//隐式实例化:自动实参推演
		cout << add<int>(23.4, 44.2) << endl;//显示声明可省,结果为67
		return 0;
}

输出结果

77

7.1

67

上面利用显示实例化指定模板参数是int类型,所以在调用函数时会自动将参 数转换为int类型。比如上面在调用int类型模板函数时,传入一个字符’A’, 则编译器会将字符类型的’A’转换为int类型,然后再与12相加得出结果

注意:对于给定的函数模板实例,显式实例化声明在一个文件中只能出现一次, 并 且在这个文件中必须给出函数模板的定义,如果定义不可见就会发生错误。

C++编译器也在不断完善,模板实例化的显式声明有时可以省略,只在调用时 用<> 显式指定要实例化的类型也可以。

例如:add(23.4, 44.2)函数调用如果改为add<int> (23.4, 44.2),则也会得出结果 67

(5)函数模板的重载

前面学习过函数的重载。而函数模板可以用来创建一个通用功能的函数,以支持多 种不同形参,不同类型的参数调用,就产生一系列重载函数。

比如定义一个两个数相加的模板
根据传入参数不同,会实例化出不同的函数,比如

template<typename T>

T add(T t1, T t2)

{

        return t1 + t2;

}

根据传入参数不同,会实例化出不同的函数,比如

int add(int t1, int t2)            //int类型参数实例化出的函数

{

        return t1 + t2;

}

double add(double t1, double t2)  //double类型参数实例化出的函数

{

        return t1 + t2;

}

最终运行的程序中会有这两个函数,实际上就是重载函数,调用时会根据传入的参 数类型来调用相应的函数

此外,函数模板本身也可以重载,即相同函数模板名可以具有不同的函数模板定义, 当进行函数调用时,编译器根据实参的类型与个数来决定调用哪个函数模板来实 例化一个函数

比如:定义一个求两个任意类型数据最大值的函数,还要定义一个求三个任意类型 数据最大值的函数,都是求任意类型数据的最大值,可以定义重载的函数模板来实现。

#include <iostream>
using namespace std;
int maxx(int a, int b) //非模板函数,求两个int类型数据的最大者
{
	cout << "调用非模板函数" << endl;
	return a > b ? a : b;
}
template<typename T>//定义求两个任意类型数据的最大值
T maxx(const T t1, const T t2)
{
	cout << "调用两个参数的模板函数" << endl;
	return t1 > t2 ? t1 : t2;
}
template<typename T>//定义求三个任意类型数据的最大值
T maxx(T t1, T t2, T t3)
{
	cout << "调用三个参数的模板函数" << endl;
	return max(max(t1, t2), t3);
}
int main()
{
	cout << maxx(1, 2) << endl;//调用非模板函数
	//当函数模板和普通函数都符合调用时,优先选择普通函数
	cout << maxx(1, 2, 3) << endl;//调用三个参数的函数模板
	cout << maxx('a', 'c') << endl;//调用两个参数的函数模板
cout << maxx(6, 3.2) << endl;//调用非模板函数
	cout << maxx<>(1, 2) << endl;   //若显示使用函数模板,则使用<> 类型列表
	cout << maxx(3.0, 4.0) << endl;  //如果函数模板产生更好的匹配使用函数模板
	cout << maxx(5.0, 6.0, 7.0) << endl;  //重载
	//cout<<max('a', 100)<<endl;  //调用普通函数(报错)
	return 0;
}

输出结果

调用非模板函数

2

调用三个参数的模板函数

3

调用两个参数的模板函数

C

调用非模板函数

6

调用两个参数的模板函数

2

调用两个参数的模板函数

4

调用三个参数的模板函数

7

从运行结果可以看出来,当函数模板和普通函数都符合调用时,优先选择普通函数

但如果函数模板能够更好地实例化一个匹配的函数,则调用时将选择函数模板,比 如上面的maxx(3.0,4.0)。

注意:如果有不同类型参数,则只允许使用非模板函数,因为模板是不允许自动类 型转化的,但普通函数可以进行自动类型转换,

(6)函数模板与普通函数小结

两者的区别:函数模板不允许自动类型转化,普通函数则能进行自动类型转换

函数模板和普通函数在一起时的调用规则:

函数模板可以像普通函数一样被重载

C++编译器优先考虑普通函数

如果函数模板可以产生一个更好的匹配,那么选择模板

可以通过空模板实参列表的语法限定编译器只通过模板匹配

(7)使用函数模板要注意的问题

(1)函数模板中的每一个类型参数在函数参数表中必须至少使用一次。例如:下 面的函数模板声明是不正确的:函数模板声明了两个参数T1与T2,但在使用 时 只使用了T1,没使用T2。

template <typename T1, typename T2>

void func(T1 t)

{

          //……

}

(2)在全局域中声明的与模板参数同名的对象、函数或类型,在函数模板中将被 隐藏。例如:在函数体内访问num是访问的T类型的num,而不是全局变量num

int num;

template <typename T>

void func(T t)

{

     T num;

}

3)函数模板中定义声明的对象或类型不能与模板参数同名。例如:

template <typename T>

void func(T t)

{

        Typedef float T;  //错误,定义的类型与模板参数名相同

        //……

}

(4)模板参数名在同一模板参数表中只能使用一次,但可在多个函数模板声明或 定义之间重复使用。例如:

template <typename T, typename T>  //错误,在同一个模板中重复定义模板参数

void func1(T t1, T t2) { }

template<typename T>

void func2(T t1) { }

template <typename T>    //在不同函数模板中可重复使用相同模板参数名

void func3(T t3) { }

(5)模板的定义和多处声明所使用的模板参数名不一定要必须相同。例如:

//模板的前向声明

template <typename T>

void func1(T t1,T t2);

//模板的定义

template <typename U>

void func1(U t1, U t2)

{

    //……

}

6)函数模板如果有多个模板参数,则每个模板类型前都必须使用关键字typename 或class修饰。例如:

template <typename T, class U>   //两个关键字可以混用

void func(T t, U u)  { }

template <typename T,U>        //错误,每一个模板参数前都要有关键字修饰

void func(T t, U u)  { }

4,类模板

除了函数模板外,C++中还支持类模板。类模板是对成员数据类型不同的类的抽象,它 说明了类的定义规则,一个类模板可以生成多种具体的类。与函数模板的定义形式类似, 类模板也是使用template关键字和尖括号“<>”中的模板形参进行说明,类的定义形 式与普通类相同。

类模板并不能直接使用,需要对其进行实例化,实例化的方法是:类名<具体类型> 对 象名。通过类模板实例化得到的对象的操作办法,与普通类对象的操作办法相同。

1,定义类模板的格式

与函数模板的定义形式类似,类模板也是使用template关键字和尖括号“<>”中 的模板形参进行说明,类的定义形式与普通类相同。格式如下:

template<typename 形参名,typename 形参名…>

class 类名

{

        ………

}

说明:

(1)类模板中的关键字含义与函数模板相同。

(2)类模板中的类型参数可用在类声明和类实现中。类模板的模板形参(类型参 数)不能为空,一旦声明了类模板就可以用类模板的形参名声明类中的成员变量和 成员函数,即在类中使用内置数据类型的地方都可以使用模板形参名来代替。

例如:

template<typename T>   //这里不能有分号。一个模板形参

class A

{

public:

        T a;   //成员变量

        T b;   //成员变量

        T func(T a, T b);  //成员函数声明

};

2,类与类模板的关系

由于类模板包含类型参数,因此也称为参数化类,如果说类是对象的抽象,对象是 类的实例,则类模板是类的抽象,类是类模板的实例。

3,类模板的实例化

定义了类模板后就要使用类模板创建对象以及实现类中的成员函数,这个过程其实 也是类模板实例化的过程,实例化出的具体类称为模板类。

(1)使用类模板创建对象时,必须指明具体的数据类型。

例如:用上述定义的模板类A创建对象,则在类A后面跟<>,并在里面表明相应 的类型:

A<int>  a;    //类A中凡是用到模板形参的地方都会被int类型所代替

强调:与函数模板不同的是,类模板在实例化时,必须在尖括号中为模板形参显式 地指明数据类型(实参),编译器不能根据给定的数据推演出数据类型。即:不存 在将整型值10推演为int类型传递给模板形参的实参推演过程,必须要在<>中指 定int类型。

(2)当类模板有两个模板形参时,创建对象时,类型之间要用逗号分隔开。

template<typename T1, typename T2>

class B

{

public:

        T1 a;

        T2 b;

        T1 func(T1 a, T2& b);

};

B <int, string>  b;    //创建模板类B的一个对象

(3)可以使用对象指针的方式来实例化

Point<float, float> *p1 = new Point<float, float>(10.6, 109.3);

Point<char*, char*> *p = new Point<char*, char*>("东京180度", "北纬210度");

注意:赋值号两边都要指明具体的数据类型,且要保持一致。

(4)案例

定义一个类模板实现两个数比较大小(求两数的大者与小者)

#include <iostream>
using namespace std;
template<typename T>
class Compare
{
private:
	T t1, t2;
public:
	Compare(T a, T b) :t1(a), t2(b) {}
	T max() { return t1 > t2 ? t1 : t2; }
	T min() { return t1 < t2 ? t1 : t2; }
};
int main()
{
	Compare<int> c1(1, 2);          //定义int类型的类对象
	cout << "int max: " << c1.max() << endl;
	Compare<double> c2(1.2, 3.4);    //定义double类型的对象
	cout << "double min: " << c2.min() << endl;
	Compare<char> c3('a', 'b');        // 定义char类型的对象
	cout << "char max: " << c3.max() << endl;
	return 0;
}

输出结果

int max: 2

double min: 1.2

char max: b

创建对象时,在类名后<>中指定模板形参的类型,编译器先根据类型实例化出一个 具体的类,然后再创建这个具体实例的对象。

Compare<int> c1(1, 2)语句,在编译时创建一个类:

class Compare

{

private:

        int t1, t2;

public:

        Compare(int a, int b) :t1(a), t2(b){}

        int max(){ return t1 > t2 ? t1 : t2; }

        int min(){ return t1 < t2 ? t1 : t2; }

};

然后再根据这个类创建对象c1。这和函数模板一样,都不会减少最终执行程序的 代码。

说明:

如果对函数模板add(T t1, T t2)进行add(1, 1.2)调用,则编译会报错,因为指定了两 种类型的参数,编译器无法确定依据哪个参数来调用。但对于类模板来说,编译器 则不会报错。

template<typename T>

class A

{

public:

        A( ) { };

        T add(T t1, T t2)

        {

                return t1+t2;

        }

};

A<int> a;     //定义对象a

a.add(1,1.2);   //不会报错。因为在定义对象a时就已经指定是int类型,当add()函数模板实例化时也会实例化出一个int类型的函数,它会自动将double类型的1.2转换为int类型的1。

(5)模板声明或定义的作用域

模板的声明或定义只能在全局、命名空间或类范围内进行,不能在局部范围、函数 内进行,比如不能在main()函数中声明或定义一个模板。声明或定义一个模板还有 以下几点需注意:

(1)如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏。

(2)模板参数名不能被当作类模板定义中类成员的名字。

(3)同一个模板参数名在模板参数表中只能出现一次。

(4)在不同的类模板声明或定义中,模板参数名可以被重复使用。

(6)在类模板外部定义成员函数

类中的成员函数既可以在类中定义,也可以在类外定义。类模板中的成员函数同样 可以在类模板的定义中定义,也可以在类模板定义之外定义,只是在类外定义成员 函数时需要带上模板头:template<模板参数表>。

(1)在类模板外部定义成员函数的方法

template<模板形参表>

函数返回类型 类名<模板形参名>::函数名(参数列表){}

说明:

(1)template是类模板的声明,在实现成员函数时,也要加上类作用域,而且在 类名后要用<>指明类的模板形参。

(2)template后的模板形参应与要定义的类模板形参一致。

例如:有下列类模板的定义

template<typename T1, typename T2>  //模板头

class B

{

public:

        T1 a;

        T2 b;

        T1 func(T1 a, T2& b);

};

如果在类模板外定义类B的成员函数func(),其实现如下:

template<typename T1, typename T2>

T1 B<T1,T2>::func(T1 a, T2& b) { }

#include <cstdlib>
#include <iostream>
using namespace std;
template<typename T>   //类模板
class Array
{
private:
	int size;
	T* ptr;
public:
	Array(T arr[], int s);   //构造函数
	void show();
};
template<typename T>   //类模板外定义其成员函数
Array<T>::Array(T arr[], int s)
{
	ptr = new T[s];
	size = s;
	for (int i = 0; i < size; i++)
	{
		ptr[i] = arr[i];
	}
}
template<typename T>   //类模板外定义其成员函数
void Array<T>::show()
{
	for (int i = 0; i < size; i++)
		cout << *(ptr + i) << " ";
	cout << endl;
}
int main()
{
	char cArr[] = { 'a', 'b', 'c', 'd', 'e' };
	Array<char> a1(cArr, 5);   //创建类模板的对象
	a1.show();
	int iArr[10] = { 1, 2, 3, 4, 5, 6 };
	Array<int> a2(iArr, 10);
	a2.show();
	system("pause");
	return 0;
}

输出结果

a b c d e

1 2 3 4 5 6 0 0 0 0

注意:类模板在实例化时,带有模板形参的成员函数并不随着自动被实例化,只有 当它被调用或取地址时,才被实例化。

上面创建了两个函数,一个构造函数,一个show函数,主要是在类外定义成员函 数的实现方式掌握就行

5,类模板与友元函数

普通的类中可以声明友元函数和友元类,同样在类模板中也可以声明友元函数和友元类。 类模板中的友元函数可以有三种形式:非模板友元函数、约束模板友元函数、非约束模板友元函数

1,非模板友元函数

非模板友元就是在类模板中声明普通的友元函数和友元类。

例如:在一个类模板中声明一个友元函数

template <typename T>

class A

{

        T  t;

public:

        friend void func();

}

在类模板A中声明了一个普通的友元函数func(),func()函数是类模板A所有实例 的友元函数,它可以访问全局对象,也可以使用全局指针访问非全局对象,可以创 建自己的对象,也可以访问独立于对象的模板的静态数据成员。

类模板的友元函数也可以拥有模板参数,用于操作类模板对应实例类型的对象

例如:

template <typename T>

class A

{

        T  t;

public:

        friend void show(const A<T>& a);

}

说明:show()函数并不是模板函数,只是使用一个模板作参数,这就要求在使用友 元函数时必须要显式具体化,指明友元函数要引用的参数的类型。

例如:

void show(const A<int>& a);  //模板形参为int类型的show()函数是A<int>类的友元 函数

void show(const A<double>& a);

//模板形参为double类型的show()函数是A<double>类的友元函数

#include <cstdlib>
#include <iostream>
using namespace std;
template<typename T>
class A
{
	T x;
public:
	A(const T& t)
	{
		x = t;
	}
	friend void show(const A<T>& a);//有参友元函数
};
void show(const A<int>& a)//模板形参为int类型(显示具体化)
{
	cout << "int:" << a.x << endl;
}
void show(const A<double>& a)//模板形参为double类型(显示具体化)
{
	cout << "double:" << a.x << endl;
}
int main()
{
	A<int> a(10);//创建int类型对象
	show(a);
	A<double> b(12.5);//创建double类型对象
	show(b);
	return 0;
}

输出结果

int:10

double:12.5

注意:在调用有模板形参的友元函数时,要对友元函数显式具体化,它们是各自相 同类型的对象的友元函数。

2,约束模板友元函数

这种友元函数本身就是一个函数模板,但其实例化类型取决于类被实例化时的类型 (被约束)。每个类的实例化都会产生一个与之匹配的具体化的友元函数。

(1)在类定义的前面声明函数模板

template<typename T>

void func();

template<typename T>

void show(T& t);

(2)在类模板中将函数模板声明为类的友元函数

template<typename U>

class A

{         ……     

        friend void func<U>();   //友元函数模板

        friend void show<>(A<U>& a);   //友元函数模板

};

说明:函数名后的<>指出函数模板要实例化的类型,它是由类模板的参数类型 决定的。

例如:如果定义一个类模板对象:A <int> a,则编译器会生成下面类定义:

class A

{         ……

        friend void func<int>();

        friend void show<>(A<int>& a);

};

类中友元函数模板会根据类的实例化类型而实例化出与之匹配的具体函数。

注意:类模板的实例化不会实例化一个友元函数,只是声明友元而不实例化, 只有在调用时,函数才会实例化。

上述友元函数声明中,show()函数有类的引用作为参数,可以从函数参数推断 出模板类型参数,所以其函数名后的<>可以为空。

(3)为友元函数模板提供定义

为函数模板提供定义,必须在类内声明,类外定义。

例如:

template<typename T>

void func() { cout << A<T>::成员 << endl; }

template<typename T>

void show(T& t) { cout << t.成员 << endl; }

例如:

#include <cstdlib> 
#include <iostream>
using namespace std;
//(1)函数模板声明
template<typename T>
void func();
template<typename T>
void show(T& t);
//类模板定义
template <typename U>
class A
{
private:
	U item;
	static int count;
public:
	A(const U& u) :item(u) { count++; }
	~A() { count--; }
	//(2)在类模板中将函数模板声明为类的友元函数
	friend void func<U>();   //友元函数模板
	friend void show<>(A<U>& a);   //友元函数模板
};
template<typename T>
int A<T>::count = 0;     //类A的T类型对象的个数
//(3)友元函数模板的定义
template<typename T>
void func()
{
	cout << "template size: " << sizeof(A<T>) << ";";
	cout << " template func(): " << A<T>::count << endl;
}
template<typename T>
void show(T& t)
{
	cout << t.item << endl;
}
int main()
{
	func<int>();    //调用int类型的函数模板实例,int类型,其大小为	4字节
	A<int> a(10);   //定义类对象
	A<int> b(20);
	A<double> c(1.2);
	show(a);   //调用show()函数,输出类对象的数据成员值
	show(b);
	show(c);
	cout << "func<int> output:\n";
	func<int>();    //运行到此,已经创建了两个int类型对象
	cout << "func<double>() output:\n";
	func<double>();
	system("pause");
	return 0;
}

输出结果

template size: 4; template func(): 0

10

20

1.2

func<int> output:

template size: 4; template func(): 2

func<double>() output:

template size: 8; template func(): 1

分析:将func()与show()函数定义成了模板并声明为类的友元,在定义函数模 板时是在类外定义的,当调用函数时,func()函数后带有<>说明函数的实例化 类型,而show()是直接调用的。

3、非约束模板友元函数

在类内部声明友元函数模板,友元函数的模板形参与类模板的形参没有联系,此时 友元函数为类模板的非约束模板友元函数。

例如:

template<typename T>

class A

{         ……

         template<typename T, typename U>  //在类内部声明函数模板

         friend void show(T& t, U& u);

};

非约束模板友元函数模板在类内声明,在类外定义;它是类模板每个实例的友元, 可以访问所有实例的类成员。

例如:

#include <cstdlib>
#include <iostream>
using namespace std;
template<typename T>
class A
{
private:
	T item;
public:
	A(const T& t) :item(t) {}
	template<typename U, typename V>  //在类内部声明函数模板
	friend void show(U& u, V& v);
};
template<typename U, typename V>
void show(U& u, V& v)
{
	cout << u.item << "," << v.item << endl;
}
int main()
{
	A<int> a(10);
	A<int> b(20);
	A<double> c(1.2);
	cout << "a,b: ";
	show(a, b);
	cout << "a,c: ";
	show(a, c);
	system("pause");
	return 0;
}

输出结果

a,b: 10,20

a,c: 10,1.2

分析:函数模板的形参类型与类模板的形参类型不相关,因此,它可以接受任何类型的参数:第一次调用传入的是两个int类型的类对象,第二次调用传入的是一个 int类型和一个double类型的对象。

  • 72
    点赞
  • 262
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++ 中的 vector 是一个动态数组,可以在运行时动态增长或缩小。它是一个模板类,可以存储任何类型的数据。 使用 vector 前需要包含头文件 vector。 ### 创建 vector 创建一个 vector 对象的语法如下: ```c++ vector<数据类型> 变量名; ``` 例如,创建一个存储整数的 vector 对象: ```c++ vector<int> v1; ``` 创建一个存储字符串的 vector 对象: ```c++ vector<string> v2; ``` ### 添加元素 使用 push_back() 方法可以向 vector 中添加元素,语法如下: ```c++ v.push_back(元素); ``` 例如,向 v1 中添加元素: ```c++ v1.push_back(10); v1.push_back(20); v1.push_back(30); ``` ### 访问元素 可以使用下标运算符 [] 或 at() 方法来访问 vector 中的元素。 例如,访问 v1 中的元素: ```c++ cout << v1[0] << endl; // 输出 10 cout << v1.at(1) << endl; // 输出 20 ``` ### 获取 vector 的大小 使用 size() 方法可以获取 vector 中存储元素的数量。 例如,获取 v1 的大小: ```c++ cout << v1.size() << endl; // 输出 3 ``` ### 遍历 vector 可以使用 for 循环或迭代器来遍历 vector 中的元素。 例如,使用 for 循环遍历 v1: ```c++ for (int i = 0; i < v1.size(); i++) { cout << v1[i] << " "; } ``` 使用迭代器遍历 v1: ```c++ vector<int>::iterator it; for (it = v1.begin(); it != v1.end(); it++) { cout << *it << " "; } ``` ### 删除元素 使用 erase() 方法可以删除 vector 中的元素,语法如下: ```c++ v.erase(迭代器); ``` 例如,删除 v1 中的第二个元素: ```c++ v1.erase(v1.begin() + 1); ``` ### 插入元素 使用 insert() 方法可以向 vector 中插入元素,语法如下: ```c++ v.insert(迭代器, 元素); ``` 例如,向 v1 中插入元素 15: ```c++ v1.insert(v1.begin() + 1, 15); ``` ### 清空 vector 使用 clear() 方法可以清空 vector 中的所有元素。 例如,清空 v1: ```c++ v1.clear(); ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

真的没事鸭

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

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

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

打赏作者

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

抵扣说明:

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

余额充值