首页 > 编程语言 >C++ 模版详解 | 函数模板 | 类模版

C++ 模版详解 | 函数模板 | 类模版

时间:2024-08-17 14:23:00浏览次数:12  
标签:函数 模版 Age C++ Person template 模板 Name

前言 

什么是模板?

  • 模板是一个泛型编程的概念,即不考虑类型的一种编程方式,能够实现代码重用,提高效率
  • 模板可分为函数模板、类模板 

模板的声明和定义

模板的声明有两种,一种就是typename,另外一种就是使用class ,一般使用一种声明格式就可以了,不建议混合使用。

  • template <typename T>  
  • template <class T> 

template就是模板的意思,声明创建模板
typename表明其后面的符号是一种数据类型,可以用class代替
T是通用的数据类型,名称可以替换,通常为大写字母

函数模板

模板函数:建立一个通用的函数,其返回值类型,形参的类型都不确定指定,而是采用虚拟类型来代替,对于功能相同的函数,就无须重复写代码,并且模板函数和普通函数长相及使用方法非常类似,唯一的区别,类型参数化。

语法:

template<typename 类型参数1, typename 类型参数2, ...>
返回值类型名 函数名(参数表)
{
	//函数体的定义
}

//或者使用下面这种方式
template<class 类型参数1, class类型参数2, ...>
返回值类型名 函数名(参数表)
{
	//函数体的定义
}

 例如交换两个数的函数模板:

template<typename T>
T Swap(T a, T b){
    temp = a;
    a = b;
    b = temp;  
}

int main(){
    //当输入数据是整型
    int a = 19; 
    int b = 20;
    Swap(a,b);
    cout <<"交换后的结果是:" << endl;
    cout <<"a = " << a << endl;
    cout <<"b = " << b << endl;

    //当输入数据是浮点型
    double a = 19.1;
    double b = 20.5;
    Swap(a,b);
    cout <<"交换后的结果是:" << endl;
    cout <<"a = " << a << endl;
    cout <<"b = " << b << endl;
}

如果不用模板的话,针对不同变量的数据类型就需要分别写两个交换函数, 但是用了模板,只需一个函数就可以对所用类型的数据类型进行交换。

注意:使用模板函数时并不是所有的变量类型都适用,有时候会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数,所以此时我们就需要对该类型特化出一个模板函数(就是写出一个模板函数专门给该类型使用

在写特例化的模板函数时,需要直接在形参参数列表中写明参数类型,不能再使用类型参数。

特例化分为部分特例化和全部特例化。全部特例化就是上边这种,将两个参数都写明参数类型。部分特例化就是只将一部分的参数写明参数类型

template<>//全特化,此处为空
char Swap(const char a, const char b)
{
	temp = a;
    a = b;
    b = temp; 
}

 

类模板 

使用template关键字不但可以定义函数模板,也可以定义类模板,类模板代表一族类,是用来描述通用数据或处理方法的机制,它使类中的一些数据成员和成员函数的参数或返回值可以取任意的数据类型。类模板可以说是用类生成类,减少了类的定义数量。 

//类模版定义
template <类型形式及参数> 
class 类模板名{
		//类成员声明
}

//在类外面定义成员函数
template <类型形式及参数> 
类型名 类名<模板参数标识符列表>::函数名(参数表)
//类模板成员类外实现
template<typename T1, typename T2>
class Person
{
public:
	Person(T1 name, T2 age);
	/*{
		this->m_Name = name;
		this->m_Age = age;
	}*/

	void showPerson();
	/*{
		cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
	}*/

	T1 m_Name;
	T2 m_Age;
};

//构造函数的类外实现
template<typename T1,typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}


//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}

void test01()
{
	Person<string, int>p("Tom", 30);
	p.showPerson();
}


int main()
{
	test01();
	
	system("pause");
	return 0;
}

注意

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数
//类模版参数可以有默认值,注意,设置默认时,该默认值的右边都必须有默认值
template<typename NameType, typename AgeType = int> //指定默认参数为int
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_Age = age;
		this->m_Name = name;
	}

	void showPerson()
	{
		cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
	}

	NameType m_Name;
	AgeType m_Age;
};

void test01()
{
	Person<string>p("小红",18); //类模板在参数列表中有默认参数
}
   
int main()
{
	test01();

	system("pause");
	return 0;
}

类模版与继承

当类模板碰到继承时,需要注意以下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需为类模板
//类模板与继承
template<typename T>
class Base
{
	T m;
};

//class Son: public Base //错误,必须要知道父类中的T类型,才能继承给子类
class Son :public Base<int>
{

};

void test01()
{
	Son s1;
}

//如果想灵活指定父类中T的类型,子类也需要变成类模板
template<typename T1,typename T2>
class Son2 : public Base<T2>
{
public:
	Son2()
	{
		cout << "T1的类型为:" << typeid(T1).name() << endl;
		cout << "T2的类型为:" << typeid(T2).name() << endl;

	}
	T1 obj;
};

void test02()
{
	Son2<int,char> s2;
}

int main()
{
	test02();
	
	system("pause");
	return 0;
}

 类模板分文件编写

如果工程中需要利用多个类模板,那么将这些类模板都写在同一个文件中将会导致代码可读性变差,所以有必要对类模板进行分文件编写,但是类模板的分文件编写面临着一些问题,以下是类模板分文件编写面临的问题及解决方法。

问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制的

第一种方法不写了,感兴趣的可以去看这篇博文:C++编程——类模板-CSDN博客

直接说分文件编写,利用.hpp
person.hperson.cpp的内容写到一起,并将后缀名改为.hpp,这是类模板分文件编写最常用的方式

1.编写person.hpp文件: 模板的声明具体实现写在一起

#pragma once
#include <iostream>
using namespace std;
#include <string>

//声明
template<ctypename T1, typename T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

//具体实现
//构造函数的类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_Name = name;
	this->m_Age = age;
}

//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}

 2.编写main函数:将hpp文件包含进来就可以了

#include <iostream>
using namespace std;
#include <string>
#include "person.hpp"

void test01()
{
	Person<string, int>p("Tom", 30);
	p.showPerson();
}

int main()
{
	test01();
	
	system("pause");
	return 0;
}

 类模板与友元

  • 全局函数类内实现:直接在类内声明友元即可
  • 全局函数类外实现:需要提前让编译器知道全局函数的存在

1.全局函数的类内实现 

template<typename T1, typename T2>
class Person
{
	//全局函数类内实现,直接声明friend
	friend void printPerson(Person<T1, T2> p)
	{
		cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
	}
public:
	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	
private:
	T1 m_Name;
	T2 m_Age;
};

void test01()
{
	Person<string, int>p("Tom", 30);
	printPerson(p);
}
int main()
{
	test01();
	
	system("pause");
	return 0;
}

2.全局函数类外实现

//提前让编译器知道Person类的存在
template<class T1, class T2>
class Person;

//类外实现
template<class T1, class T2>
void printPerson(Person<T1, T2> p)
{
	cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}

template<class T1, class T2>
class Person
{
	//全局函数类外实现 
	//加空模板参数列表
	//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
	friend void printPerson<>(Person<T1, T2> p);
public:
	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	
private:
	T1 m_Name;
	T2 m_Age;
};



void test01()
{
	Person<string, int>p("Tom", 30);
	printPerson(p);
}


int main()
{
	test01();
	
	system("pause");
	return 0;
}

 注:以上类模版参照博文:C++编程——类模板-CSDN博客

标签:函数,模版,Age,C++,Person,template,模板,Name
From: https://blog.csdn.net/weixin_45754224/article/details/141208683

相关文章

  • 【生日视频制作】飞机机身AE模板修改文字软件生成器教程特效素材【AE模板】
    飞机机身生日视频制作教程AE模板改文字特效广软件告生成器素材【生日视频制作】飞机机身AE模板修改文字软件生成器教程特效素材【AE模板】生日视频制作步骤:安装AE软件下载AE模板把AE模板导入AE软件修改图片或文字渲染出视频......
  • C++多维数组与指针
    定义inta[3][4]={{1,3,5,7},{9,11,13,15},{17,18,21,23}};a代表二维数组首元素的地址,现在的首元素不是一个整型变量,而是由4个整型元素所组成的一维数组,因此a代表的是首行的起始地址,a+1代表第二行首地址。a代表的是首行的起始地址,即a[0]行的首地址,&a[0]a+1代表第二行首......
  • C++类和对象(中)
    前言:我们学习了类和对象的上部分,对类和对象有了一些认识,接下来了解类和对象的中间部分,构造函数,析构函数,拷贝构造,赋值构造这部分也比较重要,我们需要牢牢掌握,一起加油吧!1.类的默认成员函数默认成员函数就是我们不用写系统自动生成的函数,我们不写的情况下编译器会默认生成6......
  • C++编程:内存栅栏(Memory Barrier)详解及在多线程编程中的应用
    文章目录0.引言1.什么是内存栅栏?2.为什么需要内存栅栏?本质原因是什么?2.1编译器优化2.2CPU乱序执行3.ARM64和x86架构下的内存栅栏差异3.1x86架构3.2ARM64架构4.代码示例4.1代码解析4.2memory_order_release和memory_order_acquire解释4.3为什么是“releas......
  • 杭电基础100题(2000~2099)C++ 本萌新的刷题日记
    开始之前本人是刚学完C++基础语法的萌新,从B站了解到了杭电的100道水题基础题,于是打算开始刷题并在这里写下解题思路和一些想法,以便日后回顾,顺便分享给大家。我的计划是一天15题。这是我第一次在CSDN上发文章,还不是很熟悉怎么编辑。基本上每一题都会把代码和感想放这里。200......
  • 了解一下宏定义#define吧c++
    在C++中,宏定义是通过 #define 指令实现的,它用于创建符号常量或宏函数。这是一种预处理指令,意味着它在编译之前被处理。下面是宏定义的用法、理解和重点。宏的基本语法定义常量:#definePI3.14159这里,PI 是一个常量,它的值是 3.14159。在代码中每次使用 PI 时,编译器......
  • 二叉树的递归与非递归遍历:C++实现
    在数据结构的学习中,二叉树是一个非常重要的概念。遍历二叉树是理解和操作二叉树的基础。本文将介绍如何使用C++实现二叉树的递归和非递归遍历,包括前序、中序和后序遍历,并对每种遍历方法的原理进行简要介绍。二叉树节点定义首先,我们定义一个简单的二叉树节点结构:structTreeN......
  • 2-sat 模板
    2-Sat\[\begin{align*}&\LARGE\color{Red}大意:\\&有n个数a_i,m个约束条件都需要满足\\&条件形如(i,a,j,b)\quada_i=a\\text{or}\a_j=b\\\\\\&\LARGE\color{Red}思路:\\&让a_i表示0,a_{i+n}表示1\\&转换条件表达式成:\\&a_i=a\\\te......
  • 【CPP】C++模板:初阶到进阶语法与实用编程示例
    关于我:睡觉待开机:个人主页个人专栏:《优选算法》《C语言》《CPP》生活的理想,就是为了理想的生活!作者留言PDF版免费提供:倘若有需要,想拿我写的博客进行学习和交流,可以私信我将免费提供PDF版。留下你的建议:倘若你发现本文中的内容和配图有任何错误或改进建......
  • C++ 小节3
    1、析构函数相关1.析构函数:函数名与类名相同,前面有~,没有返回值,不能写void,没有参数;只能有一个,不能重载2.析构函数的作用:主要在对象销毁时释放申请的堆内存,关闭文件,关闭网络连接,关闭数据库连接等;3.析构函数的执行:(不显式调用,自动执行)1)作用域到了时自动执行析构函数......