自学内容网 自学内容网

c++的类和对象

c++面向对象的三大特性:封装、继承、多态!!!

c++认为完事万物都皆为对象,对象上有其属性和行为

比如:人可以作为对象,属性有姓名、年龄、身高、体重等等,行为有走、跑、跳、吃饭、唱歌等等。车也可以作为对象、属性有轮胎、方向盘、车灯等等,行为有载人、放音乐、开空调等具有相同性质的对象,我们可以抽象称位类,人属于人类,车属于车类

🌷封装

🎈意义:(c++面向对象的三大特征值之一)

1.将属性和行为作为一个整体,表现生活中的事物:

语法:
class 类名{访问权限:属性/行为};

 示例一:设计一个圆类,求圆类的周长和面积

#include<iostream>
#include<cmath>
using namespace std;
//设计一个圆类,求圆类的周长和面积
const double PI = 3.14;//圆周率
class Circle {
//访问权限
//公共权限
public:
//属性:
//半径
int m_r;
//行为:
//获取圆的周长
double caculateC() {
return 2 * m_r * PI;
}
//计算面积
double caculateS() {
return pow(m_r, 2) * PI;
}
};
int main() {
//通过圆类创建一个具体的圆(对象)
Circle c1;
c1.m_r = 10;
//输出周长
cout << "圆的周长为:" << c1.caculateC() << endl;
//输出半径
cout << "圆的面积为:" << c1.caculateS() << endl;


return 0;
}

示例二:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

#include<iostream>
#include<cstring>
using namespace std;
//设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
class Student {
//公共权限
public:
//属性:姓名,学号
string m_name;
int m_number;
//行为:
//显示学生姓名,学号
void show() {
cout << "学生的姓名为:" << m_name << endl;
cout << "学生的学号为:" << m_number << endl;
}
//给姓名赋值
void setname(string name) {
m_name = name;
}
//给学号赋值
void setnumber(int num) {
m_number = num;
}
};
int main() {
//创建一个具体学生:示例化对象
Student stu1,stu2;
//给stu1赋值名字
stu1.setname("张三");
//给stu1赋值学号
stu1.setnumber(1);
//给stu2赋值名字
stu2.setname("李四");
//给stu2赋值学号
stu2.setnumber(2);
stu1.show();
stu2.show();
return 0;
}

 注:类中的属性和行为,统一称为成员。属性称为成员属性和成员变量,行为称为成员函数和成员方法

2.将属性和行为加以权限控制

三种访问权限:

  • 公共权限(public):成员类内和类外都可以访问
  • 保护权限(protected):成员类内可以访问,类外不可以访问(子类可以访问父类中的保护内容)
  • 私有权限(private):成员类内可以访问,类外不可以访问(子类不可以访问父类的私有内容)
#include<iostream>
using namespace std;
//访问权限
//公共权限 public
//保护权限 protect
class Person {
//公共权限
public:
string m_name;//姓名
//保护权限
protected:
string m_car;//汽车
//私有权限
private:
int m_password;
//行为
void func() {
m_name = "张三";
m_car = "拖拉机";
m_password = 123456;
}
};
int main() {
//实例化具体对象
Person p1;
p1.m_name = "李四";
//p1.m_car = "奔驰";//保护权限内容在类外不能访问会报错
//p1.m_password = 123;//私有权限内容在类外不能访问会报错


return 0;
} 

🎈struct和class区别

在c++中struct和class唯一的区别就在于默认的访问权限不同

struct默认权限为公共

class默认权限为私有

#include<iostream>
using namespace std;
class c1 {
int m_A;//默认权限 私有
};

struct C2{
int m_A;//默认权限 公有
};
int main() {
c1 c1;
//c1.m_A = 100;//报错,权限为私有,类外不能访问
C2 c2;
c2.m_A = 100;//权限为公共,类外可以访问

return 0;
}

成员属性设置为私有

优点:

  • 将所有成员属性设置为私有,可以自己控制读写权限
  • 对于写权限,可以检测数据的有效性
#include<iostream>
#include<cstring>
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 name) {
m_idol = name;
}
//设置年龄
void setage(int age) {
if (age >= 0 && age <= 150) {
m_age = age;
}
else cout << "年龄输入有误,设置年龄失败" << endl;
}
private:
string m_name;//姓名, 可读可写

int m_age=18;//年龄,只读 也可以写(年龄必须在0-150之间)

string  m_idol;//偶像,只写

};
int main() {
Person p;
//姓名设置
p.setname("张三");
//年龄设置
//p.m_age = 20;//不能设置只能获取
cout<<p.getname()<<endl;
cout << p.getage() << endl;
//设置偶像
p.setIdol("姜云升");
//cout << p.idol << endl;//只写不能读

//设置年龄
p.setage(130);
cout << p.getage() << endl;
return 0;
}

练习:

  • 设计立方体类(Cube)
  • 求出立方体的面积和体积
  • 分别用全局函数和成员函数判断两个立方体是否相等

#include<iostream>
#include<cstring>
using namespace std;
//设计立方体类
class Cube {
private:
//属性
int m_L;//长
int m_H;//高
int m_W;//宽
public:
//设置长
void setL(int l) {
m_L = l;
}
//设置宽
void setW(int w) {
m_W = w;
}
//设置高
void setH(int h) {
m_H = h;
}
//获取长
int getL() {
return m_L; 
}
//获取宽
int getW() {
return m_W;
}
//获取高
int getH() {
return m_H;
}
//获取立方体面积
int S() {
return 2 * (m_L * m_H + m_L * m_W + m_H * m_W);
}

//获取立方体体积
int V() {
return m_H * m_W * m_L;
}
//利用成员函数判断两个立方体是否相等
bool isSameByclass(Cube &c) {
if (m_H == c.getH() && m_L == c.getL() && m_W == c.getW()) return 1;
else return 0;
}
};
//利用全局函数判断两个立方体是否相等
bool isSame(Cube& c1, Cube& c2) {
if (c1.getH() == c2.getH() && c1.getL() == c2.getL() && c1.getW() == c2.getW()) return 1;
else return 0;
}
int main() {
//创建立方体对象
Cube c1;
c1.setH(10);
c1.setL(10);
c1.setW(10);
cout << "面积为:" << c1.S() << endl;
cout << "体积为:" << c1.V() << endl;
//创建第二个立方体
Cube c2;
c2.setH(10);
c2.setL(10);
c2.setW(10);
//全局函数判断两个立方体是否相等
bool ret = isSame(c1, c2);
if (ret == 1) cout << "c1,c2两个立方体相等" << endl;
else cout << "c1,c2两个立方体不相等" << endl;

//成员函数判断两个立方体是否相等
bool ret2 = c1.isSameByclass(c2);
if (ret2 == 1) cout << "c1,c2两个立方体相等" << endl;
else cout << "c1,c2两个立方体不相等" << endl;
return 0;
}

练习二:点和圆的关系

设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系

分析:
 

#include<iostream>
#include<cmath>
using namespace std;
//点和圆的关系
class Point {
private:
int m_x;//x坐标
int m_y;//y坐标
public:
//设置x坐标
void setx(int x) {
m_x = x;
}
//设置y坐标
void sety(int y) {
m_y = y;
}
//获取x坐标
int getx() {
return m_x;
}
//获取y坐标
int gety() {
return m_y;
}

};
class Circle {
private:
int m_r;//半径
Point m_center;//圆心
public:
//设置r
void setr(int x) {
m_r = x;
}
//获取半径
int getr() {
return m_r;
}
//设置圆心
void setcenter(Point center) {
m_center = center;
}

//获取圆心
Point getcenter() {
return m_center;
}

};

//判断点和圆关系的函数
void  isincircle(Circle& c, Point& p) {
//计算两点之间距离的平方
int dis=pow(c.getcenter().getx()-p.getx(),2)+pow(c.getcenter().gety() - p.gety(),2);
//计算半径的平方
int rdis = pow(c.getr(), 2);
//判断两个大小关系
if (dis == rdis) cout << "点在圆上" << endl;
else if (dis > rdis) cout << "点在圆外" << endl;
else {
cout << "点在圆内" << endl;
}
}
int main() {
Circle c;
Point center;
Point p;
c.setr(10);
p.setx(10);
p.sety(11);
center.setx(10);
center.sety(0);
c.setcenter(center);

isincircle(c, p);
return 0;
}

或者:

circle.h

#pragma once
#include"point.h"
#include<iostream>
#include<cmath>
using namespace std;
class Circle {
private:
int m_r;//半径
Point m_center;//圆心
public:
//设置r
void setr(int x);
//获取半径
int getr();
//设置圆心
void setcenter(Point center);

//获取圆心
Point getcenter();

};

circle.cpp 

#include"circle.h"
//设置r
void Circle::setr(int x) {
m_r = x;
}
//获取半径
int Circle::getr() {
return m_r;
}
//设置圆心
void Circle::setcenter(Point center) {
m_center = center;
}
//获取圆心
Point Circle::getcenter() {
return m_center;
}



point.h 

#pragma once
#include<iostream>
#include<cmath>
using namespace std;
class Point {
private:
int m_x;//x坐标
int m_y;//y坐标
public:
//设置x坐标
void setx(int x);
//设置y坐标
void sety(int y);
//获取x坐标
int getx();
//获取y坐标
int gety();

};

point.cpp 

#include"point.h"
void Point::setx(int x) {
m_x = x;
}
//设置y坐标
void Point::sety(int y) {
m_y = y;
}
//获取x坐标
int Point::getx() {
return m_x;
}
//获取y坐标
int Point::gety() {
return m_y;
}


mian.cpp

#include<iostream>
#include<cmath>
#include"circle.h"
#include"point.h"
using namespace std;

//判断点和圆关系的函数
void  isincircle(Circle& c, Point& p) {
//计算两点之间距离的平方
int dis=pow(c.getcenter().getx()-p.getx(),2)+pow(c.getcenter().gety() - p.gety(),2);
//计算半径的平方
int rdis = pow(c.getr(), 2);
//判断两个大小关系
if (dis == rdis) cout << "点在圆上" << endl;
else if (dis > rdis) cout << "点在圆外" << endl;
else {
cout << "点在圆内" << endl;
}
}
int main() {
Circle c;
Point center;
Point p;
c.setr(10);
p.setx(10);
p.sety(9);
center.setx(10);
center.sety(0);
c.setcenter(center);

isincircle(c, p);
return 0;
}

🌷对象的初始化和清理

  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
  • C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置

🎈构造函数和析构函数

构造函数

主要作用:创建对象时为成员的属性赋值,构造函数由编译器自动调用,无需手动调用
语法:
类名(){}
  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象的时候回自动调用构造,无需手动调用,而且指只会调用一次

析构函数

主要作用:对象销毁前西永自动调用,执行一些清理工作
语法:
~类名(){}
  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前回自动调用析构,无需手动调用,而且只会调用一次
#include<iostream>
using namespace std;
class Person {
public:
//创建一个构造函数
Person() {
cout << "Person的构造函数调用" << endl;
}
//创建一个析构函数
~Person() {
cout << "Person的析构函数调用" << endl;
}
};
void test01() {
Person p;
}
int main() {
test01();
Person p;
system("pause");


return 0;
}

🎈构造函数的分类及调用:

#include<iostream>
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;
}
~Person() {
cout << "Person的析构函数" << endl;
}
int age;
};
//调用
void test01() {
//1.括号法
//Person p1;//默认构造函数调用
//Person p2(10);//有参函数构造
//Person p3(p2);//调用拷贝函数

//2.显示法
//Person p1;
//Person p2 = Person(10);//有参构造
//Person p3 = Person(p2);//调用拷贝构造
//Person(10);//匿名对象  当前行执行结束后,系统立即回收匿名对象

//3.隐式转换法
Person p4=10;//相当于Person p4=Person(10)
Person p5 = p4;//拷贝构造函数调用
}
int main() {
test01();
return 0;
}

🎈拷贝构造函数调用时机

运用:

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
#include<iostream>
using namespace std;
class Person {
public:
int m_age;
Person() {
cout << "Person默认构造函数调用" << endl;
}
~Person() {
cout << "Person析构函数调用" << endl;
}
Person(int age) {
m_age = age;
cout << "Person有参构造函数调用" << endl;
}
Person(const Person& p) {
cout << "Person拷贝构造调用" << endl;
m_age = p.m_age;
}
};
//1.使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person p1(20);
Person p2(p1);
}
//2.值传递的方式给函数参数传值
void doWork(Person p) {

}
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;
}
int main() {
//test01();
//test02();
test03();

return 0;
}

🎈构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则

  • 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

🎈深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

#include<iostream>
using namespace std;
class Person {
public:
int m_age;
int* m_height;
Person() {
cout << "Person的默认构造函数调用" << endl;
}

Person(int age,int height) {
m_age = age;
m_height = new int(height);
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;
}
};
void test01() {
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_age<<"身高为:"<<*p1.m_height << endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.m_age << "身高为:" <<*p2.m_height << endl;

}
int main() {
test01();
system("pause");


return 0;
}

🎈初始化列表

作用:c++提供了初始化列表语法,用来初始化属性

语法:

构造函数():属性1(值1),属性2(值2),……{}

示例:

#include<iostream>
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, m_b, m_c;

};
void test01() {
//Person p(10, 20, 30);
Person p(30,20,10);
cout << p.m_a << " " << p.m_b << " " << p.m_c << endl;
}
int main() {
test01();


return 0;
}

🎈类对象作为类成员

c++类中的成员可以是另一个类的对象,我们称该成员为对象成员

例如:

class A{};
class B{
    A a;
};

构造函数顺序:当其他类作为本类的成员,先构造其他类成员,再构造本身
析构函数顺序:和构造函数相反

如下代码所示 

#include<iostream>
using namespace std;
//手机类
class Phone {
public:
//手机品牌命名
Phone(string name) {
m_pname = name;
cout << "Phone的构造函数调用" << endl;
}
~Phone() {
cout << "Phone的析构函数调用" << endl;
}
string m_pname;//品牌

};
//人类
class Person {
public:
// Phone m_phone=pname 隐式转换法
Person(string name, string pname) :name(name), m_phone(pname) {
cout << "Person的构造函数调用" << endl;
}
~Person() {
cout << "Person的析构函数调用" << endl;
}

//姓名
string name;
//手机
Phone m_phone;
};
//构造函数顺序
//当其他类作为本类的成员,先构造其他类成员,再构造本身
//析构函数的顺序和构造函数相反

void test01(){
Person p("张三","iphone16promax");
cout << p.name << "拿着" << p.m_phone.m_pname << endl;
}
int main() {
test01();


return 0;
}

🎈静态成员(static)

是在成员变量和成员函数前加上关键字static

分类:

1.静态成员变量
  • 所有对象共享同一份数据
  • 再编译阶段分配内存
  • 类内声明,类外初始化
  • 两种访问方式:通过类名或者对象进行访问

两种访问方式

#include<iostream>
using namespace std;
class Person {
public:
static int m_a;
};
int Person::m_a=100;
void test02() {
Person p;
cout <<Person::m_a << endl;//通过类名访问
cout << p.m_a << endl;//通过对象进行访问
}
int main() {
test02();



return 0;
}

所有对象共享同一份数据

#include<iostream>
using namespace std;
class Person {
public:
static int m_a;
};
 int Person::m_a=100;
void test01() {
Person p;
cout << p.m_a << endl;
Person p2;
p2.m_a = 200;
cout << p.m_a << endl;
}
int main() {
test01();



return 0;
}

私有成员的静态变量

#include<iostream>
using namespace std;
class Person {
public:
static int m_a;
private:
static int m_b;
};
int Person::m_a = 100;
int Person::m_b = 200;
void test02() {
Person p;
//cout << p.m_b << endl;//类外访问不到私有成员的静态变量
}
int main() {
test02();
return 0;
}

2.静态成员函数
  • 所有对象共用一个函数
  • 静态成员函数只能访问静态成员变量

两种访问静态成员函数的方式:

#include<iostream>
using namespace std;
class Person {
public:
//静态成员函数
static void func() {
cout << "static func函数的调用" << endl;
}
};
void test01() {
Person p;
p.func();//通过对象调用
Person::func();//通过类名访问
}
int main() {
test01();



return 0;
}

静态成员函数作用域

#include<iostream>
using namespace std;
class Person {
public:
//静态成员函数
static void func() {
m_a = 100;
//m_b = 200;//【报错】静态成员函数不可以访问非静态成员变量
cout << "static func函数的调用" << endl;
}
static int m_a;
int m_b;
private:
static void func() {
cout << "static func2函数的调用" << endl;
}
};
int Person::m_a;
void test01() {
Person p;
p.func();//通过对象调用
Person::func();//通过类名访问
//Person::func2();//类外访问不到私有静态成员函数
}
int main() {
test01();
return 0;
}

🌷c++对象模型与this指针

🎈成员变量和成员函数分开存储

在c++中,类内的成员变量和成员函数分开存储。只有非静态成员变量才属于类的对象上。

空对象占用内存为1【c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,每个空对象都应该有一个独一无二的内存地址】

#include<iostream>
using namespace std;
class Person {
public:

};
void test01() {
Person p;
cout << "size of p=" << sizeof(p) << endl;
}
int main() {
test01();

return 0;
}

一个int类型对象占用4个字节(只要非空,该占几个占几个)

#include<iostream>
using namespace std;
class Person {
public:
int m_a;
};

void test02() {
Person p;
cout << "size of p=" << sizeof(p) << endl;
}
int main() {
test02();

return 0;
}

成员变量和成员函数时分开存储的

#include<iostream>
using namespace std;
class Person {
public:
int m_a; //非静态成员 属于类的对象上
static int m_b;//静态成员 不属于类的对象上
void func() {  //非静态成员函数
}
static void func2() { //静态成员函数 不属于类的对象上

}
};
int Person:: m_b;
void test01() {
Person p;
cout << "size of p=" << sizeof(p) << endl;
}
void test02() {
Person p;
cout << "size of p=" << sizeof(p) << endl;
}
int main() {
test02();

return 0;
}

🎈this指针

每一个静态成员函数只会诞生一份函数示例,也就是说,多个同类型的对象会公用一块代码

c++通过提供特殊的对象指针,this指针,解决上述问题。

  • this指针指向被调用的成员函数所属的对象
  • this指针是隐含每一个非静态成员函数内的一种指针
  • this指针不需要定义,直接用即可

作用1:解决名称冲突【规范名称】

我们将成员的名称统一命名为m_开头就是为了区变量和成员,不然容易混淆

例:下面这段代码我们预期输出18,但是输出的结果并不是

#include<iostream>
using namespace std;
class Person {
public:
int age;
Person(int age) {
age = age;
}
};
void test01() {
Person p1(18);
cout << "p1的年龄为" << p1.age << endl;
}
int main() {
test01();


return 0;
}

我们将成员名称和变量名称进行区分:

#include<iostream>
using namespace std;
class Person {
public:
int m_age;
Person(int age) {
m_age = age;
}
};
void test01() {
Person p1(18);
cout << "p1的年龄为" << p1.m_age << endl;
}
int main() {
test01();


return 0;
}

这下就解决了!

或者

#include<iostream>
using namespace std;
class Person {
public:
int age;
Person(int age) {
this->age = age;//this指针指向被调用的成员函数所属的对象
}
};
void test01() {
Person p1(18);
cout << "p1的年龄为" << p1.age << endl;
}
int main() {
test01();


return 0;
}

这样也可以(this指向p1)

this指针指向被调用的成员函数所属的对象

作用2:返回对象本身用*this

#include<iostream>
using namespace std;
class Person {
public:
int age;
Person(int age) {
this->age = age;
}
Person &PersonADDage(Person &p) {
this->age += p.age;
return *this;//this指向p2的对象,*this指向p2整体
}
};
void test01() {
Person p1(18);
cout << "p1的年龄为" << p1.age << endl;
}
void test02() {
Person p1(10);
Person p2(10);
p2.PersonADDage(p1).PersonADDage(p1).PersonADDage(p1);//链式编程思想
cout << "p2的年龄为:" << p2.age << endl;
}
int main() {
test02();


return 0;
}

🎈空指针访问成员函数

#include<iostream>
using namespace std;
class Person {
public:
int m_age;
void showClassName() {
cout << "this is Person class" << endl;
}
void showage() {
if (this == NULL)  return;
cout << "age=" << m_age << endl;
}
};
void test01() {
Person * p=NULL;
p->showClassName();
//p->showage(); //【报错】传入的指针是NULL
}
int main() {
test01();


return 0;
}

🎈const修饰成员函数

常函数:

  • 成员函数后加const后我们成为这个函数为常函数
  • 长函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

在成员函数加上const,修改this指针,让指针指向的值也不可以修改

#include<iostream>
using namespace std;
class Person {
public:
int m_a;
//this指针的本质是指针常量,指针的指向是不可以修改的
//const Person *const this
void showPerson()const {
this->m_a = 100;
//this = NULL;//this指针不可以修改指针的指向
}
};
void test01() {
Person p;
p.showPerson();
}
int main() {

return 0;
}

mutable关键字:特殊变量,即使在常函数和常对象中也可以修改这个值

#include<iostream>
using namespace std;
class Person {
public:
int m_a;
mutable int m_b;//特殊变量,即使在常函数中,也可以修改这个值
//this指针的本质是指针常量,指针的指向是不可以修改的
//const Person *const this
void showPerson()const {
this->m_b = 100;
//this->m_a = 100;//【报错】
//this = NULL;//this指针不可以修改指针的指向
}
};
void test01() {
Person p;
p.showPerson();
}
int main() {

return 0;
}

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
#include<iostream>
using namespace std;
class Person {
public:
int m_a;
mutable int m_b;//特殊变量,即使在常函数中,也可以修改这个值
//this指针的本质是指针常量,指针的指向是不可以修改的
//const Person *const this
void showPerson()const {
this->m_b = 100;
//this->m_a = 100;//【报错】
//this = NULL;//this指针不可以修改指针的指向
}
void func() {

}
};
void test01() {
Person p;
p.showPerson();
}
void test02() {
const Person p;//在对象前加const 变成常对象
//p.m_a = 100;//【报错】常对象不可以修改
p.m_b = 100;//m_b特殊值可以修改

//常函数和常对象只能调用常函数
p.showPerson();
//p.func();//【报错】常对象不可以调用普通成员函数
}
int main() {

return 0;
}

🌷友元

在程序里,有一些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术。

友元的目的就是让一个函数或类访问另一个类中私有成员

🎈友元的三种实现方式:

全局函数做友元

friend void 函数名();//在类中加上关键字friend然后把函数赋值过来

举例:

#include<iostream>
using namespace std;
//建筑物类
class Building {
//goodfriend全局函数是Building的好朋友,可以访问Building中的私有成员
friend void goodfriend(Building* building);
public:
Building() {
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
string m_sittingroom;//客厅
private:
string m_bedroom;//卧室
};
//全局函数
void goodfriend(Building *building) {
cout << "好朋友全局函数正在访问:" << building->m_sittingroom << endl;
cout << "好朋友全局函数正在访问:" << building->m_bedroom << endl;
}
void test01() {
Building b;
goodfriend(&b);
}
int main() {
test01();



return 0;
}

类做友元

#include<iostream>
#include<cstring>
using namespace std;
class Building;
class GoodFriend {
public:
GoodFriend();
void visit();//参观函数 访问Building中的属性
Building* building;
};
class Building {
//GoodFriend是Building类的好朋友,可以访问该类的私有成员
friend class GoodFriend;
public:
Building();
string m_sittingroom;//客厅
private:
string m_bedroom;//卧室

};
//类外写成员函数
Building::Building() {
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
GoodFriend::GoodFriend() {
//创建一个建筑物对象
building = new Building;
}
void GoodFriend::visit() {
cout << "好朋友类正在访问:" << building->m_sittingroom << endl;
cout << "好朋友类正在访问:" << building->m_bedroom << endl;
}
void test01() {
GoodFriend gg;
gg.visit();
}
int main() {
test01();
return 0;
}

 

成员函数做友元

#include<iostream>
#include<string>
using namespace std;
class Building;
class GoodFriend {
public:
GoodFriend();
void visit();//让visit可以访问Building中的私有成员
void visit2();//让visit2不可以访问Building中的私有成员
Building* building;
};
class Building {
// 告诉编译器 GoodFriend类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodFriend::visit();
public:
Building();
public:
string m_sittingroom;//客厅
private:
string m_bedroom;//卧室
};
//类外实现成员函数
Building::Building() {
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
GoodFriend::GoodFriend() {
building = new Building;
}
void GoodFriend::visit() {
cout << "visit函数正在访问:" << building->m_sittingroom << endl;
cout << "visit2函数正在访问:" << building->m_bedroom << endl;
}
void GoodFriend::visit2() {
cout << "visit函数正在访问:" << building->m_sittingroom << endl;
//cout << "visit2函数正在访问:" << building->m_bedroom << endl;//【报错】不能访问私有成员
}
void test01() {
GoodFriend gg;
gg.visit();
gg.visit2();
}
int main() {
test01();


return 0;
}

🌷运算符重载

概念:对也已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

🎈加号运算符重载

成员函数重载+号

#include<iostream>
#include<string>
using namespace std;
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;
p3 = p1 + p2;
//成员函数运算重载本质
//Person p3 = p1.operator+(p2);
cout << p3.m_a<<" " << p3.m_b;
}
int main() {
test01();


return 0;
}

全局函数重载+号

#include<iostream>
#include<string>
using namespace std;
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;
p3 = p1 + p2;
//全局函数预算重载本质
//p3 = operator+(p1, p2);
cout << p3.m_a << " " << p3.m_b;
}
int main() {
test01();


return 0;
}

运算符重组,也可以发生函数重载

#include<iostream>
#include<string>
using namespace std;
class Person {
public: 

int m_a;
int m_b;
};

//函数重载的版本
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;
p3 = p1 + 30;
cout << p3.m_a << " " << p3.m_b;
}
int main() {
test01();


return 0;
}

🎈左移运算符重载

作用:可以输出自定义数据类型

#include<iostream>
using namespace std;
class Person {
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a, int b) {
m_a = a;
m_b = b;
}
private:

//利用成员函数重载左移运算符 p.operator<<(cout) 简化版本p<<cout
//一般不会利用成员函数重载
/*void operator<<(cout) {

 }*/
int m_a;
int m_b;

};
//只能用全局函数重载左移运算符
//用ostream可以使代码返回cout让代码实现链式输出
ostream & operator<<(ostream &cout, Person &p) {//本质operator<<(cout,p) 简化cout<<p
cout << "m_a" << p.m_a <<endl<< "m_b" << p.m_b << endl;
return cout;
}
void test01() { 
Person p(10, 10);
cout << p <<endl;
}
int main() {
test01();


return 0;
}

🎈递增运算符重载

#include<iostream>
using namespace std;
//自定义的整型
class Myinteger {
friend ostream& operator<<(ostream& cout, Myinteger myint);

public:
Myinteger() {
m_num = 0;
}
//重载前置++运算符
Myinteger &operator++() {
//先进行++运算
m_num++;
//再将自身做返回
return *this;
}
// 重载后置++运算符
//int 代表占位参数,可以用于区分前置和后置递增
Myinteger operator++(int) {
//先 记录当时结果
Myinteger temp = *this;
//后 递增
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;
cout << myint << endl;
}
void test02() {
Myinteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main() {
test01();
test02();
return 0;
}

🎈赋值运算符重载

c++编译器至少给一个类添加4个函数

1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符operartor=,对属性进行值拷贝
#include<iostream>
using namespace std;
class Person {
public: 
Person(int age) {
m_age = new int(age);
}
~Person() {
if (m_age != NULL) {
delete m_age;
m_age = NULL;
}
}
//重载赋值运算符
Person &operator=(Person &p) {
 //编译器提供浅拷贝
 // m_age=p.m_age
  
//应该先判断是否有数据在堆区,如有先释放干净,然后再深拷贝
if (m_age != NULL) { 
delete m_age;
m_age = NULL;
}
//深拷贝
m_age = new int(*p.m_age); 
return *this;
}
int *m_age;

};
void test01() {
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1;//赋值操作
cout << *p1.m_age << endl; 
cout << *p2.m_age << endl;
cout << *p3.m_age << endl;

}
int main() {
test01();


return 0;
}

🎈关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

#include<iostream>
using namespace std;
class Person {
public:
Person(string name, int age) {
m_name = name;
m_age = age;
}
//重载==号
bool operator==(Person& p) {
if (this->m_name == p.m_name&& this->m_age==p.m_age) {
return true;
}
return false;
}
//重载!=号
bool operator!=(Person& p) {
if (this->m_name == p.m_name && this->m_age == p.m_age) {
return false;
}
 return true;
}
private:
string m_name;
int m_age;
};
void test01() {
Person p1("Tom",18);
Person p2("Tom", 18);
if (p1 == p2) {
cout << "p1=p2" << endl;
}
else {
cout << "两个不相等" << endl;
}
if (p1 != p2) {
cout << "p1和p2不相等" << endl;
}
else {
cout << "两个相等" << endl;
}
}
int main() {
test01();



return 0;
}

🎈函数调用运算符重载

  • 函数调用运算符()也可以重载

  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数

  • 仿函数没有固定写法,非常灵活

#include<iostream>
using namespace std;
class Myprint {
public:
void operator()(string test) {
cout << test << endl;
}
};
void  Myprint2(string test) {
cout << test << endl;
}
void test01() {
Myprint myprint;
myprint("hello world!");//由于使用起来非常像函数调用,因此称为仿函数

Myprint2("hello world!");
}

//仿函数非常灵活没有固定的写法
class Myadd {
public:
int operator()(int num1, int num2) {
return num1 + num2;
}
};
void test02() {
Myadd myadd;
int ret = myadd(100, 100);
cout << ret << endl;
//匿名函数对象
cout << Myadd()(100, 100);
}
int main() {
//test01();
test02();



return 0;
}

🌷继承

继承是面向对象的三大特性之一

有些类与类之间存在特殊的关系。例如下图:

定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候我们就可以考虑继承的技术,减少重复代码

🎈基本语法:

class 子类 :继承方式

 🎈组成:

  1. 子类:派生类(包含父类继承过来的以及自己的)

  2. 父类:基类

 例:

#include<iostream>
using namespace std;
//继承实现页面 
//公共页面类
class BasePage{
public:
void header() {
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer() {
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left() {
cout << "Java、C++、Python ……(公共分类列表)" << endl;
}
};

//Java页面
class Java :public BasePage {
public:
void content() {
cout << "Java学科视频" << endl;
}
};
//c++页面
class Cpp :public BasePage {
public:
void content() {
cout << "Python学科视频" << endl;
}
 };
//python页面 
class Python :public BasePage {
public:
void content() {
cout << "Python学科视频" << endl;
}
};
void test01() { 
cout << "Java页内容如下:" << endl;
Java ja;
ja.header();
ja.content();
ja.footer();
ja.left();
cout << "----------------------------" << endl;
cout << "Pyton页面内容如下:" << endl;
Python py;
py.header();
py.content();
py.footer();
py.left();
cout << "Python页面内容" << endl;
cout << "----------------------------" << endl;
}
int main() {
test01();
return 0;
}

🎈继承方式

  1. 公共继承

  2. 保护继承

  3. 私有继承

#include<iostream>
using namespace std;
//继承方式
//公共继承
class Base1 {
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son1 :public Base1 {
public:
void func() {
m_a = 10;//父类中的公共成员到子类中依旧是公共权限
m_b = 10;//父类中的保护权限成员到子类中国依旧是保护权限
//m_c = 30;//父类中的私有权限成员,子类访问不到
}
//保护权限
};
void test01() {
Son1 s1;
s1.m_a = 100;
//s1.m_b = 100;//到son1中m_b是保护权限,类外访问不到
}
//保护继承
class Base2{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son2 :protected Base2 {
public:
void func() {
m_a = 100;//父类中公共成员,到子类变为保护权限
m_b = 100;//父类中保护成员,到子类还是保护权限
//m_c = 100;//父类中的私有成员子类访问不到
}
};
void test02() {
Son2 s1;
//s1.m_a = 1000;//保护权限类外不能访问
}
//私有继承
class Base3 {
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son3 :private Base3 {
public:
void func() {
m_a = 100;//父类中公共权限成员到子类中变为私有成员
m_b = 100;//父类中保护权限成员到子类中变为私有成员
//m_c = 100;//父类中私有成员子类访问不到

}
};
void test03() {
Son3 s1;
//s1.m_a = 1000;//父类公共成员 到子类变为私有成员
//s1.m_b = 1000;//父类保护成员 到子类变为私有成员
}
class GrandSon3 :public Son3 {
public:
void func() {
//m_a = 1000;
 //m_b = 1000;

}
};
int main(){




return 0;
}

 🎈继承中的对象模型

  • 父类中所有非静态成员都会被子类继承下去

  • 父类中私有成员的属性是被编译器给隐藏了,因此是访问不到的,但是确实是被继承下去了

#include<iostream>
using namespace std;
//继承中的对象模型
class Base {
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son :public Base {
public:
int m_d;
};
void test01() {
cout << "size of Son" << sizeof(Son)<< endl;
}
int main() {
test01();
return 0;
}

 

🎈继承中构造和析构顺序 

子类继承父类后,当创建子类对象,也会调用父类构造函数

问:父类和子类的构造和析构顺序是谁先谁后呢?

#include<iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base的构造函数" << endl;
}
~Base() {
cout << "Base的析构函数" << endl;
}
};
class Son :public Base {
public:
Son() {
cout << "Son的构造函数" << endl;
}
~Son() {
cout << "Son的析构函数" << endl;
}
};
void test01() {
//Base b;
Son s;
}
int main() {
test01();




return 0;
}

顺序如下:

  1. 先构造父类,在构造子类
  2. 析构顺序与构造相反

🎈继承同名成员处理方式

问:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?

  • 访问子类同名成员 直接访问即可

  • 访问父类 同名成员 需要加作用域

同名成员属性处理

例:

#include<iostream>
using namespace std;
//继承中同名成员处理
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 << "m_a=" << s.m_a << endl; 
}
int main() {
test01();
return 0;
}

同名成员函数处理

#include<iostream>
using namespace std;
//继承中同名成员处理
class Base {
public:
Base() {
m_a = 100;
}
void func() {
cout << "Base-func()调用" << endl;
}
void func(int a) {
cout << "Base-func(int a)调用" << endl;
}
int m_a;  
};
class  Son :public Base {
public:
Son() {
m_a = 200; 
}
void func() {
cout << "Son-func()调用" << endl;
}
int m_a;
};
//同名成员函数处理
void test02() {
Son s;
s.func();//直接调用 子类中同名成员
s.Base::func();//加作用域 父类中同名成员
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名函数
//如果想访问父类中的被隐藏的同名成员函数,需要加作用域
s.Base::func(100);
}
int main() {
test02();
return 0;
}

总结:

  1. 子类对象可以直接访问子类中的同名成员函数

  2. 子类对象加作用域可以访问到父类同名成员

  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

🎈继承同名静态成员处理方式

静态成员和非静态成员出现同名:

  • 访问子类同名成员 直接访问即可

  • 访问父类同名成员 需要加作用域

#include<iostream>
using namespace std;
//继承中的同名静态成员处理方式
class Base {
public:
static int m_a;
static void func() {
cout << "Base-static func()" << endl;
 }
};
int Base::m_a = 100;
class Son :public Base {
public:
static int m_a;
static void func() {
cout << "Son-static func()" << endl;
}
};
int Son::m_a=200;
//同名静态成员属性
void test01() {
//1.通过对象访问数据
Son s;
cout << "通过对象访问:" << endl;
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;
}
//同名静态成员函数
void test02() {
//通过对象调用
cout << "通过对象方式访问" << endl;
Son s;
s.func();
s.Base::func();
//通过类名调用
cout << "通过类名访问" << endl;
Son::func();
Son::Base::func();
}
int main() {
test01();
test02();
return 0;
}

🎈多继承语法

c++允许一个类继承多个类

语法:

class 子类:继承方式 父类1,继承方式 父类2……
#include<iostream>
using namespace std;
class Base1 {
public:
Base1() {
m_a = 100;
}
int m_a;
};
class Base2 {
public:
Base2() {
m_a = 200;
}
int m_a;
};
class Son :public Base1, public Base2 {
public:
Son() {
m_c = 300;
m_d = 400;
}
int m_c, m_d;
};
void test01() {
Son s;
cout <<"sizeof(s)" << sizeof(s) << endl;
//当父类变量同名时需要加作用域区分
cout<<"Base1 m_a=" << s.Base1::m_a << endl;
cout << "Base2 m_a=" << s.Base2::m_a << endl;
}
int main() {
test01();



return 0;
}

🎈菱形继承

概念:

  • 两个派生类继承同一个基类
  • 又有某个类同时继承着两个派生类
  • 这种继承被称为菱形继承,或者钻石继承

典型的菱形继承案例

#include<iostream>
using namespace std;
//动物类
class Animal {
public:
int m_age;
};

//利用虚继承可以解决菱形继承的问题
//在继承之前加上关键字virtual变为虚继承
// Animal称为虚基类
//羊类
class Sheep :virtual public Animal {

};
//驼类
class Tuo :virtual public Animal {

};
//羊驼类
class SheepTuo :public Sheep, public Tuo {

};
void test01() {
SheepTuo st;
st.Sheep::m_age = 18;
st.Tuo::m_age = 28;
//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_age =" << st.Sheep::m_age << endl;
cout << "st.Tuo::m_age =" << st.Tuo::m_age << endl;
cout << "st.m_age= " << st.m_age << endl;

//这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份,资源浪费
}
int main() {
test01();

return 0;
}

🌷多态

🎈多态的基本概念

分类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名

  • 动态多态:派生类和虚函数实现运行多态

什么是函数重载?

  1. 函数名相同:重载的函数必须具有相同的函数名。

  2. 参数列表不同

    • 参数的数量不同。
    • 参数的类型不同。
    • 参数的顺序不同(虽然这种重载在实际应用中不常见,因为顺序的不同通常意味着功能上的显著差异,更适合使用不同的函数名)。
  3. 返回类型:函数的返回类型不能作为重载的依据。即使两个函数的返回类型不同,但只要它们的参数列表相同,就不能构成重载。

  4. 作用域相同:重载的函数必须在同一个作用域内。

  5. 调用:编译器会根据函数调用时提供的参数来选择合适的重载版本。

例:

#include <iostream>  
using namespace std;  
  
// 重载函数,计算两个整数的和  
int add(int a, int b) {  
    return a + b;  
}  
  
// 重载函数,计算三个整数的和  
int add(int a, int b, int c) {  
    return a + b + c;  
}  
  
// 重载函数,计算两个双精度浮点数的和  
double add(double x, double y) {  
    return x + y;  
}  
  
int main() {  
    cout << "Sum of 2 and 3 is: " << add(2, 3) << endl;  
    cout << "Sum of 1, 2, and 3 is: " << add(1, 2, 3) << endl;  
    cout << "Sum of 2.5 and 3.5 is: " << add(2.5, 3.5) << endl;  
    return 0;  
}

区别: 

  • 静态多态的函数地址旱绑定--编译阶段确定函数地址

  • 动态多态的函数地址晚绑定--运行阶段确定函数地址

例:

#include<bits/stdc++.h>
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& a) {  //Animal的引用cat
a.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();


return 0;
}

实现动态多态:

  1. 有继承关系

  2. 子类重写父类虚函数(返回值类型,函数名,参数列表完全相同)

  3. 父类指针或者引用执行子类

🎈多态案例——计算器类

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰

  • 可读性强

  • 利于前期和后期的扩展及维护

#include<bits/stdc++.h>
using namespace std;
//分别利用普通写法和多态写法实现计算器功能
//普通写法
class Caculator {
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;
else if (oper == "/") return m_num1 / m_num2;
}

int m_num1;//操作数1
int m_num2;//操作数2
};

//利用多态实现计算器
//实现计算器抽象类
class AbstractCaculator {
public:
virtual int getResult() {
return 0;
}
int m_num1;
int m_num2;
};

//实现加法计算器
class AddCaculator :public AbstractCaculator{
int getResult() {
return m_num1 + m_num2;
}
};
//实现减法计算器
class SubCaculator :public AbstractCaculator {
public:
int getResult() {
return m_num1 - m_num2;

}
};
//实现乘法计算器
class MulCaculator :public AbstractCaculator {
public:
int getResult() {
return m_num1 * m_num2;
}
};
void test01() {
//创建一个计算器对象
Caculator 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;
cout << c.m_num1 << "/" << c.m_num2 << "=" << c.getResult("/") << endl;
}
void test02() {
//多态使用条件
//父类指针或者引用指向子类对象
//加法运算
AbstractCaculator* abc = new AddCaculator;
abc->m_num1 = 100;
abc->m_num2 = 100;
cout << abc->m_num1 << "+" << abc->m_num2 << "=" << abc->getResult()<< endl;
delete abc;

//减法运算
abc = new SubCaculator;
abc->m_num1 = 100;
abc->m_num2 = 100;
cout << abc->m_num1 << "-" << abc->m_num2 << "=" << abc->getResult() << endl;
delete abc;

//乘法运算
abc = new MulCaculator;
abc->m_num1 = 100;
abc->m_num2 = 100;
cout << abc->m_num1 << "*" << abc->m_num2 << "=" << abc->getResult() << endl;
delete abc;
}
int main() {
//test01();
test02();
return 0;
}

🎈纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

语法:

virtual 返回值类型 函数吗(参数列表)=0;

当类中有了纯虚函数,被称为抽象类

抽象类特点:

  • 无法实例化对象

  • 抽象类的子类,必须要重写父类中的纯虚函数,否则也属于抽象类

#include<bits/stdc++.h>
using namespace std;
//纯虚函数和抽象类
class Base {
public:
//纯虚函数
//只要有一个纯虚函数,称为抽象类

virtual void func() = 0;
};
class Son :public Base {
public:
virtual void func() {
cout << "func函数调用" << endl; 
}
};
void test01() {
//Base b;//抽象类无法实例化对象
//new Base;//抽象类无法实例化对象
Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象
Base* base = new Son;
base->func();
}
int main() {
test01();
return 0;
}

🎈多态案例二——制作饮品

案例描述:

制作饮品的大致流程:煮水,冲泡,倒入杯中,加入辅料

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

#include<bits/stdc++.h>
using namespace std;
//多态案例2——制作饮品
class Abstractdrinking {
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink() {
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee :public Abstractdrinking {
public:
//煮水
virtual void Boil() {
cout << "煮农夫山泉水" << 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) {
abs->makeDrink();
//释放堆区数据
delete abs;
}
void test01() {
//制作咖啡
doWork(new Coffee);
cout << endl;
//制作茶叶
doWork(new Tea);
}
int main() {
test01();
return 0;
}

🎈虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构的共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名()=0
类名::~类名(){}
#include<bits/stdc++.h>
using namespace std;
class Animal {
public:
//纯虚函数
Animal() {
cout << "Animal的构造函数调用" << endl;
}
//利用虚析构可以解决父类指针释放子类对象时不干净的问题
/*virtual ~Animal() {
cout << "Animal的析构函数调用" << endl;
}*/

//纯虚析构 需要声明也需要实现
//有了纯虚析构之后,这个类属于抽象类无法实例化对象
virtual ~Animal() = 0;
virtual void Speak() = 0;
};
Animal::~Animal() {
cout << "Animal的纯虚析构函数调用" << endl;
}
class Cat :public Animal {
public:
Cat(string name) {
m_name=new string(name);
cout << "Cat的构造函数调用" << endl;
}
virtual void Speak() {
cout <<*m_name<< "小猫在说话" << endl;
}
string* m_name;
~Cat() {
if (m_name != NULL) {
cout << "Cat析构函数的调用" << endl;
delete m_name;
m_name = NULL;
 }
}

};
void test01() {
Animal* animal = new Cat("Tom");
animal->Speak();

//父类的指针在析构的时候不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露
delete animal;
}
int main() {
test01();


return 0;
}

总结:

  1. 虚析构和纯虚析构都是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
  3. 拥有纯虚析构的类也属于抽象类


原文地址:https://blog.csdn.net/2301_80544540/article/details/142418087

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!