面向对象编程三个基本特征: 1. 封装 2. 继承 3. 多态
一个操作随着所传递或捆绑的对象类型的不同能够做出不同的反应,其行为模式称为多态。(一般地,一个容器,其元素是基类对象的指针或引用,才有多态可言)
- 虚函数(采用虚函数会影响一些程序的效率)
1. 多态条件:仅仅对于对象的指针和引用的间接访问,才会发生多态现象。
1 class Base{ //含有虚函数的类比不用虚函数的类多了一个指针的空间,指向类中的虚函数表,会影响程序效率 2 public: 3 virtual void fn(){cout<<"In Base class\n";} //加上virtual编译器会做滞后处理 4 }; 5 6 class Sub : public Base{ 7 public: 8 virtual void fn(){cout<<"In Sub class\n";} //可以省略virtual 9 }; 10 11 void test(Base &b){ void test(Base b){ 12 b.fn(); b.fn(); 13 }//传递引用,会触发多态 }//值传递,子类对象变成基类对象,不触发多态 14 15 int main() 16 { 17 Base bc; 18 Sub sc; 19 test(bc); 20 test(sc); 21 }
2. 重载与覆盖:
覆盖:子类重定义父类的虚函数,导致不同类对象的该成员函数操作表现出不同的行为,称为成员函数的覆盖。(函数声明必须完全相同,包括:函数名、返回类型(协变)、参数类型、个数和顺序都必须完全相同),否则,成员函数即使加上virtual也不会被编译器做滞后处理。
3. 若干限制:
- 只有类的成员函数才能声明为虚函数;(虚函数仅适用于有继承关系的对象)
- 静态成员函数不能是虚函数;(静态成员函数不受对象的捆绑)
- 内联函数不能是虚函数;(内联函数不能在运行中动态地确定其位置)
- 构造函数不能是虚函数;
- 析构函数可以是虚函数且通常声明为虚函数;
- 纯虚函数
抽象类的用途是被继承,定义抽象类就是在类定义中至少声明一个纯虚函数。所谓纯虚函数是指被标明为不具体实现的虚函数。纯虚函数的声明形式是在虚函数声明形式后跟“=0”。抽象类是不允许有实例对象的,即不能由抽象类创建对象。但我们可以用抽象类的指针和引用来进行多态编程。
- 纯虚析构函数
有些时候,你想使一个类成为抽象类,但刚好又没有任何纯虚函数。怎么办?所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数。
这里是一个例子:
class awov {
public:
virtual ~awov() = 0; // 声明一个纯虚析构函数
};
这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。但这里还有一件事:必须提供纯虚析构函数的定义:
awov::~awov() {} // 纯虚析构函数的定义
这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~awov的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来,最后还是得回去把它添上
总结:
当定义一个指向子类实例的父类指针的时候,内存中实例化了子类,由于子类继承了父类,因此内存中的子类里包含父类的所有成员.但由于声明的是父类指针,因此该指针不能够访问子类的成员.而只能访问父类的成员.然而在父类里可以声明纯虚函数和定义虚函数.使用父类指针访问虚函数或纯虚函数的时候,访问到的是子类里重写的函数. 当然,对于虚函数,如果子类里没有对其重写的话,仍然访问到父类里定义的虚函数.可见虚函数和纯虚函数的却别仅仅在于:纯虚函数没有定义,只有声明
再有一个要注意的是析构函数要声明为虚函数,这样在delete父类指针的时候,才会调用实例化的子类的虚函数,否则只会调用父类的析构函数,造成子类的剩余部分没被释放,从而造成内存的泄漏.
- 类型转换
1. 动态转换(dynamic_cast):dynamic_cast操作是专门针对有虚函数的继承结构来的,它将基类指针转换成想要的子类指针,以做好子类操作的准备。
注:
dynamic_cast操作所针对的基类指针(即圆括号中的表达式),如果所指向的对象中不包含有想要的子类对象,则将得到0值结果:
1 Savings s("8288", 1000); 2 Account* pa = &s; //先将s转化为基类指针 3 Checking* pc = dynamic_cast<Checking*>(pa); //pc为0,因为Saving类和Checking类都是Account的子类但不互相继承,所以pa指向的Saving实体不包含Check对象,所以pc为0
2. 静态转换(static_cast):
- static_cast转换并不是专门针对指针的,只要是相关类型的转换,都可以操作。无非它主要针对的是确定的类型,而不是针对多态。关于多态的类型转换由dynamic_cast去做。
- 由于void* 到任何类型的指针都可以进行相融性转换,所以,可以用static_cast来进行。
- static_cast比type(表达式)的“防盗性”更强,因为可以通过指针值的非0判断,可以避免转换后的操作失常。
3. 常量转换(const_cast):去掉常量性的转换操作,从const type类型转换到type类型
1 const char* max(const char* s1, const char* s2){ 2 return strcmp(s1, s2)>0 ? s1 : s2; 3 } 4 5 int fn(){ 6 char* p = max("hello", "world"); //错 7 8 char* p = const_cast<char*>(max("hello", "world")); //对 9 }
4. 重解释转换(一般用来转换不同类型指针): reinterpret_cast<type>(表达式)
指针的类型表明不同类型的指针,其类型不同,是不能互相赋值的。
例如:
float f = 34.5;
int *ip = &f; //错:float的地址不能赋给int指针
但从地址值的本质来说,无非是用二进制表示的整数而已。因此从内存空间位置性而言,不同类型的指针又是统一的,都是二进制的地址。
int *ip = reinterpret_cast<int*>(&f);
该地址的空间不管放着什么数据,都将它作为整形看待。