目录
一,对象的初始化和清理
1、构造函数和析构函数
2、构造函数的分类及调用
3、拷贝构造函数调用时机
4、构造函数调用规则
5、深拷贝与浅拷贝
6、初始化列表
7、类对象作为类成员
8、静态成员
二,对象模型和this指针
1、成员变量和成员函数分开存储
2、this指针概念
3、空指针访问成员函数
4、const修饰成员函数
三、友元
1、全局函数作友元
2、类作友元
3、成员函数作友元
四、运算符重载
1、加号运算符重载
2、左移运算符重载
3、递增运算符重载
4、赋值运算符重载
5、关系运算符重载
6、函数调用运算符重载
五、继承
1、继承基本语法
2、继承方式
3、继承中的对象模型
4、继承中的构造和析构顺序
5、继承同名成员处理方式
6、继承同名静态成员处理方式
7、多继承语法
8、菱形继承
六、多态
1.多态的基本概念
2、多态案例--计算器实现
3、纯虚函数和抽象类
4、多态案例--制作饮品
5、虚析构和纯虚析构
6、多态案例--电脑组装
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1,构造函数,没有返回值也不写void
2,函数名称与类名相同
3,构造函数可以有参数,因此可以发生重载
4,程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
1,析构函数,没有返回值也不写void
2,函数名称与类名相同,在名称前加上符号~
3,析构函数不可以有参数,因此不可以发生重载
4,程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
class Person
{
public:
//1,1、构造函数
Person()
{
cout<<"Person 的构造函数调用"<
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
1,括号法 2,显示法 3,隐式转换法
//构造函数的分类及调用
//分类
// 按照参数分类 无参构造(默认构造)和有参构造
// 按照类型分类 普通构造 拷贝构造
class Person
{
public:
//普通构造函数 无参构造
Person()
{
cout<<"Person 的无参构造函数调用"<
c++中拷贝构造函数调用时机通常有三种情况:
1,使用一个已经创建完毕的对象来初始化一个新对象
2,值传递的方式给函数参数传值
3,以值方式返回局部对象
//拷贝构造函数的调用时机
//1,使用一个已经创建完毕的对象来初始化一个新对象
//2,值传递的方式给函数参数传值
//3,值方式返回局部对象
class Person
{
public:
Person()
{
cout<<"Person 的默认构造函数"<
默认情况下,c++编译器至少给一个类添加3个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
1、如果用户定义有参数构造函数,c++不在提供默认无参构造函数,但是会提供默认拷贝构造
2、如果用户定义拷贝构造函数,c++不会再提供其他构造函数
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝:简单的赋值拷贝操作
//如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
class Person
{
public:
//无参(默认)构造函数
Person()
{
cout<<"无参构造函数!"<
作用:c++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)……{}
class Person
{
public:
//传统方式初始化
// Person(int a,int b,int c)
// {
// m_a=a;
// m_b=b;
// m_c=c;
// }
//初始化列表方式初始化
Person(int a,int b,int c):m_a(a),m_b(b),m_c(c){}
void printPerson()
{
cout<<"m_a: "<
c++类的成员可以是另一个类的对象,我们称该成员为对象成员
例如:
class A{}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
//手机类
class Phone
{
public:
Phone(string pName)
{
cout<<"Phone的构造函数调用"<
静态成员就是再成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
1.静态成员变量
1.1所有对象共享同一份数据
1.2在编译阶段分配内存
1.3类内声明,类外初始化
2.静态成员函数
1.1所有对象共享同一个函数
1.2静态成员函数只能访问静态成员变量
#include
using namespace std;
//静态成员变量
class Person
{
public:
//1所有对象共享同一份数据
//2在编译阶段分配内存
//3类内声明,类外初始化
static int m_a;
//静态成员变量也是有访问权限的
private:
static int m_b;
};
//Person作用域下的m_a初始化
int Person::m_a=100;
int Person::m_b=200;
void test01()
{
Person p;
cout<
在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
#include
using namespace std;
//成员变量和成员函数分开存储
class Person
{
int m_a;//非静态成员变量 属于类的对象上
static int m_b;//静态成员变量 不属于类的对象上
void func(){}//非静态成员函数 不属于类的对象上
static void func2(){}//静态成员函数 不属于类的对象上
};
int Person::m_b = 0;
void test01()
{
Person p;
//空对象占用内存空间为:1
//c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout<<"size of p = "< 2、this指针概念
通过上一小节,我们知道c++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
答:c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用即可
this指针的用途:
1.当形参和成员变量同名时,可用this指针来区分
2.在类的非静态成员函数中返回对象本身,可使用return *this
#include
using namespace std;
class Person
{
public:
Person(int age)
{
//this指针指向被调用的成员函数所属的对象
this->age=age;
}
Person& PersonAddAge(Person& p)//注意返回值要为引用
{
this->age+=p.age;
//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
}
int age;
};
//1、解决名称冲突
void test01()
{
Person p1(18);
cout<<"p1的年龄为: "<
3、空指针访问成员函数
c++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include
using namespace std;
class Person
{
public:
void showClassName()
{
cout<<"this is Person class"<m_age<showClassName();
p->showPersonAge();
}
int main()
{
test01();
return 0;
}
4、const修饰成员函数
常函数:
1.成员函数后加const后我们可以称这个函数为常函数
2.常函数内不可以修改成员属性
3.成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
1.声明对象前加const称该对象为常对象
2.常对象只能调用常函数
#include
using namespace std;
class Person
{
public:
//this指针的本质 是指针常量 指针的指向是不可修改的
//this指针 等价于 Person* const this;
//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
void showPerson()const//等价于 const Person* const this
{
this->m_b=100;
// this->m_a=100;//错误,原因如上
//this=NULL //错误,原因如上
}
void fun()
{
}
int m_a;
mutable int m_b;//特殊变量,即使在常含数中,也可以修改这个值
};
void test01()
{
Person p;
p.showPerson();
}
//常对象
void test02()
{
const Person p{};//在对象前加const,变为常对象
// p.m_a=100;//报错
p.m_b=100;//m_b是特殊值,在常对象也可以修改
//常对象只能调用常函数
p.showPerson();//正确
// p.func();//报错 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main()
{
test01();
test02();
return 0;
}
三、友元
生活中你的家有客厅(public),有你的卧室(private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。友元的目的就是让一个函数或者类访问另一个类中私有成员
友元关键字为friend
1、全局函数作友元
#include
using namespace std;
//建筑物类
class Building
{
//GoodGay全局函数是Building的好朋友,可以访问Building中私有成员
friend void GoodGay(Building &building);
public:
Building()
{
m_SittingRoom="客厅";
m_BedRoom="卧室";
}
public:
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//全局函数
void GoodGay(Building &building)
{
cout<<"好基友的全局函数正在访问"<
2、类作友元
#include
using namespace std;
class Building;
class GoodGay
{
public:
GoodGay();//构造函数
void visit();//参观函数访问Building中的属性
Building* building;
};
class Building
{
//GoodGay类是本类的好朋友,可以访问本类中私有成员
friend class GoodGay;
public:
Building();//构造函数
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//类外成员函数
Building::Building()
{
m_SittingRoom="客厅";
m_BedRoom="卧室";
}
GoodGay::GoodGay()
{
//创建建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout<<"好基友类正在访问:"<m_SittingRoom<m_BedRoom<
3、成员函数作友元
#include
using namespace std;
class Building;
class GoodGay
{
public:
GoodGay();
void visit();//让visit函数可以访问Building中私有成员
void visit2();//让visit2函数不可以访问Building中私有成员
Building* building;
};
class Building
{
//告诉编译器 GoodGay类中的visit成员函数是Building好朋友,可以访问私有内容
friend void GoodGay::visit();
public:
Building();//构造函数
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//类外实现成员函数
Building::Building()
{
m_SittingRoom="客厅";
m_BedRoom="卧室";
}
GoodGay::GoodGay()
{
//创建建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout<<"visit函数正在访问: "<m_SittingRoom<m_BedRoom<m_SittingRoom<m_BedRoom<
四、运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
1、加号运算符重载
作用:实现两个自定义数据类型相加的运算
#include
using namespace std;
//加号运算符重载
//1、成员函数重载+号
class Person
{
public:
// Person operator+(Person &p)
// {
// Person temp;
// temp.m_a=this->m_a+p.m_a;
// temp.m_b=this->m_b+p.m_b;
// return temp;
// }
int m_a;
int m_b;
};
//2、全局函数重载+号
Person operator+(Person &p1,Person &p2)
{
Person temp;
temp.m_a=p1.m_a+p2.m_a;
temp.m_b=p1.m_b+p2.m_b;
return temp;
}
//函数重载的版本
Person operator+(Person &p1,int num)
{
Person temp;
temp.m_a=p1.m_a+num;
temp.m_b=p1.m_b+num;
return temp;
}
void test01()
{
Person p1;
p1.m_a=10;p1.m_b=10;
Person p2;
p2.m_a=10;p2.m_b=10;
//成员函数重载本质调用
//Person p3 = p1.operator+(p2);
//全局函数重载本质调用
//Person p3 = operator+(p1,p2);
Person p3=p1+p2;
//运算符重载也可以发生函数重载
Person p4=p1+100;//相当于 operator+(p1,100);
cout<
总结1:对于内置的数据类型的表达式的运算符是不可能改变的
总结2:不要滥用运算符重载
2、左移运算符重载
作用:可以输出自定义的数据类型。
#include
using namespace std;
//左移运算符重载
class Person
{
public:
//利用成员函数重载左移运算符 p.operator<<(cout) 简化版本 p<3、递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
#include
using namespace std;
// 自定义整形
class MyInteger
{
friend ostream& operator<<(ostream& cout,MyInteger myint);
public:
MyInteger()
{
m_num=0;
}
//重载前置++运算符,返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++()
{
//先进行++运算
m_num++;
//再将自身返回
return *this;
}
//重载后置++运算符,注意这里是返回值而不是引用
MyInteger operator++(int)//int代表占位参数,可以用于区分前置和后置递增
{
//先 记录当时结果
MyInteger temp=*this;
//后 递增
m_num++;
//最后将记录的结果做返回操作
return temp;
}
private:
int m_num;
};
//重载<<运算符
ostream& operator<<(ostream& cout,MyInteger myint)
{
cout<
总结:前置++返回引用,后置++返回值
4、赋值运算符重载
c++编译器至少给一个类添加4个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4,赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深拷贝问题
#include
using namespace std;
class Person
{
public:
Person(int age)
{
m_age=new int(age);
}
~Person()
{
if(m_age!=NULL)
{
delete m_age;
m_age=NULL;
}
}
//重载赋值运算符 注意返回引用
Person& operator=(Person &p)
{
//编译器提供浅拷贝
//m_age=p.m_age;
//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if(m_age!=NULL)
{
delete m_age;
m_age=NULL;
}
m_age = new int(*p.m_age);
return *this;//返回对象本身
}
int *m_age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3=p2=p1;
cout<<"p1的年龄为: "<<*p1.m_age<
5、关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行比对操作。
#include
using namespace std;
class Person
{
public:
Person(string name, int age)
{
m_name = name;
m_age = age;
}
//重载==号
bool operator==(Person &p)
{
if(this->m_name==p.m_name&&this->m_age==p.m_age)return true;
else return false;
}
//重载!=号
bool operator!=(Person &p)
{
if(this->m_name==p.m_name&&this->m_age==p.m_age)return false;
else return true;
}
string m_name;
int m_age;
};
void test01()
{
Person p1("Tom",18);
Person p2("Tom",18);
if(p1==p2)cout<<"YES"<
6、函数调用运算符重载
1.函数调用运算符()也可以重载
2.由于重载后使用方式非常像函数的调用,因此称为仿函数
3.仿函数没有固定写法,非常灵活
#include
using namespace std;
class MyPrint
{
public:
void operator()(string test)
{
cout<
五、继承
继承是面向对象的三大特性之一
有些类与类之间存在特殊的关系。例如第一级是动物,第二级是猫,狗,那么第二级的成员除了拥有上一级的特性,还有自己的特性,这个时候就可以考虑利用继承的技术,减少重复代码。
1、继承基本语法
#include
using namespace std;
//普通实现页面
// Java页面
// class Java
// {
// public:
// void header()
// {
// cout<<"首页、公开课、登录、注册……(公共头部)"<
总结:
继承的好处:可以减少重复的代码
语法:class A:public B;
A类称为子类或派生类 B类称为父类或基类
2、继承方式
1.公共继承 2.保护继承 3.私有继承
1.在公共继承中,父类的私有权限不可访问,其余的权限都变成公共权限
2.在保护继承中,父类的私有权限不可访问,其余的权限都变成保护权限
3.在私有继承中,父类的私有权限不可访问,其余的权限都变成私有权限
#include
using namespace std;
//继承方式
//公共继承
class Base1
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son1:public Base1
{
public:
void func()
{
m_a=10;//父类种的公共权限成员到子类中依然是公共权限
m_b=10;//父类中的保护权限成员到子类中依然是保护权限
//m_c=10;//父类中的私有权限成员到子类中访问不到,报错
}
};
void test01()
{
son1 s1;
s1.m_a=100;
// s1.m_b=100;//报错,到son1中m_b是保护权限类外方位不到
}
//保护继承
class Base2
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son2:protected Base2
{
public:
void func()
{
m_a=10;//父类种的公共权限成员到子类中变为保护权限
m_b=10;//父类中的保护权限成员到子类中依然是保护权限
//m_c=10;//父类中的私有权限成员到子类中访问不到,报错
}
};
void test02()
{
son2 s2;
// s2.m_a=100;//在son2中m_a变为保护权限,因此类外访问不到,报错
// s2.m_b=100;//在son2中m_b变为保护权限,因此类外访问不到,报错
}
//私有继承
class Base3
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son3:private Base3
{
public:
void func()
{
m_a=10;//父类种的公共权限成员到子类中变为私有权限
m_b=10;//父类中的保护权限成员到子类中依然是保护权限
//m_c=10;//父类中的私有权限成员到子类中访问不到,报错
}
};
void test03()
{
son3 s3;
// s3.m_a=1000;//在son3中m_a变为私有权限,因此类外访问不到,报错
// s3.m_b=1000;//在son3中m_a变为私有权限,因此类外访问不到,报错
}
int main()
{
return 0;
}
3、继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
#include
using namespace std;
class Base
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son:public Base
{
public:
int m_d;
};
void test01()
{
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性,是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
cout<<"size of son = "<
4、继承中的构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
继承中的构造和析构顺序如下:
先构造父类,再构造子类,析构的顺序与构造的顺序相反
#include
using namespace std;
class Base
{
public:
Base()
{
cout << "Base构造函数!" << endl;
}
~Base()
{
cout << "Base析构函数" << endl;
}
};
class son : public Base
{
public:
son()
{
cout << "son构造函数!" << endl;
}
~son()
{
cout << "son析构函数" << endl;
}
};
void test01()
{
// 继承中的构造和析构顺序如下:
// 先构造父类,再构造子类,析构的顺序与构造的顺序相反
son p;
}
int main()
{
test01();
return 0;
}
5、继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢/
1.访问子类同名成员,直接访问即可
2.访问父类同名成员,需要加作用域、
3.当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
#include
using namespace std;
class Base
{
public:
Base()
{
m_a=100;
}
void func()
{
cout<<"Base-func()调用"< 6、继承同名静态成员处理方式
问题:继承中同名静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
1.访问子类同名成员 直接访问即可
2.访问父类同名成员 需要加作用域
#include
using namespace std;
class Base
{
public:
static int m_a;
static void func()
{
cout<<"Base - static void func()的调用"<
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象和通过类名)
7、多继承语法
c++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2……
多继承可能会引发父类中有同名成员出现,需要加作用域区分
c++实际开发中不建议使用多继承
#include
using namespace std;
class Base
{
public:
Base()
{
m_a=100;
}
int m_a;
};
class Base2
{
public:
Base2()
{
m_a=200;
}
int m_a;
};
//子类 需要继承Base1和Base2
class son:public Base,public Base2
{
public:
son()
{
m_c=300;
m_d=400;
}
int m_c;
int m_d;
};
void test01()
{
son s;
cout<<"size of son = "<8、菱形继承
菱形继承概念:
两个派生类继承同一个基类 ,又有某个类同时继承着两个派生类,这种继承方式被称为菱形继承,或者钻石继承
#include
using namespace std;
//动物类
class Animal
{
public:
int m_age;
};
//利用虚继承可以解决菱形继承的问题
//在继承之前加上关键子virtual变为虚继承
//Animal类称为虚基类
//羊类
class Sheep:virtual public Animal{};
//驼类
class Tuo:virtual public Animal{};
//羊驼类
class SheepTuo:public Sheep,public Tuo{};
void test01()
{
SheepTuo st;
st.Sheep::m_age=18;
st.Tuo::m_age=28;
//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout<<"st.Sheep::m_age= "< 六、多态
1.多态的基本概念
多态是c++面向对象三大特性之一
多态分为两类
1.静态多态:函数重载和运算符重载属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态
静态多态与动态多态区别:
1.静态多态的函数地址早绑定 - 编译阶段确定函数地址
2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include
using namespace std;
//多态
//动物类
class Animal
{
public:
//函数面前加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
virtual void speak()
{
cout<<"动物在说话"<
重写:函数返回值类型,函数名,参数列表完全一致称为重写
2、多态案例--计算器实现
多态的优点:
1.代码组织结构清晰 2.可读性强 3.利于前期和后期的扩展以及维护
#include
using namespace std;
//分别利用普通写法和多态技术实现计算器
//普通写法
class Calculator
{
public:
int getResult(string oper)
{
if(oper=="+")return m_num1+m_num2;
else if(oper=="-")return m_num1-m_num2;
else if(oper=="*")return m_num1*m_num2;
//如果想扩展新的功能,需要修改源码
//在真实开发中,提倡开闭原则
//开闭原则:对扩展进行开放,对修改进行关闭
}
int m_num1;//操作数1
int m_num2;//操作数2
};
void test01()
{
//创建计算器对象
Calculator c;
c.m_num1=10;c.m_num2=10;
cout<m_num1=10;
abc->m_num2=10;
cout<m_num1<<" + "<m_num2<<" = "<getResult()<m_num1=100;
abc->m_num2=100;
cout<m_num1<<" - "<m_num2<<" = "<getResult()<m_num1=100;
abc->m_num2=100;
cout<m_num1<<" * "<m_num2<<" = "<getResult()<
3、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现毫无意义的,主要是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数写法:virtual 返回值类型 函数名 (参数列表)= 0;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点:
1.无法实例化对象
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include
using namespace std;
//纯虚函数和抽象类
class Base
{
public:
//纯虚函数
//只要有一个纯虚函数,这个类称为抽象类
//抽象类特点:
//1、无法实例化对象
//2、抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
virtual void func()=0;
};
class son:public Base
{
public:
virtual void func()
{
cout<<"func函数调用"<func();
}
int main()
{
test01();
return 0;
}
4、多态案例--制作饮品
制作饮品的大致流程为:煮水-冲泡-倒入杯中-加入辅料
利用多态技术实现本案例,提供抽象类制作饮品基类,提供子类制作咖啡和茶叶
#include
using namespace std;
//多态案例2 制作饮品
class AbstractDrinking
{
public:
//煮水
virtual void Boil()=0;
//冲泡
virtual void Brew()=0;
//倒入杯中
virtual void PourInCup()=0;
//加入辅料
virtual void PutSomething()=0;
//制作饮品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSomething();
}
};
class Coffee:public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout<<"煮农夫山泉"<makeDrink();
delete abs;//释放
}
void test01()
{
//制作咖啡
doWork(new Coffee);
//制作茶叶
doWork(new Tea);
}
int main()
{
test01();
return 0;
}
5、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
虚析构和纯虚析构区别:
1.如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名()
纯虚析构语法:virtual ~类名()=0;类名::~类名(){}
#include
using namespace std;
//虚析构和纯虚析构
class Animal
{
public:
Animal()
{
cout<<"Animal构造函数调用"<speak();
//父类指针在析构时候 不会调用子类中虚构函数,导致子类如果有堆区属性,出现内存泄漏情况
delete animal;
}
int main()
{
test01();
return 0;
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
6、多态案例--电脑组装
案例描述:
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)将每个零件封装除抽象基类,并且提供不同的厂商生产不同的零件,例如Intel和Lenovo厂商,创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include
using namespace std;
//抽象不同零件类
//抽象CPU类
class CPU
{
public:
//抽象的计算函数
virtual void calculate()=0;
};
//抽象显卡类
class VideoCard
{
public:
//抽象的显示函数
virtual void display()=0;
};
//抽象内存条类
class Memory
{
public:
//抽象的存储函数
virtual void storage()=0;
};
//电脑类
class Computer
{
public:
Computer(CPU *cpu,VideoCard *vc,Memory *mem)
{
m_cpu=cpu;
m_vc=vc;
m_mem=mem;
}
//提供工作的函数
void work()
{
//让零件工作起来,调用接口
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//提供析构函数释放3个电脑零件
~Computer()
{
//释放CPU零件
if(m_cpu!=NULL)
{
delete m_cpu;
m_cpu=NULL;
}
//释放显卡零件
if(m_vc!=NULL)
{
delete m_vc;
m_vc=NULL;
}
//释放内存条零件
if(m_mem!=NULL)
{
delete m_mem;
m_mem=NULL;
}
}
private:
CPU *m_cpu;//cpu的零件指针
VideoCard *m_vc;//显卡零件指针
Memory *m_mem;//内存条零件指针
};
//具体厂商
//Intel厂商
class IntelCPU:public CPU
{
public:
virtual void calculate()
{
cout<<"Intel的CPU开始计算了"<work();
delete computer1;
//第二台电脑组装
cout<<"第二台电脑开始工作"<work();
delete computer2;
//第三台电脑组转
cout<<"第三台电脑开始工作"<work();
delete computer3;
}
int main()
{
test01();
return 0;
}