要求:
设计一个数组模板类(MyArray),完成对不同类型元素的管理
操作步骤
设计头文件
在 qtcreate下先创建03_code的项目,然后右键点击03_code添加新文件,点击头文件,点击Choose
![](https://img-blog.csdnimg.cn/img_convert/7c460bf13393c32aede966349746137d.png)
命名为 myarry.hpp,其中 .hpp是头文件和源文件结合的一种,一般头文件 .h 是定义类模板,而.cpp 实现类模板的公共方法操作,但是这种方式存在问题,所以为了方便我们操作,C++可以直接使用 .hpp 实现定义类模板和实现它的公共操作方法
![](https://img-blog.csdnimg.cn/img_convert/04daf9881d7925b4b40b6cec6fe1c8b6.png)
设计模板类
#ifndef MYARRY_HPP
#define MYARRY_HPP
#include <iostream>
#include <string.h>
using namespace std;
template <class T>
class MyArry{
template <typename T1>
friend ostream & operator<<(ostream &out,MyArry<T1> ob);
private:
T *arry; //保存数组地址
int size; //大小
int capacity; //容量
public:
//无参构造
MyArry();
//有参构造
MyArry(int capacity);
//拷贝构造
MyArry(MyArry &ob);
//析构函数
~MyArry();
//重载运算符,完成深拷贝
MyArry & operator=(MyArry &ob);
//输入函数
void pushBack(T elem);
};
#endif // MYARRY_HPP
公共方法实现
无参构造
因为没有参数,所以指针为空,其余参数为 0
//无参构造 成员变量初始化
template<class T>
MyArry<T>::MyArry()
{
arry = NULL;
size = 0;
capacity = 0;
}
有参构造
//有参构造 成员变量赋值
template<class T>
MyArry<T>::MyArry(int capacity)
{
this->capacity = capacity;
arry = new T[this->capacity];
size = 0;
//清空数组
memset(arry,0,sizeof(T)*capacity);
}
拷贝构造
拷贝构造本质是构造函数
在上面的代码中,旧对象给新对象初始化就会调用拷贝构造函数
目的是为了在结束的时候新对象也会调用析构函数释放掉新创建的堆区空间,而不会造成新旧对象前后释放指针变量指向的同一块堆区空间,造成多次释放
//拷贝构造
template<class T>
MyArry<T>::MyArry(MyArry &ob)
{
//ob没有空间,拷贝的空间也没有
if (ob.arry == NULL){
arry = NULL;
size = 0;
capacity = 0;
}
//ob有空间,所以即将拷贝的也有空间
else{
capacity = ob.capacity;
size = ob.size;
//申请堆区数组空间
arry = new T[capacity];
//将旧堆区空间的值复制到新堆区空间
memcpy(arry,ob.arry,sizeof(T)*capacity);
}
}
析构函数
//析构函数,释放堆区空间
template<class T>
MyArry<T>::~MyArry()
{
if(arry != NULL){ //若堆区空间为空就不用析构
delete [] arry;
}
}
重载运算符,完成 “=”深拷贝
返回值是引用类型是因为可以链式操作完成 “=”深拷贝,即 ........ob3 = ob2 = ob1 = ob,返回值依然是一个 MyArry<T> 类型,MyArry<T> 必不可少,因为它模板类名 MyArry 和 T 一起完整的描述了是一个如何的返回值
//深拷贝的时候完成赋值重载运算符 ob1 = ob2
template<class T>
MyArry<T> &MyArry<T>::operator=(MyArry &ob)
{
//判断 this->arry是否存在空间
if(this->arry != NULL){
delete [] arry;
arry = NULL;
}
capacity = ob.capacity;
size = ob.size;
//申请数组堆区空间
arry = new T[capacity];
//将旧堆区空间的值复制到新堆区空间
memset(arry,0,sizeof(T)*capacity);
//将旧堆区空间的值复制到新堆区空间
memcpy(arry,ob.arry,sizeof(T)*capacity);
//链式完成赋值重载运算符,返回左边运算符的引用
return *this;
}
插入函数
//输入函数
template<class T>
void MyArry<T>::pushBack(T elem)
{
if (size == capacity){ //满
//扩展容量
capacity = (capacity == 0?1:2*capacity);
//根据容量申请空间
T *temp = new T[capacity];
//无参构造没有给空间的代价,因为无参构造中 size == capacity
if(arry != NULL){
//将旧空间的内容拷贝到新空间
memcpy(temp,arry,sizeof(T)*size);
//释放旧空间
delete [] arry;
}
arry = temp;
}
arry[size] = elem;
size++;
return;
}
全局输出函数
注意:类模板的T和函数模板的T1不能重名
这里使用全局函数定义是因为 cout<< ob中cout和 类的实例化对象不是同一个对象(也就是cout不是我们自定义的类型),而ob是我们自定义的对象,而cout 是系统自带的,不能像后面的Person 类一样重载 > 运算符定义为成员函数实现(因为 > 两边都是Person在比较)。
//全局函数重载
//注意:类模板的T和函数模板的T1不能重名
template <typename T1>
ostream & operator<<(ostream &out,MyArry<T1> ob){
int i;
for(i = 0; i<ob.size;i++){
out<<ob.arry[i]<<endl;
}
return out;
}
代码实现
MyArry.hpp头文件中的代码实现
#ifndef MYARRY_HPP
#define MYARRY_HPP
#include <iostream>
#include <string.h>
using namespace std;
template <class T>
class MyArry{
template <typename T1>
friend ostream & operator<<(ostream &out,MyArry<T1> ob);
private:
T *arry; //保存数组地址
int size; //大小
int capacity; //容量
public:
//无参构造
MyArry();
//有参构造
MyArry(int capacity);
//拷贝构造
MyArry(MyArry &ob);
//析构函数
~MyArry();
//重载运算符,完成深拷贝
MyArry & operator=(MyArry &ob);
//插入函数
void pushBack(T elem);
};
#endif // MYARRY_HPP
//无参构造 初始化
template<class T>
MyArry<T>::MyArry()
{
arry = NULL;
size = 0;
capacity = 0;
}
//有参构造 成员变量赋值
template<class T>
MyArry<T>::MyArry(int capacity)
{
this->capacity = capacity;
arry = new T[this->capacity];
size = 0;
//清空数组
memset(arry,0,sizeof(T)*capacity);
}
//拷贝构造
template<class T>
MyArry<T>::MyArry(MyArry &ob)
{
//ob没有空间,拷贝的空间也没有
if (ob.arry == NULL){
arry = NULL;
size = 0;
capacity = 0;
}
//ob有空间,所以即将拷贝的也有空间
else{
capacity = ob.capacity;
size = ob.size;
//申请堆区数组空间
arry = new T[capacity];
//将旧堆区空间的值复制到新堆区空间
memcpy(arry,ob.arry,sizeof(T)*capacity);
}
}
//析构函数
template<class T>
MyArry<T>::~MyArry()
{
if(arry != NULL){ //若堆区空间为空就不用析构
delete [] arry;
}
}
//深拷贝的时候完成赋值重载运算符 ob1 = ob2
template<class T>
MyArry<T> &MyArry<T>::operator=(MyArry &ob)
{
//判断 this->arry是否存在空间
if(this->arry != NULL){
delete [] arry;
arry = NULL;
}
capacity = ob.capacity;
size = ob.size;
//申请数组堆区空间
arry = new T[capacity];
//将旧堆区空间的值复制到新堆区空间
memset(arry,0,sizeof(T)*capacity);
//将旧堆区空间的值复制到新堆区空间
memcpy(arry,ob.arry,sizeof(T)*capacity);
//链式完成赋值重载运算符,返回左边运算符的引用
return *this;
}
//输入函数
template<class T>
void MyArry<T>::pushBack(T elem)
{
if (size == capacity){ //满
//扩展容量
capacity = (capacity == 0?1:2*capacity);
//根据容量申请空间
T *temp = new T[capacity];
//无参构造没有给空间的代价,因为无参构造中 size == capacity
if(arry != NULL){
//将旧空间的内容拷贝到新空间
memcpy(temp,arry,sizeof(T)*size);
//释放旧空间
delete [] arry;
}
arry = temp;
}
arry[size] = elem;
size++;
return;
}
//全局函数重载
//注意:类模板的T和函数模板的T1不能重名
template <typename T1>
ostream & operator<<(ostream &out,MyArry<T1> ob){
int i;
for(i = 0; i<ob.size;i++){
out<<ob.arry[i]<<endl;
}
return out;
}
main.cpp中的测试代码实现如下
#include <iostream>
#include "myarry.hpp"
using namespace std;
int main(int argc, char *argv[])
{
MyArry<int> ob(5);
ob.pushBack(10);
ob.pushBack(20);
ob.pushBack(30);
ob.pushBack(40);
ob.pushBack(50);
cout<<ob<<endl;
cout<<"------------------------------------"<<endl;
MyArry<char> ob1(5);
ob1.pushBack('a');
ob1.pushBack('b');
ob1.pushBack('c');
ob1.pushBack('d');
ob1.pushBack('e');
cout<<ob1<<endl;
return 0;
}
代码测试
![](https://img-blog.csdnimg.cn/img_convert/fabafc78dec5d537596734330d770aaa.png)
在main.cpp中测试自定义类型
#include <iostream>
#include "myarry.hpp"
using namespace std;
class Person{
private:
int num;
string name;
int score;
public:
Person(){};
Person(int num,string name,int score){
this->num = num;
this->name = name;
this->score = score;
}
};
int main(int argc, char *argv[])
{
MyArry<int> ob(5);
ob.pushBack(10);
ob.pushBack(20);
ob.pushBack(30);
ob.pushBack(40);
ob.pushBack(50);
cout<<ob<<endl;
cout<<"------------------------------------"<<endl;
MyArry<char> ob1(5);
ob1.pushBack('a');
ob1.pushBack('b');
ob1.pushBack('c');
ob1.pushBack('d');
ob1.pushBack('e');
cout<<ob1<<endl;
cout<<"------------------------------------"<<endl;
MyArry<Person> ob3;
ob3.pushBack(Person(123,"ml",99.9));
ob3.pushBack(Person(456,"ll",100.0));
ob3.pushBack(Person(789,"ll",100.0));
ob3.pushBack(Person(101,"ll",100.0));
ob3.pushBack(Person(111,"ll",100.0));
cout<<ob3S<<endl;
return 0;
}
遍历出错,原因 Person 找不到输出运算符
![](https://img-blog.csdnimg.cn/img_convert/0910bb48199aef68f2d8e018fd1c3b0a.png)
解决方法是让 Person 重载输出运算符,并且要设置该函数为 Person 类的友元函数,便于访问 Person 的私有属性
#include <iostream>
#include "myarry.hpp"
using namespace std;
class Person{
friend ostream & operator<<(ostream &out,Person ob);
private:
int num;
string name;
int score;
public:
Person(){};
Person(int num,string name,int score){
this->num = num;
this->name = name;
this->score = score;
}
};
//设置为 Person 类的友元,便于访问 Person 的私有属性
ostream & operator<<(ostream &out,Person ob){
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
int main(int argc, char *argv[])
{
MyArry<int> ob(5);
ob.pushBack(10);
ob.pushBack(20);
ob.pushBack(30);
ob.pushBack(40);
ob.pushBack(50);
cout<<ob<<endl;
cout<<"------------------------------------"<<endl;
MyArry<char> ob1(5);
ob1.pushBack('a');
ob1.pushBack('b');
ob1.pushBack('c');
ob1.pushBack('d');
ob1.pushBack('e');
cout<<ob1<<endl;
cout<<"------------------------------------"<<endl;
MyArry<Person> ob3;
ob3.pushBack(Person(123,"ml",99.9));
ob3.pushBack(Person(456,"ll",100.0));
ob3.pushBack(Person(789,"ll",100.0));
ob3.pushBack(Person(101,"ll",100.0));
ob3.pushBack(Person(111,"ll",100.0));
cout<<ob3<<endl;
return 0;
}
打印成功
![](https://img-blog.csdnimg.cn/img_convert/01f24c1622667fe4e4dd7a9d3860503e.png)
在MyArry.hpp中添加模板类 MyArry 的公共排序函数如下
函数实现
//冒泡排序
template<class T>
void MyArry<T>::sortArry()
{
if(arry == NULL){
cout<<"容器为空!";
}
else{
int i = 0,j = 0;
for(i = 0;i < size -1;i++){
for(j = 0;j < size - i -1;j++){
if(arry[j] > arry[j+1]){
T temp = arry[j];
arry[j] = arry[j+1];
arry[j+1] = temp;
}
}
}
}
return;
}
代码测试
可以看到对于基本类型的确是能够进行正确的排序
![](https://img-blog.csdnimg.cn/img_convert/fbc65a9a3cebb10f2aa031049a64345f.png)
但是对于前面自定义的复杂类型,却不能进行排序,编译出错
![](https://img-blog.csdnimg.cn/img_convert/02a0c8017d59b17b2c51ea7fc37d9390.png)
这是因为当 ob3 无参构造之后调用排序函数 sortArry 的时候,在排序中不识别 > 运算符,所以只需要对 Person 进行 > 运算符重载
![](https://img-blog.csdnimg.cn/img_convert/2b9bd9fcaef6bdf8cbcd5a7874944052.png)
在Person 类中重载 > 运算符
ob1 > ob2 两个都是Person,所以使用成员函数实现
#include <iostream>
#include "myarry.hpp"
using namespace std;
class Person{
friend ostream & operator<<(ostream &out,Person ob);
private:
int num;
string name;
int score;
public:
Person(){};
Person(int num,string name,int score){
this->num = num;
this->name = name;
this->score = score;
}
//Person类重 > 载运算
bool operator >(const Person ob){
return num > ob.num;
}
};
//设置为 Person 类的友元,便于访问 Person 的私有属性
ostream & operator<<(ostream &out,Person ob){
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
int main(int argc, char *argv[])
{
MyArry<int> ob(5);
ob.pushBack(10);
ob.pushBack(80);
ob.pushBack(30);
ob.pushBack(60);
ob.pushBack(50);
cout<<ob<<endl;
ob.sortArry();
cout<<ob<<endl;
cout<<"------------------------------------"<<endl;
MyArry<char> ob1(5);
ob1.pushBack('a');
ob1.pushBack('g');
ob1.pushBack('c');
ob1.pushBack('m');
ob1.pushBack('b');
cout<<ob1<<endl;
ob1.sortArry();
cout<<ob1<<endl;
cout<<"------------------------------------"<<endl;
MyArry<Person> ob3;
ob3.pushBack(Person(123,"ml",99.9));
ob3.pushBack(Person(456,"hhh",100.0));
ob3.pushBack(Person(789,"kkk",100.0));
ob3.pushBack(Person(101,"bb",100.0));
ob3.pushBack(Person(111,"vvv",100.0));
cout<<ob3<<endl;
ob3.sortArry();
cout<<ob3<<endl;
return 0;
}
代码测试
![](https://img-blog.csdnimg.cn/img_convert/a9f4e0e8c7ab04c0d361d0a559f01e70.png)
总结
模板类定义好之后,对于自定义类型,不要修改模板类的代码,只需要根据需要对自定义类型进行相应的运算符重载即可
运算符重载:就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型,对当前类进行运算符重载,而不修改模板类中的代码
语法:定义重载运算符就像定义函数,只是该函数的名字是 operator@,这里的@代表了被重载的运算符
思路:
-
弄懂运算符的运算对象的个数。(个数决定了重载运算符的个数)
-
识别运算符左边的运算对象,是自定义类的对象还是其他
运算符两边都是类的对象:全局函数实现不推荐(因为还要设置该函数为友元函数,访问类的私有属性),但是可以直接实现为成员函数,因为类的成员函数可以直接访问类的私有属性,并且少一个参数
其他:只能是全局函数实现
实例:
全局函数重载 cout<<ob输出
成员函数重载 Person.num > Person.num