自学内容网 自学内容网

C++之初识模版

目录

1.关于模版的介绍

2.函数模版

2.1函数模板概念

2.2函数模板格式

2.3 函数模板的原理

2.4 函数模板的实例化

2.5模板参数的匹配原则

3.类模版

3.1类模板的定义格式

3.2 类模板的实例化


1.关于模版的介绍

  • C++中的模板是一种通用编程工具,它允许程序员编写具有通用性的函数或类,可以适用于不同类型的数据。模板使用泛型编程的概念,即代码可以处理多种不同的数据类型,而不只是特定的类型。
  • 在C++中,有两种类型的模板:函数模板和类模板。                 
  • --->函数模板是一种通用的函数定义,可以用于处理不同类型的参数。函数模板使用一对尖括号 "<>" 包围一个或多个类型参数,这些类型参数在函数定义中可以用作函数参数或返回类型的占位符。在调用函数模板时,编译器会根据实际参数的类型来推导出模板参数的具体类型。
  • --->类模板是一种通用的类定义,可以用于创建具有相同结构但可能使用不同类型的成员的类。类模板的定义使用一对尖括号 "<>" 包围一个或多个类型参数,这些类型参数在类定义中可以用作类的成员变量、成员函数的参数或返回类型的占位符。在实例化类模板时,需要提供实际类型的参数。
  • 使用模板可以提供代码的重用性和灵活性,因为它使得编写可以用于处理不同类型数据的通用代码成为可能。模板还可以提供更好的类型检查,并在编译时进行错误检查。

2.函数模版

2.1函数模板概念

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

2.2函数模板格式

template<typename T1, typename T2,......,typename Tn>返回值类型 函数名(参数列表){}
演示:
template<typename T>//template<class T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 0; double c = 3.1415925, d = 4.2526036;
Swap(a, b); Swap(c, d);
cout << a << b << c << d << endl;
return 0;
}

2.3 函数模板的原理

    函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
    在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
演示:
template<typename T>//template<class T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 0; 
Swap(a, b); 
    double c = 3.1415925, d = 4.2526036;
Swap(c, d);
char e = 'a', f = 'b';
Swap(e, f);
return 0;
}

2.4 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
演示:
template<typename T1,typename T2>//template<class T>
void Swap(T1& a, T2& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 0; 
    double c = 3.1415925, d = 4.2526036;
    Swap(a, b); 
Swap(c, d);
Swap(a, d);
return 0;
}

这段代码可以成功运行吗?当然可以,因为我们用typename创建了两个模版参数T1和T2,传参时会发生隐式实例化,即便是a与d参数类型不同,也可以分别实例化,那如果将参数减少为一个呢?

// 专门处理int的加法函数
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版本
}

这时候报错了,这是因为a是int类型,d是double类型,如果都交给同一个模版参数那么编译器就不知道要隐式实例化为int还是double类型,这时有两种解决办法:
  1. 将a强制类型转换为double或将d强制类型转换为int;
  2. 使用显式实例化
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
template<typename V>//template<class T>
void Swap(const V& a,const V& b)
{
V tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 0; 
    double c = 3.1415925, d = 4.2526036;
Swap<double>(a, d);
return 0;
}

注意:这里只是演示显式实例化,交换函数在传参时会产生临时变量,具有常性,因此必须用const类型进行接收,不然权限就被放大了,但const修饰之后不可以更改值,因此还请读者注意!

2.5模板参数的匹配原则

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 专门处理int的加法函数
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版本
}
2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
// 专门处理int的加法函数
int Add(int left, int right)
{
 return left + right;
}
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
 return left + right;
}
void Test()
{
 Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
 Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函
数
}
3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

3.类模版

3.1类模板的定义格式

形式:

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

演示:

template<class T>
class A
{
public:
A(int year = 2025, int month = 1, int day = 19);
~A()
{}
private:
T* arr;
int _year;
int _month;
int _day;
};

template<class T>//作用域就是一个函数或者就是一个类,提取出来就要加上模板
A<T>::A(int year, int month , int day)//A是类名 A<T>是类型 要区分开来
{
cout << year << "-" << month << "-" << day << endl;
this->_year = year;
this->_month = month;
this->_day = day;
}

注意:

  1. 缺省参数只能在声明时候标注,在定义的时候标注会引发冲突;
  2. 一般在类中模版参数类型为class,而普通函数模版参数类型为typename;
  3. 类中函数在类外定义时,需要加上template...因为模版的作用域是一个类或者是一个函数,每当重新定义新的类或者函数,都需要重新加上模版
  4. 类外函数声明在用模版参数时不再是简单的类名::函数名,而需要在类名后加上<模版参数>,这是语法规定!

3.2 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

演示:
template<class T>
class A
{
public:
A(int year = 2025, int month = 1, int day = 19);
~A()
{}
private:
T* arr;
int _year;
int _month;
int _day;
};
template<class T>//作用域就是一个函数或者就是一个类,提取出来就要加上模板
A<T>::A(int year, int month , int day)//A是类名 A<T>是类型 要区分开来
{
cout << year << "-" << month << "-" << day << endl;
this->_year = year;
this->_month = month;
this->_day = day;
}
int main()
{
A<int> a1;
A<double> a2;
return 0;
}

在main函数中实例化一个类时,使用<>,中间填入模版类型即可~

补充:

  1. 模板的实参在任何时候都可以省略,模板实参省略意思为隐式实例化,一般情况下都使用隐式实例化,不需指定模板类型参数,让编译器进行推导,但有些情况下编译器推导时可能会有歧义,比如:模板参数只有一个类型T,但是用两个不同类型隐式实例化。

  2. 类模板与模板类所指的是同一概念,类模板是一个类家族,模板类是通过类模板实例化的具体类

  3. 类模板的参数可以是虚拟类型的,也可以是具体类型,C++中类模板的参数即为模板参数列表中内容,有两种方式:类型参数和非类型参数,类型参数:即类型参数化,将来实例化为具体的实际类型,有点像函数的形参,形参可以接受不同值的实参;非类型参数:在定义时给定了具体的类型,用该类型定义的为常量。

  4. 类模板中的成员函数全是模板函数,定义时都必须通过完整的模板语法进行定义。 因为所有类模板的成员函数,放在类外定义时,需要在函数名前加类名,而类名实际为ClassName<T>,所以定义时还需加模板参数列表。

- - - - - - ————————————本文结束———————————— - - - - - -


原文地址:https://blog.csdn.net/Dai_renwen/article/details/145308641

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!