【C++ 程序设计】第 9 章:函数模板与类模板

news2024/12/25 8:56:37

目录

一、函数模板

(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。
  • 在主函数中,我们初始化了四个变量 nmd 和 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. 先找参数完全匹配的普通函数(不是由模板实例化得到的模板函数)。
  2. 再找参数完全匹配的模板函数。
  3. 然后找实参经过自动类型转换后能够匹配的普通函数。
  4. 如果上面的都找不到,则报错。


二、类模板

(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 和成员函数 print 输出数据成员 data 的值。
  • 在 main 函数中,程序首先创建了 Derived 类的一个对象 dDerived 继承了 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 和一个成员函数 print,该成员函数用于输出 data 的值。接着,定义了名为 Derived 的普通派生类,该普通派生类是从模板基类 TBase 继承而来。在 main 函数中定义了一个 Derived 类的对象 d,然后调用了基类中的成员函数 print() 输出 data。由于 TBase 是一个类模板,因此使用时需要指定模板参数的类型,这里使用了 int
  • 包含头文件和命名空间的声明:
    #include <iostream>
    using namespace std;
  • 定义名为 TBase 的类模板,该模板中包含一个数据成员 data 和一个成员函数 print,函数 print 可以输出 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 的成员函数 print,输出 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> 的成员函数 print,其输出的是 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 都包含了 print 函数,而在派生类中,需要使用 TBase::print() 显式调用基类的 print 函数才能输出基类中的 k

  • 包含头文件和命名空间的声明:
    #include <iostream>
    #include <string>
    
    using namespace std;
  • 定义了一个名为 TBase 的类,其中包含一个私有数据成员 k 和一个公有成员函数 printprint 函数输出 k 的值:
    class TBase { // 定义类 TBase
        int k; // 私有数据成员 k
    public:
        void print() { // 公有成员函数 print 输出 k 的值
            cout << "TBase::" << k << endl;
        }
    };
  • 定义一个类模板 TDerived,该类继承自 TBase,包含一个私有数据成员 data 和两个成员函数 setData 和 print。其中,setData 函数用于设置 data 的值,print 函数用于输出 data 和继承自基类 TBase 的 k 的值。需要注意的是,在 print 函数中,使用了 TBase::print() 对基类中的 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 的 print 函数输出 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 和 print。在 print 函数中,调用了基类 TBase 的 print 函数打印出 k 的值,并输出 data 的值到标准输出流中,即输出到屏幕上。
  • 在 main 函数中,首先定义了一个名为 d 的 TDerived<string> 类对象,然后调用它的 setData 函数将 data 的值设置为字符串 “2019”。最后,调用 d 的 print 函数输出 data 和继承自基类 TBase 的 k 的值。
  • 由于 TBase 类中的 k 数据成员没有显式初始化,在实例化 TDerived 类对象时,其数据成员 k 获得了默认值 0。因此,程序的输出结果为 “TBase::0\nTDerived::2019”。
TBase::0
TDerived::2019

【示例三】 类模板的定义、继承及其成员函数的调用

【示例代码】成员函数 print 的作用是输出类中数据成员的值到标准输出流,而且此程序实现了二次派生的类模板,并通过实例化不同类型的类模板来调用它们的 print 函数:

#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;
}

【代码详解】

  • 该程序主要是为了演示类模板的定义、继承及其成员函数的调用。成员函数 print 的作用是输出类中数据成员的值到标准输出流,程序通过实例化不同类型的类模板 TDerived 来调用它们的 print 函数。其中,基类 TBase 的 data1 和派生类 TDerived 的 data2 均被输出到标准输出流中。
  • 头文件声明和命名空间:
    #include <iostream>
    #include <string>
    
    using namespace std;
  • 定义了一个名为 TBase 的类模板,其中包含一个私有数据成员 data1 和一个公有成员函数 printprint 函数用于输出 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 和一个公有成员函数 print,其中在 print 函数中使用 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 ,并调用其 print 函数输出数据成员的值。接着声明了一个 TDerived<string, string> 类对象 d2 ,并调用其 print 函数输出数据成员的值:
    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 和一个公有成员函数 print,而 TDerived 类包含一个私有数据成员 data2,以及一个公有成员函数 print。在 print 函数中,使用 TBase<T1>::print() 调用了基类 TBase 中的 print 函数,并将类中的数据成员值输出到标准输出流中。
  • 在 main 函数中,首先定义了一个名为 d 的 TDerived<int, int> 类对象,并调用它的 print 函数输出数据成员的值。接着定义了一个名为 d2 的 TDerived<string, string> 类对象,并调用它的 print 函数输出数据成员的值。
  • 在该程序中,因为基类 TBase 和派生类 TDerived 分别定义了不同的模板参数类型,所以在实例化 TDerived 类对象时会使用默认构造函数(即未提供值时数据成员被默认初始化为 0 或空字符串(“”).)。所以,输出结果为 “TBase::0\nTDerived::0\nTBase::\nTDerived::\n”。
TBase::0
TDerived::0
TBase::
TDerived::

【示例四】 类模板的定义和使用

【示例代码】该程序共定义了两个类模板 TBase 和 TDerived

  • TBase 模板定义了一个公有数据成员 data1 和一个成员函数 printprint 函数负责输出数据成员 data1 的值
  • TDerived 模板继承自 TBase 模板并定义了一个公有数据成员 data2 和一个成员函数 printprint 函数中使用 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 是公有的,成员函数 print 用来输出 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 类型的对象。成员函数 print 输出 data1 的值,它首先调用基类 TBase 的 print 函数输出 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 的对象 dd2 和 d1,分别使用了不同的模板参数类型。通过分别为对象的成员变量 data1 和 data2 分别赋值后,调用 print 函数输出不同类型的模板参数对象的成员变量值。每一个 TDerived 对象都分别调用了一次 print 函数:
    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 和 TDerivedTDerived 是 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 和 TDerivedTBase 类模板中定义了私有成员变量 data1 和成员函数 print(),它们的访问权限是 private 和 publicTDerived 类模板继承于 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

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

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

相关文章

阵列模式综合第三部分:深度学习(附源码)

一、前言 这个例子展示了如何设计和训练卷积神经网络&#xff08;CNN&#xff09;来计算产生所需模式的元素权重。 二、介绍 模式合成是阵列处理中的一个重要课题。阵列权重有助于塑造传感器阵列的波束图案&#xff0c;以匹配所需图案。传统上&#xff0c;由于空间信号处理和频…

SSL工作原理

SSL 是一个安全协议&#xff0c;它提供使用 TCP/IP 的通信应用程序间的隐私与完整性。因特网的 超文本传输协议&#xff08;HTTP&#xff09;使用 SSL 来实现安全的通信。 在客户端与服务器间传输的数据是通过使用对称算法&#xff08;如 DES 或 RC4&#xff09;进行加密的。公…

使用ZenDAS进行Gompertz趋势分析

某项目做了18次测试&#xff0c;每次测试发现的缺陷个数如下表所示&#xff1a; 测试序号 发现缺陷数 1 60 2 96 3 157 4 191 5 155 6 106 7 64 8 335 9 92 10 196 11 109 12 133 13 166 14 129 15 16 16 30 17 19 18 5 对上述的数据在Z…

IPv6手工隧道配置与验证实验

IPv6手工隧道配置与验证实验 【实验目的】 熟悉IPv6手工隧道的概念。 掌握IPv6和IPv4共存的实现方法。 掌握IPv6手工隧道的配置。 验证配置。 【实验拓扑】 实验拓扑如下图所示。 实验拓扑 设备参数如表所示。 设备参数表 设备 接口 IPv6地址 子网掩码位数 默认网…

中间件-netty(1)

netty 前言篇 文章目录 一、IO基础篇1.概念1.1 阻塞(Block)和非阻塞(Non-Block)1.2 同步(Synchronization)和异步(Asynchronous)1.3 BIO 与 NIO 对比1.3.1 面向流与面向缓冲1.3.2 阻塞与非阻塞1.3.3 选择器的问世 2.NIO 和 BIO 如何影响应用程序的设计2.1 API调用2.2 数据处理2…

蓝桥杯专题-试题版-【操作格子】【查找整数】【分解质因数】【高精度加法】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

Spring FrameWork从入门到NB -三级缓存解决循环依赖内幕 (二)

开始用上一篇文章讲到的Spring依赖注入的步骤&#xff0c;用两个例子来推导一下整个过程&#xff0c;举例子有助于了解真相。 先用一个最简单的例子&#xff1a;没有依赖的单例bean的创建。 推导过程是需要上一篇文章的步骤的&#xff0c;要参照步骤一步一步来。 无依赖的单…

Linux内核代码60%都是驱动?驱动代码不会造成内核臃肿吗?

为什么内核中驱动占比最高 一、前言二、Linux中避免内核臃肿的措施2.1 交叉编译及SDK包的裁剪2.2 设备树2.3 模块化2.4 硬件抽象层 三、嵌入式Linux的裁剪四、总结 一、前言 今天逛知乎看到这么一个问题&#xff1a;为什么Linux内核代码60%都是驱动? 如果每支持新的设备就加入…

【设计模式】Java设计模式——模板方法模式(Template Pattern)

文章目录 1. 介绍1.1 定义1.2 作用 2. 模式结构2.1 UML类图2.2 模式组成 3. 代码实例3.1 背景3.2 应用 4. 优点5. 缺点6. 应用场景 1. 介绍 1.1 定义 模板方法模式(Template Pattern)&#xff0c;又叫模板模式&#xff0c;它属于行为型模式模板方法模式定义一个模板结构&…

Apikit 自学日记:版本管理

功能入口&#xff1a;API管理应用 / 选中某个项目 / 项目管理菜单 / 项目版本管理 项目版本管理功能模块提供对项目级别的版本管理&#xff0c;可新增、删除、对比项目级版本。在创建项目版本号的时候会对整个项目的部分模块数据进行快照保存。可用于每次迭代发布打全局版本号…

ESP32设备驱动-TMF8801激光测距传感器驱动

TMF8801激光测距传感器驱动 文章目录 TMF8801激光测距传感器驱动1、TMF8801介绍2、硬件准备3、软件准备4、驱动实现1、TMF8801介绍 TMF8801 是一款真正的直接飞行时间 (ToF) 传感器系统,采用单一模块化封装,通过亚纳秒光脉冲和抗锯齿“秒表”方法测量往返时间,提供高精度深度…

流量分析工具wireshark-学习笔记

&#xff08;一&#xff09;wireshark工具 1、wireshark工具简介 Wireshark是一种开源网络分析工具&#xff0c;它可以让你在计算机网络上捕获和查看数据包&#xff0c;并能帮助你深入了解网络的运行和协议的实现。它可以捕获不同类型的流量&#xff0c;包括以太网、Wi-Fi、TC…

【面试题】面试官问:如果有100个请求,你如何使用Promise控制并发?

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 开篇 在现代Web开发中&#xff0c;异步请求已经成为了必不可少的一部分。然而&#xff0c;…

ISP之图像降分辨率

1、图像缩放背景 图像的放大、缩小(简称缩放)是图像处理的一种处理方法。所谓图像缩放是指图像分辨率的改变&#xff0c;它在图像显示、传输、图像分析以及动画制作、电影合成、甚至医学图像处理中都有着相当广泛的应用。比如要在1024 X 768 分辨率的显示器上全屏显示800 X 60…

8.2 电压比较器(2)

五、集成电压比较器 1、集成电压比较器的主要特点和分类 电压比较器可将模拟信号转换成二值信号&#xff0c;即只有高电平和低电平两种状态的离散信号。因此&#xff0c;可用电压比较器作为模拟电路和数字电路的接口电路。集成电压比较器虽然比集成运放的开环增益低&#xff…

强化学习从基础到进阶-案例与实践[6]:演员-评论员算法(advantage actor-critic,A2C),异步A2C、与生成对抗网络的联系等详解

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

Arcmap读取nc文件并导出为tif格式

Arcmap读取nc文件并导出为tif格式 前言操作步骤 前言 在使用某一降水数据的时候&#xff0c;发现直接把nc格式的数据拖进Arcmap&#xff0c;查看属性表的时候是空的&#xff0c;点击图上的信息也只会显示一个值&#xff0c;但这个nc数据应该是有很多个值的&#xff08;我的数据…

Kubernetes - adm搭建 · 保姆级教程

master&#xff08;2C/4G&#xff0c;cpu核心数要求大于2&#xff09; 192.168.179.25 docker、kubeadm、kubelet、kubectl、flannel node01&#xff08;2C/2G&#xff09; 192.168.179.26 docker、kubeadm、kubelet、kubectl…

【JavaScript】JavaScript中的nodeName、nodeType、nodeValue区别

文章目录 JavaScript中的nodeName、nodeType、nodeValue区别(一)nodeName(二)nodeValue(三)nodeType JS代码demo JavaScript中的nodeName、nodeType、nodeValue区别 (一)nodeName https://www.w3schools.cn/jsref/prop_node_nodename.html 元素节点的 nodeName是标签名称 属性…

ffmpeg windows编译及调试完整版

目录 编译 基础环境准备 依赖环境安装 依赖库安装 X264 fdk-aac X265 ffmpeg-4.3.6 调试 基础项目环境搭建 VS2019项目创建 VS2019项目代码 vs2019配置 VS2019调试 编译 基础环境准备 1、安装vs2019环境 2、安装msys2工具 3、开始菜单启动x86 Native Tools Comm…