C++ primer plus 第14章 C++中的代码重用

valarray类是头文件valarray支持的,它支持诸如将数组中所有元素的值相加及在数组中找出最大和最小的值的操作。valarray被定义为一个模板类,以便能够处理不同的数据类型。模板特性意味着声明对象时,必须指定具体的数据类型。使用valarray类来声明一个对象时,需要在标识符valarray后面加上一对尖括号,并在其中包含所需的数据类型:

valarray<int> q_values;

在C++11中也可以使用初始化列表。使用公有继承时,类可以继承接口,可能还有实现。使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据。

包含、私有继承和保护继承用于实现has-a关系。

使用公有继承,基类的公有方法将成为派生类的公有方法。派生类将继承基类的接口;这是is-a关系的一部分。使用私有继承,基类的公有方法将成为派生类的私有方法。总之,派生类不继承基类的接口。正如从被包含对象中看到,这种不完全继承是has-a关系的一部分。使用私有继承,类将继承实现。通过术语子对象来表示通过继承或包含添加的对象。私有继承提供的特性与包含相同:获得实现,但不获得接口。

要进行私有继承,要使用关键字private,Student类应从两个类派生而来,因此声明将列出这两个类:

class Student:private std::string,private std::valarray<double>
{
   public:
}

使用多个基类的继承被称为多重继承(MI)。对于继承类,新版本的构造函数将使用成员初始化列表语法,它使用类名而不是成员名来标识构造函数:


//包含和私有继承的主要区别之一
Student(const char *str,const double *pd,int n)
    :std::string(str),ArrayDb(pd,n){}//string类和ArrayDb类——私有继承的构造函数

Student(const char *str,const double *pd,int n)
    :name(str),scores(pd,n){}//包含使用的构造函数
//

//私有继承使得能够使用类名和作用域解析运算符来调用基类的方法:
double Student::Average() const
{
    if(ArrayDb::size()>0)
        return ArrayDb::sum()/ArrayDb::size();
    else
        return 0;
}
//总之,使用包含时将使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法。


//使用作用域解析运算符可以访问基类的方法,但如果要使用基类对象本身,要使用强制类型转换
//访问基类对象
const string & Student::Name() const
{
    return (const string &) *this;//指针this指向用来调用方法的对象
}


//访问基类的友元函数:用类名显式的限定函数名不适合于友元函数,这是因为友元不属于类,然而,可以通过显式的转换为基类来调用正确的函数。
ostream & operator<<(ostream &os,const Student &stu)
{
    os<<"Score for"<<(const String &)stu<<":\n";
}

1.使用using重新定义访问权限

(1)使用保护派生或私有派生时,基类的公有成员将成为保护成员或私有成员。假设要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。

//假设希望Student类能够使用valarray类的sum()方法,可以在Student类的声明中声明一个sum()方法,然后像下面这样定义该方法:
double Student::sum() const
{
    return std::valarray<double>::sum();
}

(2)另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明(就像声明空间那样)来指出派生类可以使用特定的基类成员,即使采用的是私有派生。注意使用using声明只使用成员名——没有圆括号、函数特征和返回类型。

class Student:private std::string,private std::valarray<double>
{
public:
    using std::valarray<double>::min;
    using std::valarray<double>::max;
}

using声明只适用于继承,而不适用于包含。

2.多重继承

MI描述的是有多个直接基类的类。与单继承一样,公有MI表示的也是is-a关系,必须使用关键字public来限定每个基类。私有MI和保护MI可以表示has-a的关系。

MI的两个主要问题:(1)从两个不同的基类继承同名方法;(2)从两个或更多相关基类那里继承同一个类的多个实例;

//假设从Singer和Waiter公有派生出SingingWaiter
class SingingWaiter:public Singer,public Waiter{......};
//Singer和Waiter都继承了一个Worker组件,因此SingingWaiter将包含两个Worker组件
SingingWaiter ed;
Worker *pw=&ed;//二义性
//应使用类型转换来指定对象:
Worker *pw1=(Waiter *) &ed;
Worker *pw2=(Singer *) &ed;

这使得基类指针引来不同的对象(多态性)复杂化。C++引入多重继承的同时,引入了一种新技术——虚基类,使MI成为可能。

3.虚基类

虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。

//通过在类声明中使用关键字virtual,可以使Worker被用作Singer和Waiter的虚基类
class Singer:virtual public Worker{};
class Waiter:public virtual Worker{};//次序无关
//然后可将SingingWaiter类定义为:
class SingingWaiter:public Singer,public Waiter{};//SingingWaiter只包含一个Worker子对象,所以可以使用多态


//Worker是虚基类,则下面这种信息自动传递将不起作用。
SingingWaiter(const Worker &wk,int p=0,int v=Singer::other):Waiter(wk,p),Singer(wk,v){}
//wk从两个不同的路径传递给Worker对象,为避免这种呢冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类。然而,编译器必须在构造派生对象之前构造基类对象组件;在上述情况下,编译器将使用Worker的默认构造函数。
//如果不希望默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数,因此构造函数应该是这样的:
SingingWaiter(const Worker &wk,int p=0,int v=Singer::other):Worker(wk),Waiter(wk,p),Singer(wk,v){}

如果类有间接虚基类,则除非只需使用该基类的默认构造函数,否则必须显式的调用该虚基类的某个构造函数。

使用作用 与解析运算符来澄清编程者的使用哪个方法。

//对于单继承来说,用派生类调用基类的方法是可以的
void Waiter::show() const
{
    Worker::show();
    cout<<"Panache rating:"<<panache<<"\n";
}
void HeadWaiter::show() const
{
    Waiter::show();
    cout<<"Presence rating:"<<presence<<"\n";
}
//这样的递增方式对SingingWaiter无效因为它忽略了Waiter组件
void SingingWaiter::show()
{
    Singer::show();
}
//可以同时调用Waiter()版本的show()来补救
void SingingWaiter::show()
{
    Singer::show();
    Waiter::show();
}
//然而这将显示两次——解决办法:使用模块化方式,而不是递增方式即提供一个只显示Worker组件的方法和一个只显示Waiter组件或Singer组件的方法。然后在SingingWaiter::show()方法中将组件组合起来
void Worker::Data() const
{
    cout<<"Name:"<<fullname<<"\n";
    cout<<"Employee ID:"<<id<<"\n";
}
void Waiter::Data() const
{
    cout<<"Panache rating:"<<panache<<"\n";
}
void Singer::Data() const
{
    cout<<"Vocal range:"<<pv[voice]<<"\n";
}
void SingingWaiter::Data() const
{
    Singer::Data();
    Waiter::Data();
}
void SingingWaiter::show() const
{
    cout<<"Category:singing waiter\n";
    Worker::Data();
    Data();
}
//另一种办法是将所有的数据组件都设置为保护的,而不是私有的,不过使用保护方法将可以更严格的控制对数据的访问。

如果基类是虚基类,派生类将包含基类的一个子对象;如果基类不是虚基类,派生类将包含多个子对象。当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。

派生类中的名称优先于直接或间接祖先类中的相同的名称。

4.类模板

模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数。C++的类模板为生成通用的类声明提供了一种更好的方法。C++库提供了多个类模板。模板不是函数,它们不能单独编译,模板必须与特定的模板实例化请求一起使用。

使用模板类:需要声明一个类型为模板类的对象,方法是使用所需的具体类型替换泛型名。

泛型标识符(Type)——成为类型参数,它们类似于变量,但赋给它们的不能是数字,而只能是类型。必须显式的提供所需的类型

正确的使用指针栈:(1)让调用程序提供一个指针数组,其中每个指针都指向不同的字符串

允许指定数组大小的简单数组模板:(1)在类中使用动态数组和构造函数参数来提供元素数目;(2)使用模板参数来提供常规数组的大小——C++新增的模板array。

模板嵌套:

template<typename T>
    template<typename V>

模板类声明也可以有友元。模板的友元分3类:(1)非模板友元;(2)约束模板友元,即友元的类型取决于类被实例化时的类型;(3)非约束模板友元,即友元的所有具体化都是类的每个具体化的友元。

模板类的非模板友元函数:

template <class T>
class HasFriend
{
public:
    friend void counts();
};//例如counts()将是类hasFriend<int>和HasFriend<string>的友元

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值