C++模板(函数模板/类模板)

C++模板(函数模板/类模板)文章目录一 泛型编程二 函数模板一 泛型编程在引入泛型编程之前 我们先来看这样一个问题 怎么做到实现一个通用的交换函数呢 在 C 语言阶段我们可能会像下面这样写 需要分别实现不同类型的交换函数 又由于 C 语言不允许出现同名函数 所以函数名也需要不一样 写起来很繁琐 voidSwapi int e1 int e2 inttmp e1 e1 e2 e2 tmp voidSwapd double e1 double e2 doubletmp

一.泛型编程

在引入泛型编程之前,我们先来看这样一个问题,怎么做到实现一个通用的交换函数呢?

在C语言阶段我们可能会像下面这样写,需要分别实现不同类型的交换函数,又由于C语言不允许出现同名函数,所以函数名也需要不一样,写起来很繁琐

void Swapi(int* e1, int* e2) { 
    int tmp = *e1; *e1 = *e2; *e2 = tmp; } void Swapd(double* e1, double* e2) { 
    double tmp = *e1; *e1 = *e2; *e2 = tmp; } 

C++因为名称修饰规则支持了重载函数,虽然函数名可以一样,但还是要分别实现不同类型的交换函数,也很繁琐

void Swap(int& e1, int& e2) { 
    int tmp = e1; e1 = e2; e2 = tmp; } void Swap(double& e1, double& e2) { 
    double tmp = e1; e1 = e2; e2 = tmp; } 

我们也可以看出上述代码只是参数类型不一样,其他的实现过程和逻辑都是一样的,为了解决(每一个类型都要自己去实现一个函数出来)这样的繁琐的问题,我们引入了泛型编程

(1).所谓泛型编程,是以独立于任何特定类型的方式编写代码,使用泛型编程时,我们需要提供具体程序实例所操作的类或值

(2).模板是泛型编程的基础,模板是创建类或函数的蓝图或公式,我们给这些蓝图或公式足够的信息,让这些蓝图或公式真正的转变为具体的类或函数,这种转变发生在编译时

(3).模板支持将类型作为参数的程序设计方式,从而实现了对泛型程序设计的支持,也就是说C++模板机制允许将类型作为参数

二.函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

函数模板格式

template<typename T1, typename T2,......,typename Tn> // 返回值类型 函数名(参数列表){} 

上面的问题就得以解决了

template<class T> void Swap(T& a, T& b) { 
    T tmp = a; a = b; b = tmp; } 

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事交给了编译器

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用 int 类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码

(1).函数模板的实例化

(1).隐式实例化 : 让编译器自己根据实参的类型推导模板参数的类型

template<class T> T Add(const T& a, const T& b) { 
    return a + b; } int main() { 
    int a = 1, b = 2; cout << Add(a,b) << endl; } 

(2).显示实例化 : 在函数名后的<>中指定模板参数的实际类型

template<class T> T Add(const T& a, const T& b) { 
    return a + b; } int main() { 
    int a = 1; double b = 2.2; cout<<Add<int>(a,b)<<endl; cout<<Add<double>(a,b)<<endl; } 

模板参数的匹配原则

(1). 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

(2). 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板实例化出来的函数

// 非模板函数 int Add(int left, int right) { 
    return left + right; } // 模板函数 template<class T> T Add(T left, T right) { 
    return left + right; } void Test() { 
    Add(1, 2); // 与非模板函数匹配,编译器不需要进行模板实例化 Add<int>(1, 2); // 调用编译器进行模板实例化的函数 Add(1,2.0) // 调用编译器进行模板实例化的函数 } 

(2).非类型模板参数

在模板参数列表里,还可以定义非类型参数,非类型参数代表的是一个值,既然非类型参数代表一个值,不是一个类型,肯定不能用typename/class关键字来修饰这个值,我们当然要用我们以往学习过的传统类型名来指定非类型参数了

当模板被实例化时,这种非类型模板参数的值,或者是用户提供的,或者是编译器自己推断的,但这些值必须都得是常量表达式,因为模板实例化发生在编译阶段

#include 
     using namespace std; template<int a,int b> int add1() { 
    return a + b; } template<class T,int a,int b> int add2(T c) { 
    return c + a + b; } template<unsigned L1,unsigned L2> int charscmp(const char(&p1)[L1],const char(&p2)[L2]) { 
    return strcmp(p1, p2); } int main() { 
    cout << add1<1, 2>() << endl; cout << add2<int, 1, 2>(5) << endl; cout << add2<int, 1, 2>(1.6) << endl; cout << charscmp("test2", "test") << endl; } 

三.类模板

类模板定义格式 :

template<class T1, class T2, ..., class Tn> class 类模板名 { 
    // 类内成员定义 }; 

编译器不能为类模板推断模板类型参数,因此,类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

#include 
     #include 
     using namespace std; namespace lyp { 
    template<class T> class vector { 
    public: vector() :_a(nullptr) ,_size(0) ,_capacity(0) { 
   } ~vector() { 
    delete[]_a; _a = nullptr; _size = _capacity = 0; } T& operator[](size_t pos) { 
    return _a[pos]; } int size() { 
    return _size; } void push_back(const T& x) { 
    // 需要动态增容 if (_size == _capacity) { 
    int newcapacity = _capacity == 0 ? 4 : _capacity * 2; T* tmp = new T[newcapacity]; if (_a) { 
    memcpy(tmp, _a, sizeof(T) * _size); } delete[]_a; _a = tmp; _capacity = newcapacity; } _a[_size++] = x; } private: T* _a; int _size; int _capacity; }; } int main() { 
    lyp::vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); for (int i = 0; i < v1.size(); i++) { 
    // v1.operator[](3) cout << v1[i] << endl; } std::vector<double> v2; v2.push_back(1.1); v2.push_back(2.2); v2.push_back(3.3); v2.push_back(4.4); for (int i = 0; i < v2.size(); i++) { 
    cout << v2[i] << endl; } } 

(1).类模板的成员函数

template<typename T> class myvector { 
    public: // 构造函数 myvector(); // 赋值运算符重载 myvector& operator=(const myvector& v); // 会被隐式声明成内联函数 void func() { 
    // ..... } }; template<class T> myvector<T>& myvector<T>:: operator=(const myvector& v) { 
    // ..... } 

(2).非类型模板参数

template<class T,int size = 10> class myarray { 
    public: void func(); private: T arr[size]; }; template<class T,int size> void myarray<T,size>::func() { 
    cout << size << endl; return; } 

四.模板的特化

模板特化:就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例版本

#include 
     using namespace std; template<class T> bool IsEqual(const T& left, const T& right) { 
    return left == right; } int main() { 
    const char* p1 = "hello"; const char* p2 = "hello"; cout << IsEqual(p1, p2) << endl;; return 0; } 

上面的模板在比较字符数组时并不会达到我们想要的效果,因为该模板比较的是字符数组的地址,比较的结果是不相等,但我们想要的结果是相等,所以需要模板特化

(1).函数模板特化

解决方案 :

#include 
     using namespace std; template<class T> bool IsEqual(const T& left, const T& right) { 
    return left == right; } template<> bool IsEqual<const char*>(const char* const& left, const char* const& right) { 
    return strcmp(left, right) == 0; } int main() { 
    const char* p1 = "hello"; const char* p2 = "hello"; cout << IsEqual(p1, p2) << endl; return 0; } 

(1). template <> : 空模板形参表

(2). compare

: 模板名字后指定特化时的模板形参即const char *类型,就是说在以实参类型 const char * 调用函数时,将产生该模板的特化版本,而不是泛型版本,也可以为其他指针类型定义特化版本如int *.

(3). (const char * const &v1, const char * const &v2)可以理解为: const char * const &v1, 去掉const修饰符,实际类型是:char* &v1,也就是v1是一个引用,一个指向char型指针的引用,即指针的引用,加上const修饰符,v1就是一个指向const char* 指针的 const引用,对v1的操作就是对指针本身的操作,操作方式与指针一致,比如*v1,是正确的;

注意这里的const char *, 由于形参是一个指向指针的const引用,所以调用特化版本时的实参指针类型(并非存储的数据的类型)可以为const也可以为非const,但是由于这里形参指针指向的数据类型为const char *(强调存储的数据是const),所以实参指针所指向的数据类型也必须为const,否则类型不匹配;

函数模板特化的例子

template<class T,class U> void tfunc(T t, U u) { 
    cout << "泛化版本" << endl; cout << t << endl; cout << u << endl; } template<> void tfunc<int, double>(int t, double u) { 
    cout << "全特化版本" << endl; cout << t << endl; cout << u << endl; } template<class U> void tfunc(double t,U u) { 
    cout << "重载实现类似偏特化版本" << endl; cout << t << endl; cout << u << endl; } 

(2).类模板特化

(1) . 全特化

全特化即是将模板参数列表中所有的参数都确定化

#include 
     using namespace std; template<class T1,class T2> class A { 
    public: A() { 
    cout << "A 
   
     " 
    << endl; } }; template<> class A<int, int> { 
    public: A() { 
    cout << "A 
   
     " 
    << endl; } }; int main() { 
    A<int, double> a; A<int, int> aa; // 全特化 return 0; } 

(2). 偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本

1). 部分特化

#include 
     using namespace std; template<class T1,class T2> class A { 
    public: A() { 
    cout << "A 
   
     " 
    << endl; } }; template<class T> class A<T, int> { 
    public: A() { 
    cout << "A 
   
     " 
    << endl; } }; int main() { 
    A<int,double> a; A<int, int> aa; // 偏特化 return 0; } 

2). 参数更进一步的限制

#include 
     using namespace std; template<class T1,class T2> class A { 
    public: A() { 
    cout << "A 
   
     " 
    << endl; } }; template<class T1,class T2> class A<T1*, T2*> { 
    public: A() { 
    cout << "A 
   
     " 
    << endl; } }; template<class T1, class T2> class A<T1&, T2&> { 
    public: A() { 
    cout << "A 
   
     " 
    << endl; } }; int main() { 
    A<int,int> a; A<int*, double*> aa; // A 
    A<int&, double&> aaa; // A 
    A<int*, double&> aaaa; // A 
    return 0; } 

五.模板不支持分离编译

在编译阶段,每一个cpp文件都是相对独立的,并不知道另一个编译文件的存在,若存在外部调用,会在链接阶段进行重定位。

模板的实例化其实只能发生在本编译单元的调用。如果出现非本编译单元的模板调用,也就是分离式编译,只能等待链接时重定位,但是模板并没有实例化,所以会出现链接出错。

因此,建议将声明和定义放到一个文件 “xxx.hpp” 里面或者”xxx.h”

// a.h template<class T> T Add(const T& a, const T& b); // a.cpp template<class T> T Add(const T& a, const T& b) { 
    return a + b; } // main.cpp #include"a.h" int main() { 
    Add(1, 2); Add(1.0, 2.0); return 0; } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/213729.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午5:46
下一篇 2026年3月18日 下午5:46


相关推荐

  • 使用javascript实现数组截取

    使用javascript实现数组截取前言:在开发项目的过程中遇到这样的一个问题,就是需要对接口查询出来的数据两个两个的进行截取,之后分别两个两个的放入数组中,再把这些数组放到一个新数组中,实现方法如下:方法一:functionarrayChunk(array,size){ letdata=[]; for(leti=0;i<array.length;i+=size){ data.push(array.slice(i,i+size)) } returndata;}arrayChunk([{i

    2022年6月5日
    41
  • 腾讯的第三条路:组织与 AI 的平衡术

    腾讯的第三条路:组织与 AI 的平衡术

    2026年3月13日
    2
  • 内部类JSON序列化

    内部类JSON序列化一 Java 成员内部类的实例化 OuterClassou newOuterClas OuterClass InnerClassin outerClass newInnerClas 其中 OuterClass 为外部类 InnerClass 为成员内部类 二 内部类序列化 packagecom iflytek chy importjava uti

    2026年3月17日
    1
  • 什么软件可以测试网络的稳定性,网络稳定性测试软件

    什么软件可以测试网络的稳定性,网络稳定性测试软件@ECHOoffcolor0Aecho欢迎进行网络稳定状况测试(测试开始时间%date%%time%)echo=======================================================================echo运行脚本后不要关闭这个窗口,让它一直测试你的网络,不想测试了需手动关闭echo当你老掉线的时候运行本脚本,建议测试时间在30分钟左…

    2025年10月10日
    2
  • 电驴让分享继续 服务器不稳定,电驴快快跑—分流教程

    电驴让分享继续 服务器不稳定,电驴快快跑—分流教程您可能感兴趣的话题 电驴核心提示 具体的入门教程我这里不详细写了 有兴趣的可以找下我前面发过的 下面是一部分发布以及分流资源的补充教程 具体的入门教程我这里不详细写了 有兴趣的可以找下我前面发过的 下面是一部分发布以及分流资源的补充教程 首先必须明确一点 电驴是一个用来共享的 P2P 软件 它的工作基本原理与 bt 类似 都是通过用户和用户之间的传输 来达到分享资源的目的 但有一点它与 bt 有着本质区别 那

    2026年3月19日
    2
  • Go 学习笔记(28)— nil(nil 不能比较、不是关键字或保留字、nil 没有默认类型、不同类型的 nil 指针是一样的、不同类型的 nil 是不能比较的、相同类型的 nil 可能也无法比较)

    Go 学习笔记(28)— nil(nil 不能比较、不是关键字或保留字、nil 没有默认类型、不同类型的 nil 指针是一样的、不同类型的 nil 是不能比较的、相同类型的 nil 可能也无法比较)在 Go 语言中 布尔类型的零值 初始值 为 false 数值类型的零值为 0 字符串类型的零值为空字符串 而指针 切片 映射 通道 函数和接口的零值则是 nil nil 是 Go 语言中一个预定义好的标识符 有过其他编程语言开发经验的开发者也许会把 nil 看作其他语言中的 null NULL 其实这并不是完全正确的 因为 Go 语言中的 nil 和其他语言中的 nul

    2026年3月17日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号