C++基础:继承

1 多态共有继承实现方式

1 在派生类中重新定义基类方法。

2 使用虚方法。

如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。

2 为何需要虚析构函数

如果析构函数不是虚的,则将只调用对应于指针类型的析构函数,这意味着只有基类的析构函数被调用,即使指针指向的是一个派生类对象。

如果析构函数是虚的,

3 私有继承

使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。表示has-a的关系(保护继承也可以表示has-a)

使用公有继承,基类的公有方法将成为派生类的公有方法。

3.1 初始化基类组件

成员初始化列表语法,使用类名而不是成员名来标识构造函数

class Student : private std::string, private std::valarray<double> {
private:
    typedef std::valarray<double> ArrayDb;
    std::ostream & arr_out(std::ostream & os) const;
public:
    Student() : std::string("Null Student"), ArrayDb() {}
    explicit Student(const std::string & s)
        : std::string(s), ArrayDb() {}
}

3.2 访问基类的方法

使用类名和作用域解析运算符来调用方法

double Student::Average() const {
    if (ArrayDb::size() > 0)
        return ArrayDb::sum()/ArrayDb::size();
    else
        return 0;
}

或者使用using在派生类声明

// using声明只使用成员名,没有圆括号,函数特征标和返回类型。
class Student : private std::string, private std::valarray<double> {
...
public:
    using std::valarray<double>::min;
    using std::valarray<double>::max;
    // 这将使加const和未加const的都被派生类可用
    using std::valarray<double>::operator[];
}


 

3.3 访问基类的对象

使用强制类型转换将派生类对象(Student)转换为基类(string)对象,再用this指针调用。

const std::string &Student::Name() const {
    return (const string &) *this;
}
3.4 访问基类的友元函数

友元不属于类,可以通过显式类型转换为基类来调用基类的友元函数。

// arr_out是基类string的友元函数
ostream & operator<<(ostream & os, const Student & stu) {
    os << "Scores for " << (const string &) stu << ":\n";
    stu.arr_out(os);
    return os;
}

stu不自动转换为string引用,原因为在私有继承,未进行显示类型转换的派生类引用或指针,无法赋值给基类的引用或赋值。

并且有可能造成os << stu;与友元函数原型相匹配造成递归调用。

并且是多重继承,无法确定转换成哪一个类型。

4 使用包含还是私有继承

包含私有继承
易于理解所提供的特性比较多
可以直接通过名称使用对象通过protected等权限访问控制来让派生类访问基类成员
私有继承太抽象需要重新定义虚函数,需要使用派生类
私有继承会引起很多问题,尤其从多个类继承时

5 保护继承以及三种继承对比

保护继承:基类的公有成员和保护成员都将成为派生类的保护成员。但是当从派生类再派生出一个类时,各种继承的差别就体现了出来。

特征公有继承保护继承私有继承
公有成员变成派生类的公有成员派生类的保护成员派生类的私有成员
保护成员变成派生类的保护成员派生类的保护成员派生类的私有成员
私有成员变成只能通过基类接口访问只能通过基类接口访问只能通过基类接口访问
能否隐式向上转换在派生类中能

6 多重继承

使用多个基类继承(有可能会导致问题),表示is-a的关系。

必须使用public来限定没一个基类,否则编译器会认为是私有派生。

可能带来的问题:

1 从两个不同的基类继承同名方法

2 从两个或更多相关基类那里继承同一个类的多个实例(涉及到一个对象有多个基类实例带来的二义性,解决这个问题可以使用虚基类)

6.1 虚基类

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

注意:

C++在基类是虚基类时,禁止信息通过中间类自动传递给基类,如果不希望默认构造函数来构造虚基类对象,则需要显示调用所需基类构造函数。

调用方法时要注意,对于虚基类,当派生类SingerWaiter将继承的同名方法都调用时,才会调用基类对应的同名方法。

class Waiter : virtual public Worker {}
class Singer : virtual public Worker {}
class SingerWaiter : public Singer, public Waiter {}

void SingerWaiter::Data() const {
    Singer::Data();
    Waiter::Data();
}

6.2 其他虚基类相关问题

混合使用虚基类和非虚基类:使用虚基类的派生构造一个基类对象,使用非虚基类构造多个。

虚基类和支配:派生类中的名称优先于基类,在只有一个最优先的名称的时候,不会产生二义性。

7 类模板

模板不是类和成员函数定义,是C++编译器指令,说明了如何生成类和成员函数。

模板的具体实现被称为实例化和具体化。

模板不是函数,不能单独编译,必须与特定的模板实例化请求一起使用。

样例:

template <class Type>
class Stack {
private:
    enum {MAX = 10};
    Type items[MAX];
    int top;
public:
    Stack();
    bool isempty();
    bool isfull();
    bool push(const Type & item);
    bool pop(Type & item);
};

template<class Type>
Stack<Type>::Stack() {
    top = 0;
}

template<class Type>
bool Stack<Type>::isfull() {
    return top == MAX;
}

template<class Type>
bool Stack<Type>::push(const Type &item) {
    if (top < MAX) {
        items[top++] = item;
        return true;
    }
    return false;
}

需要创建一个模板类对象来使用模板,如下:

Stack<std::string> st;
st.isfull()
7.1 设计模板时需要注意的点
// 不对,仅仅创建指针,没有创建用于保存输入字符串的空间
Stack<char *> st;
7.2 表达式参数

// 其中n为表达式参数
template <class T, int n>
class ArrayTP {
private:
    T ar[n];
public:
    ArrayTP() {};
    explicit ArrayTP(const T & v);
    virtual T & operator[](int i);
    virtual T operator[](int i) const;
};

表达式参数的限制:可以为整型,枚举,引用或者指针。并且模板代码不能改变表达式参数的值,不能使用表达式参数的地址。

7.3 模板具体化
7.3.1 隐式实例化

即声明对象并指出所需类型

ArrayTP<int, 100> stuff;
7.3.2 显式实例化

使用关键字template指出所需类型来声明类,编译器将生成类声明的显式实例化

template class ArrayTP<string, 100>

7.3.3 显示具体化

是特定类型的定义,有时候可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。

template<typename T>
class SortedArray
{

}
7.3.4 部分具体化

部分限制模板的通用性。

template <class T1, class T2> class Pair { ... }; // general
template <class T1> Pair<T1, int> { ... }; // 部分具体化
template <> class Pair<int, int> { ... };  // 显式具体化

如果有多个模板可以选择,将使用具体化程度最高的。

Pair<double, double> p1; // general
Pair<double, int> p2;    // T1, int 部分具体化
Pair<int, int>p3;        // 显示具体化

如果提供的类型不是指针,使用genaral,如果是指针,使用指针具体化版本

Pair<char> fb1;   // general
Pair<char *> fb2; // Pair T*的具体化,如果每有,就将T转换为char *类型

7.4 模板也可以被用作参数
template <template <typename T> class Thing>
class Crab
{
private:
    Thing<int> s1;
    Thing<double> s2;
}

// 如果声明了Crab<King> nebula;
// King必须是一个模板类,也就是

template <typename T>
class King { ... };

7.5 模板类也可以有友元

7.5.1 非模板友元

这里的模板类,T如果是int,友元函数就是下面的

void reports(HasFriend<int> & hf)

也就是说,提前为一系列模板的友元函数做了定义,其并不是模板函数

#include <iostream>
using std::cout;
using std::endl;

template <typename T>
class HasFriend {
private:
    T item;
    static int ct;
public:
    HasFriend(const T & i) : item(i) {ct++;}
    ~HasFriend() {ct--;}
    friend void counts();
    friend void reports(HasFriend<T> &);
};

template <typename T>
int HasFriend<T>::ct = 0;
void counts() {
    cout << "int count: " << HasFriend<int>::ct << ", ";
    cout << "double count: " << HasFriend<double>::ct << endl;
}

void reports(HasFriend<int> & hf) {
    cout << "HasFriend<int>: " << hf.item << endl;
}

void reports(HasFriend<double> & hf) {
    cout << "HasFriend<double>: " << hf.item << endl;
}

int main() {
    cout << "No objects declared: ";
    counts();
    
    HasFriend<int> hfi1(10);
    cout << "After hfi1 declared: ";
    counts();
    HasFriend<int> hfi2(20);
    cout << "After hfi2 declared: ";
    counts();
    HasFriend<double> hfdb(10.5);
    cout << "After hfidb declared: ";
    counts();

    reports(hfi1);
    reports(hfi2);
    reports(hfdb);

    return 0;
}

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

也可以使友元函数本身成为模板。1首先要在类定义前面声明每个模板函数,2然后在函数中再次将模板声明为友元,3最后为友元函数提供模板定义。

//
// Created by hydtest20241015 on 10/22/24.
//

#include <iostream>
using std::cout;
using std::endl;

// 1
template <typename T> void counts();
template <typename T> void report(T &);

template <typename TT>
class HasFriendT {
private:
    TT item;
    static int ct;
public:
    HasFriendT(const TT & i) : item(i) {ct++;}
    ~HasFriendT() {ct--;}
    
    // 2
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT> &);
};

// 3
template <typename T>
void counts() {
    cout << "template size: " << sizeof(HasFriendT<T>) << "; ";
    cout << "template counts(): " << HasFriendT<T>::ct << endl;
}

template<typename T>
void report(T & hf) {
    cout << hf.item << endl;
}

7.5.3 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元

在类内部声明模板

template <typename T>
class ManyFriend {
private:
    T item;
public:
    ManyFriend(const T & i) : item(i) {}
    template <typename C, typename D> friend void show2(C &, D &);
};

template <typename C, typename D> void show2(C & c, D & d) {
    cout << c.item << ", " << d.item << endl;
}

7.6 模板别名

template <typename T>
using arrtype = std::array<T, 12>

arrtype<double> gallons;

8 不要以多态的方式处理数组

class BST { ... };
class BalancedBST : public BST { ... };

void printBSTArray(ostream& s, const BST array[], int num)
{
    for (int i = 0; i < num; ++i) {
        s << array[i];
    }
}

BST BSTArray[10];

BalanceBST bBSTArray[10];

printBSTArray(cout, bBSTArray, 10);

array[i]其实是一个指针算术表达式的简写,它代表的其实是*(array + i)。array所指内存和array + i所指内存的间隔是i * sizeof(数组中的对象),而当前数组类型为基类类型,而子类由于相较于基类总有更多的实现和成员,因此子类的sizeof大于基类。所以这样的多态数组会导致未定义行为。

并且,删除这样的数组,上述问题也会以不同的面貌出现。
 

delete [] array;

会生产出这样的代码

for (int i = the number of elements in the array - 1; i >= 0; --i)
{
    array[i].BST::~BST();
}

通过基类指针删除派生类构成的数组,结果未定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值