目录
虚函数:允许用基类的指针来调用 子类对应的虚函数实现
是C++中用于实现多态的机制,核心理念就是通过基类访问派生类定义的函数。
一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由多态方式调用的时候(通过指针)动态绑定。
由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为"虚"函数。
class Base
{
public:
virtual void Print(){cout<<"1";}
};
class A: public Base
{
public:
virtual void Print() {cout<<"2";}
};
1、虚函数可以被直接使用(动态多态,由子类实现覆盖,使用指针)。
2、虚函数只能借助于指针或者引用来达到多态的效果。
int main()
{
Base * p = new A();
p->Print();
}
3、虚函数和纯虚函数的定义中不能有static标识符
原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind)
4、虚函数是严格的一对一关系(父类 子类的一对同名同参数 函数)
(1)父类虚函数对应子类普通函数:
代表着子类覆盖了父类的虚函数
(2)父类虚函数对应子类虚函数:
代表着子类覆盖了父类的虚函数,并可以在被子类的子类覆盖。
(3)子类的函数与父类虚函数若同名但不同参,则毫无关系。
属于对继承的父类虚函数的一种重载。
纯虚函数与抽象类:含有纯虚函数的类被称为抽象类
纯虚函数:在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。
在基类中实现纯虚函数的方法是在函数原型后加 =0:
virtual void funtion()=0
含有纯虚函数的类被称为抽象类(abstract class)
纯虚函数是特殊的虚函数,特殊在于不可以直接调用,必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
派生类函数 覆盖/隐藏 基类函数
(1)类的关系是子类和父类之间的关系,是垂直关系
(2)覆盖只能由一个方法或只能由一对方法产生关系
覆盖(重写):覆盖基类虚函数(参数必须相同)
覆盖(也叫重写):是指在派生类中重新对基类中的虚函数重新实现。
函数名和参数必须一样,返回值可以不一样(允许返回值协变)。
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
如果参数不同,即使有virtual也算作隐藏。
隐藏:(1)隐藏父类非虚函数;(2)隐藏父类虚函数(参数名不同)
定义:子类对继承的父类的函数进行重写,实际是进行隐藏父类的函数。父类函数还存在。
特征:
(1)隐藏父类非虚函数;
(2)隐藏父类虚函数(参数名不同)
同类(作用域)中有多个同名方法
重载:同一作用域中多个同名函数,形参列表必须不同
操作符(运算符)重载
class Box
{
public:
int length;
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
return box;
}
};
int main()
{
Box Box1; // 声明 Box1,类型为 Box
Box1.length=1;
Box Box2;
Box2.length=3;
Box Box3=Box1+Box2;
cout<<Box3.length;
}
不可重载运算符:
.:成员访问运算符
.*, ->*:成员指针访问运算符
:::域运算符
sizeof:长度运算符
?::条件运算符
#: 预处理符号
可以重载运算符:
双目算术运算符 + (加),-(减),*(乘),/(除),% (取模)
关系运算符 ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)
逻辑运算符 ||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符 + (正),-(负),*(指针),&(取地址)
自增自减运算符 ++(自增),--(自减)
位运算符 | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
赋值运算符 =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
空间申请与释放 new, delete, new[ ] , delete[]
其他运算符 ()(函数调用),->(成员访问),,(逗号),[](下标)
继承
多继承:一个类有多个基类,这样的继承关系称为多继承
多继承声明语法:
class C: public A,public B
{
//类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。此处为先A,后B
}
构造函数:
C(形参列表): B(实参列表), A(实参列表),
//其他操作,y依然是先A后B,取决于定义派生类时顺序
}
多继承重复调用同一基类构造函数造成二义性:
根据多继承构造函数的调用顺序,先构造Base类对象再被继承为a类对象,
然后又先构造Base类对象再被继承为b类对象。
可见对于C类对象来说,Base构造了两次,所以Base中的任何属性都具有二义性。
分析C的对象模型:
虚继承解决多继承的二义性问题
如果要让Base在C中只产生一个对象,则应该对公共基类Base声明为虚继承,使得这个公共基类成为虚基类。
当类中出现virtual时,C++编译器会对象添加一个vptr指针,同时会产生一个虚函数表
//当类中出现virtual时,C++编译器会对象添加一个vptr指针,同时会产生一个虚函数表
class Base{};
class A: virtual public Base{};
class B : virtual public Base{};
class C :public A,public B{};
C的对象模型:
实际上A应该包括a和vptr指向的x,共12字节。B一样
接口继承和实现继承
所谓接口继承就是派生类只继承函数的接口,也就是声明。而实现继承就是派生类同时继承函数的接口和实现。
1、声明一个纯虚函数(pure visual)的目的就是为了让派生类只继承函数接口,即接口继承。
2、声明一个非纯虚函数(impure visual)的目的是为了让派生类继承函数接口和缺省实现。
3、声明一个非虚函数(non visual)的目的是为了让派生类继承函数接口和一份强制实现。
继承中构造函数调用顺序
C++当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推。
构造函数一开始构造时,总是要调用它的基类的构造函数,然后才开始执行其构造函数体。
在对象析构时,其顺序正好相反。
this指针的含义
“this指针”指向了成员函数作用的对象,在成员函数执行的过程中,正是通过“this指针”才能找到对象所在的地址,因而也就能找到对象的所有非静态成员变量的地址。
在类中构造函数创造一个对象。