目录
(2)解决方式2:通过虚继承的方式,从公共基类中得到一份成员,避免了二义性
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;
}
}
运行结果如下: