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();
}
通过基类指针删除派生类构成的数组,结果未定义。