目录
一、函数模板
(1)函数模板的概念
(2)函数模板的示例
(3)函数或函数模板调用语句的匹配顺序
二、类模板
(1)类模板概念
(2)类模板示例
(3)类模板与继承
一、函数模板
- 设计程序中的函数时,可能会遇到函数中参数的类型有差异,但需要实现的功能类似的情形。
- 函数重载可以处理这种情形。
- 重载函数的参数表中,可以写不同类型的参数,从而可以处理不同的情形。
(1)函数模板的概念
- 为了提高效率,实现代码复用,C++ 提供了一种处理机制,即使用函数模板。
- 函数在设计时并不使用实际的类型,而是使用虚拟的类型参数。
- 这样可以不必为每种不同的类型都编写代码段。
- 当用实际的类型来实例化这种函数时,将函数模板与某个具体数据类型连用。
- 编译器将以函数模板为样板,生成一个函数,即产生了模板函数,这个过程称为函数模板实例化。
- 函数模板实例化的过程由编译器完成。
- 程序设计时并不给出相应数据的类型,编译时,由编译器根据实际的类型进行实例化。
(2)函数模板的示例
- 实际上,函数模板不是一个具体的函数,编译器不能为其生成可执行代码。
- 定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。
- 函数模板中还可以带多个类型参数。
虽然 函数模板 的使用形式与 函数 类似,但二者有本质的 区别 ,主要表现在以下 3 个方面:
- 函数模板本身在编译时不会生成任何目标代码,只有当通过模板生成具体的函数实例时才会生成目标代码。
- 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数那样只将声明放在头文件中。
- 函数指针也只能指向模板的实例,而不能指向模板本身。
【示例一】 创建了一个模板函数
abs
,通过模板参数 T,实现获取任意类型变量的绝对值。在主函数中定义了不同类型的变量,并通过abs
函数输出它们的绝对值【示例代码】
#include <iostream> // 导入 iostream 库 using namespace std; template<typename T> // 创建模板函数 T abs(T x) // 定义模板函数 { return x < 0 ? -x : x; // 使用三目运算符计算绝对值并返回 } int main() // 主函数 { int n = -5; // 初始化变量 n 为 -5 int m = 10; // 初始化变量 m 为 10 double d = -0.5; // 初始化变量 d 为 -0.5 float f = 3.2; // 初始化变量 f 为 3.2 cout << n << "的绝对值是:" << abs(n) << endl; // 输出 n 的绝对值 cout << m << "的绝对值是:" << abs(m) << endl; // 输出 m 的绝对值 cout << d << "的绝对值是:" << abs(d) << endl; // 输出 d 的绝对值 cout << f << "的绝对值是:" << abs(f) << endl; // 输出 f 的绝对值 return 0; // 返回 0 表示执行成功 }
【代码详解】
- 程序首先导入了
iostream
库,该库包含了std::cout
和std::endl
等标准输出工具。- 在模板函数
abs
中,我们使用了一个条件三目运算符返回传入参数的绝对值。该函数的参数是一个通用类型T
变量 x。- 在主函数中,我们初始化了四个变量
n
、m
、d
和f
,分别为整型-5
、整型10
、双精度浮点型-0.5
以及单精度浮点型3.2
。接下来我们分别调用了abs
函数,输出各变量的绝对值。最后程序执行成功,返回0
。【执行结果】
- 这段代码创建了一个模板函数
abs
,通过模板参数 T,实现获取任意类型变量的绝对值。在主函数中定义了不同类型的变量,并通过abs
函数输出它们的绝对值。- 这说明模板函数在多种类型的变量中都能正常工作。
- 输出结果为:
-5的绝对值是:5 10的绝对值是:10 -0.5的绝对值是:0.5 3.2的绝对值是:3.2
【示例二】 模板函数
【示例代码】在主函数中,调用 abs(n) 时,编译器根据实参 n 的类型 int,推导出模板中的类型参数 T 为 int,然后实例化函数模板,生成函数模板 abs 的一个实例。
int abs(int x) //定义求整数x的绝对值函数 { return x < 0 ? -x : x; //判断x是否小于0,如果是则取相反数,否则返回x }
【代码详解】
- 这段代码定义了一个求整数绝对值的函数
abs
,即传入整数x
,返回其对应的绝对值。用三目运算符来进行判断,如果x
小于 0,则取相反数,否则直接返回x
。
【示例三】 模板函数
【示例代码】当调用abs(d)时,根据实参d的类型double,又实例化一个新的函数。
double abs(double x) //定义求双精度浮点型x的绝对值函数 { return x < 0 ? -x : x; //判断x是否小于0,如果是则取相反数,否则返回x }
【代码详解】
- 这段代码定义了一个求双精度浮点数绝对值的函数
abs
,即传入双精度浮点数x
,返回其对应的绝对值。用三目运算符来进行判断,如果x
小于 0,则取相反数,否则直接返回x
。 实际上,函数模板不是一个具体的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。
【示例四】通用的函数模板和一个日期类,其中函数模板可交换不同类型的变量值,并测试了模板函数 Swap 在不同数据类型下的使用
【示例代码】
#include<iostream> //包含标准输入输出流库 using namespace std; template<class T> //定义通用类型模板 void Swap(T &x, T &y) //定义模板函数 Swap,参数为 T 类型引用 x 和 y { T tmp = x; //定义 T 类型变量 tmp,赋值为 x x = y; //把 y 的值赋给 x y = tmp; //把 tmp 的值赋给 y } class myDate //定义 myDate 类 { public: myDate(); //默认构造函数 myDate(int, int, int); //构造函数 void printDate() const; //打印日期信息 private: int year, month, day; //成员变量:年、月、日 }; myDate::myDate() //默认构造函数的实现 { year = 1970; //初始年为 1970 month = 1; //初始月为 1 day = 1; //初始日为 1 } myDate::myDate(int y, int m, int d) //参数构造函数的实现 { year = y; //初始化年份 month = m; //初始化月份 day = d; //初始化日期 } void myDate::printDate() const //打印日期信息 { cout << year << "/" << month << "/" << day << endl; //输出日期 return; //返回空值 } int main() //主函数 { int n = 1, m = 2; //定义两个整型变量 n 和 m,分别赋值为 1 和 2 Swap(n, m); //调用 Swap 函数进行值交换 cout << n << " " << m << endl; //输出交换后的结果 double f = 1.2, g = 2.3; //定义两个双精度浮点数变量 f 和 g,分别赋值为 1.2 和 2.3 Swap(f, g); //调用 Swap 函数进行值交换 cout << f << " " << g << endl; //输出交换后的结果 myDate d1, d2(2000, 1, 1); //定义两个日期对象,d1 默认构造函数创建,d2 利用参数构造函数创建 Swap(d1, d2); //调用 Swap 函数进行值交换 d1.printDate(); //输出日期信息 d2.printDate(); //输出日期信息 return 0; //程序执行成功,返回0 }
【代码详解】
- 该程序定义了一个通用类型模板
Swap
,并利用此模板实现了 C++ 中的值交换,其中Swap
可以实现不同类型的变量值交换。还定义了一个myDate
类表示日期,包含构造函数、默认构造函数和打印日期信息的函数。在main
函数中创建不同的类型变量,调用Swap
函数进行值交换,以示此函数的通用性。#include<iostream>
:包含标准输入输出流库。using namespace std;
:使用标准库的命名空间。template<class T>
:定义一个通用类型模板,使用class
或typename
其中之一来声明类型模板参数,冒号后面的代码是模板变量的类型名称,即使用模板时实际传入的数据类型。void Swap(T &x, T &y)
:定义模板函数Swap
,参数为T
类型引用x
和y
,函数的功能是交换x
和y
的值。T tmp = x;
:定义一个类型为T
的tmp
变量,并将x
的值赋给它。x = y;
:将y
的值赋给x
。y = tmp;
:将tmp
的值赋给y
。class myDate
:定义了一个日期类myDate
。myDate()
:是默认构造函数,将年月日初始化为 1970 年 1 月 1 日。成员函数myDate()
用于给成员变量赋初值,不需要写返回类型,因为在调用该函数时不需要返回任何值。myDate(int y, int m, int d)
:是带参数的构造函数,用于初始化年月日的值。初始化时需要传入年、月、日三个整数值,根据值初始化成员变量。void printDate() const
:是打印日期信息的函数,成员函数printDate()
用于输出日期类中三个数据的值,不会对成员变量进行修改,故加上const
修饰符来防止在函数中修改成员变量。int main()
:是主函数,是程序的执行起点。int n = 1, m = 2;
:定义两个整型变量n
和m
,并将它们分别赋值为1
和2
。Swap(n, m);
:调用 Swap 函数,进行n
和m
的值交换。cout << n << " " << m << endl;
:输出交换后的n
和m
。double f = 1.2, g = 2.3;
:定义两个双精度浮点数变量f
和g
,并将它们分别赋值为1.2
和2.3
。Swap(f, g);
:调用 Swap 函数,进行f
和g
的值交换。cout << f << " " << g << endl;
:输出交换后的f
和g
。myDate d1, d2(2000, 1, 1);
:定义两个日期对象,其中d1
通过默认构造函数创建,d2
利用参数构造函数创建。Swap(d1, d2);
:调用 Swap 函数,进行d1
和d2
的值交换。d1.printDate();
:输出d1
的日期信息。d2.printDate();
:输出d2
的日期信息。return 0;
:程序执行成功,返回0
。【执行结果】
- 首先,在主函数中分别定义了两个整型变量 n 和 m,并赋值为 1 和 2,然后调用了 Swap(n, m) 进行值的交换,并输出了交换后的结果,即 “1 2” 变成了 “2 1”。
- 接下来,定义了两个双精度浮点数变量 f 和 g,并赋值为 1.2 和 2.3。调用了 Swap(f, g) 进行值的交换,并输出了交换后的结果,即 “1.2 2.3” 变成了 “2.3 1.2”。
- 最后,定义了两个日期对象 d1 和 d2,d1 利用默认构造函数创建,d2 利用参数构造函数创建,然后调用了 Swap(d1, d2) 进行值的交换,也就是将 d1 和 d2 中的年、月、日都互相交换。最后分别输出了交换后的日期信息,即输出了 “1970/1/1” 和 “2000/1/1”。
- 该程序的执行结果为:
1 2 2.3 1.2 1970/1/1 2000/1/1
【示例五】 函数模板中还可以带多个类型参数
【示例代码】下面这个函数模板的写法是合法的:
template <class T1, class T2> // 声明模板,T1和T2是类型参数 void print(T1 arg1, T2 arg2) // 定义打印函数,参数类型为T1和T2 { cout << arg1 << "," << arg2 << endl; // 打印参数arg1和arg2 }
【代码详解】
此处定义了一个打印函数,其参数类型和数量不确定,由模板类型参数T1和T2决定。由于T1或T2可以为任何类型(包括内置类型和自定义类型),所以该函数在需要打印不同类型数据的时候就非常的方便。
在编译时,编译器将根据函数调用时传递的参数类型,自动实例化生成特定类型的函数代码。并通过模板的显式实例化的方式可以减少编译时间和优化代码。
该函数实现方式比较简单,直接将参数arg1和arg2输出到屏幕上,并在每次输出结束后换行。需要注意的是,cout为标准输出流对象,需要包含<iostream>头文件,否则编译会出错。
(3)函数或函数模板调用语句的匹配顺序
函数与函数模板也是允许重载的。
C++ 编译器遵循以下先后顺序:
- 先找参数完全匹配的普通函数(不是由模板实例化得到的模板函数)。
- 再找参数完全匹配的模板函数。
- 然后找实参经过自动类型转换后能够匹配的普通函数。
- 如果上面的都找不到,则报错。
二、类模板
(1)类模板概念
- 通过类模板,可以实例化一个个的类。
- 继承机制也是在一系列的类之间建立某种联系,这两种涉及多个类的机制是有很大差异的。
- 类是相同类型事物的抽象,有继承关系的类可以具有不同的操作。
- 而模板是不同类型的事物具有相同的操作,实例化后的类之间没有联系,相互独立。
【格式一】声明类模板的一般格式如下:
template <模板参数表> class 类模板名 { 类体定义 }
【说明】
- 其中,“模板参数表” 的形式与函数模板中的 “模板参数表” 完全一样。
- 类体定义与普通类的定义几乎相同,只是在它的成员变量和成员函数中通常要用到模板的类型参数。
【格式二】
- 类模板的成员函数既可以在类体内进行说明,也可以在类体外进行说明。
- 如果在类体内定义,则自动成为内联函数。如果需要在类模板以外定义其成员函数,则要采用以下格式:
template <模板参数表> 返回类型名 类模板名<模板参数标识符列表>::成员函数名(参数表) { 函数体 }
【说明】
类模板声明本身并不是一个类,它说明了类的一个家族。 只有当被其他代码引用时,模板才根据引用的需要生成具体的类
【格式三】
- 不能使用类模板来直接生成对象,因为类型参数是不确定的,必须先为模板参数指定 “实参”,即模板要 “实例化” 后,才可以创建对象。
- 也就是说,当使用类模板创建对象时,要随类模板名给出对应于类型形参或普通形参的具体实参,格式如下:
类模板名 <模板参数表> 对象名1,…,对象名n; // 或是 类模板名 <模板参数表> 对象名1(构造函数实参),…,对象名构造函数实参);
- 编译器由类模板生成类的过程称为类模板的实例化。
- 由类模板实例化得到的类称为模板类。
- 要注意的是,与类型形参相对应的实参是类型名。
(2)类模板示例
- 二元组是常用的一种结构。
- 可以定义两个值的二元组,如平面坐标系下点的横、纵坐标组成的二元组。
- 还可以定义两个字符串的二元组,如字典中单词与释义组成的二元组。
- 还可以定义学生姓名及其成绩的二元组。二元组的例子非常多,不胜枚举。
- 如果要定义二元组的类,则需要根据组成二元组的类型定义很多不同的类。
- 现在可以使用类模板来解决问题。
【示例一】 实现了模板类TestClass的定义和实例化
【示例代码】在 main 函数中依次创建 TestClass<char> 和 TestClass<double> 两个对象,并演示了如何使用它们的成员变量和成员函数:
#include <iostream> //包含标准输入输出流库 using namespace std; template<class T> //定义一个模板类,其数据成员为T类型数组 class TestClass { public: T buffer[10]; //T类型数组 T getData(int j); //成员函数声明,用于获取该数组中的元素 }; template<class T> //定义模板类的成员函数,返回指定下标处的数据 T TestClass<T>::getData(int j) { return *(buffer+j); }; int main() //主函数 { TestClass<char> ClassInstA; //定义一个模板类的对象,类型是char int i; char cArr[6] = "abcde"; //定义一个char类型的数据数组 for (i = 0; i < 5; i++) //赋值给buffer数组 ClassInstA.buffer[i] = cArr[i]; for (i = 0; i < 5; i++) //依次输出buffer中元素的值 { char res = ClassInstA.getData(i); //调用成员函数getData cout << res << " "; //打印结果 } cout << endl; //换行 TestClass<double> ClassInstF; //定义一个模板类的对象,类型是double double fArr[6] = {12.1, 23.2, 34.3, 45.4, 56.5, 67.6}; //定义一个double类型的数据数组 for (i = 0; i < 6; i++) //赋值给buffer数组 ClassInstF.buffer[i] = fArr[i] - 10; for (i = 0; i < 6; i++) //依次输出buffer中元素的值 { double res = ClassInstF.getData(i); //调用成员函数getData cout << res << " "; //打印结果 } cout << endl; //换行 return 0; //程序执行成功,返回0 }
【代码详解】
- 定义了一个模板类 TestClass,其中包含一个 T 类型的数组 buffer 和一个成员函数getData,用于获取数组 buffer 中制定位置的元素:
#include<iostream> //包含标准输入输出流库 using namespace std; template<class T> //定义一个模板类,其数据成员为T类型数组 class TestClass { public: T buffer[10]; //T类型数组 T getData(int j); //成员函数声明,用于获取该数组中的元素 };
- 在类外部定义函数 getData,使用作用域解析符 “::” 表示这个函数是属于 TestClass 类范围的。获取数组 buffer 中指定下标处的元素值:
template<class T> //定义模板类的成员函数,返回指定下标处的数据 T TestClass<T>::getData(int j) { return *(buffer+j); };
- 在 main 函数中,首先定义一个 TestClass 对象 ClassInstA,类型被指定为 char。然后将数组 cArr 中的元素逐个赋值给 ClassInstA 的 buffer 数组,再依次调用成员函数getData 获取 buffer 中每个元素的值,并打印输出。执行完此段代码后输出的结果是:“a b c d e”(以空格隔开),最后换行:
int main() { TestClass<char> ClassInstA; //定义一个模板类的对象,类型是char int i; char cArr[6] = "abcde"; //定义一个char类型的数据数组 for (i = 0; i < 5; i++) //赋值给buffer数组 ClassInstA.buffer[i] = cArr[i]; for (i = 0; i < 5; i++) //依次输出buffer中元素的值 { char res = ClassInstA.getData(i); //调用成员函数getData cout << res << " "; //打印结果 } cout << endl; //换行
- 接着,定义一个TestClass对象ClassInstF,类型被指定为double。然后将数组fArr中的元素减去10并依次存储在ClassInstF的buffer数组中,并逐个调用成员函数getData获取buffer中每个元素的值,并打印输出。执行完此段代码后输出的结果是:“2.1 13.2 24.3 35.4 46.5 57.6”(以空格隔开),最后换行:
TestClass<double> ClassInstF; //定义一个模板类的对象,类型是double double fArr[6] = {12.1, 23.2, 34.3, 45.4, 56.5, 67.6}; //定义一个double类型的数据数组 for (i = 0; i < 6; i++) //赋值给buffer数组 ClassInstF.buffer[i] = fArr[i] - 10; for (i = 0; i < 6; i++) //依次输出buffer中元素的值 { double res = ClassInstF.getData(i); //调用成员函数getData cout << res << " "; //打印结果 } cout << endl; //换行
- 最后,在main函数中返回0,程序执行结束:
return 0; //程序执行成功,返回0 }
【执行结果】
- 程序定义了一个模板类 TestClass ,其中有一个数据成员 buffer ,是一个 T 类型的数组。TestClass 还有一个成员函数 getData,用于获取 buffer 中指定下标处的值。
- 在主函数中,首先创建了一个 TestClass<char> 类型的对象 ClassInstA,并将 "abcde" 这个字符串逐个存储在 ClassInstA 的 buffer 数组中,然后逐个获取 buffer 中元素的值,并打印输出。因此,执行结果中先打印出了 "abcde" 的每个字符(以空格隔开),然后换行。
- 接着,创建了一个 TestClass<double> 类型的对象 ClassInstF,并将 {12.1, 23.2, 34.3, 45.4, 56.5, 67.6} 数组中的每个元素减去了 10 后,存储在 buffer 数组中。再依次获取 buffer 中元素的值,并打印输出。因此,执行结果中先打印出了每个元素减 10 后的值(以空格隔开),然后换行。
- 需要注意的是,T 可以是任何数据类型,通过指定不同类型的类型参数 T,就可以创建出不同的类对象,并对这些对象进行操作。另外,因为是模板类,所以可以在程序中复用这个类模板创建很多对象,而不用针对每种需求都再编写一个新的类。
- 该程序执行结果为:
a b c d e 2.1 13.2 24.3 35.4 46.5 57.6
【示例二】 类模板和数组的使用
【示例代码】展示了 C++ 中的类模板和数组的使用,以及如何通过数组的下标来访问数组中的元素,并输出其值:
#include <iostream> using namespace std; template<int i> class TestClass { public: int buffer[i]; // 声明一个大小固定为 i 的整型数组 int getData(int j); // 声明一个获取 buffer 下标为 j 的元素值的方法 }; template<int i> int TestClass<i>::getData(int j) { return *(buffer+j); // 返回 buffer 数组下标为 j 的元素值 } int main() { TestClass<6> ClassInstF; // 初始化大小为 6 的 TestClass 实例 int i; double fArr[6] = {12.1, 23.2, 34.3, 45.4, 56.5, 67.6}; for (i = 0; i < 6; i++) { ClassInstF.buffer[i] = fArr[i] - 10; // 循环将 fArr 中每个元素减去 10 后赋值给 ClassInstF 的 buffer 数组 } for (i = 0; i < 6; i++) { double res = ClassInstF.getData(i); // 循环获取 ClassInstF 的 buffer 数组中每个元素的值 cout << res << " "; } cout << endl; return 0; }
【代码详解】
- 该程序定义了一个名为
TestClass
的类模板,该模板包含一个大小为i
的整型数组和一个获取数组元素方法。继而在main
函数中,首先声明了一个大小为 6 的TestClass
类型的对象ClassInstF
,然后使用for
循环将一个已知的双精度数组fArr
中的每个元素减去 10 赋值给ClassInstF
的buffer
数组,最后再使用for
循环访问ClassInstF
的buffer
数组,并输出其中每个元素的值。该程序可以帮助理解 C++ 中类模板和数组的使用方法。- 头文件声明和命名空间:
#include <iostream> using namespace std;
- 定义一个名为 TestClass 的类模板,模板参数为 i,其中 buffer 是一个大小为 i 的 int 类型数组,getData 是一个公共方法,参数为 int 类型的 j,返回值是 buffer 数组下标为 j 的元素值:
template<int i> class TestClass { public: int buffer[i]; // 声明一个大小固定为 i 的整型数组 int getData(int j); // 声明一个获取 buffer 下标为 j 的元素值的方法 };
- 定义 TestClass 类中 getData 方法的具体实现,根据传入的参数 j 对 buffer 数组进行下标访问,返回所得元素值:
template<int i> int TestClass<i>::getData(int j) { return *(buffer+j); // 返回 buffer 数组下标为 j 的元素值 }
- 在 main 函数中,声明 TestClass<6> 类型的 ClassInstF 实例,其中 i 的值为 6。通过 for 循环,将大小为 6 的 fArr 数组中的每个元素值减去 10 后存入 ClassInstF 的 buffer 数组中。再次通过 for 循环,将 ClassInstF 的 buffer 数组中的每个元素值存入 res,然后输出该值。最后,输出一个换行符并返回 0:
int main() { TestClass<6> ClassInstF; // 初始化大小为 6 的 TestClass 实例 int i; double fArr[6] = {12.1, 23.2, 34.3, 45.4, 56.5, 67.6}; for (i = 0; i < 6; i++) { ClassInstF.buffer[i] = fArr[i] - 10; // 循环将 fArr 中每个元素减去 10 后赋值给 ClassInstF 的 buffer 数组 } for (i = 0; i < 6; i++) { double res = ClassInstF.getData(i); // 循环获取 ClassInstF 的 buffer 数组中每个元素的值 cout << res << " "; } cout << endl; return 0; }
【执行结果】
- 该结果是将
fArr
中的每个元素减去 10 后存储到ClassInstF
的buffer
数组中,然后依次输出buffer
数组中的每个元素值。由于buffer
数组的元素类型为整型,而fArr
中的元素类型为双精度浮点数,因此在将fArr
中的每个元素减去 10 时也会将其类型转换为整型。2.1 13.2 24.3 35.4 46.5 57.6
(3)类模板与继承
类之间允许继承,类模板之间也允许继承。具体来说,类模板和类模板之间、类模板和类之间可以互相继承,它们之间的常见派生关系有以下 4 种情况:
- 普通类继承模板类。
- 类模板继承普通类。
- 类模板继承类模板。
- 类模板继承模板类。
根据类模板实例化的类即是模板类。
【示例一】 类模板的使用方法以及派生类的定义和继承的方法
【示例代码】
- 以下程序定义了一个类模板基类
TBase
和一个从TBase
派生而来的类Derived
,使用了类模板中的数据成员data
和成员函数data
的值。- 在
main
函数中,程序首先创建了Derived
类的一个对象d
,Derived
继承了TBase<int>
,而TBase
模板类中的数据类型是T
,类似于一个泛型,因此data
的数据类型为int
。接着,程序调用d
的成员函数print()
,输出了数据成员data
的值到标准输出流中,即输出到屏幕上。- 以下程序主要是为了演示类模板的使用方法以及派生类的定义和继承的方法:
#include <iostream> using namespace std; template <class T> // 定义类模板基类 class TBase { T data; // 类模板基类中的数据成员 public: void print() { // 在类模板基类中定义成员函数 cout << data << endl; } }; class Derived : public TBase<int> { // 使用类模板基类,定义普通派生类 }; int main() { Derived d; // 定义普通派生类对象 d.print(); // 调用类模板基类中的成员函数 return 0; }
【代码详解】
- 该程序定义了名为
TBase
的类模板基类,这个类模板中包含一个数据成员data
和一个成员函数data
的值。接着,定义了名为Derived
的普通派生类,该普通派生类是从模板基类TBase
继承而来。在main
函数中定义了一个Derived
类的对象d
,然后调用了基类中的成员函数print()
输出data
。由于TBase
是一个类模板,因此使用时需要指定模板参数的类型,这里使用了int
。- 包含头文件和命名空间的声明:
#include <iostream> using namespace std;
- 定义名为
TBase
的类模板,该模板中包含一个数据成员data
和一个成员函数data
的值:template <class T> // 定义类模板基类 class TBase { T data; // 类模板基类中的数据成员 public: void print() { // 在类模板基类中定义成员函数 cout << data << endl; } };
- 定义普通派生类
Derived
,该类继承自TBase<int>
:class Derived : public TBase<int> { // 使用类模板基类,定义普通派生类 };
- 在
main
函数中,首先声明一个名为d
的派生类对象Derived
。然后,调用d
的成员函数data
的值。由于Derived
派生自TBase<int>
,因此TBase
类模板中data
的类型为int
,可以直接输出。最后返回值 0:int main() { Derived d; // 定义普通派生类对象 d.print(); // 调用类模板基类中的成员函数 return 0; }
【执行结果】
因为
TBase
模板类中的数据成员data
没有被初始化,其值将取决于存储在该内存位置上的旧数据。在该程序中,data
的类型为int
,因此默认初始值为 0。在
main
函数中,定义了一个名为d
的Derived
类对象,然后调用d
继承自TBase<int>
的成员函数TBase
模板类中data
的初始值 0。因此,程序输出了一个值为 0 的整数:
0
【示例二】 类的继承和成员函数的显式调用方法
【示例代码】
#include <iostream> #include <string> using namespace std; class TBase { // 定义类 TBase int k; // 私有数据成员 k public: void print() { // 公有成员函数 print 输出 k 的值 cout << "TBase::" << k << endl; } }; template <class T> class TDerived : public TBase { // 定义类 TDerived 继承自 TBase T data; // 私有数据成员 data public: void setData(T x) { // 设置 data 值 data = x; } void print() { // 输出 data 和 k 的值 TBase::print(); // 调用基类中的 print 函数 cout << "TDerived::" << data << endl; } }; int main() { TDerived<string> d; // 定义 TDerived 类对象 d d.setData("2019"); // 设置 d 的 data 值为 "2019" d.print(); // 输出 data 和 k 的值 return 0; }
【代码详解】
该程序主要是为了演示类的继承和成员函数的显式调用方法,以及使用类模板。其中,基类
TBase
和派生类TDerived
都包含了TBase::print()
显式调用基类的k
。- 包含头文件和命名空间的声明:
#include <iostream> #include <string> using namespace std;
- 定义了一个名为
TBase
的类,其中包含一个私有数据成员k
和一个公有成员函数k
的值:class TBase { // 定义类 TBase int k; // 私有数据成员 k public: void print() { // 公有成员函数 print 输出 k 的值 cout << "TBase::" << k << endl; } };
- 定义一个类模板
TDerived
,该类继承自TBase
,包含一个私有数据成员data
和两个成员函数setData
和setData
函数用于设置data
的值,data
和继承自基类TBase
的k
的值。需要注意的是,在TBase::print()
对基类中的template <class T> class TDerived : public TBase { // 定义类 TDerived 继承自 TBase T data; // 私有数据成员 data public: void setData(T x) { // 设置 data 值 data = x; } void print() { // 输出 data 和 k 的值 TBase::print(); // 调用基类中的 print 函数 cout << "TDerived::" << data << endl; } };
- 在
main
函数中,首先定义了一个名为d
的TDerived<string>
类对象,然后调用它的setData
函数将data
的值设置为字符串 “2019”。最后,调用d
的data
和继承自基类TBase
的k
的值:int main() { TDerived<string> d; // 定义 TDerived 类对象 d d.setData("2019"); // 设置 d 的 data 值为 "2019" d.print(); // 输出 data 和 k 的值 return 0; }
【执行结果】
- 该程序中定义了一个基类
TBase
和一个继承自TBase
的模板类TDerived
,其中TBase
类包含一个私有数据成员k
,而TDerived
类包含一个私有的数据成员data
,同时还包含了两个成员函数setData
和TBase
的k
的值,并输出data
的值到标准输出流中,即输出到屏幕上。- 在
main
函数中,首先定义了一个名为d
的TDerived<string>
类对象,然后调用它的setData
函数将data
的值设置为字符串 “2019”。最后,调用d
的data
和继承自基类TBase
的k
的值。- 由于
TBase
类中的k
数据成员没有显式初始化,在实例化TDerived
类对象时,其数据成员k
获得了默认值 0。因此,程序的输出结果为 “TBase::0\nTDerived::2019”。TBase::0 TDerived::2019
【示例三】 类模板的定义、继承及其成员函数的调用
【示例代码】成员函数
#include <iostream> #include <string> using namespace std; template <class T> class TBase { // 定义类模板 TBase T data1; // 私有数据成员 data1 public: void print() { // 成员函数 print 输出 data1 的值 cout << "TBase::" << data1 << endl; } }; template <class T1, class T2> class TDerived : public TBase<T1> { // 定义类模板 TDerived,继承 TBase T2 data2; // 私有数据成员 data2 public: void print() { // 成员函数 print 输出 data1 和 data2 的值 TBase<T1>::print(); // 调用基类 TBase 的 print 函数 cout << "TDerived::" << data2 << endl; } }; int main() { TDerived<int, int> d; // 声明 TDerived<int, int> 类对象 d d.print(); // 输出 data1 和 data2 的值 TDerived<string, string> d2; // 声明 TDerived<string, string> 类对象 d2 d2.print(); // 输出 data1 和 data2 的值 return 0; }
【代码详解】
- 该程序主要是为了演示类模板的定义、继承及其成员函数的调用。成员函数
TDerived
来调用它们的TBase
的data1
和派生类TDerived
的data2
均被输出到标准输出流中。- 头文件声明和命名空间:
#include <iostream> #include <string> using namespace std;
- 定义了一个名为
TBase
的类模板,其中包含一个私有数据成员data1
和一个公有成员函数data1
的值:#include <iostream> #include <string> using namespace std; template <class T> class TBase { // 定义类模板 TBase T data1; // 私有数据成员 data1 public: void print() { // 成员函数 print 输出 data1 的值 cout << "TBase::" << data1 << endl; } };
- 定义了一个名为
TDerived
的类模板,继承自TBase
,并包含了一个私有数据成员data2
和一个公有成员函数TBase<T1>::print()
的形式进行了基类的显式调用,用于输出基类TBase
中的data1
的值,然后将data2
的值输出到标准输出流中:template <class T1, class T2> class TDerived : public TBase<T1> { // 定义类模板 TDerived,继承 TBase T2 data2; // 私有数据成员 data2 public: void print() { // 成员函数 print 输出 data1 和 data2 的值 TBase<T1>::print(); // 调用基类 TBase 的 print 函数 cout << "TDerived::" << data2 << endl; } };
- 在
main
函数中,首先声明了一个TDerived<int, int>
类对象d
,并调用其TDerived<string, string>
类对象d2
,并调用其int main() { TDerived<int, int> d; // 声明 TDerived<int, int> 类对象 d d.print(); // 输出 data1 和 data2 的值 TDerived<string, string> d2; // 声明 TDerived<string, string> 类对象 d2 d2.print(); // 输出 data1 和 data2 的值 return 0; }
【执行结果】
- 该程序中定义了一个类模板
TBase
和一个继承自TBase
的类模板TDerived
,其中TBase
类包含一个私有数据成员data1
和一个公有成员函数TDerived
类包含一个私有数据成员data2
,以及一个公有成员函数TBase<T1>::print()
调用了基类TBase
中的- 在
main
函数中,首先定义了一个名为d
的TDerived<int, int>
类对象,并调用它的d2
的TDerived<string, string>
类对象,并调用它的- 在该程序中,因为基类
TBase
和派生类TDerived
分别定义了不同的模板参数类型,所以在实例化TDerived
类对象时会使用默认构造函数(即未提供值时数据成员被默认初始化为 0 或空字符串(“”).)。所以,输出结果为 “TBase::0\nTDerived::0\nTBase::\nTDerived::\n”。TBase::0 TDerived::0 TBase:: TDerived::
【示例四】 类模板的定义和使用
【示例代码】该程序共定义了两个类模板
TBase
和TDerived
:
TBase
模板定义了一个公有数据成员data1
和一个成员函数data1
的值TDerived
模板继承自TBase
模板并定义了一个公有数据成员data2
和一个成员函数TBase<T1>::print()
#include <iostream> #include <string> using namespace std; template <class T> class TBase { public: T data1; // 公有数据成员 data1 public: void print() { cout << "TBase::" << data1 << endl; // 成员函数 print 输出 data1 的值 } }; template <class T1, class T2> class TDerived : public TBase<T1> { public: T2 data2; // 公有数据成员 data2 public: void print() { TBase<T1>::print(); // 成员函数 print 调用基类 TBase 的 print 函数输出 data1 的值 cout << "TDerived::" << data2 << endl; // 输出 data2 的值 } }; int main() { TDerived<int, int> d; // 声明 TDerived<int, int> 类对象 d d.data1 = 5; // 给 d 的 data1 赋值 5 d.data2 = 8; // 给 d 的 data2 赋值 8 d.print(); // 输出 data1 和 data2 的值 TDerived<string, string> d2; // 声明 TDerived<string, string> 类对象 d2 d2.data1 = "happy"; // 给 d2 的 data1 赋值 "happy" d2.data2 = "new year"; // 给 d2 的 data2 赋值 "new year" d2.print(); // 输出 data1 和 data2 的值 TDerived<int, string> d1; // 声明 TDerived<int, string> 类对象 d1 d1.data1 = 2020; // 给 d1 的 data1 赋值 2020 d1.data2 = "good luck"; // 给 d1 的 data2 赋值 "good luck" d1.print(); // 输出 data1 和 data2 的值 return 0; }
【代码详解】
- 这段代码定义了一个模板类
TBase
和一个从TBase
类继承而来的模板类TDerived
,并在main
函数中声明和使用了这两个类的实例。- 头文件声明和命名空间:
#include <iostream> #include <string> using namespace std;
- 这部分代码定义了一个模板类
TBase
,其中成员变量data1
是公有的,成员函数data1
的值。TBase
类型的对象data1
是模板类型T
的一个对象:template <class T> class TBase { public: T data1; // 公有数据成员 data1 public: void print() { cout << "TBase::" << data1 << endl; // 成员函数 print 输出 data1 的值 } };
- 这部分代码定义了一个继承自
TBase
模板类的模板类TDerived
,其中T1
和T2
是两个不同的模板参数。TDerived
类型的对象data2
和data1
分别是T2
和T1
类型的对象。成员函数data1
的值,它首先调用基类TBase
的data1
的值,然后输出data2
的值:template <class T1, class T2> class TDerived : public TBase<T1> { public: T2 data2; // 公有数据成员 data2 public: void print() { TBase<T1>::print(); // 成员函数 print 调用基类 TBase 的 print 函数输出 data1 的值 cout << "TDerived::" << data2 << endl; // 输出 data2 的值 } };
- 这部分代码定义了
main
函数。在函数中,分别声明了三个模板类TDerived
的对象d
、d2
和d1
,分别使用了不同的模板参数类型。通过分别为对象的成员变量data1
和data2
分别赋值后,调用TDerived
对象都分别调用了一次int main() { TDerived<int, int> d; // 声明 TDerived<int, int> 类对象 d d.data1 = 5; // 给 d 的 data1 赋值 5 d.data2 = 8; // 给 d 的 data2 赋值 8 d.print(); // 输出 data1 和 data2 的值 TDerived<string, string> d2; // 声明 TDerived<string, string> 类对象 d2 d2.data1 = "happy"; // 给 d2 的 data1 赋值 "happy" d2.data2 = "new year"; // 给 d2 的 data2 赋值 "new year" d2.print(); // 输出 data1 和 data2 的值 TDerived<int, string> d1; // 声明 TDerived<int, string> 类对象 d1 d1.data1 = 2020; // 给 d1 的 data1 赋值 2020 d1.data2 = "good luck"; // 给 d1 的 data2 赋值 "good luck" d1.print(); // 输出 data1 和 data2 的值 return 0; }
【执行结果】
- 该结果是由于程序定义了模板类
TBase
和TDerived
。TDerived
是TBase
的一个子类,每个类都有一个名为data1
和data2
的变量,分别为T1
和T2
类型。main
函数创建了TDerived
类的三个对象并为它们的数据成员赋值。然后通过调用每个对象的print()
函数,输出了每个对象的data1
和data2
。TBase::5 TDerived::8 TBase::happy TDerived::new year TBase::2020 TDerived::good luck
【示例五】 类模板的定义
【示例代码】该程序定义了两个类模板,分别是
TBase
和TDerived:
TBase
类模板中定义了私有成员变量data1
和成员函数print()
,它们的访问权限是private
和public
TDerived
类模板继承于TBase<int>
模板,并定义了私有成员变量data2
和公有成员函数print()
#include <iostream> #include <string> using namespace std; template<class T> class TBase // 类模板 { T data1; public: void print() { cout << "TBase::" << data1 << endl; } }; template<class T2> class TDerived: public TBase<int> // 类模板继承于模板类 { T2 data2; public: void print() { TBase<int>::print(); // 调用基类 TBase 的 print 函数输出 data1 的值 cout << "TDerived::" << data2 << endl; } }; int main() { TDerived<int> d; d.print(); TDerived<string> d2; d2.print(); return 0; }
【代码详解】
该程序定义了两个类模板,分别是
TBase
和TDerived
。TBase
类模板中定义了私有成员变量data1
和成员函数print()
,它们的访问权限是private
和public
。TDerived
类模板继承于TBase<int>
模板,并定义了私有成员变量data2
和公有成员函数print()
。在
main
函数中,程序分别声明了两个模板类TDerived<int>
和TDerived<string>
的对象d
和d2
,并调用它们的print()
函数。输出结果中,前面的字符串"TBase::"
和"TDerived::"
是在print()
函数中输出的,后面的数值或字符串是对象的成员变量值。- 头文件声明和命名空间:
#include <iostream> using namespace std;
- 这部分代码定义了一个
TBase
类模板,该模板有一个私有成员变量data1
,还定义了一个公共成员函数print()
,用于输出data1
的值:template<class T> class TBase // 类模板 { T data1; public: void print() { cout << "TBase::" << data1 << endl; } };
- 这部分代码定义了一个
TDerived
类模板,该模板继承了TBase<int>
模板,并有一个类型为T2
的公共成员变量data2
。成员函数print()
输出了data1
的值和data2
的值,其中通过在基类名字和函数名之间使用双冒号::
的方法调用了基类TBase
的print()
方法:template<class T2> class TDerived: public TBase<int> // 类模板继承于模板类 { T2 data2; public: void print() { TBase<int>::print(); // 调用基类 TBase 的 print 函数输出 data1 的值 cout << "TDerived::" << data2 << endl; } };
- 这部分代码在
main()
函数中使用TDerived
类模板实例化了两个对象d
和d2
,分别是TDerived<int>
和TDerived<string>
。接着,该程序调用每个对象的print()
方法,分别输出data1
和data2
的值:int main() { TDerived<int> d; d.print(); TDerived<string> d2; d2.print(); return 0; }
【执行结果】
该程序使用了类模板
TBase
和TDerived
,并在main
函数中创建了TDerived<int>
和TDerived<string>
的对象d
和d2
。因为TDerived
类继承自TBase<int>
,所以在TDerived<int>
中,T 被指定为int
,在TDerived<string>
中,T 被指定为默认的string
。程序首先调用
d
对象的print()
函数,该函数调用TBase<int>
的成员函数print()
,输出 “TBase::0”,再输出data2
的值 “”,这是因为TDerived<int>
类模板中的data2
是未初始化的,所以其值为默认值。接下来,程序再次调用d2
对象的print()
函数,同样调用TBase<int>
的print()
函数,输出 “TBase::”,再输出data2
的值 “”。因此,程序的输出结果为:
TBase::0 TDerived:: TBase:: TDerived::
【示例六】类模板和继承的使用方法和共同使用方式
【示例代码】通过两个模板类建立了类之间的继承关系,通过继承可以方便地实现代码的复用,同时使用类模板又使得代码可以适用于不同数据类型的情况:
#include <iostream> #include <string> using namespace std; template<class T> class TBase // 定义类模板 TBase { public: T data1; // 一个公有成员变量 void print() // 一个公有成员函数,输出 data1 的值 { cout << "TBase::" << data1 << endl; } }; template<class T2> class TDerived: public TBase<int> // 定义类模板 TDerived,它继承于模板类 TBase<int> { public: T2 data2; // 一个公有成员变量 void print() // 一个公有成员函数,输出 data1 的值及 data2 的值 { TBase<int>::print(); // 调用基类 TBase 的 print 函数输出 data1 的值 cout << "TDerived::" << data2 << endl; } }; int main() { TDerived<int> d; // 声明一个 TDerived<int> 对象 d d.data1 = 5; // 初始化对象 d 的 data1 成员 d.data2 = 8; // 初始化对象 d 的 data2 成员 d.print(); // 调用对象 d 的 print 成员函数,输出结果 TDerived<string> d2; // 声明一个 TDerived<string> 对象 d2 d2.data1 = 2020; // 初始化对象 d2 的 data1 成员 d2.data2 = "good luck"; // 初始化对象 d2 的 data2 成员 d2.print(); // 调用对象 d2 的 print 成员函数,输出结果 return 0; // 返回 0,表示程序运行正常结束 }
【代码详解】
该程序使用类模板
TBase
和TDerived
,并在main
函数中创建了TDerived<int>
和TDerived<string>
的对象d
和d2
。其中,TBase
类模板是一个简单类,包含一个data1
成员变量和一个print()
成员函数;TDerived
类模板是一个继承于TBase<int>
模板的类模板,增加了一个data2
成员变量和print()
成员函数。在
main()
函数中,程序首先声明了一个TDerived<int>
的对象d
,对其data1
和data2
成员进行了初始化,并调用它的print()
函数输出结果。然后声明了一个TDerived<string>
的对象d2
,对其data1
和data2
成员进行了初始化,并调用它的print()
函数输出结果。- 头文件声明和命名空间:
#include <iostream> #include <string> using namespace std;
- 定义了一个类模板 TBase,该类模板包含一个公有成员 data1 和一个公有成员函数print(),用于输出 data1 的值:
template<class T> class TBase // 定义类模板 TBase { public: T data1; // 一个公有成员变量 void print() // 一个公有成员函数,输出 data1 的值 { cout << "TBase::" << data1 << endl; } };
- 定义了类模板 TDerived,它是从模板类 TBase<int> 继承而来。定义了一个公有成员data2 和一个公有成员函数 print(),用于输出 data1 和 data2 的值。在 print() 函数中,为了调用基类 TBase 的 print() 函数输出 data1 的值,使用了 TBase<int>::print() 语法:
template<class T2> class TDerived: public TBase<int> // 定义类模板 TDerived,它继承于模板类 TBase<int> { public: T2 data2; // 一个公有成员变量 void print() // 一个公有成员函数,输出 data1 的值及 data2 的值 { TBase<int>::print(); // 调用基类 TBase 的 print 函数输出 data1 的值 cout << "TDerived::" << data2 << endl; } };
- 在main函数中,先声明了一个TDerived<int>对象d,然后分别对d的data1和data2成员进行了初始化,最后调用d的print()函数。接着又声明了一个TDerived<string>对象d2,然后也分别对d2的data1和data2成员进行了初始化,最后调用d2的print()函数。最后返回0,表示程序执行成功结束:
int main() { TDerived<int> d; // 声明一个TDerived<int>对象d d.data1 = 5; // 初始化对象d的data1成员 d.data2 = 8; // 初始化对象d的data2成员 d.print(); // 调用对象d的print成员函数,输出结果 TDerived<string> d2; // 声明一个TDerived<string>对象d2 d2.data1 = 2020; // 初始化对象d2的data1成员 d2.data2 = "good luck"; // 初始化对象d2的data2成员 d2.print(); // 调用对象d2的print成员函数,输出结果 return 0; // 返回0,表示程序运行正常结束 }
【执行结果】
对象 d 的 data1 成员初始化为 5 ,data2 成员初始化为 8 。调用对象 d 的 print() 函数,首先输出 "TBass::5",然后输出 "TDerived::8"。
对象 d2 的 data1 成员初始化为 2020,data2 成员初始化为 "good luck"。调用对象 d2 的 print() 函数,首先输出 "TBase::2020",然后输出 "TDerived::good luck"。
TBase::5 TDerived::8 TBase::2020 TDerived::good luck