write in advance
主要考察ABC(abstract basic type) 的应用。
at least a pure virutal function existing in the class makes a class abstract base class.
abstract basic class is a special kind of class, it provides the common charactericstrcs that its derived classes would need. a ABC cannot be instantiated, which means that u cannot have a ABC object. However, having a reference whose type is ABC is valid, which is a similar situation when it comes to pointers. that is, i can have a code fragment like this.
// here ,basebase is a ABC, and baseDMA derived from it
basebase* ptr;
baseDMA obj1;
basebase& rfb = obj1;
ptr = &obj1;
the reason why code like this works is that upcasting is working here.
upcasting is a mechanism that a base class reference or pointer can refer to or point to derived class object without explicit type cast. it is used commonly in the condition that there is a inheritance. you can see them in my method file, test file and many other places.
注意 ABC 无法实例化为对象。我们可以不为它提供函数的定义,如果你和我一样坚持提供,也是允许的。
此外,此处我设置了两级继承关系。 baseDMA 继承自 basebase,lacksDMA 与 hasDMA继承自 baseDMA
head file
#ifndef DMA_H_
#define DMA_H_
#include<iostream>
class basebase
{
private:
int rating;
public:
basebase(int r = 0);
basebase(const basebase&);
basebase& operator=(const basebase&);
virtual ~basebase() = 0;
friend std::ostream& operator<<(std::ostream&, const basebase&);
virtual void show() const;
};
class baseDMA : public basebase
{
private:
char* label;
public:
baseDMA(const char*label = "NULL",int r = 0);
baseDMA(const baseDMA&);
virtual ~baseDMA();
baseDMA& operator=(const baseDMA&);
friend std::ostream& operator<<(std::ostream& os,const baseDMA& rs);
virtual void show() const;
};
class lacksDMA : public baseDMA
{
private:
enum {COL_LEN = 40};
char color[COL_LEN];
public:
lacksDMA(const char* c = "black", const char* label = "NULL", int r = 0);
lacksDMA(const char* c, const baseDMA& );
friend std::ostream& operator<<(std::ostream&, const lacksDMA&);
void show() const;
};
class hasDMA :public baseDMA
{
private:
char* style;
public:
hasDMA(const char* s = "none", const char* label = "null", int r = 0);
hasDMA(const char*s, const baseDMA&);
hasDMA(const hasDMA&);
~hasDMA();
hasDMA& operator=(const hasDMA& rhs);
friend std::ostream& operator<<(std::ostream&, const hasDMA& rhs);
void show() const;
};
#endif
method file
#include"DMA.H"
basebase::basebase(int r)
{
rating = r;
}
basebase::~basebase()
{ }
basebase::basebase(const basebase& rhs)
{
rating = rhs.rating;
}
basebase& basebase::operator=(const basebase& rhs)
{
if ( &rhs == this)
return *this;
rating = rhs.rating;
return *this;
}
std::ostream& operator<<(std::ostream& os, const basebase& rhs)
{
os << rhs.rating << " ";
return os;
}
baseDMA::baseDMA(const char* lab, int r)
: basebase(r)
{
label = new char[strlen(lab) + 1];
strcpy(label,lab);
}
baseDMA::baseDMA(const baseDMA& rhs)
: basebase(rhs)
{
label = new char[strlen(rhs.label) + 1];
strcpy(label,rhs.label);
}
baseDMA::~baseDMA()
{
delete[] label;
}
baseDMA& baseDMA::operator=(const baseDMA& rhs)
{
if (&rhs == this)
return *this;
basebase::operator=(rhs); // invoke the base class
delete[] label;
label = new char[strlen(rhs.label) + 1];
strcpy(label,rhs.label);
return *this;
}
std::ostream& operator<<(std::ostream& os, const baseDMA& rhs)
{
rhs.basebase::show(); // attention
os << rhs.label << " ";
return os;
}
lacksDMA::lacksDMA(const char* colo, const char* label, int r)
:baseDMA(label,r)
{
strncpy(color,colo,COL_LEN);
color[COL_LEN - 1] = '\0';
}
lacksDMA::lacksDMA(const char* colo, const baseDMA& rhs)
: baseDMA(rhs)
{
strncpy(color, colo, COL_LEN);
color[COL_LEN - 1] = '\0';
}
std::ostream& operator<<(std::ostream& os, const lacksDMA& rhs)
{
os << baseDMA(rhs) ;
os << rhs.color << " ";
return os;
}
hasDMA::hasDMA(const char* s, const char* label, int r)
: baseDMA(label,r)
{
style = new char[strlen(s) + 1];
strcpy(style, s);
}
hasDMA::hasDMA(const char* s, const baseDMA& rhs)
: baseDMA(rhs)
{
style = new char[strlen(s) + 1];
strcpy(style,s);
}
hasDMA::hasDMA(const hasDMA& rhs)
: baseDMA(rhs)
{
style = new char[strlen(rhs.style) + 1];
strcpy(style,rhs.style);
}
hasDMA::~hasDMA()
{
delete[] style;
}
hasDMA& hasDMA::operator=(const hasDMA& rhs)
{
if (&rhs == this)
return *this;
baseDMA::operator=(rhs);
delete[] style;
style = new char[strlen(rhs.style) + 1];
strcpy(style, rhs.style);
return *this;
}
std::ostream& operator<<(std::ostream& os, const hasDMA& rhs)
{
os << baseDMA(rhs);
os << rhs.style << " ";
return os;
}
void basebase::show() const
{
std::cout << rating << " ";
return;
}
void baseDMA::show() const
{
basebase::show();
std::cout << label << " ";
return;
}
void lacksDMA::show() const
{
baseDMA::show();
std::cout << color << " ";
}
void hasDMA::show() const
{
baseDMA::show();
std::cout << style << " ";
}
test file
此处仿照书上的做法, 使用了一个数据类型全为base class 的数组,并在for循环中逐个初始化数组中的指针变量,此处数组中的变量类型为baseDMA,其实也可以为basebase(ABC类)。只要指针的类型为基类即可。ABC只是限制了不能实例化该类型的对象,并没有限制该类型指针,引用的使用。
注意到我初始化数组中对应指针变量的内容时使用了new 加上对应的类型。你可能很熟悉built-in type类型变量使用 new 申请内存,并初始化,而user-defined type实际上也遵循同样的语法规则。
此处我有一个while循环,while循环的测试语句写得有点笨重,循环退出条件为 输入的数值为 1或2或3。我需要保证每个变量都被正确地初始化,否则在我第二个 for 循环处,输出未初始化的指针所指向的内容,它的行为是未定义的,undefined。我们应该避免这样的情况。
目前而言,类的定义和使用都是正确的。注意到我用了switch,多分支选择。
#include"DMA.h"
#include<iostream>
const int ArrSize = 4;
int main()
{
basebase* haha[3];
lacksDMA obj1;
haha[0] = &obj1;
basebase& rhs = obj1;
baseDMA* ptr[ArrSize];
const char* ptc[ArrSize] = {"C++","hello","world","bye"};
const char* ptco[ArrSize] = {"red","yellow","green","purple"};
const char* ptsty[ArrSize] {"guitar","car","clothes","book"};
const int num[ArrSize] = { 2,4,5,6 };
int select = -1;
for (int i = 0; i < ArrSize; i++)
{
std::cin >> select;
while (select != 1 && select != 2 && select != 3)
{
std::cout << "enter the right number\n";
std::cin >> select;
}
//std::cout << "great, enter the right number\n";
switch (select)
{
case 1: ptr[i] = new baseDMA(ptc[i],num[i]);
break;
case 2: ptr[i] = new lacksDMA(ptco[i],ptc[i],num[i]);
break;
case 3: ptr[i] = new hasDMA(ptsty[i],ptc[i],num[i]);
break;
}
std::cout << "good job, next one\n";
}
for (int i = 0; i < ArrSize; i++)
{
std::cout << std::endl;
(*(ptr+i))->show(); //ptr[i]->show();
}
return 0;
}
summary
代码复用很有必要。如此简单的类的示例,我写了很长时间,大部分时间都放在了没什么区别的重复部分。开始感受到继承的重要性了。
有一段时间没有写关于类的习题,在写友元函数发生了点小插曲。是友元函数和成员函数不一样,当我们提供友元函数的定义时,它不需要也不能有类解析符,因为友元是类的朋友,不是类的部分。
另一件丢脸的事情是,我增加了虚函数show(),用于展示对象的内容。在调试的过程中,发现无论我如何修改,输出都未发生变化。正当我惶恐奇怪之际,发现 test file中负责输出的函数并非是 show() 函数。——写练习时,至少得保持头脑清醒。至少应该知道自己改的部分,和测试的部分。
在test file中,我并没有手动逐个测试三个类的 copy constructor, assignment operator。一是暂时没想到很有意思的测试代码,又不想写很笨的代码,二是因为我反复核对这些函数的具体实现,相信自己已经正确定义。如果你看到这里,你有兴趣,你可以自己试试。
此外,test file中的 指针数组也值得注意。自己习惯了数值中的变量类型都是内置类型,当数组元素变量为指针时,出现了一会迷糊。
明天我把下一题也写了。然后推进到 multiple inheritance。