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 ---- 通用的数据类型,名称可以替换,通常为大写字母

使用模板的摸底是为了提高复用性,将类型参数化

示例

  1. 不使用模板:
#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;

  1. 使用模板:
#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 函数模板注意事项

  • 模板的使用方式:

    1. 自动类型推导
    2. 显式的指定类型
  • 使用注意事项:

    1. 自动类型推导,必须推导出一直的数据类型T,才可以使用
    2. 模板必须要确定出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 普通函数与模板函数的区别

  • 普通函数与模板函数的区别

    1. 普通函数调用时可以发生自动类型转换(隐式类型转换)
    2. 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
    3. 函数模板调用时,如果利用显式指定类型的方式,可以发生隐式类型转换
  • 案例(伪代码):

......
    /* 模板函数,返回两数之和 */
    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 普通函数与模板函数的调用规则

  • 调用规则如下:
    1. 如果模板函数和普通函数都可以实现,优先调用普通函数
    2. 可以通过空模板参数列表来强制调用模板函数
    3. 模板函数也可以发生重载
    4. 如果模板函数可以产生更好的匹配,优先调用函数模板
  • 示例(伪代码):
......
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;

  • 总结
    1. 利用具体化的模板,可以解决自定义类型的通用化
    2. 学习模板并不是为了写模板,而是在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 类模板与函数模板的区别

  • 类模板与函数模板的区别主要有两点:
    1. 类模板没有自动类型推导的使用方式
    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. 普通类中的成员函数一开始就可以创建
    2. 类模板中的成员函数在调用时才创建

简单来说,就是即使模板类中的成员函数调用出错也能编译通过,因为在编译时还没有创建该成员函数,但在运行时会报错。

1.3.4 类模板对象做函数参数

  • 类模板实例化出的对象,向函数传参的三种方式:
    1. 指定传入的类型 : 直接显示对象的数据类型(最常用)
    2. 参数模板化 : 将对象中的参数变为模板进行传递
    3. 整个类模板化 : 将这个对象类型模板化进行传递
  • 示例:
#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 类模板与继承

  • 当模板类需要被继承时, 需要注意以下几点:
    1. 当子类继承的是一个模板类时,子类在声明的时候,要指定出父类的类型
    2. 如果不指定,编译器无法给子类分配内存
    3. 如果想灵活指定出父类中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 类模板分文件编写

  • 问题

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

  • 解决

    1. solution1. 直接包含(#include).cpp源文件;
    2. 将.h文件中的类模板成员函数声明和.cpp文件中的类模板成员函数实现写入同一个文件,并更改后缀名为.hpp,并在.cpp源文件中包含(#include)这个hpp文件,注意,hpp 是约定的名称,不是强制名称 (主流方法)。

1.3.8 类模板与友元