大部分思考过程是在代码中标注出来
一、封装
1.封装的意义
(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)类在设计时,可以把属性和行为放在不同权限下,加以控制。
访问权限:①public 公共权限 成员类内类外均可访问
②protected 保护权限 成员类内可以访问,类外不可以访问
③private 私有权限 成员类内可以访问,类外不可以访问
2.struct与class的区别: 默认的访问权限不同
struct默认公共权限
class默认私有权限
问题:类与结构体很相似,二者除了默认访问权限不同,还有什么其他不同吗?
①赋值方式区别
结构体struct可以直接赋值,class不行
struct student S={"张三",18,100};
②类型不同
struct是值类型,class是引用类型
③概念不同
二者语法基本相同,从声明到使用,都很相似,但struct的约束比class多,理论上,struct能做到的class也能做到,class能做到的struct不一定能做到。
3.成员属性设置为私有
好处:①可以自己控制读写权限
②可以检测数据有效性
#include<iostream>
using namespace std;
class Person
{
public:
void setName(string name) //设置姓名
{
m_Name=name;
}
string getName() //获取姓名
{
return m_Name;
}
int getAge() //获取年龄
{
return m_Age;
}
void setIdol(string idol) //设置偶像
{
m_Idol=idol;
}
private:
string m_Name; //可读可写(既设置又获取)
int m_Age; //只读(只获取)
string m_Idol; //只写(只设置)
};
int main()
{
Person p; //创建具体的人(实例化对象)
p.setName("张三"); //姓名设置
cout<<"姓名: "<<p.getName()<<endl; //获取姓名
cout<<"年龄: "<<p.getAge()<<endl; //只读
p.setIdol("小明"); //只写
system("pause");
return 0;
}
练习:
(1)设置立方体类:求出面积体积,分别用全局函数和成员函数判断俩立方体是否相同
#include<iostream>
using namespace std;
class Cube
{
public:
void setL(int l) //设置长
{
m_L = l;
}
int getL() //获取长
{
return m_L;
}
void setW(int w) //设置宽
{
m_W = w;
}
int getW() //获取宽
{
return m_W;
}
void setH(int h) //设置高
{
m_H = h;
}
int getH() //获取高
{
return m_H;
}
int calculateS() //获取面积
{
return 2*m_L*m_W+2*m_H*m_W+2*m_L*m_H;
}
int calculateV() //获取体积
{
return m_L*m_W* m_H;
}
//利用成员函数判断两个立方体是否相等
bool isSameByClass(Cube &c)
{
if (m_L==c.getL() && m_W==c.getW() && m_H==c.getH())
{
return true;
}
return false;
}
private:
int m_L; //长
int m_W; //宽
int m_H; //高
};
//利用全局函数判断两个立方体是否相等
bool isSame(Cube& c1, Cube& c2)
{
if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH())
{
return true;
}
return false;
}
int main()
{
Cube c1; //创建第一个立方体对象
c1.setL(10);
c1.setW(10);
c1.setH(10);
cout << "c1的面积为:" << c1.calculateS() << endl;
cout << "c1的体积为:" << c1.calculateV() << endl;
Cube c2; //创建第二个立方体对象
c2.setL(10);
c2.setW(10);
c2.setH(10);
//利用全局函数判断
bool ret = isSame(c1, c2);
if (ret)
{
cout << "全局函数判断:两个立方体相等" << endl;
}
else
{
cout << "全局函数判断:两个立方体不相等" << endl;
}
//利用成员函数判断
ret = c1.isSameByClass(c2);
if (ret)
{
cout << "成员函数判断:两个立方体相等" << endl;
}
else
{
cout << "成员函数判断:两个立方体不相等" << endl;
}
system("pause");
return 0;
}
问题:成员函数和全局函数的区别?
①成员函数面向对象,全局函数面向过程
②成员函数比全局函数少一个参数
(2)点和圆的关系:设置一个圆类和点类,计算点与圆的关系
#include<iostream>
using namespace std;
class Point //点类
{
public:
void setX(int x) //设置x
{
m_X=x;
}
int getX() //获取x
{
return m_X;
}
void setY(int y) //设置y
{
m_Y=y;
}
int getY() //获取y
{
return m_Y;
}
private:
int m_X;
int m_Y;
};
class Circle //圆类
{
public:
void setR(int r) //设置半径
{
m_R=r;
}
int getR() //获取半径
{
return m_R;
}
void setCenter(Point center) //设置圆心
{
m_Center=center;
}
Point getCenter() //获取圆心
{
return m_Center;
}
private:
int m_R; //半径
Point m_Center; //圆心
//在类中可以让另一个类作为本类中的成员
};
//判断点和圆位置关系的函数
void isInCircle(Circle &c,Point &p)
{
//计算两点间距离的平方
int distance=
(c.getCenter().getX()-p.getX()) * (c.getCenter().getX()-p.getX())+
(c.getCenter().getY()-p.getY()) * (c.getCenter().getY()-p.getY());
//计算半径的平方
int rDistance = c.getR() * c.getR();
//判断关系
if(distance == rDistance)
{
cout<<"点在圆上"<<endl;
}
else if(distance > rDistance)
{
cout<<"点在圆外"<<endl;
}
else
{
cout<<"点在圆内"<<endl;
}
}
int main()
{
Circle c; //创建圆
c.setR(10);
Point center; //圆心
center.setX(10);
center.setY(0);
c.setCenter(center);
Point p; //创建点
p.setX(10);
p.setY(10);
isInCircle(c,p); //判断关系
system("pause");
return 0;
}
把点类和圆类拆分到另一个文件中
①头文件point.h
#pragma once //防止头文件重复包含
#include <iostream>
using namespace std;
class Point // 点类
{
public:
void setX(int x); // 设置x
int getX(); // 获取x
void setY(int y); // 设置y
int getY(); // 获取y
private:
int m_X;
int m_Y;
};
//只保留成员函数的声明,把实现部分删掉
②源文件point.cpp
#include "point.h" //包含刚刚写的头文件
void Point::setX(int x) // 设置x
{
m_X = x;
}
int Point::getX() // 获取x
{
return m_X;
}
void Point::setY(int y) // 设置y
{
m_Y = y;
}
int Point::getY() // 获取y
{
return m_Y;
}
//只保留函数的所有实现
//Point::的作用是,告诉它是Point作用域下的成员函数
③头文件circle.h
#pragma once
#include <iostream>
using namespace std;
#include "point.h"
class Circle // 圆类
{
public:
void setR(int r); // 设置半径
int getR(); // 获取半径
void setCenter(Point center); // 设置圆心
Point getCenter(); // 获取圆心
private:
int m_R; // 半径
Point m_Center; // 圆心
// 在类中可以让另一个类作为本类中的成员
};
④源文件circle.cpp
#include "circle.h"
void Circle::setR(int r) // 设置半径
{
m_R = r;
}
int Circle::getR() // 获取半径
{
return m_R;
}
void Circle::setCenter(Point center) // 设置圆心
{
m_Center = center;
}
Point Circle::getCenter() // 获取圆心
{
return m_Center;
}
二、对象的初始化和清理
1.构造函数和析构函数
构造函数:创建对象时为对象的成员属性赋值(初始化)
析构函数:对象销毁前系统自动调用,执行一些清理工作(清理)
语法不同:
(1)构造函数:①无返回值也无void
类名(){} ②函数名与类名相同
③可以有参数,可以发生重载
④程序在调用对象时自动调用构造,且只调用一次
(2)析构函数:①无返回值也无void
~类名(){} ②函数名与类名相同,在名称前加上~
③不可以有参数,不可以发生重载
④程序在调用对象时自动调用构造,且只调用一次
class Person
{
public:
//构造函数
Person()
{
cout << "Person的构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
};
void test01() //测试案例
{
Person p; //创建对象(局部变量)。在栈上的数据,test01执行完毕后,释放这个对象
}
//test01执行完毕后自动调用对象的析构函数
int main() {
test01(); //调用
system("pause");
return 0;
}
构造和析构都是必须有的实现,如果我们自己不提供,编译器就会提供一个空实现的构造和析构
2.构造函数的分类及调用
分类方式:
(1)按参数分:①有参构造 ②无参构造(默认构造)
(2)按类型分:①普通构造 ②拷贝构造
调用方式:
①括号法 ②显示法 ③隐式转换法
分类:
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;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
public:
int age;
};
调用:
①括号法(最常用)
void test01()
{
Person p1; //默认构造函数调用
// 注意不要加 (),否则编译器会认为是一个函数声明
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数
cout << "p2的年龄为: " << p2.age << endl;
cout << "p3的年龄为: " << p3.age << endl;
}
运行结果:
②显示法
void test01()
{
Person p1; //默认构造函数
Person p2 = Person(10); //有参构造
Person p3 = Person(p2); //拷贝构造
}
注意事项1:匿名对象
void test01()
{
Person(10);
//匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
cout << "aaaa" << endl;
}
运行结果:
匿名对象的特点:aaaa在对象被销毁掉后才打印
注意事项2:不要利用拷贝构造函数,初始化匿名对象
void test01()
{
Person p1; //默认构造函数
Person p2 = Person(10); //有参构造
Person p3 = Person(p2); //拷贝构造
Person(p3); //编译器会认为Person(p3)==Person p3; 对象声明
}
③隐式转换法
void test01()
{
Person p4 = 10; //相当于写了 Person p4 = Person(10); 有参构造
Person p5 = p4; //相当于写了 Person p5 = Person(p4); 拷贝构造
}
3.拷贝构造函数的调用时机
通常有三种情况:
①使用一个已经创建完毕的对象来初始化一个新对象
②值传递的方式给函数参数传值
③ 以值方式返回局部对象
class Person
{
public:
Person()
{
cout << "无参构造函数!" << endl;
m_Age = 0;
}
Person(int age)
{
cout << "有参构造函数!" << endl;
m_Age = age;
}
Person(const Person& p)
{
cout << "拷贝构造函数!" << endl;
m_Age = p.m_Age;
}
//析构函数在释放内存之前调用
~Person()
{
cout << "析构函数!" << endl;
}
public:
int m_Age;
};
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,而是赋值操作
}
//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02()
{
Person p; //无参构造函数
doWork(p);
}
//3. 以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;
}
4.构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数
①默认构造函数(无参,函数体为空)
②默认析构函数(无参,函数体为空)
③默认拷贝构造函数,对属性进行值拷贝
调用规则:
①如果用户定义有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数
②如果用户定义拷贝构造函数,C++不再提供其他构造函数
void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
void test02()
{
//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
Person p1; //此时如果用户自己没有提供默认构造,会出错
Person p2(10); //用户提供的有参
Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
//如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; //此时如果用户自己没有提供默认构造,会出错
Person p5(10); //此时如果用户自己没有提供有参,会出错
Person p6(p5); //用户自己提供拷贝构造
}
5.深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作(如果利用编译器提供的拷贝构造函数,就是浅拷贝)
深拷贝:在堆区中重新申请空间,进行拷贝操作
#include<iostream>
using namespace std;
//浅拷贝问题:堆区的内存重复释放
//要利用深拷贝来解决
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height); //把数据创建到堆区 ,new int返回的是int*
//堆区的数据手动开辟,也需要手动释放 (在对象销毁前释放)
cout << "Person的有参构造函数调用" << endl;
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person &p)
{
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; 编译器默认实现就是这行代码 ,浅拷贝
m_Height = new int(*p.m_Height); //深拷贝
}
~Person()
{
//析构代码,将堆区开辟数据做释放操作
if(m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person的析构函数调用" << endl;
}
int m_Age;
int *m_Height; //要把身高这个数据拆递到堆区
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_Age << "身高为:" << *p1.m_Height << endl;//*p1解引用
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}
析构代码的用途:将堆区开辟数据释放
6.初始化列表
作用:给类中的属性进行初始化操作
语法:构造函数():属性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 << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main()
{
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
7.类对象作为类成员
类中的成员可以是另一个类的对象,称为对象成员
class A {}
class B
{
A a;
}
B中有对象A作为成员,A是对象成员
当创建B对象时,A和B的构造和析构的顺序是谁先谁后?
构造顺序:先调用对象成员的构造,再调用本类构造
析构顺序相反
class Phone //手机类
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone构造" << endl;
}
~Phone()
{
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
class Person //人类
{
public:
//初始化列表可以告诉编译器调用哪一个构造函数
//Phone m_Phone = pName 隐式转换法
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
string m_Name; //姓名
Phone m_Phone; //手机
};
void test01()
{
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
Person p("张三" , "苹果X");
cout << p.m_Name << "拿着:" << p.m_Phone.m_PhoneName << endl;
}
8.静态成员
定义:在成员变量和成员函数前加上关键字static,称为静态成员
分类:(1)静态成员变量
①所有对象共享同一份数据
②在编译阶段分配内存
③类内声明,类外初始化
class Person
{
//类内声明
public:
static int m_A; //静态成员变量
private:
static int m_B; //静态成员变量也是有访问权限的
};
//类外初始化
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}
(2)静态成员函数
①所有对象共享一个函数
②静态成员函数只能访问静态成员变量
class Person
{
public:
static void func() //静态成员函数
{
m_A = 100; //静态成员函数 可以访问 静态成员变量
cout << "static void func调用" << endl;
//m_B = 100; //错误,静态成员函数 不可以访问 非静态成员变量,无法区分到底是哪个对象的m_B属性
}
static int m_A; //静态成员变量的类内声明
int m_B; //非静态成员变量
private:
//静态成员函数也是有访问权限的
static void func2()
{
cout << "static void func2调用" << endl;
}
};
int Person::m_A = 10; //静态成员变量的类外初始化
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.func();
//2、通过类名
Person::func();
//Person::func2(); //类外访问不到私有静态成员函数
}
三、C++对象模型和this指针
1.成员变量和成员函数分开储存
类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
class Person
{
};
void test01()
{
Person p;
//空对象占用内存空间为:1个字节
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout << "size of p = " << sizeof(p) <<endl;
}
空对象占内存空间为:1个字节
class Person
{
int m_A; //非静态成员变量 属于类的对象上的
static int m_B; //静态成员变量 不属于类的对象上
void func(){} //非静态成员函数 不属于类的对象上,所有函数共享一个函数实例
static void func2() {} //静态成员函数 不属于类的对象上
};
int Person::m_B = 0;
void test01()
{
Person p;
cout << "size of p = " << sizeof(p) <<endl;
}
2.this指针概念
每一个非静态成员函数只会诞生一份函数实例,也就是多个同类型的对象会共用一块代码
那这一块代码是如何区分哪个对象调用自己的呢?
用this指针,指向被调用的成员函数所属的对象
概念:this指针是隐含每一个非静态成员函数内的一种指针,无需定义,直接使用就行
用途:①当形参和成员变量同名时,可用this指针来区分
②在类的非静态成员函数中返回对象本身,可用return *this
①解决名称冲突
this指向的age和属性的age是一样的(看颜色)
② 返回对象本身用*this
class Person
{
public:
Person(int age)
{
this->age = age; //this指针指向 被调用的成员函数 所属的对象
}
void PersonAddAge(Person &p) //&p用引用方式传入
{
this->age += p.age; //把p人的年龄加到自身
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddAge(p1);
cout << "p2.age = " << p2.age << endl;
}
class Person
{
public:
Person(int age)
{
this->age = age;
}
Person& PersonAddAge(Person &p) //Person&用引用方式返回
//如果用Person则为值返回,会创建新的对象,每次返回都是一个新的对象
{
this->age += p.age;
return *this; //this指向p2的指针,而*this指向的就是p2这个对象本体
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10); //链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //加三次
cout << "p2.age = " << p2.age << endl;
}
3.空指针访问成员函数
空指针也可以调用成员函数,但要注意有没有用到this指针
如果用到this指针,需判断代码的健壮性
//空指针访问成员函数
class Person
{
public:
void ShowClassName()
{
cout << "我是Person类!" << endl;
}
void ShowPersonAge()
{
if (this == NULL)
{
return;
}
cout << "age= " << m_Age << endl; //m_Age是一个属性,属性前默认有个this->
//即m_Age==this->m_Age
}
public:
int m_Age;
};
void test01()
{
Person * p = NULL; //空指针
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPersonAge(); //但是如果成员函数中用到了this指针,就不可以了
}
4.const修饰成员函数
常函数:①成员函数后加const后我们称这个函数为常函数
②常函数不可以修改成员属性
③成员属性声明时加关键字mutable后,常函数中依然可以修改
常对象:①声明对象前加const称该对象为常对象
②常对象只能调用常函数
常函数:
class Person
{
public:
//this指针的本质 是指针常量,指针的指向不可修改,指向的值可以修改
//即为Person * const this;
//如果想让指针指向的值也不可以修改,需要声明常函数
//即为const Person * const this;
void ShowPerson() const //常函数
//在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改了
{
//this->m_A = 100; 不允许修改指针指向的值
//this = NULL; this指针不可以修改指针的指向的
this->m_B = 100;
}
int m_A;
mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值
};
void test01()
{
Person p;
p.ShowPerson();
}
常对象:
void test02()
{
const Person p; //常对象
//p.m_A = 100; 报错,不允许修改指针指向的值
p.m_B = 100; //可修改
p.ShowPerson(); //常对象只能调用常函数
//p.func(); 不可以调用普通成员函数,因为普通成员函数可以修改属性
}
四、友元
在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元
目的:让一个函数或者类 访问另一个类中的私有成员
关键字:friend
三种实现:①全局函数做友元;②类做友元;③成员函数做友元
1.全局函数做友元
friend+全局函数
class Building
{
//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
friend void goodGay(Building * building);
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
void goodGay(Building * building) //全局函数
{
cout << "好基友全局函数 正在访问: " << building->m_SittingRoom << endl;
cout << "好基友全局函数 正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
Building b;
goodGay(&b);
}
2.类做友元
补充:公有成员函数的类外定义
class Person
{
public:
void phone(int p); //在类中声明函数原型
};
void Person::phone(int p) //在类外定义函数
{
// 函数体
}
类做友元:friend+类名
class Building; //类声明
class goodGay
{
public:
goodGay();
void visit(); //visit函数 访问Building中的属性
private:
Building *building; //指向new出来的对象
//定义一个指针变量building,指针类型是Building这个类
};
class Building
{
//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
friend class goodGay;
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building() //类外写成员函数,作用域::表示是Building下的一个构造函数
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
goodGay::goodGay() //类外写成员函数,作用域::表示是gooGay下的一个构造函数
{
building = new Building; //创建建筑物对象 new在堆区创建了一个对象
//new什么数据类型就返回什么数据类型的指针
//让building内部指针维护堆区的对象
}
void goodGay::visit() //类外写成员函数,作用域::表示是goodGay下的一个visit函数
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg; //实例化对象
gg.visit(); //对象调用visit函数
}
3.成员函数做友元
friend+类名::+函数名()
易错点:
①声明“友元成员函数”的类(Building)必须在“该成员函数”的所属类(goodGay)之前声明,在之后定义
②该成员函数(void visit( );)必须在类内(goodGay内)声明,类外定义void goodGay::visit(){...}
③goodGay类中构造函数的实现必须在Building类的定义之后,因为goodGay中的构造函数需要调用Building的构造函数
class Building;
class goodGay
{
public:
goodGay();
void visit(); //让visit函数作为Building的好朋友,可以访问Building中私有成员
void visit2(); //让visit2函数不可以访问Building中私有成员
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
//cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
五、运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
运算符重载,也可以发生函数重载
1.加号运算符重载+
作用:实现两个自定义数据类型相加的运算
class Person
{
public:
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; //错误,没有与这些操作数匹配的+运算符
}
①通过成员函数实现重载
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;
};
②成员函数重载本质调用
Person p3 = p1.operator+(p2);
//Person p3 = p1 + p2; //简化成这样
③通过全局函数实现重载
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 p3 = operator+(p1,p2);
//Person p3 = p1 + p2; //简化成这样
⑤运算符重载,也可以发生函数重载
//函数重载的版本
Person operator+(Person &p1, int num)
{
Person temp;
temp.m_A = p2.m_A + num;
temp.m_B = p2.m_B + num;
return temp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p4 = p1 + 100; //Person + int,相当于 operator+(p1,10)
cout << "p4.m_A" << p4.m_A << endl;
cout << "p4.m_B" << p4.m_B << endl;
}
2.左移运算符重载<<
作用:可以输出自定义数据类型
class Person
{
public:
int m_A;
int m_B;
};
void test01()
{
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p; //报错,没有与这些操作数匹配的<<运算符
}
① 不会利用成员函数重载左移运算符,因为无法实现cout在左侧
class Person
{
public:
void operator<<(Person &p)
//最后调用结果为p.operator<<(p) 没有这么多对象
{
}
int m_A;
int m_B;
};
class Person
{
public:
void operator<<(cout)
//本质:p.operator<<(cout),p调用operator成员函数,然后cout再参数传递进去
//简化版本:p << cout ,与预期cout << p结果不符
{
}
int m_A;
int m_B;
};
②只能利用全局函数重载左移运算符
cout是什么数据类型呢?cout是标准的输出流对象,通过ostream类创建出来的
void operator<<(ostream &cout,Person &p)
//本质:operator << (cout,p) //简化:cout << p
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
}
void test01()
{
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p;
}
这个全局函数的意思就是,如果在别的函数中输出cout << (Person类的对象)时,就会把这个当作形参,然后执行内部的代码cout << "m_A = "... ...
ostream &cout的原因:cout对象全局只能有一个,用引用的方式传递过来。输出流不能拷贝,也就是说要传入输出流对象参数只能地址传递,不能值传递
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 << "hello world" << endl; //链式编程思想
//如果cout << p调用之后返回的是void,那就无法继续后面
//如果返回的还是cout,就可以继续往后输出别的内容
}
用ostream &的原因:return cout返回了cout本身,才能链式使用,返回值和参数都设为同一种类型,这里的ostream类型必须是引用类型,所以返回值必须是引用。ostream&用来修饰返回的类型,返回的是ostream类的对象cout
可以把cout改成其他名称(比如out)因为引用的本质是起别名,传参的时候,ostream &out=cout而且传入的实参是cout
如果把类的属性私有化,用友元:
class Person
{
friend ostream & operator<<(ostream &out,Person &p);
public:
Person(int a,int b) //构造函数赋初值
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
ostream & operator<<(ostream &out,Person &p)
{
out << "m_A = " << p.m_A << " m_B = " << p.m_B;
return out;
}
void test01()
{
Person p(10,10);
//p.m_A = 10;
//p.m_B = 10; //私有成员不可以外部赋值
cout << p << "hello world" << endl;
}
3.递增运算符重载++
作用:通过重载递增运算符,实现自己的整型数据
分类:①前置++递增 ②后置++递增
区别:前置递增返回引用,后置递增返回值
class Myinter //自定义整型
{
friend ostream& operator<<(ostream& cout,Myinter myint);
public:
Myinter()
{
m_Num = 0;
}
private:
int m_Num;
};
//重载左移运算符
ostream& operator<<(ostream& cout,Myinter myint)
{
cout << myint.m_Num;
return cout;
}
void test01()
{
Myinter myint;
cout << ++myint << endl; //报错,没有与这些操作数匹配的++运算符
}
①前置++递增
class Myinter
{
friend ostream& operator<<(ostream& cout,Myinter myint);
public:
Myinter()
{
m_Num = 0;
}
Myinter& operator++()
//Myinter&返回引用是为了一直对一个数据进行递增操作
//如果返回值就不能重新进行返回值的自加
{
m_Num++; //先进行++运算
return *this; //再返回自身,*this解引用
}
private:
int m_Num;
};
②后置++递增
class Myinter
{
friend ostream& operator<<(ostream& cout,Myinter myint);
public:
Myinter()
{
m_Num = 0;
}
Myinter operator++(int) //int代表占位参数,可以用于区分前置和后置递增
//Myinter返回值,因为temp是局部变量不能引用传递
{
Myinter temp = *this; //先记录当时的结果
m_Num++; //后递增
return temp; //最后返回记录结果
}
private:
int m_Num;
};
4.赋值运算符重载=
c++编译器至少给一个类添加4个函数:
①默认构造函数 ②默认析构函数 ③默认拷贝构造函数,对属性进行值拷贝
④赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
//将年龄数据开辟到堆区,new int返回的是地址用int*指针接收
//堆区,由程序员手动开辟也手动释放
}
~Person() //析构函数将堆区数据释放
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//重载赋值运算符
Person& operator=(Person &p)
{
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//m_Age = p.m_Age; //编译器提供的代码是浅拷贝
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 << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
5.关系运算符重载==,!=
作用:可以让两个自定义类型对象进行对比操作
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;
}
return false;
}
string m_Name;
int m_Age;
};
void test01()
{
Person p1("Tom",18);
Person p2("Jerry",18);
if(p1==p2)
{
cout << "p1和p2是相等的" << endl;
}
else
{
cout << "p1和p2是不相等的" << endl;
}
}
6.函数调用运算符重载()
由于重载后使用的方式很像函数的调用,因此称为仿函数,非常灵活没有固定写法
class MyPrint //打印输出类
{
public:
void operator()(string test) //函数调用运算符重载
{
cout << test << endl;
}
};
void test01()
{
MyPrint myprint;
myprint("hello world"); //仿函数
}
函数调用:
void MyPrint02(string test) //函数调用
{
cout << test << endl;
}
void test01()
{
MyPrint myprint;
MyPrint02("hello world");
}
非常灵活,没有固定写法:
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd add;
int ret = add(100, 100);
cout << "ret = " << ret << endl;
//匿名函数对象调用
//匿名对象:类型( ),特点:当前行执行完后立即被释放
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
六、继承
有些类与类之间存在特殊的关系,比如:
定义这些类时,下级别的成员除了有上一级的共性,还有自己的特性,这时可以用继承来减少重复的代码
1.继承的基本语法
class A : public B;
public为继承方式,A类为 子类 或 派生类,B类为 父类 或 基类
如何实现网页中的内容?
普通实现:
//Java页面
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "JAVA学科视频" << endl;
}
};
//Python页面
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
//Java页面
cout << "Java下载视频页面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python页面
cout << "Python下载视频页面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++页面
cout << "C++下载视频页面如下: " << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
继承实现:
class BasePage //公共页面类
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
};
//Java页面
class Java : public BasePage
{
public:
void content()
{
cout << "JAVA学科视频" << endl;
}
};
//Python页面
class Python : public BasePage
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP : public BasePage
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
//Java页面
cout << "Java下载视频页面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python页面
cout << "Python下载视频页面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++页面
cout << "C++下载视频页面如下: " << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
2.继承方式
三种:①公共继承 ②保护继承 ③私有继承
大小:公共 < 保护 < 私有,继承后,大的继承方式可以把父类的小的权限改变
如果不写继承方式,则默认为private方式
①公共继承
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 Son2:protected Base1 //保护继承
{
public:
void func()
{
m_A = 100; //父类中公共成员,到子类中变为保护权限
m_B = 100; //父类中保护成员,到子类中变为保护权限
//m_C = 100; //父类中私有成员,子类访问不到
}
};
void test02()
{
Son2 s1;
//s1.m_A = 1000; //在Son1中m_A变为保护权限 类外访问不到
//s1.m_B = 1000; //在Son2中m_B是保护权限 类外访问不到
}
③私有继承
class Son3:private Base1 //私有继承
{
public:
void func()
{
m_A = 100; //父类中公共成员,到子类中变为私有成员
m_B = 100; //父类中保护成员,到子类中变为私有成员
//m_C = 100; //父类中私有成员,子类访问不到
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
//m_A = 1000;
//m_B = 1000;
}
};
void test03()
{
Son3 s1;
//s1.m_A = 1000; //在Son3中m_A变为私有成员 类外访问不到
//s1.m_B = 1000; //在Son3中m_B变为私有成员 类外访问不到
}
3.继承中的对象模型
从父类继承过来的成员,哪些属于子类对象?
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 << "sizeof Son = " << sizeof(Son) << endl;
}
4.继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
顺序:父构——子构——子析——父析
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;
}
5.继承同名成员处理方式
当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
①访问子类同名成员:直接访问
②访问父类同名成员:加作用域
同名成员属性处理:
class Base
{
public:
Base()
{
m_A = 100;
}
public:
int m_A;
};
class Son : public Base
{
public:
Son()
{
m_A = 200;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl; //子类:直接访问
cout << "Base下的m_A = " << s.Base::m_A << endl; //父类:加作用域
}
同名成员函数处理:
class Base
{
public:
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a) //函数重载
{
cout << "Base - func(int a)调用" << endl;
}
public:
int m_A;
};
class Son : public Base
{
public:
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
s.func(); //子类:直接调用
s.Base::func(); //父类:加作用域
//s.func(10); //报错
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
s.Base::func(10);
}
6.继承同名静态成员处理方式
继承中同名的静态成员在子类对象上该如何进行访问?
与上文处理方式一致,只不过有两种访问方式,1通过对象,2通过类名
①访问子类同名成员:直接访问
②访问父类同名成员:加作用域
同名成员属性处理:
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()
{
cout << "通过对象访问: " << endl; //通过对象访问
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
cout << "通过类名访问: " << endl; //通过类名访问
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
//第一个::代表通过类名方式访问,第二个::代表访问父类作用域下
}
同名成员函数处理:
class Base
{
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A; //类内声明
};
int Base::m_A = 100; //类外初始化
class Son : public Base
{
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A; //类内声明
};
int Son::m_A = 200; //类外初始化
void test02()
{
cout << "通过对象访问: " << endl; //通过对象访问
Son s;
s.func();
s.Base::func();
cout << "通过类名访问: " << endl; //通过类名访问
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作用域访问
Son::Base::func(100);
}
7.多继承语法
一个类可以继承多个类
语法:class 子类 : 继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分调用哪一个父类的成员
class Base1
{
public:
Base1()
{
m_A = 100;
}
public:
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200; //开始是m_B 不会出问题,但是改为m_A就会出现不明确
}
public:
int m_A;
};
class Son : public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
public:
int m_C;
int m_D;
};
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
cout << "Base1::m_A= " << s.Base1::m_A << endl; //当父类中出现同名成员,需要加作用域区分
cout << "Base2::m_A= " << s.Base2::m_A << endl;
}
8.菱形继承
概念:两个子类继承同一个父类,又有某个类同时继承着两个子类
羊和驼都继承了动物的数据,当羊驼使用数据时,就会产生二义性
羊驼继承了两份动物的数据,但是这份数据只需要一份就行,导致资源浪费以及毫无意义
class Animal
{
public:
int m_Age;
};
class Yang : public Animal {};
class Tuo : public Animal {};
class YangTuo : public Yang, public Tuo {};
void test01()
{
YangTuo st;
st.Yang::m_Age = 18;//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
st.Tuo::m_Age = 28;
cout << "st.Yang::m_Age = " << st.Yang::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
}
菱形继承导致数据有两份,资源浪费
利用虚继承解决菱形继承的问题:
class Animal
{
public:
int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal类称为虚基类
class Yang : virtual public Animal {};
class Tuo : virtual public Animal {};
class YangTuo : public Yang, public Tuo {};
void test01()
{
YangTuo st;
st.Yang::m_Age = 18;
st.Tuo::m_Age = 28;
cout << "st.Yang::m_Age = " << st.Yang::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
相当于开辟一个父类的变量名的类型空间存储,而子类继承的不是拷贝父类的内存空间,而是直接继承一个指针指向那块数据的存储空间
七、多态
1.多态的基本概念
分类:①静态多态:函数重载 和 运算符重载
②动态多态:派生类和虚函数
区别:①静态多态的函数地址早绑定,编译阶段确定函数地址
②动态多态的函数地址晚绑定,运行阶段确定函数地址
动态多态满足条件:①有继承关系 ②子类重写父类的虚函数
动态多态使用条件:父类的指针或引用 指向子类对象
class Animal
{
public:
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
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();
}
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
2.多态的原理剖析
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
void test01()
{
cout << "sizeof Animal = " << sizeof(Animal) << endl;
}
函数前加上virtual关键字变成虚函数后:
(32位指针4个字节,64位指针8个字节)
3.多态案例一:计算器类
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:①代码组织结构清晰 ②可读性强 ③利于前期和后期的扩展以及维护
普通实现:
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;
}
//如果要提供新的运算,需要修改源码
}
public:
int m_Num1; //操作数1
int m_Num2; //操作数2
};
void test01()
{
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
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;
}
在开发中,提倡开闭原则:对扩展进行开放,对修改进行关闭
多态实现:
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 test01()
{
//父类的指针或者引用指向子类时会发生多态
//创建加法计算器
AbstractCalculator *abc = new AddCalculator;
//指针写法,new在堆区上创建了一个对象,返回的是指针
//引用写法:AbstractCalculator & abc = AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc; //堆区数据手动开辟手动释放,用完了记得销毁
//创建减法计算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建乘法计算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
4.纯虚函数和抽象类
在多态中,通常父类中虚函数的实现毫无意义,主要都是调用子类重写的内容
纯虚函数语法:virtualo 返回值类型 函数名 (参数列表)= 0;
抽象类:当类中有了纯虚函数,这个类就称为抽象类
抽象类特点:①无法实例化对象
②子类必须重写抽象类中的纯虚函数,否则子类也属于抽象类,无法实例化对象
虚函数的作用作用就是创建虚函数列表从而达到通过指针联系子类,即多态
相当于在父类中存了个指针,就能指向子类中的函数
或者说父类中的函数也抽象成了类,不同子类中重写的函数就是这个抽象函数类的对象
class Base //抽象类
{
public:
virtual void func() = 0; //纯虚函数
};
class Son :public Base
{
public:
virtual void func() //子类重写父类中的纯虚函数
{
cout << "func调用" << endl;
};
};
void test01()
{
//Base b; //报错,抽象类无法实例化对象(栈区)
//new Base; // 错误,抽象类无法实例化对象(堆区)
Base * base = new Son;
base->func();
delete base;//记得释放
}
5.多态案例二:制作饮品
制作饮品流程:煮水——冲泡——倒入杯子——加入辅料
用多态实现,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
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 << "煮农夫山泉!" << endl;
}
virtual void Brew() //冲泡
{
cout << "冲泡咖啡!" << endl;
}
virtual void PourInCup() //倒入杯中
{
cout << "将咖啡倒入杯中!" << endl;
}
virtual void PutSomething() //加入辅料
{
cout << "加入牛奶!" << endl;
}
};
class Tea : public AbstractDrinking //制作茶水
{
public:
virtual void Boil() //烧水
{
cout << "煮自来水!" << endl;
}
virtual void Brew() //冲泡
{
cout << "冲泡茶叶!" << endl;
}
virtual void PourInCup() //倒入杯中
{
cout << "将茶水倒入杯中!" << endl;
}
virtual void PutSomething() //加入辅料
{
cout << "加入枸杞!" << endl;
}
};
void DoWork(AbstractDrinking* abs)
//AbstractDrinking* abs = new Coffee 和 new tea 父类的指针指向子类对象
//形参 实参
{
abs->MakeDrink();
delete abs; //记得释放
}
void test01()
{
DoWork(new Coffee); //
cout << "--------------" << endl;
DoWork(new Tea);
}
6.虚析构和纯虚析构
多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:把父类中的析构函数改为 虚析构 或者 纯虚析构
语法:虚析构:virtual ~类名(){ }
纯虚析构:virtual ~类名()= 0;
类名::~类名(){ }
共性:①可以解决父类指针释放子类对象时不干净的问题 ②都需要有具体的函数实现
区别:如果有纯虚析构,该类属于抽象类,无法实例化对象
虚析构:
class Animal
{
public:
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
virtual void Speak() = 0; //纯虚函数
~Animal()
{
cout << "Animal 析构函数调用!" << endl;
}
};
class Cat : public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用!" << endl;
m_Name = new string(name); //new返回string指针
}
virtual void Speak() //重写
{
cout << *m_Name << "小猫在说话!" << endl; //解引用
}
~Cat()
{
cout << "Cat析构函数调用!" << endl;
if (this->m_Name != NULL)
{
delete m_Name; //delete是清空指针指向的堆区内存,并不清空指针
m_Name = NULL; //NULL是把指针替换为NULL地址
}
}
public:
string *m_Name; //把名字创建到堆区,用指针维护它
};
void test01()
{
Animal *animal = new Cat("Tom");
animal->Speak();
delete animal; //记得释放
}
正确顺序:父类构造——子类构造——执行Speak函数——(子类析构)——父类析构
问题:少了子类析构,说明堆区数据没有被释放干净,导致内存泄漏
原因:如果子类中有属性开辟到堆区,那么父类指针在释放时只会调用父类自己的析构函数,而无法调用到子类的析构代码,此时堆内存中的子类数据还没有被清除
内存泄漏是指:程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
解决方式:把父类析构加上virtual关键字变成虚析构
纯虚析构:
class Animal
{
public:
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
virtual void Speak() = 0; //纯虚函数
virtual ~Animal() = 0; //纯虚析构 类内声明
};
Animal::~Animal() //类外实现
{
cout << "Animal 纯虚析构函数调用!" << endl;
}
如果子类中没有堆区数据,可以不写为虚析构或者纯虚析构
7.多态案例三:电脑组装
电脑的主要组成部件为 CPU(计算) 显卡(显示) 内存条(存储),将每个零件封装出抽象基类,并提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商,创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三太不同的电脑进行工作