「C++语法」继承与派生,虚基类与虚函数 (千字详解)

继承

单继承

继承的访问规则

派生类的访问权限规则

  1. public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用

  2. private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,私有财产神圣不可侵犯嘛,即便是子女,朋友,都不可以使用。

  3. protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。

/基类
class A{
public:
    int mA;
protected:
    int mB;
private:
    int mC;
};
 
//1. 公有(public)继承
class B : public A{
public:
    void PrintB(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
class SubB : public B{
    void PrintSubB(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
void test01(){
 
    B b;
    cout << b.mA << endl; //可访问基类public属性
    //cout << b.mB << endl; //不可访问基类protected属性
    //cout << b.mC << endl; //不可访问基类private属性
}
 
//2. 私有(private)继承
class C : private A{
public:
    void PrintC(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
class SubC : public C{
    void PrintSubC(){
        //cout << mA << endl; //不可访问基类public属性
        //cout << mB << endl; //不可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
void test02(){
    C c;
    //cout << c.mA << endl; //不可访问基类public属性
    //cout << c.mB << endl; //不可访问基类protected属性
    //cout << c.mC << endl; //不可访问基类private属性
}
//3. 保护(protected)继承
class D : protected A{
public:
    void PrintD(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
class SubD : public D{
    void PrintD(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
void test03(){
    D d;
    //cout << d.mA << endl; //不可访问基类public属性
    //cout << d.mB << endl; //不可访问基类protected属性
    //cout << d.mC << endl; //不可访问基类private属性
}

构造函数和析构函数

  • 子类对象在创建时会首先调用父类的构造函数

  • 父类构造函数执行完毕后,才会调用子类的构造函数

  • 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数

  • 析构函数调用顺序和构造函数相反

class A{
public:
    A(){
        cout << "A类构造函数!" << endl;
    }
    ~A(){
        cout << "A类析构函数!" << endl;
    }
};
 
class B : public A{
public:
    B(){
        cout << "B类构造函数!" << endl;
    }
    ~B(){
        cout << "B类析构函数!" << endl;
    }
};
 
class C : public B{
public:
    C(){
        cout << "C类构造函数!" << endl;
    }
    ~C(){
        cout << "C类析构函数!" << endl;
    }
};
 
void test(){
    C c;
}

运行结果:

A类构造函数!

B类构造函数!

C类构造函数!

C类析构函数!

B类析构函数!

A类析构函数!

继承与组合混搭的构造和析构

请添加图片描述

class D{
public:
    D(){
        cout << "D类构造函数!" << endl;
    }
    ~D(){
        cout << "D类析构函数!" << endl;
    }
};
class A{
public:
    A(){
        cout << "A类构造函数!" << endl;
    }
    ~A(){
        cout << "A类析构函数!" << endl;
    }
};
class B : public A{
public:
    B(){
        cout << "B类构造函数!" << endl;
    }
    ~B(){
        cout << "B类析构函数!" << endl;
    }
};
class C : public B{
public:
    C(){
        cout << "C类构造函数!" << endl;
    }
    ~C(){
        cout << "C类析构函数!" << endl;
    }
public:
    D c;
};
void test(){
    C c;
}

运行结果:
A类构造函数!

B类构造函数!

D类构造函数!

C类构造函数!

C类析构函数!

D类析构函数!

B类析构函数!

A类析构函数!

继承中同名成员注意事项

  1. 当子类成员和父类成员同名时,子类依然从父类继承同名成
  2. 如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)
  3. 在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)
  4. 无法在子类中访问基类中重载的函数

非自动继承函数

构造函数,析构函数,operator=

继承中的静态成员

和非静态成员的共同点:

  1. 都可以继承到派生类
  2. 如果重新定义了静态成员函数,所有基类的其他重载函数会被隐藏
  3. 改变基类中一个函数的特征,所有使用该函数名的基类版本都被隐藏

静态成员函数不是虚函数

多继承

class base{
public:
    int a,b;
    base(int _a,int _b):a(_a),b(_b){
        cout<<"base="<<a<<" "<<b<<endl;
    };
};
class sub1:virtual public base{
public:
    int c;
    sub1(int _a,int _b,int _c):base(_a,_b),c(_c){
        cout<<"sub1="<<a<<" "<<b<<endl;
    };
};
class sub2:virtual public base{
public:
    int d;
    sub2(int _a,int _b,int _d):base(_a,_b),d(_d){
        
        cout<<"sub2="<<a<<" "<<b<<endl;
    };
};
class ssub:public sub1,public sub2{
public:
    int e;
    ssub(int _a,int _b,int _c,int _d,int _e):base(_a,_b),sub1(_d,_b,_c),sub2(_e,_b,_d),e(_e){
        cout<< "ssub="<<a<<' '<< b<<endl;
    };
};

int main(){
    ssub p(1,2,3,4,5);
    cout<<p.a<<' '<<p.b<<' '<<p.c<<' '<<p.d<<' '<<p.e<<endl;
}

运行结果:

base=1 2

sub1=1 2

sub2=1 2

ssub=1 2

1 2 3 4 5
  1. sub1, sub2中对a初始化不改变base类中a的值
  2. 如果没有显示调用虚基类的构造函数,默认调用无参的构造函数

基类和派生类指针

class base{
public:
    int a,b;
    virtual void p()=0;
    base(int _a,int _b):a(_a),b(_b){
        cout<<"base="<<a<<" "<<b<<endl;
    };
    void get(){
        cout<<"base"<<endl;
    }
};
class sub1:virtual public base{
public:
    int c;
     void p(){
        cout<<"sub1"<<endl;
    }
    void get(){
        cout<<"sub1"<<endl;
    }
    sub1(int _a,int _b,int _c):base(_a,_b),c(_c){
        cout<<"sub1="<<a<<" "<<b<<endl;
    };
};
int main(){
    base* t= new sub1(1,2,3);
    t->get();   //等价于t->base::get();
    t->p();      //等价于t->sub1::p();
}

运行结果:

base=1 2

sub1=1 2

base

sub1
  1. 基类指针指向基类对象,简单。只需要通过基类指针简单地调用基类的功能。

  2. 派生类指针指向派生类对象,简单。只需要通过派生类指针简单地调用派生类功能。

  3. 将基类指针指向派生类对象是安全的,因为派生类对象“是”它的基类的对象。

    基类指针指向派生类:只能访问基类中有的函数且是父类空间内的.

  4. 将派生类指针指向基类对象,会产生编译错误。“是”关系只适用于从派生类到它的直接(或间接)基类,反过来不行。 基类对象并不包含派生类才有的成员,这些成员只能通过派生类指针调用。

一:继承中的指针问题。
1. 指向基类的指针可以指向派生类对象,当基类指针指向派生类对象时,这种指针只能访问派生对象从基类继承而来的那些成员,不能访问子类特有的元素,除非应用强类型转换,例如有基类B和从B派生的子类D,则B *p;D dd; p=ⅆ是可以的,指针p只能访问从基类派生而来的成员,不能访问派生类D特有的成员.因为基类不知道派生类中的这些成员。
2. 不能使派生类指针指向基类对象.
3. 如果派生类中覆盖了基类中的成员变量或函数,则当声明一个基类指针指向派生类对象时,这个基类指针只能访问基类中的成员变量或函数。例如:基类B和派生类D都定义了函数f,则B *p; D m; p=&m; m.f()将调用基类中的函数f()而不会调用派生类中的函数f()。
4. 如果基类指针指向派生类对象,则当对其进行增减运算时,它将指向它所认为的基类的下一个对象,而不会指向派生类的下一个对象,因此,应该认为对这种指针进行的增减操作是无效的.
二:虚函数
1. 为什么要使用虚函数:正如上面第1和3点所讲的,当声明一个基类指针指向派生类对象时,这个基类指针只能访问基类中的成员函数,不能访问派生类中特有的成员变量或函数。如果使用虚函数就能使这个指向派生类对象的基类指针访问派生类中的成员函数,而不是基类中的成员函数,基于这一点派生类中的这个成员函数就必须和基类中的虚函数的形式完全相同,不然基类指针就找不到派生类中的这个成员函数。使用虚函数就实现了一个接口多种方法。
————————————————

详见csdn blog:# 基类指针和派生类指针

虚函数

C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。
子类可以重写父类的虚函数实现子类的特殊化。

一旦某个函数(在基类中)被声明成了虚函数,,在派生类(子类)中也被声明成了虚函数

*虽然说你只需要在父类中的该函数声明前加上virtual就可以do到上述的目的,而不需要all的子类都在该函数声明前加virtual,但为了方便他人和自己维护所写的代码,你写虚函数的话就最好在父类及其all的子类中把该函数都声明virtual的*

父类加了virtual,子类不需要加virtual,多余。加了也不会报错。

父类中不是virtual,子类是virtual,那么父类中的不是虚函数,子类及子子类的派生类中该函数才是虚函数

虚函数签名不匹配

函数的签名包括:函数名,参数列表,const属性。

虚函数签名不匹配导致会新建一个虚函数而非重写原函数

虚析构函数

引入原因:

一个基类指针可以通过new产生一个派生类对象,如果delete关键字去删除这个指针时,仅仅会调用基类的析构函数,而派生类的空间没有被释放,这样会造成内存泄露.
虚析构函数使在删除指向派生类对象的基类指针时,可以通过调用派生类的析构函数来实现释放派生类所占内存,从而防止内存泄露.

class Base {
public:
    virtual ~ Base(){
        cout<< "delete Base" << endl;
    }
    virtual void func(){
        cout<< "Base func"<<endl;
    }
};

class Derived: public Base{
public:
    ~ Derived(){
        cout << "delete Derived"<< endl;
    }
    void func(){
        cout<< "Derived func2"<<endl;
    }
};

int main()
{
   Base * ptr = new Derived();
   ptr->func();
   delete ptr;
    return 0;
}

纯虚函数

与虚函数区别

虚函数不是函数是没有实现的,纯虚函数是没有实现的函数
纯虚函数是一个接口的作用

definition

virtual void func() =0

引入原因

在很多情况下,基类本身生成对象是不合情理的,声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例.
纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。

抽象类

包含纯虚函数的类为抽象类

  1. 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。
  2. 抽象类是不能定义对象的。
  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值