C++模板(泛型编程)

简介: C++模板(泛型编程)

C++模板(泛型编程)

时刻牢记编译时,链接时,运行时!!还有静态和动态的区别!!


1. 理解泛型编程

泛型编程就是编写与类型无关的通用型代码,是代码复用的一种手段,C++中模板是实现泛型编程的一种手段。(通过模板达到对参数类型的控制,通过模板达到对逻辑的控制。)


2. 函数模板

# typename 也可以用 class 代替
template<typename T>
void Swap(T& left, T& right)
{
  T temp = left;
  left = right;
  right = temp;
}


1. 函数模板的理解

函数模板本身不是函数,就是一个模板。在编译器编译阶段,会根据传入的实参类型推导生成对应类型的函数。也就是说,模板就是把我们要干的活交给了编译器!让编译器去生成对应类型的函数。(编译时动态生成的)

2. 函数模板的实例化

模板的实例化就是编译器根据实参类型推导对应类型函数的过程。



1. 隐式实例化

就是编译器根据实参自动推导。


2. 显式实例化

显式指定模板参数类型。

template<typename T>
void Swap(T& left, T& right)
{
  T temp = left;
  left = right;
  right = temp;
}
int main()
{
  int a = 1;
  double d = 3.3;
  // 显式指定
  Swap<int>(a, d);
  return 0;
}


3. 函数模板的匹配规则

如果有显式实现的参数类型最匹配的函数,那就优先匹配显式实现的函数,编译器不会再生成对应类型的函数。否则编译器才会生成对应类型的函数。


3. 类模板

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


类模板必须显式的指定模板参数类型,因为这个编译器无法自动推导!

// vector只是类名,vector<int>才是类型
vector<int> s1;
vector<double> s2;


取类模板内的未知类型

==在类模板没有实例化之前不能使用类模板里的未知类型(如T这种模板类型)。==因为类模板没有实例化,它里面的类型也是虚拟类型,编译器无法确定其具体类型,导致后期无法处理!

template<class Container>
void printContainer(const Container& container)
{
  // 不行!Container类模板没有实例化之前它里面的类型是虚拟类型,
  // 编译器无法确定其具体类型
  // Container::const_iterator cit = contianer.begin();
  // 加 typename 修饰告诉编译器,等类模板实例化后再去获取类型!就可以了
  typename Container::const_iterator cit = contianer.begin();
  while(cit != container.end())
  {
    cout << *cit << " ";
    ++cit;
  }
  cout << endl;
}
int main()
{
  list<int> lt;
  lt.push_back(1);
  lt.push_back(2);
  lt.push_back(3);
  lt.push_back(4);
  printContainer(lt);
}


这时在该虚拟类型前加 typename 修饰,告诉编译器这是一个虚拟类型,等到类模板实例化后再去获取具体的类型。


4. 非类型模板参数

模板参数分为类型模板参数和非类型模板参数。类型模板参数就是普通的在 class/typename后面的参数,由调用方指定参数类型,也可以给缺省值。而非类型模板参数就是用一个常量作为模板参数。


注意非类型模板参数必须是常量,在编译期间就能确认其取值!不能是浮点类型,字符串这些,基本只能是整型。


非类型模板参数可以使得代码更灵活,以前只能传类型,但是非类型模板参数可以设置大小。

// N 就是非类型模板参数,可以给缺省值
template<class T, size_t N = 10>
class Test
{
  // ...
}


5. 模板特化与偏特化

一般而言,模板就是为了实现与类型无关的代码。但是有时有些方法可能不能做到对所有的类型都适用,比如指针和引用类型。这时就要针对这些类型进行特殊的处理,也就是针对这些类型实现一份特殊的方法—特化。 (模板的使用可以理解是泛化)


1. 函数模板特化

// 一个函数模板
template<class T>
bool Less(T left, T right)
{
  return left < right;
}
// 函数模板针对 int* 类型的特化
template<>
bool Less<int*>(int* left, int* right)
{
  return *left < *right;
}
// 不如直接针对该类型写一个函数!简单明了
bool Less(int* left, int* right)
{
  return *left < *right;
}
// 感觉这样更好!
template<class T>
bool Less(T* left, T* right)
{
  return *left < *right;
}


但是一般而言需要函数模板特化时都是直接把这个函数再实现一下,简单省事,代码可读性还好!!!所以基本不会用函数模板。


2. 类模板特化

就是针对类模板中特定的类型进行特殊化处理。类模板特化分为全特化和偏特化两种。


1. 全特化

template<class T1, class T2>
class Data
{
public:
  Data() {cout<<"Data<T1, T2>" <<endl;}
private:
  T1 _d1;
  T2 _d2;
};
// 全特化,就是将所有模板参数特化
template<>                // 这里什么也不写
class Data<int, char>     // 这里加上具体的类型
{ 
public:
  Data() {cout<<"Data<int, char>" <<endl;}
private:
  int _d1;
  char _d2;
};


2. 偏特化

==任何针对模版参数做进一步条件限制的都叫做偏特化。==一般有两种方式:一是对部分模板参数特化处理;二是对参数做进一步的限制,比如限制其为 类型 的指针或引用。

// 方式1:将部分参数特化
template <class T1>
class Data<T1, int>
{
public:
  Data() {cout<<"Data<T1, int>" <<endl;}
private:
  T1 _d1;
  int _d2;
};
// 方式2:对参数做进一步限制
template <class T1, class T2>
class Data <T1*, T2*>    // 限制两个模板参数都是 类型 的指针
{
public:
  Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
  T1 _d1;
  T2 _d2;
};


6. 模板分离编译的问题

一个程序是由很多源文件共同编译实现的,在编译时每个源文件都会生成一个目标文件,然后由链接器链接所有的目标文件生成可执行文件,这个过程称为分离编译!


但是模板分离编译会有问题!(就是在 xxx.h 文件中声明一个模板,然后在xxx.cc中定义该模板,在另外一个 xxx.cc文件中使用模板)



会有链接错误!!在模板定义的文件中不知道模板参数的具体类型,所以不会实例化出真正可以调用的函数,符号表中也就没有函数地址!!所以在链接时找不到对应的函数地址。


模板分离编译问题的解决:


  1. 不要把 模板的定义 和 使用模板的函数 分到不同的文件中分开编译!
  2. 在模板定义的文件中显式的指定模板参数类型。

x.cc`文件中使用模板==)


[外链图片转存中…(img-N2ohKzpw-1700226167249)]


会有链接错误!!在模板定义的文件中不知道模板参数的具体类型,所以不会实例化出真正可以调用的函数,符号表中也就没有函数地址!!所以在链接时找不到对应的函数地址。


模板分离编译问题的解决:


  1. 不要把 模板的定义 和 使用模板的函数 分到不同的文件中分开编译!
  2. 在模板定义的文件中显式的指定模板参数类型。

最后挂个链接,欢迎一起学习,一起进步!https://xxetb.xet.tech/s/4G6TWG

相关文章
|
1月前
|
监控 Linux C++
4步实现C++插件化编程,轻松实现功能定制与扩展(2)
本文是《4步实现C++插件化编程》的延伸,重点介绍了新增的插件“热拔插”功能。通过`inotify`接口监控指定路径下的文件变动,结合`epoll`实现非阻塞监听,动态加载或卸载插件。核心设计包括`SprDirWatch`工具类封装`inotify`,以及`PluginManager`管理插件生命周期。验证部分展示了插件加载与卸载的日志及模块状态,确保功能稳定可靠。优化过程中解决了动态链接库句柄泄露问题,强调了采纳用户建议的重要性。
64 12
4步实现C++插件化编程,轻松实现功能定制与扩展(2)
|
1月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
2月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
2月前
|
编译器 C++
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
2月前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
2月前
|
安全 C++
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
42 0
|
2月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
2月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
5月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
182 4
|
2月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。

热门文章

最新文章