【C++】模板进阶

简介: 【C++】模板进阶

1.非类型模板参数

模板参数分为 类型形参 和 非类型形参

类型形参即出现在模板参数列表中, 跟在class或者typename之类的参数类型名称

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将参数当成常量使用

#include<iostream>
using namespace std;
#define N 10
template <class T>//类型模板参数
class Array
{
public:
private:
    T _a[N];
};
int main()
{
    Array<int> a;//10
    Array<double>b;//100
    return 0;
}

使用类型模板参数,虽然看似可以解决问题,但若a要10个int的数组,b要100个double的数组,宏就没办法解决了


所以为了解决这个问题,引入非类型模板参数,定义的是常量,一般是整形常量

#include<iostream>
using namespace std;
template <class T,size_t N>//非类型模板参数
class Array
{
public:
private:
    T _a[N];
};
int main()
{
    Array<int,10> a;
    Array<double,20>b;
    return 0;
}

同样非类型模板参数还可以给缺省值 ,由于是整型常量,所以缺省值也是常量


同样在函数内部,无法对常量值进行修改

#include<iostream>
using namespace std;
template<class T,size_t N=20>
void func(const T& a)
{
    N = 20;//左操作数必须为左值
}
int main()
{
    func(1);
}

必须为整形常量 整形包括 int 、char、 long 、long long 、short

若为double类型就会报错

2. 模板特化

使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理

函数模板特化


#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
    bool operator<(const Date& d)const
    {
        return (_year < d._year) ||
            (_year == d._year && _month < d._month) ||
            (_year == d._year && _month == d._month && _day < d._day);
    }
    bool operator>(const Date& d)const
    {
        return (_year > d._year) ||
            (_year == d._year && _month > d._month) ||
            (_year == d._year && _month == d._month && _day > d._day);
    }
    friend ostream& operator<<(ostream& _cout, const Date& d)
    {
        _cout << d._year << "-" << d._month << "-" << d._day;
        return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};
template<class T>
bool Less(T left, T right)
{
    return left < right;
}
int main()
{
    cout << Less(1, 2) << endl; // 可以比较,结果正确
    Date d1(2022, 7, 7);
    Date d2(2022, 7, 8);
    cout << Less(d1, d2) << endl; // 可以比较,结果正确
    Date* p1 = &d1;
    Date* p2 = &d2;
    cout << Less(p1, p2) << endl; // 可以比较,结果错误
    return 0;
}

若p1与p2都为Date*,则调用Less ,会变成 left与right指针的比较 ,不符合预期,我们想要的是日期与日期之间的比较


所以为了防止这种 特殊情况的发生,所以使用 模板特化 (对某些类型进行特殊化处理)

此时就可以正常比较两个日期


函数模板特化意义不大,可以直接使用函数重载的方式来实现的

类模板特化

偏特化

偏特化——进一步的限制

必须在原模版的基础上才可以使用

针对的是指针这个泛类

全特化

必须在原模版的基础上才可以使用

只针对其中一个,如指针中的日期类

半特化

对部分参数特化

template<class T1, class T2>
class Data
{
public:
    Data() { cout << "Data<T1, T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};
//半特化
template <class T1>
class Data<T1, int>//只将第一个模板参数特化
{
public:
    Data() { cout << "Data<T1, int>" << endl; }
private:
    T1 _d1;
    int _d2;
};
void TestVector()
{
    Data<int, int> d1;//Data<T1, int>
    Data<int*, int> d3;
//Data<T1, int>
    Data<double, int> d4;
//Data<T1, int>
}
int main()
{
    TestVector();
    return 0;
}

此时虽然有两个参数,但是只特化了一个 ,无论传的是 int、int*、double 都是走到半特化

参数的进一步限制

两个参数特化为引用类型

两个参数特化为指针类型

3. 模板的分离编译

模板并不支持分离编译 即声明在一个文件,定义在一个文件

此时调用模板add 就会报错,因为模板的声明和定义不在同一文件中

调用普通函数func,即可正常运行

模板会发生链接错误

func.h func.cpp test.cpp

预处理:头文件展开/ 注释的消除/ 条件编译/ 宏的替换


编译: 检查语法,生成汇编代码

func.s test.s

汇编:汇编代码转换为二进制机器码

func.o test.o

链接:合并生成可执行文件

a.out

由预处理阶段到 编译阶段时,通过func.i生成func.s ,生成汇编指令 ,但是只能生成func的,不能生成add的

函数被编译后生成指令,才可以找到第一句指令的地址 即函数地址


func会被编译成一堆指令,所以在func.o有func函数的地址,但是没有add的地址,因为add没有实例化 ,没办法确定T

就像是你差一点就可以交首付了,你打电话给你哥们借钱,你哥们说没问题,就像声明一样,这是一种承诺,

所以在编译阶段 add就可以过了 即call add( ? )

?代表现在没有地址

等到链接阶段才能拿到地址 ,而现在只是说得到承诺,到时候可以去拿到地址啦

但是 当你要交首付的时候,你哥们突然说借不了钱了,那这个房子首付也就交不上了

就好比 链接时 找不到add的函数地址了,所以会链接错误

链接之前不会交互,各走各的,不知道不能实例化,因为没办法确定T

解决链接错误的问题

显示实例化

虽然设置成double可以解决double的问题,但是传int 等又会报错,所以还是有很大的缺陷

局限性,实际中一般不使用

将声明和定义放在一个文件中

此时在预处理阶段将头文件展开,由于声明和定义都在一起,所以此时add函数 call时有地址存在,

不需要向func一样,链接时去寻找函数地址了

声明和定义放在一起,直接就可以实例化,编译时就有地址,不需要链接

4. 模板总结

缺陷

1. 模板会导致代码膨胀问题,也会导致编译时间变长

模板会进行实例化,所以运行时间变长,因为是一个模板,若传过来int 、char、double 类型都要实现,所以代码会膨胀


2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

通常为一报错就一大堆错误,一般只需要找到第一个错误并解决它,就可以了

————————————————

版权声明:本文为CSDN博主「风起、风落」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_62939852/article/details/129735680

相关文章
|
4月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
126 0
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
95 0
|
12月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
243 10
|
编译器 C++
【C++】——初识模板
【C++】——初识模板
【C++】——初识模板
|
7月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
8月前
|
编译器 C++
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
8月前
|
安全 C++
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
106 0
|
8月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
11月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
340 4
|
11月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
136 3