目录
1 继承---面向对象三大特性之一
1.1 继承的基本语法
作用:减少重复的代码,(A类==子类==派生类;B类==父类==派生类)
语法:class 子类:继承方式 父类
class A:public B;
派生类中的成员包含两大部分:(1)一类是从基类继承过来的;(2)一类是自己增加的成员;
从基类继承过来的表现其共性,而新增的成员体现了其个性。
1.2 继承方式
三种:(1)公共继承;(2)保护继承;(3)私有继承;
1.3 继承中的对象模型
//------------练习22:继承中的对象模型-------------------
class Base//父类,基类
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son:public Base//子类
{
public:
int m_D;
};
int main()
{
Son test;//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性,是被编译器隐藏了,因此访问不到,但确实被继承下去了
cout<<sizeof(test)<<endl;//输出为16
return 0;
}
1.4 构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
总结:继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
1.5 同名成员处理
当子类和父类出现同名的成员,通过子类对象访问->
(1)访问子类同名成员,直接访问;(2)访问父类同名成员,需要加作用域
//同名成员属性处理
Son S;
cout<<"子类对象访问同名子类成员"<<S.m_A<<endl;
cout<<"子类对象访问同名父类成员"<<S.Base::m_A<<endl;
//同名成员函数处理
S.func();//直接调用,调用是子类中的同名成员
S.Base::func();//加作用域,调用是父类中的同名成员函数
注意:子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数,加作用域才可访问
1.6 同名静态成员处理---与同名成员处理一致
静态成员特点:共用同一份数据;编译就分配内存;类内声明,类外初始化;
//-------------练习24:子类和父类静态同名成员------------
//静态变量:共用同一份;编译分配内存;类内声明,类外初始化
class Base
{
public:
static int m_A;
};
int Base::m_A=100;//类外初始化
class Son:public Base
{
public:
static int m_A;
};
int Son::m_A=200;//类外初始化
void test01()//同名静态变量访问
{
//1、子类对象访问静态成员变量
Son S1;
cout <<"通过子类对象访问"<<endl;
cout<<"子类静态成员变量:"<<S1.m_A<<endl;
cout<<"父类静态成员变量:"<<S1.Base::m_A<<endl;
//2、子类类名访问静态成员变量
cout <<"通过子类类名访问"<<endl;
cout<<"子类静态成员变量:"<<Son::m_A<<endl;
cout<<"父类静态成员变量:"<<Son::Base::m_A<<endl;
//第一个::代表通过类名方式访问静态,第二个::代表访问父类作用域下
}
int main()
{
test01();
return 0;
}
总结:同名静态成员处理方式和非静态一样,只不过有两种访问方式(通过对象和通过类名)
1.7 多继承语法---C++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2
多继承可能会引发父类中有同名成员出现,需要加作用域区分---C++实际开发中不建议用多继承
class Son:public Base1,public Base2
{};
//当父类中出现同名成员,需要加作用域区分
cout<<"Base1::m_A="<<s.Base1::m_A<<endl;
cout<<"Base2::m_A="<<s.Base2::m_A<<endl;
1.8 菱形继承问题以及解决方法
1、菱形继承概念:
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,称为菱形继承,或钻石继承
class Animal//虚基类
{
public:
int m_Age;
};
class Sheep:virtual public Animal//虚继承
{
};
class Camel:virtual public Animal//虚继承
{
};
class Alpaca:public Sheep,public Camel
{
};
int main()
{
Alpaca A;
A.Sheep::m_Age=18;
A.Camel::m_Age=20;
cout<<"A.Sheep::m_Age"<<A.Sheep::m_Age<<endl;//20
cout<<"A.Camel::m_Age"<<A.Camel::m_Age<<endl;//20
cout<<"A.m_Age"<<A.m_Age<<endl;//20
return 0;
}
总结:
(1)菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义;
(2)利用虚继承可以解决菱形继承问题
2 多态---面向对象三大特性之一
2.1 多态基本概念
1、多态分两类
(1)静态多态:函数重载和运算符重载属于静态多态,复用函数名;
(2)动态多态:派生类和虚函数实现运行多态
2、静态多态和动态多态区别:
(1)静态多态的函数地址早绑定-编译阶段确定函数地址;
(2)动态多态的函数地址晚绑定-运行阶段确定函数地址;
//--------------练习26:多态的基本语法------------------------
//多态的条件:(1)有继承关系;(2)子类重写父类的虚函数
//动态多态的使用:父类的指针或者引用,执行子类对象
class Animal
{
public:
virtual void speak()//虚函数
{
cout<<"动物说话"<<endl;
}
};
class Cat:public Animal//继承父类
{
public:
//子类函数重写:函数返回值类型,函数名,参数列表,完全相同
void speak()
{
cout<<"小猫说话"<<endl;
}
};
class Dog:public Animal//继承父类
{
public:
void speak()
{
cout<<"小狗说话"<<endl;
}
};
//地址早绑定,在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void DoSpeak(Animal& animal)//Animal& animal cat;
{
animal.speak();
}
int main()
{
Dog dog;
DoSpeak(dog);
Cat cat;
DoSpeak(cat);
return 0;
}
总结:1、多态满足条件:(1)有继承关系;(2)子类重写父类中的虚函数
2、多态使用条件---父类指针或引用指向子类对象
3、重写:函数返回值类型 函数名 参数列表 完全一致称为重写
2.2 多态的原理剖析
多态的优点:
(1)代码组织结构清晰;(2)可读性强;(3)利于前期和后期的扩展以及维护
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
2.3 纯虚函数和抽象类
1、纯虚函数
在多态中,通常父类中的虚函数的实现是毫无意义的,主要都是调用子类重写的内容。因此可以将虚函数改为纯虚函数,
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:(1)无法实例化对象;(2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类
2.4 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么福类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:(1)可以解决父类指针释放子类对选哪个;(2)都需要有具体的函数实现
虚析构和纯虚析构区别:(1)如果是纯虚析构,该类属于抽象类,无法实例化对象;(2)纯虚析构,需要声明也需要实现,实现式函数加作用域
虚析构语法:virtual ~类名(){}
纯虚构语法:virtual ~类名()=0;类名::~类名(){}
//-------------------练习29:虚析构和纯虚析构函数-------------------
//在利用多态时,父类指针或引用指向子类对象,如果子类对象中有堆区数据
//多态时执行不到子类的析构函数的,此时需要父类析构变成虚析构或者纯虚析构
class Animal//父类,纯虚,抽象类,不可以实例化对象
{
public:
Animal()//父类构造函数
{
cout<<"Animal构造函数调用"<<endl;
}
virtual void speak()=0;//纯虚函数,多态入口
//virtual ~Animal()//虚析构函数
//{
// cout<<"Animal析构函数调用"<<endl;
//}
virtual ~Animal()=0;//纯虚构函数,类内声明,类外实现
};
Animal::~Animal()//纯虚析构函数,类外声明
{
cout<<"Animal析构函数调用"<<endl;
}
class Cat:public Animal//继承父类
{
public:
Cat(string name)//子类构造函数
{
m_Name=new string(name);//在堆区创建数据并返回指针
cout<<"Cat构造函数调用"<<endl;
}
void speak()//子类必须重写父类虚函数
{
cout<<"小猫说话"<<endl;
}
string* m_Name;//为了有堆区数据,因此使用指针
~Cat()//子类析构函数,必须释放堆区内存
{
if(m_Name!=NULL)
{
delete m_Name;
m_Name=NULL;
cout<<"Cat析构函数调用"<<endl;
}
}
};
void test()
{
Animal* animal=new Cat("Tom");//堆区开辟数据,父类指针指向子类对象
animal->speak();
delete animal;
}
int main()
{
test();
return 0;
}
总结:(1)虚析构和纯虚析构就是用来解决通过父类指针释放子类对象;
(2)如果子类中没有堆区数据,可以不写为虚析构和纯虚析构;
(3)拥有纯虚析构函数的类也属于抽象类;
2.5 案例
//--------------------练习30:多态案例三:电脑组装-------------------
//电脑主要组合部件为CPU(计算),显卡(显示),内存条(存储)
//将每个零件封装出抽象基类,并且提供不同厂商生产不同的零件,例如Intel和Lenovo厂商
//创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三台不同的电脑进行工作
//1、每个零件创建一个父类
class CPU//基类,抽象类
{
public:
virtual void Caculator()=0;//虚函数
};
class VidoCard
{
public:
virtual void Display()=0;//虚函数
};
class Memory
{
public:
virtual void Storage()=0;//虚函数
};
//2、创建一个电脑类
class Computer
{
public:
Computer(CPU* cpu,VidoCard* vc,Memory* mem)//构造函数初始化,电脑组装零件
{
m_cpu=cpu;
m_vc=vc;
m_mem=mem;
}
void DoWork()
{
m_cpu->Caculator();//CPU计算
m_vc->Display();//显卡显示
m_mem->Storage();//内存条存储
}
CPU* m_cpu;
VidoCard* m_vc;
Memory* m_mem;
};
//3、创建零件
//Intel厂商零件(子类)
class IntelCPU:public CPU
{
public:
void Caculator()
{
cout<<"IntelCPU计算"<<endl;
}
};
class IntelVidoCard:public VidoCard
{
public:
void Display()
{
cout<<"IntelVidoCard显示"<<endl;
}
};
class IntelMemory:public Memory
{
public:
void Storage()
{
cout<<"IntelMemory存储"<<endl;
}
};
//Lenovo厂商零件子类
class LenovoCPU:public CPU
{
public:
void Caculator()
{
cout<<"LenovoCPU计算"<<endl;
}
};
class LenovoVidoCard:public VidoCard
{
public:
void Display()
{
cout<<"LenovoVidoCard显示"<<endl;
}
};
class LenovoMemory:public Memory
{
public:
void Storage()
{
cout<<"LenovoMemory存储"<<endl;
}
};
void test()
{
//父类指针或者引用指向子类对象
CPU* cpu=new IntelCPU;
VidoCard* vc=new IntelVidoCard;
Memory* mem=new IntelMemory;
//组装电脑
Computer computer(cpu,vc,mem);
//电脑运行
computer.DoWork();
}
int main()
{
test();
return 0;
}