基础知识点 | 1013_多态,继承等细节知识点及几个错题总结

1.多态之虚函数


在面向对象中,多态指的是使用相同的函数名来访问函数不同的实现方法,即“一种接口,多种方法”,用相同的形式访问一组通用的运算,

C++语言支持编译时多态运行时多态

  • 编译时多态指的是系统在编译时能确定调用哪个函数,它具有执行速度快的优点,运算符重载函数重载就是编译时多态。
  • 运行时多态指的是系统在运行时动态决定调用哪个函数,它为系统提供了灵活和高度问题抽象的优点,通过继承虚函数实现运行时多态。
    • 运行时多态的基础是基类指针基类指针可以指向任何派生类对象。在基类中的某成员函数被声明为虚函数后,在之后的派生类中可以被重新定义。但在定义时,其函数原型,包括返回类型、函数名、参数个数和参数类型的顺序,都必须与基类中的原型完全相同。只要在基类中显式声明了虚函数,那么在之后的派生类中就不需要用关键字virtual来显式声明了,可以略去,因为系统会根据其是否和基类中虚函数原型完全相同来判断是不是虚函数。所以,派生类中的虚函数如果不显式声明也还是虚函数。

  1. 因为虚函数使用的基础是赋值兼容,赋值兼容是指在需要用到基类对象的任何地方都可以用公有派生类的对象来代替,而赋值兼容成立的条件是派生类从基类public继承而来。所以,当使用虚函数时,派生类必须是基类public派生的。
  2. 定义虚函数时,不一定要在最高层的类中,而是看在需要动态多态性的几个层次中的最高层类中声明虚函数。
  3. 只有通过基类指针来访问虚函数才能实现运行时多态的特性。
  4. 一个虚函数无论被公有继承了多少次,它仍然是虚函数。
  5. 虚函数必须是所在类的成员函数,而不能是友元函数,也不能是静态成员函数。因为虚函数调用要靠特定的对象类决定该激活哪一个函数。
  6. 内联(inline)函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的, 即使虚函数在类内部定义,编译时将其看作非内联。
  7. 构造函数不能是虚函数,但析构函数可以是虚函数。

如果调用的函数是实函数,则看指针的定义;
如果调用的函数是虚函数,则看指针的指向(赋值)


// 基类
class Base{
    int x;
public:
    Base(int b): x(b) {}
    // 基类中的display函数是
    virtual void display(){
        cout << x;
    };
};

// 派生类
class Derived: public Base{
    int y;
public:
    Derived(int d): Base(d), y(d) {} 
    void display(){
        cout << y;
    }
};

// main函数
int main()
{
    Base b(1);		// 基类对象
    Derived d(2);	// 派生类对象
    Base *p = & d;	// 把派生类对象的地址赋值给基类对象的指针变量
    
    b.display();	// 调用基类对象中的虚函数
    d.display();	// 调用派生类对象中的虚函数
    p->display();	// 通过指向基类对象的的指针,访问派生类中的重写的虚函数
    
    return 0;
}
  • 通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。

// 父类A
class A
{   
public: 
	void virtual f() {       
        cout << "A" << " ";
    }
};
// 公有继承A类的子类B
class B : public A
{   
public:
	void virtual f(){       
        cout << "B" << " ";
    }
};
int main(){
    A *pa = new A();	// 基类指针指向基类对象
    pa->f();
    B *pb=(B *)pa;	// 派生类指针指向基类对象
    pb->f();    
    delete pa, pb;
    
    pa=new B();	//基类指针指向派生类对象
    pa->f();
    pb=(B *)pa;	// 派生类指针指向派生类对象
    pb->f();
    return 0;
}
  • 无论指针指向值的数据类型如何改变,对象的内容都不变

// 基类
class Base {
public:
    Base() {
        echo();
    }
    virtual void echo() {
        printf("Base");
    }
};
// 公有继承基类的派生类
class Derived:public Base {
public:
    Derived() {
        echo();
    }
    virtual void echo() {
        printf("Derived");
    }
};
int main() {
    Base* base = new Derived();
    base->echo();
    return 0;
}
  • 永远不要在构造函数中调用虚函数!!!
  • Base* base = new Derived(); 这句话分为两部分来看:
    • 首先执行左半部分,新建一个基类指针。由于基类构造函数中调用虚函数,因此输出 Base
    • 紧接着执行右半部分,新建一个派生类对象。此时调用派生类构造函数,输出 Derived
  • base->echo(); 考察虚函数的使用了。此时基类指针指向派生类对象,输出 Derived


2.继承之父子类


  1. 子类构造函数调用父类构造函数用super
  2. 子类重写父类方法后,若想调用父类中被重写的方法,用super
  3. 未被重写的方法可以直接调用。


3.__try 和 __finally


int GetResult(int a){
int b = 0;
__try{
    if ( a != 0 ){
        b++;
    }
    // 运行到此处时代码返回
    return b;
}
// finally块中的内容一定会执行
__finally{
    --b;
}
return b;
}

  • __ try{} 块中无论运行什么代码,即使是 return 和 触发异常,在程序推出前都会运行 __ finally{} 中的代码,这样设计的目的是便于资源回收。


4.printf 从右向左编译,从左向右输出



5.缺省参数


定义:即默认参数。

主要规则:调用时你只能从最后一个参数开始进行省略,换句话说,如果你要省略一个参数,你必须省略它后面所有的参数,即:带缺省值的参数必须放在参数表的最后面。

最重要的一点:缺省参数是静态绑定的!


class A{
public:
    virtual void func(int val = 1){ 
        std::cout<<"A->"<<val <<std::endl;
    }
    virtual void test(){ 
        func();
    }
};
class B : public A{
public:
    void func(int val=0){
        std::cout<<"B->"<<val <<std::endl;
    }
};
int main(int argc ,char* argv[]){
    B*p = new B;
    p->test();
	return 0;
}


6.一个常做常错的题


unsigned int value = 1024;	// value 的二进制是 1后接10个零
bool condition = *((bool *)(&value));	// 取低8位作为condition,所以此刻 condition 为0
if (condition) {
    value += 1;
    condition = *((bool *)(&value));
}
if (condition) {
    value += 1;
    condition = *((bool *)(&value));
}

posted @ 2021-10-13 21:39  不是勇士  阅读(103)  评论(0)    收藏  举报