面试题答案
一键面试1. 声明与定义特殊之处
- 模板参数声明:模板函数需要在函数定义之前声明模板参数。例如:
template <typename T>
T add(T a, T b) {
return a + b;
}
这里template <typename T>
声明了一个类型模板参数T
,在函数体中可以使用T
作为类型。
- 定义的通用性:模板函数的定义是通用的,它不针对具体类型,而是根据调用时传入的实际类型生成具体的函数实例。
2. 实例化机制
- 普通函数:普通函数在编译时就确定了函数的具体实现,一旦定义就不会改变。例如:
int add_int(int a, int b) {
return a + b;
}
这个函数只能处理int
类型的参数。
- 模板函数:模板函数是在使用(调用)时根据传入的实际类型进行实例化。例如:
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int result1 = add(2, 3); // 实例化出add<int>(int, int)
double result2 = add(2.5, 3.5); // 实例化出add<double>(double, double)
return 0;
}
编译器会根据调用时传入的参数类型,为add
函数生成针对int
和double
类型的具体函数实例。
3. 作用域
- 普通函数:普通函数的作用域遵循常规的C++作用域规则,从定义处开始到所在作用域结束。例如:
void func() {
int a = 10;
{
int b = 20;
// b的作用域从这里开始到这个块结束
}
// 这里不能访问b
}
- 模板函数:模板函数的作用域同样遵循常规规则,但由于其实例化的特性,模板函数的声明在其作用域内任何地方都可以实例化。例如:
template <typename T>
T multiply(T a, T b) {
return a * b;
}
int main() {
int result = multiply(5, 3); // 实例化出multiply<int>(int, int)
return 0;
}
只要在multiply
模板函数声明后的作用域内,都可以根据需要实例化它。
4. 头文件和源文件放置策略
- 普通函数:普通函数通常在源文件(
.cpp
)中定义,在头文件(.h
)中声明。例如,add_int.h
:
#ifndef ADD_INT_H
#define ADD_INT_H
int add_int(int a, int b);
#endif
add_int.cpp
:
#include "add_int.h"
int add_int(int a, int b) {
return a + b;
}
- 模板函数:由于模板函数在实例化时需要完整的定义,所以通常将模板函数的声明和定义都放在头文件中。例如,
add_template.h
:
#ifndef ADD_TEMPLATE_H
#define ADD_TEMPLATE_H
template <typename T>
T add(T a, T b) {
return a + b;
}
#endif
在main.cpp
中使用时:
#include "add_template.h"
int main() {
int result = add(2, 3);
return 0;
}
如果将模板函数的定义放在源文件中,在实例化时可能会找不到定义,导致链接错误。因为模板函数的实例化是在包含模板定义的编译单元中进行的,而不是像普通函数那样在链接阶段统一处理。