泛型编程是一种编程范式,它允许程序员编写与类型无关的代码,从而在编译期确定具体的类型。我们通过模板编写一个函数可以让编译器,自动生成对应类型的实现,这大大减少了重复代码的出现,还提高了代码的可维护性和可扩展性,很重要的一点是模板有零运行时开销特性。
在c中想要将一个算法设备多种类型,只能创建多份不同的函数,并且在函数尾部标注类型,来到c++中如果没有学模板,你可以实现函数重载,可以做到同一函数名不同参数类型,这两者方法都需要创建多个函数,是相当繁琐的操作,大大降低了程序的可维护性,而模板完美解决了这种问题,我们可以通过一份代码轻松的适配所有类型。

模板基础

C++ 模板的实例化遵循“两次编译”规则:

  • 第一次编译:编译器在解析模板定义时,检查基本语法(如类型名称、符号匹配),但不会生成实际代码。
  • 第二次编译:当模板被具体实例化时(例如 MyClass<int>),编译器会根据模板定义生成特定类型的实际代码。

函数模板

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

使用模板很简单,我们只需要使用template关键字,再跟上模板参数列表就可以了,模板的参数需要使用typename关键字来定义,上面的代码演示了一个函数模板,它的返回值和两个参数的类型都为T,这个T就会在编译时成为对应的类型
模板代码的生成过程称为 模板实例化,编译器会根据调用时的参数类型进行推导,我们也可以通过模板参数列表显示实例化,在实例化阶段,编译器也会检测类型的合法性,如果我们传入两个const char*这时候编译器就会保存,

func(1, 2);
func(1.1, 2.2);
func("hello", "world");
func<double>(1.1, 2.2);

const char* a = "hello";
const char* b = "world";
func(a, b);
main.cpp: In instantiation of 'T func(T, T) [with T = const char*]':
main.cpp:74:9:   required from here
main.cpp:69:14: error: invalid operands of types 'const char*' and 'const char*' to binary 'operator+'
   69 |     return a + b;
      |            ~~^~~

类模板

template <typename Key, typename Value, int N>
class KeyValuePair {
public:
    Key key;
    Value value;
    int nn;
    KeyValuePair(Key k, Value v) : key(k), value(v), nn(N) { }
};
// 在类外实现(仍需在头文件中)
template <typename Key, typename Value>
KeyValuePair<Key, Value>::KeyValuePair(Key k, Value v)
    : key(k), value(v) {
}
KeyValuePair<int,int> D(1,1);

需要注意类模板的实现通常需要放在头文件中,但若强行将成员函数的定义移到 .cpp 文件中,则必须通过 显式实例化指定具体类型,否则会导致链接错误。这是由 C++ 的编译模型和模板的工作机制决定的。
想要找到为什么会这样,我们想要简单了解一下cpp的编译机制,在编译时所有cpp文件都是独立编译的,而且编译器无法看到其他cpp文件的实现,它只能看到当前cpp与通过,预编译器引入的其他文件,又因为模板想要根据模板定义 + 具体类型参数 当场生成 实际代码,在编译cpp文件时没有具体的类型参数,这时候编译器不会生成任何代码,当编译main.cpp文件时看到了KeyValuePair<int,int>,这会触发实例化机制,但是因为类模板的头文件只有声明,编译器无法生成如何代码,最后在运行时发现找不到对应的构造函数,这就导致了链接错误
最后的int N是非类型参数,用于传入常量

模板特化

对于特定类型进行特别处理,分为全特化与偏特化两种,模板特化可以针对某些类型或值,优化性能、处理特殊逻辑或解决通用模板无法覆盖的问题。

// 通用模板
template <typename T, typename U>
struct MyPair {
    void print() { 
        std::cout << "Generic pair" << std::endl; 
    }
};
// 偏特化
template <typename T>
struct MyPair<T, T> {
    void print() { 
        std::cout << "MyPair<T, T>" << std::endl; 
    }
};
// 偏特化
template <typename T>
struct MyPair<T, int> {
    void print() { 
        std::cout << "MyPair<T, int>" << std::endl; 
    }
};
// 全特化
template <>
struct MyPair<double, int> {
    void print() { 
        std::cout << "MyPair<double, int>" << std::endl; 
    }
};


// 通用模板
template <typename T, typename U>
void printValue(T value, U unit) {
    std::cout << "T: " << value << " U: " << unit << std::endl;
}
// 通过重载实现偏特化
template <typename U>
void printValue(const char* value, U unit) {
    std::cout << "const char*: " << value << " U: " << unit << std::endl;
}
// 全特化
template <>
void printValue<const char*, const char*>(const char* value, const char* unit) {
    std::cout << "const char*: " << value << " const char*: " << unit << std::endl;
}

int main(){
    printValue(42, "km");//T: 42 U: km
    printValue("Hello", 45.55);//const char*: Hello U: 45.55
    printValue("Hello", "World");//const char*: Hello U: World

    MyPair<double, int>().print();//MyPair<double, int>
    MyPair<char, int>().print();//MyPair<T, int>
    MyPair<char, char>().print();//MyPair<T, T>
    MyPair<int, char>().print();//Generic pair
    return 0;
}

对于函数特化它只有全特化,偏特化想要通过重载实现

可变参数模板

这是c++ 11标注非常重要的概念,这个知识点被很多人叫做黑魔法,可以实现很多意想不到的功能,允许模板接受 任意数量 和 任意类型 的参数,这就是它的强大之处,通过typename...可以定义参数包,参数包可以包含 0 个或多个参数。

template<typename... Args>
void func(Args... args){
	std::cout << sizeof...(args) << std::endl;
};

func(1, 2, 3, 4, 5);//5
func(1, 2, 3);//3
func(1);//1
func();//0

上面的代码定义了一个可变参数函数,sizeof…用于获得参数包的大小,我们想要使用参数包的内容,参数包需要通过 展开 来使用,这也叫做参数包展开。展开的常见方式包括递归展开、折叠表达式(C++17)、逗号展开等。

// 通过递归
template<typename T>
void print(T head) {
    std::cout << "end " << head << std::endl;
}
template<typename T, typename... Args>
void print(T head, Args... rest) {
    std::cout << "parameter " << head << std::endl;
    print(rest...);
}
print(1, 3.14, "hello");
// 输出
parameter 1
parameter 3.14
end hello

// 折叠表达式(C++17)
template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 折叠表达式求和 (args1 + args2 + ...)
}
template <typename... Args>
void printAll(Args... args) {
    (std::cout << ... << args) << std::endl;
}
template<class ...Args>
void ShowList(Args... args){
    int arr[] = { args... }; //列表初始化
    for (auto e : arr) {
        std::cout << e << " ";
    }
    std::cout << std::endl;
};

sum(1.6, 2.5, 1)//5.1
printAll("hello ", "world", "!");//hello world!
ShowList(1, 2, 3, 4, 5);//1 2 3 4 5
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐