c++ primer plus chapter14 c++中的代码重用

valarray类:由头文件valarray支持,是用于处理数值的数组,语法如下:

/* 一个int类型数组a,长度是0 */
valarray<int> a;
/* 一个int类型数组,长度是8 */
valarray<int> a(8);
/* 一个int类型数组,长度是8,初始值全是10 */
valarray<int> a(10, 8)
/* 使用a1的前2个元素初始化a2 */
int a1[3] = {1, 2, 3};
valarray<int> a2(a1, 2);

常用的类方法如下:

operator[]():使用下标访问每个元素
size():      获取包含的元素个数
sum():       返回所有元素的总和
max():       返回最大的元素
min():       返回最小的元素

接口和实现:使用public继承,类可以继承接口,可能继承实现(基类的纯虚函数没有实现)。获取接口是is-a关系。使用嵌套,类可以获取实现,但是不能获取接口。获取实现是has-a关系。

列表初始化顺序:按照类中定义的顺序,而不是初始化列表中的顺序,如下:

class Test
{
private:
    string i;
    int j;
public:
    Test(string _i, int _j) : j(_j), i(_i){}
};

虽然初始化列表中先写的j,但是i仍然先被初始化,因为在类的对象中i是先被定义的。

private继承:除了嵌套之外,另一种实现has-a关系的途径是私有继承。使用public继承的时候,基类的公有方法成为派生类的公有方法,即派生类继承了基类的公有接口,使用private继承的时候,基类的公有方法和保护方法成为派生类的私有方法,继承了实现但是没有继承接口。

嵌套和私有继承的区别:

1.嵌套包含了显式命令的对象成员,private继承提供了无名称的子对象成员

2.同样适用列表初始化方法,嵌套使用对象名,private继承使用类名(因为没有成员名)

私有继承访问基类对象的方法是,对this指针进行强制类型转换,从而访问到继承而来的基类对象。在private继承中,不使用显式类型转换,不能将派生类指针或者引用赋值给基类指针或者引用。

使用嵌套还是private继承:嵌套允许创建多个对象,继承只能包含一个;若需要访问基类的保护成员或者重写虚方法的时候,才使用私有继承,否则一般使用嵌套。

保护继承:基类的protected成员和public成员在派生类中成为保护成员,存在第三代继承的时候能看出来区别,第三代继承可以使用第二代继承的保护方法。

一个在派生类外访问基类函数的方法:使用using,如下:

class father
{
public:
    func();
};

class son : private father
{
public:
    using father::func;
};

/* 调用者 */
son s;
s.func();

多重继承:c++允许多重继承,即一个类可以继承自多个父类,这和ruby不同。多重继承MI的主要问题是:

1.多个不同的基类有同名方法

2.多个基类有共享的始祖类,导致派生类继承同一个始祖类的多个实例

一个简单的例子:

class Worker
{
};

class Waiter : public Worker
{
};

class Singer : public Worker
{
};

class SingingWaiter : public Watier, public Singer
{
};

由于Waiter类和Singer类共有基类Worker类,因此一个SingingWatier类的对象中将有两个Worker类对象,一个是Waiter类中的,一个是Singer类中的,通常可以将基类指针指向派生类对象,但是此时会有问题,因为SingingWaiter对象中有两个Worker对象,不知道应该选择哪个地址,需要显式类型转换。

虚基类:多重继承的派生类拥有多个始祖类的实例是不必要的,使用虚基类防止这种情况。虚基类不是修改类声明,而是继承时指定虚继承,如下:

class Worker
{
};

class Singer : virtual public Worker
{
};

class Waiter : virtual public Worker
{
};

这样,Singer和Waiter共享一个Worker对象。使用虚基类后注意的点:

1.需要修改构造函数,比如,SingingWaiter构造函数初始化列表使用了Singer和Waiter的构造函数,而Singer和Waiter的构造函数都使用了Worker的构造函数,SingingWatier自动传递信息的时候,将通过两条不同的途径,因此C++在基类是虚的时候,禁止通过中间类将信息传递给基类,但这样的语法会通过编译,编译器的处理是,调用虚基类的默认构造函数。因此,需要显式调用虚基类的构造函数,注意只能在虚基类的时候这样用,否则编译报错

2.可能调用二义性的方法,在单继承中,派生类使用最近祖先的方法,但是多重继承中,每个直接祖先都有一个同名函数,那么调用此函数就会二义性。可以通过域解析运算符::或者重写此方法来解决。但是还会有问题,比如如下:

class T1
{
private:
    int t1;
public:
    void show() const{cout << t1 << endl;}
};

class T2 : virtual public T1
{
private:
    int t2;
public:
    void show() const{cout << t2 << endl;}
};

class T3 : virtual public T1
{
private:
    int t3;
public:
    void show() const[cout << t3 << endl;}
};

class T4 : public T2, public T3
{
public:
    void show() const
    {
        T2::show();
        T3::show();
    }
};

T4是多重继承,希望打印所有成员,但是同时使用T2和T3的show(),导致T1的show()被调用了两次,但是若不同时使用T2和T3的,就会导致有的数据没有被打印。这种情况只能通过精致的编码来解决,每个类将打印自己成员的功能抽象出来,封装成public的data(),然后用show()选择自己想要的进行组合,这样在T4中就可以调用T1,T2,T3的data或者T2的show()加T3的data()。data()应该设计成protected的,只有类的继承层级中可以使用,外界不能使用。

关于多重继承派生链中的函数二义性:

1.若同名函数只在一条链上,那么优先级同单链继承

2.若同名函数在不同链上,不管哪条链上祖先更近,优先级相同,二义性

3.这种派生链中的优先级关系不管函数是否是私有的,若某个私有函数优先级更高,那么即使派生类没有访问权限,仍然试图访问这个函数,导致编译期报错

容器类:用来存储其他对象或者数据类型,需要泛型编程,模板类用来解决这个问题。模板类需要将实现和声明都放在头文件中,因为模板不是函数,不能单独编译,必须和实例化请求一起使用。一个简单的例子如下:

template <typename T, int n>
class Test
{
private:
    T a[n];
};

其中T是泛型,n叫做表达式参数,它指定了特殊的类型而不是泛型。表达式参数只能是整型、枚举、引用、指针,比如double n就是不合法的,但是double *n是可以的。模板代码中不能修改参数的值也不能使用参数的地址。

可以给类模板参数提供默认值,这和函数模板不同。模板也有3种具体化方式,如下:

template <class T, int n>
class ArrayTP
{
private:
    T a[n];
};

1.隐式实例化
ArrayTP<int, 100> stuff;
ArrayTP<int, 100> * p;        //这只是个指针,只有需要对象的时候,才会生成类的隐式实例化
p = new ArrayTP<int ,100>;

2.显式实例化
template class ArrayTP<int, 100>;

3.显式具体化
template <> class ArrayTP<const char *, 100>
{
private:
    int j;
};

成员模板:模板可用作结构、类、模板类的成员。声明时使用不同的typename,定义时嵌套typename,如下:

template <typename T>
class beta
{
private:
    T t;
public:
    template <typename U>
    void blab(U u, T t);
};

template <typename T>
    template <typename U>
    void beta<T>::blab(U u, T t)
    {
    }

模板用作参数:如下代码:

template <template <typename> class thing>

其中template <typename> class是参数,thing是参数。

模板类的友元:模板类可以有友元,分如下三种:

1.非模板友元

2.约束模板友元,友元类型取决于类被实例化时的类型

3.非约束模板友元,友元的所有具体化都是类的每一个具体化的友元,如下:

1.非模板友元,counts函数是模板所有实例化的友元
template <class T>
class HasFriend
{
private:
    static int ct;
public:
    friend void counts();
    friend void report(HasFriend<T> &);
};

2.约束模板友元
template <typename T> void counts();
template <typename V>
class Test
{
friend void counts<TT>();
};

3.非约束模板友元
template <typename T>
class Test
{
template <typename C, Typename D>
friend void show(C &, D&);
};

1.非模板友元

对于非模板友元,有2种形式,一种是形如counts这样的函数,没有对象参数,可以访问独立于对象的类模板的静态数据成员,比如ct。需要访问对象的时候,使用report这样的函数,注意参数不是HasFriend,因为没有HasFriend这样的对象,必须使用HasFriend<T>作为参数,gcc会报declares a non-template function告警,因为不希望创建非模板友元函数。

2.约束模板友元,本质是对每个具体化的类产生一个友元模板,包含3步,首先在类定义前面声明模板友元,然后在类模板中再次声明为友元,最后给出定义。

3.非约束模板友元,本质是一个模板函数,是所有类具体化的友元

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值