write in advance
简单的两个类,一个基类一个派生类,提供一些member functions, 需要完成member functions定义的书写。
基类中有 char* 变量用 new allocate memory,也有char数组,以及一个int 类型的变量。意图很明显,将两种字符存储方式放置在一起,在处理copy constructor, assignment operator时可以进行比对,加深理解。
派生类中同样是 char* 变量,用new 申请内存,存放字符串。当然, 在派生类的 special member functions 处,也复习了 public derived 的方法。——我们这章学习的内容。
此处不提供test file。一是题目没有要求,二是想用不那么笨的方法测试,可能后续会补充。其实在完成了类的定义后,我们就可以像对待built-in type一样对待我们的class,所以需要测试的是这几个函数。此处,至少在这些练习中,测试接口并没有那么重要。——当然,未经过测试的接口不使用或者不一定正确。
head file
照着书上的写下来。我在类中定义了一个 static const int 类型的变量,作为类内常量。
派生类的初始化函数中少了一项 const char* 类型的变量,我给它补上了。
#ifndef PORT_H_
#define PORT_H_
#include<iostream>
class Port
{
private:
static const int ArrSize = 20;
char* brand;
char style[ArrSize];
int bottles;
public:
Port(const char* br = "none", const char* st = "none", int bo = 0);
Port(const Port&);
virtual ~Port();
Port& operator=(const Port&);
Port& operator+=(int b);
Port& operator-=(int b);
int BottleCount() const { return bottles; }
virtual void show() const;
friend std::ostream& operator<<(std::ostream& os,const Port& rhs);
};
class VintagePort : public Port
{
private:
char* nickname;
int year;
public:
VintagePort();
VintagePort(const char* br, const char* sty, int b, const char* nm, int y);
VintagePort(const VintagePort&);
~VintagePort();
VintagePort& operator=(const VintagePort&);
void show() const;
friend std::ostream& operator<<(std::ostream& os, const VintagePort&);
};
#endif
method file
基类的定义没什么好说的,都是很基本的操作。
派生类的copy constructor 调用基类的 copy constructor完成基类部分的初始化,派生类的operator=在确定不是自我赋值后采用同样的方法。虽然两者所做的事情很类似,但 copy constructor 用于初始化, operator= 用于赋值,且 copy constructor 可以使用memberlist initialization,因为它是 constructor。
#include"Port.h"
Port::Port(const char* br, const char* sty, int bot)
: bottles(bot)
{
brand = new char [strlen(br) + 1];
strcpy(brand,br);
strncpy(style,sty,ArrSize);
style[ArrSize - 1] = '\0';
}
Port::Port(const Port& rhs)
: bottles(rhs.bottles)
{
delete[] brand;
brand = new char[strlen(rhs.brand) + 1];
strcpy(brand,rhs.brand);
strncpy(style,rhs.style, ArrSize);
style[ArrSize - 1] = '\0';
}
Port::~Port()
{
delete[] brand;
}
Port& Port::operator=(const Port& rhs)
{
if (&rhs == this)
return *this;
delete[] brand;
brand = new char[strlen(rhs.brand) + 1];
strcpy(brand,rhs.brand);
strcpy(style, rhs.style); // assume that rhs.style is
bottles = rhs.bottles; // properly set, which is guaranteed
return *this; // by copy constructor, default constructor
}
Port& Port::operator+=(int b)
{
bottles += b;
return *this;
}
Port& Port::operator-=(int b)
{
if (bottles > b)
bottles -= b;
else
std::cout << "bottoles no way to be negative\n";
return *this;
}
void Port::show() const
{
std::cout << "Brand: " << brand ;
std::cout << "\nKind: " << style;
std::cout << "\nBottles: " << bottles;
return;
}
std::ostream& operator<<(std::ostream& os, const Port& rhs)
{
os << rhs.brand << ", " << rhs.style << ", "
<< rhs.bottles << " ";
return os;
}
VintagePort::VintagePort()
:Port(),year(2024)
{
nickname = new char[strlen("C++") + 1];
strcpy(nickname,"C++");
}
VintagePort::VintagePort(const char* br, const char* sty,
int b, const char* nm, int y)
: Port(br,sty,b), year(y)
{
nickname = new char[strlen(nm) + 1];
strcpy(nickname,nm);
}
VintagePort::VintagePort(const VintagePort& rhs)
: Port(rhs),year(rhs.year)
{
nickname = new char[strlen(rhs.nickname) + 1];
strcpy(nickname,rhs.nickname);
}
VintagePort::~VintagePort()
{
delete[] nickname;
}
VintagePort& VintagePort::operator=(const VintagePort& rhs)
{
if (&rhs == this)
return *this;
Port::operator=(rhs);
delete[] nickname;
nickname = new char[strlen(rhs.nickname) + 1];
strcpy(nickname, rhs.nickname);
return *this;
}
void VintagePort::show() const
{
Port::show();
std::cout << "Nikename: " << nickname;
std::cout << "\nyear: " << year;
return;
}
std::ostream& operator<<(std::ostream& os, const VintagePort& rhs)
{
os << (const Port&)rhs;
os << rhs.nickname << " " << rhs.year << " ";
return os;
}
这里有一个亮点,在 operator=() 函数中,我使用了 strcpy(), 而不是更安全的strncpy(),因为我的copy constructor,constructor 保证了该类型的变量都被正确地初始化,所以在赋值情况下,等式两边的数组都应该被正确地设置。
最后的 operator<< 函数中,使用了显示转换类型,将 派生类的rhs转换为基类,完成基类内容的输出。在对基类完成操作后,再输出派生类的内容。
questions related to the exercise
一些方法重定义而一些没有:重定义的方法因为类的派生需要对派生类的数据进行操作,自然与基类的方法不同,自然需要声明为 virtual ,重定义。但类中可能有一些基类和派生类都需要的,不变的部分,比如此处的 BottoleCount(),在派生类中我们并不改变 bottle 的数量,也没有访问权限,这个方法在基类、派生类中都能满足我们的需要,自然不需要重定义。
operator= 与 operator<< 不是虚函数: 虚函数是指函数的参数列表,函数名都相同的,在派生类和基类中实现不相同任务的方法。operator= , operator<< 的参数列表就不相同,它们是函数重载 function overloading, 不是虚函数。
summary
现在,你可以很容易地看到,在派生类的方法中,它往往只处理派生类自己的那一部分变量。对于基类的部分,我们总有办法处理。
如果是copy constrcutor 或者 constructor,我们可以直接将 派生类的引用 作为参数传递给基类的方法,完成基类的操作。member initialization list很常用,效率很高。
如果是 operator=, 或是特定的 virtual function,我们在派生类的函数内部调用基类的方法,基类的方法负责基类部分的变量。
而对于友元函数,我们选择将参数类型显示转化 (explicit cast)为基类。
所以,如你所见,因为类的设计,派生类没有基类private部分的访问权限,只能通过基类的public或者protected方法实现对那部分变量的操作。这些方法,就是 接口。理解 接口 的概念与类 设计思想还需要更多的阅读与练习。加油吧。