代码仅供参考
基本功能已实现,代码可自由复制、修改、转载。
课程名称: 面向对象技术(C++)
实验项目: 多态性应用
实验仪器: 计算机
- 实验目的
1. 理解面向对象思想中泛型的概念、重要意义;
2. 正确声明和使用模板类;
- 实验内容
- 仿照std::vector, 以模板类的形式实现一个通用的顺序存储容器 MyVector。
- 仿照std:: list, 以模板类的形式实现一个通用的链式存储容器MyList。
- 以模板类的形式实现栈和队列。
基本要求:
1)体现泛型思想、体现继承和派生;
2)要给出关于设计思想的描述或是对设计过程做出必要说明和解释。
3)编写相应的测试用例来验证或者测试所设计类的基本功能及效率。
- 三。
MyVector 这个类模板包含三个公有成员变量:Capacity、size 和 data。Capacity 表示当前数组的容量大小,size 表示数组中当前元素的数量,data 是指向数组元素的指针。 template<class T> class MyVector{ public: int Capacity; int size=0; T* data; 1.默认构造 MyVector()=default; 2.析构函数,释放数据 ~MyVector(){ delete []data; } 3.构造函数:接受一个int类型的数字n,n为MyVector容量大小 MyVector(int n):Capacity{n},data{new T[n]}{} 4.拷贝构造函数:接受一个 MyVector 类型的对象作为参数,创建一个新的 MyVector 类型的对象,它的数据与参数对象相同。 MyVector(const MyVector &v):size{v.size},Capacity{v.Capacity},data{new T[v.Capacity]}{ for(int i=0;i<size;i++){ data[i]=v.data[i]; } } Node *node=L.head->next; while(GetSize()<L.GetSize()) { insert_back(node->data); node=node->next; } } 5.移动构造函数:接受一个右值引用的MyVector 类型对象作为参数,创建一个新的 MyVector 类型的对象,它的值与参数对象相同,并将参数对象的指针置为 null。 MyVector(const MyVector &&v):size{v.size},Capacity{v.Capacity},data{v.data}{ v.data=nullptr; v.size=0; v.Capacity=0; } 6.添加数据模板函数 :接受一个T的对象t,把t送入此MyVector push(T t){ data[size++]=t; } erase(){ size--; } 7.拷贝赋值运算符:接受一个 MyVector类型的对象作为参数,将当前对象的值设置为参数对象的值。 MyVector & operator=(const MyVector &v){ delete []data; size=v.size; Capacity=v.Capacity; data=new T [Capacity]; for(int i=0;i<size;i++){ data[i]=v.data[i]; } } 8.索引运算符:重载 [] 运算符,接受一个整数类型的索引作为参数,返回当前对象中对应位置的数据。 T& operator[](int index){ if(index<size)return data[index]; else {cout<<"索引越界"<<endl; exit(1);} } 9.输出运算符:重载 << 运算符,用于将 MyVector类型的对象的全部数据输出到标准输出流。 friend std::ostream &operator<<(std::ostream& out,const MyVector &v){ for(int i=0;i<v.size;i++){ out<<v.data[i]<<endl; } return out; } 10.输入运算符:重载>>运算符,用于将 MyVector类型的对象的数据写入。 friend std::istream &operator>>(istream &input,MyVector &v){ int n; T t; cout<<"输入要输入的个数:"<<endl; cin>>n; for(int i=0;i<n;i++){ cout<<"输入第"<<i<<"个数据:"<<endl; cin>>t; v.push(t); } return input; } }; MyList: 这个类模板包含两个个公有成员变量:Capacity、size 和 。Capacity 表示当前数组的容量大小,size 表示数组中当前元素的数量。还有一个结构体Node,用来实现List数据存储 template<class T> class MyList{ public: int size=0; int Capacity=0; typedef struct Node{ T data; Node *next; Node *prev; Node(T d):data(d){} Node(){} }Node; Node *head,*tail,*temp; 1.默认构造 MyList()=default; 2.析构函数:使用循环从头节点开始把每个节点内存释放 ~MyList(){ Node *dt=head->next; while(dt!=tail){ Node *cd=dt; dt=dt->next; delete cd; } delete head; delete tail; } 3.构造函数:接受一个int类型的数字n,n为MyList容量大小 MyList(int n):Capacity{n},head{new Node},tail{new Node}{ temp=head; head->next=tail; tail->prev=head; } int GetSize()const{ return this->size; } 4.拷贝构造函数:接受一个 MyListr 类型的对象作为参数,创建一个新的 MyList 类型的对象,它的数据与参数对象相同。使用循环把数据拷贝到新的MyList对象上 //拷贝构造 MyList(const MyList &L):head{new Node},tail(new Node),Capacity{L.Capacity}{ temp=head; head->next=tail; tail->prev=head; Node *node=L.head->next; while(GetSize()<L.GetSize()) { insert_back(node->data); node=node->next; } } 5.移动构造函数:接受一个右值引用的MyList 类型对象作为参数,创建一个新的 MyList类型的对象,它的值与参数对象相同,并将参数对象的指针head与tail设置为null。 //移动构造 MyList( MyList &&L):size{L.size},Capacity{L.Capacity}{ head=L.head; tail=L.tail; temp=L.temp; L.head=L.tail=L.temp=nullptr; } 6.添加数据模板函数 :接受一个T的对象t,使用尾插法把t送入此MyList void insert_back(T t){ Node *node=new Node(t); tail->prev=node; node->next=tail; node->prev=temp; temp->next=node; temp=temp->next; size++; } 7.添加数据模板函数 :接受一个T的对象t,使用头插法把t送入此MyList void insert_front(T t){ Node *node=new Node(t); node->prev=head; node->next=head->next; head->next->prev=node; head->next=node; size++; } 8.删除数据函数 :从尾部删除一个元素 void pop_back(){ size--; Node *tt=temp; temp->prev->next=tail; tail->prev=temp->prev; temp=tail->prev; delete tt; } 9.删除数据函数 :从头部删除一个元素 void pop_front(){ size--; Node *tt=head->next; head->next->next->prev=head; head->next=head->next->next; delete tt; } 9.查找数据函数 :查找MyList中第n个元素 T GetByOrder(int n){ if(n>size){ cout<<"索引越界"<<endl; exit(1); } Node *ts=head; for(int i=0;i<=n;i++){ ts=ts->next; } return ts->data; } 10.拷贝赋值运算符:接受一个 MyList类型的对象作为参数,将当前对象的值设置为参数对象的值。 MyList& operator=(const MyList &L) { this->size=0; Node *node=L.head->next; while(GetSize()<L.GetSize()) { insert_back(node->data); node=node->next; return *this; }} 11.输出运算符:重载 << 运算符,用于将 MyList类型的对象的全部数据输出到标准输出流。 friend std::ostream &operator <<(ostream &out,const MyList &L){ for(Node *i=L.head->next;i!=L.tail;i=i->next){ cout<<i->data<<endl; } return out; } 12.输入运算符:重载>>运算符,用于将 MyList类型的对象的数据写入。 friend std::istream & operator>>(istream &in,MyList &L){ int n; cout<<"输入要输入数据的个数:"; cin>>n; T t; for(int i=0;i<n;i++){ cout<<"请输入第"<<i<<"个数据:"; cin>>t; L.insert_back(t); } return in; } MyStack: 这个类模板继承MyVector:其中有Capacity、size,*data继承自MyVector 。 template<class T> class MyStack:public MyVector<T>{ public: 1.默认构造 MyStack()=default; 2.构造函数:接受一个int类型的数字n,n为MyStack容量大小 MyStack(int n):MyVector<T>(n){} 3.析构函数:因为Stack没有成员变量,什么都不需要执行,这个会自动执行基类的析构函数 ~MyStack(){ } //拷贝构造 4.拷贝构造函数:接受一个 Mystack 类型的对象作为参数,创建一个新的 MyStack类型的对象,它的数据与参数对象相同。使用循环把数据拷贝到新的MyStack对象上 MyStack(const MyStack &s):MyVector<T>{s.size}{ for(int i=0;i<s.size;i++){ this->data[i]=s.data[i];; this->size++; } } 5.移动构造函数:接受一个右值引用的MyStack 类型对象作为参数,创建一个新的 MyStack类型的对象,它的值与参数对象相同,因为没有成员变量,直接调用父类的移动构造 //移动构造 MyStack(MyStack &&s):MyVector<T>{std::move(s)}{ } 6.删除数据函数 :从尾部删除一个元素 void pop(){ *this->size--; } 7.查询数据函数 :按顺序查询第n个数据 T GetElement(int n){ return this->data[n]; } 8.输出运算符:重载 << 运算符,用于将 MyStack类型的对象的全部数据输出到标准输出流。 friend std::ostream &operator<<(std::ostream& out,const MyStack &v){ cout<<"数据为:"<<endl; for(int i=0;i<v.size;i++){ out<<v.data[i]<<endl; } return out; } 9.输入运算符:重载>>运算符,用于将 MyStack类型的对象的数据写入。 friend std::istream &operator>>(istream &input,MyStack &v){ int n; T t; cout<<"输入要输入的个数:"<<endl; cin>>n; for(int i=0;i<n;i++){ cout<<"输入第"<<i<<"个数据:"<<endl; cin>>t; v.push(t); } return input; } }; Myqueue: 这个类模板继承MyList:其中有Capacity、size,Node继承自MyList template<class T> class Myqueue:public MyList<T>{ public: 1.重新定义基类的Node,方便后续使用 typedef typename MyList<T>::Node Node; 1.默认构造 Myqueue()=default; 2.构造函数:接受一个int类型的数字n,n为Myqueue容量大小 Myqueue(int n):MyList<T>(n){} //拷贝构造 3.拷贝构造函数:接受一个 Myqueue 类型的对象作为参数,创建一个新的 Myqueue类型的对象,它的数据与参数对象相同。使用循环把数据拷贝到新的Myqueue对象上 Myqueue(const Myqueue &q){ this->head=new Node; this->tail=new Node; this->head->next=this->tail; this->tail->prev=this->head; this->temp=this->head; Node * tp=q.head->next; while(tp!=q.tail){ this->insert_back(tp->data); tp=tp->next; } } 5.移动构造函数:接受一个右值引用的Myqueue类型对象作为参数,创建一个新的 Myqueue类型的对象,它的值与参数对象相同,因为没有成员变量,直接调用父类的移动构造 //移动构造 Myqueue(Myqueue &&q):MyList<T>{std::move(q)}{ } 6.添加数据模板函数 :接受一个T的对象t,使用头插法把t送入此Myqueue void enque(T t){ this->insert_back(t); } 7.删除数据函数 :调用基类函数,从头部删除一个元素 void deque(){ this->pop_front(); } 8..查询数据函数 :按顺序查询第n个数据 T GetElement(int n){ Node *no=this->head->next; for(int i=0;i<n;i++){ no=no->next; } return no->data; } 9.输入运算符:重载>>运算符,用于将 Myqueue类型的对象的数据写入 friend std::istream &operator>>(istream &input,Myqueue &v){ int n; T t; cout<<"输入要输入的个数:"<<endl; cin>>n; for(int i=0;i<n;i++){ cout<<"输入第"<<i<<"个数据:"<<endl; cin>>t; v.enque(t); } return input; } 10.输出运算符:重载 << 运算符,用于将 Myqueue类型的对象的全部数据输出到标准输出流。 friend std::ostream& operator<<(ostream &out,Myqueue &q){ cout<<"数据为:"<<endl; Node *t=q.head->next; for(int i=0;i<q.size;i++,t=t->next){ cout<<t->data<<endl; } return out; } };
测试代码
- 实验总结
泛型是面向对象编程中非常重要的概念之一,它提供了一种通用的、与具体类型无关的数据类型定义方式,使得代码的复用性和可扩展性更强。在使用泛型时,不需要事先知道具体数据类型,而是在运行时再确定具体的类型,在处理不同类型的数据时能够更加灵活和方便。
C++ 中的泛型可以使用模板类(template class)实现。模板类可以用来定义一种通用的类模板,可以在该模板的基础上创建出具体的类型。使用模板类需要指定模板参数,这些参数用来代替具体的数据类型,在实例化时确定具体的类型。这样可以避免为每种数据类型编写单独的代码。
通过实现以上数据结构,深入掌握了面向对象思想中泛型概念和模板类的使用方法,提高了我的编程技能和代码复用性。同时,通过对具体数据结构的实现,我深入理解了这些数据结构的内部原理和实现方式。