继承
继承
本章讲授的主要内容是C++中的继承
分别从应用场景、语法、内存结构等方面进行阐述。
继承是面向对象的三大特性之一(封装、继承、多态),继承更多的是先有其设计模式后有其语法支持,在设计模式的角度,与继承通常被一起讨论的是组合
。
C++中继承的场景
面向对象中封装的目的是提高代码的复用度,封装是手段,最终要达到的状态是高内聚、低耦合,只有这样的状态才能更好的实现代码复用。
继承的目的也是代码的复用,不过可以说继承是代码复用的结果。
在面向对象的语言中,思考逻辑是基于『类』的,一类为单位进行封装,构建面向对象中的运行逻辑,类与类的关系既可以是平行的,也可以是派生的,并没有统一的规则,这些通常都是基于业务逻辑进行考量的。
类的继承是复用代码最简单的方式之一,因此很容易导致滥用。不加限制的对类进行派生会导致产生一个臃肿庞大的类,这样实质是对高内聚低耦合原则的破坏,大类的产生会降低面向对象的封装与灵活性,得不偿失。
与类不同,组合
是另一种代码复用的方式。这种方式不是直接通过继承的关系来复用代码,而是通过将不同的类汇聚到一起的方式来复用代码,代码的复用单位是类。通过这种方式组合在一起产生的新类既拥有了其他类的代码,又不会产生大类,造成臃肿,非常灵活。
关于到底是继承
还是组合
,业界一直都有讨论,通常认为,从逻辑上而言,当类与类之间的关系是is-a
时,则适合用继承,而类与类的关系是has-a
的关系是,则适合用组合。
而更为激进的是,现在有些编程语言,更为推荐使用组合而非继承的方式类构建面向对象的世界)如golang语言,就推荐使用组合而非继承的方式类复用代码)
C++面向对象继承的语法支持
#include <iostream>
using namespace std;
class Node{
public:
Node(int v = 0):value(v){}
~Node(){}
void showValue(){
cout<<value<<endl;
}
int getValue()const{return value;}
private:
int value;
};
#include <iostream>
#include "Node.h"
using namespace std;
class TTNode:Node{
};
int main(){
TTNode ttnode;
//ttnode.showValue();编译报错。因为默认继承关系是private,子类既无法访问父类的private成员,也无法访问父类的public成员
return 0;
}
private
protect
public
三个权限关键字,权限大小依次放开。
private
仅本类有权限访问。
protect
仅本类以及子类有权限访问。
public
完全放开,不设限制。
#include <iostream>
#include "Node.h"
using namespace std;
class TTNode:public Node{
public:
TTNode(int v = 0):Node(v){}//通过参数列表的形式来使用父类的构造器
};
int main(){
//复用了Node类的成员变量与成员函数
TTNode ttnode(11);
ttnode.showValue();//11
return 0;
}
对父类成员函数的重写,C++子类会默认继承父类的public成员函数,同时,子类也可以支持重写同名父类函数,构成函数重写,这样子类就能默认使用自己的成员方法了
#include <iostream>
#include "Node.h"
using namespace std;
class TTNode:public Node{
public:
TTNode(int v = 0):Node(v){}//通过参数列表的形式来使用父类的构造器
//与父类函数同名,只要同名,而无论其参数是否相同,都构成重写关系,子类会默认调用自身的成员函数,而非父类的(除非指定调用父类的
void showValue(){
cout<<"ttnode value "<<this->getValue()<<endl;
}
};
int main(){
//复用了Node类的成员变量与成员函数
TTNode ttnode(11);
ttnode.showValue();//ttnode value 11
//指定父类的成员函数
ttndoe.Node::showValue();//11
return 0;
}
在类的派生关系中,基类先产生构造,派生类后构造。析构时则派生类先析构,基类后析构。
继承的内存关系
C++的类继承中,子类是完全将父类的内存成员直接拷贝进自己的内存空间中,而无论父类的成员变量是何种访问权限(priavte
权限的成员变量也会被拷贝进子类) 。基类的成员地址在低地址区,派生类的成员地址在高地址区,这样的好处在于可以直接使用父类指针指向子类对象,能通过父类指针直接访问子类数据,而无需担心数据越界的问题。
实际是,无论是结构体还是类,通过结构体名或者对象名访问成员变量,本质都是访问其内存偏移地址处的内存,以结构体或者对象首地址为基地址,不同的成员变量名就是不同的偏移值,编译时就已经决定。
#include <iostream>
using namespace std;
class Node{
public:
void showValue(){
cout<<value<<endl;
}
void showMeteValue(){
cout<<meteValue<<endl;
}
private:
int value;
int meteValue;
};
class TTNode{
public:
void showId(){
cout<<mid<<endl;
}
private:
int mid;
};
int main(){
Node node;
//通过直接访问内存偏移值来访问成员变量,无视其语法层面的private
*(int*)&node = 11;
node.showvalue();//11
*((int*)&node+1) = 12;
node.showMetaValue();//12
//同理,派生类中继承自父类的成员内存与父类本身结构一致
TTNode ttnode;
*(int*)&ttnode = 11;
ttnode.showvalue();//11
*((int*)&ttnode+1) = 12;
ttnode.showMetaValue();//12
//自己的成员在父类成员之后,依次排列,当然,C++类中成员也遵守内存对齐原则
*((int*)&ttnode+2) = 33;
ttnode.showId();//33
return 0;
}
因为C++中基类与派生类的内存结构是一脉相承的,因此父类指针可以直接指向子类,并且访问子类中的成员而无需担心错误.
#include <iostream>
using namespace std;
class Node{
public:
int a;
int b;
void showAValue(){
cout<<a<<endl;
}
};
class TTNode:public Node{
public:
int c;
}
int main(){
Node *node = new TTnode;
node->a = 11;
a->showAValue();//11
return 0;
}
posted on 2021-12-28 00:24 shadow_fan 阅读(36) 评论(0) 编辑 收藏 举报