c++的类和对象

发布于:2024-10-17 ⋅ 阅读:(6) ⋅ 点赞:(0)

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. 拥有纯虚析构的类也属于抽象类