C++泛型编程&模板学习笔记
Posted 狱典司
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++泛型编程&模板学习笔记相关的知识,希望对你有一定的参考价值。
该篇笔记主要总结了黑马程序员C++课程中讲解的模板部分。
C++泛型编程&模板学习笔记
1. 模板
1.1 模板的概念
模板就是建立通用的工具,大大提高复用性。
需要注意的是,模板不可以直接使用,它只是一个框架,模板的通用并不是万能的。
1.2 函数模板
-
C++除了面对对象的编程思想,还有一种编程思想称为泛型的编程,主要基于模板。
-
C++提供两种模板机制:①函数模板 和 ②类模板
1.2.1 函数模板语法:
template<typename T>
或
template<class T> // 注意,不加分号
函数声明或定义
解释:
- template ---- 声明创建模板
- typename ---- 表明其后面的符号是一种数据类型,可以用class替代
- T ---- 通用的数据类型,名称可以替换,通常为大写字母
使用模板的摸底是为了提高复用性,将类型参数化。
示例
- 不使用模板:
#include<bits/stdc++.h>
using namespace std;
/* 整型交换 */
void swapInt( int &a, int &b )
int tmp = a;
a = b;
b = tmp;
/* 浮点型交换 */
void swapDouble( double &a, double &b )
double tmp = a;
a = b;
b = tmp;
int main()
int a = 10, b = 20;
swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double c = 11.11, d = 22.22;
swapDouble(c,d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
return 0;
- 使用模板:
#include<bits/stdc++.h>
using namespace std;
/* 模板 */
/* 声明一个模板,告知编译器T是一个通用数据类型 */
template<typename T>
void mySwap( T &a, T &b )
T tmp = a;
a = b;
b = tmp;
int main()
/* 1. 模板使用 --- 自动类型推导 */
int a = 10, b = 20;
mySwap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
/* 2. 模板使用 --- 显式指定类型 */
double c = 11.11, d = 22.22;
mySwap<double>(c,d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
return 0;
1.2.2 函数模板注意事项
-
模板的使用方式:
- 自动类型推导
- 显式的指定类型
-
使用注意事项:
- 自动类型推导,必须推导出一直的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
1.2.3 模板案例
#include<bits/stdc++.h>
using namespace std;
/* 交换函数模板 */
template<typename T>
void mySwap( T &a, T &b )
T tmp = a;
a = b;
b = tmp;
/* 排序函数模板(选择排序,升序) */
template<typename T> void mySort( T arr[], int len )
for( int i = 0; i < len; i ++ )
int min_pos = i;
for( int j = i + 1; j < len; j ++ )
if( arr[min_pos] > arr[j] ) min_pos = j;
if( min_pos != i ) mySwap( arr[min_pos], arr[i] );
/* 打印数组函数模板 */
template<typename T> void myPrint( T arr[], int len )
for( int i = 0; i < len; i ++ ) cout << arr[i] <<" ";
cout << endl;
int main()
/* 排序字符数组并打印 */
char ch_arr[] = "fedcba";
mySort(ch_arr, sizeof(ch_arr)-1);
myPrint(ch_arr, sizeof(ch_arr)-1);
/* 排序整型数组并打印 */
int int_arr[] = 5,4,3,2,1;
mySort(int_arr, sizeof(int_arr)/sizeof(int));
myPrint(int_arr, sizeof(int_arr)/sizeof(int));
return 0;
1.2.4 普通函数与模板函数的区别
-
普通函数与模板函数的区别:
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 函数模板调用时,如果利用显式指定类型的方式,可以发生隐式类型转换
-
案例(伪代码):
......
/* 模板函数,返回两数之和 */
template<typename T> T myAdd( T a, T b ) return a+b ;
......
int a = 10, b = 20;
char c = 'c';
/* 自动类型推导 */
cout << myAdd(a, b) << endl; // 输出30
cout << myAdd(a, c) << endl; // 报错,自动类型推导不做隐式类型转换
/* 显式指定类型 */
cout << myAdd<int>(a, c) << endl; // 输出109,c的ASCCI值是99
1.2.5 普通函数与模板函数的调用规则
- 调用规则如下:
- 如果模板函数和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用模板函数
- 模板函数也可以发生重载
- 如果模板函数可以产生更好的匹配,优先调用函数模板
- 示例(伪代码):
......
void myPrint( int a, int b )
cout << "调用普通函数" << endl;
template<typename T> void myPrint( T a, T b )
cout << "调用模板函数" << endl;
template<typename T> void myPrint( T a, T b, T c )
cout << "调用重载的模板函数" << endl;
......
/* 1. 如果模板函数和普通函数都可以实现,优先调用普通函数 */
/* 哪怕普通函数只有声明没有实现,调用的也是普通函数 */
myPrint(1,2); // 调用的是普通函数
/* 2. 可以通过空模板参数列表来强制调用模板函数 */
myPrint<>(1, 2); // 调用的是模板函数
/* 3. 模板函数也可以发生重载 */
myPrint(1,2,3); // 调用的是重载的模板函数
/* 4. 如果模板函数可以产生更好的匹配,优先调用函数模板 */
/* 下例子中,如果将参数传递到普通函数,还需要做隐式的类型转换,故选择了模板函数,即所谓“更好的匹配” */
myPrint('a', 'b'); // 调用的是模板函数
1.2.6 模板的局限性(具体化)
模板并不是万能的,例如在下面的例子的赋值操作中,如果传入a和b的是一个数组,就无法实现了:
template<typename T> assi(T a, T b) a = b;
再例如,在下面的代码中如果传入的是像Person这样的自定义数据类型,也无法正常运行:
template<typename T> bool cmp( T a, T b ) return a > b;
因此C++为了解决这类问题,提供模板的重载,可以为这些特定的数据类型提供具体的模板。
#include<bits/stdc++.h>
using namespace std;
/* Person类 */
class Person
public:
int m_age;
int m_height;
Person(int age, int height)
m_age = age;
m_height = height;
;
/* 比较是否相同 模板函数 */
template<typename T> bool myCmp( T &a, T &b )
return a == b;
/* 利用具体化的Person类参数实现模板函数重载,具体化优先调用 */
template<> bool myCmp( Person &p1, Person &p2 )
return p1.m_age == p2.m_age && p1.m_height == p2.m_height;
int main()
Person p1(28,170);
Person p2(18, 180);
cout << ( myCmp(p1, p2)?"相同":"不同" ) << endl;
return 0;
- 总结:
- 利用具体化的模板,可以解决自定义类型的通用化
- 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
1.3 类模板
1.3.1 类模板语法
-
类模板作用:
建立一个通用类,类中的成员、数据类型可以不具体指定,用一个虚拟的类型来代表。
-
语法
template<class T> 类
或者
template<typename T> 类
- 案例
#include<bits/stdc++.h>
using namespace std;
/* Person模板类 */
template<class NameType, class AgeType> class Person
public:
NameType m_name;
AgeType m_age;
Person(NameType name, AgeType age)
m_name = name;
m_age = age;
;
int main()
Person<string, int> p1("魈",3500);
Person<string, int> p2("枫原万叶", 23);
return 0;
总结:类模板与函数模板语法非常相似,在声明模板template之后紧跟一个类,该类就成为模板类。
1.3.2 类模板与函数模板的区别
- 类模板与函数模板的区别主要有两点:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
- 示例:
#include<bits/stdc++.h>
using namespace std;
/* Person模板类,含默认参数 */
template<class NameType = string, class AgeType = int> class Person
public:
NameType m_name;
AgeType m_age;
Person(NameType name, AgeType age)
m_name = name;
m_age = age;
;
int main()
/* 以下方式皆可构造成功 */
Person<string, int> p1("魈",3500);
Person<string> p2("枫原万叶", 23);
Person<> p3("鹿野苑平藏",16);
Person<char> p4('W', 0);
Person<int, float> p5(9999, 3.14);
return 0;
1.3.3 类模板中成员函数的创建时机
- 类模板中的成员函数和普通类中的成员函数创建时机有区别:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
简单来说,就是即使模板类中的成员函数调用出错也能编译通过,因为在编译时还没有创建该成员函数,但在运行时会报错。
1.3.4 类模板对象做函数参数
- 类模板实例化出的对象,向函数传参的三种方式:
- 指定传入的类型 : 直接显示对象的数据类型(最常用)
- 参数模板化 : 将对象中的参数变为模板进行传递
- 整个类模板化 : 将这个对象类型模板化进行传递
- 示例:
#include<bits/stdc++.h>
using namespace std;
/* Person模板类,含默认参数 */
template<class NameType=string, class AgeType=int>
class Person
public:
NameType m_name;
AgeType m_age;
/* 构造函数 */
Person(NameType name, AgeType age)
m_name = name;
m_age = age;
/* 成员函数 */
void showPerson()
cout << m_name << " " << m_age << endl;
;
/* 1. 指定传入的类型 */
void test01( Person<string, int> &p )
p.showPerson();
/* 2. 参数模板化 */
template<typename T1, typename T2>
void test02( Person<T1, T2> &p )
p.showPerson();
/* 3. 类模板化 */
template<class T> void test03( T &p )
p.showPerson();
int main()
Person<> p3("鹿野苑平藏",16);
/* 以下方式皆可传参成功 */
test01(p3); test02(p3); test03(p3);
return 0;
1.3.5 类模板与继承
- 当模板类需要被继承时, 需要注意以下几点:
- 当子类继承的是一个模板类时,子类在声明的时候,要指定出父类的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为模板
- 示例:
#include<bits/stdc++.h>
using namespace std;
/* Animal模板类,含默认参数 */
template<class T=string> class Animal
public:
T m_name;
;
/* Person类,继承自Animal模板类 */
/* 错误的继承方式 */
class God : public Animal; // 继承错误
/* 方式1. 指定父类类型 */
class Person : public Animal<string>; // 继承成功
/* 方式2. 将子类写为模板 */
/* 2.1 */
template<class T>
class Pet : public Animal<T>; // 继承成功
/* 2.2 */
template<class T1, class T2>
class Legit : public Animal<T1> // 继承成功
T2 obj;
;
int main()
return 0;
1.3.6 类模板成员函数的类外实现
- 当模板类的成员函数在需要在类内声明,类外实现时,要将作用域的类也模板化
#include<bits/stdc++.h>
using namespace std;
/* Animal模板类 */
template<class T1, class T2> class Person
public:
T1 m_name;
T2 m_age;
/*
1. 构造函数的类内实现(构造列表):
Person(T1 name, T2 age):m_name(name), m_age(age)
*/
/* 2. 构造&成员函数的类内声明,类外实现 */
/* 类内声明: */
Person(T1, T2);
void showPerson();
;
/* 构造类外实现 */
template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age)
this->m_name = name;
this->m_age = age;
/* 成员函数类外实现 */
template<class T1, class T2>
void Person<T1,T2>::showPerson()
cout << this->m_name << " " << this->m_age << endl;
int main()
Person<string, int> p("钟离", 5000);
p.showPerson();
return 0;
1.3.7 类模板分文件编写
-
问题
类模板中成员函数创建时机在调用阶段,导致份文件编写时链接不到。
-
解决
- solution1. 直接包含(#include).cpp源文件;
- 将.h文件中的类模板成员函数声明和.cpp文件中的类模板成员函数实现写入同一个文件,并更改后缀名为.hpp,并在.cpp源文件中包含(#include)这个hpp文件,注意,hpp 是约定的名称,不是强制名称 (主流方法)。
1.3.8 类模板与友元
- ① 全局函数的类内
以上是关于C++泛型编程&模板学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
C++ Primer 5th笔记(chap 16 模板和泛型编程)模板特例化
C++ Primer 5th笔记(chap 16 模板和泛型编程)函数指针和实参推断
C++ Primer 5th笔记(chap 16 模板和泛型编程)转发参数包