C++核心

主要针对C++面向对象编程技术做详细讲解

C++的核心与精髓 

目录

1 内存分区模型

1.1 程序运行前

1.2 程序运行后

1.3 new操作符

2 引用

2.1 引用的基本使用

2.2 引用注意事项

2.3 引用做函数参数

2.4 引用做函数返回值

2.5 引用的本质

2.6 常量引用

3 函数提高

3.1 函数默认参数

3.2 函数占位参数

3.3 函数重载

3.3.1 函数重载概述

3.3.2 函数重载注意事项

4 类和对象

4.1 封装

4.1.1 封装的意义

4.1.2 struct和class区别

4.1.3 成员属性设置为私有

4.2 对象的初始化和清理

4.2.1 构造函数和析构函数

4.2.2 构造函数的分类及调用

4.2.3 拷贝构造函数调用时机

4.2.4 构造函数调用规则

4.2.5 深拷贝与浅拷贝

4.2.6 初始化列表

4.2.7 类对象作为类成员

4.2.8 静态成员

4.3 C++对象模型和this指针

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

4.3.2 this指针概念

4.3.3 空指针访问成员函数

4.3.4 const修饰成员函数

4.4 友元

4.4.1 全局函数做友元

 4.4.2 类做友元

4.4.3 成员函数做友元

4.5 运算符重载

4.5.1 加号运算符重载

4.5.2 左移运算符重载

4.5.3 递增运算符重载

4.5.4 赋值运算符重载

4.5.5 关系运算符重载

4.5.6 函数调用运算符重载

4.6 继承

4.6.1 继承的基本语法

4.6.2 继承方式

4.6.3 继承中的对象模型

4.6.4 继承中构造和析构顺序

4.6.5 继承中同名成员处理方式

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

4.6.7 多继承语法

4.6.8 菱形继承

4.7 多态

4.7.1 多态的基本概念

4.7.2 多态案例---计算器类

4.7.3 纯虚函数和抽象类

4.7.4 多态案例二---制作饮品

4.7.5 虚析构和纯虚析构

4.7.6 多态案例三----电脑组装


1 内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

1.代码区:存放函数体的二进制代码,由操作系统进行管理的

2.全局区:存放全局变量和静态变量以及常量

3.栈区:由编译器自动分配释放,存放函数的参数值,局部变量等

4.堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回

内存四区意义:

不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

1.1 程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

代码区:

存放CPU执行的机器指令

代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

代码区是只读的,使其只读的原因是防止程序意外的修改了它的指令

全局区:

存放全局变量和变量

全局区还包含了常量区,字符串常量和其他常量也存放在此

该区域的数据在程序结束后由操作系统释放

局部变量:定义在函数体内部的变量称为局部变量,它的作用域仅限于函数内部,离开该函数内部就是 无效的,在使用就会报错

全局变量:在所有函数外部定义的变量称为全局变量,它的作用域默认整个程序,也就是所有的源文件

示例:

#include <iostream>
using namespace std;

//3.全局变量:
int g_a = 10;
int g_b = 10;

//const修饰的全局变量,全局常量
const int c_g_a = 10;
const int c_g_b = 10;
int main()
{
	//全局区:存放全局变量,静态变量,常量

	//1.创建普通局部变量
	int a = 10;
	int b = 10;

	cout << "局部变量a的地址为:" << (int)&a << endl;
	cout << "局部变量b的地址为:" << (int)&b << endl;

	cout << "全局变量g_a的地址为:" << (int)&g_a << endl;
	cout << "全局变量g_b的地址为:" << (int)&g_b << endl;

	//2.静态变量:在普通变量前面加static,属于静态变量
	static int s_a = 10;
	static int s_b = 10;

	cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
	cout << "静态变量s_b的地址为:" << (int)&s_b << endl;

	//4.常量
	//4.1 字符串常量
	cout << "字符串常量的地址为:" << (int)&"hello world" << endl;

	//4.2 const修饰的变量
	//4.2.1 const修饰的全局变量
	cout << "全局常量 c_g_a的地址为:" << (int)&c_g_a << endl;
	cout << "全局常量 c_g_b的地址为:" << (int)&c_g_b << endl;

	//4.2.2const修饰的局部变量
	const int c_l_a = 10;// c表示const,g表示global,l表示local;
	const int c_l_b = 10;

	cout << "局部常量 c_l_a的地址为:" << (int)&c_l_a << endl;
	cout << "局部常量 c_l_b的地址为:" << (int)&c_l_b << endl;

	system ("pause");

	return 0;
}

总结:

1. C++中在程序运行前分为全局区和代码区

2. 代码区特点是共享和只读

3. 全局区中存放全局变量,静态变量和常量

4. 常量区中存放const修饰的全局变量和字符串常量

1.2 程序运行后

栈区:

由编译器自动分配释放,存放函数的参数值,局部变量等

注意:

不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

示例:

#include <iostream>
using namespace std;

//栈区数据的注意事项:不要返回局部变量的地址
//栈区开辟的数据由编译器自动释放

int * func(int b)//形参数据也会放在栈区
{
	b = 100;
	int a = 10; //局部变量   存放在栈区,栈区的数据在函数执行完后自动释放
	return &a;//返回局部变量的地址
}
int main()
{
	//接收func函数的返回值
	int * p = func(1);

	cout << * p << endl;

	system ("pause");

	return 0;
}

堆区:

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在C++中主要利用new在堆区开辟内存

示例:

#include <iostream>
using namespace std;

int * func()
{
	//利用new关键字,可以将数据开辟到堆区
	//指针 本质也是局部变量,放在栈上,指针保存的数据是放在堆区
	int * p = new int (10);
	return p;
}
int main()
{
	//在堆区开辟数据
	int * p = func();
	cout << *p << endl;

	system ("pause");

	return 0;
}

总结:

堆区数据由程序员管理开放和释放

堆区数据利用new关键字进行开辟内存

1.3 new操作符

C++中利用new操作符在堆区开辟数据

堆区开辟数据,由程序员手动开辟,手动释放,释放利用操作符delete

语法:new  数据类型

利用new创建的数据,会返回该数据对应的类型的指针

示例1:基本语法

#include <iostream>
using namespace std;

//1.new的基本语法
int * func()
{
	//在堆区创建整型数据
	//new返回的是该数据类型的指针
	int * p = new int (10);
	return p;
}

void test01()
{
	int * p = func();
	cout << *p <<endl;//堆区的数据,由程序员管理开辟,程序员管理释放
	cout << *p <<endl;
	//释放利用操作符delete
	delete p;
	cout << *p <<endl;//内存已经被释放,再次访问就是非法操作,输出结果乱码
}


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

	return 0;
}

示例2 :开辟数组

#include <iostream>
using namespace std;


//2.在堆区利用new开辟数组
void test02()
{
	//创建10个整型数据的数组,在堆区
	int * arr = new int [10];//10代表数组中有10个元素
	for (int i = 0;i < 10;i++)
	{
		arr[i] = i+100;//给10个元素赋值:100-109
	}
	for(int i = 0;i <10;i++)
	{
		cout << arr[i] << endl;
	}
	//释放堆区数组要加[]
	delete [] arr;
}

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

	return 0;
}

2 引用

2.1 引用的基本使用

作用:给变量起别名

语法: 数据类型  &别名  = 原名

示例:

#include <iostream>
using namespace std;

int main()
{
	//引用基本语法:数据类型 &别名 = 原名

	int a = 10;
	//创建引用
	int &b = a;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 20;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	system ("pause");

	return 0;
}

2.2 引用注意事项

引用必须初始化

引用在初始化后,不可以改变(比如:b成为a的别名后就不可以为c的别名)

示例:

#include <iostream>
using namespace std;

int main()
{
	//引用注意事项:   
	int a = 10;

	//1.引用必须初始化
	//int &b;//错误,必须要初始化
	int &b = a;

	//2.引用在初始化后,不可以改变
	int c = 20;
	b = c;//赋值操作,而不是更改引用
	//int &b = c;//引用在初始化后,不可以改变
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;

	system ("pause");

	return 0;
}

2.3 引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

示例

#include <iostream>
using namespace std;

//交换函数
//1.值传递
void mySwap01(int a,int b)
{
	int temp = a;
	a = b;
	b = temp;

	//cout << "swap01_a = " << a << endl;
	///cout << "swap01_b = " << b << endl;
}

//2.地址传递
void mySwap02 (int *a,int *b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

//3.引用传递
void mySwap03 (int &a,int &b)
{
	int temp = a;
	a = b;
	b = temp ;
}

int main()
{
	int a = 10;
	int b = 20;

	//mySwap01 (a,b); //值传递,形参不会修饰实参
	//mySwap02 (&a,&b); //地址传递,形参会修饰实参
	mySwap03 (a,b);//引用传递,形参会修饰实参

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	system ("pause");

	return 0;
}

总结:通过引用参数产生的效果同按地址传递是一样的。引用语法更加清楚简单

2.4 引用做函数返回值

作用:引用是可以作为函数的返回值存在的

注意:不要返回局部变量引用

用法:函数调用作为左值

示例:

#include <iostream>
using namespace std;

//引用做函数返回值
//1.注意:不要返回局部变量的引用
int& test01()//函数的返回值是引用
{
	int a = 10; //局部变量  存放在四区中的栈区
	return a;
}

//2.函数的调用作为左值
int& test02()
{
	static int a = 10;//静态变量 存放在全局区,全局区上的数据在程序结束后系统释放
	return a;
}
int main()
{
	//int &ref = test01();

	//cout << "ref = " << ref << endl;//第一次结果正确,是因为编译器做了保留
	//cout << "ref = " << ref << endl;//第二次结果错误,因为a的内存已经被释放

	int &ref2 = test02();
	cout << "ref2 = " << ref2 << endl;
    cout << "ref2 = " << ref2 << endl;

	test02() = 1000;//如果函数的返回值是引用,这个函数调用可以作为左值
	cout << "ref2 = " << ref2 << endl;
    cout << "ref2 = " << ref2 << endl;

	system ("pause");

	return 0;
}

2.5 引用的本质

本质:引用的本质在C++内部实现是一个指针常量

示例:

#include <iostream>
using namespace std;

//引用的本质在C++内部实现是一个指针常量
void fun(int & ref)//发现是引用,转换为int * const ref = &a;
{
	ref = 100;//ref是引用,转换为*ref = 100;
}

int main()
{
	int a = 10;

	//自动转换为 int * const ref = &a;指针常量是指针指向不可改,也说明为什么引用不可更改
	int & ref = a;
	ref = 20;//内部发现ref是引用,自动帮我们转换为:*ref = 20;

	cout << "a = " << a << endl;
	cout << "ref = " << ref << endl;

	fun (a);

	system ("pause");

	return 0;
}

结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了。

2.6 常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

示例:

#include <iostream>
using namespace std;

//打印数据函数
void showValue (const int &val)
{
	//val = 1000;
	cout << "val = " << val <<endl;
}

int main()
{
	//常量引用主要用来修饰形参,防止误操作
	//int a = 10;
	//int &ref = 10;//引用必须引一块合法的内存空间
	//const int & ref = 10;//加上const之后,编译器将代码修改 int temp = 10; const int & ref = temp;
	//ref = 20;//加上const 之后变为只读,不可以修改

	int a = 100;

	showValue (a);
	cout << "a = " <<a <<endl;

	system ("pause");

	return 0;
}

3 函数提高

3.1 函数默认参数

在C++中,函数的形参列表中形参是可以有默认值的

语法:返回值类型  函数名  (参数 = 默认值){  }

示例:

#include <iostream>
using namespace std;

//函数默认参数
//如果我们自己传入数据,就用自己的数据,如果没有就用默认值
//语法:返回值类型  函数名  (形参 = 默认值){  }
//注意:
/*
1.如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
2.如果函数的声明有了默认参数,那么函数实现就不能有默认参数
声明和实现只能有一个有默认参数

int func1(int a = 10,int b = 10);//函数声明

int func1(int a,int b)//函数实现
{
   return a + b;
}
*/
int func(int a, int b = 20,int c = 30)
{
	return a + b + c;
}

int main()
{
	cout << func(10,30) << endl;

	system ("pause");

	return 0;
}

3.2 函数占位参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:返回值类型   函数名  (数据类型){  }

#include <iostream>
using namespace std;

//占位参数
//占位参数还可以有默认参数
void func(int a,int = 10)//第二个int起占位作用,int = 10表示第二个参数默认值为10
{
	cout << "this is func" <<  endl;
}

int main()
{
	//func(10,10);//占位参数必须填补
	func(10);//占位参数有默认值不用填补
	system ("pause");

	return 0;
}

3.3 函数重载

3.3.1 函数重载概述

作用:函数名可以相同,提高复用性

函数重载的满足条件

1.同一个作用域下

2.函数名称相同

3.函数参数类型不同,或者个数不同,或者顺序不同

注意:

函数的返回值不可作为函数重载的条件

示例:

#include <iostream>
using namespace std;

//函数重载
//可以让函数名相同,提高复用性
/*函数重载的满足条件:
1.同一个作用域下   (不在main函数内就叫全局作用域)
2.函数名称相同
3.函数参数类型不同,或者个数不同,或者顺序不同
*/

void func()
{
	cout << "func的调用" << endl;
}

void func(int a)
{
	cout << "func(int a)的调用!" << endl;
}

void func(double a)
{
	cout << "func(double a)的调用!" << endl;
}

void func(int a,double b)
{
	cout << "func(int a,double b)的调用!" << endl;
}

void func(double a,int b)
{
	cout << "func(double a,int b)的调用!" << endl;
}
/*注意:
函数的返回值不可作为函数重载的条件
*/
/*int func(double a,int b)
{
	cout << "func(double a,int b)的调用!" << endl;
}*/

int main()
{
	func();//调第一个函数
	func(10);//调第二个函数
	func(3.4);//调第三个函数
	func(10,3.1);//调第四个函数
	func(3.2,1);//调第五个函数

	system ("pause");

	return 0;
}

3.3.2 函数重载注意事项

1.引用作为重载条件

2.函数重载碰到函数默认参数

示例:

#include <iostream>
using namespace std;

//函数重载注意事项
//1.引用作为重载条件
void func(int &a)
{
	cout <<"func(int &a)调用" << endl;
}

void func(const int &a)
{
	cout <<"func(const int &a)调用" << endl;
}
//2.函数重载碰到函数默认参数

void func2(int a, int b = 10)
{
	cout <<"func2(int a, int b)调用" << endl;
}

void func2(int a)
{
	cout <<"func2(int a)调用" << endl;
}

int main()
{
	int a = 10;
	//func(a);//调第一个函数

	//func(10);//调第二个函数

	//func2(10);//函数重载碰到默认参数,出现二义性,报错,尽量避免这种情况

	system ("pause");

	return 0;
}

4 类和对象

C++面向对象的三大特性为:封装,继承,多态

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

例如:

人可以作为对象,属性有姓名,年龄,身高,体重...,行为有走,跑,跳,....

车也可以作为对象,属性有轮胎,方向盘,车灯...,行为有载人,放音乐...

具有相同性质的对象,我们可以抽象成为,人属于人类,车属于车类

4.1 封装

4.1.1 封装的意义

封装是C++面向对象三大特性之一

封装的意义:

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

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

封装意义1:

在设计类的时候,属性和行为写在一起,表现事物

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

类中的属性和行为 统一成为 成员
属性 也称为成员属性 或 成员变量
行为 也成为 成员函数 或 成员方法

示例1:设计一个圆类,求圆的周长

#include <iostream>
using namespace std;

//圆周率:
const double PI = 3.14;

//设计一个圆类,求圆的周长
//圆求周长公式:2 * PI * 半径

//class 代表设计一个类,类后面紧跟着就是类名称
class Circle 
{
	//访问权限
	//公共权限
public :
	//属性
	int m_r;//半径
	//行为: 获取圆的周长
	double calculateZC()
	{
		return 2 * PI * m_r;
	}
};

int main()
{
	//通过圆类 创建具体的圆(对象)
	//实例化  (通过一个类,创建一个对象的过程)
	Circle c1;
	//给圆对象的属性进行赋值
	c1.m_r = 10;
	cout << "圆的周长为:" << c1.calculateZC() << endl;

	system ("pause");

	return 0;
}

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

#include <iostream>
using namespace std;
#include <string>

//设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
class Student 
{
public://公共权限

	//类中的属性和行为 统一成为 成员
	//属性 也称为成员属性或成员变量
	//行为 也成为 成员函数或成员方法

	//属性
	string m_name;//姓名
	int m_ID;//学号

	//行为
	//显示姓名和学号
	void showStudent()
	{
		cout << "姓名:" << m_name << "   学号:" << m_ID << endl;
	}

	//也可以通过行为来给对象赋值
	//给姓名赋值
	void setname(string name)
	{
		m_name = name;
	}
	//给学号赋值
	void setID(int id )
	{
		m_ID = id;
	}
};

int main()
{
	//创建一个具体学生    实例化对象
	Student s1;
	//给s1对象进行属性赋值操作
	//s1.m_name = "张三";
	s1.setname("张三");
	//s1.m_ID = 1;
	s1.setID (2);

	//显示学生信息
	s1.showStudent ();
	
	Student s2;
	s2.m_name = "李四";
	s2.m_ID = 2;
	s2.showStudent ();

	system ("pause");

	return 0;
}

封装意义2:

类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有3种:

1.public  公共权限

2.protected  保护权限

3.private  私有权限

示例:

#include <iostream>
using namespace std;

//访问权限有3种:

//1.public  公共权限  成员在类内可以访问,类外可以访问

//2.protected  保护权限    成员在类内可以访问,类外不可以访问

//3.private  私有权限     成员在类内可以访问,类外不可以访问

class person
{
public:
	//公共权限
	string m_name; //姓名

protected:
	//保护权限
	string m_car; //汽车

private:
	//私有权限
	int m_password; //银行卡密码

public:
	void func()
	{
		m_name = "张三";
		m_car = "拖拉机";
		m_password = 123456;
	}
};

int main()
{
	//实例化一个具体的对象
	person p1;
	p1.m_name = "李四";
	//p1.m_car = "奔驰";//保护权限的内容,在类外访问不到
	//p1.m_password = 123123;//私有权限的内容,在类外访问不到

	system ("pause");

	return 0;
}

4.1.2 struct和class区别

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

区别:

struct 默认权限为公共

class默认权限为私有

#include <iostream>
using namespace std;

//struct和class区别:struct 默认权限为公共,class默认权限为私有
class C1
{
	int m_A;//默认权限为私有
};

struct C2
{
	int m_A;//默认权限为公共
};

int main()
{
	C1 c1;
	//c1.m_A = 100;//class默认权限为私有,因此不可以访问

	C2 c2;
	c2.m_A = 100;//struct 默认权限为公共,因此可以访问

	system ("pause");

	return 0;
}

4.1.3 成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限

优点2:对于写权限,我们可以检测数据的有效性

示例:

#include <iostream>
using namespace std;
#include <string>

//成员属性设置为私有
class Person
{
public:
	//写姓名(设置姓名)
	void setname(string name)
	{
		m_name = name;
	}
	//获取姓名
	string getname()
	{
		return m_name ;
	}
	//获取年龄  可读可写   如果想修改(年龄范围必须是0-150之间)
	int getage()
	{
		//m_age = 0;//初始化年龄
		return m_age;
	}
	//设置年龄
	void setage(int age)
	{
		if (age < 0 || age > 150)
		{
			m_age = 0;
			cout << "你这个老妖精" << endl;
			return;
		}
		m_age = age;
	}
	//设置情人    只写
	void setlover(string lover)
	{
		m_lover = lover;
	}
private:
	//姓名  可读可写
	string m_name;
	//年龄   只读
	int m_age;
	//情人   只写
	string m_lover;
};
int main()
{
	 Person p;
	 //p.m_name = "张三";//成员属性设置为私有所以不可访问
	 p.setname ("张三");
	 p.setlover ("汤圆");
	 p.setage (210);

	 cout << "姓名为:" << p.getname ()<< endl;

	 cout << "年龄为:" << p.getage ()<< endl;

	system ("pause");

	return 0;
}

练习案例1:设计立方体类

设计立方体类(Cube)

求出立方体的面积和体积

分别用全局函数和成员函数判断两个立方体是否相等

#include <iostream>
using namespace std;

//设计立方体类:
//1.创建立方体类
//2.设计属性   一般属性尽量设置为私有
//3.设计行为:获取立方体的面积和体积
//4.分别用全局函数和成员函数,判断两个立方体是否相等

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_l*m_h + 2*m_h*m_w;
	}
	//获取立方体体积
	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);

	//cout << "c2的面积为:" << c2.calculateS () << endl;
	//cout << "c2的体积为:" << c2.calculateV () << endl;

	//利用全局函数判断
	bool ret = isSame(c1,c2);
	if (ret)
	{
		cout <<"c1和c2是相等的" << endl;
	}
	else
	{
		cout <<"c1和c2是不相等的" << endl;
	}

	//利用成员函数判断
	ret = c1.isSameByClass(c2);
	if (ret)
	{
		cout <<"成员函数判断:c1和c2是相等的" << endl;
	}
	else
	{
		cout <<"成员函数判断:c1和c2是不相等的" << endl;
	}

	system ("pause");

	return 0;
}

练习案例2:点和圆的关系

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

4.2 对象的初始化和清理

C++中的面向对象来源于生活,每个对象也会有初始设置以及对象销毁前的清理数据的设置

4.2.1 构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题

  一个对象或者变量没有初始状态,对其使用后果是未知

  同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

C++利用了构造函数和析构函数解决以上问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供

编译器提供的构造函数和析构函数是空实现

构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用

析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

构造函数和析构函数写在类里面

构造函数语法: 类名 () { }

1. 构造函数,没有返回值也不写void

2. 函数名称与类名相同

3. 构造函数可以有参数,因此可以发生重载

4. 程序在调用对象时候会自动调用构造,无需手动调用,而且 只会调用一次

析构函数语法: ~类名 () { }

1.析构函数,没有返回值也不写void

2. 函数名称与类名相同

3. 析构函数不可以有参数,因此不可以发生重载

4. 程序在对象销毁前会自动调用析构,无需手动调用,而且 只会调用一次

示例:

#include <iostream>
using namespace std;

//对象的初始化和清理
//1. 构造函数:进行初始化操作
class Person 
{
public:

	//1.1 构造函数
	//没有返回值也不写void
	//函数名称与类名相同
	//可以有参数,因此可以发生重载
	//调用对象时候会自动调用构造,无需手动调用,只会调用一次
	Person()
	{
		cout << "Person构造函数的调用" << endl;
	}

	//2. 析构函数:进行清理的操作
	//析构函数,没有返回值也不写void
	//函数名称与类名相同
	//析构函数不可以有参数,因此不可以发生重载
	//程序在对象销毁前会自动调用析构,无需手动调用,而且 只会调用一次
	~Person()
	{
		cout << "Person析构函数的调用" << endl;
	}
};
//构造和析构都是必须有的,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
	Person p;//在栈上的数据,test01执行完后就会释放这个对象,程序在对象销毁前会自动调用析构,
}


int main()
{
	//test01();

	Person p;//如果直接写在main函数中,运行结果只有构造没有析构,原因是:只有在销毁前会自动调用析构

	system ("pause");

	return 0;
}

4.2.2 构造函数的分类及调用

两种分类方式:

       按参数分为:有参构造和无参构造(默认构造)

       按类型分为:普通构造和拷贝构造

三种调用方式:

       括号法

       显示法

       隐式转换法

注意事项1:调用默认构造函数时,不要加();

注意事项2:不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p3) == Person p3;

示例:

#include <iostream>
using namespace std;

//构造函数的分类及调用
//分类:
//按参数分为:有参构造和无参构造(默认构造)
//按类型分为:普通构造和拷贝构造
class Person
{
public:
	//构造函数
	//普通构造函数:
	int age;

	Person ()//无参构造
	{
		cout << "Person的无参构造函数调用" << endl;
	}
	Person (int a)//有参构造
	{
		age = a;
		cout << "Person的有参构造函数调用" << endl;
	}
	//拷贝构造函数
	Person(const Person &p)//const是为了限定它本身,防止数据改变
	{
		age = p.age;//将传入的人身上的所有属性,拷贝到我身上
		cout << "拷贝构造函数调用" << endl;
	}

	//析构函数
	~Person ()
	{
		cout << "Person的析构函数调用" << endl;
	}
		
};


void test01()
{
	//调用:
//1.括号法   常用
	//Person p1;//默认构造函数(无参构造函数)调用
	//Person p2(10);//有参构造函数调用
	//Person p3(p2);//拷贝构造函数调用

	//注意事项1:调用默认构造函数时,不要加()
	//Person p1();因为这行代码编译器会认为是一个函数声明,不会认为在创建对象

	//cout << "p2的年龄为:" << p2.age << endl;
	//cout << "p3的年龄为:" << p3.age << endl;

//2.显示法
	Person p1;//无参构造
	Person p2 = Person(10);//有参构造
	Person p3 = Person(p2);//拷贝构造

	//Person(10);//单独写就是匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象(当前行结束后,马上析构)
	cout << "aaaa" << endl;

	//注意事项2:不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p3) == Person p3;与57行重复定义
	//Person(p3);

//3.隐式转换法
	Person p4 = 10;//相当于写了 Person p4 = Person(10);有参构造
	Person p5 = p4;//拷贝构造
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

4.2.3 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况:

1.使用一个已经创建完毕的对象来初始化一个新对象

2.值传递的方式给函数参数传值

3.以值方式返回局部对象

示例:

#include <iostream>
using namespace std;

class Person
{
public:
	Person ()
	{
		cout << "Person默认构造函数调用" << endl;
	}

	Person (int age)
	{
		cout << "Person有参构造函数调用" << endl;
		m_Age = age;
	}

	Person (const Person &p)
	{
		cout << "Person拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
	}

	~Person ()
	{
		cout << "Person析构函数调用" << endl;
	}

	int m_Age;
};

//拷贝构造函数调用时机:
//1.使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
	Person p1(20);
	Person p2(p1);

	cout << "p2的年龄为:" << p2.m_Age <<endl;
}

//2.值传递的方式给函数参数传值
void dowork(Person p)
{

}

void test02()
{
	Person p;//创建一个默认构造函数
	dowork(p);
}
//3.以值方式返回局部对象
Person dowork2()
{
	Person p1;
	cout <<(int*)&p1 <<endl;//(int *)是将地址以十六进制输出
	return p1;
}

void test03()
{
	Person p = dowork2();
	cout << (int*)&p <<endl;
}

int main()
{
	//test01();
	//test02();
	test03();

	system ("pause");

	return 0;
}

4.2.4 构造函数调用规则

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

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

1.如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造

2.如果用户定义拷贝构造函数,C++不会提供其他构造函数

示例:

#include <iostream>
using namespace std;

//构造函数调用规则
//1.如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造

//2.如果用户定义拷贝构造函数,C++不会提供其他普通构造函数
class Person
{
public:
	//Person()
	//{
		//cout << "Person的默认构造函数调用" << endl;
	//}

	//Person(int age)
	//{
		//m_Age = age;

		//cout << "Person的有参构造函数调用" << endl;
	//}

	Person(const Person &p)
	{
		m_Age = p.m_Age ;

	   cout << "Person的拷贝构造函数调用" << endl;
	}

	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}

	int m_Age;
};

//void test05()
//{
	//Person p;
	//p.m_Age = 18;

	//Person p2(p);

	//cout <<"p2的年龄为:"<< p.m_Age <<endl;
//}

void test06()
{
	//Person p;
	//Person p(20);
	//Person p2 (p);

	//cout <<"p2的年龄为:"<< p2.m_Age <<endl;
}
#if 1
int main()
{
	//test05();
	test06();

	system ("pause");

	return 0;
}
#endif

4.2.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);

		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);
	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;
}

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

4.2.6 初始化列表

作用:

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;
	int m_B;
	int m_C;
};

void test01()
{
	//Person p(10,20,30);
	Person p(30,20,10);

	cout <<"m_A = "<<p.m_A <<endl;
	cout <<"m_B = "<<p.m_B <<endl;
	cout <<"m_C = "<<p.m_C <<endl;
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

4.2.7 类对象作为类成员

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

例如:

class A {}
calss B 
{
     A a;

}

B类中有对象A作为成员,A为对象成员

当创建B对象时,A与B的构造和析构的顺序:

当其他类的对象作为本类的成员,构造时候先构造类对象,再构造自身,
析构的顺序与构造相反

示例:

#include <iostream>
using namespace std;
#include <string>

//类对象作为类成员
//手机类:
class Phone
{
public:
	//手机品牌名称
	string m_PName;
	Phone(string pName)
	{
		cout <<"Phone的构造函数调用" <<endl;
		m_PName = pName;
	}

	
	~Phone ()
	{
		cout <<"Phone的析构函数调用" <<endl;
	}
};

//人类:
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("张三","苹果");

	cout << p.m_Name <<"拿着:" << p.m_Phone.m_PName  <<endl;
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

4.2.8 静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为:

1. 静态成员变量:

所有对象共享一份数据

在编译阶段分配内存

类内声明,类外初始化

2. 静态成员函数

所有对象共享同一个函数

静态成员函数只能访问静态成员变量

示例1 :静态成员变量

#include <iostream>
using namespace std;

//静态成员变量
class Person
{
public :
	//1.所有对象都共享同一份数据
	//2.在编译阶段分配内存
	//3.类内声明,类外初始化操作
	static int m_A;

	//静态成员变量也是有访问权限的
//private:
	//static int m_B;
	
};

int Person::m_A = 100;//Person::m_A意思是Person作用域下的成员m_A;
//int Person::m_B = 200;

void test01()
{
	Person p;
	cout <<p.m_A <<endl;

	Person p2;
	p2.m_A = 200;


	cout <<p.m_A <<endl;
}

void test02()
{
	//静态成员变量,不属于某个对象上,所有对象都共享同一份数据,因此静态成员变量有两种访问方式:
	//1.通过对象进行访问
	//Person p;
	//cout << p.m_A << endl;

	//2.通过类名进行访问
	cout << Person::m_A <<endl;
	//cout << Person::m_B <<endl;//类外访问不到私有静态成员变量
}
int main()
{
	//test01();
	test02();

	system ("pause");

	return 0;
}

示例2:静态成员函数

#include <iostream>
using namespace std;

//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量

class Person
{
public:
	static void func() //静态成员函数
	{
		m_A = 100;//静态成员函数 能访问 静态成员变量  因为所有对象共享一份数据所以可以进行访问
		//m_B = 200;//静态成员函数 不能访问 非静态成员变量 因为非静态成员变量只能通过对象进行访问,无法区分到底是哪个对象的m_B属性

		cout <<"static void func调用"<< endl;
	}

	static int m_A; //静态成员变量
	int m_B;//非静态成员变量

	//静态成员函数也是有访问权限的
//private:
	//static void func2()
	//{
	//	cout <<"static void func2调用"<< endl;
	//}
};

int Person:: m_A = 0; //静态成员变量初始化(类内声明,类外初始化)

void test01()
{
	//有两种访问方式:
	//1.通过对象访问
	Person p;
	p.func ();

	//2.通过类名访问
	Person::func(); //Person作用域下的func函数
	//Person::func2 (); 类外访问不到私有静态成员函数
}
int main()
{

	test01();

	system ("pause");

	return 0;
}

4.3 C++对象模型和this指针

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

在C++中,类内的成员变量和成员函数分开存储

只有非静态成员变量才属于类的对象上

示例:

#include <iostream>
using namespace std;

//成员变量 和 成员函数 分开存储
class Person
{
	int m_A;//非静态成员变量 属于类的对象上

	static int m_B;//静态成员变量 不属于类的对象上

	void func()//非静态成员函数 不属于类的对象上
	{

	}

	static void func2()//静态成员函数 不属于类的对象上
	{

	}
};

int Person::m_B = 0;//初始化

void test01()
{
	Person p;
	//空对象占用的内存空间为:1字节
	//因为C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
	//每个空对象也应该有一个独一无二的内存地址
	cout << "sizeof p = " << sizeof(p)<< endl;

}

void test02()
{
	Person p;
	cout << "sizeof p = " << sizeof(p)<< endl;
}

int main()
{
	//test01();
	test02();

	system ("pause");

	return 0;
}

4.3.2 this指针概念

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

问题:这一块代码是如何区分哪个对象调用自己的呢?

C++通过提供特殊的对象指针,this指针,解决这个问题。

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

this指针是隐含每一个非静态成员函数内的一种指针,不需要定义,直接使用即可

this指针的用途:

1.当形参和成员变量同名时,可用this指针来区分

2.在类的非静态成员函数中返回对象本身,可用return *this

示例:

#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age)//构造函数
	{
		//this指针指向的是 被调用的成员函数 所属的对象
		this -> age = age;
	}

	Person&  PersonAddAge(Person &p)//Person & 用引用的方式返回本体
	{
		this->age += p.age ;

		return *this;//this指向的是p2的指针,*this指向的是对象的本体
	}

	//Person  PersonAddAge(Person &p)//如果返回的是值类型,就相当于值传递
//	{
		//this->age += p.age ;

		//return *this;//this指向的是p2的指针,*this指向的是对象的本体
	//}

	int age;
};
//1.解决名称冲突
void test01()
{
	Person p1(18);
	cout << "p1的年龄:"<< p1.age <<endl;

}
//2.返回对象本身用 *this
void test02()
{
	Person p1(10);

	Person p2(10);

	//链式编程思想:
	p2.PersonAddAge (p1).PersonAddAge(p1).PersonAddAge(p1);

	cout <<"p2的年龄为:"<<p2.age <<endl;
}
int main()
{
	//test01();
	test02();

	system ("pause");

	return 0;
}

4.3.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

示例 :

#include <iostream>
using namespace std;

//空指针访问成员函数
class Person
{
public:
	void showClass()
	{
		cout <<"this is Person class"<<endl;
	}

	void showPersonAge()
	{
		//报错原因是因为传入的指针是空指针
		//cout << "age = "<< this -> m_Age <<endl;

		if(this == NULL)
		{
			return;
		}//加if语句防止代码崩溃掉
		cout << "age = "<< this -> m_Age <<endl;
	}

	int m_Age;
};

void test01()
{
	Person *p = NULL;//空指针

	p->showClass ();

	p->showPersonAge ();
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

4.3.4 const修饰成员函数

常函数:

成员函数后加const后我们称这个函数为常函数

常函数内不可以修改成员属性

成员属性声明时加关键字mutable后,在常函数中依旧可以修改

常对象:

声明对象前加const称该对象为常对象

常对象只能调用常函数

常对象的属性不可修改

示例:

#include <iostream>
using namespace std;

//const修饰成员函数


class Person
{
public:
	//this指针的本质:指针常量,指针的指向不可修改,但是值可以修改
	//const Person * const this;//指针的指向和值不可修改
	//在成员函数后加const修饰的是this指针,让指针指向的值也不可修改
	
	void showPerson()const //常函数
	{
		//this->m_A = 100;
		this->m_B = 200;
		//this = NULL;//this指针不可以修改指针的指向
	}

	int m_A;
	mutable int m_B;// 特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable

	void func()
	{
		m_A = 100;
	}
};

void test01()
{
	Person p;
	p.showPerson ();
}

//常对象
void test02()
{
	const Person p;//在对象前加const 变为 常对象
	//p.m_A = 100;//常对象的属性不可修改
	p.m_B = 200;//m_B是 一个特殊值,在常对象下可以修改

	//常对象只能调用常函数
	p.showPerson ();
	//p.func();//常对象不可以调用 普通成员函数,因为普通成员函数可以修改属性
}
int main()
{
	system ("pause");

	return 0;
}

4.4 友元

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

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

友元的关键字:friend

友元的三种实现:

全局函数做友元

类做友元

成员函数做友元

4.4.1 全局函数做友元

示例:

#include<iostream>
using namespace std;
#include<string>

//房屋类
class Building
{
	friend void goodGay(Building *building);//goodGay全局函数是Building的好朋友,可以访问Building中私有的成员
public:
	Building ()//构造函数
	{
		m_SittingRoom = "客厅";
		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 building ;
	goodGay (&building);
}
int main()
{
	test01();

	system ("pause");

	return 0;

}

 4.4.2 类做友元

#include<iostream>
using namespace std;
#include<string>

//类做友元
class Building;

class goodGay
{
public:
	goodGay();

public:
	void visit();//参观函数 访问Buliding中的属性
	Building * building;

};

class Building 
{
	friend class goodGay;//goodGay类是本类的好朋友,可以访问本类中的私有成员
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 test01()
{
	goodGay gg;
	gg.visit ();
}
int main()
{
	test01();

	system ("pause");

	return 0;
}

4.4.3 成员函数做友元

#include<iostream>
using namespace std;
#include<string>

//成员函数做友元
class Building;
class GoodGay
{
public:
	GoodGay ();//构造函数

	void visit();//让visit函数可以访问Building中私有成员
	void visit2();//让visit2函数不可以访问Building中私有成员

	Building * building;

};

class Building
{
	friend void GoodGay::visit();//告诉编译器GoodGay类下的成员函数作为本类的好朋友,可以访问本类中的私有成员

public:
	Building ();//构造函数
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};

//类外实现成员函数
Building ::Building ()//Building作用域下的构造函数Building
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

GoodGay::GoodGay ()//GoodGay作用域下的构造函数GoodGay
{
	building = new Building ;
}

void GoodGay::visit()
{
	cout <<"visit函数正在访问:"<< building ->m_SittingRoom <<endl;
	cout <<"visit函数正在访问:"<< building ->m_BedRoom <<endl;
}

void GoodGay:: visit2()
{
	cout <<"visit2函数正在访问:"<< building ->m_SittingRoom <<endl;
	//cout <<"visit2函数正在访问:"<< building ->m_BedRoom  <<endl;
}

void test01()
{
	GoodGay gg;
	gg.visit();
	gg.visit2 ();
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

4.5 运算符重载

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

4.5.1 加号运算符重载

作用:实现两个自定义数据类型相加的运算

示例:

#include <iostream>
using namespace std;

//加号运算符重载
class Person
{
public:
	//1.成员函数重载+号
	/*Person operator+(Person &p)
	{
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;

		return temp;

	}*/

	int m_A;
	int m_B;
};

//2.全局函数重载+号
	Person operator+(Person &p1, Person &p2)
	{
		Person temp;
		temp.m_A = p1.m_A + p2.m_A ;
		temp.m_B = p1.m_B + p2.m_B;

		return temp;
	}

//函数重载的版本
Person operator+(Person &p1, int num)
{
	Person temp;
    temp.m_A = p1.m_A + num;
	temp.m_B = p1.m_B + num;

	return temp;
}

void test01()
{
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;

	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	//成员函数重载本质调用:
	//Person p3 = p1.operator+(p2);

	//全局函数重载本质调用:
	//Person p3 = operator+(p1,p2);
	
	Person p3 = p1 + p2;

	//运算符重载 也可以发生函数重载
	Person p4 = p1 + 100;//Person + int

	cout <<"p3.m_A = "<< p3.m_A <<endl;
	cout <<"p3.m_B = "<< p3.m_B <<endl;

	cout <<"p4.m_B = "<< p4.m_B <<endl;
	cout <<"p4.m_A = "<< p4.m_A <<endl;
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

 总结:

1. 对于内置数据类型表达式的运算符是不可以改变的

2. 不要滥用运算符重载

4.5.2 左移运算符重载

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

示例:

#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
	//不会利用成员函数重载<<运算符(左移运算符),因为无法实现cout在左侧
	//void operator<<(cout)
	//{

	//}

	int m_A;
	int m_B;

};

//只能利用全局函数重载左移运算符
ostream & operator<<(ostream &cout ,Person &p)//本质:operator<<(cout,p) 简化成 cout << p
{
	cout << "m_A = "<< p.m_A << "  m_B = " << p.m_B;

	return cout;
} 

void test01()
{
	Person p(10,10);
	//p.m_A = 10;
	//p.m_B = 20;

	cout << p << endl;
}
int main()
{
	test01();

	system ("pause");

	return 0;
}

4.5.3 递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

示例:

#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;
	}

	//重载后置++运算符
	MyInteger operator++(int)  //void operator++(int) 这个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();

	system ("pause");

	return 0;
}

总结:前置递增返回的是引用,后置递增返回的是值

4.5.4 赋值运算符重载

C++编译器至少给一个类添加四个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

4.默认运算符 operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

示例:

#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的年龄为 :" << *p1.m_Age <<endl;
	cout << "p2的年龄为 :" << *p2.m_Age <<endl;
	cout << "p3的年龄为 :" << *p3.m_Age <<endl;

}
int main()
{
	test01();

	/*
	int a= 10;
	int b = 20;
	int c = 30;

	c = b = a;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "b = " << b << endl;
	*/

	system ("pause");

	return 0;
}

4.5.5 关系运算符重载

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

示例:

#include <iostream>
using namespace std;
#include<string>

//关系运算符重载
class Person
{
public:
	Person(string name,int age)
	{
		m_Name = name;
		m_Age = age;
	}

	//重载 == 号
	bool operator==(Person &p)
	{
		if(this->m_Age == p.m_Age && this->m_Name == p.m_Name )
		{
			return true;
		}
		return false;
	}
	//重载 != 号
	bool operator!=(Person &p)
	{
		if(this->m_Age == p.m_Age && this->m_Name == p.m_Name )
		{
			return false;
		}
		return true;
	}
	int m_Age;
	string m_Name;
};

void test01()
{
	Person p1("Tom",18);
	Person p2("Tom",18);

	if(p1 == p2)
	{
		cout <<"p1和p2是相等的"<< endl;
	}
	else
	{
		cout <<"p1和p2是不相等的"<< endl;
	}

	if(p1 != p2)
	{
		cout <<"p1和p2是不相等的"<< endl;
	}
	else
	{
		cout <<"p1和p2是相等的"<< endl;
	}
}
int main()
{
	test01();

	system ("pause");

	return 0;
}

4.5.6 函数调用运算符重载

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

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

仿函数没有固定写法,很灵活

示例:

#include<iostream>
using namespace std;
#include<string>

//函数调用运算符重载
//打印输出类
class MyPrint
{
public:
	//重载函数调用运算符
	void operator()(string test)
	{
		cout << test <<endl;
	}

};

void test01()
{
	MyPrint myprint;

	myprint ("Hello Word");
}

//仿函数使用非常灵活,没有固定写法
//加法类
class MyAdd
{
public:
	int operator()(int num1,int num2)
	{
		return num1 + num2;
	}
};

void test02()
{
	MyAdd myadd;

	int ret = myadd(100,100);

	cout << "ret =" << ret <<endl; 

	//匿名函数对象
	cout <<MyAdd ()(100,100) <<endl;
}

int main()
{
	//test01();

	test02();

	system ("pause");

	return 0;
}

4.6 继承

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

有些类与类之间存在特殊关系,例如:

 我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性

这时我们就可以考虑利用继承的技术,减少重复代码

4.6.1 继承的基本语法

示例:

#include <iostream>
using namespace std;
//普通实现页面

Java页面
//class Java
//{
//public:
//	void header()
//	{
//		cout << "首页、公开课、登录、注册...(公共头部)" <<endl;
//	}
//	void foot()
//	{
//		cout <<"帮助中心、交流合作,站内地图...(公共底部)" <<endl;
//	}
//	void left()
//	{
//		cout <<"Java、Python、C++(公共分类列表)" <<endl;
//	}
//	void content()
//	{
//		cout << "Java学科视频" <<endl;
//	}
//};
//
Python页面
//class Python
//{
//public:
//	void header()
//	{
//		cout << "首页、公开课、登录、注册...(公共头部)" <<endl;
//	}
//	void foot()
//	{
//		cout <<"帮助中心、交流合作,站内地图...(公共底部)" <<endl;
//	}
//	void left()
//	{
//		cout <<"Java、Python、C++(公共分类列表)" <<endl;
//	}
//	void content()
//	{
//		cout << "Python学科视频" <<endl;
//	}
//};
//
C++页面
//class CPP
//{
//public:
//	void header()
//	{
//		cout << "首页、公开课、登录、注册...(公共头部)" <<endl;
//	}
//	void foot()
//	{
//		cout <<"帮助中心、交流合作,站内地图...(公共底部)" <<endl;
//	}
//	void left()
//	{
//		cout <<"Java、Python、C++(公共分类列表)" <<endl;
//	}
//	void content()
//	{
//		cout << "C++学科视频" <<endl;
//	}
//};

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

//继承的好处:减少重复代码
//语法:class 子类 :继承方式 父类
//子类  也称为 派生类
//父类  也称为 基类
//Java页面
class Java:public BasePage //继承上面 BasePage这个类
{
public:
	void content()
	{
		cout << "Java学科视频" <<endl;
	}
};

//Python页面
class Python:public BasePage //继承上面 BasePage这个类
{
public:
	void content()
	{
		cout << "Python学科视频" <<endl;
	}
};

//C++页面
class CPP:public BasePage //继承上面 BasePage这个类
{
public:
	void content()
	{
		cout << "C++学科视频" <<endl;
	}
};

void test01()
{
	cout << "Java下载视频的页面如下:"<<endl;
	Java ja;

	ja.header ();
	ja.foot ();
	ja.left ();
	ja.content ();

	cout <<"----------------------------------"<<endl;
	cout << "Python下载视频的页面如下:"<<endl;
	Python py;

	py.header ();
	py.foot ();
	py.left ();
	py.content ();

	cout <<"----------------------------------"<<endl;
	cout << "C++下载视频的页面如下:"<<endl;
	CPP cpp;

	cpp.header ();
	cpp.foot ();
	cpp.left ();
	cpp.content ();
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

总结:

继承的好处:减少重复代码
语法:class 子类 :继承方式  父类
子类  也称为 派生类
父类  也称为 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员

从基类继承过来的表现其共性,而新增加的成员体现了其个性

4.6.2 继承方式

继承方式一共有三种:

公共继承

保护继承

私有继承

 示例:

#include <iostream>
using namespace std;

//继承方式
//1.公共继承
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 = 20;  //父类中的保护权限成员  到子类中仍然是保护权限
		//m_C = 30;//父类中的私有权限成员  到子类中访问不到
	}
};

//2.保护继承
class Base2 //父类
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son2 :protected  Base2 //子类
{
public:
	void func()
	{
		m_A = 10; //父类中的公共权限成员  到子类变为保护权限成员
		m_B = 20;  //父类中的保护权限成员  到子类中仍然是保护权限
		//m_C = 30;//父类中的私有权限成员  到子类中访问不到
	}
};

//3.私有继承
class Base3 //父类
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son3 :private  Base3 //子类
{
public:
	void func()
	{
		m_A = 10; //父类中的公共权限成员  到子类变为私有权限成员 
		m_B = 20;  //父类中的保护权限成员  到子类中变为私有权限成员 
		//m_C = 30;//父类中的私有权限成员  到子类中访问不到
	}
};

class GrandSon3:public Son3 //验证Son3中的成员是否变为私有权限
{
public:
	void func()
	{
		//m_A = 100;  //到了Son3中m_A变为私有成员,子类访问不到
		//m_B = 100;  //同上
		//m_C = 100;  //同上
	}
};

void test01()
{
	Son1 s1;
	s1.m_A = 100;//Son1中 m_A是公共权限,类内,类外都可访问到
	//s1.m_B = 100; //Son1中 m_B是保护权限,类外访问不到  也侧面的验证了父类中的保护权限成员  到子类中仍然是保护权限
}

void test02()
{
	Son2 s1;
	//s1.m_A = 100;//Son2中 m_A是保护权限,类外访问不到
	//s1.m_B = 100; //Son2中 m_B是保护权限,类外访问不到 
}

void test03()
{
	Son3 s1;
	//s1.m_A = 100;//Son3中 m_A是私有成员,类外访问不到
	//s1.m_B = 100; //Son3中 m_B是私有成员,类外访问不到
}
int main()
{
	test01();
	
	system ("pause");

	return 0;
}

4.6.3 继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

示例:

#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 << "sizeof Son = " <<sizeof(Son)<<endl;
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

总结:
    父类中所有非静态成员属性都会被子类继承下去
    父类中私有成员属性 是被编译器给隐蔽了 ,因此是访问不到,但是确实被继承下去了

4.6.4 继承中构造和析构顺序

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

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

示例:

#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();

	system ("pause");

	return 0;
}

总结:

继承中的构造和析构顺序如下:
先构造父类,在构造子类,析构的顺序与构造顺序相反

4.6.5 继承中同名成员处理方式

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

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

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

示例:

#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 test01()
{
	Son s;
	cout << "Son下的m_A = " << s.m_A << endl; 
	//如果通过子类对象 访问到父类中同名成员 需要加作用域
	cout << "Base下的m_A = " << s.Base::m_A << endl; 
}
//同名函数处理方式
void test02()
{
	Son s;
	s.func(); //直接调用   调用的是子类中的同名成员

	//如何调用到父类中的同名成员函数?
	s.Base::func(); 

	//如果子类中出现和父类相同的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
	//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
	s.Base::func(100);
}

int main()
{
	//test01();
	test02();

	system ("pause");

	return 0;
}

总结:

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

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

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

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

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

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

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

示例:

#include<iostream>
using namespace std;

//继承中同名静态成员处理方式
class Base
{
public :
	static int m_A;  //静态成员变量 类内声明,类外初始化

	static void func()
	{
		cout <<"Base-static void func()"<<endl;
	}
};
int Base::m_A = 100; //静态成员变量初始化

class Son :public Base
{
public :
	static int m_A; 

	static void func()
	{
		cout <<"Son-static void func()"<<endl;
	}
};
int Son::m_A = 200;

//同名静态成员属性
void test01()
{
	//1.通过对象访问
	cout << "通过对象访问: "<<endl;
	Son s;
	cout << "Son 下的m_A = " << s.m_A <<endl;
	cout << "Base 下的m_A = " << s.Base::m_A <<endl;

	//2.通过类名访问
	cout << "通过类名访问: "<<endl;
	cout << "Son 下的m_A = " << Son::m_A <<endl;
	//第一个::代表通过类名方式访问  第二个::代表访问父类作用域下
	cout << "Base 下的m_A = " << Son::Base::m_A <<endl;
}

//同名静态成员函数
void test02()
{
	//1.通过对象访问
	cout << "通过对象访问: "<<endl;
	Son s;
	s.func ();
	s.Base::func ();

	//2.通过类名访问
	cout << "通过类名访问: "<<endl;
	Son::func ();
	Son::Base::func();
}

int main()
{
	//test01();
	test02();

	system ("pause");

	return 0;
}

总结:

同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)

4.6.7 多继承语法

C++中允许一个类继承多个类

语法:class  子类  :继承方式  父类1,继承方式  父类2.....

多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议用多继承

示例:

#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;
};

//子类    需要继承Base1 和Base2
//语法:class  子类  :继承方式  父类1,继承方式  父类2.....
class Son:public Base1 ,public Base2
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}

	int m_C;
	int m_D;
};

void test01()
{
	Son s;

	cout <<"sizeof Son  = "<<sizeof(Son)<<endl;
	//父类中有同名成员出现,需要加作用域区分
	cout << "Base1-m_A = " <<s.Base1::m_A <<endl;
	cout << "Base2-m_A = " <<s.Base2::m_A <<endl;
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

4.6.8 菱形继承

菱形继承概念:

两个派生类继承同一个基类

又有某个类同时继承这两个派生类

这种继承被称为菱形继承,或者钻石继承

示例:

#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();

	system ("pause");

	return 0;
}

总结:

菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及含无意义

利用虚继承可以解决菱形继承问题

4.7 多态

4.7.1 多态的基本概念

多态是C++面向对象三大特征之一

多态分为两类:

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

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

静态多态和动态多态的区别:

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

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

示例:

#include <iostream>
using namespace std;

//多态的基本概念
//动物类
class Animal 
{
public:
	//虚函数
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

//猫类
class Cat :public Animal 
{
public:
	//函数重写:函数返回值类型 函数名 参数列表 要完全相同
	virtual void speak() //virtual可写可不写
	{
		cout << "小猫在说话" << endl;
	}
};

//狗类
class Dog :public Animal 
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

//执行说话的函数
//地址早绑定 在编译阶段就确定了函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定

//动态多态满足条件:
//1. 得有继承关系
//2. 子类要重写父类中的虚函数

//动态多态的使用:
//父类的指针或者引用 指向子类对象

void doSpeak(Animal  &animal ) //Animal &animal = cat;
{
	animal. speak();
}

void tset01()
{
	Cat cat;
	doSpeak (cat);

	Dog dog;
	doSpeak (dog);
}

int main()
{
	tset01();

	system ("pause");

	return 0;
}

总结:
动态多态满足条件:
1. 得有继承关系
2. 子类要重写父类中的虚函数

动态多态的使用条件:
父类的指针或者引用 指向子类对象

函数重写:函数返回值类型 函数名 参数列表 要完全相同

4.7.2 多态案例---计算器类

案例描述:

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

多态的优点:

1. 代码组织结构清晰

2. 可读性强

3. 利于前期和后期的扩展以及维护

示例:

#include <iostream>
using namespace std;
#include <string>

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

//普通写法:
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 ;
		}
		//如果想扩展新的功能,需要修改源码
		//在真实开发中,提倡 开闭原则
		// 开闭原则:对扩展进行开放,对修改进行关闭
	}

	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;
}

//利用多态实现计算器类

//多态的优点:
//1. 代码组织结构清晰
//2. 可读性强
//3. 利于前期和后期的扩展以及维护

//实现计算器抽象类
class AbstractCalculator
{
public:
	virtual int getResult() //虚函数
	{
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//加法计算器类
class AddCalculator : public AbstractCalculator 
{
public:
	int getResult ()
	{
		return m_Num1 + m_Num2 ;
	}
};

//减法计算器类
class SubCalculator : public AbstractCalculator 
{
public:
	int getResult ()
	{
		return m_Num1 - m_Num2 ;
	}
};

//乘法计算器类
class MulCalculator : public AbstractCalculator 
{
public:
	int getResult ()
	{
		return m_Num1 * m_Num2 ;
	}
};

void test02()
{
	//动态多态的使用条件:父类的指针或者引用 指向子类对象
	//加法运算:
	AbstractCalculator *abc = new AddCalculator ; //new AddCalculator创建加法类对象
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;

	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	//用完后记得销毁
	delete abc;

	//减法运算
	abc = new SubCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;

	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	//用完后记得销毁
	delete abc;

	//乘法运算
	abc = new MulCalculator ;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;

	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	//用完后记得销毁
	delete abc;
}

int main()
{
	test01();
	test02();

	system ("pause");

	return 0;
}

4.7.3 纯虚函数和抽象类

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

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

纯虚函数语法:virtual  返回值类型  函数名  (参数列表)= 0;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

1. 无法实例化对象

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

示例:

#include <iostream>
using namespace std;

//纯虚函数和抽象类
class Base
{
public:
	virtual void func() = 0; //纯虚函数
	//类中只要有一个纯虚函数,这个类就称为抽象类
	//抽象类的特点:
	//1.无法实例化对象
	//2.抽象类的子类必须重写父类中的纯虚函数,否则也属于抽象类
};

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();

	system ("pause");

	return 0;
}

4.7.4 多态案例二---制作饮品

案例描述:

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

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

 示例:

#include <iostream>
using namespace std;

//多态案例二---制作饮品
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
//{
//	abs->makeDrink ();
//	delete abs;  //释放
//}

void test01()
{
	制作咖啡
	//doWork (new Coffee);

	//cout <<"-------------"<<endl;

	制作茶
	//doWork (new Tea);
	AbstractDrinking * abs = new Coffee;
	abs->makeDrink ();
	cout <<"-------------"<<endl;
	abs = new Tea;
	abs->makeDrink (); //88-93行和97-103行与104-108行实现功能相同

}

int main()
{
	test01();

	system ("pause");

	return 0;
}

4.7.5 虚析构和纯虚析构

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

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

虚析构和纯虚析构共性

1. 可以解决父类指针释放子类对象

2. 都需要具体函数实现

虚析构和纯虚析构区别

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

虚析构语法:

virtual  ~类名() { }

纯虚析构语法:

virtual  ~类名()= 0;

类名 ::~类名(){ }

示例:

#include <iostream>
using namespace std;
#include <string>

//虚析构和纯虚析构
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)
	{
		cout << "Cat构造函数的调用" << endl;
		m_Name = new string (name);
	}

	virtual void speak()
	{
		cout << *m_Name << "小猫在说话" << endl;
	};

	~Cat()
	{
		if(m_Name != NULL)
		{
			cout << "Cat析构函数的调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
	string *m_Name;
};

void test01()
{
	Animal * animal = new Cat("Tom");
	animal ->speak ();
	//父类的指针在析构的时候,不会调用子类中的析构函数,导致子类如果有堆区属性,出现内存泄露情况
	delete animal ;
}

int main()
{
	test01();

	system ("pause");

	return 0;
}

总结:

1. 虚析构或纯虚析构就是用来解决父类指针释放子类对象

2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3. 拥有纯虚析构函数的类也属于抽象类

4.7.6 多态案例三----电脑组装

案例描述:

电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件。例如:Intel厂商和Lenovo厂商

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

示例:

#include <iostream>
using namespace std;

//多态案例三----电脑组装

//抽象出不同零件类
//抽象CPU类
class CPU
{
public :
	//抽象的计算函数
	virtual void Calculate() = 0; //纯虚函数
};

//抽象显卡类
class VideoCard
{
public :
	//抽象的显示函数
	virtual void Display() = 0; //纯虚函数
};

//抽象内存条类
class Memory
{
public :
	//抽象的存储函数
	virtual void storage() = 0; //纯虚函数
};

//电脑类
class Computer
{
public:
	Computer (CPU * cpu,VideoCard * vc,Memory * mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//提供工作的函数
	void Work()
	{
		//让零件工作起来,调用具体的实现接口
		m_cpu ->Calculate ();
		m_vc ->Display ();
		m_mem ->storage ();
	}

	//提供析构函数 释放3个电脑零件
	~Computer()
	{
		//释放CPU零件
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}

		//释放显卡零件
		if (m_vc != NULL)
		{
			delete m_cpu;
			m_vc = NULL;
		}

		//释放内存条零件
		if (m_mem != NULL)
		{
			delete m_cpu;
			m_mem = NULL;
		}
	}
private:
	CPU * m_cpu; //CPU的零件指针
	VideoCard * m_vc; //显卡的零件指针
	Memory * m_mem; //内存条的零件指针

};

//具体厂商
//Inter厂商
class InterCPU :public CPU
{
public :
	virtual void Calculate ()
	{
		cout << "Inter的CPU开始计算了"<<endl;
	}
};

class InterVideoCard :public VideoCard
{
public :
	virtual void Display  ()
	{
		cout << "Inter的显卡开始显示了"<<endl;
	}
};

class InterMemory :public Memory
{
public :
	virtual void storage   ()
	{
		cout << "Inter的内存条开始存储了"<<endl;
	}
};

//Lenovo厂商
class LenovoCPU :public CPU
{
public :
	virtual void Calculate ()
	{
		cout << "Lenovo的CPU开始计算了"<<endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public :
	virtual void Display  ()
	{
		cout << "Lenovo的显卡开始显示了"<<endl;
	}
};

class LenovoMemory :public Memory
{
public :
	virtual void storage   ()
	{
		cout << "Lenovo的内存条开始存储了"<<endl;
	}
};

//组装不同的电脑
void test01()
{
	//第一台电脑零件
	CPU * interCPU = new InterCPU ;
	VideoCard * interCard = new InterVideoCard ;
	Memory * interMem = new InterMemory ;

	cout << "第一台电脑开始工作:"<< endl;
	//创建第一台电脑
	Computer * computer1 = new Computer (interCPU,interCard,interMem );
	computer1 ->Work ();
	delete computer1 ;
	
	cout <<"----------------------"<<endl;
	cout << "第二台电脑开始工作:"<< endl;
	//创建第二台电脑
	Computer * computer2 = new Computer (new LenovoCPU ,new LenovoVideoCard,new LenovoMemory );
	computer2 ->Work ();
	delete computer2 ;

	cout <<"----------------------"<<endl;
	cout << "第三台电脑开始工作:"<< endl;
	//创建第三台电脑
	Computer * computer3 = new Computer (new InterCPU ,new LenovoVideoCard,new LenovoMemory );
	computer3 ->Work ();
	delete computer3 ;
}
int main()
{
	test01();

	system ("pause");

	return 0;
}

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值