当前位置:网站首页>函数模板_类模板
函数模板_类模板
2022-06-25 06:39:00 【誰能久伴不乏】
为什么要有函数模板
项目需求: 实现多个函数用来返回两个数的最大值,要求能支持char类型、int类型、double类型变量
// demo 15-2.c
#include <iostream>
using namespace std;
int Max(int a, int b)
{
return a>b ? a:b;
}
char Max(char a, char b)
{
return a>b ? a:b;
}
float Max(float a, float b)
{
return a>b ? a:b;
}
void main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout<<"max(1, 2) = "<<Max(x, y)<<endl;
float a = 2.0;
float b = 3.0;
cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;
system("pause");
return ;
}
实际上,以上程序,只需要一个“函数”就可以搞定!
// demo 15-3.c
#include <iostream>
using namespace std;
/* int Max(int a, int b) { return a>b ? a:b; } char Max(char a, char b) { return a>b ? a:b; } float Max(float a, float b) { return a>b ? a:b; } */
//template 关键字告诉C++编译器 我要开始泛型编程了,请你不要随意报错
//T - 参数化数据类型
template <typename T>
T Max(T a, T b){
return a>b ? a:b;
}
/*如果T 使用int 类型调用,相当于调用下面这个函数 int Max(int a, int b) { return a>b ? a:b; } */
void main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout<<"max(1, 2) = "<<Max(x, y)<<endl; //实现参数类型的自动推导
cout<<"max(1, 2) = "<<Max<int>(x,y)<<endl;//显示类型调用
float a = 2.0;
float b = 3.0;
cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;
system("pause");
return ;
}
函数模板语法
所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
函数模板定义形式
由以下三部分组成: 模板说明 + 函数定义 + 函数模板调用
template < 类型形式参数表 >
类型 函数名 (形式参数表)
{
//语句序列
}
模板说明
template < 类型形式参数表 >
类型形式参数的形式:
typename T1 , typename T2 , …… , typename Tn
或 class T1 , class T2 , …… , class Tn
(注:typename 和 class 的效果完全等同)函数定义
类型 函数名 (形式参数表)
{
}
注意:模板说明的类属参数必须在函数定义中出现一次
函数参数表中可以使用类属类型参数,也可以使用一般类型参数函数模板调用
max(a, b); //显式类型调用
max(a, b); //自动数据类型推导
4.模板函数
函数模板和函数重载
5.函数模板和函数重载
// demo 15-4.c
#include <iostream>
using namespace std;
template <typename T>
void Swap(T &a, T &b){
T t;
t = a;
a = b;
b = t;
cout<<"Swap 模板函数被调用了"<<endl;
}
/* void Swap(char &a, int &b){ int t; t = a; a = b; b = t; cout<<"Swap 普通函数被调用了"<<endl; } */
void main(void){
char cNum = 'c';
int iNum = 65;
//第一种情况,模板函数和普通函数并存,参数类型和普通重载函数更匹配
//调用普通函数
//Swap(cNum, iNum);
//第二种情况 不存在普通函数,函数模板会隐式数据类型转换嘛?
//结论:不提供隐式的数据类型转换,必须是严格的匹配
//Swap(cNum, iNum);
system("pause");
return ;
}
函数模板和普通函数区别结论:
两者允许并存
函数模板不允许自动类型转化
普通函数能够进行自动类型转换
// demo 15-5.c
#include <iostream>
using namespace std;
//第一版
int Max(int a, int b)
{
cout<<"调用 int Max(int a, int b)"<<endl;
return a>b ? a:b;
}
template<typename T>
T Max(T a, T b)
{
cout<<"调用 T Max(T a, T b)"<<endl;
return a>b ? a:b;
}
template <typename T>
T Max(T a, T b, T c){
cout<<"调用 T Max(T a, T b, T c)"<<endl;
return Max(Max(a, b), c);
}
//第二版
int Max1(int a, int b)
{
cout<<"调用 int Max(int a, int b)"<<endl;
return a>b ? a:b;
}
template<typename T1, typename T2>
T1 Max1(T1 a, T2 b)
{
cout<<"调用 T Max1(T1 a, T2 b)"<<endl;
return a>b ? a:b;
}
void main(void){
int a = 1;
int b = 2;
//当函数模板和普通函数都符合调用时,优先选择普通函数
//cout<<"Max(a, b)"<<Max(a, b)<<endl;
//如果显式的使用函数模板,则使用<> 类型列表
//Max<>(a, b);
char c = 'a';
//如果函数模板会产生更好的匹配,使用函数模板
//Max1(c, a);
//Max(1.0, 2.0);
Max(3.0, 4.0, 5.0);
system("pause");
return ;
}
函数模板和普通函数在一起,调用规则:
1 函数模板可以像普通函数一样被重载
2 C++编译器优先考虑普通函数
3 如果函数模板可以产生一个更好的匹配,那么选择模板
4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
5. 编译器并不是把函数模板处理成能够处理任意类型的函数
6. 编译器从函数模板通过具体类型产生不同的函数
类模板的使用
1.为什么需要类模板
类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以通过如下面语句声明了一个类模板:
2.类模板用于实现类所需数据的类型参数化
3.类模板在表示支持多种数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响
类模板定义
类模板由模板说明和类说明构成
模板说明同函数模板,如下:
template <类型形式参数表>
类声明
例如:
template
class ClassName
{
//ClassName 的成员函数
private :
Type DataMember;
}
单个类模板使用
// demo 15-8.c
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t=0)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
void printA(A<int> &a){
cout<<a.getT()<<endl;
}
int main(void){
//1.模板类定义类对象,必须显示指定类型
//2.模板种如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(666);
cout<<a.getT()<<endl;
//模板类做为函数参数
printA(a);
system("pause");
return 0;
}
继承类模板使用
// demo 15-9.c
#include <iostream>
using namespace std;
//继承中父子类和模板类的结合情况
//1.父类一般类,子类是模板类, 和普通继承的玩法类似
//2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数
//3.父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中
/*class B { public: B(int b) { this->b = b; } private: int b; }; */
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
template <typename Tb>
class B: public A<int>
{
public:
B(Tb b):A<Tb>(b)
{
this->b = b;
}
private:
Tb b;
};
void printA(A<int> &a){
cout<<a.getT()<<endl;
}
int main(void){
//1.模板类定义类对象,必须显示指定类型
//2.模板种如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(666);
cout<<a.getT()<<endl;
B<int> b(888);
cout<<"b(888): "<<b.getT()<<endl;
//模板类做为函数参数
printA(a);
system("pause");
return 0;
}
结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么
1.父类一般类,子类是模板类, 和普通继承的玩法类似
2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数
3.父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中
4.所有的类模板函数写在类的外部,在一个cpp中
// demo 15-9.c
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t=0);
T &getT();
A operator +(const A &other);
void print();
private:
T t;
};
/* class A { public: A(int t=0); int &getT(); A operator +(const A &other); void print(); private: int t; }; */
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T &A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T> &other){
A<T> tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print(){
cout<<this->t<<endl;
}
int main(void){
A<int> a(666), b(888);
//cout<<a.getT()<<endl;
A<int> tmp = a + b;
tmp.print();
system("pause");
return 0;
}
总结:
在同一个cpp 文件中把模板类的成员函数放到类的外部,需要注意以下几点
1.函数前声明 template <类型形式参数表>
2.类的成员函数前的类限定域说明必须要带上虚拟参数列表
3.返回的变量是模板类的对象时必须带上虚拟参数列表
4.成员函数参数中出现模板类的对象时必须带上虚拟参数列表
5.成员函数内部没有限定
// demo.h
#pragma once
template <typename T>
class A
{
public:
A(T t=0);
T &getT();
A operator +(const A &other);
void print();
private:
T t;
};
// demo 15-10.c
#include "demo.h"
#include <iostream>
using namespace std;
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T &A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T> &other){
A<T> tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print(){
cout<<this->t<<endl;
}
int main(void){
A<int> a(666), b(888);
//cout<<a.getT()<<endl;
A<int> tmp = a + b;
tmp.print();
system("pause");
return 0;
}
注意:当类模板的声明(.h文件)和实现(.cpp 或.hpp文件)完全分离,因为类模板的特殊实现,我们应在使用类模板时使用#include 包含 实现部分的.cpp 或.hpp文件。
特殊情况 友元函数
// demo 15-11.c
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t=0);
//声明一个友元函数,实现对两个A类对象进行加法操作
template <typename T>
friend A<T> addA(const A<T> &a, const A<T> &b);
T &getT();
A operator +(const A &other);
void print();
private:
T t;
};
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T &A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T> &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print(){
cout<<this->t<<endl;
}
//A 类的友元函数,就是它的好朋友
template <typename T>
A<T> addA(const A<T> &a, const A<T> &b){
A<T> tmp;
cout<<"call addA()..."<<endl;
tmp.t = a.t + b.t;
return tmp;
}
int main(void){
A<int> a(666), b(888);
//cout<<a.getT()<<endl;
A<int> tmp = a + b;
A<int> tmp1 = addA<int>(a, b);
tmp.print();
tmp1.print();
system("pause");
return 0;
}
结论:
(1)类内部声明友元函数,必须写成一下形式
template
friend A addA (A &a, A &b);
(2)友元函数实现 必须写成
template
A add(A &a, A &b)
{
//…
}
(3)友元函数调用 必须写成
A c4 = addA(c1, c2);
模板类和静态成员
// demo 15-12.c
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t=0);
T &getT();
A operator +(const A &other);
void print();
public:
static int count;
private:
T t;
};
template <typename T> int A<T>::count = 666;
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T &A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T> &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print(){
cout<<this->t<<endl;
}
/* //当我们的虚拟的类型T被 int 实例化以后,模板类如下: class A { public: A(int t=0); int &getT(); A operator +(const A &other); void print(); public: static int count; private: int t; }; int A::count = 666; A::A(int t) { this->t = t; } int &A::getT() { return t; } A A::operator+(const A &other){ A tmp; //类的内部类型可以显示声明也可以不显示 tmp.t =this->t + other.t; return tmp; } void A::print(){ cout<<this->t<<endl; } */
/* //当我们的虚拟的类型T被 float 实例化以后,模板类如下: class A { public: A(float t=0); float &getT(); A operator +(const A &other); void print(); public: static int count; private: float t; }; int A::count = 666; A::A(float t) { this->t = t; } float &A::getT() { return t; } A A::operator+(const A &other){ A tmp; //类的内部类型可以显示声明也可以不显示 tmp.t =this->t + other.t; return tmp; } void A::print(){ cout<<this->t<<endl; } */
int main(void){
A<int> a(666), b(888);
A<int> tmp = a + b;
//A a(666), b(888);
//A tmp = a + b;
A<float> c(777), d(999);
a.count = 888;
cout<<"b.count:"<<b.count<<endl;
cout<<"c.count:"<<c.count<<endl;
cout<<"d.count:"<<d.count<<endl;
c.count = 1000;
cout<<"修改后, d.count:"<<d.count<<endl;
//tmp.print();
system("pause");
return 0;
}
总结:
1.从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
2. 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
3.static 数据成员也可以使用虚拟类型参数T
类模板使用总结
归纳以上的介绍,可以这样声明和使用类模板:
- 先写出一个实际的类。
- 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的T)。
- 在类声明前面加入一行,格式为:
template <typename 虚拟类型参数>
如:
template
class A
{…}; //类体 - 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
或 类模板名<实际类型名> 对象名(实参表列);
如:
A cmp;
A cmp(3,7); - 如果在类模板外定义成员函数,应写成类模板形式:
template <typename 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点补充: - 类模板的类型参数可以有一个或多个,每个类型前面都必须加typename 或class,如:
template <typename T1,typename T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass<int, char> object; - 和使用类一样,使用类模板时要注意其作用域,只有在它的有效作用域内用使用它定义对象。
- 模板类也可以有支持继承,有层次关系,一个类模板可以作为基类,派生出派生模板类。
类模板实战
1)请设计一个数组模板类( Vector ),完成对int、char、float、double 以及任意的自定义类等类型元素进行管理。
需求
a.实现构造函数
b.实现拷贝构造函数
c.实现cout << 操作
d.实现下标访问符[] 的重载操作
e.实现 = 号操作符重载
// demo 15-13 Vector.h
#include <iostream>
using namespace std;
template <typename T>
class Vector
{
//Vector<int> a(10); cout<<a;
friend ostream &operator<< <T> (ostream &out, const Vector &object);
public:
Vector(int size = 128); //构造函数
Vector(const Vector &object); //拷贝构造函数
//Vector<int> a(10); a
//operator<<()
int getLength();//获取内部储存的元素个数
//Vector<int> a1, a2; a1[0]
T& operator[](int index);
//实现=操作符重载
//a1 = a2 = a3;
Vector &operator=(const Vector &object);
~Vector(); //析构函数
private:
T *m_base;
int m_len;
};
// demo 15-13 Vector.cpp
#include <iostream>
using namespace std;
#include "Vector.h"
//cout<<a<<b<<c;
template<typename T>
ostream &operator<<(ostream &out, const Vector<T> &object){
for(int i=0; i<object.m_len; i++){
out << object.m_base[i] << " ";//Student a("18","李小花"); cout<< a<<endl;
}
out<<endl;
return out;
}
template <typename T>
Vector<T>::Vector(int size){
//构造函数
if(size > 0){
m_len = size;
m_base = new T[m_len];
}
}
template <typename T>
Vector<T>::Vector(const Vector<T> &object){
//拷贝构造函数
//根据传入的对象元素个数分配空间
m_len = object.m_len;
m_base = new T[m_len];
//数据的拷贝
for(int i=0; i<m_len; i++){
m_base[i] = object.m_base[i];
}
}
template <typename T>
int Vector<T>::getLength(){
return m_len;
}
//Vector<int> a1, a2; a1[0]
template <typename T>
T& Vector<T>::operator[](int index){
return m_base[index];// return *(m_base+index);
}
//实现=操作符重载
//a1 = a2 = a3;
template <typename T>
Vector<T> &Vector<T>::operator=(const Vector<T> &object){
if(m_base != NULL){
delete[] m_base;
m_base = NULL;
m_len = 0;
}
//根据传入的对象元素个数分配空间
m_len = object.m_len;
m_base = new T[m_len];
//数据的拷贝
for(int i=0; i<m_len; i++){
m_base[i] = object.m_base[i];
}
return *this; // a3 = a2 = a1;
}
template <typename T>
Vector<T>::~Vector(){
//析构函数
if(m_base != NULL){
delete[] m_base;
m_base = NULL;
m_len = 0;
}
}
// demo 15-13 13_类模板实战.cpp
#include <iostream>
using namespace std;
#include "Vector.cpp"
class Student{
friend ostream &operator<<(ostream &out, const Student &object);
public:
Student(){
age = 0;
name[0] = '\0';
}
Student(int _age, char *_name){
age = _age;
strcpy_s(name, 64, _name);
}
void print(){
cout<<name<<", "<<age<<endl;
}
~Student(){
}
private:
int age;
char name[64];
};
ostream &operator<<(ostream &out, const Student &object){
out<<"("<<object.name<<" , "<<object.age<<")";
return out;
}
int main(){
Student s1(18, "李小花");
Student s2(19, "王大炮");
Vector<Student *> studentVector(2);
studentVector[0] = &s1;
studentVector[1] = &s2;
/*for(int i=0; i<studentVector.getLength(); i++){ studentVector[i].print(); }*/
cout<<studentVector<<endl;
system("pause");
//ostream cout;
Vector<int> myVector(10);
//int a[10]; len: sizeof(a)/sizeof(a[0])
for(int i=0; i<myVector.getLength(); i++){
myVector[i] = i;
}
cout<<myVector<<endl;
system("pause");
for(int i=0; i<myVector.getLength(); i++){
cout<<myVector[i]<<endl;
}
//测试拷贝构造函数
Vector<int> myIntVector1(myVector);
cout<<"myIntVector1 中的元素如下:"<<endl;
for(int i=0; i<myIntVector1.getLength(); i++){
cout<<myIntVector1[i]<<endl;
}
cout<<"---end---"<<endl;
//测试赋值运算符重载
Vector<int> myIntVector2(1);
myIntVector2 = myIntVector1;
cout<<"myIntVector2 中的元素如下:"<<endl;
for(int i=0; i<myIntVector1.getLength(); i++){
cout<<myIntVector1[i]<<endl;
}
cout<<"---end---"<<endl;
Vector<float> myVector1(10);
//int a[10]; len: sizeof(a)/sizeof(a[0])
for(int i=0; i<myVector1.getLength(); i++){
myVector1[i] = i*0.1f;
}
for(int i=0; i<myVector1.getLength(); i++){
cout<<myVector1[i]<<endl;
}
system("pause");
return 0;
}
边栏推荐
- 【UVM入门 ===> Episode_9 】~ 寄存器模型、寄存器模型的集成、寄存器模型的常规方法、寄存器模型的应用场景
- Omni toolbox direct download
- Tempest HDMI leak receive 2
- How is the network connected?
- Operate cnblogs metaweblog API
- 对链表进行插入排序[dummy统一操作+断链核心--被动节点]
- VectorDraw Developer Framework 10.10
- 【批处理DOS-CMD命令-汇总和小结】-外部命令-cmd下载命令、抓包命令(wget)
- Distributed quorum NWR of the alchemy furnace of the Supreme Master
- Let's talk about MCU crash caused by hardware problems
猜你喜欢
【UVM入門 ===> Episode_9 】~ 寄存器模型、寄存器模型的集成、寄存器模型的常規方法、寄存器模型的應用場景
Sichuan earth microelectronics high performance, high integration and low cost isolated 485 transceiver
Design a MySQL table for message queue to store message data
诸葛亮 VS 庞统,拿下分布式 Paxos
Why "New Year's Eve", the original memory burst!
鸿蒙页面菜单的选择
From perceptron to transformer, a brief history of deep learning
Research on 3D model retrieval method based on two channel attention residual network - Zhou Jie - paper notes
[C language] one dimensional array
【批处理DOS-CMD命令-汇总和小结】-外部命令-cmd下载命令、抓包命令(wget)
随机推荐
[batch dos-cmd command - summary and summary] - commands related to Internet access and network communication (Ping, Telnet, NSLOOKUP, ARP, tracert, ipconfig)
Analysis on the trend of the number of national cinemas, film viewers and average ticket prices in 2021 [figure]
Loopholes in the missed scanning system of Lvmeng and its repair scheme
Full range of isolator chips with integrated isolated power supply
The principle of Zener diode, what is its function?
TEMPEST HDMI泄漏接收 2
Shell command learning
Sichuan Tuwei ca-is3105w fully integrated DC-DC converter
Path planner based on time potential function in dynamic environment
JMeter introduction practice ----- use of global variables and local variables
Vscode official configuration synchronization scheme
Ca-is1200u current detection isolation amplifier has been delivered in batch
【UVM入門 ===> Episode_9 】~ 寄存器模型、寄存器模型的集成、寄存器模型的常規方法、寄存器模型的應用場景
Can we use function pointers in go- Can we have function pointers in Go?
图扑软件数字孪生 3D 风电场,智慧风电之海上风电
Ns32f103c8t6 can perfectly replace stm32f103c8t6
Is it possible to use Jasmine's toHaveBeenCalledWith matcher with a regular expression?
Design a MySQL table for message queue to store message data
【批處理DOS-CMD命令-匯總和小結】-cmd擴展命令、擴展功能(cmd /e:on、cmd /e:off)
13 `bs_ duixiang. Tag tag ` get a tag object