概述
通过类的继承(派生)来引入 是一个 的关系
被继承的类——基类,继承的类——继承类。
#include <iostream>
using namespace std;
struct Base
{
};
struct Base2 : Base
{
};
// 这是类的声明,下面继承部分不是类的声明
struct Drive;
// Drive派生自Base基类
// struct不写public,默认也是public继承
// 但是如果是class,那么默认是private继承,所以class经常看到public继承
struct Drive : public Base2
{
};
int main()
{
Drive d;
// 使用基类的指针或引用可以指向派生类对象
// Base2继承自Base,Drive继承自Base2,还是可以的
Base& ref = d;
Base* ptr = &d;
}
使用基类的指针或引用可以指向派生类对象
静态类型 V.S. 动态类型
静态类型:编译器就可以确定的类型。
动态类型:在运行期为ref或者指针赋予的实际的类型。
#include <iostream>
using namespace std;
struct Base
{
void fun() {}
};
struct Base2 : public Base
{
};
struct Drive : public Base2
{
void fun2() {}
};
struct Drive2 : public Base2
{
};
int main()
{
Drive d;
Base& ref = d;
ref.fun(); // 编译器只看静态类型是否可以接收
// ref.fun2(); // fun2是Drive的函数,是ref的动态类型,ref不能调用动态类型的函数
Drive d2;
Base* ptr = &d;
ptr = &d2; // ptr对应的静态类型没有发生改变,还是Base。但是动态类型从Drive到Drive2了
}
protected 限定符:派生类可访问
#include <iostream>
using namespace std;
struct Base
{
protected: // 如果是protected,那么Derive的fun2,就可以调用了
void fun() {}
int x;
};
struct Derive : public Base
{
public:
void fun2()
{
fun(); // fun如果是private,无法访问
x; // 因为proteted,所以可以访问fun和x
}
};
int main()
{
Derive d;
Base b;
// b.fun(); // fun private不行
d.fun2();
// b.fun2(); // b不能访问Derive的函数
}
类的派生会形成嵌套域
派生类中的名称定义会覆盖基类
#include <iostream>
using namespace std;
struct Base
{
int val = 2;
};
struct Derive : public Base
{
public:
void fun2()
{
cout << val << endl;
}
// int val = 3;
};
int main()
{
Derive d;
Base b;
d.fun2(); // 3 因为Derive里的val覆盖了Base的val
// 根本原因是Derive是嵌套在Base里的
cout << d.val << endl; // 如果把Derive的val注释掉,那么会去找Base的val。因为他们是嵌套的关系
}
使用域操作符显式访问基类成员
#include <iostream>
using namespace std;
struct Base
{
int val = 2;
};
struct Derive : public Base
{
public:
void fun2()
{
cout << val << endl;
cout << Base::val << endl; // 域操作符
}
int val = 3;
};
int main()
{
Derive d;
Base b;
d.fun2(); // 3 因为Derive里的val覆盖了Base的val
}
在派生类中调用基类的构造函数
#include <iostream>
using namespace std;
struct Base
{
Base ()
{
cout << "Base constructor is called" << endl;
}
};
struct Derive : public Base
{
public:
Derive ()
{
cout << "Derive constructor is called" << endl;
}
};
int main()
{
Derive d; // Base构造函数先调用,再调用Derive构造函数
}
#include <iostream>
using namespace std;
struct Base
{
Base (int)
{
cout << "Base constructor is called" << endl;
}
};
struct Derive : public Base
{
public:
Derive (int a)
: Base(a) // 通过初始化列表来去调用基类Base的构造函数
{
cout << "Derive constructor is called" << endl;
}
};
int main()
{
Derive d(100); // Base构造函数先调用,再调用Derive构造函数
}
虚函数
通过虚函数与引用(指针)实现动态绑定
使用关键字 virtual 引入
非静态、非构造函数可声明为虚函数
虚函数会引入vtable结构
#include <iostream>
using namespace std;
struct Base
{
virtual void baseMethod() {} // 虚函数
int baseMember;
};
struct myClassDerive : public Base
{
virtual void deriveMethod() {} // 虚函数
int deriveMember;
};
struct myClassDerive2 : public myClassDerive
{
virtual void deriveMethod2() {} // 虚函数
int deriveMember2;
};
int main()
{
myClassDerive2 d;
Base& b = d;
Base* ptr = &d;
myClassDerive2& d2 = dynamic_cast<myClassDerive2&>(b);
myClassDerive2* ptr2 = dynamic_cast<myClassDerive2*>(ptr);
}
如果我们想把一个基类的指针转成一个派生类的指针,如果基类的指针确实指向派生类的地址,那么这个转换就是成功的,否则指向空指针。基类的引用转换成派生类的引用,如果基类引用确实绑定在派生类的引用,就是成功的,否则抛出异常。
static_cast是在编译期处理的,但是dynamic_cast是在运行期发生的,判断动态类型能不能发生转变。如果要编写高性能的程序,dynamic_cast慎用。
#include <iostream>
using namespace std;
struct Base
{
virtual void fun()
{
cout << "Base :fun() is called" << endl;
}
};
struct Derive : Base
{
void fun()
{
cout << "Derive :fun() is called" << endl;
}
};
int main()
{
Derive d;
d.fun();
Base& b = d;
b.fun(); // 注意,使用虚函数,那么就会调用Derive的fun,但是如果没有使用虚函数,就会使用Base里的fun
// 没有了虚函数,就没有个vtable。所以对象的绑定都会在编译期确定,而编译期只看静态类型,b就是Base,所以只能调用Base中的fun
}
虚函数在派生类中的重写( override )
#include <iostream>
using namespace std;
struct Base
{
virtual void fun()
{
cout << "Base :fun() is called" << endl;
}
};
struct Derive : Base
{
// override 重写
void fun()
{
cout << "Derive :fun() is called" << endl;
}
};
void proc(Base& b)
{
b.fun();
}
int main()
{
// 动态类型实现的运行期的一个多态,也叫运行期多态
Derive d;
proc(d); // 这个参数的动态类型是derive,所以调用Derive的fun
Base b;
proc(b); // 这个参数动态、静态类型都是Base,所以调用Base的fun
}
函数签名保持不变(返回类型可以是原始返回指针 / 引用类型的派生指针 / 引用类型)
#include <iostream>
using namespace std;
struct Base2 {};
struct Derive2 : Base2 {};
struct Base
{
virtual Base2& fun()
{
cout << "Base :fun() is called" << endl;
static Base2 b;
return b;
}
};
struct Derive : Base
{
// override 重写
Derive2& fun()
{
cout << "Derive :fun() is called" << endl;
static Derive2 inst;
return inst;
}
};
void proc(Base& b)
{
b.fun();
}
int main()
{
// 动态类型实现的运行期的一个多态,也叫运行期多态
Derive d;
proc(d); // 这个参数的动态类型是derive,所以调用Derive的fun
Base b;
proc(b); // 这个参数动态、静态类型都是Base,所以调用Base的fun
}
可以通过 = 0 声明纯虚函数,相应地构造抽象基类
虚函数特性保持不变
override 关键字