4 类和对象
C++面向对象的三大特性为:封装,继承和多态
C++认为万事万物皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名,年龄....行为有跑,跳......
车可以作为对象,属性有车胎,方向灯....行为有载人,放音乐....
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
4.1 封装
4.1.1 封装的意义
封装的意义一:
- 将属性和行为作为一个整体,表现生活中的实物
- 将属性和行为加以权限控制
#include <iostream>
#include <string>
using namespace std;
//设计一个圆类,求圆的周长
//周长的公式:2 * PI * r
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;
}
类中的属性和行为统称为 成员
属性==成员属性==成员变量
行为==成员函数==成员方法
封装的意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种
1,public 公共权限
2,protected 保护权限
3,private 私有权限
#include <iostream>
#include <string>
using namespace std;
class Students
{
//访问权限
//公共权限
public:
//保护权限
string name;
protected:
string car;
//私有权限
private:
int card;
//这三个权限在类内都可以访问,但是后两个在类外就不允许访问了
void show()
{
name = "jackie";
car = "劳斯莱斯";
card = 1234;
}
};
int main()
{
Students student1;
student1.name = "张三";
cout << student1.name << endl;
//下面这段就会报错,保护权限不允许访问
student1.car = "QQ";
cout << student1.car << endl;
//下面这段就会报错,私有权限不允许访问
student1.card = 345;
cout << student1.card << endl;
system("pause");
return 0;
}
4.1.2 struct和class的区别
区别在于两者的默认权限不同,class的默认权限是私有权限,struct的默认权限是公有权限
#include <iostream>
#include <string>
using namespace std;
class C1
{
string name; //class中的成员变量默认为私有权限
};
struct C2
{
string name; //class中的成员变量默认为公有权限
};
int main()
{
C1 c1;
C2 c2;
//c1.name = "jackie";
c2.name = "jackie";
//cout << c1.name << endl;
cout << c2.name << endl;
system("pause");
return 0;
}
4.1.3 成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
#include <iostream>
#include <string>
using namespace std;
class C1
{
public:
//控制权限,可读可写
void SetName(string name)
{
m_name = name;
}
string GetName()
{
return m_name;
}
//控制权限,只读
int GetAge()
{
m_age = 18;
return m_age;
}
//控制权限,只写
void SetLover(string lover)
{
m_lover = lover;
}
//下面的成员属性是不允许访问的,但是我们可以使用函数对下面成员进行访问
private:
string m_name;
int m_age;
string m_lover;
};
int main()
{
C1 c1;
c1.SetName("jackie");
cout << c1.GetName() << endl;
cout << c1.GetAge() << endl;
c1.SetLover("family");
system("pause");
return 0;
}
4.2 对象的初始化和清理
-
生活中我们买的电子产品通常都有出场设置 ,在某一天我们不用的时候也会删除一些自己的信息来保护自己的安全
-
C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
4.2.1 构造函数和析构函数
一个对象或者变量没有初始化状态,对其使用后果是未知的
同样的使用完一个对象或者变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决了上面的问题,这两个函数将会被编译器自动调用,完成对象初始化和清理的工作。对象初始化和清理工作是编译器强制要求我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
- 析构函数:主要作用于对象销毁前,系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不用写void
- 函数名称与类相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象的时候会自动调用构造,无需手动调用,而且只调用一次
析构函数语法:~类名(){}
- 析构函数,没有返回值也不用写void
- 函数名称与类相同,在名称前面加~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在销毁对象的时候会自动调用析构,无需手动调用,而且只调用一次
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
};
void test()
{
Person person; //在这里创建一个函数,作用是创建Person的实例化对象,调用这个函数的时候就会直接调用Person类里面的构造函数,
//当这个函数调用结束的时候,说明对象被销毁了,所以会直接调用析构函数
}
int main()
{
test();
Person p; //这个对象在创建的时候也会调用构造函数,但是不会像上面那个函数一样还会调用析构函数,因为p在mian函数里面,
//什么时候mian函数结束才会调用析构函数,但是我们可能已经看不到了
system("pause");
return 0;
}
4.2.2 构造函数的分类及调用
两种分类方式
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式
- 括号法
- 显示法
- 隐式转换法
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝参构造函数
Person(const Person &p) //这里传入的类对象对所有属性全部被拷贝,为什么是这种结构呢(const Person &p)?
//因为传入的类里面的属性不能发生改变,故需要加const且还要传地址
{
age = p.age;
cout << "Person拷贝参构造函数的调用" << endl;
}
int age;
};
void test()
{
//1,括号法调用
Person p1; //没有参数传进去也就是说会调用无参构造
Person p2(10); //有参数传进去也就是说会调用有参构造
Person p3(p2); //有类对象传进去也就是说会调用拷贝构造
cout << "p2的年龄" << p2.age << endl;
cout << "p3的年龄" << p3.age << endl;
//2,显示法调用
Person p4;
Person p5 = Person(10); //有参构造,Person(10)是匿名对象,相当于没有创建变量,但是有了等号就相当于给了你一个名字
Person p6 = Person(p5); //构造函数
Person(10); //匿名对象可以直接创建,就是没有对应的变量接受而已,所以这个匿名对象有个特点就是会在这一行结束的时候就销毁对象了,不是函数结束的时候
//注意不要利用拷贝构造函数初始化匿名对象,例如Person(p3) 编译器会这样理解Person(p3) === Person p3;相当于创建了一个p3的变量,会与上面的p3重命名
//3,隐式转换法
Person p7 = 10; //相当于写了Person p7 = Person(10);
Person p8 = p7; //相当于写了Person p8 = Person(p7);
}
int main()
{
test();
system("pause");
return 0;
}
4.2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有一下三种
- 使用一个已经创建完毕的对象来初始化一个新的对象
- 值传递的方式给函数参数传值
- 以值方式返回局部变量
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "Person默认构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝参构造函数
Person(const Person &p)
{
age = p.age;
cout << "Person拷贝参构造函数的调用" << endl;
}
int age;
};
//1,使用一个已经创建完毕的对象来初始化一个新的对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄" << p2.age << endl;
}
//2,值传递的方式给函数参数传值
void doWork1(Person p)
{
}
void test02()
{
Person p1; //这里是默认构造函数
doWork1(p1); //这里会调用拷贝构造函数有2中理解方式,首先值传递就是直接拷贝一个参数传递给另一个函数,其次,可以理解为Person p = p1,这相当于隐式调用。
}
//3,以值方式返回局部变量
Person doWork2()
{
Person p1;
return p1; //返回的是p1的值,而不是p1,也就相当于拷贝了一个数值传递出去
}
void test03()
{
Person p = doWork2(); //助力依然可以用两种方式理解,1是doWork函数拷贝了一个p1的值传递,需要用拷贝构造函数,2是 Person p = p1,还是隐式调用中的拷贝形式。
}
int main()
{
//test01();
//test02(); //运行这个函数就会调用默认构造和拷贝构造函数
test03();
system("pause");
return 0;
}
4.2.4 构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下
- 如果用户定义有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数
- 如果用户定义拷贝构造函数,C++不再提供其他构造函数
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "Person默认构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
/*
//拷贝参构造函数
Person(const Person &p)
{
age = p.age;
cout << "Person拷贝参构造函数的调用" << endl;
}
*/
//当只有拷贝构造的时候,编译器就不会给你默认构造和有参构造
int age;
};
void test01()
{
Person p1;
p1.age = 18; //有了有参构造,编译器就不会提供无参(默认)构造,但是会提供拷贝构造
Person p2(p1);
cout << "p2的年龄" << p2.age << endl; //p2的年龄依然会是18,在调用默认配置函数的时候,编译器会自动将p1的属性全部赋值给p2
}
int main()
{
test01();
return 0;
}
4.2.5 深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
深拷贝:在堆区重申申请空间,进行拷贝操作
浅拷贝:简单的赋值拷贝操作
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "Person默认构造函数的调用" << endl;
}
//有参构造函数
Person(int a,int height)
{
age = a;
Height = new int(height);
cout << "Person有参构造函数的调用" << endl;
}
//拷贝参构造函数
Person(const Person &p)
{
age = p.age;
//Height = p.Height 编译器默认会使用的操作,是一个浅拷贝,这里拷贝的就是一个地址所以拷贝的对象指向了同一个堆区,在释放的时候就会被释放两次,故浅拷贝在这里不适合,需要用到深拷贝
//深拷贝操作,这样就不会释放同一个堆区了,重新开辟一个堆区
Height = new int(*p.Height);
cout << "Person拷贝参构造函数的调用" << endl;
}
~Person()
{
//通常需要在析构函数里面对前面的堆区进行释放
if(Height != NULL)
{
delete Height;
Height = NULL;
}
cout << "Person析构函数的调用" << endl;
}
int age;
int * Height;
};
void test01()
{
Person p1(18,160);
Person p2(p1);
cout << "p1的年龄" << p1.age << endl;
cout << "p2的年龄" << p2.age << endl;
cout << "p1的身高" << p1.Height << endl;
cout << "p2的身高" << p2.Height << endl; //浅拷贝的身高的地址完全一样,指向了同一个地址,深拷贝的话地址就不一样了
}
int main()
{
test01();
return 0;
}
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
4.2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数() :属性1(值1) 属性2(值2) ...... {}
#include <iostream>
#include <string>
using namespace std;
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)
{
}
int m_A;
int m_B;
int m_C;
};
//传统的测试、
void test01()
{
Person p(10, 20, 30);
cout << p.m_A << endl;
cout << p.m_B << endl;
cout << p.m_C << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
//点类
class Point
{
int m_X:
int m_Y;
};
//圆类
class Circle
{
//类对象成为类成员
Point p;
};
圆类中有点类的对象作为成员,点就是圆类的对象成员
那么创建圆对象的时候,圆和点的构造函数和析构函数的顺序是谁先谁后呢?下面的例子可以给你解答。
#include <iostream>
#include <string>
using namespace std;
class Phone
{
public:
Phone(string name)
{
P_name = name;
cout << "phone类的构造函数" << endl;
}
~Phone()
{
cout << "Phone类的析构函数" << endl;
}
string P_name;
};
class Person
{
public:
//这里含有隐式转换法,Phone m_Phone = phone,所以下面的写法是正确的
Person(string name,string phone) :m_Name(name),m_Phone(phone)
{
cout << m_Name << "偷了别人的" << m_Phone.P_name << endl;
cout << "Person类的构造函数" << endl;
}
~Person()
{
cout << "Person类的析构函数" << endl;
}
//姓名
string m_Name;
//手机
Phone m_Phone;
};
int main()
{
Person p("张三","苹果手机");
system("pause");
return 0;
}
结论:当创建人这个对象的时候,你需要将这个人身上的零部件给创建好才能够创建好这个人,当销毁的时候正好相反(也可以用占的知识去理解,先进后出)。
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
// 在编译阶段分配内存
// 类内声明
static int m_A;
//静态成员变量也是有访问权限的
private:
static int m_B;
};
// 类外初始化,指明是Person作用域下
int Person::m_A = 100;
int Person::m_B = 100;
void test01()
{
Person p1;
//p1.m_A = 100;假如没有上面的的初始化,在这里初始化的话是会报错的,所以静态变量需要初始化完成才能创建对象
cout << p1.m_A << endl;
Person p2;
p2.m_A = 200;
//100 ? 200 结果是200,所有对象共享同一份数据
cout << p1.m_A << endl;
}
void test02()
{
//所有对象共享同一份数据,因此有两种访问方式
//1,通过对象来访问
// Person p;
// cout << p.m_A << endl;
//2,通过类名来访问
cout << Person::m_A << endl;
//cout << Person::m_B << endl; 无法在类外访问有私有权限的成员变量
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//静态成员函数
static void func()
{
m_A = 200; //静态成员函数可以调用静态成员变量
//m_B = 200; 这里就会报错,因为静态成员函数不能访问非静态成员变量
cout << "static void func1()的调用" << endl;
}
static int m_A;
int m_B;
private:
//静态成员也是有私有权限的
static void func2()
{
cout << "static void func2()的调用" << endl;
}
};
int Person::m_A = 100;
void test01()
{
Person p;
//1,通过类访问静态成员函数
p.func();
//2,通过类名来访问成员函数
Person::func();
}
int main()
{
test01();
system("pause");
return 0;
}
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
在C++中,类内的成员函数和成员变量是分开存储的
只有非静态成员才属于类的对象上
#include <iostream>
#include <string>
using namespace std;
class Person01
{
};
class Person02
{
int m_A; //非静态成原变量
static int m_B; //静态成员变量
static void func() //静态成员函数
{
}
void func1() //非静态成员函数也不占对象的字节
{
}
};
int Person02::m_B = 100;
void test01()
{
//空对象所占的字节为1,在结构里也是一样,空结构体也是1
Person01 p1;
cout << "Person01所占字节=" << sizeof(p1)<< endl;
}
void test02()
{
//对象里面的非静态成员变量属于这个对象,所以这个非静态成员变量所占的大小就是这个对象的大小,也就是4
//静态成员变量和静态成员函数都不属于某一个对象,而是属于全部对象,所以不是占这个对象上,不会增加这个对象的大小
Person02 p2;
cout << "Person02所占字节=" << sizeof(p2)<< endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.3.2 this指针的概念
通过4.3.1我们知道成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一份代码
那么问题是:这一块代码是如何区分那个对象调用自己呢?
C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象
this指针是隐含在每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途
- 当形参和成员变量同名时,可以用this指针来区分(重命名)
- 在类的非静态成员函数中返回对象本身,可以使用return *this
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//形参和成员变量重命名
Person(int age)
{
this->age = age; //this指针 指向被调用的成员函数 所属的对象(p)
}
//这个函数作用是将p1的年龄加到p2上面去
void PersonAddAge(Person p1)
{
this->age += p1.age; //this->age 调用这个函数的age也就是p2的age
}
//这个函数作用是将p1的年龄加到p2上面去
Person & PersonAddAge01(Person p1)
{
this->age += p1.age; //this->age 调用这个函数的age也就是p2的age
return *this; //this是指向p2的指针,所以*this也就是p2,返回值使用了引用也就是说返回的还是p2的本身
}
int age;
};
void test01()
{
Person p(18);
cout << "p的年龄为" << p.age <<endl;
}
void test02()
{
Person p1(10);
Person p2(10);
p2.PersonAddAge(p1);
cout << "p2加上p1的年龄为" << p2.age << endl; //p1+p2 = 20
//链式函数
p2.PersonAddAge01(p1).PersonAddAge01(p1).PersonAddAge01(p1);
cout << "p2加上p1的年龄为" << p2.age << endl; //p2.PersonAddAge01(p1)返回值是p2的本身,所以它还可以继续调用函数
//当函数的返回值不是Person &而是Person那么根据拷贝函数的理念也就创建了另一个对象,不再是p2
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this
如果用到this指针,需要加以判断保证代码的健壮性
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
void showClassName()
{
cout << " 无成员变量的输出 " << endl;
}
void showClassAge()
{
if(this == NULL)
{
return;
}
cout << " 有成员变量的输出 " << age << endl; //空指针无法访问有成员变量的函数,因为这个age相当于this->age,这个this指向就不可以为空,所以会报错
//想要不报错就需要在判断这个this指向为空的时候给打断这个函数,如上方所示
}
int age;
};
void test01()
{
Person *p = NULL;
p->showClassName(); //空指针也是可以访问无成员变量的函数
p->showClassAge();
}
int main()
{
test01();
system("pause");
return 0;
}
4.3.4 const修饰成员函数
常函数:
- 成员函数加上const后我们称这个函数为常函数
- 常函数内不可以修改修改成员属性
- 成员属性声明时加关键字mutable后,在常函数里面依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数(其他对象还是可以调用常函数)
- 常对象也能调用mutable修饰的成员变量
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person()
{
}
//常函数
void showPerson() const //放在成员函数后面是为了修饰this指针,让this的指向的值不可以修改
//const Person * const this
{
//this->m_A = 100; 报错,常函数里面不能调用成员属性
this->m_B = 100;
cout << m_B << endl;
}
void func()
{
m_A = 1000;
}
int m_A;
mutable int m_B; //mutable(可变的)这个关键字可以使成员属性在常函数里面修改
};
void test01()
{
Person p;
p.showPerson(); //其他对象依然可以调用常函数
}
//常对象
void test02()
{
const Person p;
//p.m_A = 100; 常对象不能调用没有经过mutable关键字修饰的成员变量
p.m_B = 200; //常对象能调用经过mutable关键字修饰的成员变量
p.showPerson(); //常对象调用常函数,不能调用非常函数
//p.func(); 报错,假如说可以调用,因为这个函数可以修改成员变量,那么调用函数从侧面就修改了成员变量,所以不能调用非常函数
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.4 友元
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要弄到友元
友元的目的就是让一些函数或者类,访问另一类的私有成员
友元的关键字是:friend
友元的三种实现
- 全局函数做友元
- 类做友元(类中访问私有函数)
- 成员函数做友元
4.4.1 全局函数做友元
#include <iostream>
#include <string>
using namespace std;
//建筑类
class Building
{
friend void GoodGay(Building *building); //Building的友元,这个阿叔可以访问类中的私有属性
public:
Building()
{
SettingRoom = "客厅";
BedRoom = "卧室";
}
public:
string SettingRoom; //客厅
private:
string BedRoom; //卧室
};
//全局函数
void GoodGay(Building *building)
{
cout << "全局函数访问公有属性:" << building->SettingRoom << endl;
cout << "全局函数访问私有属性:" << building->BedRoom << endl;
}
void test01()
{
Building building;
GoodGay(&building);
}
int main()
{
test01();
system("pause");
return 0;
}
4.4.2 类做友元
#include <iostream>
#include <string>
using namespace std;
//声明下面有Building类,最好是反过来,不然下面的问题需要好好理解一下
class Building;
//好友类,想要访问Buildng类的私有属性
class GoodGay
{
public:
//首先先定义的GoodGay类,编译器在这个类里面找不到Building类,所以下面的构造和访问函数全部会报错,只能使用类外写构造和访问函数的形式
//最终要的是,在类外的构造函数和访问函数需要在Building类下面,使编译器识别出来
GoodGay();
void visit();
/*
GoodGay()
{
building = new Building;
}
void visit()
{
cout << "好友类访问Building类的公有属性" << building->SettingRoom << endl;
cout << "好友类访问Building类的私有属性" << building->BedRoom << endl;
}
*/
//必须使用指针来创建一个对象,因为声明在下面,这只是一个指针,且没有初始化
Building *building;
};
//建筑类
class Building
{
friend class GoodGay;
public:
Building()
{
SettingRoom = "客厅";
BedRoom = "卧室";
}
public:
string SettingRoom; //客厅
private:
string BedRoom; //卧室
};
//类外的构造函数和访问函数
GoodGay::GoodGay()
{
//开辟了一个Building空间,并且在这里进行了初始化
building = new Building;
}
void GoodGay::visit()
{
cout << "好友类访问Building类的公有属性" << building->SettingRoom << endl;
cout << "好友类访问Building类的私有属性" << building->BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
cout << gg.building->SettingRoom << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.4.3 成员函数做友元
#include <iostream>
#include <string>
using namespace std;
//声明下面有Building类,最好是反过来,不然下面的问题需要好好理解一下
class Building;
//好友类,想要访问Buildng类的私有属性
class GoodGay
{
public:
GoodGay();
void visit();
void visit1();
//必须使用指针来创建一个对象,因为声明在下面,这只是一个指针,且没有初始化
Building *building;
};
//建筑类
class Building
{
//上一讲的代码类作为友元,类中的所有函数都可以访问Buildng类的私有属性,这一讲是成员函数作为友元,只有这个成员函数才能访问私有属性
friend void GoodGay::visit();
public:
Building()
{
SettingRoom = "客厅";
BedRoom = "卧室";
}
public:
string SettingRoom; //客厅
private:
string BedRoom; //卧室
};
//类外的构造函数和访问函数
GoodGay::GoodGay()
{
//开辟了一个Building空间,并且在这里进行了初始化
building = new Building;
}
void GoodGay::visit()
{
cout << "好友类访问Building类的公有属性" << building->SettingRoom << endl;
cout << "好友类访问Building类的私有属性" << building->BedRoom << endl;
}
void GoodGay::visit1()
{
cout << "好友类访问Building类的公有属性" << building->SettingRoom << endl;
//cout << "好友类访问Building类的私有属性" << building->BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit1();
}
int main()
{
test01();
system("pause");
return 0;
}
4.5 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//1,成员函数实现+重载
// 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 + p2; //成员函数的本质调用是Person p3 = p1.operator+(p2) 全局函数的本质调用是Person p3 =opeator+(p1,p2)
Person p4 = p1 + 100; //运算符重载,也可以使用函数重载
cout << "p3.m_A=" << p3.m_A << endl;
cout << "p3.m_A=" << p3.m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结1:对于内置的数据类型(就是普通的加减乘除)的表达式的运算法是不可改变的
总结2:不要滥用运算符重载(本身是加你函数体内部写成减)
4.5.2 左移运算符重载
作用:可以输出自定义数据类型
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
//加入在成员函数里面进行左移运算符(<<)的重载,这个函数的本质就是Person p; p.operator<<(cout)
//这样的话cout就是在右边了,不符合 cout需要在左边
// void operator<<(cout)
// {
// }
int m_A;
int m_B;
};
//全局函数实现左移运算符,函数的本质是operator<<(cout,p) 这样cout就在p的左边了,也就可以简化为cout<<p
//***重要的是理解cout是可以在函数里面的,他的类型是ostream(数据流),在函数里面只能有一个,所以使用的是引用
//这个函数返回的是cout就是说可以继续最加东西cou<<
ostream & operator<<(ostream &cout, Person &p)
{
cout << "m_A=" << p.m_A << "m_B=" << p.m_B;
return cout;
}
void test01()
{
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p;
//***第一个左移运算符其实是重装载的,所以会直接调用上面那个全局函数
//***第二,三个左移运算符是系统自带的左移运算符,所以后面的值可以输出,endl代表的是下一行的意思
cout << p << "hello" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
#include <iostream>
#include <string>
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赋值给temp
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream & operator<<(ostream &cout, MyInteger myint)
{
cout << myint.m_Num;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++(++myint) << endl; //前置递增两次,为何要递增两次呢,就是因为如果上面的成员函数(前置递增运算符)返回值是MyInter,
//返回的只是myint的值,也就是另一个对象了,第一次++看不出来,但是第二次++就可以看出myint没有递增
cout << myint << endl;
}
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl; //后置递增不能使用两次,因为返回的在上面的成员函数(后置递增运算符)中返回的中间变量,所以myint只增加了一次
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
总结:前置递增返回的是引用,后置递增返回的是值
4.5.4 赋值运算符重载
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝函数函数,对属性进行值拷贝
- 赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深拷贝问题
#include <iostream>
#include <string>
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赋值是地址,所以赋值后指向的是同一个堆区,会进行二次释放,导致报错
//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);
//这里想要实现连续赋值的话,上面的赋值重载必须是返回Person&,因为p2调用重载的=,返回的是p2才能继续赋值给p3
//如果返回值是void的话 p3=void就出错了
p3 = p2 = p1; //赋值
cout << "p1的年龄=" << *p1.m_Age << endl;
cout << "p2的年龄=" << *p2.m_Age << endl;
cout << "p2的年龄=" << *p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.5.5 关系运算符重载
作用:重载关系运算符。可以让两个自定义的类型对象进行对比操作
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
//重载关系运算符(==)
bool operator==(Person &p)
{
if(m_Name==p.m_Name && m_Age == p.m_Age)
{
return true;
}
return false;
}
//重载关系运算符(!=)
bool operator!=(Person &p)
{
if(m_Name==p.m_Name && m_Age == p.m_Age)
{
return false;
}
return true;
}
string m_Name;
int m_Age;
};
void test01()
{
Person p1("jackie",18);
Person p2("ackie",18);
if(p1 == p2)
{
cout << "p1等于p2" << endl;
}
else
{
cout << "p1不等于p2" << endl;
}
}
void test02()
{
Person p1("jackie",18);
Person p2("jackie",18);
if(p1 != p2)
{
cout << "p1不等于p2" << endl;
}
else
{
cout << "p1等于p2" << endl;
}
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.5.6 函数调用运算符重载
- 函数运算符()也可以进行重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
#include <iostream>
#include <string>
using namespace std;
class MyPrint
{
public:
//函数运算符进行重载
void operator()(string print)
{
cout << print << endl;
}
//仿函数没有固定写法,非常灵活
int operator()(int a,int b)
{
return a+b;
}
};
void test01()
{
MyPrint p;
//由于重载后使用的方式非常像函数的调用,因此称为仿函数
p("hello world!!");
//相加
int c= p(10,10);
cout << c << endl;
//匿名函数对象,MyPrint(匿名函数)也可以直接地调用重载的函数运算符
cout << MyPrint()(10,10) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.6 继承
有些类与类之间存在特殊的关系,例如下图所示
我们可以发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特点
这个时候我们就可以考虑利用继承的技术,减少代码的重复性
4.6.1 继承的基本用法
语法:class 子类 :继承方式 父类
子类也称为派生类,父类也称为基类
#include <iostream>
#include <string>
using namespace std;
//一个网页的公共部分类
class BasePage
{
public:
//头部
void header()
{
cout << "首页,公开课,登录,注册...."<<endl;
}
//尾部
void footer()
{
cout << "帮助中心,交流合作,产品...."<<endl;
}
};
//语法:class 子类 :继承方式 父类
class Java : public BasePage
{
public:
void content()
{
cout << "Java的视屏集合点"<<endl;
}
};
class Python : public BasePage
{
public:
void content()
{
cout << "Python的视屏集合点"<<endl;
}
};
void test01()
{
Java ja;
ja.header();
ja.footer();
ja.content();
Python py;
py.header();
py.footer();
py.content();
}
int main()
{
test01();
system("pause");
return 0;
}
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过来的表现为其共性,而自己新增的成员则表现为其个性
4.6.2 继承方式
继承方式一共有三种:
- 公有继承
- 保护继承
- 私有继承
#include <iostream>
#include <string>
using namespace std;
//父类
class Father
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//语法:class 子类 :继承方式 父类
class Son1 : public Father
{
void func()
{
m_A = 100; //继承过来之后依然是公有权限
m_B = 100; //继承过来之后依然是保护权限
//m_C = 100; 报错,Son1继承不到Fother是有私有成员
}
};
void test01()
{
Son1 son1;
son1.m_A = 10;
//son1.m_B = 10; //类外无法访问保护权限
}
class Son2 : protected Father
{
void func()
{
m_A = 100; //继承过来之后共有权限变成了保护权限
m_B = 100; //继承过来之后依然是保护权限
//m_C = 100; 报错,Son2继承不到Fother的私有成员
}
};
void test02()
{
Son2 son2;
//son2.m_A = 10; //类外无法访问保护权限
//son2.m_B = 10; //类外无法访问保护权限
}
class Son3 : private Father
{
void func()
{
m_A = 100; //继承过来之后共有权限变成了私有权限
m_B = 100; //继承过来之后共有权限变成了私有权限
//m_C = 100; 报错,Son3继承不到Fother的私有成员
}
};
void test03()
{
Son3 son3;
//son3.m_A = 10; //类外无法访问私有权限
//son3.m_B = 10; //类外无法访问私有权限
}
//孙子类
class GrendSon : public Son3
{
void func()
{
//m_A = 100; son3中m_A,m_B已经是私有权限了,所以GrendSon也就无法继承m_A,m_B,也就访问不到了
//m_B = 100;
//m_C = 100; 报错,GrendSon继承不到Son3是有私有成员
}
};
int main()
{
test01();
system("pause");
return 0;
}
4.6.3 继承中的对象模型
问题:从父类继承过来的成员,那些属于子类对象中?
#include <iostream>
#include <string>
using namespace std;
//父类
class Father
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是被隐藏了,但是还是会继承下来
};
//语法:class 子类 :继承方式? 父类
class Son1 : public Father
{
public:
int m_D;
};
void test01()
{
Son1 son;
//大小是16字节
cout << "son的大小=" << sizeof(son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.6.4 继承中的构造与析构函数
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构函数顺序是谁先谁后呢?
#include <iostream>
#include <string>
using namespace std;
//父类
class Father
{
public:
Father()
{
cout << "Father的构造函数" << endl;
}
~Father()
{
cout << "Father的析构函数" << endl;
}
};
//语法:class 子类 :继承方式? 父类
class Son1 : public Father
{
public:
Son1()
{
cout << "Son1的构造函数" << endl;
}
~Son1()
{
cout << "Son1的析构函数" << endl;
}
};
void test01()
{
Son1 s;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:继承中,先调用父类的构造函数,后调用子类的构造函数(先有父亲后有儿子)
析构函数则是跟构造完全相反(白发人送黑发人)
4.6.5 继承同名成员的处理方式
问题:当子类和父类出现同名的成员,如何通过子类对象,访问到子类或者父类的同名的数据呢?
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
#include <iostream>
#include <string>
using namespace std;
//父类
class Father
{
public:
Father()
{
m_A = 100;
}
void func()
{
cout << "Father下的func()" << endl;
}
void func(int a)
{
cout << "Son1下的func(int a)" << endl;
}
int m_A;
};
//语法:class 子类 :继承方式? 父类
class Son1 : public Father
{
public:
Son1()
{
m_A = 200;
}
void func()
{
cout << "Son1下的func()" << endl;
}
int m_A;
};
void test01()
{
Son1 son;
cout << son.m_A << endl; //结果是100,同名的情况下,访问的是自身的成员,且把父类的同名给隐藏了
cout << son.Father::m_A << endl; //想要访问的话需要加上作用域
son.func(); //调用的是son类下的func()
son.Father::func(); //想要访问父类的成员同名函数,需要加作用域
son.Father::func(100); //当子类中没有同名函数的时候,父类的重载函数也是可以直接调用的,但是当子类有同名函数的时候
//父类中的同名和重载都会隐藏起来,所以重载同名函数的访问需要加作用域
}
int main()
{
test01();
system("pause");
return 0;
}
4.6.6 继承同名静态成员的处理方式
问题:继承中同名静态成员在子类对象上如何进行访问的?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
#include <iostream>
#include <string>
using namespace std;
//父类
class Father
{
public:
static void func()
{
cout << "Father下的func()函数" << endl;
}
static int m_A;
};
//类内声明,类外初始化
int Father::m_A = 100;
//语法:class 子类 :继承方式? 父类
class Son1 : public Father
{
public:
static void func()
{
cout << "Son1下的func()函数" << endl;
}
static int m_A;
};
int Son1::m_A = 200;
//同名的静态成员属性
void test01()
{
cout << "通过对象来访问成员属性" << endl;
Son1 son;
cout << "Son作用域下的m_A=" <<son.m_A << endl;
cout << "Father作用域下的m_A=" <<son.Father::m_A << endl; //想要访问的话需要加上作用域
cout << "通过类来访问成员属性" << endl;
cout << "Son作用域下的m_A=" <<Son1::m_A << endl;
//第一个::的意思是通过类名来访问 第二个::代表访问Father作用域下的
cout << "Father作用域下的m_A=" <<Son1::Father::m_A << endl;
}
//同名的静态函数
void test02()
{
cout << "通过对象来访问成员函数" << endl;
Son1 son;
son.func();
son.Father::func();
cout << "通过类来访问成员函数" << endl;
Son1::func();
Son1::Father::func();
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.6.7 多继承语法
C++允许一个类继承多个类
语法:class 子类 :继承方式 父类 ,继承方式 父类..........
多继承可能引发父类中有同名成员的出现,需要加作用域区分
C++在开发中不建议用多继承
4.6.8 菱形继承
菱形继承的概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承方式称为菱形继承,或者钻石继承
典型的菱形继承案例:
菱形继承的问题
- 养和驼都继承了动物的数据,当羊驼使用数据时,就会产生二义性
- 羊驼继承自动物的数据继承了两份,其实我们知道,继承一份就够了
#include <iostream>
#include <string>
using namespace std;
//动物类
class Animal
{
public:
int Age;
};
//羊类,虚继承(解决菱形问题)
class Sheep : virtual public Animal
{
};
//驼类,虚继承(解决菱形问题)
class Tuo : virtual public Animal
{
};
//羊驼类
class SheepTuo : public Sheep , public Tuo
{
};
void test01()
{
SheepTuo st;
//这里是多继承,可以加作用域进行访问
st.Sheep::Age = 100;
st.Tuo::Age = 200;
//但是羊驼继承了两个年龄,它本身只需要一个年龄,也就造成了资源的浪费
//虚继承可以解决菱形继承的问题
cout << "st.Sheep::Age = " << st.Sheep::Age << endl; //未虚继承时年龄是100,虚继承之后年龄是200
cout << "st.Tuo::Age = " << st.Tuo::Age << endl;
//虚继承之后,也没了二义性的问题了,因为这个年龄只有一份数据,指的都是羊驼的年龄
cout << "st.Age = " << st.Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源的浪费以及毫无意义
- 利用虚继承可以解决菱形继承的问题
4.7 多态
4.7.1 多态的基本概念
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别
- 静态多态的函数地址早已绑定--编译阶段确定函数地址
- 动态多态的函数地址晚绑定--运行阶段确定函数地址
#include <iostream>
#include <string>
using namespace std;
//动物类
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.speak();
}
void test01()
{
Animal an;
doSpeak(an); //动物在说话
Cat cat;
doSpeak(cat); //子类和父类之间不需要强制转换,Animal &animal = cat 这是可以的
//结果依然是-动物在说话,因为这是静态多态,地址早已经绑定了
//然而我们希望传的是谁,就希望是谁在说话,地址晚绑定能解决这个问题
}
//多态的测试
//给父类的Speak()函数加上virtual,是一个虚函数
//多态满足条件
//1,有继承关系
//2,子类重写父类中的虚函数
//多态使用
//父类指针或者引用指向子类对象(Animal &animal = cat)
void test02()
{
Dog dog;
doSpeak(dog); //变成了狗在说话
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
总结:
多态满足条件
1,有继承关系
2,子类重写父类中的虚函数
多态使用
父类指针或者引用指向子类对象(Animal &animal = cat)
重写:函数返回值类型,函数名,参数列表完全一致称为重写(也就是子类和父类有完全一样的函数,函数体内部可以不一样)
这里是别人对父类为什么可以指向子类做的讲解:C++父类指针指向子类对象的实现原理_XX風的博客-CSDN博客_c++ 父类指针指向子类
//************************************************************************
下面是一个实例,使用常规方法和多态的方法来实现一个计算器的运算
#include <iostream>
#include <string>
using namespace std;
//计算机类
class Calculator
{
public:
int getResult(string operat)
{
if(operat == "+")
{
return m_A + m_B;
}
else if (operat == "-")
{
return m_A - m_B;
}
else if (operat == "*")
{
return m_A * m_B;
}
return 0;
}
int m_A;
int m_B;
};
//上面是常规的计算机运算类,当实际开发者,我们如果发现*运算出错的话,我们需要在源码里面进行修改,或者当我们增加其他运算的时候(开方),也需要在源码修改
//但是在实际的开发中,我们尽量不在源码中修改(因为你也不知道源码是怎么跑起来的,修改后可能就不跑了)
//使用多态会更加适合在开发中
void test01()
{
Calculator cal;
cal.m_A = 10;
cal.m_B = 10;
cout << cal.m_A << "+" << cal.m_B << "=" << cal.getResult("+") << endl;
cout << cal.m_A << "-" << cal.m_B << "=" << cal.getResult("-") << endl;
cout << cal.m_A << "*" << cal.m_B << "=" << cal.getResult("*") << endl;
}
//使用多态的计算机计算
class abstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_A;
int m_B;
};
//加法运算
class addCalculator : public abstractCalculator
{
int getResult()
{
return m_A + m_B;
}
};
//减法运算
class reduceCalculator : public abstractCalculator
{
int getResult()
{
return m_A - m_B;
}
};
//乘法运算
class takeCalculator : public abstractCalculator
{
int getResult()
{
return m_A * m_B;
}
};
void test02()
{
//多态使用的规则,需基类指向派生类
//加法运算
abstractCalculator *abc = new addCalculator;
abc->m_A = 10;
abc->m_B = 10;
cout << abc->getResult() << endl;
delete abc;
//减法运算
abc = new reduceCalculator;
abc->m_A = 10;
abc->m_B = 10;
cout << abc->getResult() << endl;
delete abc;
//乘法运算
abc = new takeCalculator;
abc->m_A = 10;
abc->m_B = 10;
cout << abc->getResult() << endl;
delete abc;
//当还需要加什么运算的时候,不需要修改原来的代码,直接在后面加就行,这就是多态的好处
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容
因此可以将虚函数改成纯虚函数
纯虚函数的语法:virtual 返回值类型 函数名 (参数列表)= 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实现实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream>
#include <string>
using namespace std;
//基类
class Base
{
public:
//纯虚函数
virtual void func() = 0;
};
class Son : public Base
{
//重写
virtual void func()
{
cout << "纯虚函数的实验" << endl;
}
};
void test01()
{
//Base b; //抽象类无法实例化对象
//Son s; //子类必须重写父类的纯虚函数,否则无法实例化对象
//多态使用需要父类指向子类
Base *b = new Son;
b->func();
delete b;
}
int main()
{
test01();
system("pause");
return 0;
}
***********************************************************************************************************
下面是使用纯虚函数制作饮品的事例
#include <iostream>
#include <string>
using namespace std;
//抽象制作饮品
class AbstractDrink
{
public:
/* 煮水 */
virtual void boil() = 0;
/* 倒入材料 */
virtual void material() = 0;
/* 倒入杯子 */
virtual void pourintocup() = 0;
/* 辅料 */
virtual void ecipients() = 0;
void func()
{
boil();
material();
pourintocup();
ecipients();
}
};
class Coffee : public AbstractDrink
{
/* 煮农夫三拳 */
virtual void boil()
{
cout << "煮农夫三拳" << endl;
}
/* 倒入咖啡 */
virtual void material()
{
cout << "倒入咖啡" << endl;
}
/* 倒入杯子 */
virtual void pourintocup()
{
cout << "倒入杯子" << endl;
}
/* 牛奶和糖 */
virtual void ecipients()
{
cout << "牛奶和糖" << endl;
}
};
//想要制作其他的,可以继续加
void doWork(AbstractDrink &abc)
{
abc.func();
}
void test01()
{
Coffee cof;
doWork(cof);
}
int main()
{
test01();
system("pause");
return 0;
}
4.7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类在释放时无法调用到子类的析构代码
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名()= 0; 类内声明
类名::类名(){} 类外实现
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal的构造函数" << endl;
}
//加上关键字virtual就成了虚析构函数
//如果不是虚析构函数或者纯虚析构函数,在test01()函数中就不会释放Cat中的堆区数据(不会进入Cat的析构函数)
// virtual ~Animal()
// {
// cout << "Animal的析构函数" << endl;
// }
//纯虚析构函数需要类内声明,类外实现
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal的析构函数" << endl;
}
class Cat : public Animal
{
public:
Cat(string name)
{
Name =new string(name);
cout << "Cat的构造函数" << endl;
}
~Cat()
{
cout << "Cat的析构函数" << endl;
if(Name != NULL)
{
delete Name;
Name = NULL;
}
}
string *Name;
};
void test01()
{
Animal *animal = new Cat("汤姆");
//释放了Animal的指针,但是Cat中也有堆区数据,也需要释放
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
***********************************************************************************************************
以下是个案例:
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储),将每一个零件封装出抽象基类,并且提供不同的厂商生产不同的部件,例如intel厂商和lenovo厂商创建电脑类提供电脑工作的函数,并且调用每个零件工作的接口,测试时组装三台不同的电脑进行工作
#include <iostream>
#include <string>
using namespace std;
//CPU
class CPU
{
public:
virtual void calculate() = 0;
};
//显卡
class VideoCard
{
public:
virtual void display() = 0;
};
//内存条
class Memory
{
public:
virtual void storege() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU *cpu,VideoCard *mc,Memory *mem)
{
//这里就是多态的产生的地方,我们本身需要传的是基类的对象,在下面的函数中,我们传进来的起始是子类的对象
//父类指向子类
m_cpu = cpu;
m_vc = mc;
m_mem = mem;
}
void doWork()
{
//让零件工作起来
m_cpu->calculate();
m_vc->display();
m_mem->storege();
}
//如果传进来的是堆区的数据(指针),还需要进行释放
//提供析构函数
~Computer()
{
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;
VideoCard *m_vc;
Memory *m_mem;
};
//intel的厂商
class IntelCPU :public CPU
{
public:
virtual void calculate()
{
cout << " Intel的CPU开始计算了 "<< endl;
}
};
class IntelVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << " Intel的显卡开始显示了 "<< endl;
}
};
class IntelMemory :public Memory
{
virtual void storege()
{
cout << " Intel的内存条开始存储了 "<< endl;
}
};
//Lenovo的厂商
class LenovoCPU :public CPU
{
public:
virtual void calculate()
{
cout << " Lenovo的CPU开始计算了 "<< endl;
}
};
class LenovoVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << " Lenovo的显卡开始显示了 "<< endl;
}
};
class LenovoMemory :public Memory
{
virtual void storege()
{
cout << " Lenovo的内存条开始存储了 "<< endl;
}
};
void test01()
{
//创建第一台电脑
cout << "创建第一台电脑" << endl;
Computer *computer1 = new Computer(new IntelCPU,new IntelVideoCard,new IntelMemory);
computer1->doWork();
delete computer1;
//创建第二台电脑
cout << "创建第二台电脑" << endl;
Computer *computer2 = new Computer(new LenovoCPU,new LenovoVideoCard,new LenovoMemory);
computer2->doWork();
delete computer2;
//创建第三台电脑
cout << "创建第三台电脑" << endl;
Computer *computer3 = new Computer(new LenovoCPU,new IntelVideoCard,new IntelMemory);
computer3->doWork();
delete computer3;
}
int main()
{
test01();
system("pause");
return 0;
}