6-C++中继承的权限有三种情况、创建和销毁的执行过程、多继承、菱形继承、多态的相关知识点

目录

1.继承的权限有三种

(1)public继承

(2)protected继承

(3)private继承

总结

 2.创建和销毁的执行过程

(1)类中有成员对象情况

(2)继承时情况

(3)创建和销毁综合

 3.多继承

多继承的问题

4.菱形继承(钻石继承)

(1)解决方式1:通过作用域限定符的方式进行区分

(2)解决方式2:通过虚继承的方式,从公共基类中得到一份成员,避免了二义性

5.多态

(1)子类可以转成基类的情况

(2)多态的实现

(3)多态练习

(4)多态缺陷


1.继承的权限有三种

不管哪种继承方式,基类私有成员在子类中都不能直接访问

公共继承全不变,保护继承变保护,私有继承变私有

(1)public继承

基类的public成员基类的protected成员基类的private成员
在子类中访问的访问权限成为子类public成员成为子类public成员不能直接访问

#include <iostream>

using namespace std;
class Father{
public:
    string publicStr;
protected:
    string protectedStr;
private:
    string privateStr;
};
class Son:public Father{
public:
    void method(){
        cout<<publicStr<<endl;
        cout<<protectedStr<<endl;
        //cout<<privateStr<<endl;
    }
};

int main(){
    Son s;
    //公有继承时
    //父类中公共属性,在子类中也是public权限,全局可以访问
    s.publicStr;
}

(2)protected继承

基类的public成员基类的protected成员基类的private成员
在子类中访问的访问权限成为子类protected成员成为子类protected成员不能直接访问

#include <iostream>
using namespace std;
class Father{
public:
string publicStr;
protected:
string protectedStr;
private:
string privateStr;
};
class Son:protected Father{
public:
void method(){
    cout<<publicStr<<endl;
    cout<<protectedStr<<endl;
}
};
class GrandSon:public Son{
void method(){
    cout<<publicStr<<endl;
    cout<<protectedStr<<endl;
}

};
int main()
{
    Son s;
     // 子类保护继承时,父类的公共成员,在子类中变成保护成员,类外就访问不到
    //s.publicStr;

}

(3)private继承

基类的public成员基类的protected成员基类的private成员
在子类中访问的访问权限成为子类private成员成为子类private成员不能直接访问

#include <iostream>
using namespace std;
class Father{
public:
    string publicStr;
protected:
    string protectedStr;
private:
    string privateStr;
};

class Son:private Father{
public:
void method(){
    cout<<publicStr<<endl;
    cout<<protectedStr<<endl;
}
};
class GrandSon:public Son{
void method(){
//        cout<<publicStr<<endl;   //这些成员变量在Son类中是私有的,访问不到
//        cout<<protectedStr<<endl;
}

};
int main()
{
    Son s;


}

总结

 2.创建和销毁的执行过程

(1)类中有成员对象情况

类的数据成员也可以时个对象,成为成员对象

      当类中有成员对象时,实例化对象先调用成员对象的构造函数,再调用对象自己的构造函数

//小孔成(成员对象)像    翻着来

#include <iostream>

using namespace std;

//has a
class Engine{
public:
    Engine(){
        cout<<"Engine的构造函数"<<endl;
    }
    ~Engine(){
        cout<<"Engine的析构函数"<<endl;          //对象销毁前的处理
    }
};

class Car{
public:
    Engine e;   //成员对象
    Car(){
        cout<<"Car的构造函数"<<endl;
    }
    ~Car(){
        cout<<"Car的析构函数"<<endl;          //对象销毁前的处理
    }
};

int main(){
    Car c;
}

运行结果: 

 *****先调用成员对象的构造函数,再调用自己的,析构函数时相反*****

(2)继承时情况

      继承时构造和析构的顺序

      先调用父类的构造函数,完成从父类继承部分的初始化。然后调用子类自己的构造函数,完成子类中特有的成员初始化。

#include <iostream>

using namespace std;

class Father{
public:
    Father(){
        cout<<"Father的构造函数"<<endl;
    }
    ~Father(){
        cout<<"Father的析构函数"<<endl;          //对象销毁前的处理
    }
};

class Son:public Father{
public:
    Son(){
        cout<<"Son的构造函数"<<endl;
    }
    ~Son(){
        cout<<"Son的析构函数"<<endl;          //对象销毁前的处理
    }
};

int main(){
    Son s;
}

运行结果:

(3)创建和销毁综合

创建的顺序和销毁的顺序  是对称的

#include <iostream>

using namespace std;
class Value{
private:
    string str;
public:
    Value(string str){
        this->str=str;
        cout<<str<<"创建了"<<endl;
    }
    ~Value(){
        cout<<str<<"销毁了"<<endl;
    }
};

class Father{
public:
    static Value s_v;   //静态成员类内声明 类外初始化
    Value v=Value("父类的成员对象");
    Father(){
        cout<<"Father的构造函数"<<endl;
    }
    ~Father(){
        cout<<"Father的析构函数"<<endl;
    }
};
Value Father::s_v=Value("父类的静态成员对象");

class Son:public Father{
public:
    static Value s_v;//静态成员类内声明,类外初始化
    Value v=Value("子类的成员对象");
    Son(){
        cout<<"Son的构造函数"<<endl;
    }
    ~Son(){
        cout<<"Son的析构函数"<<endl;
    }
};
Value Son::s_v=Value("子类的静态成员对象");

int main(){
    cout<<"--程序开始之前--"<<endl;
        { //局部作用域 栈内存对象出了这个范围(两个花括号之间)就会销毁
            Son s;
        }
    cout<<"---程序结束---"<<endl;

}

运行结果如下:

 3.多继承

一个类的基类有多个就是多继承。可以继承多个基类中的成员

#include <iostream>
using namespace std;
class Bed{
public:
    void lay(){
        cout<<"可以躺着"<<endl;
    }
};
class Sofa{
public:
    void sit(){
        cout<<"可以坐着"<<endl;
    }
};
class SofaBed:public Sofa,public Bed{

};

int main(){
    SofaBed sf;
    sf.lay();
    sf.sit();
}

多继承的问题

多继承实际开发的极少,多继承所增加程序的复杂性和困难性,比他的便利性要高

当多个基类中有重名的成员时,直接访问会产生二义性,可以使用作用域限定符::的方式进行区分

#include <iostream>
using namespace std;
class Bed{
public:
    void lay(){
        cout<<"可以躺着"<<endl;
    }
    void position(){
        cout<<"放在卧室"<<endl;
    }
    int price=4000;
};
class Sofa{
public:
    void sit(){
        cout<<"可以坐着"<<endl;
    }
    void position(){
        cout<<"放在课厅"<<endl;
    }
    int price=2000;
};
class SofaBed:public Sofa,public Bed{

};

int main(){
    SofaBed sf;
    sf.lay();
    sf.sit();
    //沙发床中继承了 Bed::position 和 Sofa::position
    //sf.position(); //会产生歧义 犯了二义性
    sf.Bed::position();
    sf.Sofa::position();
    //cout<<sf.price<<endl;//二义性
    cout<<sf.Sofa::price<<endl;
    cout<<sf.Bed::price<<endl;

}

4.菱形继承(钻石继承)

如果一个基类有两个派生类,这两个派生类有作为父类,派生出子类,这时子类如果直接访问基类成员,也会产生二义性问题

(1)解决方式1:通过作用域限定符的方式进行区分

#include <iostream>
using namespace std;
class Furniture{
public:
    void show(){
       cout<<"一个家具" <<endl;
    }
};

class Bed:public Furniture{
public:

};
class Sofa:public Furniture{
public:

};
class SofaBed:public Sofa,public Bed{
    //从Sofa这条路径得到一个show()
    //从Bed这条路径也得到一个show()
};

int main(){
    SofaBed sf;
    //sf.show();  //ambiguous
    sf.Bed::show();
    sf.Sofa::show();


}

(2)解决方式2:通过虚继承的方式,从公共基类中得到一份成员,避免了二义性

#include <iostream>
using namespace std;
class Furniture{  //虚基类
public:
    void show(){
       cout<<"一个家具" <<endl;
    }
    int a=10;
};

class Bed:virtual public Furniture{
public:

};
class Sofa:virtual public Furniture{
public:

};
class SofaBed:public Sofa,public Bed{
    //从Sofa这条路径得到一个show()
    //从Bed这条路径也得到一个show()
};

int main(){
    SofaBed sf;
    //sf.show();  //ambiguous
    cout<<sf.a<<endl;
    cout<<&(sf.a)<<endl;
    cout<<&(sf.Bed::a)<<endl;
    cout<<&(sf.Sofa::a)<<endl;

}

5.多态

多态按照字面的意思可以认为是“多种状态”,可以简单概括为“一个接口,多种状态”,即程序在运行时动态决定调用的代码。

多态与模板的区别在于,模板针对不同的数据类型采用样的策略,而多态针对不同的数据类型采用不同的策略。

多态条件:

1.公有继承

2.基类的指针或者引用指向派生类的对象

3.子类覆盖基类中的虚函数

    虚函数就是用virtual关键字修饰的函数

(1)子类可以转成基类的情况

     在公有继承情况下:

    基类对象可以用派生类对象赋值

    基类的引用可以指向派生类对象

    基类的指针可以指向派生类对象地址

    上述过程称为向上转换,但是只能够访问基类中公共的部分

#include <iostream>

using namespace std;
class Person{
private:
    string name;
    string sex;
public:
    Person(string name,string sex){
        this->name=name;
        this->sex=sex;
    }
    void show(){
        cout<<"姓名:"<<name<<"性别:"<<sex<<endl;
    }
};
class Employee:public Person{
private:
    double salary;  //子类新增
    int work_id;
public:
    Employee(string name,string sex,double salary,int work_id):Person(name,sex){
        this->salary=salary;
        this->work_id=work_id;
    }
    void show(){
        Person::show();
        cout<<"工资:"<<salary<<"工号:"<<work_id<<endl;
    }

};

int main(){
    Employee e("张三","男",2000,2023001);
    e.show();
    Person p=e;
    p.show();  //姓名:张三 性别 男
    
    //e.Person::show();

    Person& p2=e;
    p2.show();

    Person* p3=&e;
    p3->show();
}

未实现运行多态的问题

没有实现运行时多态出现的问题:只是根据接口参数的类型,调用其方法,并不能根据传入的实际对象类型,调用实际对象的方法。

#include <iostream>
using namespace std;
class Animal{
public:
    void eat(){
        cout<<"吃东西"<<endl;
    }
};

class Cat:public Animal{
public:
    void eat(){
        cout<<"吃猫粮"<<endl;
    }
};

class Dog:public Animal{
public:
    void eat(){
        cout<<"吃狗粮"<<endl;
    }
};

class Panda:public Animal{
public:
    void eat(){
        cout<<"吃竹子"<<endl;
    }
};

//三个类需要三个独立的接口,如果100个需要100个。。很麻烦
//void method(Cat& c){
//    c.eat();
//}
//void method(Dog& d){
//    d.eat();
//}
//void method(Panda * p){
//    p->eat();
//}

//写出公共接口,这样接收Animal的所有子类对象
void method(Animal& a){
    a.eat();
}

void method(Animal *a){
    a->eat();
}

int main(){
    Cat c;
    method(c);     // 吃东西

    Dog dog2;
    method(dog2);    // 吃东西

    Panda panda2;
    method(&panda2);   // 吃东西

}

地址的早绑定:编译的时候已经确定调用哪个类的函数

地址的晚绑定:需要运行的时候再决定调用哪个类的函数,需要用到虚函数,这样才能实现运行时多态

(2)多态的实现

     在派生类中,使用之前的函数隐藏的方式重新实现一个基类中的虚函数,此时就函数覆盖,函数覆盖与虚函数具有以下特点:

1.当函数覆盖成功时,虚函数具有传递性

2.C++11中可以在派生类的新覆盖的函数后增加override关键字进行覆盖是否成功的验证

3.成员函数与析构函数可以定义为虚函数,静态成员函数与构造函数不可以定义为虚函数,因为构造函数不能被派生类继承,也就没办法覆盖重写。静态成员函数与对象无关

4.如果成员函数的声明与定义分离,virtual关键字只需加在声明处

记忆:

传递override来验证

成员析构虚函数

分离只需加声明

重载:同一作用域(类),同名函数,参数的顺序、个数、类型不同都可以重载。函数的返回值类型不能作为重载条件(函数重载、运算符重载)

重定义:有继承,子类重定义父类的同名函数(非虚函数),参数顺序、个数、类型可以不同。

子类的同名函数会屏蔽父类的所有同名函数(如果想访问父类隐藏的函数可以通过作用域解决)

重写(覆盖):有继承,子类重写父类的虚函数,返回值类型、函数名、参数顺序、个数、类型都必须一致。

#include <iostream>
using namespace std;
class Animal{
public:
    //声明和定义分离的 virtual关键字只需加在声明处
    void virtual eat();

};
void Animal::eat(){
    cout<<"吃东西"<<endl;
}
class Cat:public Animal{
public:
    //override用来检测,有没有重写父类中的虚函数
    void eat() override{
        cout<<"吃猫粮"<<endl;
    }
};

class Dog:public Animal{
public:
    void eat(){
        cout<<"吃狗粮"<<endl;
    }
};

class Panda:public Animal{
public:
    void eat(){
        cout<<"吃竹子"<<endl;
    }
};

//三个类需要三个独立的接口,如果100个需要100个。。很麻烦
//void method(Cat& c){
//    c.eat();
//}
//void method(Dog& d){
//    d.eat();
//}
//void method(Panda * p){
//    p->eat();
//}

//写出公共接口,这样接受Animal的所有子类对象
void method(Animal& a){
    a.eat();
}

void method(Animal *a){
    a->eat();
}

int main(){
    Cat c;
    method(c);     // 吃猫粮

    Dog dog2;
    method(dog2);    // 吃狗粮

    Panda panda2;
    method(&panda2);   // 吃竹子

}

(3)多态练习

Role 角色类

Role派生出 AD战士类 AP 法师类 ADC射手类 ,里面都有一个attack()方法

有个公共的接口mehtod,可以接受所有角色的派生类对象,根据传入对象的不同,调用不同的方式

比如传入AD对象 会显示出AD对象的攻击方式

传入AP对象 会显示出AP对象的攻击方式

#include <iostream>

using namespace std;
class Role{
public:
    virtual void attack();
};
void Role::attack(){
    cout<<"攻击方法"<<endl;
}

class AD:public Role{
    void attack(){
        cout<<"AD: "<<"物理伤害"<<endl;
    }
};

class AP:public Role{
    void attack(){
        cout<<"AP: "<<"法术伤害"<<endl;
    }
};

class ADC:public Role{
    void attack(){
        cout<<"ADC: "<<"真实伤害"<<endl;
    }
};

void method(Role& r){
    r.attack();
}
void method(Role* r){
    r->attack();
}
int main(){
    AD d;
    method(d);

    AP p;
    method(&p);

    ADC *c=new ADC;
    method(c);
}

(4)多态缺陷

可能造成内存泄漏

#include <iostream>
using namespace std;

class Animal{
public:
    //声明和定义分离的virtual关键字只需加在声明处
    virtual void eat(){
        cout<<"吃东西"<<endl;
    }
    Animal(){
        cout<<"Animal的构造函数"<<endl;
    }
    ~Animal(){
        cout<<"Animal的析构函数"<<endl;
    }
};

class Cat:public Animal{
public:
    Cat(){
        cout<<"Cat的构造函数"<<endl;
    }
    void eat() override{
        cout<<"吃猫粮"<<endl;
    }
    ~Cat(){
        cout<<"Cat的析构函数"<<endl;
    }
};

//写出公共接口,这样接受Animal的所有子类对象

void method(Animal& a){
    a.eat();
}
void method(Animal *a){
    a->eat();
}
int main(){
    {
    Animal *a=new Cat;
    delete a;
    a=NULL;
    }
}

运行结果:

 这时只会调用Animal的析构函数,Cat的析构函数没有调用,可能会造成Cat中的为成员分配的空间没有释放,造成内存泄漏。

解决方式:基类的析构函数前加virtual关键字

#include <iostream>
using namespace std;
class Animal{
public:
    //声明和定义分离的virtual关键字只需加在声明处
    virtual void eat(){
        cout<<"吃东西"<<endl;
    }
    Animal(){
        cout<<"Animal的构造函数"<<endl;
    }
    virtual ~Animal(){
        cout<<"Animal的析构函数"<<endl;
    }
};

class Cat:public Animal{
public:
    Cat(){
        cout<<"Cat的构造函数"<<endl;
    }
    ~Cat(){
        cout<<"Cat的析构函数"<<endl;
    }
};

//写出公共接口,这样接收Animal的所有子类对象
void method(Animal& a){
    a.eat();
}
void method(Animal *a){
    a->eat();
}


int main(){
    {
        Animal *a=new Cat;
        delete a;
        a=NULL;

    }
}

运行结果如下:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值