面向对象程序设计——Visual C++

第14章 模板

1、函数模板与重载有什么关系?

函数模板和重载都是C++中实现泛型编程的重要手段。函数模板是一种通用的函数定义,可以用于生成多个具有相同功能但不同类型的函数。重载是指在同一作用域内定义多个同名但参数列表不同的函数,以便根据不同的参数类型和个数来选择合适的函数进行调用。函数模板和重载的关系如下:

  1. 函数模板可以实现函数重载的功能。例如,可以定义一个通用的max函数模板,用于比较两个值的大小,然后根据不同的参数类型和个数来选择合适的函数进行调用。
  2. 函数重载可以与函数模板一起使用。例如,可以定义一个名为print的函数模板,用于输出任意类型的数据,然后根据不同的参数类型和个数来选择合适的函数进行调用。同时,还可以定义多个同名但参数列表不同的print函数,以便根据不同的参数类型和个数来选择合适的函数进行调用。 总的来说,函数模板和重载都是C++中实现泛型编程的重要手段,可以提高程序的灵活性和可读性。具体使用哪种方式,需要根据具体的需求和情况来决定。

2、试区分模板函数与函数模板?

在C++中,模板函数和函数模板是两个不同的概念。它们的区别如下:

  1. 模板函数是指使用模板定义的函数,可以是函数模板或成员函数模板。它们的定义方式与普通函数相同,只是在函数名后面加上了模板参数列表。例如:
template<typename T>
void func(T t){...} //函数模板
template<typename T>
class MyClass{
public:
    void func(T t){...} //成员函数模板
};
  1. 函数模板是指定义通用函数的模板,可以用于生成多个具有相同功能但不同类型的函数。函数模板的定义方式与普通函数不同,需要在函数名前面加上template关键字,并在尖括号中定义模板参数列表。例如:
template<typename T>
T max(T a, T b){...} //定义一个通用的max函数模板

区分模板函数和函数模板的关键在于,模板函数是使用模板定义的函数,而函数模板是定义通用函数的模板。同时,函数模板可以用于生成多个具有相同功能但不同类型的函数,而模板函数只能生成一个函数。

3、模板函数与重载的普通非模板函数被调用时的匹配先后顺序如何?

在C++中,模板函数和重载的普通非模板函数被调用时,编译器会按照以下顺序进行匹配:

  1. 精确匹配:如果有一个函数的参数类型与调用时的参数类型完全相同,则选择该函数。
  2. 模板匹配:如果有一个函数是模板函数,并且可以通过模板参数推导得到与调用时的参数类型完全相同的函数,则选择该函数。
  3. 标准类型转换匹配:如果有一个函数可以通过标准类型转换(如int到double的转换)得到与调用时的参数类型完全相同的函数,则选择该函数。
  4. 用户自定义类型转换匹配:如果有一个函数可以通过用户自定义的类型转换(如类型转换函数)得到与调用时的参数类型完全相同的函数,则选择该函数。
  5. 不匹配:如果以上都不符合,则选择不匹配。 需要注意的是,如果有多个函数都符合以上条件,则编译器会选择最匹配的函数进行调用。如果还存在歧义,则会报错。因此,在编写程序时,应该尽量避免出现函数重载和模板函数重载的情况,以免产生歧义。

4、试区分类模板与模板类?

在C++中,模板和模板类是两个不同的概念。它们的区别如下:

  1. 模板是一种通用的代码定义,可以用于生成多个具有相同功能但不同类型的代码。模板可以是函数模板或类模板。例如,可以定义一个通用的max函数模板,用于比较两个值的大小,然后根据不同的参数类型和个数来选择合适的函数进行调用。
  2. 模板类是一种使用模板定义的类,可以用于生成多个具有相同功能但不同类型的类。模板类的定义方式与普通类不同,需要在类名后面加上尖括号,并在尖括号中定义模板参数列表。例如,可以定义一个通用的Stack类模板,用于保存任意类型的数据。 区分模板和模板类的关键在于,模板是一种通用的代码定义,可以用于生成多个具有相同功能但不同类型的代码,而模板类是一种使用模板定义的类,可以用于生成多个具有相同功能但不同类型的类。同时,模板可以是函数模板或类模板,而模板类只能是类模板。

5、如何从模板类派生出非模板类?

在C++中,可以从模板类派生出非模板类,方法如下:

  1. 定义一个继承自模板类的非模板类。
  2. 在非模板类中指定模板参数,以确定模板类的具体类型。 例如,可以如下定义一个从模板类Stack派生出的非模板类IntStack:
template<typenameT> class Stack{...};//定义一个模板类Stack
class IntStack:public Stack<int>{...};//定义一个从模板类Stack派生出的非模板类IntStack

在上述代码中,IntStack是一个继承自模板类Stack的非模板类,它指定了模板参数为int,以确定Stack的具体类型为Stack<int>。 通过以上的定义,可以在使用IntStack时,自动调用Stack<int>中定义的成员函数和数据成员,从而实现自定义的操作。 需要注意的是,从模板类派生出非模板类时,需要指定模板参数,以确定模板类的具体类型。同时,派生类中可以使用模板类中定义的成员函数和数据成员,但是不能再定义新的模板参数。

习题

1、编写一个函数模板,它返回两个值的最小值。要求确保能正确处理字符串。

#include <iostream>
#include <string>

template<typename T>
T min(const T& a, const T& b) {
    return b < a ? b : a;
}

template<>
std::string min<std::string>(const std::string& a, const std::string& b) {
    return b < a ? b : a;
}

int main() {
    std::cout << min(2, 3) << std::endl; // 输出2
    std::cout << min(3.14, 2.78) << std::endl; // 输出2.78
    std::cout << min(std::string("hello"), std::string("world")) << std::endl; // 输出"hello"
    return 0;
}

在这个例子中,函数模板min有两个模板参数,类型参数T和返回值类型R。其中,类型参数T表示输入的参数类型,返回值类型R则表示返回值的类型,这里省略了返回值类型,因为它可以通过类型推导自动推导出来。在函数模板的实现中,使用const T&表示参数类型为T的常引用,可以提高函数的效率,避免拷贝大型的对象。此外,对于字符串类型,由于字符串不能直接进行比较,需要特化函数模板min,重新实现字符串类型的比较。

2、编写一个函数模板,它返回数值类型值的绝对值。

template <typename T>
T absoluteValue(T val) {
    return (val < 0) ? -val : val;
}

这个函数模板接受一个类型为 T 的参数 val,并返回它的绝对值。在函数中,我们使用了一个三目运算符,如果 val 是负数,返回它的相反数,否则返回 val 本身。

我们可以将这个函数模板用于不同类型的参数,如整型、浮点型等等。例如:

int a = -5;
float b = -3.14;
double c = 2.71828;

cout << absoluteValue(a) << endl;  // 输出 5
cout << absoluteValue(b) << endl;  // 输出 3.14
cout << absoluteValue(c) << endl;  // 输出 2.71828

注意,这个函数模板只适用于能够进行小于运算符比较的类型。对于自定义的类型,我们需要重载小于运算符才能使用这个函数模板。

3、编写一个通用的数组(Array)类模板,要求包括对数组进行写数据和读数据操作。

template<typename T, int N>
class Array {
private:
    T data[N];
public:
    Array() {} // 默认构造函数
    T& operator[](int i) { return data[i]; } // 重载下标运算符
    const T& operator[](int i) const { return data[i]; } // 重载下标运算符(常量版本)
    int size() const { return N; } // 返回数组大小
};

该类模板可以用于任何类型T和任何正整数N,其中N为数组大小。使用该类模板可以这样操作:

// 定义一个数组实例
Array<int, 5> a;

// 写入数据
for (int i = 0; i < a.size(); i++) {
    a[i] = i * i;
}

// 读取数据
for (int i = 0; i < a.size(); i++) {
    std::cout << a[i] << " ";
}

在上面的示例中,我们定义了一个大小为5的整数数组,并对其进行写数据和读数据操作。

4、编写一个通用的队列(Queue)类模板,要求包括对队列进行入队、出队和打印操作。

#include <iostream>
#include <cstdlib>
using namespace std;

const int MAX_SIZE = 100;

template <class T>
class Queue {
private:
    T data[MAX_SIZE];
    int front, rear;
public:
    Queue() {
        front = rear = 0;
    }
    bool isEmpty() {
        return front == rear;
    }
    bool isFull() {
        return (rear + 1) % MAX_SIZE == front;
    }
    bool EnQueue(T x) {
        if (isFull())
            return false;
        data[rear] = x;
        rear = (rear + 1) % MAX_SIZE;
        return true;
    }
    bool DeQueue(T &x) {
        if (isEmpty())
            return false;
        x = data[front];
        front = (front + 1) % MAX_SIZE;
        return true;
    }
    void PrintQueue() {
        if (isEmpty())
            cout << "Queue is empty" << endl;
        else {
            int i = front;
            while (i != rear) {
                cout << data[i] << " ";
                i = (i + 1) % MAX_SIZE;
            }
            cout << endl;
        }
    }
};

int main() {
    Queue<int> q;
    for (int i = 1; i <= 5; i++)
        q.EnQueue(i);
    q.PrintQueue();
    int x;
    q.DeQueue(x);
    cout << "DeQueue element: " << x << endl;
    q.PrintQueue();
    return 0;
}

该模板类中使用了数组来实现队列,定义了数据成员front和rear,分别表示队头和队尾在数组中的下标。函数成员包括:isEmpty()、isFull()、EnQueue()、DeQueue()和PrintQueue(),分别用于判断队列是否为空、队列是否已满、向队列中插入元素、从队列中删除元素以及打印队列元素。其中EnQueue()和DeQueue()函数分别采用了循环队列的方式实现。