目录
1. 封装
1.1 封装的意义
1.1.1 封装意义—
1.1.2 封装意义二
1.2 struct和class区别
1.3?成员属性设置为私有
2. 对象初始化和清理
2.1 构造函数和析构函数
2.2 构造函数的分类及调用
2.3 拷贝构造函数调用时机
2.4 构造函数调用规则
2.5 深拷贝与浅拷贝
2.6 初始化列表
2.7 类对象作为类成员
2.8 静态成员
3. C++对象模型和this指针
3.1 成员变量和成员函数分开存储
3.2 this指针概念
3.3 空指针访问成员函数
3.4 const修饰成员函数
4.4 友元
4.4.1 全局函数做友元
4.4.2 类做友元
4.4.3 成员函数做友元
5. 运算符重载
6. 继承
6.1 继承的基本使用
6.2 继承方式
6.3 继承中构造和析构顺序
6.4 继承同名成员处理方式
6.4.1 同名成员变量的处理方式
6.4.2 同名成员函数的处理方式
6.5 继承同名静态成员处理方式
6.5.1 同名静态成员属性
6.5.2 同名静态成员函数
6.6?多继承语法
6.7 菱形继承
7. 多态
7.1 多态的基本概念
7.2 多态案例一计算器类
7.3 纯虚函数和抽象类
7.4 多态案例二:制作饮品
7.5 虚析构和纯虚析构
C++面向对象的三大特性为:封装、继承、多本
C++认为万事万物都皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体....,行为有走、跑、跳、吃饭、唱歌...车也可以作为对象,属性有轮胎、方向盘、车灯...行为有载人、放音乐、放空调... 具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
1. 封装
1.1 封装的意义
封装是C++面向对象三大特性之—
封装的意义:
????????将属性和行为作为一个整体,表现生活中的事物
????????将属性和行为加以权限控制
1.1.1 封装意义—
????????在设计类的时候,属性和行为写在一起,表现事物
语法:class类名{ 访问权限: 展性 / 行为 };
示例1:设计一个圆类,求圆的周长
示例代码:
#include<iostream>
using namespace std;
const double PI = 3.14;
// 设计一个圆类,求圆的周长
class Circle {
// 访问权限
public: //公共权限
// 属性
int m_r; //半径
// 行为
// 获取圆的周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main() {
// 实例化:通过圆类 创建具体的圆(对象)
Circle c1;
// 给圆的对象的属性进行赋值
c1.m_r = 10;
cout << "圆的周长:" << c1.calculateZC() << endl;
system("pause");
return 0;
}
运行结果:
?
示例2: 设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
示例代码:
#include<iostream>
using namespace std;
#include<string>
class Student
{
public: // 公共权限
// 成员:类中的属性和行为统一称为成员
// 属性:成员属性、成员变量
// 行为:成员函数、成员方法
string m_Name; // 属性(学生姓名)
int m_Id; // 属性(学生id)
void showStudent() // 行为:显示姓名和学号
{
cout << "姓名:" << m_Name << " 学号:" << m_Id << endl;
}
void setNameAndId(string name, int Id) // 行为:给姓名赋值
{
m_Name = name;
m_Id = Id;
}
};
int main() {
Student s; //实例化学生对象
直接给属性赋值
//s.m_Name = "张三";
//s.m_Id = 1;
// 通过行为给属性赋值
s.setNameAndId("张三",10);
//显示学生信息
s.showStudent();
system("pause");
return 0;
}
1.1.2 封装意义二
????????类在设计时,可以把属性和行为放在不同的权限下,加以控制
????????访问权限有三种:
????????????????1. public? ? ? ? ? ? ? ? ?公共权限:成员 类内类外都可以访问
????????????????2. protected? ? ? ? ? ? 保护权限:成员?类内可以访问,类外不可以访问?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?子类可以访问父类中的保护内容
????????????????3. private????????????????私有权限:成员?类内可以访问,类外不可以访问
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?子类可以不可以访问父类中的私有内容
1.2 struct和class区别
在C++中struct和class唯—的区别就在于默认的访问权限不同
区别:
????????struct默认权限为公共,如下代码中,无错误提示
?
????????class默认权限为私有,如下代码中,有错误提示
?
1.3?成员属性设置为私有
????????优点1:将所有成员属性设置为私有,可以自己控制读写权限
????????优点2:对于写权限,我们可以检测数据的有效性(判断写入的数据是否合规)
????????示例:(我想设置私有成员中的?
????????????????????????????????姓名 / 可读可写??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 年龄 / 只读
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? id / 只写
????????????????????????)
????????解决思路:
? ? ? ? 可以在公有权限中,对三个私有属性分别设置相应的成员函数,如果要可读可写,就在公有权限中编辑读和写的函数。
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
// 设置姓名(可写姓名)
void setName(string name)
{
m_Name = name;
}
// 获取姓名(可读姓名)
string getName()
{
return m_Name;
}
private:
string m_Name;
int m_age;
int id;
};
int main() {
Person p;
p.setName("张三");
cout << "姓名:" << p.getName() << endl;
system("pause");
return 0;
}
2. 对象初始化和清理
????????生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全。
????????C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。
2.1 构造函数和析构函数
????????对象的初始化和清理也是两个非常重要的安全问题
????????—个对象或者变量没有初始状态,对其使用后果是未知
????????同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
????????C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
????????构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
????????析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:????????类名(){}
????????1.构造函数,没有返回值也不写void
????????2.函数名称与类名相同
????????3.构造函数可以有参数,因此可以发生重载
????????4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:????????~类名(){}
????????1.析构函数,没有返回值也不写void
????????2.函数名称与类名相同,在名称前加上符号~
????????3.析构函数不可以有参数,因此不可以发生重载
????????4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
构造函数与析构函数的使用示例:
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
// 1. 构造函数
Person()
{
cout << "Person 构造函数的调用" << endl;
}
// 2. 析构函数
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};
void test01()
{
Person p;
}
int main() {
test01();
system("pause");
return 0;
}
2.2 构造函数的分类及调用
两种分类方式:
????????按参数分为:有参构造和无参构造
????????按类型分为:普通构造和拷贝构造
三种调用方式:
????????括号法
????????显示法
????????隐式转换法
2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
? ? ? ? 1.使用一个已经创建完毕的对象来初始化一个新对象
? ? ? ? 2.值传递的方式给函数参数传值
? ? ? ? 3.以值方式返回局部对象
2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
????????1.默认构造函数(无参,函数体为空)
????????2.默认析构函数(无参,函数体为空)
????????3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
????????1.如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
????????2.如果用户定义拷贝构造函数,C++不会再提供其他构造函数
2.5 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作 ?
2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1) ,属性2(值2) ... {}
传统初始化操作:通过构造函数初始化属性
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
// 传统初始化操作:通过构造函数初始化属性
Person(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
Person p(10, 20, 30);
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}
int main() {
test01();
system("pause");
return 0;
}
通过初始化列表:
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
Person():m_A(10),m_B(10),m_C(30)
{
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
Person p;
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}
int main() {
test01();
system("pause");
return 0;
}
2.7 类对象作为类成员
????????C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例如:
class A {}
class B
{
A a;
}
????????B类中有对象A作为成员,A为对象成员,那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
????????答案:是A先构造
2.8 静态成员
静态成员就是在成员变呈和成员函数前加上关键字static,称为静态成员
静态成员分为:
????????静态成员变量
????????????????所有对象共享同一份数据
????????????????在编译阶段分配内存
????????????????类内声明,类外初始化
????????????????(静态成员变量也是有访问权限的,如私有权限的静态成员变量在类外是访问不到的)
????????静态成员函数
????????????????所有对象共享同一个函数
????????????????静态成员函数只能访问静态成员变量
以下示例代码证明了所有对象共享一份数据:
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
static int m_A; // 类内申明
};
int Person::m_A = 100; // 类外初始化
void test01()
{
Person p;
cout << "p的 m_A = " << p.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p2的 m_A = " << p.m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
结果:
?
因此静态成员变量有两种访问方式:
1.通过对象进行访问
Person p;
cout << p.m_A << endl;
2.通过类名访问
cout << Person::m_A << endl;
以下示例代码呈现了静态成员函数的两种访问方式
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
static void func()
{
cout << "调用func" << endl;
}
};
void test01()
{
// 通过对象访问
Person p;
p.func();
// 通过类名访问
Person::func();
}
int main() {
test01();
system("pause");
return 0;
}
3. C++对象模型和this指针
3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
3.2 this指针概念
通过3.1我们知道在C++中成员变虽和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
????????1.当形参和成员变量同名时,可用this指针来区分
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
Person(int age)
{
// this指针指向被调用的成员函数 所属的对象
this->age = age;
}
int age;
};
void test01()
{
Person p1(18);
cout << "p1的年龄" << p1.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
?
????????2.在类的非静态成员函数中返回对象本身,可使用return *this
3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
错误写法:
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
void showClassName()
{
cout << "show class name" << endl;
}
void showPersonAge()
{
cout << "age = " << this->m_Age << endl;
}
int m_Age;
};
void test01()
{
Person * p = NULL;
//p->showClassName();
p->showPersonAge();
}
int main() {
test01();
system("pause");
return 0;
}
?报错原因是因为传入的指针是为NULL
正确写法:
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
void showClassName()
{
cout << "show class name" << endl;
}
void showPersonAge()
{
if (this == NULL)
{
return;
}
cout << "age = " << this->m_Age << endl;
}
int m_Age;
};
void test01()
{
Person * p = NULL;
//p->showClassName();
p->showPersonAge();
}
int main() {
test01();
system("pause");
return 0;
}
3.4 const修饰成员函数
常函数:
1.成员函数后加const后我们称为这个函数为常函数(在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改)
2.常函数中不可以修改成员属性
?
3.成员属性声明时加关捷字mutable后,在常函数中依然可以修改
常对象:
1.声明对象前加const称该对象为常对象
const Person p;
2.常对象只能调用常函数 ?
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
void showPerson(int num) const
{
m_A = num;
}
mutable int m_A;
};
void test01()
{
const Person p;
p.showPerson(100);
cout << p.m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
?
4.4 友元
????????生活中你的家有客厅(Public),有你的卧室(Private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去但是呢,你也可以允许你的好闰蜜好基友进去。
????????在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术,友元的目的就是让—个函数或者类访问另一个类中私有成员,友元的关键字为friend。
友元的三种实现
????????1.全局函数做友元
????????2.类做友元
????????3.成员函数做友元
4.4.1 全局函数做友元
#include<iostream>
using namespace std;
#include<string>
class Building
{
friend void goodFriend(Building *building); //表示goodFriend全局函数是本类的友元,可以访问本类的私有属性
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
void goodFriend(Building *building)
{
cout << "goodFriend 正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
Building building;
goodFriend(&building);
}
int main() {
test01();
system("pause");
return 0;
}
?
4.4.2 类做友元
#include<iostream>
using namespace std;
#include<string>
class Building;
class goodFriend
{
public:
goodFriend();
void visit(); //用来访问Building中的属性
Building * building;
};
class Building
{
friend class goodFriend; //表示goodFriend类是Building类的友元,可以访问Building中的私有成员
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
// 类外写成员函数
Building::Building() //Building的构造函数
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
goodFriend::goodFriend() //goodFriend的构造函数
{
// 创建building对象
building = new Building;
}
void goodFriend::visit()
{
cout << "goodFriend类正在访问:" << building->m_SittingRoom << endl;
cout << "goodFriend类正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
goodFriend gg;
gg.visit();
};
int main() {
test01();
system("pause");
return 0;
}
?
4.4.3 成员函数做友元
#include<iostream>
using namespace std;
#include<string>
class Building;
class goodFriend
{
public:
goodFriend(); //申明构造函数
void visit(); //让visit函数可以访问Building中的私有成员
Building * building;
};
class Building
{
friend void goodFriend::visit(); //让goodFriend类中的visit成员函数作为本类的友元
public:
Building(); //申明构造函数
public:
string m_SitingRoom;
private:
string m_BedRoom;
};
//类外实现成员函数
Building::Building()
{
m_SitingRoom = "客厅";
m_BedRoom = "卧室";
}
goodFriend::goodFriend()
{
building = new Building;
}
void goodFriend::visit()
{
cout << "visit函数正在访问:" << building->m_SitingRoom << endl;
cout << "visit函数正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
goodFriend gg;
gg.visit();
};
int main() {
test01();
system("pause");
return 0;
}
?
5. 运算符重载
????????运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算法重载
作用:实现两个自定义数据类型相加的运算
方式1:通过成员函数重载+号?
#include<iostream>
using namespace std;
#include<string>
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;
};
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 + p2;
//Person p3 = p1.operator+(p2); // 成员函数重载本质调用
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
};
int main() {
test01();
system("pause");
return 0;
}
方式2:通过全局函数重载+号?
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
int m_A;
int m_B;
};
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;
}
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 + p2;
// Person p3 = operator+(p1, p2) // 全局函数重载本质调用
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
};
int main() {
test01();
system("pause");
return 0;
}
6. 继承
继承是面向对象三大特性之一
有些类与类之间存在特殊的关系,例如下图中:
?????????我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候我们就可以考虑利用继承的技术,减少重复代码。
6.1 继承的基本使用
不使用继承(重复代码太多):
#include<iostream>
using namespace std;
#include<string>
class Java
{
public:
void header()
{
cout << "header" << endl;
}
void footer()
{
cout << "footer" << endl;
}
void content()
{
cout << "---Java---" << endl;
}
};
class Python
{
public:
void header()
{
cout << "header" << endl;
}
void footer()
{
cout << "footer" << endl;
}
void content()
{
cout << "---Python---" << endl;
}
};
void test01()
{
Java ja;
ja.footer();
ja.header();
ja.content();
Python py;
py.footer();
py.header();
py.content();
};
int main() {
test01();
system("pause");
return 0;
}
使用继承:
#include<iostream>
using namespace std;
#include<string>
//继承实现
class Base
{
public:
void header()
{
cout << "header" << endl;
}
void footer()
{
cout << "footer" << endl;
}
};
class Java :public Base
{
public:
void content()
{
cout << "---Java---" << endl;
}
};
class Python :public Base
{
public:
void content()
{
cout << "---Python---" << endl;
}
};
void test01()
{
Java ja;
ja.footer();
ja.header();
ja.content();
Python py;
py.footer();
py.header();
py.content();
};
int main() {
test01();
system("pause");
return 0;
}
6.2 继承方式
继承的语法:? class子类:继承方式? 父类
继承方式一共有三种:
????????1.公共继承
????????2.保护继承·
????????3.私有继承
?
6.3 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后? ?
#include<iostream>
using namespace std;
#include<string>
//继承实现
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 s;
};
int main() {
test01();
system("pause");
return 0;
}
?
6.4 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
? ? ? ? 方式1:访问子类同名成员直接访问即可
? ? ? ? 方式2:访问父类同名成员需要加作用域
6.4.1 同名成员变量的处理方式
#include<iostream>
using namespace std;
#include<string>
//继承实现
class Base
{
public:
Base()
{
m_A = 100;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
int m_A;
};
void test01()
{
Son s;
cout << "Son m_A = " << s.m_A << endl; // 访问子类中的m_A
cout << "Base m_A = " << s.Base::m_A << endl; // 访问父类中的m_A
};
int main() {
test01();
system("pause");
return 0;
}
?
6.4.2 同名成员函数的处理方式
#include<iostream>
using namespace std;
#include<string>
//继承实现
class Base
{
public:
void func()
{
cout << "调用Base的func" << endl;
}
};
class Son :public Base
{
public:
void func()
{
cout << "调用Son的func" << endl;
}
};
void test01()
{
Son s;
s.func(); // 直接调用,调用的是子类中同名的成员函数
s.Base::func(); // 调用父类中的func
};
int main() {
test01();
system("pause");
return 0;
}
6.5 继承同名静态成员处理方式
问题:继承承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
????????访问子类同名成员直接访问即可
????????访问父类同名成员需要加作用域
6.5.1 同名静态成员属性
#include<iostream>
using namespace std;
#include<string>
//继承实现
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()
{
// 通过对象访问
Son s;
cout << "Son的m_A = " << s.m_A << endl;
cout << "Base的m_A = " << s.Base::m_A << endl;
// 通过类名访问
cout << "Son的m_A = " << Son::m_A << endl;
cout << "Base的m_A = " << Son::Base::m_A << endl; // 第一个::代表通过类名方式访问, 第二个::代表访问父类作用域下
};
int main() {
test01();
system("pause");
return 0;
}
6.5.2 同名静态成员函数
#include<iostream>
using namespace std;
#include<string>
//继承实现
class Base
{
public:
static void func()
{
cout << "Base static void func()" << endl;
}
};
class Son :public Base
{
public:
static void func()
{
cout << "Son static void func()" << endl;
}
};
// 同名静态成员函数
void test02()
{
Son s;
s.func(); // 调用子类的func
s.Base::func(); // 调用父类的func
}
int main() {
test02();
system("pause");
return 0;
}
6.6?多继承语法
C++允许一个类继承多个类
语法: class子类︰继承方式父类1,继承方式父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
#include<iostream>
using namespace std;
#include<string>
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_B = 200;
}
int m_B;
};
class Son:public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
// 同名静态成员函数
void test01()
{
Son s;
cout << s.Base1::m_A << endl;
cout << s.Base2::m_B << endl;
cout << s.m_C << endl;
}
int main() {
test01();
system("pause");
return 0;
}
6.7 菱形继承
菱形继承概念:
????????两个派生类继承同一个基类
????????又有某个类同时继承者两个派生类
????????这种继承被称为菱形继承,或者钻石继承 ?
?菱形继承问题:
????????1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
可以用作用域加以区分:?
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
int m_Age;
};
class Sheep : public Animal {};
class Tuo : 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 = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
??
????????⒉草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
利用虚继承 解决菱形继承的问题:
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
int m_Age;
};
// 利用虚继承 解决菱形继承的问题
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : virtual public Sheep, public Tuo {};
// 同名静态成员函数
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
// 当菱形继承,两个父类拥有相同成员属性时,可以用作用域加以区分
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
?
7. 多态
7.1 多态的基本概念
多态是C++面向对象三大特性之—
多态分为两类:
????????静态多态:函数重载和运算符重载属于静态多态,复用函数名
????????动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
????????静态多态的函数地址早绑定 – 编译阶段确定函数地址
????????动态多态的函数地址晚绑定 – 运行阶段确定函数地址
总结:
????????多态满足条件:
????????????????有继承关系
????????????????子类重写父类中的虚函数
????????多态使用条件:
????????????????父类指针或引用指向子类对象
????????重写:函数返回值类型函数名参数列表完全一致称为重写
下面通过案例进行讲解多态:
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
void speak()
{
cout << "Animal speak" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "Cat speak" << endl;
}
};
// 同名静态成员函数
void doSpeak(Animal &animal) // 地址早绑定,在编译阶段确定函数地址
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
}
int main() {
test01();
system("pause");
return 0;
}
????????发现想让猫说话,但是执行的是动物说话。原因是地址早绑定,在编译阶段确定函数地址。
????????如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,即地址晚绑定,通过关键字 virtual 实现:
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
virtual void speak() //虚函数
{
cout << "Animal speak" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "Cat speak" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "Dog speak" << endl;
}
};
// 同名静态成员函数
void doSpeak(Animal &animal) // 地址早绑定,在编译阶段确定函数地址
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
?
7.2 多态案例一计算器类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
????????代码组织结构清晰
????????可读性强
????????利于前期和后期的扩展以及维护
普通写法:
#include<iostream>
using namespace std;
#include<string>
class Calculate
{
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;
int m_Num2;
};
void test01()
{
// 创建计算器对象
Calculate c;
c.m_Num1 = 10;
c.m_Num2 = 20;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
int main() {
test01();
system("pause");
return 0;
}
?
多态写法:
#include<iostream>
using namespace std;
#include<string>
// 利用多态实现计算器
//实现计算器抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器类
class AddCalculator :public AbstractCalculator
{
public:
int getResult() //子类重写父类中的虚函数
{
return m_Num1 + m_Num2;
}
};
//减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器类
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//多态使用条件
//父类指针或者引用指向子类对象
//加法运算
AbstractCalculator * abc = new AddCalculator; //父类指针指向子类对象
abc->m_Num1 = 10;
abc->m_Num2 = 20;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc; //销毁指针
//减法运算
abc = new SubCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 200;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//乘法运算
abc = new MulCalculator;
abc->m_Num1 = 5;
abc->m_Num2 = 15;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main() {
test02();
system("pause");
return 0;
}
?
?
7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类
#include<iostream>
using namespace std;
class Base
{
public:
// 只要类中有一个纯虚函数,这个类就称为抽象类
virtual void func() = 0;
};
int main() {
system("pause");
return 0;
}
抽象类特点:
? ? ? ?1. 无法实例化对象
?
? ? ? ? 2. 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
????????????????????????????????子类重写抽象类中的纯虚函数才能实例化:
#include<iostream>
using namespace std;
#include<string>
class Base
{
public:
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func() {}
};
void test01()
{
//Son s;
Base * base = new Son;
base->func();
}
int main() {
test01();
system("pause");
return 0;
}
?
7.4 多态案例二:制作饮品
制作饮品的大致流程为:煮水-冲泡-倒入杯中-加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#include<iostream>
using namespace std;
#include<string>
class AbstractMakeDrinking
{
public:
virtual void Boil() = 0; //煮水
virtual void Brew() = 0; //冲泡
virtual void PourInCup() = 0; //倒入杯中
virtual void PutSomeThing() = 0; //加入辅料
void makeDirnk() //只要一调用makeDirnk,就会执行这4个步骤
{
Boil();
Brew();
PourInCup();
PutSomeThing();
}
};
class MakeCoffee:public AbstractMakeDrinking
{
public:
virtual void Boil()
{
cout << "煮水" << endl;
}
virtual void Brew()
{
cout << "冲泡" << endl;
}
virtual void PourInCup()
{
cout << "倒入水中" << endl;
}
virtual void PutSomeThing()
{
cout << "加入牛奶" << endl;
}
};
class MakeTea :public AbstractMakeDrinking
{
public:
virtual void Boil()
{
cout << "煮水" << endl;
}
virtual void Brew()
{
cout << "冲泡" << endl;
}
virtual void PourInCup()
{
cout << "倒入水中" << endl;
}
virtual void PutSomeThing()
{
cout << "加入柠檬" << endl;
}
};
void doWork(AbstractMakeDrinking * abs)
{
abs->makeDirnk();
delete abs; //释放指针(在堆区)
}
void test01()
{
doWork(new MakeCoffee);
cout << "————————" << endl;
doWork(new MakeTea);
}
int main() {
test01();
system("pause");
return 0;
}
?
7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
#include<iostream>
using namespace std;
#include<string>
class Animal
{
public:
Animal()
{
cout << "调用Animal构造函数" << endl;
}
~Animal()
{
cout << "调用Animal析构函数" << endl;
}
virtual void speak() = 0; //纯虚函数
};
class Cat :public Animal
{
public:
Cat(string name)
{
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "调用Cat析构函数" << endl;
delete m_Name;
m_Name = NULL;
}
}
string * m_Name;
};
void test01()
{
Animal * animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
?
发现没有调用Cat的析构函数
????????原因:父指针在析构时候,不会调用子类中的析构函数,导致子类如果有堆区属性,会出现内存泄露情况。
????????解决方式:将父类中的析构函数改为虚析构或者纯虚析构
1. 使用虚析构方式:
将
~Animal()
{
cout << "调用Animal析构函数" << endl;
}
改为
virtual ~Animal()
{
cout << "调用Animal析构函数" << endl;
}
?2. 纯虚析构
class Animal
{
public:
Animal()
{
cout << "调用Animal构造函数" << endl;
}
virtual ~Animal() = 0; //纯虚析构
virtual void speak() = 0; //纯虚函数
};
Animal::~Animal()
{
cout << "调用Animal纯虚析构函数" << endl;
}
虚析构和纯虚析构共性:
????????1.可以解决父类指针释放子类对象
????????2.都需要有具体的函数实现
虚析构和纯虚析构区别:
????????如果是纯虚析构,该类属于抽象类,无法实例化对象
|