C++——模板(进阶)

news/2025/3/27 10:52:38/

目录:


非类型模板参数

模板参数分类:类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
// 非类型模板参数 -- 常量 
template<class T, size_t N> // 第一个参数是类型,第二个是常量
// template<class T, size_t N = 10> // 也可以给缺省值,所以模板参数和函数参数是非常像的 
class array
{
private:T _a[N];
};int main()
{array<int, 100> a1; // 创建一个对象存放int类型,容量是100array<double, 200> a2; // 创建一个对象存放double类型,容量是200// array<int> a3; // 创建一个对象存放int类型,容量取缺省参数10
}

【注意】:非类型模板参数只能是整型一类的,如char,int,size_t,不可以是double或自定义类型。


模板的特化

什么是模板的特化:

        通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板。

template<class T> // 函数模板比较left < right
bool Less(T left, T right)
{return left < right;
}int main()
{cout << Less(1, 2) << endl; int n1 = 1;int n2 = 2;cout << Less(n1, n2) << endl;int* p1 = &n1;int* p2 = &n2;cout << Less(p1, p2) << endl;return 0;
}

前两个都是正确的,但是,第三个传入参数时默认匹配的是int*,比较的是地址,所以结果不是1,所以只需要使用模板的特化就可以解决。

template<class T> // 函数模板比较left < right
bool Less(T left, T right)
{return left < right;
}// 针对某些类型进行特殊化处理
// 函数模板特化
template<>
bool Less<int*>(int* left, int* right)
{return *left < *right;
}int main()
{cout << Less(1, 2) << endl; int n1 = 1;int n2 = 2;cout << Less(n1, n2) << endl;int* p1 = &n1;int* p2 = &n2;cout << Less(p1, p2) << endl;return 0;
}

        这里就举一个小例子,虽然不是很符合,但是也可以看出特化的作用。在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化。

函数模板特化

函数模板的特化步骤:
  1. 必须要先有一个基础的函数模板。
  2. 关键字template后面接一对空的尖括号<>。
  3.  函数名后跟一对尖括号,尖括号中指定需要特化的类型。
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
        【注意】:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。不写模板函数,直接写一个这种类型的函数。
bool Less(int* left, int* right)
{return *left < *right;
}
        该种实现简单明了,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

类模板特化

template<class T>
struct Less
{bool operator()(const T& x1, const T& x2) const{return x1 < x2;}
};// 类模板特化
template<>
struct Less<int*>
{bool operator()(int* x1, int* x2) const{return *x1 < *x2;}
};

        类模板的步骤和函数模板差不多,差别就是函数模板的<>是写在函数名旁边,类模板的<>要写在类名旁边。

全特化

全特化即是将模板参数列表中所有的参数都确定化。
template<class T1, class T2>
class A
{
public:A() { cout << "A<T1, T2>" << endl; }
};// 全特化
template<>
class A<int, char>
{
public:A() { cout << "A<int, char>" << endl; }
};void TestVector()
{A<int, int> a1;A<int, char> a2;
}int main()
{TestVector();return 0;
}

偏特化

任何针对模版参数进一步进行条件限制设计的特化版本。
template<class T1, class T2>
class A
{
public:A() { cout << "A<T1, T2>" << endl; }
};// 偏特化
template<class T1>
class A <T1, double>
{
public:A() { cout << "A<T1, double>" << endl; }
};template<class T1, class T2>
class A <T1*, T2*>
{
public:A() { cout << "A<T1*, T2*>" << endl; }
};template<class T1, class T2>
class A <T1&, T2&>
{
public:A() { cout << "A<T1&, T2&>" << endl; }
};template<class T1, class T2>
class A <T1&, T2*>
{
public:A() { cout << "A<T1&, T2*>" << endl; }
};int main()
{A<int, int> a0;    // 调用基础模板A<int, double> a1; // 调用特化double模板A<int*, int*> a2;  // 调用特化指针模板A<int&, int&> a3;  // 调用特化引用模板A<int&, int*> a4;  // 调用引用和指针模板return 0;
}

        模板的也就是匹配,能匹配的就匹配,尽量去匹配,不能匹配的就报错。


模板分离编译

分离编译为:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。下面是一段简单的分离编译的代码,是运行正常的。
// a.h
int A(const int& left, const int& right);// a.cpp
int A(const int& left, const int& right)
{return left + right;
}// main.cpp
int main()
{cout << A(1, 2) << endl;return 0;
}
但是模板的分离编译是会出问题的,所以下面就来说一下为什么会出问题。把代码改为分离编译的。
// a.h
template<class T>
T A(const T& left, const T& right);// a.cpp
template<class T>
T A(const T& left, const T& right)
{return left + right;
}// main.cpp
int main()
{cout << A(1, 2) << endl;return 0;
}

C++程序想要运行就要经过这些步骤预处理 ----> 编译 ----> 汇编 ----> 链接

        编译是:对程序按照语言特性进行词法、语法、语义分析,错误检查无误后生成汇编代码,但是头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的。

        链接是:将多个.o(bj)文件合并成一个,并处理没有解决的地址问题。

        在a.cpp文件中,编译器没有看到对A模板函数的实例化,因此不会生成具体的加法函数;在main.o(bj)中调用A<int>,编译器在链接时才会找到它的地址,但是这两个函数没有实例化没有生成具体代码,所以链接时会报错。

        在.h文件中没有定义,T不确定,所以没有办法实例化,没有实例化就无法进符号表,链接的时候肯定会报错。

解决方法

        所以模板不推荐使用声明定义分离在两个文件中,声明和定义放到同一个文件中就可以了。

模板特点

【优点】
  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
  2. 增强了代码的灵活性。
【缺陷】
  1. 模板会导致代码膨胀问题,也会导致编译时间变长。
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。

        它的优点显而易见,帮助我们实现泛型编程,缺点在编译报错的时候也可以看到很长的一串报错信息,还有一点是,每实现一块类型,编译器都会重新生成一份模板代码。


http://www.ppmy.cn/news/1252902.html

相关文章

C++概念相关练习题

目录 一、内存管理 二、运算符重载 三、this指针 四、构造&析构函数 一、内存管理 下面有关c内存分配堆栈说法错误的是( ) A.对于栈来讲&#xff0c;是由编译器自动管理&#xff0c;无需我们手工控制&#xff1b;对于堆来说&#xff0c;释放工作由程序员控制 B. 对…

深入 C 语言和程序运行原理 实战项目代码在CentOS 7上编译

cat /etc/redhat-release看到操作系统的版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到内核版本是3.10.0-957.21.3.el7.x86_64。 安装gtest 参考博客《使用gtest和lcov测试代码覆盖率》 wget https://github.com/google/googletest/archive/refs/…

STM32 定时器TIM

单片机学习 目录 文章目录 前言 一、TIM简介 二、STM32的三种定时器 2.1基本定时器 2.1.1定时中断功能 1. 时钟源 2. 预分频器 3. 计数器 4. 自动重装寄存器 5.更新中断和更新事件 2.1.2主模式触发DAC功能 2.2 计数模式 2.2通用定时器 2.2.1 时钟源 外部时钟模式2 外部时钟模式…

目标检测YOLO实战应用案例100讲-基于改进YOLO深度学习模型的烟支外观质量检测

目录 知识储备 图像质量评价 什么是图像质量 客观评价 清晰度 1.斜边法 2.西门星图

【Azure 架构师学习笔记】- Azure Databricks (1) - 环境搭建

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 前言 Databricks 已经成为了数据科学的必备工具&#xff0c;今时今日你已经很难抛开它来谈大数据&#xff0c;它常用于做复杂的ETL中的T&#xff0c; 数据分析&#xff0c;数据挖掘等&#xff0c;…

7-1 单词翻转分数 10

7-1 单词翻转 分数 10 作者 于延 单位 哈尔滨师范大学 问题描述 输入一个句子&#xff08;一行&#xff09;&#xff0c;将句子中的每一个单词翻转后输出。 输入描述 只有一行&#xff0c;为一个字符串&#xff0c;不超过500个字符。单词之间以空格隔开。 输出描述 翻…

LangChain的函数,工具和代理(三):LangChain中轻松实现OpenAI函数调用

在我之前写的两篇博客中:OpenAI的函数调用,LangChain的表达式语言(LCEL)中介绍了如何利用openai的api来实现函数调用功能&#xff0c;以及在langchain中如何实现openai的函数调用功能&#xff0c;在这两篇博客中&#xff0c;我们都需要手动去创建一个结构比较复杂的函数描述变量…

frp 配置内网访问

frp介绍 frp 是一个开源、简洁易用、高性能的内网穿透软件&#xff0c;支持 tcp, udp, http, https 等协议。frp 项目官网是 https://github.com/fatedier/frp 下载地址&#xff1a; https://github.com/fatedier/frp/releases frp工作原理 服务端运行&#xff0c;监听一个…