类继承
基类
父类 被别人继承的类
派生类
子类 继承别人的类
需要自己的构造函数
可以添加额外的属性和方法
继承
class Cson : public C1 {}
class Cson : private C1 {}
class Cson : protected C1 {}
class Cson : C1 {} // 默认 private
详细解释见下面
函数联编
静态联编
编译过程中确定使用那个函数代码块的过程 编译时分配内存
如函数重载,普通函数调用 效率高 只能编译时确定
动态联编
运行过程中确定使用那个函数代码块的过程 运行时分配内存
如虚函数 效率低 但是可以运行时决定某些事
以上两种联编模式是为了考虑到效率和概念模型
效率 静态 > 动态
概念模型 在设计类时,能静态的就静态,部分动态效率会更高
构造函数
C1中的构造 和子类中的构造一起使用才是初始化完成
Cson::Cson(int a,int b):C1(a),numb(b){
}
如果没有调用基类的构造函数,则等同于调用了基类的默认构造函数
Cson::Cson(int a,int b) : numb(b){
}
所以除非是使用默认构造函数 否则都应该显示调用基类构造函数
构造和析构执行顺序
创建时 先调用基类的构造函数 再调用派生类的构造函数
释放时 先调用派生类的析构函数 再调用基类的析构函数
继承关系
is-a 继承
派生类是一个基类对象 例如 香蕉是水果(午饭有香蕉,香蕉不是午饭)
需要重写父类某些方法
公有继承 public
class Cson : public C1 {}
基类的公有成员是派生类的公有 私有是私有 保护是保护
应用场景
实例化对象 可以直接用基类公有成员
私有继承
class Cson : private C1 {}
class Cson : C1 {} // 默认 private
基类的公有成员和保护成员将成为派生类的私有成员。
所以基类的公有方法将不再是派生类的公有方法。 只能在派生类的成员方法中调用
应用场景
实例化对象 不能直接用基类的任何成员
保护继承
class Cson : protected C1 {}
保护继承 是私有继承的变体
使用保护继承时 基类的公有成员和保护成员将成为派生类的保护成员 其他同私有继承
与私有继承的区别在于第三代, 私有继承第三代则不能再使用,保护继承可以使用
继承 使用using 定义访问权限
public:
using std::valarray<int>::min;
这样的写法 使得该类可以使用min方法
c1.min() 等同于 std::valarray<int>::min();
typedef std::vector<int> vlist;
class C1 : private std::string,private vlist {}
C1(char* str,int n) : std::string(str),vlist(n) {};
std::string getStr() const { return (const std::string&)*this; }
int C3::fn1() const { return vlist::size();}
has-a 组合 复合
一个新的类包含另一个类
组合
class C1 {
public :
std::string name; // C1类中 包含std::string 类
}
复合
class C1 {
public :
std::string* name; // C1类中 包含std::string 类的指针
}
typedef std::vector<int> vlist;
class C1 {
private:
std::string name;
vlist l1;
public:
std::string getStr() const { return name; }
int fn1(){ return l1.size(); }
}
多态继承
同一个方法在基类和派生类中行为不同,具有多种形态,有两种实现方法
派生类中重写基类方法 override
虚函数 virtual 主要
virtual 虚函数
虚函数的作用是 指出该方法在基类和派生类行为不同
通过指针或引用调用虚函数将根据是具体哪个类的对象 用哪个类的方法
函数形参为基类指针或引用 传入实参后 调用virtual方法会根据具体类而定
C1 c1;
C1son c2;
C1 & q_c1 = c1;
C1 & q_c2 = c2;
q_c1.show();
q_c2.show();
要求和注意点
派生类会重新定义的方法需要加virtual 变成虚函数 否则设置为非虚方法
在基类中声明为虚函数,在派生类中也默认,但派生类中显示说明更好
通常为基类的析构函数声明成一个虚函数,也是一个好做法
只用在原型中, 定义中不用加virtual关键字
析构函数应尽可能是虚函数,除非不发生继承关系(没有错误只是效率问题)
也叫虚析构
有一个都是父类指针的数组,可能指向父类,也可能指向子类
在释放时
如果是普通析构函数,则可能会发生先调用父析构再调用子析构的错误
如果是虚析构函数, 则会根据指向对象的类型确定释放顺序
构造函数,友元函数不能是虚函数
如果派生类没有重新实现虚方法,则将调用上一级最先碰到实现的虚函数
子类新定义一个 不同参数 同名函数的虚函数,可能会引起各种问题
1 基类虚函数被隐藏
2 子类并没有虚函数重载
3 返回值 基类返回基类 子类返回子类是可以的
基类实现了虚重载 子类需要将全部版本都实现一次,否则会隐藏没实现部分
例如
基类实现了
virtual f1(int)
virtual f1(char)
virtual f1(short)
派生类则应该全部实现一次,只实现一个 则另外两个会被隐藏
派生类调用基类方法
C1Son::show(){
C1::show(); 如果需要调用基类的方法,需要加上::运算符
...
}
C1::Son::getName(){
getAge(); // 在派生类中没有重新定义基类方法 则可以直接调用基类方法(此时其实是继承过来了)
}
纯虚函数
当类中有 纯虚函数时 则该类将不能被实现,只能通过继承实现子类,此种叫抽象基类
virtual int fn1() const = 0
在定义结尾处 添加 =0
提供未实现的函数,需要在子类中实现
抽象基类
A类和B类有很多共同点,则抽象出一个公共基类C 再分别派生出A,B;
C类中包含
1 共同的属性
2 共同的方法
3 同样的方法,不同的实现 --- 纯虚函数
需要至少包含一个纯虚函数,当类中包含了一个纯虚函数时,则不能创建该类的对象
纯虚函数的类只用做基类
将任意方法定义为抽象的 则该类都是抽象类 C++中 在头文件中 定义成一个抽象的 在定义中进行实现也是可以的
void fn(int a) = 0;
void C1::fn1(int a){num = a};
C1 类为抽象类 虽然没有纯虚函数
头文件 virtual int fn1() = 0;
源文件 int C1::fn1(){}
这样做 可以通过一个基类 同时管理 多个子类 ,同时确保了子类至少包含基类的指定功能。
经典示例
普通用户和会员
类指针和引用
基类指针 可以在不进行显式类型转换的情况下指向派生类对象;
基类引用 可以在不进行显式类型转换的情况下引用派生类对象;
Cson cs1 = Cson(1)
C1 & q_c1 = cs1; // 基类引用可以直接指向派生类
C1 * p_c1 = *cs1; // 基类指针可以直接指向派生类
但是基类的指针和引用 指向派生类时 只能使用基类的方法,不能使用派生类新创建的方法
所以函数形参为基类引用或指针时 可以传递基类或派生类
继承与动态内存分配
情况1 基类使用了new 派生类不使用new
基类使用new 需要处理好 构造,复制,赋值,析构构造函数
派生类不使用new 则无需再处理以上4个构造函数
情况2 基类使用new 派生类使用new
此时则必须为派生类显示定义 析构,复制,赋值,函数
析构 对自身新创建的new进行delete
复制 必须调用基类的复制构造函数,来处理基类数据
sonDMA::sonDMA(const sonDMA & s1):DMA(s1){
}
赋值
sonDMA& sonDMA::operator=(const sonDMA & s1){
if(this == &s1)return *this; // 1 = 1 直接返回1
DMA::operator=(s1); // 这句话必须显示定义 为调用基类赋值运算符
...
}
继承与友元函数
派生类如何访问基类的友元
答 使用基类的友元
因为友元函数不是成员函数吗,所以不能用作用域解析符
答 使用强制类型转换
os << (baseDMA &) hs;
多重继承
多重继承 需要谨慎使用
问题1 从两个不同的基类继承同名方法
重写一个方法,指定使用哪个类的方法
void fn(){
C1::fn();
}
问题2 从两个或更多相关基类那里继承同 一个类的多个实例
一个示例
以下继承关系
class c1 : public cf;
class c2 : public cf;
class cPlus : public c1,public c2;
此时 cPlus 将有两个cf类 会有重复错误
cf* p = &cPlush; // 指针不知道指向哪里 (c1的cf 还是c2的cf)
解决办法
cf* p = (c1*) &cPlush; 强制转换指定
解决办法2 虚基类 virtual
从多个类派生出的对象 只继承一个基类对象
// 顺序没有关系 多个类 需要有 virtual 关键字
class c1 : virtual public cf {}
class c2 : public virtual cf {}
class cPlus : public c1,public c2;
此时不会发生多个基类的问题
问题3 构造函数问题
基于问题2 产生问题3
cPlus(1,2,3):cf(2),c1(2,3),c2(2,3){
// 此时需要显示使用虚基类构造函数 在初始化成员列表中
// 这是规定 原因也是c1 和c2 调用父构造函数重复问题
}