继承和派生
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;
}
/*
执行结果:
无参
猫吃骨头
*/