【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

相关文章
|
24天前
|
编译器 C++
【C++】——初识模板
【C++】——初识模板
30 1
【C++】——初识模板
|
2月前
|
程序员 C++
C++模板元编程入门
【7月更文挑战第9天】C++模板元编程是一项强大而复杂的技术,它允许程序员在编译时进行复杂的计算和操作,从而提高了程序的性能和灵活性。然而,模板元编程的复杂性和抽象性也使其难以掌握和应用。通过本文的介绍,希望能够帮助你初步了解C++模板元编程的基本概念和技术要点,为进一步深入学习和应用打下坚实的基础。在实际开发中,合理运用模板元编程技术,可以极大地提升程序的性能和可维护性。
|
3月前
|
安全 编译器 C++
C++一分钟之-编译时计算:constexpr与模板元编程
【6月更文挑战第28天】在C++中,`constexpr`和模板元编程用于编译时计算,提升性能和类型安全。`constexpr`指示编译器在编译时计算函数或对象,而模板元编程通过模板生成类型依赖代码。常见问题包括误解constexpr函数限制和模板递归深度。解决策略包括理解规则、编写清晰代码、测试验证和适度使用。通过实战示例展示了如何使用`constexpr`计算阶乘和模板元编程计算平方。
55 13
|
2月前
|
存储 编译器 C++
【C++】详解C++的模板
【C++】详解C++的模板
|
1月前
|
编译器 C++
【C++】模板初级
【C++】模板初级
|
1月前
|
安全 编译器 C++
【C++】模板进阶
【C++】模板进阶
|
30天前
|
并行计算 测试技术 开发工具
【简历模板】c/c++软件工程师
【简历模板】c/c++软件工程师
40 0
|
2月前
|
安全 编译器 C++
C++一分钟之-模板元编程实例:类型 traits
【7月更文挑战第15天】C++的模板元编程利用编译时计算提升性能,类型traits是其中的关键,用于查询和修改类型信息。文章探讨了如何使用和避免过度复杂化、误用模板特化及依赖特定编译器的问题。示例展示了`is_same`类型trait的实现,用于检查类型相等。通过`add_pointer`和`remove_reference`等traits,可以构建更复杂的类型转换逻辑。类型traits增强了代码效率和安全性,是深入C++编程的必备工具。
49 11
|
2月前
|
编译器 C++ 容器
C++一分钟之-可变模板参数与模板模板参数
【7月更文挑战第21天】C++的模板实现泛型编程,C++11引入可变模板参数和模板模板参数增强其功能。可变模板参数(如`print`函数)用于处理任意数量的参数,需注意展开参数包和递归调用时的处理。模板模板参数(如`printContainer`函数)允许将模板作为参数,需确保模板参数匹配和默认值兼容。这些特性增加灵活性,但正确使用是关键。
42 4
|
2月前
|
Java 编译器 Linux
【c++】模板进阶
本文详细介绍了C++中的模板技术,包括非类型模板参数的概念、如何使用它解决静态栈的问题,以及模板特化,如函数模板特化和类模板特化的过程,以提升代码的灵活性和针对性。同时讨论了模板可能导致的代码膨胀和编译时间增加的问题。
24 2