【C++】继承和派生
继承和派生
- 1.概念和作用
- 示例代码:继承的语法
- 2.语法规则
- 3.继承方式
- 示例代码1:公有继承
- 示例代码2:私有继承
- 4.子类的大小
- 示例代码:
- 5.继承以后,构造和析构调用规则
- 5.1 先调用父类的构造(无参构造),然后在调用子类的构造
- 示例代码:继承后,构造函数的调用规则
- 5.2 先析构子类,再析构父类
- 5.3 程序员指定要调用父类某个版本的构造函数
- 示例代码:继承后子类指定要调用父类某个版本的构造函数
- 6.子类出现跟父类同名的方法(子类隐藏父类的同名方法)
- 示例代码:子类隐藏了父类的同名方法
- C++中隐藏,重载,重写(复写,覆盖)三个概念的区别?
- (1)隐藏的概念
- 示例代码:隐藏的使用
- (2)重载的概念(要求相同作用域)
- 示例代码:同一个类中的函数重载
1.概念和作用
- 继承:
描述的是生活中is-a这种关系(公有继承)
Cat继承Animal类 Cat is an Animal - 派生:
Animal派生出Cat
子类(派生类):Cat就是子类(派生类)
父类(基类):被继承的那个类就是父类
作用:
\quad 提高代码的复用性(提供可重用的代码)
子类如果继承了父类,子类可以直接使用父类的公有方法
示例代码:继承的语法
#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.语法规则
class 子类的名字:public 父类的名字 //公有继承(最多)
class 子类的名字:private 父类的名字 //私有继承
class 子类的名字:protected 父类的名字 //保护继承
{子类的成员
};
私有继承和保护继承用来描述has-a(包含、拥有关系)这种关系
私有继承:父类派生出子类以后,子类不能再继续私有派生孙子类
保护继承:父类派生出子类以后,子类可以继续保护派生孙子类
如果继承的时候没有写权限修饰词,默认是私有继承
比如:class Cat:Animal //猫私有继承了Animal
{};
3.继承方式
继承方式有三种: 公有继承(public)、保护继承(protected)、私有继承(private)
上面表格权限是基类中的成员继承到子类后的成员权限。
1)如果派生类在继承基类的时候选择 公有继承(public)
\quad 那么基类的公有成员就是在派生类中也是充当公有成员,可以直接在派生类的内部和外部使用
\quad 那么基类的保护成员就是在派生类中也是充当保护成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么 基类的私有成员可以继承,但是不能直接访问,可以通过基类的公有方法和保护方法间接访问
2)如果派生类在继承基类的时候选择 保护继承(protected)
\quad 那么基类的公有成员就是在派生类中是充当保护成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么基类的保护成员就是在派生类中是充当保护成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么 基类的私有成员可以继承,但是不能直接访问,可以通过基类的公有方法和保护方法间接访问
3) 如果派生类在继承基类的时候选择 私有继承(private)
\quad 那么基类的公有成员就是在派生类中是充当私有成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么基类的保护成员就是在派生类中是充当私有成员,可以在派生类的内部使用,但是不能外部使用
\quad 那么 基类的私有成员可以继承,但是不能直接访问,可以通过基类的公有方法和保护方法间接访问
示例代码1:公有继承
#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:私有继承
#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修饰的成员变量,也要满足字节对齐)
子类大小底层原理:
示例代码:
#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 先调用父类的构造(无参构造),然后在调用子类的构造
示例代码:继承后,构造函数的调用规则
#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 先析构子类,再析构父类
#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 程序员指定要调用父类某个版本的构造函数
子类构造(形参列表):父类构造(传递给父类的实参)
{}
示例代码:继承后子类指定要调用父类某个版本的构造函数
#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
示例代码:子类隐藏了父类的同名方法
#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)隐藏的概念
第一: 必须是发生在父子类之间
第二:子类必须要重新定义跟父类相同名字的函数,参数可以相同,也可以不相同
此时子类会隐藏父类的同名方法,隐藏的具体体现,子类对象无法直接调用父类的同名方法了,必须子类对象.父类名::同名方法()
示例代码:隐藏的使用
#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)重载的概念(要求相同作用域)
第一:必须发生在同一个类的里面
第二:函数名相同,参数的类型或者个数至少有一个不同
示例代码:同一个类中的函数重载
#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;
}/*
执行结果:无参猫吃骨头
*/