C++核心编程

目录

1.内存分区模型

1.1程序运行前

1.2程序运行后

1.3new操作符

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.2struct和class的区别

4.1.4封装的练习案例

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.3C++对象模型和this指针

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

4.3.2this指针

 4.3.3空指针访问成员函数

4.3.4const修饰成员函数

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多态案例2——制作饮品

4.7.5虚析构和纯虚析构

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

5 文件操作

5.1文本文件

5.5.1写文件

5.1.2读文件

5.2二进制文件

5.2.1写文件

5.2.2读文件



1.内存分区模型

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

  1. 代码区:存放函数体的二进制代码,由操作系统进行管理
  2. 全局区:存放全局变量静态变量以及常量
  3. 栈区:将编译器自动分配释放,存放函数值,局部变量等
  4. 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

 内存四区的意义:

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

1.1程序运行前

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

代码区:

  1. 存放cpu执行的机器指令(二进制代码)
  2. 代码区是共享的,共享的
  3. 目的是对于被频繁执行的程序,只需要在内存中有一份代码即可
  4. 代码区是只读的,使其只读的原因是防止程序意外地修改了他的指令

全局区:

  1. 全局变量静态变量存放在此
  2. 全局区还包含了常量区,字符串常量和其他常量(const 修饰的全局变量)也存放在此
  3. 该区域的数据在程序结束后由操作系统释放
#include <iostream>
using namespace std;
//全局变量
int g_a = 10;
int g_b = 10;

//const修饰的全局变量
const int c_g_a = 10;
int main(void)
{
	//全局区
	//全局变量 、静态变量、常量

	//创建普通局部变量
	int a = 10;
	int b = 10;
	cout <<"局部变量a的地址为:"<< &a << endl;
	cout << "局部变量a的地址为:"<<&b << endl;
	cout << "全局变量g_a 的地址为:" << &g_a << endl;
	cout << "全局变量g_b 的地址为:" << &g_b << endl;

	//静态变量   在普通变量前面加static,属于静态变量
	static int s_a = 10;
	static int s_b = 10;
	cout << "静态变量s_a 的地址为:" << &s_a << endl;
	cout << "静态变量s_b 的地址为:" << &s_b << endl;

	//常量  
	//字符串常量  双引号引起来的常量
	cout << "字符串常量的地址为:  "<<&"hello world "<<endl;
	//const修饰的变量
	//const 修饰的全局变量
	cout << "全局常量c_g_a的地址为: " << &c_g_a << endl;
	//const 修饰的局部变量
	const int c_l_a = 10;  //c-const、l-local、g-global
	const int c_l_b = 10;
	cout << "局部常量c_l_a的地址为: " << &c_l_a << endl;
	cout << "局部常量c_l_b的地址为: " << &c_l_b << endl;
	system("pause");
	return 0;
}

总结:

  1.  C++在程序运行前分为全局区和代码区
  2. 代码区特点是共享和只读
  3. 全局区中存放全局变量、静态变量、常量区
  4. 常量区中存放const修饰的全局变量和字符串常量

1.2程序运行后

栈区:

  1. 由编译器自动分配释放,存放函数的参数值、局部变量
  2. 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
#include<iostream>
using namespace std;

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

int *func(int b)         //形参数据也会放在栈区
{
	b = 10;
	int  a = 10;// 局部变量 存放在栈区,栈区的数据在函数执行完后自动释放
	return &a; //返回局部变量的地址
}
int main(void)
{
	int* p = func(1);//接受func函数的返回值
	cout << *p << endl;       //第一次可以打印正确的数字是因为编译器做了保留
	cout << *p << endl;     //第二次这个数据就不再保留了
	system("pause");
	return 0;
}

 

堆区:

  1. 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
  2. 在c++中主要利用new在堆区开辟内存
#include <iostream>
using namespace std;
int * func()
{
	//利用new关键字  可以将数据开辟到堆区
	//指针 本质也是局部变量,放在栈上,指针保存的数据放在了堆区
	int *p = new int(10);
	return p;
}
int main(void)
{

	//在堆区中开辟数据
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;
	cout << *p << endl;
	system("pause");
	return 0;
}

1.3new操作符

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

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

 语法:new 数据类型

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

#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;
	cout << *p << endl;

	//堆区的数据由程序员管理开辟,由程序员管理释放
	//如果想释放堆区得数据,利用关键字delete
	delete p;
	//cout << *p << endl; //报错  内存已经被释放,再次访问就是非法操作
}
//2.在堆区利用new开辟数组
void test02()
{
	//创建10整数型数据的数组,在堆区
	int *arr = new int[10];   //10代表数组有10个元素
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	//释放堆区数组
	//释放数组的时候 要加[]才可以
	delete[] arr;

}
int main(void)
{

	test01();
	test02();
	system("pause");
	return 0;
}

2.引用

2.1引用的基本使用

作用:给变量起别名

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

#include <iostream>
using namespace std;

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

	int a = 10;
	//创建引用
	int& b = a;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 100;
	cout << "修改b后a = " << a << endl;
	cout << "修改b后b = " << b << endl;
	system("pause");
	return 0;
}

 

2.2引用注意事项

  1. 引用必须初始化
  2. 引用在初始化后不可以改变

#include <iostream>
using namespace std;

int main(void)
{
	int a = 10;
	//1.引用必须初始化
	//int& b;//错误代码,必须初始化
	int& b = a;
	//2.引用在初始化后,不可以改变
	int c = 20;

	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;
}
// 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(void)
{
	int a = 10;
	int b = 20;

	mySwap01(a, b); //值传递,形参不会改变实参
	cout << "值传递:"<<endl;
	cout << "a = " << a << endl;
	cout << "b = " << b <<endl;

	mySwap02(&a, &b); //地址传递,形参可以修饰实参
	cout << "地址传递:"<<endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	a = 10;
	b = 20;
	mySwap03(a, b);       //引用传递,形参也可以修饰实参
	cout << "引用传递:" << endl;
	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(void)
{
	int &ref1 = test01();
	cout << "ref1 = " << ref1 << endl;   //第一次结果正确,是因为编译器做了保留
	cout << "ref1 = " << ref1 << endl;   //错误 乱码 因为a的内存已经释放
	cout << "ref1 = " << ref1 << endl;

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

	test02() = 1000;  //相当于a = 1000//如果函数的返回值是引用,这个函数调用可以作为左值
	cout << "ref2 = " << ref2 << endl;    //ref2是a的别名
	cout << "ref2 = " << ref2 << endl;
		
	system("pause");
	return 0;
}

2.5引用的本质

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

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

2.6常量引用

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

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

#include <iostream>
using namespace std;

//打印数据
void showvalue(const int& val)
{
	//val = 1000;//加const后不可以修改,防止误操作
	cout << "val = " << val << endl;
}
int main(void)
{
	//常量引用
	//使用场景:用来修饰形参,防止误操作
	int a = 100;
	//int& ref = 10;      //引用必须引一块合法的内存空间

	//加上const 之后,编译器将代码修改为int temp = 10;int &ref = temp;
	//const int& ref = 10;
	//ref = 20;        //加入const之后变为只读,不可以修改
	showvalue(a);
	cout << "a  =" << a << endl;
	system("pause");
	return 0;
}

3.函数提高

3.1函数默认参数

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

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

#include <iostream>
using namespace std;

//函数的默认参数
//如果我们自己传入了数据,就用传入的数据。如果没有,就用默认值
//语法:返回值类型 函数名(参数 = 默认值){}

int func(int a, int b = 20, int c = 30)
{
	return a + b + c;
}

//注意事项:
//1.如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
//int func2(int a = 10, int b, int c, int d)     //错误❌
//{
//	return a + b + c;                     
//}

//2.如果函数声明有默认参数,函数实现就不能有默认参数。声明和实现只能有一个有默认参数
//int func2(int a = 10, int b = 10);       //错误❌
//int func2(int a =10, int b = 10)
//{
//	return a + b;
//}
int main(void)
{
	cout << func(10,30) << endl;

	system("pause");
	return 0;
}

3.2函数占位参数

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

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

在现阶段函数的占位参数意义不大,但是后面的课程中会用到该技术

#include <iostream>
using namespace std;

//占位参数
// 返回值类型 函数名(数据类型){}
//目前阶段的占位参数 我们还用不到 后面课程中会用到
//占位参数还可以有默认参数
void func(int a,int = 10)
{
	cout << "this is func" << endl;
}
int main(void)
{
	func(10);

	system("pause");
	return 0;
}

3.3函数重载

3.3.1函数重载概述

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

函数重载满足条件:

  1. 同一个作用域下
  2. 函数名称相同
  3. 函数参数类型不同或者个数不同或者顺序不同
#include <iostream>
using namespace std;

//函数重载
//可以让函数名相同,提高复用性

//函数重载的满足条件
//1.同一个作用域下
//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 main(void)
{
	func();
	func(10);
	func(3.14);
	func(10, 3.14);
	func(3.14, 10);
	system("pause");
	return 0;
}

3.3.2函数重载注意事项

  1. 引用作为重载条件
  2. 函数重载碰到函数默认参数
#include <iostream>
using namespace std;

//函数重载的注意事项
//1.引用作为重载的条件
void func(int& a)  //int &a  = 10;不合法
{
	cout << "func(int &a)的调用" << endl;
}
void func(const int& a) //const int &a = 10;合法在编译器相当于int temp= 10;int &a = temp
{
	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(void)
{
	int a = 10;
	func(a);    // 调用的是没有加const的

	func(20);       //调用的是加const的

	func2(10);         //报错 函数重载调用不明确,出现二义性,尽量避免这种情况
	system("pause");
	return 0;
}

4.类和对象

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

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

例如:

  1. 人可以作为对象,属性有姓名、年龄、身高、体重...行为有走,跑,跳,吃饭......
  2. 车也可以作为对象,属性有轮胎、方向盘、车灯.....行为有载人、放音乐......

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

4.1封装

4.1.1封装的意义

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

封装的意义:

  1. 将属性和行为作为一个整体,表现生活中的事物
  2. 将属性和行为加以权限控制

封装的意义1:

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

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

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

#include <iostream>
using namespace std;

//圆周率 
const double PI = 3.14;

//设计一个圆类,求圆的周长
//园求周长的公式 :2*Π*半径
class Circle
{
	//访问权限
public:         //公共权限
	//属性
	//半径
	int m_r; 
	//行为
	//获取圆的周长
	double calculateZC()
	{
		return 2 * PI * m_r;
	}

};


int main(void)
{
	//通过圆类 创建具体的圆(对象)
	//实例化(通过一个类 创建一个对象的过程)
	Circle c1;
	//给圆对象的属性进行赋值
	c1.m_r = 10;

	cout << "圆的周长为:" << c1.calculateZC() << endl;
	system("pause");
	return 0;
}

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

#include <iostream>
using namespace std;
//设计一个学生类,属性有姓名和学号,
//可以给姓名和学号赋值,可以显示学生的姓名和学号

//设计学生类
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(void)
{
	//创建一个具体对象  实例化对象
	student s1;
	//给s1对象 进行属性赋值操作
	s1.m_name = "张三";
	s1.m_ID = 1;
	s1.showStudent();

	student s2;
	s2.m_name = "李四";
	s2.m_ID = 2;
	s2.showStudent();

	student s3;
	s3.setname("王五");
	s3.m_ID = 20;
	s3.showStudent();
	
	student s4;
	s4.setname("赵六");
	s4.setID (33);
	s4.showStudent();


	system("pause");
	return 0;
}

封装意义2:

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

访问权限有三种:

  1. public             公共权限
  2. protected        保护权限
  3. private            私有权限
#include <iostream>
using namespace std;
//访问权限
//公共权限                     成员 类内可以访问  类外可以访问
//保护权限                     成员 类内可以访问  类外不可以访问    儿子也可以访问父亲中的保护内容
//私有权限                     成员 类内可以访问  类外不可以访问    儿子不可以访问父亲的私有内容
class Person
{
	//公共权限
public:
	string m_Name;    //姓名
protected:
	//保护权限
	string m_car;        
private:
	//私有权限
	int m_password;

public:
void func()
{
	m_Name = "张三";
	m_car = "拖拉机";
	m_password = 123456;
	cout << m_Name << m_car << m_password << endl;
}
};


int main(void)
{
	Person p1;
	p1.m_Name = "李四";
	//p1.m_car = "奔驰";       //保护权限的车在类外访问不到
	//p1.m_password = 123;      //私有权限内容类外访问不到
	p1.func();
	system("pause");
	return 0;
}

4.1.2struct和class的区别

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

区别:

  1. struct默认权限为公共
  2. class默认权限为私有
#include <iostream>
using namespace std;

class C1
{
	int m_A;       //默认权限是私有
};
struct C2
{
	int m_A;      //默认权限是公共
};
int main(void)
{
	//struct和class的区别
	//struct 默认权限是公共  public
	//class  默认权限是私有  private
	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;

//成员属性设置为私有
//1.将所有成员属性设置为私有,可以自己控制读写权限
//2.对于写权限,我们可以检测数据的有效性

class Person
{
public:
	//设置姓名
	void setName(string name)
	{
		m_Name = name;
	}
	//获取姓名
	string getName()
	{
		return m_Name;
	}

	//获取年龄         可读可写 如果想修改(年龄的范围必须是0-150之间)
	int get_Age()
	{
		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(void)
{
	Person p;
	p.setName("张三");
	cout << "姓名为: " << p.getName() << endl;
	p.setAge(1000);
	cout << "年龄为: " << p.get_Age() << endl;

	//设置情人为李女士
	p.setLover("李女士");
	system("pause");
	return 0;
}

4.1.4封装的练习案例

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

求出立方体的面积和体积

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

#include <iostream>
using namespace std;

//立方体类设计案例
//创建一个立方体类
//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 caculateS()
	{
		return 2 * m_L * m_W + 2 * m_L * m_H + 2 * m_W * m_H;
	}

	//获取立方体的体积
	int caculateV()
	{
		return m_H * m_L * m_W;
	}

	//利用成员函数判断两个立方体是否相等
	bool isSameByClass(Cube &c)
	{
		if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH())
		{
			return true;
		}
		else
			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;
	}
	else
		return false;
}
int main(void)
{
	Cube c1;
	c1.setL(10);
	c1.setW(10);
	c1.setH(10);

	cout << "c1的面积为:" << c1.caculateS() << endl;
	cout << "c1的体积为:" << c1.caculateV() << endl;

	//创建第二个立方体
	Cube c2;
	c2.setL(10);
	c2.setW(10);
	c2.setH(10);

	bool ret = isSame(c1, c2);
	if (ret)
	{
		cout << "c1和c2相等" << endl;
	}
	else
		cout << "c1和c2不相等"<<endl;

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

	system("pause");
	return 0;
}

练习案例2:点和圆的关系、程序的分文件编写

设计一个圆类,和一个点类,计算点和圆的关系。

 circle.h

#pragma once
#include <iostream>
using namespace std;
#include "point.h"
//圆类
class Circle
{
public:
	//设置半径
	void setR(int r);
	//获取半径
	int getR();
	//设置圆心
	void setCenter(Point center);
	//获取圆心
	Point getCenter();
private:
	int m_R;          //半径
	Point m_Center;    //圆心

};

point.h

#pragma once
#include <iostream>
using namespace std;

//点类
class Point
{
public:
	//设置X
	void setX(int x);
	//获取X
	int getX();
	//设置Y
	void setY(int y);
	//获取Y
	int getY();
private:
	int m_X;
	int m_Y;
};

circle.cpp

#include "circle.h"
//圆类
//设置半径
void Circle::setR(int r)
{
	m_R = r;
}
//获取半径
int Circle::getR()
{
	return m_R;
}
//设置圆心
void Circle::setCenter(Point center)
{
	m_Center = center;
}
//获取圆心
Point Circle::getCenter()
{
	return m_Center;
}

point.cpp

#include "point.h"
//点类

//设置X
void Point::setX(int x)
{
	m_X = x;
}
//获取X
int Point::getX()
{
	return m_X;
}
//设置Y
void Point::setY(int y)
{
	m_Y = y;
}
//获取Y
int Point::getY()
{
	return m_Y;
}

主文件

#include <iostream>
using namespace std;
#include "circle.h"
#include "point.h"
//点和圆的关系案例
点类
//class Point
//{
//public:
//	//设置X
//	void setX(int x)
//	{
//		m_X = x;
//	}
//	//获取X
//	int getX()
//	{
//		return m_X;
//	}
//	//设置Y
//	void setY(int y)
//	{
//		m_Y = y;
//	}
//	//获取Y
//	int getY()
//	{
//		return m_Y;
//	}
//private:
//	int m_X;
//	int m_Y;
//};
圆类
//class Circle
//{
//public:
//	//设置半径
//	void setR(int r)
//	{
//		m_R = r;
//	}
//	//获取半径
//	int getR()
//	{
//		return m_R;
//	}
//	//设置圆心
//	void setCenter(Point center)
//	{
//		m_Center = center;
//	}
//	//获取圆心
//	Point getCenter()
//	{
//		return m_Center;
//	}
//private:
//	int m_R;          //半径
//	Point m_Center;    //圆心
//
//};

//判断点和圆关系
void isInCircle(Circle &c, Point &p)
{
	//计算两点之间距离平方
	int distence = (c.getCenter().getX() - p.getX())* (c.getCenter().getX() - p.getX()) 
		+ (c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
	//计算半径的平方
	int r_distence = pow(c.getR() , 2);
	//判断关系
	if (distence == r_distence)
	{
		cout << "点在圆上" << endl;
	}
	else if (distence > r_distence)
	{
		cout << "点在圆外" << endl;
	}
	else
	{
		cout << "点在圆内" << endl;
	}
}

int main(void)
{
	//创建圆
	Circle c;
	c.setR(10);
	Point center;
	center.setX(10);
	center.setY(0);
	c.setCenter(center);
	//创建点
	Point p;
	p.setX (10);
	p.setY (10);
	//判断关系
	isInCircle(c, p);

	system("pause");
	return 0;
}

4.2对象的初始化和清理

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

4.2.1构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题。一个对象或者变量没有初始状态,对其使用后果是未知。同样的,使用完一个对象或变量,没有及时清理,也会一定在成安全问题。

  c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象初始化和清理工作是编译器强制要求我们做的事情,因此如果我们不提供构造和析构,编译器会提供。

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

  1. 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
  2. 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

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

  1. 构造函数没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次

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

  1. 析构函数,没有返回值,也不写void
  2. 函数名称与类名相同,在名称前加上符号~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次。
#include <iostream>
using namespace std;

//对象的初始化和清理
class person 
{
public:
	//1 构造函数
	//没有返回值 不用写void
	//函数名 与类名相同
	// 构造函数可以有参数,可以发生重载
	//创建对象的时候,构造函数会自动调用,而且只调用一次
	person()
	{
		cout << "Person构造函数的调用" << endl;
	}

	//2.析构函数 进行清理的操作
	//没有返回值 不写void
	//函数名与类名相同  在名称前加~
	//析构函数不可以有参数 不可以发生重载
	//对象在销毁前会自动调用析构函数,而且只调用一次
	~person()
	{
		cout << "person的析构函数调用" << endl;
	}

};
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
	person p;        //在栈上的数据,test01执行完后,释放这个对象
}

int main(void)
{
	//test01();
	person p;         //只出现构造函数的调用,是因为析构函数在对象销毁前在会调用,也就是system("pause")之后才会调用

	system("pause");
	return 0;
}

4.2.2构造函数的分类和调用

两种分类方式:

  1. 按参数分为:有参构造和无参构造
  2. 按类型分为:普通构造和拷贝构造

三种调用方式:

  1. 括号法
  2. 显示法
  3. 隐式转换法

匿名对象的特点

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

	Person(10);//匿名对象   特点:当前行执行结束后,系统会立即回收掉匿名对象
	cout << "aaaaaa" << endl; 
	//3.隐式转换法

aaaaa在析构函数后打印,说明匿名对象执行结束后,系统会立即回收掉匿名对象

#include <iostream>
using namespace std;
//1.构造函数的分类和调用
//分类
//按照参数分类 无参构造(默认构造)和有参构造
//按照类型分类 普通构造 拷贝构造
class Person
{
public:
	//构造函数
	Person()
	{
		cout << "Person的无参构造函数调用" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "Person的有参构造函数调用" << endl;
	}
	//拷贝构造函数
	Person(const Person &p)
	{
		//将传入的人身上的所有属性,拷贝到我身上
		cout << "Person拷贝构造函数的调用" << endl;
		age = p.age;
	}
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}
	int age;
};
//调用
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 << "aaaaaa" << endl; 
	//注意事项2:
	//不要利用拷贝构造函数  初始化匿名对象
	//Person(p3);        //编译器会认为Person(p3)==Person p3;   对象的声明
	//3.隐式转换法
	Person p4 = 10;    //相当于写了Person p4 = Person(10)    有参构造
	Person p5 = p4;    //拷贝构造
}
int main(void)
{
	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;
	return p1;
}
void test03()
{
	Person p = doWork2();
}

int main(void)
{
	//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.创建一个类,编译器会给每个类至少添加3个函数
//默认构造(空实现)
//析构函数(空实现)
//拷贝构造(值拷贝)
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 test01()
{
	Person p;
	p.m_age = 18;

	Person p2(p);
	cout << "p2的年龄为:" << p2.m_age << endl;
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

结果里没有”拷贝构造函数的调用“,是因为我们注释掉了自己的拷贝构造函数,编译器默认的拷贝构造函数不会有这句。但是结果显示“p2的年龄为:18”是因为编译器默认的拷贝构造函数会有m_age = age;这行代码。

如果用户定义有参构造函数,C++不再提供默认无参构造

 如图,注释掉默认构造后,调用默认构造会报错,是因为定义了有参构造函数,C++不再提供默认无参构造。

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

注释掉拷贝函数后,编译器依旧提供默认拷贝构造m_age = p.m_age;

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

#include<iostream>
using namespace std;
//构造函数的调用规则
//1.创建一个类,编译器会给每个类至少添加3个函数
//默认构造(空实现)
//析构函数(空实现)
//拷贝构造(值拷贝)

//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 test01()
//{
//	Person p;
//	p.m_age = 18;
//
//	Person p2(p);
//	cout << "p2的年龄为:" << p2.m_age << endl;
//}
void test02()
{
	Person p(28);
	Person p2(p);
	cout << "p2的年龄为:" << p2.m_age << endl;
}
int main(void)
{
	//test01();
	test02();
	system("pause");
	return 0;
}

4.2.5深拷贝与浅拷贝

深浅拷贝是面试经典问题,也是一个常见的坑。

浅拷贝:简单的复制拷贝操作(编译器提供的更好赋值操作m_height = p.m_height;)

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

 析构代码的用途:将堆区开辟的数据做释放操作。

~Person()
	{
		//析构代码,将堆区开辟数据做释放操作
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
		cout << "Person的析构函数调用" << endl;
	}
#include  <iostream>
using namespace std;
//深拷贝与浅拷贝

class Person
{
public:
	Person()
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age,int height)
	{
		cout << "Person的有参构造函数调用" << endl;
		m_age = age;
		m_height = new int(height);
	}

	//自己实现拷贝构造函数 解决浅拷贝带来的问题
	Person(const Person &p)
	{
		cout << "Person的拷贝构造函数的调用" << endl;
		m_age = p.m_age;
		//m_height = p.m_height; 编译器默认实现就是这行代码

		//深拷贝操作
		m_height = new int(*p.m_height);
	}
	~Person()
	{
		//析构代码,将堆区开辟数据做释放操作
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
		cout << "Person的析构函数调用" << endl;
	}

	int m_age;
	int *m_height;  //身高
};
void test01()
{
	Person p1(18,160);
	cout << "p1的年龄为:" << p1.m_age << "  身高为:" << *p1.m_height<< endl;
	Person p2(p1);
	cout << "p2的年龄为:" << p2.m_age << "  身高为:" << *p2.m_height<< endl;
}

int main(void)
{
	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(void)
{
	test01();

	system("pause");
	return 0;
}

4.2.7类对象作为类成员

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

例如

class A{}

class B

{

A a;

}

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

那么当创建B对象时,A与B的构造和析构顺序谁先谁后?A先构造,B后构造。B先析构,A后析构。

#include <iostream>
using namespace std;

//类对象作为类成员

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

};

//人类
class Person
{
public:
	Person(string name, string PName):m_Name(name),m_Phone(PName)
	{
		cout << "Person的构造函数调用" << endl;
	}
	~Person()
	{
		cout << "Preson的析构函数调用" << endl;
	}
	//姓名
	string m_Name;
	//手机
	Phone m_Phone;
};
//当其他类的对象作为本类的成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
void test01()
{
	Person p("张三","苹果MAX");
	cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl;
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

 

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

4.2.8静态成员

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

静态成员分为:

静态成员变量

  1. 所有对象共享一份数据
  2. 在编译阶段分配内存
  3. 类内声明,类外初始化

静态成员函数

  1. 所有对象共享同一个函数
  2. 静态成员函数只能访问静态成员变量
#include <iostream>
using namespace std;

//静态成员函数
 //所有的对象共享同一个函数
 //静态成员函数只能访问静态成员变量
class Person
{
public:
	static void func()  //静态成员函数
	{
		m_A = 100;                //静态成员函数可以访问静态成员变量
		//m_B = 200;            //报错,因为静态成员函数不可以访问非静态成员变量 ,无法区分到底是哪个对象的                                                                                                                                                                                                      
		cout << "ststic void func的调用 " << endl;
	}
	static int m_A;   //静态成员变量
	int m_B;           //非静态成员变量

	//静态成员函数也是有访问权限的   类外访问不到私有的静态成员函数
};
int Person::m_A = 0;               //静态成员变量必须类内声明,类外初始化
//有两种的访问方式
void test01()
{
	//1.通过对象访问
	Person p;
	p.func();

	//2.通过类名访问
	Person::func();            //所有的对象共享同一个函数
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

4.3C++对象模型和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 << "size of p = " << sizeof(p) << endl;
}

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

	test01();
	test02();

	system("pause");
	return 0;
}

4.3.2this指针

通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的

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

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

C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。

this指针是隐含每一个非静态成员函数内的一种指针

this指针不需要定义,直接使用即可

this指针的用途:

  1. 当形参和成员变量同名时,可用this指针来区分
  2. 在类的非静态成员函数中返回对象本身,可使用return *this
#include <iostream>
using namespace std;
class Person
{
public:
	Person(int age)
	{
		//this指针指向 被调用的成员函数 所属的对象。
		this->age = age;  //this指向p1;
	}
	Person& PersonAddage(Person& p)//如果返回的是值而不是引用的话,结果是20,因为如果返回值,则p3.PersonAddage(p2).
		                            //	返回的不是p2,而是按照本体创建的一个新的值,因为调用了拷贝构造函数
		                    //拷贝构造函数的调用时机就是用值的方式返回,会复制一份新的数据
	{
		this->age += p.age;  
		//this指向p2的指针,而*this指向的就是p2这个对象的本体
		return *this;
	}
	int age;
};
//1.解决名称冲突
void test01()
{
	Person p1(18);
	cout << "P1的年龄为:" << p1.age<< endl;
}
//2.返回对象本身用*this
void  test02()
{
	Person p2(10);
	Person p3(10);

	//链式编程思想
	p3.PersonAddage(p2).PersonAddage(p2).PersonAddage(p2);
	cout << "p3的年龄为:" << p3.age << endl;
}
int main(void)
{
	test01();
	test02();

	system("pause");
	return 0;
}

 4.3.3空指针访问成员函数

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

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

#include <iostream>
using namespace std;

//空指针调用成员函数
class Person
{
public:
	void showClassName()
	{
		cout << "this is Person Class" << endl;
	}
	void showPersonAge()
	{
		//报错原因是因为传入的指针为空
		//解决方法是加入下面两行:
		if (this == NULL)
			return;
		cout << "age = " << this->m_age << endl;///属性的前面都默认加了this指针,即this->age;
	}
	int m_age;
};
void test01()
{
	Person* p = NULL;
	p->showClassName();
	//p->showPersonAge();     //报错

}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

4.3.4const修饰成员函数

常函数:

  1. 成员函数加const之后,我们称这个函数为常函数
  2. 常函数不可以修改成员属性
  3. 成员属性声明时加mutable后,在常函数中依然可以修改

常对象:

  1. 声明对象前加const称该对象为常对象
  2. 常对象只能调用常函数
#include <iostream>
using namespace std;
//常函数

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

	}
	int m_A;
	mutable int m_B;      //特殊变量,即使在常函数中也可以修改这个值,需要加上关键字mutable
};
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(void)
{

	system("pause");
	return 0;
}

4.4友元

客厅(public)卧室(private)

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

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

友元的关键字 friend

友元的三种实现

  1. 全局函数做友元
  2. 类做友元
  3. 成员函数做友元

4.4.1全局函数做友元

#include <iostream>
using namespace std;

//建筑物类
class Building
{
	friend void goodguy(Building* building);//goodguy全局函数是Building的好朋友,可以访问Building中私有成员
public:
	Building()
	{
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}

public:
	string m_SittingRoom;    //客厅
private:
	string m_BedRoom;       //卧室
};
//全局函数
void goodguy(Building *building)
{
	cout << "好基友全局函数正在访问:" << building->m_SittingRoom << endl;
	cout << "好基友全局函数正在访问:" << building->m_BedRoom<< endl;
}
void test01()
{
	Building building;
	goodguy(&building);
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

4.4.2类做友元

#include <iostream>
using namespace std;
//类做友元
class Building
{
	friend class Goodguy;//GoodGuy是苯类的好朋友,可以访问苯类的私有成员
public:
	Building();
public:
	string m_SittingRoom;
private:
	string m_BedRoom;
};
class Goodguy
{
public:
	Goodguy();
	void visit();//参观函数访问Building中的属性
	Building* building;
};
//类外写成员函数
Building::Building()
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
Goodguy::Goodguy()
{
	//创建建筑物对象
	building = new Building;

}
void Goodguy::visit()
{
	cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
	cout << "好基友类正在访问:" << building->m_BedRoom<< endl;
}

void test01()
{
	Goodguy gg;
	gg.visit();
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

4.4.3成员函数做友元

#include <iostream>
using namespace std;
class Building;

class Goodguy               //Goodguy一定要写在Building前面
{
public:
	Goodguy();
	void visit();         //让visit函数可以访问Building中私有成员
	void visit1();       //让visit2中函数不可以访问Building中私有成员
	Building* building;
};

class Building
{
	friend void Goodguy::visit();//告诉编译器Googuy类下的visit成员函数作为本类的好朋友,可以访问私有成员
public:
	Building();
public:
	string m_SittingRoom;
private:
	string m_BedRoom;
};

//类外实现成员函数
Building::Building()
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
Goodguy::Goodguy()
{
	building = new Building;
}
void Goodguy::visit()
{
	cout << "visit函数正在访问:" << building->m_SittingRoom << endl;
	cout << "visit函数正在访问:" << building->m_BedRoom << endl;
}
void Goodguy::visit1()
{
	cout << "visit1函数正在访问:" << building->m_SittingRoom << endl;
}
void test01()
{
	Goodguy gg;
	gg.visit();
	gg.visit1();
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

4.5运算符重载

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

4.5.1加号运算符重载

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

//成员函数重载本质的调用 Person p3 = p1.operator+(p2)
 //全局函数重载的本质调用 Person p3 = operator(p1,p2) 

#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_A;
	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;
	//运算符重载也可以发生函数重载 函数名相同,传入的参数不同 就是重载
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;
}

int main(void)
{
	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<<(Person &p)
	{

	}*/
	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 << endl;
	return cout;
}

void test01()
{
	Person p(10, 10);
	
	cout << p << endl; //因为返回的是cout,所以才可以无限的往后追加
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:重载左移运算符配合友元可以实现输出自定义数据类型

4.5.3递增元素符重载

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

#include <iostream>
using namespace std;

//重载递增运算符
//自定义整型
class MyInterger
{
	friend ostream& operator<<(ostream& cout, MyInterger myint);
public:
	MyInterger()
	{
		m_Num = 0;
	}
//重载前置++运算符    返回引用是为了一直对一个数据进行递增操作
	MyInterger& operator++()
	{
		//先进行++运算
		m_Num++;
		//再将自身返回
		return *this;
	}

//重载后置++运算符
	//因为temp是局部变量 调用完就释放 所以返回引用进行后续操作是非法的
	MyInterger operator++(int)    //int代表占位参数  用于区分前置和后置递增
	{
		//先 返回结果(记录当时结果)
		MyInterger temp = *this;
		//后 递增
		m_Num++;
		//最后将记录结果返回
		return temp;
    }
private:
	int m_Num;
};
//重载左移运算符
ostream& operator<<(ostream& cout, MyInterger myint)
{
	cout << myint.m_Num;
	return cout;
}
void test01()
{
	MyInterger myint;
	cout << ++(++myint) << endl;
	cout << myint << endl;
}
void test02()
{
	MyInterger myint;
	cout << myint++ << endl;
	cout << myint << endl;
}
int main(void)
{
	test01();
	test02();

	system("pause");
	return 0;
}

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

练习:递减运算符

#include <iostream>
using namespace std;

/// <summary>
//重载递减运算符
//自定义整形
class MyInterger
{
	friend ostream& operator<<(ostream& cout, MyInterger myint);
public:
	MyInterger()
	{
		m_Num = 10;
	}
//重载前置--运算符
	MyInterger& operator--()
	{
		m_Num--;
		return *this;
	}
//重载后置--运算符
	MyInterger operator--(int)
	{
		MyInterger temp = *this;
		m_Num--;
		return temp;
	}
private:
	int m_Num;
};

ostream& operator<<(ostream& cout, MyInterger myint)
{
	cout << myint.m_Num;
	return cout;
}

void test01()
{
	MyInterger myint;
	cout << --(--myint )<< endl;
	cout << myint << endl;
}
void test02()
{
	MyInterger myint;
	cout << myint-- << endl;
	cout << myint << endl;
}
int main(void)
{
	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(60);
	Person p3(30);

	p3 = p2 = p1;     //赋值的操作
	cout << *p1.m_Age << endl;
	cout << *p2.m_Age << endl;
	cout << *p3.m_Age << endl;
}
int main(void)
{
	test01();

	system("pause");
	return 0;

}

 4.5.5关系运算符重载

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

#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;
		}
		else
			return false;
	}
	//重载!=号
	bool operator!=(Person& p)
	{
		if (this->m_Name != p.m_Name && this->m_Age != p.m_Age)
		{
			return true;
		}
		else
			return false;
	}
	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 << "p1和p2是不相等的!" << endl;
	}
	if (p1 != p2)
	{
		cout << "p1和p2是不相等的!" << endl;
	}
	else
	{
		cout << "p1和p2是相等的!" << endl;
	}
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

4.5.6函数调用运算符重载

  1. 函数调用运算符()也可以重载
  2. 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  3. 仿函数没有固定的写法,非常灵活
#include <iostream>
#include <string>
using namespace std;

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

void MyPrint02(string test)
{
	cout << test << endl;
}
void test01()
{
	MyPrint myprint;
	myprint("hello world!");              //由于使用起来非常类似于函数调用,因此称为仿函数
	MyPrint02("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) << endl;
}
int main(void)
{
	test01();
	test02();
	system("pause");
	return 0;
}

4.6.继承

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

4.6.1继承的基本语法

#include <iostream>
using namespace std;

//普通实现

//公共页面类
class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册.......(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图......(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++......(公共分类列表)" << endl;
	}
};
//继承的好处:减少重复代码
//语法:class子类:继承方式 父类
//子类也称为 派生类
//父类也称为 基类
//Java页面
class Java :public BasePage
{
public:
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};
//Python页面
class Python :public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class cpp :public BasePage
{
public:
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};
void test01()
{
	cout<< "Java下载视频的页面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:

继承的好处:可以减少重复的代码

class A:public B;

A类称为 子类或者 派生类

B类称为 父类或者 基类

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

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

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

4.6.2继承方式

继承的语法:class 子类:继承方式 父类

继承方式一共有三种:

  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 = 10;     //父类中的私有权限成员  子类中访问不到
	} 
};
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;  //在Son2中 m_A变为保护权限,因此类外访问不到
	//s1.m_B = 1000; //在Son2中 m_B变为保护权限,因此类外访问不到
}

//私有继承
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 s3;
	//s1.m_A = 1000;  //在Son3中 m_A变为私有成员,因此类外访问不到
	//s1.m_B = 1000; //在Son3中 m_B变为私有成员,因此类外访问不到
}
class GrandSon3 :public Son3
{
public:
	void func()
	{
		//m_A = 1000;     //到了Son3中,m_A变为私有,即使是儿子,也是访问不到
		//m_B = 1000;     //到了Son3中,m_B变为私有,即使是儿子,也是访问不到
	}
};
int main(void)
{

	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;
};
//cmd进入命令提示符工具
//跳转盘符 D:
//跳转文件路径 cd 具体路径下
//查看命名 dir
//cl /d1 reportSingleClassLayout类名 "文件名"    cl是小写字母L d1是数字1
void test01()
{
	//父类中所有非静态成员属性都会被子类继承下去
	//父类中私有成员属性 是被编译器隐藏 因此访问不到 但确实继承下去了
	cout << "sizeof(Son) = " << sizeof(Son) << endl;
}
int main(void)
{
	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(void)
{
	test01();

	system("pause");
	return 0;
}

总结: 继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

4.6.5继承同名成员处理方式

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

  1. 访问子类同名成员 直接访问即可
  2. 访问父类同名成员 需要加作用域
#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()重载函数的调用" << 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(void)
{
	test01();
	test02();
	system("pause");
	return 0;
}

总结:

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

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

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

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

  1. 访问子类同名成员  直接访问即可
  2. 访问父类同名成员  需要加作用域
#include <iostream>
using namespace std;
//继承中的同名静态成员处理方式
//静态成员 类内申明 类外定义
class Base
{
public:
	static int m_A;
	static void func()
	{
		cout << "Base_static void func()的调用" << endl;
	}
	static void func(int a)
	{
		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.通过对象访问
	Son s;
	s.func();
	s.Base::func();
	//2.通过类名访问
	Son::func();
	Son::Base::func();
	//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
	//如果想访问父类中被隐藏的同名成员,需要加作用域
	Son::Base::func(100);
}
int main(void)
{
	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(void)
{
	test01();

	system("pause");
	return 0;
}

4.6.8菱形继承

菱形继承的概念:

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

典型案例:

羊继承了动物类,驼继承了动物类,羊驼继承了羊类和驼类。

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,,这部分数据我们只需要一份即可。
#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 << endl;
	//这份数据我们知道 只用有一份就可以了,菱形继承导致数据有两份,资源浪费


}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:

  1. 菱形继承带来的主要问题是子类继承两份同样的数据,导致资源浪费以及毫无意义。
  2. 利用虚拟继承可以解决菱形继承问题

4.7多态

4.7.1多态的基本概念

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

多态分为两类:

  1. 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
  2. 动态多态:派生类 和 虚函数实现运行时多态

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

  1. 静态多态的函数地址早绑定——编译阶段确定函数地址
  2. 动态多态的函数的地址晚绑定——运行阶段确定函数地址
#include <iostream>
using namespace std;

//多态
//动物类
class Animal
{
public:
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数的调用了
	virtual void  speak()
	{
		cout << "动物在说话" << endl;
	}
};
//猫类
class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
//狗类
class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数对象
//如果想执行让猫说话,那么这个函数的地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定

//动态多态满足条件
//1.有继承关系
//2.子类要重写父类的虚函数 重写:函数返回值类型 函数名 参数列表 完全相同 子类virtual关键字可写可不写

//动态多态使用
//父类的指针或者引用 指向子类对象
void doSpeak(Animal& animal)   //Animal &animal = cat;
{
	animal.speak();
}
void test01()
{
	Cat cat;
	doSpeak(cat);
	Dog dog;
	doSpeak(dog);
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:

多态满足条件:

  1. 有继承关系
  2. 子类重写父类中的虚函数

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

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

多态原理剖析

4.7.2多态案例一——计算器类

案例描述:

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

多态的优点:

  1. 代码组织结构清晰
  2. 可读性强
  3. 利于前期和后期的扩展和维护
#include <iostream>
#include <string>
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;
		}
		//如果想扩展新的功能,需要修改源码
		//在真实的开发中,提倡一种原则——开闭原则
		//开闭原则:对扩展进行开发,对修改进行关闭
	}
	int m_num1;        //操作数1
	int m_num2;        //操作数2
};
//利用多态实现计算器
//多态好处:
//1.组织结构信息
//2.可读性强
//3.对于前期和后期的扩展以及维护方便

//实现计算器的抽象类
class AbstractCaculator
{
public:
	virtual int getResult()
	{
		return 0;
	}
	int m_Num1;
	int m_Num2;
};
//加法计算器类
class AddCaculator :public AbstractCaculator
{
public:
	virtual int getResult()
	{
		return m_Num1+m_Num2;
	}
};
//减法计算器类
class SubCaculator :public AbstractCaculator
{
public:
	virtual int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
//乘法计算器类
class MulCaculator :public AbstractCaculator
{
public:
	virtual 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;
}
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;

	//乘法运算
	abc = new MulCaculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
}
int main(void)
{
	test01();
	test02();

	system("pause");
	return 0;
}

总结:C++开发提倡利用多态设计程序架构 ,因为多态优点很多

4.7.3纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。

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

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

抽象类特点:

  1. 无法实例化对象
  2. 子类必须重写抽象类中纯虚函数,否则也属于抽象类
#include <iostream>
using namespace std;

//纯虚函数和抽象类
class Base
{
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类成为抽象类
	 //抽象类特点:
	//1.无法实例化对象
	//2.抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
	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(void)
{
	test01();

	system("pause");
	return 0;
}

4.7.4多态案例2——制作饮品

案例描述:

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

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

#include <iostream>
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(void)
{
	test01();

	system("pause");
	return 0;
}

4.7.5虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:

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

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

虚析构语法:

virtual ~类名(){  }

纯虚析构语法:

virtual ~类名() = 0;

类名::类名() {  }

#include <iostream>
#include <string>
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)
	{
		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(void)
{
	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();
	}
	//提供析构函数 释放三个电脑零件
	~Computer()
	{
		//释放cpu零件
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		//释放显卡零件
		if (m_vc != NULL)
		{
			delete m_vc;
			m_vc = NULL;
		}
		//释放内存条零件
		if (m_mem != NULL)
		{
			delete m_mem;
			m_mem = NULL;
		}
	}
private:
	CPU* m_cpu;      //cpu的零件指针
	VideoCard* m_vc; //显卡的零件指针
	Memory* m_mem;   //内存条零件的指针
};

//具体厂商
//Intel厂商
class IntelCPU:public CPU
{
public:
	virtual void calculate()
	{
		cout << "Inter的CPU开始计算了!" << endl;
	}
};
class IntelVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Inter的显卡开始显示了!" << endl;
	}
};
class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Inter的内存条开始存储了!" << endl;
	}
};

//联想厂商
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 * intelCpu = new IntelCPU;
	VideoCard* intelCard = new IntelVideoCard;
	Memory* intelMem = new IntelMemory;

	//创建第一台电脑
	cout << "第一台电脑:" << endl;
	Computer* computer1 = new Computer(intelCpu, intelCard, intelMem);
	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 LenovoCPU, new IntelVideoCard, new LenovoMemory);
	computer3->work();
	delete computer3;
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

5 文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。通过文件可以将数据持久化,C++中对文件操作需要包含头文件<fstream>

文件类型分为两种:

  1. 文本文件——文件以文本的ASCII码形式存储在计算机中
  2. 二进制文件——文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的三大类:

  1. ofstream:写操作
  2. ifstream:读操作
  3. fstream:读写操作

5.1文本文件

5.5.1写文件

写文件步骤如下:

  1. 包含头文件    #include <fstream>
  2. 创建流对象  ofstream ofs;
  3. 打开文件  ofs.open("文件路径",打开方式);
  4. 写数据    ofs<<"写入的数据";
  5. 关闭文件  ofs.close();

文件打开方式

打开方式解释
ios::in为读文件而打开文件
ios::out为写文件而打开文件
ios::ate初始位置:文件尾
ios::app追加方式写文件
ios::trunc如果文件存在先删除,再创建
ios::binary二进制方式

注意:文件打开方式可以配合使用,利用|操作符

例如:用二进制方式写文件 ios::binary|ios::out\

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

//文本文件 写文件
void test01()
{
	//1.包含头文件 fstream
	//2. 创建流对象
	ofstream ofs;
	//3.指定打开的方式
	ofs.open("test.txt", ios::out);
	//4.写内容
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;
	//5.关闭文件
	ofs.close();
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:

  1. 文件操作必须包含头文件fstream
  2. 读文件可以利用ofstream或者fstream类
  3. 打开文件时需要指定操作文件的路径,以及打开方式
  4. 利用<<可向文件中写数据
  5. 操作完毕要关闭文件

5.1.2读文件

读文件与写文件步骤相似,但是读取方式相对于比较多

读取文件步骤如下:

  1. 包含头文件 #include <fstream>
  2. 创建流对象 ifstream ifs;
  3. 打开文件并判断文件是否打开成功    ifs.open("文件路径",打开方式);
  4. 读数据 四种方式读取
  5. 关闭文件
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
//文本文件 读文件
void test01()
{
	//1.包含头文件 #include <fstream>
	//2.创建流对象 ifstream ifs;
	ifstream ifs;
	//3.打开文件并判断文件是否打开成功    ifs.open("文件路径",打开方式);
	ifs.open("test.txt", ios::in);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	//4.读数据 四种方式读取
	//第一种
	char buf1[1024] = {0};
	while (ifs >> buf1)
	{
		cout << buf1 << endl;
	}
	//第二种
	char buf2[1024] = { 0 };
	while (ifs.getline(buf2,1024))
	{
		cout << buf2 << endl;
	}
	//第三种
	string buf3;
	while (getline(ifs, buf3))
	{
		cout << buf3 << endl;
	}
	//第四种    不太推荐用
	char c;
	while ((c = ifs.get()) != EOF)  //EOF = End Of File
	{
		cout << c << endl;
	}
	//5.关闭文件
	ifs.close();
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:

  1. 读文件可以用ifstream,或者fstream类
  2. 利用is_open函数可以判断文件是否打开成功
  3. close关闭文件

5.2二进制文件

以二进制方式对文件进行读写操作  打开方式要指定为ios::binary

5.2.1写文件

二进制的方式写文件主要利用流对象盗用成员函数write

函数原型:ostream & write(const char*buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数。

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

//二进制文件 写文件
class Person
{
public:
	char m_Name[64];     //姓名
	int m_Age;           //年龄
};
void test01()
{
	ofstream ofs;
	ofs.open("Person.txt", ios::out | ios::binary);
	Person p = { "张三",18 };
	ofs.write((const char*)&p,sizeof(Person));
	ofs.close();
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:文件输出流对象可以通过write函数,以二进制方式写数据

ofs.write((const char*)&p,sizeof(Person));

5.2.2读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

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

//二进制文件的读文件
class Person
{
public:
	char m_Name[64];
	int m_Age;
};
//二进制文件 读文件
void test01()
{
	//1.包含头文件
	//2.创建流对象
	ifstream ifs;
	//3.打开文件 判断文件是否打开成功
	ifs.open("Person.txt", ios::in | ios::binary);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	//4.读文件
	Person p;
	ifs.read((char*)&p, sizeof(Person));
	cout << "姓名: " << p.m_Name << "  年龄:" << p.m_Age << endl;
	//5.关闭文件
	ifs.close();
}
int main(void)
{
	test01();

	system("pause");
	return 0;
}

总结:文件输入流对象 可以通过read函数,以二进制方式读数据

ifs.read((char*)&p, sizeof(Person));

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值