【C++容器篇】泛型编程,类模板,函数模板,模板特化知识点总结

博客主页:Skylar Lin
望本文能够给您带来一定的帮助,如果有错误的地方敬请斧正!
新人博主🧑,希望多多支持🍺,还有好多库存和大家分享🎁。
转载需注明出处和原作🌹。

前言

泛型编程的核心思想是将代码与特定数据类型解耦,使得代码能够适用于多种数据类型,从而实现更高程度的抽象。而这种思想在C++中通过模板技术得以实现,其中包括了函数模板类模板。使用模板,我们可以编写一次代码,然后在需要的地方根据实际数据类型进行实例化,从而获得专门针对特定数据类型优化的代码。

在本文中,我总结了C++中函数模板和类模板的使用方法,以及如何通过模板特化实现对特定数据类型的定制化处理。通过阅读本文,您一定能够对泛型编程有个不错的了解。


函数模板

函数模板允许我们定义通用的函数,其中的类型参数可以在调用时被实际的数据类型所替代。函数模板使用关键字 template 声明,并在尖括号中指定类型参数。

#include <iostream>
using namespace std;

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

int main() {
    int result1 = my_max(10, 20);          // 实例化 max<int>(10, 20)
    double result2 = my_max(3.14, 2.71);   // 实例化 max<double>(3.14, 2.71)
    int result3 = my_max<int>(13, 0.14);   // 正常,类型不一样,但可以发生隐式类型转换
  
    cout << result1 << endl;
    cout << result2 << endl;
    cout << result3 << endl;  // 输出 13
    return 0;
}

函数模板在调用时,通过自动类型推导可以避免显式指定模板参数类型,但如果需要,我们也可以通过指定类型的方式进行调用。

函数模板的重载

函数模板也可以像普通函数一样进行重载。当普通函数和函数模板都能匹配函数调用时,编译器会优先选择普通函数。只有在没有找到适合的普通函数时,才会实例化函数模板。

#include <iostream>

int max(int a, int b) {
    return (a > b) ? a : b;
}

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

template <typename T>
T max(T a, T b, T c) {
    if (a > b && a > c)
        return a;
    else if (b > a && b > c)
        return b;
    else
        return c;
}

int main() {
    int result1 = max(10, 20);      // 调用普通函数 max(int, int)
    double result2 = max(3.14, 2.71); // 实例化 max<double>(3.14, 2.71)
    int result3 = max(10, 20, 30);  // 没有找到适合的普通函数,实例化函数模板

    std::cout << result1 << std::endl;
    std::cout << result2 << std::endl;
    std::cout << result3 << std::endl;

    return 0;
}

类模板

类模板允许我们定义通用的类,其中的类型参数可以在创建对象时被实际的数据类型所替代。类模板使用关键字 template 声明,并在尖括号中指定类型参数。

#include <iostream>

template <typename T, typename U = int>
class MyContainer {
private:
    T data1;
    U data2;

public:
    MyContainer(T value1, U value2) : data1(value1), data2(value2) {}

    T getData1() const;
    U getData2() const;
};

template <typename T, typename U>
T MyContainer<T, U>::getData1() const {
    return data1;
}

template <typename T, typename U>
U MyContainer<T, U>::getData2() const {
    return data2;
}

int main() {
    MyContainer<double, double> container1(1.3, 1.4);      // 实例化 MyContainer<double, double>
    MyContainer<double> container2(1.3, 1.4);             // 实例化 MyContainer<double, int>

    auto value1 = container1.getData1();
    auto value2 = container1.getData2();

    auto value3 = container2.getData1();
    auto value4 = container2.getData2();

    std::cout << value1 << "和" << value2 << std::endl;    // 输出 1.3和1.4
    std::cout << value3 << "和" << value4 << std::endl;    // 输出 1.3和1

    return 0;
}

类模板中成员函数的创建时机

普通类中成员函数在类定义阶段创建,但类模板中的成员函数在调用时才会创建

  • 类模板中成员函数的定义实际上是模板的描述,告诉编译器如何生成函数代码,但不会创建具体函数代码。
  • 在使用类模板创建对象时,根据模板实参,编译器会实际生成成员函数的实现代码,这称为成员函数的实例化
template <typename T>
class MyContainer {
public:
    void print(T value) {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyContainer<int> intContainer;    // 创建 MyContainer<int> 对象
    intContainer.print(42);           // 实例化 print<int>(int value),输出 "Value: 42"

    MyContainer<double> doubleContainer; // 创建 MyContainer<double> 对象
    doubleContainer.print(3.14);         // 实例化 print<double>(double value),输出 "Value: 3.14"

    return 0;
}

类模板对象作为函数参数

使用类模板实例化出的对象作为参数传递给函数有三种方式:

  • 指定传入类型: 直接显示对象的数据类型(最常用)
  • 参数模板化: 将对象中的参数变为模板进行传递
  • 整个类模板化: 将对象类型作为模板参数进行传递
#include<iostream>
#include<string>
using namespace std;

template<class T1,class T2>
class Person {
public:
	Person(T1 name,T2 age) {
		this->m_Name = name;
		this->m_Age = age;
	}
	void showPerson() {
		cout << "姓名:" << this->m_Name << endl;
		cout << "年龄:" << this->m_Age << endl;
	}
	T1 m_Name;
	T2 m_Age;	
};

// 1. 指定传入类型
void printPerson1(Person<string,int> &p) {
	p.showPerson();
}
void test01() {
	Person<string,int> p ("孙悟空",999);
	printPerson1(p);
}

// 2. 参数模板化(函数模板)
template<class T1,class T2>
void printPerson2(Person<T1,T2> &p) {
	p.showPerson();
	cout << "T1的类型为:" << typeid(T1).name() << endl;
    cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02() {
	Person<string,int> p ("猪八戒",888);
	printPerson2(p);
}

// 3. 整个类模板化
template<class T>
void printPerson3(T &p) {
	p.showPerson();
	cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03() {
	Person<string,int> p ("唐三藏",777);
	printPerson3(p);
}

int main() {
	test01();
	test02();
	test03();
	return 0;
}

类模板继承

当派生类继承的基类是一个类模板时,派生类在声明时需要明确指定基类中T的类型。这是因为编译器需要在编译时为派生类分配内存。

#include <iostream>

template <typename T>
class Base {
protected:
    T data;

public:
    Base(T value) : data(value) {}

    void printData() {
        std::cout << "Base: " << data << std::endl;
    }
};

// 派生类模板继承父类模板
template <typename T>
class Derived : public Base<T> {
private:
    T extraData;

public:
    Derived(T value1, T value2) : Base<T>(value1), extraData(value2) {}

    void printExtraData() {
        std::cout << "Derived: " << this->extraData << std::endl;
    }
};

int main() {
    Derived<int> obj(10, 20);

    obj.printData();      // 输出 "Base: 10"
    obj.printExtraData(); // 输出 "Derived: 20"

    return 0;
}

如果想在基类中灵活指定T的类型,需要将派生类写成类模板。

模板特化

在C++中,可以针对特定的类型提供特定的模板实现,称为模板特化(Template Specialization)。模板特化允许我们为某些特定类型提供定制的实现,以便在使用这些类型作为模板参数时,编译器可以选择特化版本而不是通用的模板版本。模板特化有两种形式:完全特化和偏特化。

  • 完全特化是对模板的完整特化,使用 template <> 语法,尖括号中指定具体的类型。
  • 偏特化是对模板的部分特化,对一些模板参数提供特定的实现,但保留其他模板参数的通用实现。偏特化适用于函数模板的部分特殊情况。

函数模板特化

#include <iostream>

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

template <>
int add(int a, int b) {
    return a * b;
}

template <typename T, typename U>
void print(T a, U b) {
    std::cout << a << " " << b << std::endl;
}

template <typename U>
void print(int a, U b) {
    std::cout << a * 2 << " " << b << std::endl;
}

int main() {
    int result1 = add(10.0, 20.0);     // 使用通用模板,返回 30
    int result2 = add(10, 20);         // 使用特化,返回 200

    std::cout << result1 << std::endl;
    std::cout << result2 << std::endl;

    print(1.3, 1.4);         // 使用通用模板,输出 "1.3 1.4"
    print(13, 14);           // 使用偏特化,输出 "26 14"

    return 0;
}

类模板特化

关于类模板的特化,C++实际上只支持对成员函数进行特化,而不支持对整个类进行完全特化或偏特化。

#include <iostream>

template <typename T>
class MyContainer {
public:
    void print() {
        std::cout << "Generic version" << std::endl;
    }
};

template <>
void MyContainer<int>::print() {
    std::cout << "Specialization for int" << std::endl;
}

int main() {
    MyContainer<char> obj1;    // 使用通用模板,输出 "Generic version"
    MyContainer<int> obj2;     // 使用特化,输出 "Specialization for int"

    obj1.print();
    obj2.print();

    return 0;
}

SFINAE原则

当使用模板时,C++会尝试根据函数参数的类型来推导模板参数类型,但有时可能会遇到多个模板函数都可以匹配传递的参数,这会导致模板重载冲突。这时,SFINAE原则就派上用场了。

在C++中,SFINAE(Substitution Failure Is Not An Error)原则是一种模板元编程技术。它允许编译器在模板实例化时忽略一些错误,从而使得我们能够根据条件来选择合适的模板实例。
在使用SFINAE原则时,你需要了解以下几个关键要点:

  • 头文件引入: 需要引入 <type_traits> 头文件,以便使用其中的类型特性和模板工具。

  • 条件约束: 使用 std::enable_if_t 模板结合类型特性来对模板参数进行条件约束。

    类型特性(如 std::is_integral_vstd::is_floating_point_v 等)可以判断模板参数是否满足特定条件,从而影响模板参数的推导结果。

  • 模板参数默认值: 在模板参数列表中使用 typename = ...class = ... 指明约束条件。

    比如:<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>

  • 使用时机:函数重载模板特化 时使用。

下面是一个简单的例子,演示了如何使用SFINAE原则:

#include <iostream>
#include <type_traits>

// 使用 SFINAE 原则实现的模板重载
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void process(T value) {
    std::cout << "Processing integral value: " << value << std::endl;
}

template <typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
void process(T value) {
    std::cout << "Processing floating-point value: " << value << std::endl;
}

int main() {
    process(42);     // 调用第一个 process 函数
    process(3.14);   // 调用第二个 process 函数
    // process("Hello");  // 编译错误,没有匹配的函数

    return 0;
}

C++20中的新特性

C++20 引入了许多新特性,**概念(Concepts)**就是其中之一。概念同样允许我们在模板中添加约束条件。

概念的使用可以分为以下几个步骤:

  • 定义概念: 使用template <typename T> concept ConceptName = ...的形式,在等号后面使用表达式来描述约束的条件。

  • 使用概念: 在模板参数列表中使用约束的语法是:template <ConceptName T>,该模板参数与指定的概念进行关联。

  • 编写模板代码: 在编写模板函数或类时,可以在模板参数列表中使用概念来约束参数类型。这样,在调用模板函数或实例化模板类时,只有满足概念约束的参数才会成功。

#include <iostream>
#include <concepts>

// 定义一个概念,约束类型必须为整数
template <typename T>
concept IntegralType = std::is_integral_v<T>;

// 使用概念约束函数模板
template <IntegralType T>
void printOnlyIntegersWithConcept(T value) {
    std::cout << value << " is an integer." << std::endl;
}

int main() {
    printOnlyIntegersWithConcept(42);   // 调用成功,满足IntegralType概念
    // printOnlyIntegersWithConcept(3.14); // 编译错误,不满足IntegralType概念

    return 0;
}

更多高级主题

除了基础的函数模板和类模板,泛型编程领域还存在许多高级主题,值得我们深入探讨。在今后有时间 Lin 也会总结出来和大家分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Skylar Lin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值