C++学习笔记:模板

C++语言的特性之一:它支持泛型编程,而泛型编程主要就是利用了模板来实现

举几个模板的例子:

1.我们设计了一个容器类用来存储其他对象或值,例如一个队列,当我们要存储不同数据类型,甚至是自定义类型时,如果没有模板的话,我们需要给每一个数据类型写一个队列的类。当我们使用模板以后,我们就能将具体的类型作为参数传递给这个模板类,就能用不同的代码生成存储不同类型值的队列。这就是模板类

2.我们都知道一个函数swap,用来交换两个数。同样,当我们要交换不同数据类型,甚至时自定义类型时,我们就需要模板函数,只需每次输入不同参数,就能实现一个函数被用在不同数据类型中。这就是函数模板

模板的概念:

分类

模板分为:模板类和函数模板两大类。

使用

在使用时,先声明,接下来的类或者函数为模板:

template<typename Type>

1.typename 可用 class代替,因为在c++11中才提供了不易混淆的typename关键字。
2.其中Type是将类型参数化   
3.template是模板的意思 
4.两种使用方式:1.自动类型推导:必须推导出一致的类型T才行。

                            2.模板必须确定出T的类型才可以使用。

模板的局限性以及模板重载

#include<iostream>
#include<string>
using namespace std;

//学习模板并不是为了写模板,而是在STL中能够使用系统提供的模板

//当a ,b是一个数组时,就无法实现了,无法数组b给a赋值
template<typename T>
void f(T a, T b) {
	a = b;
}


template<typename T>
void com(T a, T b) {
	if (a > b) {

	}
}


//如果T的数据类型为Person这样的自定义数据类型,也无法正常运行
template<typename T>
bool compare(T &a, T &b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

void test01() {
	int a = 10;
	int b = 10;
	bool ret = compare(a, b);
	if (ret) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

//因此提供了模板的重载,可以给特定的类型提供具体化的模板
//利用具体化Person的版本实现代码,具体化优先调用

class Person {
public:
	Person() :m_Name("张三"), m_Age(18) {

	}

	Person(string name, int age) {
		this->m_Age = age;
		this->m_Name = name;
	}

	string m_Name;
	int m_Age;
};

template<> bool compare(Person& p1, Person& p2) {
	if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) {
		return true;
	}
	else {
		return false;
	}
}

void test02() {
	Person p1("Tom", 10);
	Person p2("Tom", 10);
	bool ret = compare(p1, p2);
	if (ret) {
		cout << "p1 == p2" << endl;
	}
	else {
		cout << "p1 != p2" << endl;
	}
}

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

为了满足函数模板对于自定数据类型(除此之外还有内置数据类型)的使用,提供了模板的重载,如在比较两个类时,可以重载比较的函数。如代码里的compare()函数。 该过程也称为模板的具体化,方法是在重载的函数前加上template<>,即可

 

模板类:

模板类的结构

1.类模板以下面这样的代码开头

template<typename Type>

关键字template告诉编译器,接下来要定义一个模板。Type叫做泛型标识符,泛型标识符也可用其他的字符替代,我们常用T来表示。这个也称为类型参数,意味着它类似于变量,但是赋给他们的只能是类型,而不能是数字、字符等。

当类有两个成员时,可以在开头这样声明:

template<typename T1, typename T2>

模板类的使用方法

  • 类模板没有自动类型推导
  • 类模板在模板参数列表中可以有默认参数

在写了模板类的开头以后,用Type(或者T,看自己选择哪一个),多个成员时T1,T2.....替代类中的参数类型名。而且在开头中可以写类模板的默认参数:

template<class NameType, class AgeType = int>

之后在创建类对象时候,,如果用有参构造函数创建,需要在类名后的<>加入参数的类型名,如Person类,两个参数,一个string类型的m_Name,一个int类型的m_Age:    

Person<string, int> p1("孙悟空", 999);

另外就是无自动类型推导这个注意事项

void test01() {
	//让编译器自动类型推导,会报错
	//Person p("孙悟空", 1000);

	//因此我们显示指定类型
	Person<string, int> p("孙悟空", 1000);
	p.showPerson();
}

类模板对象做函数参数

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

三种方式


1.指定传入的类型:直接显示对象的数据类型                                                                                    如:将函数参数设为:(Person<string ,int> &p)

void printPerson(Person <string, int> &p) {}


2.参数模板化:将这个对象中的参数变为模板进行传递

template <typename T1, typename T2>

void printPerson2(Person<T1, T2> &p){}

3.整个类模板化:将这个对象类型模板化进行传递

template <typename T>
void printPerson3(T &p) 

在我们日常使用时,指定传入类型使用的多

#include<iostream>
using namespace std;

template<class T1,class T2>
class Person {
public:
	Person(T1 name, T2 age) {
		this->m_Age = age;
		this->m_Name = name;
	}

	void showPerson() {
		cout << "name:" << this->m_Name << endl;
		cout << "age:" << this->m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};

//1.指定传入的类型:  直接显示对象的数据类型
//					 也就是用一个函数,函数类型为一个类,然后在函数中
//                   用传入的类,来调用类中的函数。

void printPerson(Person <string, int> &p) {
	p.showPerson();
}

void test01() {
	Person <string, int>p1("张三", 18);
	printPerson(p1);
}

// 2.参数模板化:	  将这个对象中的参数变为模板进行传递(像函数模板)
template <typename T1, typename T2>

void printPerson2(Person<T1, T2> &p) {
	p.showPerson();
	cout << "T1的类型为:" << typeid(T1).name();
	cout << "T2的类型为:" << typeid(T2).name();

}

void test02() {
	Person<string, int> p2("猪八戒", 999);
	printPerson2(p2);
}


// 3.整个类模板化     将这个对象类型模板化进行传递
template <typename T>
void printPerson3(T &p) {
	p.showPerson();
	cout << "T的类型为:" << typeid(T).name();

}

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

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

类模板成员函数类外实现 

在类外实现时,如本例中构造函数:

Person<T1,T2>::Person(int age, string name) {.....}

除作用域本就需要加外,还需加模板参数列表,并写入类有参构造函数的参数类型。

template<typename T1,class T2>
class Person
{
public:
    //构造函数类外实现
	Person(int age, string name);
		/*this->m_Age = age;
		this->m_Name = name;*/

	void showPerson();
		/*cout << "姓名:" << this->m_Name << endl;
		cout << "年龄:" << this->m_Age << endl;*/

	T1 m_Name;
	T2 m_Age;
};


template<typename T1, class T2>
Person<T1,T2>::Person(int age, string name) {
	this->m_Age = age;
	this->m_Name = name;
}

template<typename T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "姓名:" << this->m_Name << endl;
	cout << "年龄:" << this->m_Age << endl; 
}

类模板与友元

全局函数类外声明
1.需要加空模板参数列表
2.如果全局函数 是 类外实现,需要让编译器提前知道这个函数的存在。

//类外实现
//1.多出了4-10这几行代码
//2.需要在声明中加入空模板参数列表
template<class T1, class T2>
class Person;

template<class T1, class T2>
void printPerson2(Person<T1, T2>& p) {
	cout << "姓名 " << p.m_Name << "  年龄 " << p.m_Age << endl;
}


template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age) {
		this->m_Age = age;
		this->m_Name = name;
	}

	//全局函数类内声明
	friend void printPerson( const Person<T1, T2> &p){
		cout << "姓名 " << p.m_Name << "  年龄 " << p.m_Age << endl;
	}
    
    //全局函数类外声明
	friend void printPerson2<>(Person<T1, T2>& p);

	//通过类内的全局函数打印
	//void showPerson() {
	//	cout << "name:" << this->m_Name << endl;
	//	cout << "age:" << this->m_Age << endl;

	//}
private:
	string m_Name;
	int m_Age;
};

类模板分文件编写

类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决方法:直接包含.cpp源文件,而不是包含.h文件。需要让编译器看得到.cpp文件才行,因为.h中类模板中函数只声明,定义的话在调用时候才有,而且我们的.cpp文件中包含着.h文件。也就是将声明和实现写到同一个文件中,并更改后缀.cpp为.hpp,hpp是约定的名称,并不是强制要求。不分开写了,而且该文件后缀用.hpp,用的时候包含.hpp文件

分文件编写:
.h 文件和.cpp文件分开

函数模板:

设定的模板仅且必须作用于紧跟着的下一个函数,因此下一个函数必须参与T的确定

两种使用方式举例:

1.自动类型推导:必须推导出一致的类型T才行。

如该例:c 的类型为 char, 而a、b的类型为int,当比较a、c或者b、c的时候就会出错 

template<typename T>

void myswap(T &a, T &b) {
	T temp = a;
	a = b;
	b = temp;
}
 

void test01() {
	//必须推导出一致的类型T才行
	int a = 10;
	int b = 20;
	char c = 'a';
	//1.自动类型推导
	myswap(a, c);

	//2.显示指定类型
	myswap<int>(a, b);
	cout << a << endl;
	cout << b << endl;
}

2:模板必须确定出T的类型才可以使用。

//模板必须确定出T的类型才可以使用。
template<class T>

void func(){
	cout << "func函数的调用";
}

void test02() {
  	//func();	    //报错,此时虽然没有用T,但是func()仍然是一个函数模板,需确定T的类型

	func<int>();	//显示指定类型后不报错
}

函数模板的调用规则

调用规则:
1.如果普通函数和函数模板都可以实现,优先调用普通函数


2.可以通过空模板参数列表来强制调用函数模板

    即在调用时,显示指定为空

myPrint<>(a, b);


3.函数模板也可以发生重载


4.如果函数模板可以产生更好的匹配,优先调用函数模板

如实例代码中,c1,c2是字符类型,若使用普通函数还需要转换成Ascll码,但使用模板函数不需要转换,因此会调用函数模板

如果提供了函数模板,最好不要提供普通函数,否则容易出现二义性。
使用时,建议使用指定类型的方式,调用函数模板。因为我们自己输入参数时可以确定通用类型T。

#include<iostream>
using namespace std;

//此处普通函数若只有声明,没有实现,在test中,仍然优先调用普通函数
//但是只有声明,会报错。即使报错,编译器也不使用函数模板
//因此只能通过空模板参数列表来调用函数模板
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() {
	int a = 10;
	int b = 20;
	//优先调用普通函数
	myPrint(a, b);

	//空模板参数列表,强制调用函数模板
	myPrint<>(a, b);

	//函数模板也能重载
	int c = 100;
	//myPrint(a, b, c);

	//如果函数模板产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2);	//此时普通函数可隐式类型转换,但用函数模板不用转换,因此用函数模板

}
int main() {
	test01();
}

与普通函数的区别

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



#include<iostream>
using namespace std;

int myAdd01(int a, int b) {
	return a + b;
}

template<typename T>
T myAdd02(T a, T b) {
	return a + b;
}

void test01() {
	int a = 10;
	int b = 20;
	//此处普通函数发生隐式类型转换
	char c = 'c';
	cout << myAdd01(a, c) << endl;      //输出109

	//此时的话,a,c类型不同,而模板函数用自动类型推导必须推导出一致的类型才行
	//cout << mydd02(a, c) << endl;
    cout << myAdd02(a, b) << endl;		//输出30
 

	//在显示指定类型时,指定了类型为double,因此把两个int类型隐式转换成double
	cout << myAdd02<double>(a, b) << endl;		//输出30

	//在显示指定类型时,指定了类型为int,因此把字符类型隐式转换成int
	cout << myAdd02<int>(a, c) << endl;		//输出109
}

int main() {
	test01();
}

模板函数为成员函数

普通类中成员函数 和 类模板中成员函数创建时机是有区别的:
普通类中的成员从一开始就可以创建,类模板中的成员函数在调用时才创建 

#include<iostream>
using namespace std;

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;
    
	//类模板中的成员函数func1()和func2()
	
    //此处不报错,没有二义性
    //此时obj不清楚是Person1的成员,还是Person2的成员,因此它俩的成员函数就没有创建出来
	//但是因为类模板中成员函数在调用时才创建,因此可以编译通过
	void func1() {
		obj.showPerson1();
	}
	void func2() {
		obj.showPerson2();
	}
};

void test01() {
    //此时obj确定了为Person1类型
	Myclass<Person1> obj;
	obj.func1();
	
	//报错此时Myclass里的obj是Person1类的一个对象,不可调用Person2里的成员
	//m.func2();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值