【Example】C++ Template (模板)概念讲解及编译避坑

简介: C++ 不同于 Java,它没有标准的 Object 类型。也就意味着 C++ 并不存在完整的泛型编程概念。先讲 “部分的” 泛型编程概念的实现方式:模板。

什么是模板?

引用 Microsoft Docs:

模板是 c + + 中的泛型编程的基础。 作为强类型语言,c + + 要求所有变量都具有特定类型,由程序员显式声明或由编译器推断。

但是,许多数据结构和算法的外观都是相同的,无论它们的操作类型是什么。

利用模板,您可以定义类或函数的操作,并允许用户指定这些操作应使用的具体类型。

 

总结:模板是 C++ 当中支持参数类型与返回值动态化的工具,使开发人员可以动态自定义函数、类中参数与返回值类型。

模板又分为两种:函数模板 与 类模板。

 

====================================

1,函数模板

先从最简单的定义讲起:

template<classT>TAddNum(Ta, Tb) {
returna+b;
}
intmain()
{
inta=1;
intb=2;
intc=AddNum(a, b);
std::cout<<"Add Num: "<<c<<std::endl;
returnEXIT_SUCCESS; 
}

可以看到,以上函数实现了最简单的任意同类型变量相加的一个功能。

定义模板的关键字就是 template,语法:

template<classT>ortemplate<typenameT>

template<> 对函数声明或定义进行修饰,其中 T 可以是任意名字(例如Object)。

进行在模板函数调用时,编译器会根据变量类型推断函数参数类型。

 

那么,函数模板是否可以支持多种类型呢?可以!

template<classI, classF>FGetProduct(Ia, Fb) {
returna*b;
}
intmain()
{
inta_i=5;
floatb_f=8.5;
floatc_f=GetProduct(a_i, b_f);
std::cout<<"Product: "<<c_f<<std::endl;
returnEXIT_SUCCESS; 
}

以上函数最简单实现了求两个不同类型变量的乘积。当你的参数类型需要两种或以上的时候,就是在 template<> 当中增加声明,再对函数或类进行修饰:

template<classI, classF, classD>

 

那么,除了基本的数据类型,模板是否支持结构体(struct)或其他类型呢?可以!

但是,进行运算操作的时候,你要确保你的 struct 或 class 重载的相应的运算符!

typedefstructIntCell {
inta;
intb;
intc;
structIntCell(inti, intj, intk) : a(i), b(j), c(k) {};
}IntCell;
typedefstructDoubleCell {
doublea;
doubleb;
doublec;
structDoubleCell(doublei, doublej, doublek) : a(i), b(j), c(k) {};
}DoubleCell;
// ------------------------template<classstructT, classstructY>inlineboolCompareStructMemSize(structTa, structYb) {
returnsizeof(a) >sizeof(b);
};
// ------------------------intmain()
{
IntCellcell_a(1, 2, 3);
DoubleCellcell_b(1.1, 2.2, 3.3);
boolflag=CompareStructMemSize(cell_a, cell_b);
std::cout<<"bool: "<<flag<<std::endl;
returnEXIT_SUCCESS;
}

 

可以看到,以上代码当中我定义了两个不同的结构体,只是单纯的去比较这两个结构体的大小。

 

====================================

2,类模板

函数模板很好理解,那么类模板是什么呢?

可以在类模板的内部或外部定义成员函数。 如果在类模板的外部定义成员函数,则会像定义函数模板一样定义它们。 --Microsoft Docs

 

以下演示了一个最简单的使用模板的类:

template<classObject>classVectorMod {
public:
VectorMod() {
this->_vec.reserve(10);
    };
~VectorMod()
    {
this->Clear();
    };
std::vector<Object>&GetVec() {
returnthis->_vec;
    };
voidAddData(Objectin) {
this->_vec.push_back(in);
    };
intGetSize() {
returnthis->_vec.size();
    };
voidClear() {
this->_vec.clear();
std::vector<Object>().swap(this->_vec);
    }
private:
std::vector<Object>_vec;
};
这个类仅仅是简单将std::vector包装了一下而已。


于是我们可以总结出语法:

1,使用 template<> 对类声明和类定义进行修饰。

2,类内部需要使用模板类型时,直接使用相应的模板形参名。

template<classObject>classVectorMod {
std::vector<Object>_vec;
};


请注意,就像任何模板类成员函数一样,类的构造函数成员函数的定义包含模板参数列表两次。

成员函数可以是函数模板,并指定附加参数。 --Microsoft Docs

PS: 模板可以在模板类当中被定义并使用,这种情况下成为 “成员模板”,但是逻辑会过于复杂,实际开发不建议使用,了解成员模板

 

当模板类需要被使用的时候,如何进行声明并创建呢?

VectorMod<int>v_mod;
shared_ptr<VectorMod<int>>int_pool=make_shared<VectorMod<int>>();

以上演示了局部变量及智能指针的创建。然后,像平常那样调用即可:

VectorMod<int>v_mod;
v_mod.AddData(971);
v_mod.AddData(981);
v_mod.AddData(991);
for (autoi : v_mod.GetVec())
{
std::cout<<i<<std::endl;
}

好了,上面是最基本的类模板。

 

然后:类模板当中非类型形参

这是一个什么东西呢?

1,它是一个常量。

2,它的类型只能是 int 、指针、引用这三种内置类型。

3,调用它的只能是一个常量表达式

它的使用场景?

1,你封装了一个可以容纳固定大小的容器。

2,可以预初始化固定资源。

 

样例:

#include<vector>usingstd::vector;
template<classObject, intPREMEM>classDataPool{
public:
DataPool();
~DataPool();
vector<Object>&GetVec();
voidAddData(Objectin);
intGetSize();
voidClear();
private:
vector<Object>_vec;
};
// ------------------------template<classObject, intPREMEM>DataPool<Object, PREMEM>::DataPool()
{
this->_vec.reserve(PREMEM);
return;
}
template<classObject, intPREMEM>DataPool<Object, PREMEM>::~DataPool()
{
this->Clear();
return;
}
template<classObject, intPREMEM>vector<Object>&DataPool<Object, PREMEM>::GetVec()
{
returnthis->_vec;
}
template<classObject, intPREMEM>voidDataPool<Object, PREMEM>::AddData(Objectin)
{
this->_vec.push_back(in);
return;
}
template<classObject, intPREMEM>intDataPool<Object, PREMEM>::GetSize()
{
returnthis->_vec;
}
template<classObject, intPREMEM>voidDataPool<Object, PREMEM>::Clear()
{
this->_vec.clear();
std::vector<Object>().swap(this->_vec);
return;
}

以上代码,同样是将 std::vector 简单无意义包装了一下,但是,却使用了非类型形参进行了内存预分配操作以提高性能。

所以模板非类型形参的语法是:

template<classObject, intPREMEM>ortemplate<classObject, type*per>ortemplate<classObject, type&ref>

即不使用 class 或者 typename,直接使用 INT or PTR or REF。

 

那么该如何使用呢?

#include "DataPool.hpp"intmain()
{
shared_ptr<DataPool<string, 10>>str_pool=make_shared<DataPool<string, 10>>();
str_pool->AddData("Hello Byte!");
str_pool->AddData("Hello Blu!");
str_pool->AddData("Hello Frog!");
for (autos : str_pool->GetVec())
    {
std::cout<<s<<std::endl;
    }
returnEXIT_SUCCESS;
}

 

可以看到,使用它的语法就是:

DataPool<string, 10>orDataPool<string, ptr>orDataPool<string, &ref>

 

====================================

3,模板与完整泛型编程的区别(编译避坑)

C++ 的模板类在没有被使用之前,编译器完全不知道它会占用多少空间!而 C++ 每一个变量及对象占用的空间在编译的时候就要被确定!

所以 C++ 当中没有绝对的泛型编程概念。

因此,模板类必须是声明与实现同源(不一定是文件不分离),最合适的写法也就是 hpp 文件。

简单化使用.h头文件和.cpp文件分类声明时,几乎确定会报链接错误。

解决方法可以简单粗暴的将 cpp 文件 include 到 h 文件当中,但这并非标准做法,MSC编译器也已经不支持,所以最合适的做法还是使用 hpp 文件。





====================================

芯片烤电池 C++ Example 2022-Spring Season Pass :

【Example】C++ 标准库常用容器全面概述

【Example】C++ 回调函数及 std::function 与 std::bind

【Example】C++ 运算符重载

【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr

【Example】C++ 接口(抽象类)概念讲解及例子演示

【Example】C++ 虚基类与虚继承 (菱形继承问题)

【Example】C++ Template (模板)概念讲解及编译避坑

【Example】C++ 标准库 std::thread 与 std::mutex

【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)

【Example】C++ 标准库 std::condition_variable

【Example】C++ 用于编译时封装的 Pimpl 演示 (编译防火墙 Private-IMPL)

【Example】C++ 单例模式 演示代码 (被动模式、兼容VS2022编译)

====================================

相关文章
|
1月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
3月前
|
自然语言处理 编译器 C语言
为什么C/C++编译腰要先完成汇编
C/C++ 编译过程中先生成汇编语言是历史、技术和实践的共同选择。历史上,汇编语言作为成熟的中间表示方式,简化了工具链;技术上,分阶段编译更高效,汇编便于调试和移植;实践中,保留汇编阶段降低了复杂度,增强了可移植性和优化能力。即使在现代编译器中,汇编仍作为重要桥梁,帮助开发者更好地理解和优化代码。
72 25
为什么C/C++编译腰要先完成汇编
|
2月前
|
编译器 C++
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
1月前
|
存储 编译器 C++
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
196 0
|
2月前
|
安全 C++
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
43 0
|
2月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
5月前
|
自然语言处理 编译器 Linux
|
5月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
183 4
|
5月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
73 3
|
5月前
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
66 0

热门文章

最新文章

下一篇
oss创建bucket