第九天2017/04/18(3、重载/覆盖 PK 重写/重定义、父类子类混搭风、抽象类)

1、重载/覆盖 PK 重写/重定义
【预备知识】
函数重载
    必须在同一个类中发生
    子类无法重载父类的函数,父类同名的函数将会被名称覆盖
    重载是在编译期间根据参数类型和个数决定函数调用
    重载只放在同一个类之中,在编译期间就确定
函数重写
    必须发生在父类与子类之间
    父类与子类中的函数必须有完全相同的函数原型
    使用virtual关键字声明后能够产生多态(如果没有virtual,那叫重定义)
    多态是运行期间根据具体对象的类型决定函数调用
    重写发生在父子类之间,
覆盖
    父类和子类中有“同名”的函数,子类的函数会把父类的函数隐藏起来
重定义(是一种特殊的覆盖)
    父类和子类中有“相同函数原型”的函数,子类的函数会把父类的函数隐藏起来
1、重载/覆盖 PK 重写/重定义
    重载:在“同一个类”中,函数名相同、函数原型不同,此时发生重载
    覆盖:无virtual关键字,在父类、子类中,函数名相同、函数原型不同,此时在子类中的函数隐藏了父类中的函数。
---------------------------------------------------------------------------------------------
    重写:有virtual关键字,在父类、子类中,函数名、函数原型都相同,此时发生重写
    重定义:无virtual关键字,在父类、子类中,函数名、函数原型都相同,此时在子类中的函数隐藏了父类中的函数,类似于“覆盖”。

#include <iostream>
using namespace std;
class B
{
public:
    void f() {  }
    virtual void f(int i) {  } 
    void f(int i,int j) {  }
};
class D:public B
{
public:
    //子类中没有void f()函数
    void f(int i) {  }  //重写
    void f(int i,int j) {  }  //发生名称覆盖
    void f(int i,int j,int k) {  }  //发生名称覆盖
};
void g(B& b)
{
    b.f(1);
}

void main()
{
    D d;
/***************************************************************************/
//【重点】
//疑问:为什么子类对象不能调用父类中的f()函数?
//答:因为子类中有函数名为f的函数,有由于子类的函数不会重载父类的函数,所以
//子类中的f函数会把父类中的无参的f()函数给覆盖,因此直接d.f()会发生编译错误!
    //d.f();//error: 没有重载函数接受0个参数的f()
//疑问:如果我就是想用子类对象去调用父类中的f()函数,应该怎么做?
//答:加上作用域符B::,此时d对象就会调用父类B中的f()函数。
    d.B::f();  //
//【结论】子类中的f()函数不会重载父类中的f()函数,父类同名的函数将被覆盖!重载只发生在同一个类中!
/***************************************************************************/
    D dd;
//覆盖
    //dd.f();   //编译失败,因为无virtual发生同名覆盖
    dd.B::f();  //作用域B::,调用子类中的void f()
//覆盖、重定义
    dd.B::f(1,2);//作用域B::,调用子类中的void f(int i,int j)
    dd.f(1,2);  //覆盖:调用子类中的void f(int i,int j)
//多态
    B &b = d;
    b.f(1);     //多态:调用子类中的void f(int i)
    b.B::f(1);  //作用域B::,调用父类中的void f(int i)
//重载
}
----------------------------------------------------------------------
2、父类对象、子类对象混搭风【该模块中,隐藏了一个天大的Bug:“P++步长”】

本质:由于步长的影响
【结论】
        不要用父类指针p指向子类对象的数组,通过p++,去
    遍历这个子类对象的数组。
同理:也不要用子类指针p指向父类对象的数组,通过p++,去
    遍历这个父类对象的数组
【为什么?】因为父类指针p指向了子类对象的数组,在进行p++的时候,
p增加的步长是sizeof(父类),但是由于sizeof(父类)不一定等于sizeof(子类),
因此p在进行加1后,指向的位置不一定是下一个子类对象的首地址。当p指向的位置
不是下一个子类对象的首地址的时候,如果进行访问子类对象中的成员,程序必然会发生
崩溃。
//程序案例
#include <iostream>
using namespace std;
class A
{
public:
    virtual void f()
    {
        cout<<"A:f()"<<endl;
    }
};
class B:public A
{
public:
    int i;  //为什么在子类中加上一个属性i,程序就会运行崩溃:因为加了一个变量,对步长有影响
public:
    B(int i=0,int j=0){}
    virtual void f()
    {
        cout<<"B:f()"<<endl;
    }
};

void howToF(A* pBase)
{
    pBase->f();
}

int main()
{
    A *p = NULL;
    B *q = NULL;

    B c[3] = {B(1,1),B(1,1),B(1,1)};

    p = c; //父类指针p指向由子类对象构成的数组
    q = c; //子类指针q指向由子类对象构成的数组

    for(int i=0;i<3;i++)
    {
        //p->f();  //因为步长的原因,访问成员时,会发生程序崩溃
        q->f();

        p++;  //p++增加的步长是sizeof(父类),而不是sizeof(子类)
        q++;
    }

    for(int i=0;i<3;i++)
    {
        howToF(&c[i]); //形参:子类对象的地址,实参:父类指针
         //解释:此处运行正确,为什么?
//此处没有用p++形式,而是用了下标操作c[i],避开了p++的步长不一致导致的程序崩溃。
    }
    return 0;
}
----------------------------------------------------------------------
3、抽象类
#include <iostream>
using namespace std;
class A
{
public:
    virtual void ff() = 0;
};

//A  g1();  //不允许使用返回抽象类 "A" 的函数
A& g21();  //允许使用返回抽象类引用类型 "A&" 的函数
A& g22(A&); 
A& g23(A*); 
A* g31();  //允许使用返回抽象类指针类型 "A*" 的函数
A* g32(A*);
A* g32(A&);

//void f1(A  a);//不允许使用抽象类类型 "A" 的参数
void f2(A &a);//允许使用抽象类引用类型 "A&" 的参数
void f2(A *a);//允许使用抽象类指针类型 "A*" 的参数

class B:public A
{
public:
    void ff() {  }
};
int main()
{
    //A a1;//不能实例化抽象类
    //A a2 = new A;//不能实例化抽象类
    A *a4 = new B; //可以用抽象类的指针指向抽象类的子类对象

    B b1;
    A &a3 = b1;//可以用抽象类的引用指向抽象类的子类对象
}

4、多重继承与抽象类--->实现多继承接口
【知识复习】
#include <iostream>
using namespace std;
class A1
{
public:
    virtual void ff() = 0;  //纯虚函数
    void gg() { cout<<"普通函数"<<endl; }  //普通函数在A1中实现
};
class A2
{
public:
    virtual void ff() = 0;  //纯虚函数
    void gg() { cout<<"普通函数"<<endl; }//普通函数在A2中实现
};
class B:public A1,public A2//B多重继承A1、A2时
{
public:
    virtual void ff() //纯虚函数ff在B中实现
    { cout<<"纯虚函数"<<endl; }

};
int main()
{
    B b;
//b访问纯虚函数ff,不会发生二义性
    b.ff(); 
//b访问普通函数gg,会发生二义性
    //b.gg(); //编译失败
//防止二义性,应该加上作用域标识符A1::、A2::
    b.A1::gg();
    b.A2::gg();
}
【知识引出】
疑问:多重继承会发生二义性,但是多重继承在C++中有什么作用呢?
答:在项目开发过程中,多重继承的作用主要是:多重继承多个抽象类(接口),这样会有效的避免二义性。

多继承接口案例
#include <iostream>
using namespace std;
class A
{
public:
    virtual void ff(){ cout<<"ff:A"<<endl; }
};

class interface1 //抽象类接口1
{
public:
    virtual void gg1() = 0; 
};
class interface2 //抽象类接口2
{
public:
    virtual void gg2() = 0;  
};
class B:public A,public interface1,public interface2
{
//B类继承了类A、接口interface1、接口interface2
public:
    void gg1() { cout<<"gg1"<<endl; }
    void gg2() { cout<<"gg2"<<endl; }
    void kk(){ cout<<"KK"<<endl; }
};
int main()
{
    B b;
    b.ff();
    b.gg1();
    b.gg2();
    b.kk();
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值