C++ 面向对象(类和对象)—— 类模板

在这里插入图片描述

在这里插入图片描述

🎁个人主页:工藤新一¹

🔍系列专栏:C++面向对象(类和对象篇)

​ 🌟心中的天空之城,终会照亮我前方的路

🎉欢迎大家点赞👍评论📝收藏⭐文章

类模板

类模板(Class Template)C++ 中的一种模板机制,用于创建通用类的框架,它允许程序员定义一个类时,类中的某些成员(如数据类型)可以是参数化的类模板 的核心作用是提高代码的复用性和灵活性,同时避免重复编写相似的代码。

一、类模板的语法

类模板 的作用:

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

语法:

语法:template <class T> + 后紧跟 + 类声明或定义
紧跟的这一部分(类声明或定义)就叫做类模板

template ---> 声明创建模板
class---> 表示其后面的符号是一种自定义数据类型
T ---> 通用的数据类型(虚拟数据类型),名称可替换

C++
	template<class T>--->模板参数列表
    类

解释:

template — 声明创建模板

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

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

重点:

  • 数据类型参数化(将数据类型像参数般,传至模板的参数列表中)

类模板的基础实现:

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

template<class NameType, class AgeType> 
class Person
{
public:

	Person(NameType name, AgeType age)
	{
		this->Name = name;
		this->Age = age;
	}

	void shouPerson()
	{
		cout << "Name == " << this->Name
			<< " Age == " << this->Age << endl;
	}

	NameType Name;
	AgeType Age;
};

void test01()
{
	//类型参数化(将数据类型像参数一样传入模板的形参列表)
	// <> -- 模板的参数列表
	Person<string, int> p1("啦啦啦", 18);
	p1.shouPerson();
}
int main()
{
	test01();
	return 0;
}

在这里插入图片描述


总结:类模板函数模板语法相似,在声明模板 template 后加 class ,此类就被称为类模板


二、类模板与函数模板

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

​ 1.类模板中没有自动类型推导的使用

​ 2.类模板在模板参数列表中可以有默认参数


2.1类模板 - 无法自动类型推导

  • 自动类型推导
C++
    void test01()
	{
		Person p1("啦啦啦", 18);
	}

在这里插入图片描述


  • 只能使用 显示指定类型转换
C++
    void test01()
	{
		Person<string, int> p1("啦啦啦", 18);
	}

2.2参数列表中的默认参数

  • 类模板在模板参数列表中可以有默认参数
C++
    
//模板参数列表<>                       默认参数 int
	template<class NameType, class AgeType = int>
	class Person
	{
	public:
		...
	};
	
	void test02()
	{
        Person<string> p2("呦呦呦", 3);
	}

在这里插入图片描述


总结:

  • 类模板中只能使用显示指定类型方式
  • 类模板中的模板列表可以有默认参数

三、类模板中成员函数的创建时机

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

  • 普通类中的成员函数在一开始就可以创建
  • 类模板中的成员函数在调用时才创建(类模板中的成员函数在程序运行时,不会立即被创建,只有当其被调用时,才会进行创建)
C++
    
class Person1
{
public:

	void ShowPerson1()
	{
		cout << "Person1 Show" << endl;
	}
};

class Person2
{
public:

	void ShowPerson2()
	{
		cout << "Person2 Show" << endl;
	}
};

//虚拟类型(通用数据类型) T
template<class T>
class My_Class
{
public:

	//通过 虚拟类型(通用类)T 在模板类 My_Class 中实例化出了 "类对象 obj"
	T obj;

	//类模板中的成员函数
	void func1()
	{
		obj.ShowPerson1();
	}

	void func2()
	{
		obj.ShowPerson2();
	}

	//只要不去调用成员函数,func1() 以及 func2() ,func1()、func2()就不会被创建
	//因为 编译器无法确定被虚拟类型实例化出的对象 obj 到底是什么数据类型 
	//一筹莫展
	//因此 这就导致了类模板中的成员函数在一开始不会被创建(因为函数自身也无法区分其属于什么数据类型),只有被调用时才会被创建
};

void test01()
{
	My_Class<Person1> c1;
	c1.func1();

	c1.obj.ShowPerson1();


	My_Class<Person2> c2;
	c2.func2();

	c2.obj.ShowPerson2();
}

在这里插入图片描述


C++
    
void test01()
{
	My_Class<Person1> c1;
	c1.func1();
	c1.func2();//---> false

	c1.obj.ShowPerson1();
}

在这里插入图片描述


总结:类模板中的成员函数并不是在一开始就创建的,其在被调用时才会被创建


四、类模板对象做函数参数

学习目标:

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

一共有三种传入方式:

  • 指定传入的类型 — 直接显示对象的数据类型(常用)

  • 参数模板化 — 将对象中的参数变为模板进行传递

  • 整个类模板化 — 将这个对象类型 模板化进行传递


示例:

C++
    
//先声明一个类模板
template<class TypeName, class TypeAge>
class Person
{
public:

	Person(TypeName name, TypeAge age)
	{
		this->Name = name;
		this->Age = age;
	}

	void ShowPerson()
	{
		cout << "姓名:" << this->Name
			 << " 年龄:" << this->Age << endl;
	}

	TypeName Name;
	TypeAge Age;
};

4.1指定传入类型

  • 项目开发中常用的方式
C++
    
	//1、指定传入的类型 - 引用的方式传递,直接拿到 p1 的本体
	void printPerson1(Person<string, int>& p)
	{
		p.ShowPerson();
	}
	void test01()
	{
		Person<string, int> p1("啦啦啦", 18);
		printPerson1(p1);
	}

在这里插入图片描述


4.2参数模板化

  • 告诉编译器 函数printPerson2 中的 <TypeName, TypeAge> 是模板中的两个参数
C++
    
void printPerson2(Person<TypeName, TypeAge>& p)
{
	p.ShowPerson();
}

void test02()
{
	Person<string, int> p1("呦呦呦", 3);
	printPerson2(p1);
}

在这里插入图片描述


C++

template<class TypeName, class TypeAge>
void printPerson2(Person<TypeName, TypeAge>& p)
{
	p.ShowPerson();
}

void test02()
{
	Person<string, int> p1("呦呦呦", 3);
	printPerson2(p1);
}

在这里插入图片描述


4.2.1查看参数 T 中的数据类型
  • 利用库函数 typeid 查看虚拟类型 T 推导出的数据类型
C++
	template<class TypeName, class TypeAge>
	void printPerson2(Person<TypeName, TypeAge>& p)
	{	
		p.ShowPerson();
        cout <<  "TypeName 的数据类型为:" << typeid(TypeName).name() << endl;
		cout << "TypeAge 的数据类型为:" << typeid(TypeAge).name() << endl;
	}

在这里插入图片描述


4.3整个类模板化

  • 直接将类作为模板(而非仅仅实例化出的对象)
C++
    
	template<class T>
	void printPerson3(T& p)
	{
		p.ShowPerson();
        cout << "T 的数据类型为:" << typeid(T).name() << endl;
	}

	void test03()
	{ 
		Person<string, int> p("工藤新一", 18);
		printPerson3(p);
	}

在这里插入图片描述


总结:

  • 通过类模板创建的对象,有三种方式向函数中进行传参
  • 使用范围较广泛的一种是:指定传入类型

五、模板与继承

当类模板碰到继承时,需要注意以下几个点:

  • 当子类继承的父类是一个类模模板时,子类声明时要指定出父类中 T 的数据类型
  • 如果不指定,编译器无法为父类分配内存空间
  • 如果想灵活指定出父类中 T 的数据类型,子类也需要为类模板

示例:

C++
    
	//类模板与继承
	template<class T>
	class Base {
	public:
		T b;
	};

	class Son : public Base--->false,必须要知道父类中 T 的类型,才能继承给子类
    {
	public:

	};

在这里插入图片描述


C++

	//类模板与继承
    template<class T>
	class Base {
	public:
		T b;
	};

	class Son : public Base<int> 
	{
	public:

	};

在这里插入图片描述


5.1灵活指定父类中 T 的类型

  • 想要灵活指定父类中 T 的类型,子类也需变成类模板
C++
    
//类模板与继承
template<class T>
class Base {
public:
	T b;
};

template<class T1, class T2>

class Son2 : public Base<T2>
{
public:
	T1 obj;
};

void test()
{
	Son2<int, char> s2;
}

在这里插入图片描述

将数据类型像参数一样进行传递


  • 验证 T 的数据类型
C++
    
template<class T>
class Base {
public:
	T b;
};

template<class T1, class T2>

class Son2 : public Base<T2>
{
public:

	//构造函数 - 编译阶段(实例化对象时,开始被调用)
	Son2()
	{
		cout << "T1 的数据类型为:" << typeid(T1).name() << endl;
		cout << "T2 的数据类型为:" << typeid(T2).name() << endl;
	}
	T1 obj;
};

void test()
{
	Son2<int, char> s2;
}

在这里插入图片描述


总结:如果父类是类模板,那么子类需要指定出父类中 T 的数据类型


六、类模板成员函数的类外实现

学习目标:掌握类模板成员函数的类外实现

C++
    
//类模板成员函数的类外实现
template<class T1, class T2>--->模板参数列表
class Person
{
public:

	Person(T1 name, T2 age);

	void ShowPerson();

	T1 Name;
	T2 Age;
};

6.1构造函数的类外实现

C++

    //类模板 -- 构造函数的类外实现
	template<class T1, class T2>
	Person<T1, T2>::Person(T1 name, T2 age)
	{
		this->Name = name;
		this->Age = age;
	}

C++
	1、类模板参数列表的声明(告诉编译器 Person() 中的 T1、T2 是模板类型)	
		|
	template<class T1, class T2>
	
	2、<T1, T2>告诉编译器这是一个类模板的类外实现
			 |
	Person<T1, T2>::Person(T1 name, T2 age)
	
不然 Person::Person(T1 name, T2 age)--->就和不同的成员函数类外实现没区别了

6.2成员函数的类外实现

C++
    
	template<class T1, class T2>
	void Person<T1, T2>::ShowPerson()
	{
		cout << "姓名:" << this->Name
	 	 	 << " 年龄:" << this->Age << endl;
	}

总结:类模板中的成员函数类外实现时,需要加入模板参数列表


七、类模板分文件编写

学习目标:

  • 掌握类模板成员函数分文件编写时产生的问题,以及解决方式

问题:

  • 类模板中成员函数创建时期是在调用阶段,导致分文件编写时链接不到

解决方法:

  • 法一:直接包含 .cpp 源文件

  • 法二:将声明和实现写到同一个文件中,并更改后缀为 .hpp (hpp 是约定的名称,而不是强制)


Person.h 文件中

C++
    
#pragma once
#include<iostream>
using namespace std;
#include<string>

//分文件的编写问题,以及解决
template<class T1, class T2>
class Person {
public:

	Person(T1 name, T2 age);

	void ShowPerson();

	T1 Name;
	T2 Age;
};

Person.cpp 文件中

C++
    
#include"Person.h"

//模板类 - 成员函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->Name = name;
	this->Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::ShowPerson()
{
	cout << "姓名:" << this->Name
		<< " 年龄:" << this->Age << endl;
}

ClassT.cpp 文件中

C++

#include"Person.h"

void test01()
{
	Person<string, int> p("啦啦啦", 14);
	p.ShowPerson();
}
int main()
{
	test01();
	system("pause");
	system("cls");
	return 0;
}

在这里插入图片描述


7.1方法一:

  • ClassT.cpp 文件中 增添 #include"Person.cpp"

在这里插入图片描述


根据类模板函数创建时机 ----- “类模板中的成员函数在程序运行时,不会立即被创建,只有当其被调用时,才会进行创建”

在只包含 .h 文件时:

C++

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

//分文件的编写问题,以及解决
template<class T1, class T2>
class Person {
public:

	Person(T1 name, T2 age);

	void ShowPerson();

	T1 Name;
	T2 Age;
};

编译器看到了如上代码,但其并不会在 .cpp 文件中生成这两个函数(Person()、ShowPerson()),编译器无法观察 / 产生到两函数的实现代码,并且直到代码运行结束,编译器也不会发现,两个函数的实现代码

这就导致了在链接阶段,无法解析外部命令


7.2方法二:

  • .p 头文件、.cpp 源文件写到一起,将文件后缀名更改为 .hpp 文件(即声明与实现(同床共枕)全在同一文件中),最后在 classT.cpp 文件中包含 #include”.hpp” 文件

八、类模板与友元

学习目标:

  • 掌握类模板配合友元的类内实现、类外实现
  • 友元修饰成员函数**(友元函数)**

全局函数类内实现 — 直接在类内声明友元即可

全局函数类外实现 — 需要提前让编译器知道全局函数的存在(会略显复杂)


示例:

C++

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

//通过全局变量,输出 Person 信息

class Person {

public:

	Person(T1 name, T2 age)
	{
		this->Name = name;
		this->Age = age;
	}

	void ShowPerson();

private:

	T1 Name;
	T2 Age;
};

8.1全局函数类内实现

C++
    
template<class T1, class T2>
class Person {

	//全局函数 类内实现
	friend void PrintPerson(Person<T1, T2> p)--->PrintPerson()是一个全局函数
	{
		cout << "Name: " << p.Name
			<< "  Age: " << p.Age << endl;
	}
    /*
    使用友元类 - friend:修饰 PrintPerson() 函数,使 PrintPerson() 作为 Person 类的好朋友(即,称为 Person 的友元函数),它不是 Person 中的成员函数,但其有资格去访问 Person 类的私有属性
    
    并且,被关键字 friend 修饰的成员函数 PrintPerson() 会变为全局函数,作用域在全局,生命周期直至程序执行结束  
    */

public:

	Person(T1 name, T2 age)
	{
		this->Name = name;
		this->Age = age;
	}

private:

	T1 Name;
	T2 Age;
};

//全局函数 类内实现
void test01()
{
	Person<string, int> p("啦啦啦", 18);
}

8.2friend对成员函数的影响

​ 如上述代码示例中,被关键词(友元)friend 所修饰的成员函数 PrintPerson() 是一个全局函数,同时它也被称为 Person 类的**友元函数**。此时,其不再是 Person 类的成员函数,但它可以访问 Person 类中的私有属性。


8.2.1C++友元全局函数与成员函数的区别

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


8.3全局函数 类外实现

C++
    
template<class T1, class T2>
class Person {

	//全局函数类外实现
	friend void PrintPerson02(Person<T1, T2>);

public:

	Person(T1 name, T2 age)
	{
		this->Name = name;
		this->Age = age;
	}

	void ShowPerson();

private:

	T1 Name;
	T2 Age;
};

//全局函数类外实现 - 无需作用域 Person:: (因为 PrintPerson02() 为全局函数),不需要多此一举加作用域
template<class T1, class T2>
void PrintPerson02(Person<T1, T2> p)
{
	cout << "Name: " << p.Name
		 << "  Age: " << p.Age << endl;
}

void test02()
{
	Person<string, int> p("呦呦呦", 3);
	PrintPerson02(p);
}

调用产生 --- “ 无法解析外部命令 ”

在这里插入图片描述


原因:

C++
   
    class 
    { 
        //因为类内声明的函数 PrintPerson02() 是一个普通(全局)函数
		friend void PrintPerson02(Person<T1, T2>);
    }

C++
    
    //而类外实现的 PrintPerson02 是一个函数模板的实现
	template<class T1, class T2>
	void PrintPerson02(Person<T1, T2> p)
	{
		cout << "Name: " << p.Name << "  Age: " << p.Age << endl;
	}

因此,这就导致了,两个 PrintPerson02 都不是一个东西


解决方法:为**友元函数**,增加一个空模板参数列表 <>

C++
    
	class 
    { 
		friend void PrintPerson02<>(Person<T1, T2>);
    }

在这里插入图片描述


但仍需注意些许细节:如果想要全局函数**(友元函数)在类外实现,还需让编译器得知友元函数**的存在

将全局函数实现在 Person 类之前

在这里插入图片描述


在这里插入图片描述

但仍需注意:函数模板中的 Person 类模板,编译器也无法得知它的存在,因此,也许对类模板提前进行声明

C++
    
    //提前声明 友元函数 PrintPerson02
	template<class T1, class T2>
	void PrintPerson02(Person<T1, T2> p)--->但友元函数的实现中,需要提供类模板 Person
	{
		cout << "Name: " << p.Name << "  Age: " << p.Age << endl;
	}

C++
    
//因此,也需在 友元函数前,声明 类模板 Person
template<class T1, class T2>
class Person;

template<class T1, class T2>
void PrintPerson02(Person<T1, T2> p)
{
	cout << "Name: " << p.Name
		<< "  Age: " << p.Age << endl;
}

template<class T1, class T2>
class Person {

	//全局函数类外实现
	friend void PrintPerson02<>(Person<T1, T2>);

public:

	Person(T1 name, T2 age)
	{
		this->Name = name;
		this->Age = age;
	}

private:

	T1 Name;
	T2 Age;
};


void test02()
{
	Person<string, int> p("呦呦呦", 3);
	PrintPerson02(p);
}

在这里插入图片描述


在这里插入图片描述

🌟 各位看官好我是工藤新一¹呀~

🌈 愿各位心中所想,终有所致!

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值