面向对象的特征:封装,继承,多态
使用背景:比如说在动物类底下可以有带毛的动物,带毛的动物符合所有的动物的特征,只是在这个基础上再继续添加一些特征
命名:原有类型称为“基类”或“父类”,在它的基础上建立的类称为“派生类”或“子类”。
//使用方式
class 基类
{};
class 派生类
: public/protected/private 基类
{};
区分:成员元素也是有这三种方式public,…,…。和继承一样
继承方式感觉就像是加上一层的感觉,原本的元素的属性是对于派生类(派生类内部)来看待,例如而对于派生类对象(派生类外部)而言经过保护继承而言已经变成了保护属性。
区分:保护继承和私有继承的区别体现在继续派生的时候,如果在派生类中继续进行进行派生的时候就会发生很大的区别差异。
表格总结:1. 基类中的私有成员,类外(含派生类中)都不能访问
- 派生类继承的属性是取交集,给后续的派生类或者是类外的的展示。
派生类的效果:吸收基类成员,添加新成员或者隐藏旧成员。
并不是所有的元素都是可以访问的,私有成员在派生类中是不能访问的需要借助于接口函数,保护和公有的在派生类是可以访问的。
—— 公有继承被称为接口继承,保护继承、私有继承称为实现继承。
如果不能很好的理解可以借助于以下问题好好理解一下
常考题总结
Q1:派生类在类之外对于基类成员的访问 ,具有什么样的限制?
只有公有继承自基类的公有成员,可以通过派生类对象直接访问,其他情况一律都不可以进行访问
Q2:派生类在类内部对于基类成员的访问 ,具有什么样的限制?
对于基类的私有成员,不管以哪种方式继承,在派生类内部都不能访问;
对于基类的非私有成员,不管以哪种方式继承,在派生类内部都可以访问;
Q3:保护继承和私有继承的区别?
如果继承层次中都采用的是保护继承,任意层次都可以访问顶层基类的非私有成员;但如果采用私有继承之后,这种特性会被打断。
继承关系的局限性
就是类中原本的六个默认创建的函数是不可以继承的。还有就是友元是不允许继承的,因为友元破坏了封装性,友元而言私有成员都可以进行访问。
1.2单继承
单继承下派生类对象的创建和销毁
创建一个派生类对象的时候,实际上是这样进行创建:首先执行派生类的构造函数然后执行基类的构造函数,也就说构造函数不是继承的不共用的不像其他的普通函数。
然后得到下面的物理上的体现,派生类对象占用的空间是派生类元素和基类元素的空间之和。
也就说创建一个派生类对象的时候,一定会调用基类的构造函数,没有显示定义就调用默认的。
class Base {
public:
Base(long base){ cout << "Base(long)" << endl; }
private:
long _base;
};
class Derived
: public Base
{
public:
Derived(long base, long derived)
: Base(base) //显式调用基类的构造函数
,_derived(derived)
{ cout << "Derived(long)" << endl; }
private:
long _derived;
};
void test() {
Derived d;//error
}
一个派生类对象的销毁:先调用自己的析构函数,然后再调用基类对象的析构函数
【补充】引用和const对象可以在默认列表中设置默认值,但是普通的对象只能在初始化列表中进行初始化。
一定先初始化Base基类子对象,至于剩下的两个东西是按照声明的顺序进行,调用的时候肯定是先调用派生类构造函数,但是需要先走完初始化列表。
构造函数的调用区别对象成员的构造函数是使用对象名构造,但是基类子对象是没有对象呢所以说是使用类名来实现
在销毁的时候,先调用派生类的析构函数,最后调用基类子对象的析构函数。
这个时候如果还有其他的对象成员,那么后声明的对象先销毁。
总结就是按照初始化列表的顺序进行打印,特殊的就是派生类的构造函数虽然是最先调用但是最后打印。
销毁的时候就是先销毁派生类对象的,然后销毁最后声明的对象成员的,然后销毁其他的对象成员,最后销毁基类子对象的。
class Test{
public:
Test(long test)
: _test(test)
{ cout << "Test()" << endl; }
~Test(){ cout << "~Test()" << endl; }
private:
long _test;
};
class Derived
: public Base
{
public:
Derived(long base,long test,long b2,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
, _t1(test)//创建Test类的成员子对象
{
cout << "Derived()" << endl;
}
~Derived(){
cout << "~Derived()" << endl;
}
private:
long _derived;
Test _t1;
};
对于基类成员的隐藏
当派生类和基类中定义的成员名字相同的时候,就不会再调用显示出来基类的成员,而是直接调用本对象的成员
不仅仅对象可以进行隐藏,如果是函数名字相同(且只需要名字相同就可以)也可以隐藏,即使是返回值的类型不同也可以实现隐藏,即使是参数不同这个和函数重载还不太一样,参数与基类的参数信息不对应的时候会直接进行报错。
class Base{
public:
Base(long x)
: _base(x)
{
cout << "Base()" << endl;
}
long _data = 100;
private:
long _base;
};
class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{
cout << "Derived()" << endl;
}
long _data = 19;
private:
long _derived;
};
void test0(){
Derived dd(1,2);
cout << dd._data << endl;
cout << dd.Base::_data << endl;
//不推荐这样写,了解
}
与嵌套类进行区分,派生类这个地方在存储的时候会有内存的嵌套效果,但是嵌套类中却不会这样直接显示。
1.3多继承
【注意】当把基类都写在一行的时候只写一个public的时候,只有第一个是public继承,后面的都是默认的private继承。
class A
{
public:
A(){ cout << "A()" << endl; }
~A(){ cout << "~A()" << endl; }
void print() const{
cout << "A::print()" << endl;
}
};
class B
{
public:
B(){ cout << "B()" << endl; }
~B(){ cout << "~B()" << endl; }
void show() const{
cout << "B::show()" << endl;
}
};
class C
{
public:
C(){cout << "C()" << endl; }
~C(){ cout << "~C()" << endl; }
void display() const{
cout << "C::display()" << endl;
}
};
class D
: public A,B,C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
//void print() const{
// cout << "D::print()" << endl;
//}
};
class D
: public A
, public B
, public C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
//void print() const{
//cout << "D::print()" << endl;
//}
};
创建的时候构造函数的打印是顺序是ABCD,调用顺序是DABC
销毁的时候调用和打印顺序都是DCBA
场景处理:1.成员名访问冲突
如果是不同基类中有同名的函数,并且派生类中没有该函数,就会出现冲突,可以通过类作用域符实现。
也可以在派生类也定义该函数,这样的话就会隐藏基类中的同名函数。
D d;
d.A::print();
d.B::print();
d.C::print();
d.print(); //ok,当在派生类中也定义该同名函数时,直接隐藏基类函数
上述这种问题可能就会在这种菱形结构中出现。
当打印print函数的时候,D对象中包含两个A也就是说有两个print函数
可以通过d.B::print();d.C::print();
本质上解决问题:可以通过虚拟继承的方式来实现
在虚拟继承的时候存储的时候先存储一个虚基类指针最后在存储基类子对象,所以说这个时候B对象sizeof为24,D对象的sizeof是48,因为只存了一份a基类子对象,但是多存了两个虚基类指针。所以说在B类对象存到D类对象上的时候,只是先存储一个虚基类指针和自己有的成员元素,共有的A对象放在这样可以确保只包含一个A对象。
【注意】只有共享的这个采用虚拟继承别的就是采用普通继承,也即是记住中间类虚拟继承顶层基类
class A
{
public:
void print() const{
cout << "A::print()" << endl;
}
double _a;
};
class B
: virtual public A
{
public:
double _b;
};
class C
: virtual public A
{
public:
double _c;
};
class D
: public B
, public C
{
public:
double _d;
};
然后重新生成解决方案,搜索得到相应的类的内存存储情况。
1.4基类与派生类之间的转换
Base base;
Derived d1;
base = d1; //ok
d1 = base; //error
Base * pbase = &d1; //ok
Derived * pderived = &base //error
Base & rbase = d1; //ok
Derived & rderived = base; //error
通过例子可以很好的理解就是当派生类向基类进行转换的时候是可以的(向上派生到基类转型是可以的),因为其中是有基类子对象的。但是基类向派生类对象进行转换的时候会报错的(即使是一个空继承也就是不添加额外的元素),可以理解因为派生类一般会额外再添加元素。(借助于下图理解)
【注意】重点掌握基类指针指向派生类指针的情况,在log4cpp中也有使用到
1:可否把一个基类对象赋值给一个派生类对象?(ke)可否把一个派生类对象赋值给一个基类对象?(buke)
2:可否将一个基类指针指向一个派生类对象?(ke)可否将一个派生类指针指向一个基类对象?(buke)
3:可否将一个基类引用绑定一个派生类对象?(ke)可否将一个派生类引用绑定一个基类对象?(buke)
引用底层就是指针
但是在有些情况下,向下转型是合理的,也就是说本身就是一个派生类指针,将这个指针正常转换为基类指针,然后转转换回来的时候,可以保证额外的添加的信息内容也是存在在接下来的位置的。
这个地方用到了多态函数也就是虚函数
class Base {
public:
Base(long base)
: _base(base)
{ cout << "Base()" << endl; }
virtual void display(){
cout << "Base::display()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
long _base = 10;
};
class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)
, _derived(derived)
{
cout << "Derived(long)" << endl;
}
~Derived(){
cout << "~Derived()" << endl;
}
long _derived;
};
//这种情况下就是不合理的,会得到一个空指针,会打印转换失败
void test1(){
Base base(1);
Derived d1(2,3);
Base * pbase = &base;
Derived * pd = dynamic_cast<Derived*>(pbase);
if(pd){
cout << "转换成功" << endl;
pd->display();
}else{
cout << "转换失败" << endl;
}
}
往往都是向上进行转型,有些时候向下转型也是合理的,因为可能是将基类的指针指向派生类的对象,然后将这个基类的指针转换为派生类的指针是合理的,因为在可能多出来的内存空间上也是有意义的。但是这个时候不能进行转换会报错,可以使用最强的c语言类型转换。也可以使用c++中较强的转换Derived * pd = dynamic_cast<Derived*>(pbase);
派生类对象间的复制控制(拷贝构造,赋值运算)(重点)
原则:基类部分与派生类部分要单独处理
(1)派生类中没有显示定义复制控制函数的时候,会自动完成复制控制操作
(2)派生类中显示定义复制控制函数的时候,需要手动完成基类和派生类的复制控制操作
有自动还要手动是因为(只是派生类对象上)可能有堆上的空间,需要进行单独的处理
//手动显示调用的情况
class Base{
public:
Base(long base)
: _base(base)
{}
protected:
long _base = 10;
};
class Derived
: public Base
{
public:
Derived(long base, long derived)
: Base(base)
, _derived(derived)
{}
Derived(const Derived & rhs)
: Base(rhs)//调用Base的拷贝构造
, _derived(rhs._derived)
{
cout << "Derived(const Derived & rhs)" << endl;
}
Derived &operator=(const Derived & rhs){
//调用Base的赋值运算符函数
Base::operator=(rhs);
_derived = rhs._derived;
cout << "Derived& operator=(const Derived &)" << endl;
return *this;
}
private:
long _derived = 12;
};
其中这种情况就是基类的指针或者是基类的引用指向派生类的对象
【注意】需要注意当显示定义的时候,一定需要记得同时处理基类对象和派生类对象的处理。
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
居中的图片:
居中并且带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block
var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 | Value |
---|---|
电脑 | $1600 |
手机 | $12 |
导管 | $1 |
设定内容居中、居左、居右
使用:---------:
居中
使用:----------
居左
使用----------:
居右
第一列 | 第二列 | 第三列 |
---|---|---|
第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE | ASCII | HTML |
---|---|---|
Single backticks | 'Isn't this fun?' | ‘Isn’t this fun?’ |
Quotes | "Isn't this fun?" | “Isn’t this fun?” |
Dashes | -- is en-dash, --- is em-dash | – is en-dash, — is em-dash |
创建一个自定义列表
-
Markdown
- Text-to- HTML conversion tool Authors
- John
- Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞tz−1e−tdt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:
这将产生一个流程图。:
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
mermaid语法说明 ↩︎
注脚的解释 ↩︎