面试题 1 :解释一下什么是C++模板?它们主要用于解决什么问题?

C++模板是一种编程技术,它允许程序员编写与类型无关的代码。C++模板可以分为两种主要类型:函数模板和类模板。
函数模板是针对仅参数类型不同的函数而设计的。通过函数模板,程序员能够用不同类型的参数调用相同的函数,由编译器决定调用哪一种类型,并且从模板中生成相应的代码。这种方式大大减少了代码重复,提高了代码的可重用性。
类模板则是针对仅数据成员和成员函数类型不同的类而设计的。类模板使得程序员能够定义与数据类型无关的类,从而提高了类的灵活性和可重用性。
C++模板主要用于解决代码重用的问题。通过使用模板,程序员可以编写出更加通用的代码,这些代码可以处理多种数据类型,而无需为每种数据类型都单独编写代码。这大大提高了编程效率和代码的可维护性。
此外,C++模板还支持模板特化,这允许程序员为特定的数据类型提供专门的实现。这种特性使得模板更加灵活,能够适应各种复杂的需求。
总的来说,C++模板是一种强大的编程工具,它使得程序员能够编写出更加通用、灵活和可重用的代码。

面试题 2 :如何定义一个函数模板和一个类模板?

在C++中,函数模板和类模板的定义方式如下:
函数模板
函数模板的定义使用 template 关键字,后跟一个尖括号内的模板参数列表。模板参数通常使用 typename (C++14及之后)或 class 关键字来声明。函数模板的定义格式如下:

template <typename T> // 或者使用 class T
return_type function_name(parameter_list) 
{
    // 函数体
}

例如,定义一个函数模板,用于交换两个变量的值:

template <typename T>
void swap(T& a, T& b) 
{
    T temp = a;
    a = b;
    b = temp;
}

这个函数模板可以用于任何类型 T ,只要该类型支持赋值操作。
类模板
类模板的定义与函数模板类似,但是将 template 关键字放在类名的前面,并在类名后的尖括号内指定模板参数。类模板的定义格式如下:

template <typename T> // 或者使用 class T
class class_name 
{
    // 类成员定义
};

例如,定义一个简单的类模板,用于存储一个固定大小的数组:

template <typename T, std::size_t N>
class Array 
{
public:
    T& operator[](std::size_t index) 
	{
        return arr[index];
    }
    const T& operator[](std::size_t index) const 
	{
        return arr[index];
    }
private:
    T arr[N];
};

这个类模板 Array 接受两个模板参数:一个类型参数 T ,用于指定数组中元素的类型;一个非类型参数 N ,用于指定数组的大小。
在实例化模板时,编译器会根据提供的模板参数生成相应的函数或类的特化版本。例如,对于上面的 swap 函数模板,如果调用 swap(int a, int b) ,编译器就会生成一个处理 int 类型参数的函数版本。同样,对于 Array 类模板,如果声明一个 Array<int, 10> 的实例,编译器会生成一个包含 10 个 int 类型元素的数组类。

面试题 3 :模板参数是什么?它们有哪些类型?

模板参数是C++模板编程中的关键组成部分,它们使得模板具有通用性和灵活性。模板参数允许我们编写代码时指定一些未确定的部分,这些部分在模板实例化时会被具体的类型或值所替代。
模板参数主要有三种类型:
(1)类型模板参数(Type Template Parameters): 这是使用模板的主要目的。类型模板参数在声明模板时,使用 typename (C++14及之后)或 class 关键字来标记。类型模板参数表示的是一个未知的类型,在模板实例化时,可以用具体的类型来替换它。例如:

template <typename T>
T add(T a, T b)
{
    return a + b;
}

在这个例子中, T 就是一个类型模板参数,它可以是任何类型,如 int 、 double 、 std::string 等。
(2)非类型模板参数(Non-type Template Parameters): 非类型模板参数允许我们使用值(而非类型)来实例化模板。这些参数必须是常量表达式,并且它们的类型必须是整型、枚举、指针或引用。非类型模板参数主要用于控制模板的某些行为或优化。例如:

template <int N>
struct Array 
{
    int data[N];
};

在这个例子中, N 是一个非类型模板参数,它指定了数组的大小。

(3)模板的模板参数(Template Template Parameters): 这是一种特殊的模板参数,允许一个模板接受另一个模板作为参数。这种技术通常用于实现元编程的高级特性,如策略模式或自定义算法。例如:

template <template <typename> class Container>
void printContainerSize(const Container<int>& cont) 
{
    std::cout << cont.size() << std::endl;
}

在这个例子中, Container 是一个模板模板参数,它接受一个模板类作为参数。 printContainerSize 函数可以接受任何容器类型,只要该容器类型有一个 size 成员函数。
模板参数使得 C++ 模板编程变得非常灵活和强大,可以极大地减少代码冗余,提高代码重用率。

面试题 4 :模板参数中的 typename 和 class 关键字有什么区别?

在 C++ 模板参数列表中, typename 和 class 关键字在语义上是等价的,它们都可以用来声明一个类型模板参数。两者的主要区别在于历史渊源和语法规则。
(1)历史渊源:
在C++的早期版本中(C++98/03), class 是唯一的关键字用于声明类型模板参数。
在C++11中,引入了 typename 作为 class 的另一种选择,以增加代码的可读性和一致性。
在C++14中, typename 成为首选,因为它也可以用于模板模板参数和非类型模板参数的依赖名称。
(2)语法规则:
在模板参数列表中, class 和 typename 可以互换使用来声明类型模板参数。
当在模板内部使用依赖名称(即依赖于模板参数的名称)时,必须使用 typename 来明确指示该名称是一个类型。例如,在模板类的成员函数内部,如果有一个成员模板,那么该成员模板的模板参数前必须加上 typename 。

样例代码如下:

template <class T>
class MyClass 
{
    void func() 
	{
        // 在模板类的成员函数内部,使用依赖名称时必须用 typename
        typename MyClass<T>::NestedType n; // 正确
        //class MyClass<T>::NestedType m;    // 错误
    }
};

在上面代码中, NestedType 是一个依赖于模板参数 T 的类型,因此在 func 函数中使用它时,必须使用 typename 来指明它是一个类型。
总体而言, typename 和 class 在模板参数列表中用于声明类型模板参数时可以互换使用,但在模板内部, typename 具有更广泛的用途,它可以用于标记依赖名称的类型。因此,在 C++14 及以后的版本中,通常推荐使用 typename 来声明类型模板参数。

面试题 5 :解释一下模板实例化是什么过程?

模板实例化是C++模板编程中的一个重要概念。在C++中,模板本身并不是真正的函数或类,而仅仅是一种编译器用来生成函数或类的"摸具"。模板实例化指的是根据这些"摸具"生成实际的函数或类的过程。
对于函数模板而言,模板实例化会生成一个具体的函数,这个函数具有确定的参数类型和返回类型。而对于类模板,模板实例化会生成一个具体的类,这个类具有确定的成员变量和成员函数类型。
模板实例化可以是隐式的,也可以是显式的。隐式实例化发生在编译器遇到对模板的调用时,它会根据调用时的类型参数自动推导出具体的类型,并生成相应的函数或类。显式实例化则是由程序员通过特定的语法明确指定类型参数来完成的。
模板实例化是按需进行的,也就是说,只有当模板被实际使用时,编译器才会生成相应的函数或类。这有助于减少不必要的代码生成,提高编译效率。同时,由于模板实例化生成的代码是针对特定类型的,因此可以充分利用类型信息,实现更好的性能优化。

面试题 6 :模板隐式实例化和显式实例化的区别是什么?

隐式实例化 是当编译器在代码中遇到对模板的调用时自动进行的。编译器会根据调用时的类型参数自动推导出具体的类型,并生成相应的函数或类。隐式实例化是按需进行的,只有当模板被实际使用时,编译器才会生成相应的代码。这种方式的优点是方便,程序员无需手动指定类型参数,但缺点是如果模板的使用非常分散,可能会导致编译时间较长。
显式实例化 则是由程序员通过特定的语法明确指定类型参数来完成的。程序员可以在代码中直接写出模板的具体类型,从而触发编译器生成相应的函数或类。显式实例化的优点是可以提前生成所需的代码,有助于减少编译时间。此外,显式实例化还可以用于模板的显式特化,即针对特定类型提供专门的实现。但缺点是程序员需要手动指定类型参数,增加了代码的复杂性。
如下是一个简单的例子,可以说明这两者的区别:
有一个函数模板,用于计算两个数的最大值:

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

在这个例子中,并没有为 max 函数提供具体的实现,只是定义了一个模板。当在代码中调用这个函数时,如 max(1, 2) ,编译器会根据我们传递的参数类型(在这里是 int )隐式地实例化这个模板,生成一个具体的 int max(int a, int b) 函数。这个过程是自动的,无需手动指定类型参数,这就是 隐式实例化
相比之下,显式实例化需要我们手动指定类型参数。假设还是想使用 max 函数,但这次需要在调用之前就为其生成一个 int 类型的实例。可以这样做:

template int max<int>(int, int); // 显式实例化声明

int main() 
{
    int result = max<int>(1, 2); // 使用显式实例化的函数
    // ...
}

在上面代码中,在 main 函数之前显式地实例化了 max 函数模板,为 int 类型生成了具体的函数实现。这样,在 main 函数中调用 max(1, 2) 时,编译器就会使用之前显式实例化的 int max(int, int) 函数,而不是再次隐式地实例化模板。这就是 显式实例化
从上面的代码中可以看出隐式实例化和显式实例化的主要区别:
(1)触发方式: 隐式实例化是由编译器在代码遇到模板调用时自动触发的;而显式实例化是由程序员通过特定语法手动触发的。
(2)时机: 隐式实例化是按需进行的,只有当模板被实际使用时才会生成代码;显式实例化则允许程序员提前生成所需类型的代码。
(3)代码控制: 隐式实例化使得代码更加简洁,但可能导致编译器在多个地方生成相同的实例化代码,增加编译时间;显式实例化可以提供更好的控制,但要求程序员手动管理模板的实例化。

02-25 03:18