C++04_模板初阶

Posted joker D888

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++04_模板初阶相关的知识,希望对你有一定的参考价值。

【C++】04_模板初阶

01.泛型编程与模板

1.1泛型编程

如何实现一个通用的交换函数呢?

void Swap(int& left, int& right)
{
     int temp = left;
     left = right;
     right = temp;
}
void Swap(double& left, double& right)
{
     double temp = left;
     left = right;
     right = temp;
}
void Swap(char& left, char& right)
{
     char temp = left;
     left = right;
     right = temp;
}
//......

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  1. 重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数;
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错。

那能否告诉编译器一个模子让编译器根据不同的类型利用该模子来生成代码呢?

那就需要模板来搞定了。

  • C++另一种编程思想称为泛型编程,主要利用的技术就是模板
  • 泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

1.2 模板的概念

模板就是建立通用的模具,大大提高复用性

例如生活中的摸具模板,ppt模板等。

模板的特点:

  • 模板不可以直接使用,它只是一个框架
  • 模板的通用并不是万能的

模板又分为函数模板与类模板。

02.函数模板

2.1 函数模板概念和作用

  • 概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
  • 作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

2.2 函数模板语法

语法:

template<typename T1,typename T2,...,typename Tn>
//函数声明或定义
//如:
template<typename T>
void Swap( T& left, T& right)
{
     T temp = left;
     left = right;
     right = temp;
}

解释:

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

注意:typename是用来定义模板参数关键字,也可以使用class(但不能使用struct代替)。

2.3函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然 后产生一份专门处理double类型的代码,对于字符类型也是如此。

2.4函数模板的使用(实例化)

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用

  • 模板必须要确定出T的数据类型,才可以使用

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化显式实例化。

//1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
template<class T>
T Add(const T& left, const T& right)
{
     return left + right;
}
int main()
{
     int a1 = 10, a2 = 20;
     double d1 = 10.0, d2 = 20.0;
     Add(a1, a2);
     Add(d1, d2);
 
 /* Add(a1, d1);
 此语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
 编译器无法确定此处到底该将T确定为int 或者 double类型而报错
 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
 Add(a1, d1);*/
 // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
     Add(a, (int)d);
     return 0;
}

//2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
int main(void)
{
     int a = 10;
     double b = 20.0;

     // 显式实例化
     Add<int>(a, b);//如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
     return 0;
}
//又比如
template<class T>
void func()
{
	cout << "func 调用" << endl;
}

void test02()
{
	//func(); //错误,模板不能独立使用,必须确定出T的类型
	func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
}

int main() {

	test01();
	test02();
	return 0;
}

2.5普通函数与模板函数

普通函数与函数模板区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

普通函数与函数模板的调用规则:

  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; 
}

void test01()
{
	//1、如果函数模板和普通函数都可以实现,优先调用普通函数
	// 注意 如果告诉编译器  普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
	int a = 10;
	int b = 20;
	myPrint(a, b); //调用普通函数

	//2、可以通过空模板参数列表来强制调用函数模板
	myPrint<>(a, b); //调用函数模板

	//3、函数模板也可以发生重载
	int c = 30;
	myPrint(a, b, c); //调用重载的函数模板

	//4、 如果函数模板可以产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2); //调用函数模板
}

int main()
{

	test01();
	return 0;
}
//总结🗣:通过上面的案列,可以发现
//1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
//2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模
//板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板函数。

03.类模板

3.1类模板语法

类模板作用:

  • 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。

语法:

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

解释:

template — 声明创建模板

typename — 表面其后面的符号是一种数据类型,可以用class代替

T — 通用的数据类型,名称可以替换,通常为大写字母

3.2 类模板与函数模板区别

类模板与函数模板区别主要有两点:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数
#include <string>
//类模板
template<class NameType, class AgeType = int> 
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

//1、类模板没有自动类型推导的使用方式
void test01()
{
	// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
	Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
	p.showPerson();
}

//2、类模板在模板参数列表中可以有默认参数
void test02()
{
	Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数
	p.showPerson();
}
int main() 
{
	test01();
	test02();

	return 0;
}

3.3 类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建
class Person1
{
public:
	void showPerson1()
	{
		cout << "Person1 show" << endl;
	}
};

class Person2
{
public:
	void showPerson2()
	{
		cout << "Person2 show" << endl;
	}
};

template<class T>
class MyClass
{
public:
	T obj;

	//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成

	void fun1() { obj.showPerson1(); }
	void fun2() { obj.showPerson2(); }

};

void test01()
{
	MyClass<Person1> m;
	
	m.fun1();
	//m.fun2();//编译会出错,说明函数调用才会去创建成员函数
}

int main()
{
	test01();
	return 0;
}

3.4 类模板对象做函数参数

学习目标:

  • 类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

  1. 指定传入的类型 — 直接显示对象的数据类型(常用)
  2. 参数模板化 — 将对象中的参数变为模板进行传递
  3. 整个类模板化 — 将这个对象类型 模板化进行传递
#include <string>
//类模板
template<class NameType, class AgeType = int> 
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

//1、指定传入的类型(常用)
void printPerson1(Person<string, int> &p) 
{
	p.showPerson();
}
void test01()
{
	Person <string, int >p("孙悟空", 100);
	printPerson1(p);
}

//2、参数模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2>&p)
{
	p.showPerson();
	cout << "T1的类型为: " << typeid(T1).name() << endl;
	cout << "T2的类型为: " << typeid(T2).name() << endl;
}
void test02()
{
	Person <string, int >p("猪八戒", 90);
	printPerson2(p);
}

//3、整个类模板化
template<class T>
void printPerson3(T & p)
{
	cout << "T的类型为: " << typeid(T).name() << endl;
	p.showPerson();

}
void test03()
{
	Person <string, int >p("唐僧", 30);
	printPerson3(p);
}

int main() {

	test01();
	test02();
	test03();
    
	return 0;
}

3.5 类模板成员函数类外实现

  • 类模板中成员函数类外实现时,需要加上模板参数列表。
//类模板中成员函数类外实现
template<class T1, class T2>
class Person {
public:
	//成员函数类内声明
	Person(T1 name, T2 age);
	void showPerson();

public:
	T1 m_Name;
	T2 m_Age;
};

//构造函数 类外实现
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;
}

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

int main() {

	test01();
	return 0;
}

以上是关于C++04_模板初阶的主要内容,如果未能解决你的问题,请参考以下文章

C++初阶第八篇——模板初阶(泛型编程+函数模板+类模板)

C++模板初阶

C++模板初阶 | 内存管理

C++初阶:模板初阶函数模板 | 类模板

C++初阶:模板初阶函数模板 | 类模板

C++模板初阶