【C++】继承和派生

继承和派生

1.概念和作用

  • 继承:
    描述的是生活中is-a这种关系(公有继承)
    Cat继承Animal类 Cat is an Animal
  • 派生:
    Animal派生出Cat
    子类(派生类):Cat就是子类(派生类)
    父类(基类):被继承的那个类就是父类

作用:
\quad 提高代码的复用性(提供可重用的代码)
子类如果继承了父类,子类可以直接使用父类的公有方法

示例代码:继承的语法
cpp 复制代码
#include <iostream>
using namespace std;

/*
    继承的语法规则,继承的好处
    语法规则:
        class 子类:权限修饰符 父类
        {
            
        }
    继承的好处:
        子类可以使用父类的公有成员
*/

class Animal{
public:
    void eat()
    {
        cout<<"Animal类的方法:void eat()"<<endl;
    }
};

class Dog : public Animal{
public:
    
};

int main(int argc, char const *argv[])
{
    Dog dog;
    dog.eat(); // //子类可以调用父类的公有方法
    return 0;
}

/*
执行结果:
    Animal类的方法:void eat()
*/

2.语法规则

cpp 复制代码
class  子类的名字:public     父类的名字      //公有继承(最多)
class  子类的名字:private    父类的名字      //私有继承   
class  子类的名字:protected  父类的名字      //保护继承
{
        子类的成员
};

私有继承和保护继承用来描述has-a(包含、拥有关系)这种关系

私有继承:父类派生出子类以后,子类不能再继续私有派生孙子类

保护继承:父类派生出子类以后,子类可以继续保护派生孙子类

如果继承的时候没有写权限修饰词,默认是私有继承

cpp 复制代码
比如:class Cat:Animal  //猫私有继承了Animal
{

}; 

3.继承方式

继承方式有三种: 公有继承(public)、保护继承(protected)、私有继承(private)

上面表格权限是基类中的成员继承到子类后的成员权限。

1)如果派生类在继承基类的时候选择 公有继承(public)
\quad 那么基类的公有成员就是在派生类中也是充当公有成员,可以直接在派生类的内部和外部使用
\quad 那么基类的保护成员就是在派生类中也是充当保护成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么 基类的私有成员可以继承,但是不能直接访问,可以通过基类的公有方法和保护方法间接访问

2)如果派生类在继承基类的时候选择 保护继承(protected)
\quad 那么基类的公有成员就是在派生类中是充当保护成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么基类的保护成员就是在派生类中是充当保护成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么 基类的私有成员可以继承,但是不能直接访问,可以通过基类的公有方法和保护方法间接访问

3) 如果派生类在继承基类的时候选择 私有继承(private)
\quad 那么基类的公有成员就是在派生类中是充当私有成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么基类的保护成员就是在派生类中是充当私有成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么 基类的私有成员可以继承,但是不能直接访问,可以通过基类的公有方法和保护方法间接访问

示例代码1:公有继承
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    子类公有继承父类:子类究竟可以使用父类的哪些成员
    站在两个角度看问题
        角度1:子类外部,子类定义花括号之外
        角度2:子类内部,子类定义花括号之内(成员函数代码写在类的外面也算)
    父类的保护成员是专门留给子类的
    父类的私有成员是专门留给自己的
*/
class Animal
{
public:
    void eat()
    {
        cout<<"动物吃"<<endl;
    }
    int age;
private:
    void sleep()
    {
        cout<<"动物睡觉"<<endl;
    }
    float weight;
protected:
    void run()
    {
        cout<<"动物跑步"<<endl;
    }
    int color;
};
class Cat:public Animal
{
public:
    //子类的内部
    void fun()
    {
        //子类内部:父类的公有成员(可使用)
        eat();
        age=5;
        
        //子类内部:父类的私有成员(不可使用)
        //sleep();
        //weight=12.5;
        
        //子类内部:父类的保护成员(可使用)
        run();
        color=0xffffff;
    }

};

int main(int argc,char **argv)
{
    Cat c1;
    
    //子类外部:父类的公有成员(可使用)
    //c1.eat();
    //c1.age=5;
    
    //子类外部:父类的私有成员(不可使用)
    //c1.sleep();
    //c1.weight=12.5;
    
    //子类外部:父类的保护成员(不可使用)
    //c1.run();
    //c1.color=0xffffff;
    
    c1.fun();
    return 0;   
}
示例代码2:私有继承
cpp 复制代码
#include<iostream>

using namespace std;

//基类
class Base{
    
public:
        Base(int data=100):baseData(data){
            cout<<"Base()"<<endl;
        }
        ~Base(){
            cout<<"~Base()"<<endl;
        }
        void setData(int data){
            baseData = data;
        }
        int getData(){
            return baseData;
        }
protected:
        void showData(){
            cout<<"baseData:"<<baseData<<endl;
        }
private:
        int baseData;
};

class Child:private Base{
    
public:
        Child(int data=20):Base(data){
            showData();
            cout<<"Child()"<<endl;
        }
        ~Child(){
            cout<<"~Child()"<<endl;
        }
};
int main()
{
    Child mya(200);
    
    //mya.setData(1000);
    
    return 0;
}

4.子类的大小

规则:子类的大小=父类数据成员变量的和+子类本身数据成员的和(去除static修饰的成员变量,也要满足字节对齐)
子类大小底层原理:

示例代码:
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    子类大小=父类+子类所有成员变量的和,也要满足字节对齐
*/
class Animal
{
public:
    void eat()
    {
        cout<<"动物吃"<<endl;
    }
    int age;
private:
    void sleep()
    {
        cout<<"动物睡觉"<<endl;
    }
    float weight;
protected:
    void run()
    {
        cout<<"动物跑步"<<endl;
    }
    int color;
};
class Cat:public Animal
{
public:

private:
    double n;
};

int main(int argc,char **argv)
{
    Cat c1;
    cout<<"子类猫的大小: "<<sizeof(Cat)<<endl;
    
    return 0;    
}

/*
执行结果:
    子类猫的大小: 24
*/

5.继承以后,构造和析构调用规则

原理:子类对象的地址空间中包含了父类的一个副本,所以在新建子类对象的时候,会先构建父类,再构建子类

5.1 先调用父类的构造(无参构造),然后在调用子类的构造

示例代码:继承后,构造函数的调用规则
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    继承之后,构造析构的调用规则
*/
class Animal
{
public:
    Animal()
    {
        cout<<"父类Animal无参构造了"<<endl;
    }
    Animal(int m)
    {
        cout<<"父类Animal带int类型参数构造了"<<endl;
    }
};
class Cat:public Animal
{
public:
    Cat()
    {
        cout<<"子类Cat无参构造了"<<endl;
    }
    Cat(int n)
    {
        cout<<"子类Cat带int参数构造了"<<endl;
    }
    Cat(int n,string name)
    {
        cout<<"子类Cat带int和string参数构造了"<<endl;
    }
};

int main(int argc,char **argv)
{
    Cat c1;
    Cat c2(666);
    Cat c3(888,"旺财");
    return 0;   
}

/*
执行结果:
    父类Animal无参构造了
    子类Cat无参构造了
    父类Animal无参构造了
    子类Cat带int参数构造了
    父类Animal无参构造了
    子类Cat带int和string参数构造了
*/

注意: 默认情况下,编译器调用的是父类的无参构造函数(子类调用的构造函数根据实际需求来调用),如果父类没有无参构造函数,编译出错

5.2 先析构子类,再析构父类

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    继承之后,析构函数的调用规则
*/
class Animal
{
public:
    Animal()
    {
        cout<<"父类Animal无参构造了"<<endl;
    }
    Animal(int m)
    {
        cout<<"父类Animal带int类型参数构造了"<<endl;
    }
    ~Animal()
    {
        cout<<"父类Animal析构了"<<endl;
    }
};
class Cat:public Animal
{
public:
    Cat()
    {
        cout<<"子类Cat无参构造了"<<endl;
    }
    Cat(int n)
    {
        cout<<"子类Cat带int参数构造了"<<endl;
    }
    Cat(int n,string name)
    {
        cout<<"子类Cat带int和string参数构造了"<<endl;
    }
    ~Cat()
    {
        cout<<"子类Cat析构了"<<endl;
    }
};

int main(int argc,char **argv)
{
    Cat c1;
    Cat c2(666);
    Cat c3(888,"旺财");
    return 0;   
}

/*
执行结果:
    父类Animal无参构造了
    子类Cat无参构造了
    父类Animal无参构造了
    子类Cat带int参数构造了
    父类Animal无参构造了
    子类Cat带int和string参数构造了
    子类Cat析构了
    父类Animal析构了
    子类Cat析构了
    父类Animal析构了
    子类Cat析构了
    父类Animal析构了
*/

5.3 程序员指定要调用父类某个版本的构造函数

cpp 复制代码
子类构造(形参列表):父类构造(传递给父类的实参)
{

}
示例代码:继承后子类指定要调用父类某个版本的构造函数
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    默认情况下:创建子类,都是调用父类的无参构造
                程序员想要指定调用父类的某个版本的构造,该如何实现?
                    
*/
class Animal
{
public:
    Animal()
    {
        cout<<"父类Animal无参构造了"<<endl;
    }
    Animal(int m)
    {
        cout<<"父类Animal带int类型参数构造了,参数是: "<<m<<endl;
    }
};
class Cat:public Animal
{
public:
    Cat():Animal(123)
    {
        cout<<"子类Cat无参构造了"<<endl;
    }
    Cat(int n)
    {
        cout<<"子类Cat带int参数构造了"<<endl;
    }
    Cat(int n,string name):Animal(1258)
    {
        cout<<"子类Cat带int和string参数构造了"<<endl;
    }
};

int main(int argc,char **argv)
{
    Cat c1;
    Cat c2(666);
    Cat c3(888,"旺财");
    return 0;   
}

/*
执行结果
    父类Animal带int类型参数构造了,参数是: 123   ----->在子类构造函数进行指定
    子类Cat无参构造了
    父类Animal无参构造了
    子类Cat带int参数构造了
    父类Animal带int类型参数构造了,参数是: 1258 ----->在子类构造函数进行指定
    子类Cat带int和string参数构造了
*/

6.子类出现跟父类同名的方法(子类隐藏父类的同名方法)

区分方法:

c1.eat() //使用子类自己的eat

c1.Animal::eat() //使用父类的eat

示例代码:子类隐藏了父类的同名方法
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    子类和父类出现同名方法
    经典的面试问题?
       请问 重载和隐藏有什么区别?
    隐藏:  
       第一: 必须是发生在父子类之间 
       第二:子类必须要重新定义跟父类相同名字的函数,参数可以相同,也可以不相同
             此时子类会隐藏父类的同名方法,隐藏的具体体现,子类对象无法直接调用父类的同名方法了,必须子类对象.父类名::同名方法()   
*/
class Animal
{
public:
    void eat()
    {
        cout<<"动物吃"<<endl;
    }
};
class Cat:public Animal
{
public:
    void eat()
    {
        cout<<"猫吃鱼"<<endl;
    }

};

int main(int argc,char **argv)
{
    Cat c1;
    //此时通过子类对象无法直接调用父类的eat(父类同名方法被隐藏)
    c1.eat(); 
    //程序员如果一定要调用父类的同名
    c1.Animal::eat();

    return 0;   
}

/*
执行结果:
    猫吃鱼
    动物吃
*/

C++中隐藏,重载,重写(复写,覆盖)三个概念的区别?

(1)隐藏的概念

第一: 必须是发生在父子类之间

第二:子类必须要重新定义跟父类相同名字的函数,参数可以相同,也可以不相同

此时子类会隐藏父类的同名方法,隐藏的具体体现,子类对象无法直接调用父类的同名方法了,必须子类对象.父类名::同名方法()

示例代码:隐藏的使用
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    子类和父类出现同名方法
    经典的面试问题?
       请问 重载和隐藏有什么区别?
    隐藏:  
       第一: 必须是发生在父子类之间 
       第二:子类必须要重新定义跟父类相同名字的函数,参数可以相同,也可以不相同
             此时子类会隐藏父类的同名方法,隐藏的具体体现,子类对象无法直接调用父类的同名方法了,必须子类对象.父类名::同名方法()   
*/
class Animal
{
public:
    void eat()
    {
        cout<<"动物吃"<<endl;
    }
};
class Cat:public Animal
{
public:
    void eat(string food)
    {
        cout<<"猫吃"<<food<<endl;
    }

};

int main(int argc,char **argv)
{
    Cat c1;
    //此时通过子类对象无法直接调用父类的eat(父类同名方法被隐藏)
    // 错误,父类的同名方法被隐藏了,无法直接调用
    // c1.eat();  // 错误信息  error: no matching function for call to 'Cat::eat()'
    // 正确
    c1.Animal::eat(); 
    c1.eat("鱼");
    return 0;   
}

/*
执行结果:
    动物吃
    猫吃大鲨鱼
*/

(2)重载的概念(要求相同作用域)

第一:必须发生在同一个类的里面

第二:函数名相同,参数的类型或者个数至少有一个不同

示例代码:同一个类中的函数重载
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

/*
    子类和父类出现同名方法
    经典的面试问题?
       请问 重载和隐藏有什么区别?
    隐藏:  
       第一: 必须是发生在父子类之间 
       第二:子类必须要重新定义跟父类相同名字的函数,参数可以相同,也可以不相同
             此时子类会隐藏父类的同名方法,隐藏的具体体现,子类对象无法直接调用父类的同名方法了,必须子类对象.父类名::同名方法()   
*/
class Animal
{
public:
    //父类的eat被子类的两个eat给隐藏了
    void eat()
    {
        cout<<"动物吃"<<endl;
    }
};
class Cat:public Animal
{
public:
    //函数重载:发生在同一个类里面,第27行和第31两个eat就是函数重载
    void eat(string food)
    {
        cout<<"猫吃"<<food<<endl;
    }
    void eat()
    {
        cout<<"无参"<<endl;
    }

};

int main(int argc,char **argv)
{
    Cat c1;
    c1.eat();
    c1.eat("骨头");

    return 0;   
}

/*
执行结果:
    无参
    猫吃骨头
*/
相关推荐
wennieFan5 分钟前
python基础面试练习题
开发语言·python
阿福不是狗7 分钟前
Python使用总结之Linux部署python3环境
linux·开发语言·python
枣伊吕波15 分钟前
第十三节:第七部分:Stream流的中间方法、Stream流的终结方法
java·开发语言
一点也不想取名28 分钟前
解决 Java 与 JavaScript 之间特殊字符传递问题的终极方案
java·开发语言·javascript
im_AMBER1 小时前
java复习 11
java·开发语言
Cai junhao1 小时前
【Qt】工具介绍和信号与槽机制
开发语言·c++·qt·qt6.3
黑牛先生1 小时前
【Qt】信号与槽
开发语言·qt
橙子199110162 小时前
Kotlin 中的 Object
android·开发语言·kotlin
callJJ2 小时前
从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(1)
java·开发语言·spring boot·后端·spring·restful·ioc di
Python开发吖3 小时前
【已解决】python的kafka-python包连接kafka报认证失败
开发语言·python·kafka