【模板】

  • 除了OOP外,C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板

  • C++提供两种模板机制:函数模板类模板

函数模板

函数模板作用

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

语法

template<typename T>
函数声明或定义

解释

template --- 声明创建模板

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

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

例子

举个例子,我们要写一些交换数据的函数

#include<iostream>
using namespace std; //两个整形交换的函数
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
} //交换浮点型函数
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
} void test01() {
int a = 10;
int b = 20; swapInt(a, b); cout << "a = " << a << endl;
cout << "b = " << b << endl;
} int main() {
test01(); system("pause");
return 0;
}

很简单,但是像上面那样写函数,那交换不同的数据交换就要有对应的函数,很冗余

如果可以先不告诉函数输入参数的类型,用的时候再确定,就可以抽象一个通用的交换函数

这就是模板的用途,于是上面的例子便可以写成:

#include<iostream>
using namespace std; //函数模板
template<typename T> //声明一个模板,后面代码里面用T的时候不要报错,T为通用数据类型
void MySawp(T& a, T& b) {
T temp = a;
a = b;
b = temp;
} void test01() {
int a = 10;
int b = 20; //模板有两种使用方式
//1、自动类型推导数据类型
//MySawp(a, b); //2、显式指定数据类型
MySawp<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
} int main() {
test01(); system("pause");
return 0;
}

总结

  • 函数模板利用关键字 template
  • 使用函数模板有两种方式:自动类型推导、显示指定类型
  • 模板的目的是为了提高复用性,将类型参数化

注意事项

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

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

例子
#include<iostream>
using namespace std; template<class T> //typename可以替换为class
void MySawp(T& a, T& b) {
T temp = a;
a = b;
b = temp;
} //1、自动类型推导,必须推导出一致的数据类型T,才可以使用
void test01() {
int a = 10;
int b = 20;
char c = 'c'; //利用函数模板交换
//两种方式
//1、自动类型推导
MySawp(a, b);//对
//MySawp(a, c);//错,推导不出一致的T类型 cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
// 2、模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()//func写在template声明后面就已经是一个函数模板了
{//不管模板里面用没用T,都必须给T一个数据类型,func才可以被调用
cout << "func 调用" << endl;
} void test02()
{
//func(); //错误,模板不能独立使用,必须确定出T的类型
func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
} int main() {
test01(); system("pause");
return 0;
}

实例:排序函数封装

案例描述

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组int数组进行测试

代码

#include<iostream>
#include<string>
using namespace std; //交换的函数模板
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
} ////1、先写一个选择排序的函数
//void sort(int num[],int len) {
//
// for (int i = 0; i < len; i++) {
// //以第一个元素作为初始最大值
// int max = i;
// //遍历找出最大值(的下标)
// for (int j = i + 1; j < len; j++) {
// if (num[j] > num[max]) {
// max = j;
// }
// }
// //max不等于i,出现新的max值
// //更新最大值
// if (max != i) {
// mySwap(num[max], num[i]);
// }
//
// }
//} template<class T> // 也可以替换成typename
//利用选择排序,进行对数组从大到小的排序
void mySort(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
int max = i; //最大数的下标
for (int j = i + 1; j < len; j++)
{
if (arr[max] < arr[j])
{
max = j;
}
}
if (max != i) //如果最大数的下标不是i,交换两者
{
mySwap(arr[max], arr[i]);
}
}
} //冒泡排序,但是是从小到大
template<class T>
void bubleSort(T arr[], int len) {
T temp;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
} //打印
template<typename T>
void printArray(T arr[], int len) { for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void test01()
{
//测试char数组
char charArr[] = "bdcfeagh";
int num = sizeof(charArr) / sizeof(char);
mySort(charArr, num);
printArray(charArr, num);
} void test02() {
//测试int数组
int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
int num = sizeof(intArr) / sizeof(int);
mySort(intArr, num);
printArray(intArr, num);
} void test03() {
//测试int数组冒泡
int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
int num = sizeof(intArr) / sizeof(int);
bubleSort(intArr, num);
printArray(intArr, num);
} int main() {
test03(); system("pause");
return 0;
}

区别

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

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

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

类模板

类模板作用

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

语法

template<typename T>

解释

template --- 声明创建模板

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

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

例子

#include<iostream>
using namespace std;
#include <string> //给出类中成员属性的通用数据类型,可以直接给个默认值,后面就不用再写了
//Person为类模板,有NameType、AgeType两个通用数据类型
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;
}; void test01()
{
// 指定NameType 为string类型,AgeType 为 int类型
Person<string, int>P1("jk", 999);
P1.showPerson();
} //类模板没有自动类型推导,必须指定数据类型
void test02()
{
// 指定NameType 为string类型,AgeType 为 int类型
Person<string> P1("dk", 9);
P1.showPerson();
} int main() {
test01(); system("pause");
return 0;
}

类模板与函数模板区别

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

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

总结

类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

类模板对象做函数参数

类模板实例化出的对象,作为参数传向函数时,一共有三种传入方式:

  1. 指定传入的类型 --- 直接显示对象的数据类型
  2. 参数模板化 --- 将对象中的参数变为模板进行传递
  3. 整个类模板化 --- 将这个对象类型 模板化进行传递

指定传入的类型

#include<iostream>
using namespace std;
#include <string>
#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("jk", 100);
printPerson1(p);
} int main() {
test01(); system("pause");
return 0;
}

参数模板化

#include<iostream>
using namespace std;
#include <string>
#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;
}; //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("nnd", 90);
printPerson2(p);
} int main() {
test02(); system("pause");
return 0;
}

整个类模板化

#include<iostream>
using namespace std;
#include <string>
#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;
}; //3、整个类模板化
template<class T>
void printPerson3(T& p)
{
cout << "T的类型为: " << typeid(T).name() << endl;
p.showPerson(); }
void test03()
{
Person <string, int >p("sb", 30);
printPerson3(p);
} int main() { test03(); system("pause");
return 0;
}
  • 通过类模板创建的对象,可以有三种方式向函数中进行传参
  • 使用比较广泛是第一种:指定传入的类型

类模板与继承

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

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

例子

#include<iostream>
using namespace std;
#include <string> template<class T>
class Base{
T m;
}; //class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
//简单来说,继承需要用到父类Base,Base是个类模板,那就必须指定Base中的通用数据类型
class Son :public Base<int>{ //必须指定一个类型
}; void test01(){
Son c;
} //类模板继承类模板 ,可以用T2指定父类中的T类型
template<class T1, class T2>
class Son2 :public Base<T2>{
public:
Son2(){
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
T1 obj;
}; void test02(){
//class T1 == int,指定Son2维护的obj为int类型
//class T2 == char,即指定Base中的通用数据类型为char
Son2<int, char> child1;
} int main() {
test01();
test02(); system("pause");
return 0;
}

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

类模板分文件编写(以及类外实现)

单个文件的写法

例子,直接在单个文件中编写代码

#pragma once
#include<iostream>
using namespace std;
#include <string> template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age){
this->m_Name = name;
this->m_Age = age;
} void showPerson(){
cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
} T1 m_Name;
T2 m_Age;
}; void test01() {
Person<string, int>p1("jk", 18);
p1.showPerson(); } int main() {
test01(); system("pause");
return 0;
}
类模板类外实现成员函数
#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 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>p1("jk", 18);
p1.showPerson(); } int main() {
test01(); system("pause");
return 0;
}

注意:加在类外实现的数据类型后面的初始化列表,里面不要再写class

问题

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

分文件的写法

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

第一种解决方式是直接包含.cpp文件,这要直接include整个.cpp文件

错误写法

按照以前的分文件编写思路:

.h文件中要写函数、类的声明

.cpp文件通过include获取声明并实现对应函数

例如,

person.h

#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 m_Name;
T2 m_Age;
};

person.cpp

#include "person.h"

//类外实现
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;
}

主函数

#pragma once
#include<iostream>
using namespace std;
#include <string>
//第一种解决方式:直接包含.cpp文件
#include "person.cpp" void test01() {
Person<string, int>p1("jk", 18);
p1.showPerson();
} int main() {
test01(); system("pause");
return 0;
}

像上面这样分文件编写模板就会遇到问题(不涉及模板就是正确的),原因如下:

如果包含的是.h,那么编译器就只知道person.h中声明的成员函数,而没有person.cpp中的实现,肯定报错,链接不上

正确写法

既然导致错误的原因是编译器没有读到person.cpp中对应的函数实现,那直接让它读到不就完了

因此,一种简单粗暴的方法是:将函数的声明和实现都写在一块,然后在写有主函数的文件中通过include导入

实际上就是将单一文件编写的程序拆分了一下又合起来

person.cpp

#pragma once
#include<iostream>
using namespace std;
#include <string> template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age){
this->m_Name = name;
this->m_Age = age;
} void showPerson(){
cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
} T1 m_Name;
T2 m_Age;
};

主函数

#pragma once
#include<iostream>
using namespace std;
#include <string>
//第一种解决方式:直接包含.cpp文件
#include "person.cpp" void test01() {
Person<string, int>p1("jk", 18);
p1.showPerson(); } int main() {
test01(); system("pause");
return 0;
}

显然,这种写法不够优雅

使用.hpp作为类模板的存放文件

这时候有小可爱就想了,那我把声明和实现都写在.h里面不就优雅了吗?

什么你觉得还不够优雅?那把这样的.h文件改名叫.hpp,以后大家都这样写类模板,够优雅了吧?

(ps:脱裤子放屁)

于是便有了下面的写法,这也是涉及类模板时,常用的分文件编写方式

person.hpp

#include <string>

template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age); void showPerson(); 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;
}

主函数

通过include导入这些实现

#pragma once
#include<iostream>
using namespace std;
#include <string>
//第二种解决方式:将.h和.cpp中内容写到一起,后缀改为.hpp
#include "person.hpp" void test01() {
Person<string, int>p1("jk", 18);
p1.showPerson();
} int main() {
test01(); system("pause");
return 0;
}

类模板与友元

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

全局函数类外实现 - 需要提前让编译器知道全局函数的存在

全局函数类内实现

#pragma once
#include<iostream>
using namespace std;
#include <string> template<class T1,class T2>
class Person {
//全局函数,类内实现
friend void printPerson(Person<T1, T2> &p) {
cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
} public:
Person(T1 name,T2 age){
this->m_Name = name;
this->m_Age = age;
} private:
T1 m_Name;
T2 m_Age;
}; void test01() {
Person<string, int>p1("jk", 18);
printPerson(p1); } int main() {
test01(); system("pause");
return 0;
}

全局函数类外实现

#pragma once
#include<iostream>
using namespace std;
#include <string> //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 {
//全局函数,类外实现
// friend void printPerson2(Person<T1, T2>& p);//记得加“<>”
// 如果类外实现,需要让编译器提前知道该函数的存在
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;
}; ////还不能写在这里,必须写在开头让编译器先看见,要不然报错
//template<class T1, class T2>
//void printPerson2(Person<T1, T2>& p) {
// cout << "类外实现的 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
//} void test02() {
Person<string, int>p2("dk", 16);
printPerson2(p2);
} int main() { test02(); system("pause");
return 0;
}

总结

这里又一次体现了C++作者对于套娃和"万能编译器"的喜爱

忘了傻逼的全局函数类外实现吧(仅限涉及模板时)

就老老实实用全局函数做类内实现就好

类模板案例:实现通用的数组类

案例描述

实现一个通用的数组类,要求如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

实现模式

分文件写法:.hpp+.cpp主函数

那么主要的工作应该都在.hpp中完成,具体功能则在.cpp的主函数中测试

代码

myArray.hpp

先编写整体架构,提供有参构造函数和析构函数

有参构造函数和析构函数
//自定义通用数组类
#pragma once
#include<iostream>
using namespace std;
#include <string> //定义类模板MyArry
template<class T>
class MyArry { public:
//有参构造,传入容量
MyArry(int capacity) {
//cout << "MyArry有参构造" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
} //涉及在堆中开辟空间,要写一下析构函数
//释放内存
~MyArry() {
//cout << "MyArry析构函数" << endl;
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;//防止野指针
}
} private:
T* pAddress;//指针指向堆区开辟的真实数组 int m_Capacity;//数组容量
int m_Size;//数组大小
};
拷贝构造函数和重载运算符

接下来逐步添加功能,上述代码已经实现了:

  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量

接下来要实现:对内置数据类型以及自定义数据类型的数据进行存储

这里要考虑浅拷贝问题,因此可以与第四点(拷贝构造)一块实现

关于浅拷贝问题,可以看看这篇,后续我计划再用一篇博客讨论讨论

言归正传

//自定义通用数组类
#pragma once
#include<iostream>
using namespace std;
#include <string> //定义类模板MyArry
template<class T>
class MyArry { public:
//有参构造
MyArry(int capacity) {...} //防止浅拷贝问题
//拷贝构造
MyArry(const MyArry& arr) {
//cout << "MyArry拷贝构造" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
/*this->pAddress = arr.pAddress;*/ //按传进来的数组大小重新在堆区开辟空间
//深拷贝
this->pAddress = new T[arr.m_Capacity]; //将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
} //重载赋值运算符,防止出现浅拷贝问题
//防止写连等号时(类似这种arr[10] = arr[3])报错,所以返回类型是MyArry&,要对MyArry对象进行操作
MyArry& operator=(const MyArry& arr) {
//cout << "MyArry的operator=" << endl;
//先判断原来堆区是否有数据,有就先释放
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Size = 0;
} //深拷贝
//按传进来的数组的属性初始化新的数组
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[arr.m_Capacity]; //将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;//返回自身
} //释放内存
~MyArry() {...} private:
T* pAddress;//指针指向堆区开辟的真实数组 int m_Capacity;//数组容量
int m_Size;//数组大小
};
尾插法和尾删法CRUD

没什么好说的

//自定义通用数组类
#pragma once
#include<iostream>
using namespace std;
#include <string> //定义类模板MyArry
template<class T>
class MyArry { public:
//有参构造
MyArry(int capacity) {...} //拷贝构造
MyArry(const MyArry& arr) {...} //重载赋值运算符,防止出现浅拷贝问题
MyArry& operator=(const MyArry& arr) {...} //尾插法
//输入是T类型数据,且为了防止被修改,要const修饰
void Push_Back(const T& val) {
//判断容量是否等于大小
if (this->m_Capacity == this->m_Size) {
cout << "容量过大,拷不进来" << endl;
return;
}
//往数组最后一个位置插数据,即维护的this->m_Size
this->pAddress[this->m_Size] = val;
this->m_Size++;//更新数组大小
}
//尾删法
void Pop_Back() {
//让用户访问不到最后一个元素即可,逻辑删除
//判断当前数组是否还有数据
if (this->m_Size == 0) {
cout << "没东西删" << endl;
return;
}
this->m_Size--;//屏蔽调对最后一个数的访问
} //释放内存
~MyArry() {...} private:
T* pAddress;//指针指向堆区开辟的真实数组 int m_Capacity;//数组容量
int m_Size;//数组大小
};
下标访问数组中元素

以及剩下的功能

//自定义通用数组类
#pragma once
#include<iostream>
using namespace std;
#include <string> //定义类模板MyArry
template<class T>
class MyArry { public:
//有参构造
MyArry(int capacity) {...}
//拷贝构造
MyArry(const MyArry& arr) {...} //重载赋值运算符,防止出现浅拷贝问题
MyArry& operator=(const MyArry& arr) {...}
//尾插法
//输入是T类型数据,且为了防止被修改,要const修饰
void Push_Back(const T& val) {...}
//尾删法
void Pop_Back() {...} //通过下标的方式访问数组中的元素
//如果调用完之后还想作为左值存在,即arr[0] = 100
//返回类型应该是T的引用,返回数的本身
T& operator[](int index) { //返回数组中index出的元素
return this->pAddress[index]; } //获取数组容量
int getCapacity()
{
return this->m_Capacity;
} //获取数组大小
int getSize()
{
return this->m_Size;
} //释放内存
~MyArry() {...} private:
T* pAddress;//指针指向堆区开辟的真实数组 int m_Capacity;//数组容量
int m_Size;//数组大小
};
完整代码
//自定义通用数组类
#pragma once
#include<iostream>
using namespace std;
#include <string> template<class T>
class MyArry { public:
//有参构造
MyArry(int capacity) {
//cout << "MyArry有参构造" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
} //防止浅拷贝问题
//拷贝构造
MyArry(const MyArry& arr) {
//cout << "MyArry拷贝构造" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
/*this->pAddress = arr.pAddress;*/ //按传进来的数组大小重新在堆区开辟空间
//深拷贝
this->pAddress = new T[arr.m_Capacity]; //将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
} //重载赋值运算符,防止出现浅拷贝问题
MyArry& operator=(const MyArry& arr) {//防止写连等号时报错,所以返回类型是MyArry&
//cout << "MyArry的operator=" << endl;
//先判断原来堆区是否有数据,有就先释放
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Size = 0;
} //深拷贝
//按传进来的数组的属性初始化新的数组
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[arr.m_Capacity]; //将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;//返回自身
} //尾插法
//输入是T类型数据,且为了防止被修改,要const修饰
void Push_Back(const T& val) {
//判断容量是否等于大小
if (this->m_Capacity == this->m_Size) {
cout << "容量过大,拷不进来" << endl;
return;
}
//往数组最后一个位置插数据,即维护的this->m_Size
this->pAddress[this->m_Size] = val;
this->m_Size++;//更新数组大小
} //尾删法
void Pop_Back() {
//让用户访问不到最后一个元素即可,逻辑删除
//判断当前数组是否还有数据
if (this->m_Size == 0) {
cout << "没东西删" << endl;
return;
}
this->m_Size--;//屏蔽调对最后一个数的访问
} //通过下标的方式访问数组中的元素
//如果调用完之后还想作为左值存在,即arr[0] = 100
//返回类型应该是T的引用,返回数的本身
T& operator[](int index) { //返回数组中index出的元素
return this->pAddress[index]; } //获取数组容量
int getCapacity()
{
return this->m_Capacity;
} //获取数组大小
int getSize()
{
return this->m_Size;
} //涉及在堆中开辟空间,要写一下析构函数
//释放内存
~MyArry() {
//cout << "MyArry析构函数" << endl;
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;//防止野指针
}
} private:
T* pAddress;//指针指向堆区开辟的真实数组 int m_Capacity;//数组容量
int m_Size;//数组大小
};

类模板-通用数组类.cpp

在该类中进行调用测试(自定义类的就不测了,懒)

#include<iostream>
using namespace std;
#include <string>
#include "MyArray.hpp" void printIntArray(MyArry<int>& arr){
for (int i = 0; i < arr.getSize(); i++) {
cout << arr[i] << " ";
}
cout << endl;
} void test01() {
MyArry<int> arr1(5);
/*MyArry<int> arr2(arr1);
MyArry<int> arr3(15);*/
//arr3 = arr1; for (int i = 0; i < 10; i++)
{
arr1.Push_Back(i);//利用尾插法向数组中插数
}
cout << "array1打印输出:" << endl;
printIntArray(arr1);
cout << "array1的大小:" << arr1.getSize() << endl;
cout << "array1的容量:" << arr1.getCapacity() << endl; cout << "--------------------------" << endl; MyArry<int> arr2(arr1);
arr2.Pop_Back();
cout << "array2打印输出:" << endl;
printIntArray(arr2);
cout << "array2的大小:" << arr2.getSize() << endl;
cout << "array2的容量:" << arr2.getCapacity() << endl; } int main() {
test01(); system("pause");
return 0;
}

【C++ 泛型编程01:模板】函数模板与类模板的更多相关文章

  1. C++—模板(2)类模板与其特化

    我们以顺序表为例来说明,普通顺序表的定义如下: typedef int DataType; //typedef char DataType; class SeqList { private : Dat ...

  2. C++ 模板的编译 以及 类模板内部的实例化

    在C++中.编译器在看到模板的定义的时候.并不马上产生代码,仅仅有在看到用到模板时,比方调用了模板函数 或者 定义了类模板的 对象的时候.编译器才产生特定类型的代码. 一般而言,在调用函数的时候,仅仅 ...

  3. 类模板、Stack的类模板实现(自定义链栈方式,自定义数组方式)

    一.类模板 类模板:将类定义中的数据类型参数化 类模板实际上是函数模板的推广,可以用相同的类模板来组建任意类型的对象集合 (一).类模板的定义 template  <类型形参表> clas ...

  4. C++_进阶之函数模板_类模板

     C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...

  5. C++复习:函数模板和类模板

    前言 C++提供了函数模板(function template).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数模板.凡是函数体 ...

  6. C++ 函数模板与类模板(使用 Qt 开发编译环境)

    注意:本文中代码均使用 Qt 开发编译环境,如有疑问和建议欢迎随时留言. 模板是 C++ 支持参数化程序设计的工具,通过它可以实现参数多态性.所谓参数多态性,就是将程序所处理的对象的类型参数化,使得一 ...

  7. [Reprint] C++函数模板与类模板实例解析

    这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下   本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...

  8. C++解析(26):函数模板与类模板

    0.目录 1.函数模板 1.1 函数模板与泛型编程 1.2 多参数函数模板 1.3 函数重载遇上函数模板 2.类模板 2.1 类模板 2.2 多参数类模板与特化 2.3 特化的深度分析 3.小结 1. ...

  9. C++学习之函数模板与类模板

    泛型编程(Generic Programming)是一种编程范式,通过将类型参数化来实现在同一份代码上操作多种数据类型,泛型是一般化并可重复使用的意思.泛型编程最初诞生于C++中,目的是为了实现C++ ...

  10. C++ 模板常见特性(函数模板、类模板)

    背景 C++ 是很强大,有各种特性来提高代码的可重用性,有助于减少开发的代码量和工作量. C++ 提高代码的可重用性主要有两方面: 继承 模板 继承的特性我已在前面篇章写过了,本篇主要是说明「模板」的 ...

随机推荐

  1. Xpath 高级用法

    xpath 高级用法 1. 匹配当前节点下的所有: .// . 表示当前 // 表示当前标签下的所有标签 注: 要配合使用 2. 匹配某标签的属性值: /@属性名称 这里以input里的value值为 ...

  2. 网页状态码(HTTP状态码)。

    网页状态码(HTTP状态码). 状态码 说明 详情 100 继续 请求者应当继续提出请求.服务器已收到请求的一部分,正在等待其余部分. 101 切换协议 请求者已要求服务器切换协议,服务器已确认并准备 ...

  3. 用Nodejs 实现一个简单的 Redis客户端

    目录 0. 写在前面 1. 背景映入 2. 数据库选择 3. Nodejs TCP连接 3. 代码编写 4. 实验 5. wireshark 抓包分析 6. 杂与代码 0. 写在前面 大家如果有去看过 ...

  4. CSS选择器大全48式

    00.CSS选择器 CSS的选择器分类如下图,其中最最常用的就是基础选择器中的三种:元素选择器.类选择器.id选择器.伪类选择器就是元素的不同行为.状态,或逻辑.然后不同的选择器组合,基于不同的组合关 ...

  5. netty系列之: 在netty中使用 tls 协议请求 DNS 服务器

    目录 简介 支持DoT的DNS服务器 搭建支持DoT的netty客户端 TLS的客户端请求 总结 简介 在前面的文章中我们讲过了如何在netty中构造客户端分别使用tcp和udp协议向DNS服务器请求 ...

  6. Django的简单使用

    Django 基础简介 基础简介 1. 软件框架 一个公司是由公司中的各部部门来组成的,每一个部门拥有特定的职能,部门与部门之间通过相互的配合来完成让公司运转起来. 一个软件框架是由其中各个软件模块组 ...

  7. perl中 use strict会出现“requires explicit package name”错误

    转载 perl中use strict的用法 如果你使用 use strict 的话,它会强迫你用 my 声明变量,否则将会报上述错误.

  8. day08 final关键字&面向对象——多态&抽象类、方法&向上、向下转型

    day08 final关键字 最终的不可更改的 特点: 1)修饰类,类不能被继承 2)修饰方法,方法不能被重写 3)修饰成员变量(变为常量),值不能修改,名字大写,声明同时给常量赋值 main方法中 ...

  9. 过压保护芯片,高输入电压(OVP)

    PW2606是一种前端过电压和过电流保护装置.它实现了广泛的输入电压范围从2.5V到40V.过电压阈值可在外部编程或设置为内部默认设置.集成功率路径nFET开关的超低电阻确保了更好的性能电池充电系统应 ...

  10. 记一次 .NET 某工控MES程序 崩溃分析

    一:背景 1.讲故事 前几天有位朋友找到我,说他的程序出现了偶发性崩溃,已经抓到了dump文件,Windows事件日志显示的崩溃点在 clr.dll 中,让我帮忙看下是怎么回事,那到底怎么回事呢? 上 ...