C++学习笔记之模板

世间的道理,听没听说,知不知道是一回事,如何去做,又是另一回事。

书上书外的道理,如何落到实处,总是难上加难。


C++中主要通过模版来实现泛型思想,其中有两种模版机制函数模版和类模版。

函数模版主要作用是建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

1.函数模版语法

语法:template<typename T> 通用函数实现

template关键字声明函数模版<表明后面是一种数据类型(也可用class) 通常使用大写字母通用数据类型>

cpp 复制代码
#include <iostream>
using namespace std;
template<typename T>//利用模板实现通用的交换函数
void mySwap(T& a, T& b)
{
	T temp=a;
	a = b;
	b = temp;
}
int main()
{
	int a = 10;
	int b = 20;
	mySwap(a,b);//自动类型推导
	cout << "a=" << a <<"," << "b=" << b << endl;
	double c = 0.1;
	double d = 0.2;
	mySwap<double>(c,d);//模板指定类型
	cout << "c=" << c <<"," << "d=" << d << endl;
	system("pause");
	return 0;
}

2.函数模版注意事项

(1)自动类型推导,必须推导出一致的数据类型T(即如果多个函数参数使用模版的通用数据类型,这些参数类型必须一致,才能使用自动类型推导),才可以使用。

(2)模板必须要确定出T的数据类型,才可以使用,例如:对于不需要传参的函数,必须指定数据类型(可以是空模板参数列表)。

3.普通函数和函数模版之间的区别

普通函数调用时可以发生自动类型转换(隐式类型转换)而 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换 ,如果利用显示指定类型的方式(即传入参数数据类型与指定的数据类型不一致时),可以发生隐式类型转换。

调用规则:

(1)如果函数模板和普通函数都可以实现,优先调用普通函数。

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

(3) 函数模板也可以发生重载。

(4)如果函数模板可以产生更好的匹配(例如普通函数需要进行参数类型转换,而函数模版不需要),优先调用函数模板。

4.函数模版局限性

对于一些模版函数如果传入的是自定义类型,函数将无法运行,因此C++为了解决这种问题,提供模板的重载 ,可以为这些特定的类型提供具体化的模板。

cpp 复制代码
#include <iostream>
using namespace std;
template<class T>
bool mycompare(T& a, T& b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}
class Person{
public:
	int M_age;
	string M_name;
	Person(int age, string name)
	{
		this->M_age = age;
		this->M_name = name;
	}
};
template<>bool mycompare(Person &a,Person &b)//具体化,template<>开头,并通过名称来指出类型,优先级高于常规模版

{
	if (a.M_age == b.M_age && a.M_name == b.M_name)
	{
		return true;
	}
	else
	{
		return false;
	}
}
int  main()
{
	Person p1(11, "张三三");
	Person p2(12, "张三三");
	int ret = mycompare(p1, p2);
	if (ret)
	{
		cout << "p1等于p2" << endl;
	}
	else
	{
		cout << "p1不等于p2" << endl;
	}
	system("pause");
	return 0;
}

5.类模版

语法:template<typename T> 通用类

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

(1) 类模板没有自动类型推导的使用方式

(2)类模板在模板参数列表中可以有默认参数

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
template<class nametype,class agetype = int>//agetype默认参数类型为int
class Person
{
public:
	nametype m_name;
	agetype m_age;
	Person(nametype name, agetype age)
	{
		this->m_name = name;
		this->m_age = age;
	}
	void showPerson()
	{
		cout << "年龄为" << this->m_age << "姓名为" << this->m_name << endl;
	}
};

int  main()
{
	Person<string>p1("张三三",16);//无法自动推导必须指明参数模版参数,参数模版列表的第二项使用默认参数
	system("pause");
	return 0;
}

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

(1)普通类中的成员函数一开始就可以创建

(2)类模板中的成员函数在调用时才创建

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
class Person1
{
public:
	void showPerson1()
	{
		cout << "Person1" << endl;
	}
};
class Person2
{
public:
	void showPerson2()
	{
		cout << "Person2" << endl;
	}
};

template<class T>
void test01()
{
	T p1;
	p1.showPerson1();
	//p1.showPerson2();//编译不报错,因为类模板中的成员函数未创建
}


int  main()
{
	test01<Person1>();//运行时报错,因为参数模版参数指定为Person1,无法调用showPerson2
	system("pause");
	return 0;
}

6.类模版对象做函数参数

一共有三种函数参数传入方式:

(1)指定传入的类型 --- 直接显示对象的数据类型

(2)参数模板化 --- 将对象中的参数变为模板进行传递

(3)整个类模板化 --- 将这个对象类型 模板化进行传递

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
template<class nametype, class agetype >//agetype默认参数类型为int
class Person
{
public:
	nametype m_name;
	agetype m_age;
	Person(nametype name, agetype age)
	{
		this->m_name = name;
		this->m_age = age;
	}

};
void showPerson1(Person<string, int> &p)//1.指定传入类型
{
	cout << "年龄为" << p.m_age << " 姓名为" << p.m_name<< endl;
}
void test01()
{
	Person<string, int>p1("张三三", 16);
	showPerson1(p1);
}

template<class T1,class T2>//2.参数模版化,本质上是类模版化和参数模版化的结合
void showPerson2(Person<T1,T2> &p)
{
	cout << "年龄为" << p.m_age << " 姓名为" << p.m_name << endl;
	cout << "T1的数据类型" << typeid(T1).name()<< endl;
	cout << "T2的数据类型" << typeid(T2).name() << endl;
}
void test02()
{
	Person<string, int>p2("王一一", 14);
	showPerson2(p2);
}
template<class T3>//2.整个类模版化,本质上也是类模版化和参数模版化的结合
void showPerson3( T3 p)
{
	cout << "年龄为" << p.m_age << " 姓名为" << p.m_name << endl;
	cout << "T3的数据类型" << typeid(T3).name() << endl;//查看模版数据类型
}
void test03()
{
	Person<string, int>p3("周七七", 18);
	showPerson3(p3);
}

int  main()
{
	test01();
	test02();
	test03();
	Person<string,int>p1("张三三", 16);
	system("pause");
	return 0;
}

7.类模版与继承

当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型 如果不指定,编译器无法给子类分配内存。

如果想灵活指定出父类中T的类型,子类也需变为类模板。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
template<class T>
class Base
{


public:
	Base();
	void showbase();

private:
	T num;
};
template<class T>//成员函数类外实现
void Base<T>::showbase()
{
		cout << "Base" << endl;
}

template<class T>//构造函数类外实现
 Base<T>::Base()
{
		cout <<typeid(T).name() << endl;
}



class Son:public Base<int>//为父类模版指定一个类型
{
};
template<class T1,class T2>
class Son1:public Base<T2>//使用类模版继承类模版
{
public:
	Son1(){
		cout << typeid(T1).name() << endl;
		cout << typeid(T2).name() << endl;
	};
};
void test01()
{
	Son1<int,char> s1;
	s1.showbase();
};
int  main()
{
	test01();
	system("pause");
	return 0;
}

8.类模版的分文件编写

如果将模版的头文件.h和实现文件.cpp分开编写,主文件中只包含.h文件,编译时会报错。类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。

解决方式有两种:(1)直接在主文件中包含.cpp文件(2)将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。

9.类模版和友元

全局函数类内实现 - 直接在类内声明友元即可 全局函数类外实现 - 需要提前让编译器知道全局函数的存在。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
//2、全局函数配合友元 类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
template<class T1, class T2> class Person;
//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
//template<class T1, class T2> void printPerson2(Person<T1, T2> & p);
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
{
//1、全局函数配合友元 类内实现
friend void printPerson(Person<T1, T2> & p)
{
cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
}
//全局函数配合友元 类外实现
friend void printPerson2<>(Person<T1, T2> & p);
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
//1、全局函数在类内实现
void test01()
{
Person <string, int >p("Tom", 20);
printPerson(p);
}
//2、全局函数在类外实现
void test02()
{
Person <string, int >p("Jerry", 30);
printPerson2(p);
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
相关推荐
唐诺1 小时前
几种广泛使用的 C++ 编译器
c++·编译器
南宫生2 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__2 小时前
Web APIs学习 (操作DOM BOM)
学习
冷眼看人间恩怨2 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客2 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin2 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos4 小时前
c++---------数据类型
java·jvm·c++
数据的世界014 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐4 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
十年一梦实验室4 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵