C++另一种编程思想称为 泛型编程,主要利用的技术就是模板
目录
C++另一种编程思想称为 泛型编程,主要利用的技术就是模板
一、概念
二、函数模板
1、语法与使用:
2、函数模板注意事项
3、普通函数与函数模板的区别
4、普通函数与函数模板的调用规则
5、模板的局限性
三、类模板
作用:建立一个通用类,类中的成员属性的数据类型可以不具体指定,用一个虚拟的类型来表示
1、语法与使用
2、类模板与函数模板的区别
3、类模板中成员函数创建时机
4、类模板对象作函数参数
三种方式:
①指定传入的类型: 直接显示对象的数据类型(主要用的类型)
②参数模板化: 将对象中的参数变为模板进行传递
③整个类 模板化: 将这个对象类型 模板化进行传递
5、类模板与继承
①当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型
②如果不指定,编译器无法给子类分配内存,因为不知道父类中成员的大小
③如果想灵活指出父类中T的类型,子类也需要变成类模板
6、类模板成员函数类外实现
①类模板构造函数类外实现
②类模板成员函数类外实现
7、类模板分文件编写
①直接包含.cpp文件
②将声明与实现写在一个hpp文件中(约定俗成hpp,并不必须)
8、类模板与友元
①全局函数类内实现:直接在类内声明友元即可
②全局函数类外实现:先提取让编译器知道全局函数的存在
9、类模板案例
实现一个通用的数组类,要求:
①可以对内置数据类型以及自定义数据类型进行存储
②将数组中的数据存储到堆区
③构造函数中要传入数组的容量
④提供拷贝构造函数以及operator=,以防止浅拷贝问题
⑤提供尾插法与尾删法对数组中数据进行增加与删除
⑥通过下标的方式访问数组中的元素
⑦获取当前数组中元素的个数以及数组的容量
一、概念
建立通用的模具,提高复用性
如拍一寸照片的模板:
制作PPT的模板
但是注意:模板并不是万能的,如人的1寸照片模板,不能用来拍其他生物的照片
而且,制作PPT时不能直接使用,需要自己加内容啥的
因此:
①模板不能直接使用,只是一个框架,需要自己合理使用
②模板是通用的,但不是万能的
二、函数模板
作用:建立一个通用函数,其返回值类型与形参类型可以不指定,用一个虚拟的类型来代表
1、语法与使用:
template<typename T>
函数声明或定义
template:声明创建模板
typename:表示其后面的符号是一种数据类型,可以用class
T:通用的数据类型,名称可以替换,通常为大写字母T
接下来使用交换函数swap来举例:
首先写出int整型交换函数与float浮点型交换函数
// 整型 交换函数
void swapInt(int& x, int& y)
{
int tmp = y;
y = x;
x = tmp;
}
// 浮点型 交换函数
void swapFloat(float& x, float& y)
{
float tmp = y;
y = x;
x = tmp;
}
测试:
int main()
{
int a = 10;
int b = 20;
swapInt(a, b);
cout << a <<" " << b << endl;
float c = 10.5;
float d = 20.5;
swapFloat(c, d);
cout << c << " " << d << endl;
return 0;
}
可以正常交换并输出
但是,想要交换不同的数据,就必须实现不同的函数,改变返回值类型或者形参类型,但是具体代码又类似
因此,我们使用函数模板:
template<typename T>
void Myswap(T& x, T& y)
{
T tmp = y;
y = x;
x = tmp;
}
调用Myswap函数
int a = 10;
int b = 20;
Myswap(a, b);
cout << a <<" " << b << endl;
float c = 10.5;
float d = 20.5;
Myswap(c, d);
cout << c << " " << d << endl;
而我们在调用函数时,并未告知Myswap函数我们传入的类型是什么,使编译器自动推导出类型并实现
这叫做 自动推导类型
还有个 显式指定类型
int e = 100;
int f = 20;
Myswap<int>(e, f);
cout << e << " " << f << endl;
就是调用函数时,在参数列表前加上<int>,这就相当于告知编译器刚才的类型T等于int
2、函数模板注意事项
①自动推导类型,必须推导出一致的数据类型T,才可以正常使用
以我们上面的交换函数Myswap举例:
template<typename T>
void Myswap1(T& x)
{
T tmp = y;
y = x;
x = tmp;
}
我们写2个不同的数据类型,尝试调用
int a = 10;
float b = 22.4;
Myswap1(a, b);
直接报错
因此不能使用
不过,如果在函数模板里写2个typename,则可以正常调用
template<typename T,typename Y>
void Myswap1(T& x, Y& y)
{
Y tmp = y;
y = x;
x = tmp;
}
2个数据类型
int a = 10;
float b = 22.4;
Myswap1(a, b);
此时可以正常输出,但是输出结果都是int整型
②模板必须要确定T的数据类型,才可以使用
template<typename T>
void test01()
{
cout << "ko" << endl;
}
此时函数内未声明T的数据类型
尝试调用
直接报错
因为未确定T的函数类型
解决:
在函数调用参数列表前加上<int>,即随便指定个数据类型,因为函数中没有使用具体的类型
实例1:
使用函数模板实现选择排序对不同数据类型的数组进行降序排序
首先使用模板实现选择排序
template<typename T>
void Select_sort(T arr[],int len)
{
int index = 0;
for (int i = 0; i < len - 1; i++)
{
index = i;
for (int j = i + 1; j < len; j++)
{ // 我们认定的最大值比数组中某个值小
if (arr[index] < arr[j])
{ // 交换两者下标
index = j;
}
}
if (index != i) // 如果交换了下标(不等于原来的i)
{
MYswap(arr[index], arr[i]);//进行数组中数据的交换
}
}
}
最后一步有数据的交换,我们接着使用模板实现交换函数
template<typename T>
void MYswap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
这里就完成了,不过为了使数组中内容可以呈现在屏幕上,我再额外实现一个打印函数
template<typename T>
void print(T arr, int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
①int类型数组测试
void int_test()
{
// 整型数组测试
int arr[] = { 2,4,6,1,3,26,7,1,2,4,9,0,2,3 };
int len = sizeof(arr) / sizeof(arr[0]);
print(arr, len);
Select_sort(arr, len);
cout << endl;
print(arr, len);
}
②char类型数组测试
void char_test()
{
// 字符数组测试
char arr2[] = "zcnasijda";
int len2 = sizeof(arr2) / sizeof(char);
print(arr2, len2);
Select_sort(arr2, len2);
cout << endl;
print(arr2, len2);
}
成功进行降序排序
3、普通函数与函数模板的区别
①普通函数调用时,可以发生自动类型转换,(隐式类型转换)
②函数模板调用时,如果使用自动类型推导,则不会发生隐式类型转换
③函数模板调用时,如果使用显式指定类型,则可以发生隐式类型转换
首先创建普通函数
// 普通函数
void add01(int x, int y)
{
cout << x + y << endl;
}
调用:
add01(10, 20);
创建字符变量c并调用
char c = 'c';
add01(10, c);
因为c的ASCII码值是99,99+10=109,函数在内部进行隐式类型转换
下面用函数模板实现
template<typename T>
void add02(T x, T y)
{
cout << x + y << endl;
}
使用自动类型推导调用10与字符c
直接报错,因为不能进行隐式类型转换
而我们使用显式指定类型:
可以正常输出,因为进行了隐式类型转换,无论你传入什么数据,都给你转成int,转不成就报错
4、普通函数与函数模板的调用规则
①如果普通函数与函数模板都可以实现,优先调用普通函数
实现一个大致相同的普通函数与函数模板
void print(int x, int y)
{
cout << "普通函数" << endl;
}
template<typename T>
void print(T x, T y)
{
cout << "函数模板" << endl;
}
调用生成
② 在情况①下,通过空模板参数列表,可以强制调用函数模板
即在函数调用的参数列表前加<>
③函数模板也可以发送重载
template<typename T>
void print(T x, T y)
{
cout << "函数模板" << endl;
}
template<typename T>
void print(T x, T y,T z) // 重载
{
cout << "函数模板" << endl;
}
④如果函数模板可以发生更好的匹配,优先调用函数模板
2个char类型的变量,传入普通函数需要进行隐式类型转换,而传入函数模板只需要进行自动类型推导,因此优先调用函数模板
5、模板的局限性
局限性:模板的通用并不万能
举例:写一个判断变量是否相等的模板,以及相等输出相等,否则输出不等的函数
template<typename T>
bool My_compare(T a, T b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
void judge(bool a)
{
if (a)
{
cout << "相等" << endl;
}
else
{
cout << "不等" << endl;
}
}
测试:
void test01()
{
int a = 10;
int b = 10;
bool ret = My_compare(a, b);
judge(ret);
}
很明显,a==b
但是,如果a和b的类型是一个类class
class person
{
public:
person(string name,int age)
{
m_age = age;
m_name = name;
}
int m_age;
string m_name;
};
void test02()
{
person p1("Tim", 22);
person p2("Tim", 22);
bool ret = My_compare(p1, p2);
judge(ret);
}
很明显,p1==p2,但是编译器无法正常运行
因为person是自定义数据类型,编译器不知道咋办
因此,我们使用具体化person的版本实现代码,具体化优先调用
在下面重写一个模板,前面加template<>,后面数据类型写person
template<>bool My_compare(person& p1, person& p2)
{
if (p1.m_age == p2.m_age && p1.m_name == p2.m_name)
{
return true;
}
else
{
return false;
}
}
再次尝试运行
void test02()
{
person p1("Tim", 22);
person p2("Tim", 22);
bool ret = My_compare(p1, p2);
judge(ret);
}
成功,而传入数组判断相等也是同样的问题
总结:
①利用具体化的模板,可以解决自定义类型的通用化
②学习模板不是为了写模板,而是为了在STL能够运用系统提供的模板
三、类模板
作用:建立一个通用类,类中的成员属性的数据类型可以不具体指定,用一个虚拟的类型来表示
1、语法与使用
template<class T>
类
与函数模板基本相同,只要把typename改成class就行,而且二者可以互换,效果相同
例:写一个类模板,其中有属性name与age,不指定类型,调用输出
template<class Name_type,class Age_type >
class person
{
public:
person(Name_type name, Age_type age)
{
m_name = name;
m_age = age;
}
void show()
{
cout << this->m_name << this->m_age << endl;
}
Name_type m_name;
Age_type m_age;
};
测试:
void test01()
{
person<string, int> p1("Joyce", 22);
p1.show();
}
2、类模板与函数模板的区别
①类模板没有自动类型推导使用方式
对于刚才的类模板
template<class Name_type,class Age_type >
class person
{
public:
person(Name_type name, Age_type age)
{
m_name = name;
m_age = age;
}
void show()
{
cout << this->m_name << this->m_age << endl;
}
Name_type m_name;
Age_type m_age;
};
如果我们使用自动类型推导:
直接报错
而只能用显式指定类型
②类模板在模板参数列表中可以有默认参数
我们在模板的参数列表中加入默认参数:
让Age_type直接等于int
void test02()
{
person<string> p2("tatina", 22); // 显式指定类型
p2.show();
}
在调用时,我们不用写其类型也可以正常运行
3、类模板中成员函数创建时机
普通类中:成员函数一开始就可创建
类模板中:成员函数在调用时才创建
例:
首先创建2个类,区分为类1与类2,内部分别创建函数输出数字1与2
class person1
{
public:
void show1()
{
cout << "1" << endl;
}
};
class person2
{
public:
void show2()
{
cout << "2" << endl;
}
};
下面实现类模板
template<class T>
class Myclass
{
public:
T obj;
void m_show1()
{
obj.show1();
}
void m_show2()
{
obj.show2();
}
};
测试:参数列表传person1,调用m_show1函数
void test02()
{
Myclass<person1> m;
m.m_show1();
}
输出1
尝试调用2:
报错
此时就已经说明类模板中成员函数一开始没有创建,只有在调用了,才能确定对象的类型,才能创建成员函数
而我们类型传入person2,运行结果相反
4、类模板对象作函数参数
三种方式:
①指定传入的类型: 直接显示对象的数据类型(日常主要用的类型)
②参数模板化: 将对象中的参数变为模板进行传递
③整个类 模板化: 将这个对象类型 模板化进行传递
创建一个类模板,参数类型为T1与T2
template<class T1,class T2>
class person
{
public:
person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
void showinfo()
{
cout << this->m_name << this->m_age << endl;
}
T1 m_name;
T2 m_age;
};
①指定传入的类型:
void print1(person<string, int>& p)// 直接把下面的类型拿来使用
{
p.showinfo();
}
void test01()
{
person<string, int> p1("joyce", 21);
print1(p1);
}
可见,print1函数参数直接拿p1的数据类型使用,这就是指定传入的类型
②参数模板化:
void test02()
{
person<string, int> p2("tatina", 20);
print2(p2);
}
print2函数
template<class T1, class T2>
void print2(person<T1, T2>& p2)// 参树模板化为T1与T2
{
p2.showinfo();
cout << typeid(T1).name() << endl;// 查看T1的类型
cout << typeid(T2).name() << endl;// 查看T2的类型
}
就是将参数也模板化为T1与T2
同时,如果想查看数据的类型,可以使用typeid().name()函数
③整个类模板化
同样的测试函数
void test03()
{
person<string, int> p3("yomi", 1);
print3(p3);
}
然后是print3,将整个类模板化
template<class T>
void print3(T& p)// 直接用T,让编译器推导出类型
{
p.showinfo();
}
看一下T的类型
5、类模板与继承
注意:
①当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型
②如果不指定,编译器无法给子类分配内存,因为不知道父类中成员的大小
③如果想灵活指出父类中T的类型,子类也需要变成类模板
首先,创建父类base类模板
template<class T>
class base
{
public:
T m_a;
};
①尝试创建子类
class son :public base
{
;
};
直接报错:
需要指定父类中T的类型
这样就可以
不过此时,父类中的T只能是指定的T类型,为了解决这个问题:
②将子类也类模板
template<class T1,class T2>
class son2 :public base<T2>
{
T1 m_a;
};
void test02()
{
son2<string,int> s2; // 显式指定类型
}
string就是T1,int就是T2
而T2就是继承父类base,并指定父类中T的类型为int
接着我们输出T1与T2的类型
6、类模板成员函数类外实现
掌握类模板中的成员函数类外实现
首先,我们写一个常规的person类模板,其中有属性m_name与m_age,类型分别为T1与T2
带有构造函数与void show函数
template<class T1,class T2>
class person
{
public:
person(T1 name,T2 age)
{
this->m_name = name;
this->m_age = age;
}
void show()
{
cout << this->m_name << " " << this->m_age << endl;
}
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;
}
即在构造函数person(T1 name, T2 age)前加上作用域person::以及参数列表<T1,T2>
②类模板成员函数类外实现
template<class T1,class T2>
void person<T1,T2>::show() // 虽未用到T1T2,但由于其是类模板中成员函数,因此仍要写入T1/T2
{
cout << this->m_name << " " << this->m_age << endl;
}
一样的加作用域与参数列表
测试:
void test01()
{
person<string, int> p("joyce",21);
p.show();
}
7、类模板分文件编写
首先,分文件编写类模板时(类模板.h头文件,其中的成员函数在.cpp源文件实现),成员函数创建时机是在调用阶段,导致编写时链接不到
先创建person.h头文件,其中写入person类模板
template<class T1, class T2>
class person
{
public:
person(T1 name, T2 age);
void show();
T1 m_name;
T2 m_age;
};
然后创建person.cpp源文件,其中实现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>::show()
{
cout << this->m_name << " " << this->m_age << endl;
}
到目前为止,代码运行没有任何问题
但是,我们在测试函数中调用构造函数与成员函数
#include"person.h"
void test01()
{
person<string, int> p("joyce",21);
p.show();
}
直接报错 ,因为编译器无法链接类模板成员函数的实现cpp文件
①直接包含.cpp文件
我们将源文件包含的person.h文件改为person.cpp文件,这样编译器直接链接到了cpp文件与h文件
成功实现
②将声明与实现写在一个hpp文件中(约定俗成名为hpp,并非必须)
上面是声明,下面是类外实现
在源文件中包含
成功运行
8、类模板与友元
2种实现:
①全局函数类内实现:直接在类内声明友元即可
②全局函数类外实现:先提前让编译器知道全局函数的存在
首先,写一个person类模板,其中有属性m_age与m_name,以及构造函数person
template<class T1, class T2>
class person
{
public:
person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
private:
T1 m_name;
T2 m_age;
};
①全局函数类内实现
接下来,在其中加上友元的全局函数
// 全局函数 类内实现
friend void print1(person <T1, T2>p)
{
cout << p.m_name << " " << p.m_age << endl;
}
测试:
void test01()
{
person<string,int> p("joyce", 21);
print1(p);
}
②全局函数类外实现
首先在类内写上声明:
然后在类外实现:
template<class T1,class T2>
void print2(person <T1, T2>p) // 因为有T1/T2,因此需要加上类模板
{
cout << p.m_name << " " << p.m_age << endl;
}
测试:
void test02()
{
person<string, int> p("joyce", 21);
print2(p);
}
依然是链接错误,因为类模板内我们写的是普通函数的声明,而下面实现写的是函数模板的实现,二者毫不相干
解决:①我们在声明的函数名后加上<>空参数列表
此时,还未完成,仍然无法使用,紧接着:
②先将类外实现部分移动到类模板的最上方
③然后在类外实现部分的上面加上类模板的声明
这样下来,才能正确运行
9、类模板案例
实现一个通用的数组类,要求:
①可以对内置数据类型以及自定义数据类型进行存储
②将数组中的数据存储到堆区
③构造函数中要传入数组的容量
④提供拷贝构造函数以及operator=,以防止浅拷贝问题
⑤提供尾插法与尾删法对数组中数据进行增加与删除
⑥通过下标的方式访问数组中的元素
⑦获取当前数组中元素的个数以及数组的容量
首先,创建my_array类模板,包含属性p_array存储开辟的数组,m_capacity数组容量,m_size数组当前大小
template<class T>
class my_array
{
public:
// 防止浅拷贝问题的拷贝构造函数与operator=
my_array(int capacity);// 有参构造函数
my_array(const my_array &p);// 拷贝构造函数
my_array& operator=(const my_array& p); // 复制运算符的重载,返回引用才是返回自身
void push_back();// 尾插
void pop_back(); // 尾删
int get_capacity();// 获取容量
int get_size(); // 获取大小
~my_array();// 析构函数
private:
T* p_array; // 存储开辟的数组
int m_capacity;// 数组的容量
int m_size; // 数组当前大小
};
然后开始实现各个函数
①有参构造函数
my_array(int capacity)// 有参构造函数
{
this->m_capacity = capacity;
this->m_size = 0;
this->p_array = new T[this->m_capacity]; // 开辟堆区空间以存储p_array
cout << "有参构造函数" << endl;
}
②拷贝构造函数
my_array(const my_array &p)// 拷贝构造函数
{
this->m_capacity = p.m_capacity;
this->m_size = p.m_size;
//this->p_array = p.p_array;// 带来浅拷贝问题
this->p_array = new T(p.m_capacity);// 根据p的大小重新开辟空间以赋值
// 将p中的数据也拷贝过来
for (int i = 0; i < p.m_size; i++)
{
this->p_array[i] = p.p_array[i];
}
cout << "拷贝构造函数" << endl;
}
③operetor=等号重载
my_array& operator=(const my_array& p) // 赋值运算符的重载,返回引用才是返回自身
{
if (this->p_array != NULL) // 判断是否有属性在堆区,有则释放
{
delete[] this->p_array;
this->p_array = NULL;
this->m_capacity = 0;
this->m_size = 0;
}
// 深拷贝
this->m_capacity = p.m_capacity;
this->m_size = p.m_size;
this->p_array = new T[p.m_capacity]; // 重新开辟空间
for (int i = 0; i < p.m_size; i++) // 数据拷贝
{
this->p_array[i] = p.p_array[i];
}
cout << "operator=" << endl;
return *this;
}
④析构函数
~my_array()// 析构函数
{
if (this->p_array != NULL)
{
delete[] this->p_array;
this->p_array = NULL;
}
cout << "析构函数" << endl;
}
此时,我们先运行一下各个函数
#include"my_array.hpp"
int main()
{
my_array<int> arr1(5); // 有参构造
my_array<int> arr2(arr1);// 拷贝构造
my_array<int> arr3(100);
arr1 = arr3; // 自定义类型=赋值
return0;
}
⑤尾插 数据
首先,判断数组当前元素个数size是否等于容量capacity,如果相等则数组满了,直接return。
否则,直接将传入的数据赋给数组第size个元素,arr[size],因为当前个数为size个,数组下标为0-size-1的元素都有了,下一个为空的就是第size个,因此赋给第size个
void push_back(const T& val)// 尾插
{
// 先判断数组是否满
if (this->m_size == this->m_capacity)
{
return;
/* int new_capacity = m_capacity + 1;
this->p_array = new T(new_capacity);*/
}
this->p_array[this->m_size ] = val; // 尾插数据
this->m_size++; // 当前大小+1
}
⑥尾删 数据
使用户访问不到最后一个元素即可,即 使size-1,这样数组元素就少了一个
void pop_back() // 尾删
{
if (this->m_size == 0)
{
cout << "数组为空!" << endl;
return;
}
this->m_size--;
}
⑦以下标方式访问数组元素
由于my_Array是我们自己创建的数组类型,其中的元素数据类型都是T,因此我们不能直接用[]访问到元素,需要我们重载[]运算符
我们使用[]下标访问元素是为了获取一个数据,所以我们重载就直接返回一个元素的数据就行
T& operator[](int index)
{
return this->p_array[index];
}
返回值类型就是我们数组my_array中的数据类型T,同时保证返回前后是同一个元素我们用&
⑧获取大小与获取容量
直接返回size与capacity即可
int get_capacity() // 获取容量
{
return this->m_capacity;
}
int get_size() // 获取大小
{
return this->m_size;
}
代码完成,接下来
①我们创建数组,并打印输出
template<class T>
void print(T&arr) // 打印函数
{
for (int i = 0; i < 5; i++)
{
cout << arr[i] << " ";
}
}
int main()
{
my_array<int> arr1(5); // 有参构造
for (int i = 0; i < 5; i++)
{
arr1.push_back(i);
}
print(arr1);
return 0;
}
成功输出范围内的数据
② 我们再看一下我们创建数组的容量和大小
cout << "capacity" << arr1.get_capacity() << endl;
cout << "size" << arr1.get_size() << endl;
③我们拷贝arr1构造arr2,并尾删数据,再打印输出
void test01()
{
my_array<int> arr1(5); // 有参构造
for (int i = 0; i < 5; i++)
{
arr1.push_back(i);
}
print(arr1);
cout << "capacity" << arr1.get_capacity() << endl;
cout << "size" << arr1.get_size() << endl;
my_array<int> arr2(arr1);// 拷贝构造
print(arr2);
arr2.pop_back();
print(arr2);
cout << "capacity" << arr2.get_capacity() << endl;
cout << "size" << arr2.get_size() << endl;
}
④我们创建自定义数据类型,然后使用尾插尾删等的函数
先创建自定义数据类型person
class person
{
public:
person() {};
person( string name,int age )
{
this->m_age = age;
this->m_name = name;
}
int m_age;
string m_name;
};
创建打印函数print2(以我们自定义数据类型为准来创建)
void print2(my_array<person>& arr)
{
for (int i = 0; i < arr.get_size(); i++)
{
cout << arr[i].m_name <<arr[i].m_age<<endl;
}
cout << endl;
}
创建数组arr并初始化,先输出每个元素的信息与大小容量
然后尾删2个元素后,再次输出每个元素的信息与大小容量
void test02()
{
// 创建数组并初始化
my_array<person>arr(10);
person a1("joyce", 21);
person a2("tatina", 20);
person a3("knaz", 40);
person a4("nana", 20);
person a5("yomi", 1);
// 尾插到数组中
arr.push_back(a1);
arr.push_back(a2);
arr.push_back(a3);
arr.push_back(a4);
arr.push_back(a5);
// 打印数组中的每个元素的数据与大小容量
print2(arr);
cout << "capacity" << arr.get_capacity() << endl;
cout << "size" << arr.get_size() << endl;
// -----------------------------------------------------
// 尾删2个元素
arr.pop_back();
arr.pop_back();
// 打印数组中的每个元素的数据与大小容量
print2(arr);
cout << "capacity" << arr.get_capacity() << endl;
cout << "size" << arr.get_size() << endl;
}
至此全部完成