🎁个人主页:工藤新一¹
🔍系列专栏: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);
}
🌟 各位看官好,我是工藤新一¹呀~
🌈 愿各位心中所想,终有所致!