很多粉丝朋友留言表示在程序设计二(面向对象)_实训13_虚函数实验的第一关:设计和使用虚函数 的答案当中,缺少了Linked List部分的答案,现在予以补充:
第1关:设计和使用虚函数
任务描述
建立一个继承体系。List
是基类,ArrayList
和 LinkedList
是派生类。
List
提供 5 个函数,分别是增删查改显。其中,前 4 个是纯虚函数,第 5 个是虚函数。
用户需在 ArrayList
和 LinkedList
中编写实现相应的实现。
注意一条:在 ArrayList
中无需再实现显示函数。
相关知识
虚函数是 C++ 实现动态绑定的关键。所谓动态绑定,如下:
List *pList;
pList = new ArrayList;
pList->insert(pList->getSize()-1,someValue);//ArrayList做尾插入比较快
delete pList;
pList = new LinkedList;
pList->insert(0,someValue);//LinkedList做头插入比较快
delete pList;
从语法上看,pList
只是一个指向 List
类型的指针。但是,在第 2、3 行代码中,pList
实际上指向了一个 ArrayList
对象。此时通过 pList
调用了 insert
成员函数。那么,此时调用的是 List
类的insert
函数还是 ArrayList
类的 insert
函数呢?这取决于是否为虚函数。
如果 insert
成员函数是虚函数,则此时调用的是 ArrayList
的成员函数。如果 insert
成员函数不是虚函数,则此时调用的是 List
的成员函数。
因此,如果将insert
函数设置为虚函数,就可以实现这样一种效果。如同上述代码的第 5、6 行所写。通过 pList
指针,既可以调用 ArrayList
的 insert
函数,也可以调用 LinkedList
的 insert
函数。
这样做有什么好处呢?我们来描述这样一种情形。需求是开发一款使用工业摄像头的图像处理程序。于是购买了 A 公司的摄像头产品,并使用其提供的图形采集函数,编程的代码如下:
A a;//A公司的摄像头对象
a.initialize();//初始化
while(1){ //死循环
a.grab();//采集一帧
/*这里作图像处理*/
}
过了一段时间,因为各种原因,要改用 B 公司的摄像头,当然随B公司也将随产品提供其自身的函数。于是,我们需要修改代码:
B a;//B公司的摄像头对象
a.start();//初始化,为什么B公司的函数名不做成跟A公司一样呢?
while(1){//死循环
a.process();//采集一帧,这里不对B公司的取名抱任何期望
/*这里作图像处理*/
}
你会发现,如果代码中涉及到的具体的摄像头函数越多,当你改用别家产品的时候,你需要修改的代码就越多。这意味着需要花费更多的人力、物力、时间,而且更容易出错。 可以这样做,仍然是先用 A 公司的,但是设计一个 Camera
基类,然后再派生一个类用于表示 A 公司的摄像头:
class Camera{
public:
virtual void init();
virtual void getFrame();
};
class CA : public Camera{
private:
A a;//A公司的摄像头对象
public:
void init(){a.initialize();}
void getFrame(){a.grab();}
}
Camera *pCamera = new CA;
pCamera->init();
while(1){
pCamera->getFrame();
/*这里作图像处理*/
}
现在需要换产品了,怎么办?可以再添加一个CB类:
class CB : public Camera{
private:
B a;//B公司的摄像头对象
public:
void init(){a.start();}
void getFrame(){a.process();}
};
对于我们的主程序,只需要改动一处即可:
//Camera *pCamera = new CA;
//只需改动这一句即可
Camera *pCamera = new CB;
pCamera->init();
while(1){
pCamera->getFrame();
/*这里作图像处理*/
}
后面这种编程的写法,与前一种写法相比,是更容易扩展、更容易升级的。当然要想获得这个好处,必须同时做到 2 点:类的编写者必须提供虚函数,类的使用者必须使用基类的指针或者引用。
因此,当你作为类的编写者编写类的时候,特别是编写继承体系时,必须提供合适的虚函数。当你作为类的使用者使用类的时候,必须优先定义基类的指针或者引用,如果没有特殊理由,绝不使用子类的类型。
另外,注意一点:公有继承的基类的析构函数必须是虚的。
所谓纯虚函数。C++ 允许一个类声明一个纯虚函数,即该虚函数未实现,没有函数体。此时,该类是一个抽象类,它一定是作为基类使用的。纯虚函数的作用,就是为其派生类规定函数的原型。这也被称为接口。C++ 中并没有专门描述接口的语法和关键字,C++ 中使用纯虚函数来完成接口的功能(有关接口的内容可以参考第 3 关)。而Java中则将 interface
作为了一个关键字。
顺便提一句,在一个继承体系中,如果基类的某个函数被 virtual
修饰了,其派生类以及派生类的派生类等等都不需要再显式的写出 virtual
关键字,该函数在其所有派生类中均是虚的。
编程要求
根据提示,在右侧编辑器的Begin-End区域内补充代码。
这是Array.List的答案
#include "ArrayList.h"
/****************start from here**********************/
ArrayList::ArrayList()
{
size = 0;
data = new int[10];
capacity = 10;
}
ArrayList::ArrayList(const ArrayList &rhs)
{
data = new int[rhs.capacity];
size = rhs.size;
for (int i = 0; i < size; i++)
{
data[i] = rhs.data[i];
}
}
ArrayList::ArrayList(int const a[], int n)
{
data = new int[n];
size = n;
for (int i = 0; i < size; i++)
{
data[i] = a[i];
}
}
ArrayList::ArrayList(int n, int value)
{
data = new int[n];
size = n;
for (int i; i < size; i++)
{
data[i] = value;
}
}
ArrayList::~ArrayList()
{
delete[] data;
}
void ArrayList::insert(int pos, int value)
{
if (size == capacity)
{
int *old = data;
data = new int[2 * size];
for (int i = 0; i < size; i++)
{
data[i] = old[i];
}
capacity = 2 * size;
}
for (int i = size - 1; i >= pos; i--)
data[i + 1] = data[i];
data[pos] = value;
size++;
}
void ArrayList::remove(int pos)
{
for (int i = pos; i < size - 1; i++)
data[i] = data[i + 1];
size--;
}
int ArrayList::at(int pos) const
{
return data[pos];
}
void ArrayList::modify(int pos, int newValue)
{
data[pos] = newValue;
}
这是Linked.List的答案:
#include "LinkedList.h"
#include<iostream>
using namespace std;
/****************start from here**********************/
LinkedList::LinkedList() : List(0)
{
head = new Node();
}
LinkedList::LinkedList(const LinkedList &rhs) : List(rhs.size)
{
head = new Node;
Node *p = head;
for (Node *q = rhs.head->next; q; q = q->next)
{
p->next = new Node(q->data, q->next);
p = p->next;
}
}
LinkedList::LinkedList(int const a[], int n) : List(n)
{
head = new Node();
Node *p = head;
for (int i = 0; i < size; i++)
{
p->next = new Node(a[i], nullptr);
p = p->next;
}
}
LinkedList::LinkedList(int n, int value) : List(n)
{
head = new Node();
Node *p = head;
for (int i = 0; i < size; i++)
{
p->next = new Node(value, nullptr);
p = p->next;
}
}
LinkedList::~LinkedList()
{
Node *p = head;
while (head->next != NULL)
{
p = head->next;
delete head;
head = p;
}
delete head;
}
void LinkedList::insert(int pos, int value)
{
Node *p = head;
for (int i = -1; i < pos - 1; i++)
{
p = p->next;
}
Node *q = new Node(value, p->next);
p->next = q;
size++;
}
void LinkedList::remove(int pos)
{
Node *p = head;
for (int i = -1; i < pos - 1; i++)
{
p = p->next;
}
Node *q = head;
for (int j = -1; j < pos + 1; j++)
{
q = q->next;
}
p->next = q;
size--;
}
int LinkedList::at(int pos) const
{
Node *p = head;
for (int i = -1; i < pos; i++)
{
p = p->next;
}
return p->data;
}
void LinkedList::modify(int pos, int newValue)
{
Node *p = head;
for (int i = -1; i < pos; i++)
{
p = p->next;
}
p->data = newValue;
}
void LinkedList::disp(ostream &os) const
{
Node *p = head;
while (p->next != NULL)
{
p = p->next;
cout << p->data << " ";
}
cout << endl;
}