一:什么是对象,什么是类?

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

例如:

  人可以作为对象,属性有名称、年龄···,行为有走、跳、唱歌···。

具有相同性质的对象,抽象的称为类。

例如:

  人属于人类,车属于车类。

二:封装

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

意义:

  • 将属性和行为作为一个整体,表现生活中的事物。
  • 将属性和行为加以权限控制。
2.1 意义一

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

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

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

#include <iostream>
using namespace std;

double PI = 3.14;

//圆求周长的公式: PI * 2 * 半径
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;

	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()
{
	//创建一个具体的学生  实例化
	Student s1;

	//给学生属性进行赋值
	s1.m_name = "张三";
	s1.m_id = 212;

	//显示学生属性
	s1.Showstudent();

	Student s2;

	//给学生属性进行赋值
	s2.setname("李四");
	s2.setid(12);

	s2.Showstudent();

	return 0;

}

类中的属性和行为统一称为成员。

属性也被称为 成员属性 和 成员变量。

行为也被称为 成员函数 和 成员方法。

2.2 意义二

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

访问权限:

  1. public 公共权限    (类内可以访问,类外可以访问)
  2. protected 保护权限 (类内可以访问,类外不可以访问)
  3. private 私有权限    (类内可以访问,类外不可以访问)

例3:

#include <iostream>
using namespace std;

class People
{
	//公共权限
public:

	string m_name; // 姓名

	//保护权限
protected:

	string m_car; // 汽车

	//私有权限
private:

	int m_passward; // 密码

public:
	void func()
	{
		m_name = "张三";
		m_car = "宝马";
		m_passward = 1234;
	}

};

int main()
{
	//实例化对象
	People p1;

	p1.m_name = "李四";
	//p1.m_car = "奔驰"; //会报错,访问不了
	//p1.m_passward = 123;  //会报错,访问不了

	return 0;
}
2.3 struct和class的区别

   默认的访问权限不同。

struct默认权限为公共。

class默认权限为私有。

#include <iostream>
using namespace std;

struct student
{
	int m_id; // 默认公共权限
};

class people
{
	int m_name;  // 默认私有权限
};

int main()
{
	return 0;
}
2.4 成员属性设计为私有

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

    2.对于写权限,我们可以检测数据的有效性。

例4:

#include <iostream>
using namespace std;

// 1.自己控制读写权限
class people
{
public:
	//设置姓名
	void setname(string name)
	{
		m_name = name;
	}

	//获取姓名
	string showname()
	{
		return m_name;
	}

	//获取年龄
	int showage()
	{
		return m_age;
	}

	//设置偶像
	void setidol(string idol)
	{
		m_idol = idol;
	}

private:
	string m_name; // 可读可写

	int m_age=18;  // 只读

	string m_idol; // 只写
};

int main()
{
	people p1;

	p1.setname("张三");
	cout << "姓名:" << p1.showname() << endl;
	cout << "年龄:" << p1.showage() << endl;

	p1.setidol("李四");

	return 0;
}
#include <iostream>
using namespace std;

// 2.检测数据的有效性
class people
{
public:
	//设置姓名
	void setname(string name)
	{
		m_name = name;
	}

	//获取姓名
	string showname()
	{
		return m_name;
	}

	//设置年龄
	void setage(int age)
	{
		if (age < 0 || age > 150) //控制数据的有效性
		{
			cout << "输入年龄不符合条件" << endl;
			return;
		}

		m_age = age;
	}

	//获取年龄
	int showage()
	{
		return m_age;
	}

	//设置偶像
	void setidol(string idol)
	{
		m_idol = idol;
	}

private:
	string m_name; // 可读可写

	int m_age=18;  // 可读可写(年龄只能在0-150内)

	string m_idol; // 只写
};

int main()
{
	people p1;

	p1.setname("张三");
	cout << "姓名:" << p1.showname() << endl;
	cout << "年龄:" << p1.showage() << endl;

	p1.setidol("李四");

	p1.setage(130);

	cout << "年龄:" << p1.showage() << endl;

	return 0;
}

例5:设计立方体类,求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等。

#include <iostream>
using namespace std;

class Cube
{
public:

	//设置长
	void setL(int L)
	{
		m_L = L;
	}
	//获取长
	int getL()
	{
		return m_L;
	}

	//设置宽
	void setW(int W)
	{
		m_W = W;
	}
	//获取宽
	int getW()
	{
		return m_W;
	}

	//设置高
	void setH(int H)
	{
		m_H = H;
	}
	//获取高
	int getH()
	{
		return m_H;
	}

	//利用成员函数来判断两个立方体是否相同
	bool issame1(Cube& c)
	{
		if (m_H == c.getH() && m_L == c.getL() && m_W == c.getW())
		{
			return true;
		}

		return false;
	}

private:

	int m_L;
	int m_H;
	int m_W;

	int calculateS()
	{
		return 2 * m_L * m_H + 2 * m_L * m_W + 2 * m_H * m_W;
	}
	
	int calculateV()
	{
		return m_L * m_W * m_H;
	}
};

//利用全局函数来判断两个立方体是否相同
bool issame(Cube& c1, Cube& c2)  //引用节省空间
{
	if (c1.getH() == c2.getH() && c1.getL() == c2.getL() && c1.getW() == c2.getW())
	{
		return true;
	}

	return false;
}

int main()
{
	Cube c1;
	c1.setH(10);
	c1.setL(10);
	c1.setW(10);

	Cube c2;
	c2.setH(10);
	c2.setL(10);
	c2.setW(10);

	//全局函数判断
	if (issame(c1, c2))
		cout << "两个立方体是相同的" << endl;
	else
		cout << "两个立方体是不相同的" << endl;

	//成员函数判断
	bool ret = c1.issame1(c2);
	if(ret)
		cout << "两个立方体是相同的" << endl;
	else
		cout << "两个立方体是不相同的" << endl;

	return 0;
}

例6:点和圆的关系。

#include <iostream>
using namespace std;


//点类
class Point
{
public:
	void setX(int x) //设置x
	{
		m_X = x;
	}

	int getX() //获取x
	{
		return m_X;
	}

	void setY(int y) //设置y
	{
		m_Y = y;
	}

	int getY() //获取y
	{
		return m_Y;
	}

private:
	int m_X;
	int m_Y;
};

//圆类
class Circle
{
public:
	void setCenter(Point center) //设置圆心
	{
		m_Center = center;
	}

	Point getCenter() //获取圆心
	{
		return m_Center;
	}

	void setR(int r) //设置半径
	{
		m_R = r;
	}

	int getR() //获取半径
	{
		return m_R;
	}

private:
	//一个类中可以包含另一个类
	Point m_Center;

	int m_R;
};

void isInCircle(Circle& c,Point& p)
{
	//计算两点之间的距离
	int isdistance = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
		(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());

	//计算半径的平方
	int dis = c.getR() * c.getR();
	
	//进行比较
	if (isdistance == dis)
		cout << "点在圆上" << endl;
	else if (isdistance < dis)
		cout << "点在圆内" << endl;
	else
		cout << "点在圆外" << endl;

}

int main()
{
	Circle c;  //圆
	Point m; //圆心
	m.setX(10);
	m.setY(0);
	c.setCenter(m);
	c.setR(10);  //半径

	Point a; //点
	a.setX(10);
	a.setY(10);

	isInCircle(c, a);

	return 0;

}

三:对象的初始化和清理

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

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

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

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

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

1.构造函数没有返回值也不写viod。

2.函数名称与类名相同。

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

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

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

1.析造函数没有返回值也不写viod。

2.函数名称与类名相同,在名称前加上符号~。

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

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

此时我只是创建了一个对象,我并没有去调用,而这是编译器自动调用的。

3.2 构造函数的分类及调用

两种分类方式:

  按参数分类:有参构造和无参构造。

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

三种调用方法:  括号法     显示法    隐式转换法

#include <iostream>
using namespace std;

class People
{

public:
	//构造函数的分类
	People() //无参(默认)构造函数
	{
		cout << "无参构造函数的调用" << endl;
	}
	People(int a) // 有参构造函数
	{

		cout << "有参构造函数的调用" << endl;
	}
	People(const People& p) //拷贝构造函数
	{
		cout << "拷贝构造函数的调用" << endl;
	}


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


};

// 调用
void test()
{
	//1.括号法
	//People p1;  //默认构造函数调用
	//People p2(10); //有参构造函数调用
	//People p3(p2); //拷贝构造函数调用

	//2.显示法
	People p1;
	People p2 = People(10); //有参构造
	People p3 = People(p2); //拷贝构造
	
	//3.隐式转换法
	People p4 = 10; // 相当于People p4 = People(10);  有参构造
	People p5 = p4; // 拷贝构造
}

int main()
{
	test();

	return 0;

}

People(10) 为匿名对象  特点:当前行执行结束后,系统会立即回收掉匿名对象,然后再执行后面的操作。

注意事项:

1.括号法调用默认构造函数的时候不要加(),因为编译器可能会将其识别为函数声明,不会认为在创建对象。

2.使用显示法时,不要利用拷贝构造函数来初始化匿名对象,编译器会认为People(p3) == People p3。

3.3 拷贝构造函数调用时机

c++中拷贝构造函数调用时机通常有三中:

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

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

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

#include <iostream>
using namespace std;

class People
{

public:
	//构造函数的分类
	People() //无参(默认)构造函数
	{
		cout << "无参构造函数的调用" << endl;
	}
	People(int a) // 有参构造函数
	{
		m_age = a;
		cout << "有参构造函数的调用" << endl;
	}
	People(const People& p) //拷贝构造函数
	{
		m_age = p.m_age;
		cout << "拷贝构造函数的调用" << endl;
	}


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

	int m_age;
};

//1.使用一个已经创建完毕的对象来初始化一个新的对象。
void test1()
{
	People p1(20);
	People p2(p1);

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

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

}

void test2()
{
	People p;
	dowork(p);
}

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

People dowork1()
{
	People p1;
	cout << (int*)&p1 << endl;
	return p1;
}

void test3()
{
	People p = dowork1();
	cout << (int*)&p << endl;
}

int main()
{
	//test1();
	//test2();
	test3();

	return 0;

}
3.4 构造函数调用规则

默认情况下,c++编译器至少给每

个类添加三个函数

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

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

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

构造函数调用的规则:

  • 如果用户定义有参构造函数,c++不在提供无参构造,但是会提供默认拷贝函数。
  • 如果用户定义拷贝构造函数,c++不在提供其他构造函数。
#include <iostream>
using namespace std;
//1.情况一

class People
{

public:
	//构造函数的分类
	People() //无参(默认)构造函数
	{
		cout << "无参构造函数的调用" << endl;
	}
	People(int a) // 有参构造函数
	{
		m_age = a;
		cout << "有参构造函数的调用" << endl;
	}
	//People(const People& p) //拷贝构造函数
	//{
	//	m_age = p.m_age;
	//	cout << "拷贝构造函数的调用" << endl;
	//}


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

	int m_age;
};


void test1()
{
	People p;
	p.m_age = 18;
	People p1(p);

	cout << "p1的年龄:" << p1.m_age << endl;
}


int main()
{
	test1();

	return 0;

}
#include <iostream>
using namespace std;
//2.情况二

class People
{

public:
	//构造函数的分类
	//People() //无参(默认)构造函数
	//{
	//	cout << "无参构造函数的调用" << endl;
	//}
	People(int a) // 有参构造函数
	{
		m_age = a;
		cout << "有参构造函数的调用" << endl;
	}
	People(const People& p) //拷贝构造函数
	{
		m_age = p.m_age;
		cout << "拷贝构造函数的调用" << endl;
	}


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

	int m_age;
};


void test1()
{
	People p;
	
}


int main()
{
	test1();

	return 0;

}

此时People没有默认构造函数可用,编译器也不会提供。

3.5 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝工作,编译器提供的。

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

#include <iostream>
using namespace std;

class People
{

public:
	//构造函数的分类
	People() //无参(默认)构造函数
	{
		cout << "无参构造函数的调用" << endl;
	}
	People(int a,int high) // 有参构造函数
	{
		m_age = a;
		m_high=new int(high); //堆区手动开辟的空间要手动释放
		cout << "有参构造函数的调用" << endl;
	}
	


	//析构函数
	~People()
	{
		//析构代码,将堆区开辟数据做释放操作
		if (m_high != NULL)
		{
			delete m_high;
			m_high = NULL;
		}

		cout << "析构函数的调用" << endl;
	}

	int m_age;
	int* m_high;
};

void test()
{
	People p1(18,160);
	cout << "p1的年龄:" << p1.m_age << " p1的身高:" << *p1.m_high <<endl;

	People p2(p1);
	cout << "p2的年龄:" << p2.m_age << " p2的身高:" << *p2.m_high << endl;
}

int main()
{
	test();
	return 0;

}

此时会报错,为什么呢?这就是浅拷贝的不足了-------堆区的内存重复释放

浅拷贝的问题要利用深拷贝来解决,为 p2.m_high 重新分配内存

#include <iostream>
using namespace std;

class People
{

public:
	//构造函数的分类
	People() //无参(默认)构造函数
	{
		cout << "无参构造函数的调用" << endl;
	}
	People(int a,int high) // 有参构造函数
	{
		m_age = a;
		m_high=new int(high); //堆区手动开辟的空间要手动释放
		cout << "有参构造函数的调用" << endl;
	}
	
	//自己实现拷贝构造函数 解决浅拷贝带来的问题
	People(const People& p)
	{
		cout << "拷贝构造函数的调用" << endl;
		m_age = p.m_age;
		//m_high = p.m_high; // 编译器默认实现的

		//深拷贝操作
		m_high = new int(*p.m_high);

	}


	//析构函数
	~People()
	{
		//析构代码,将堆区开辟数据做释放操作
		if (m_high != NULL)
		{
			delete m_high;
			m_high = NULL;
		}

		cout << "析构函数的调用" << endl;
	}

	int m_age;
	int* m_high;
};

void test()
{
	People p1(18,160);
	cout << "p1的年龄:" << p1.m_age << " p1的身高:" << *p1.m_high <<endl;

	People p2(p1);
	cout << "p2的年龄:" << p2.m_age << " p2的身高:" << *p2.m_high << endl;
}

int main()
{
	test();
	return 0;

}
3.6 初始化列表

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

语法:构造函数():属性1(值1),属性2(值2)···{};

#include <iostream>
using namespace std;

class People
{
public:
	//传统赋值
	//People(int a, int b, int c)
	//{
	//	m_a = a;
	//	m_b = b;
	//	m_c = c;
	//}

	//初始化列表初始化属性
	People(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 test()
{
	People p(10,20,30);
	cout << "m_a = " << p.m_a << " m_b = " << p.m_b << " m_c = " << p.m_c << endl;

}

int main()
{
	test();

	return 0;
}
3.7 类对象作为类成员

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

那么那个类先进行构造,析构呢?

#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 People
{
public:

	People(string name, string phone) :m_name(name), m_phone(phone)
	{
		cout << "People的构造函数" << endl;
	};

	~People()
	{
		cout << "People的析构函数" << endl;

	}

	string m_name;
	Phone m_phone;
	
};

void test()
{
	People p("张三", "苹果");

	cout << p.m_name << "的手机是 "<< p.m_phone.m_Pname << endl;
}

int main()
{
	test();

	return 0;
}

由这个结果可以知道 :

  当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,而析构则相反。

3.8 静态成员

静态成员就是在成员函数和成员变量前加上关键词static。

静态成员分为:

1.静态成员变量:

  •    所以对象共享同一份数据。
  •    在编译阶段分配内存。
  •    类内声明,类外初始化。

2.静态成员函数:

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

3.8.1 静态成员变量

当没有在类外初始化静态成员变量时,我们无法使用这个变量。

访问方式:

1.通过对象来访问: 和上图中一样,创建一个具体的对象,用p.m_A来访问。

2.通过类名来访问:直接使用 People::m_A 进行访问。

3.8.2 静态成员函数
#include <iostream>
using namespace std;

//静态成员变量
class People
{
public:

	static void func()
	{
		m_A = 10; // 静态成员变量
		//m_B = 10; //报错,因为静态成员函数不可以访问非静态成员变量 无法区分到底是哪个对象的成员变量

		cout << "func的调用" << endl;
	}

	static int m_A; //类内声明
	int m_B;
};

int People::m_A = 100;

void test()
{
	// 1.通过对象访问
	People p;
	p.func();

	// 2.通过类名访问

	People::func();
}

int main()
{
	test();
	return 0;
}

不管是静态成员变量,还是静态成员函数都有访问权限。

3.9 c++对象模型和this指针
3.9.1 成员变量和成员函数分开存储

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

#include <iostream>
using namespace std;

// 空对象
class People
{

};

class Person
{
public:

	int m_A;  // 非静态成员变量  属于类的对象上

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

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

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

};

int Person::m_B;  //静态成员变量要在类外初始化

void test1()
{
	People p;

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

void test2()
{
	Person p;

	cout << "p sizeof: " << sizeof(p) << endl;
}

int main()
{
	//test1();
	test2();
	return 0;
}
3.9.2 this指针概念

由上一小节可以知道成员变量和成员函数分开存储的

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

那么,这块代码怎么区分是哪一个对象调用自己呢?this指针可以解决这个问题

this指针:是隐含每一个非静态成员函数内的一种指针,指向被调用的成员函数所属的对象。

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

this指针的用途:

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

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

#include <iostream>
using namespace std;

class Person
{
public:

	Person(int age)
	{
		//age = age; //此时答案是错的,因为区分不了形参和成员变量
		this->age = age;
	}

	//如果这里不是Person& 而是 Person ,那么此时每返回一个值都会重新创建一个新的变量
	Person& addage(Person& p)
	{
		this->age += p.age;


		//this指向p2得指针,而*this指向p2对象本身
		return *this;
	}

	int age;

};

void test1()
{
	Person p(10);

	cout << "p 的年龄:" << p.age << endl;
}

void test2()
{
	Person p1(10);

	Person p2(10);

	p2.addage(p1).addage(p1).addage(p1);

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

int main()
{
	//test1();
	test2();

	return 0;
}
3.9.3 空指针访问成员函数

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

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

#include <iostream>
using namespace std;

class Person
{
public:

	void showname()
	{
		cout << "this is Person" << endl;
	}

	void showage()
	{
		// 通过判断是否为空来防止错误
		if (this == NULL)
		{
			return;
		}

		// 报错的原因是因为传入的指针为空
		cout << "年龄:" << m_age << endl;  //此时 m_age相当于this->m_age  
	}

	int m_age;
};

void test1()
{
	Person* p=NULL;


	p->showname();

	p->showage();
}



int main()
{
	test1();
	

	return 0;
}
3.9.4 const 修饰成员函数

常函数:

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

常对象:

  • 声明对象前加const,称该对象为常对象。
  • 常对象只能调用常函数。
#include <iostream>
using namespace std;

class Person
{
public:

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

	void func(){}

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

void test1()
{
	Person p;

	p.showPerson();
}

//常对象
void test2()
{
	const Person p;
	//p.m_A = 10; //报错,此时不可以修改
	p.m_B = 10; //特殊变量 此时也可以修改

	//常对象只能调用常函数
	p.showPerson();
	//p.func(); //报错 因为普通成员函数可以修改属性 而常对象不允许修改属性
}

int main()
{
    //test1();
    test2();
	return 0;
}

四:友元

目的:让一个函数或类访问另一个类中私有成员。

友元的关键字为 friend

友元的实现方法:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元
4.1 全局函数做友元
#include <iostream>
using namespace std;

class Building
{
	//告诉编译器 Goodfriend全局函数是Building类的好朋友,可以访问类中的私有内容
	friend void Goodfriend(Building* build);

public:

	Building()
	{
		this->m_bedroom = "卧室";
		this->m_sittingroom = "客厅";
	}

public:
	string m_sittingroom;

private:
	string m_bedroom;

};

//全局函数
void Goodfriend(Building* build)
{
	cout << "好朋友的全局函数正在访问:" << build->m_sittingroom << endl;

	cout << "好朋友的全局函数正在访问:" << build->m_bedroom << endl;
}

void test()
{
	Building b;
	Goodfriend( &b);
}


int main()
{
	test();
	return 0;
}
4.2 类做友元
#include <iostream>
using namespace std;

//类做友元
class Building;  //声明

class Goodfriend
{
public:
	Goodfriend();

	Building* building;

	void visit(); //参观函数 访问Building中的属性

};

class Building
{
	//告诉编译器 Goodfriend类是Building类的好友
	friend class Goodfriend;
public:
	Building();

public:

	string m_sittingroom;

private:

	string m_bedroom;
};

//类外写成员函数
Building::Building()
{
	m_sittingroom = "客厅";

	m_bedroom = "卧室";
}

Goodfriend::Goodfriend()
{
	// 创建建筑物对象
	building = new Building;

}

void Goodfriend::visit()
{
	cout << "好友类正在访问:" << building->m_sittingroom << endl;

	cout << "好友类正在访问:" << building->m_bedroom << endl;
}

void test()
{
	Goodfriend g;

	g.visit();
}

int main()
{
	test();
	return 0;
}
4.3 成员函数做友元
#include <iostream>
using namespace std;


class Building;
class Goodfriend
{
public:

	Goodfriend();

	Building* building;

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

class Building
{
	// 告诉编译器Goodfriend类中的visit1可以访问Building类中的私有成员
	friend void Goodfriend::visit1();
public:
	Building();
	string m_sittingroom;

private:

	string m_bedroom;

};

//类外实现成员函数
Building::Building()
{
	m_sittingroom = "客厅";
	m_bedroom = "卧室";
}

Goodfriend::Goodfriend()
{
	building = new Building;
}

void Goodfriend::visit1()
{
	cout << "visit1 函数正在访问:" << building->m_sittingroom << endl;

	cout << "visit1 函数正在访问:" << building->m_bedroom << endl;
}

void Goodfriend::visit2()
{
	cout << "visit2 函数正在访问:" << building->m_sittingroom << endl;

	//cout << "visit2 函数正在访问:" << building->m_bedroom << endl;  报错
}

void test()
{
	Goodfriend g;
	g.visit1();
	g.visit2();
}

int main()
{
	test();
	return 0;
}

五:运算符重载

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

5.1 加号运算符重载

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

#include <iostream>
using namespace std;

class Person
{
public:

	int m_A;
	int m_B;

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

};

//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 test()
{
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;

	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;

	Person p3= p1 + p2;

	cout << "p3的m_A: " << p3.m_A << endl;
	cout << "p3的m_B: " << p3.m_B << endl;

	Person p4 = p1 + 100;
	cout << "p4的m_A: " << p4.m_A << endl;
	cout << "p4的m_B: " << p4.m_B << endl;

}

int main()
{
	test();
	return 0;
}
5.2 左移运算符重载

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

#include <iostream>
using namespace std;

class Person
{
public:

	int m_A;
	int m_B;

	// 1.成员函数实现 <<运算符重载
	//void operator<<(Person& p)  //简化为p << cout ,不符合我们平时写的 cout << p,所以不用成员函数实现
	//{
		
	//}

};

//2.全局函数实现 <<运算符重载
//返回值为ostream& 的原因是 这样可以发生链式反应,后面可以链接其他的语句
ostream& operator<<(ostream & cout, Person & p)
{
	cout << "m_A= " << p.m_A << " m_B= " << p.m_B << endl;

	return cout;
}



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

	cout << p1 << endl;

}

int main()
{
	test();
	return 0;
}
5.3 递增运算符重载

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

#include <iostream>
using namespace std;


//自定义整型
class Integer
{
	friend ostream& operator<<(ostream& cout,const Integer& i); //友元

public:
	Integer()
	{
		m_Num = 0;
	}

	//重载前置++运算符  返回引用是为了对一个数据进行递增操作
	Integer& operator++()
	{
		++m_Num;
		return *this;
	}

	//重载后置++运算符
	Integer operator++(int)  //int表示占位参数,用于区分前置和后置
	{
		//先存结果
		Integer temp = *this;
		//再递增

		m_Num++;
		//后返回值

		return temp;
	}


private:
	int m_Num;
};

//重载<<运算符
// myint++ 返回临时右值,非 const 左值引用不能绑定临时对象,不加 const 会编译报错
ostream& operator<<(ostream& cout,const Integer& i)
{
	cout << "m_Num: " << i.m_Num;
	return cout;
}

void test()
{
	Integer myint;

	cout << ++myint << endl;
	cout << myint << endl;
}

void test1()
{
	Integer myint;

	cout << myint++ << endl;
	cout << myint << endl;
}

int main()
{
	//test();
	test1();
	return 0;
}
5.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 test()
{
	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()
{
	test();
	return 0;
}
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;
		}

		return false;
	}

	string m_Name;
	int m_Age;
};

void test()
{
	Person p1("张三", 18);

	Person p2("李四", 18);

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

int main()
{
	test();

	return 0;
}
5.6 函数调用运算符重载

由于重载后使用的方式和函数调用很相似,因此称为仿函数。

#include <iostream>
using namespace std;

class Myprint
{

public:

	void operator()(string test)
	{
		cout << test << endl;
	}

};

//仿函数很灵活没有固定的写法
class Add
{
public:

	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test()
{
	Myprint print;
	print("hello world!");

}

void test2()
{
	Add a;
	cout << a(1, 3) << endl;
}

int main()
{
	test();
	test2();
	return 0;
}

六:继承

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

6.1 基本语法
#include <iostream>
using namespace std;

// 普通实现页面

class Java
{
public:

	void header()
	{
		cout << "首页,公共课,登入,注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
	}

	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}

	void content()
	{
		cout << "Java学科视频" << endl;
	}
};

class Python
{
public:

	void header()
	{
		cout << "首页,公共课,登入,注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
	}

	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}

	void content()
	{
		cout << "Python学科视频" << endl;
	}
};

class Cpp
{
public:

	void header()
	{
		cout << "首页,公共课,登入,注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
	}

	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}

	void content()
	{
		cout << "C++学科视频" << endl;
	}
};


void test()
{
	cout << "Java下载视频页面如下:" << endl;
	Java j;
	j.header();
	j.footer();
	j.left();
	j.content();

	cout << "-------------------------" << endl;
}

void test1()
{
	cout << "Python下载视频页面如下:" << endl;
	Python p;
	p.header();
	p.footer();
	p.left();
	p.content();

	cout << "-------------------------" << endl;
}

void test2()
{
	cout << "Cpp下载视频页面如下:" << endl;
	Cpp c;
	c.header();
	c.footer();
	c.left();
	c.content();

	cout << "-------------------------" << endl;
}

int main()
{
	test();
	test1();
	test2();

	return 0;
}

如果不用继承,虽然可以实现,但代码很长很乱,有大量重复代码,浪费空间。

#include <iostream>
using namespace std;

// 通过继承来实现页面
// 公共部分
class Base
{
public:
	void header()
	{
		cout << "首页,公共课,登入,注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心,交流合作。站内地图...(公共底部)" << endl;
	}

	void left()
	{
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
};

//Java
class Java :public Base
{
public:

	void content()
	{
		cout << "Java学科视频" << endl;
	}
};
//Python
class Python :public Base
{
public:

	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//Cpp
class Cpp :public Base
{
public:

	void content()
	{
		cout << "Cpp学科视频" << endl;
	}
};


void test()
{
	cout << "Java下载视频页面如下:" << endl;
	Java j;
	j.header();
	j.footer();
	j.left();
	j.content();

	cout << "-------------------------" << endl;
}

void test1()
{
	cout << "Python下载视频页面如下:" << endl;
	Python p;
	p.header();
	p.footer();
	p.left();
	p.content();

	cout << "-------------------------" << endl;
}

void test2()
{
	cout << "Cpp下载视频页面如下:" << endl;
	Cpp c;
	c.header();
	c.footer();
	c.left();
	c.content();

	cout << "-------------------------" << endl;
}

int main()
{
	test();
	test1();
	test2();

	return 0;
}

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

语法:class  子类(派生类) : 继承方式  父类(基类)

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

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 = 10;// 父类中的保护权限成员在子类中依然是保护权限
		//m_C = 10; // 父类中的私有权限成员 子类访问不到
	}
};

void test1()
{
	Son1 s;
	s.m_A = 10;
	//s.m_B = 100; //类外访问不到
}

// 2.保护继承
class Son2 :protected Base1
{
public:

	void func()
	{
		m_A = 100;  //父类中公共成员在子类中为保护成员
		m_B = 100;  //父类中保护成员在子类中为保护成员
		//m_C = 100;  //父类中私有成员 子类访问不到
	}
};

void test2()
{
	Son2 s;
	//s.m_A = 10; //变为保护权限成员 类外访问不了
}

// 3.私有继承
class Son3 :private Base1
{
public:

	void func()
	{
		 m_A = 10;  //父类中公共成员在子类中为私有成员
		 m_B = 10;  //父类中保护成员在子类中为私有成员
		 //m_C = 10;  //父类中私有成员 子类访问不到
	}
};

void test3()
{
	Son3 s;
	//s.m_A = 100; //在Son3 中变为私有成员 
}

class GrandSon3 :public Son3
{
public:

	void func()
	{
		//m_A = 10; //到Son3中 m_A变为私有 此时访问不到
		//m_B = 10;  //到Son3中 m_B变为私有 此时访问不到
	}
};

int main()
{
	return 0;
}
6.3 继承中的对象模型

从父类继承来的成员,那些属于子类对象中呢?

#include <iostream>
using namespace std;

// 公共继承
class Base1
{
public:
	int m_A;

protected:
	int m_B;

private:
	int m_C; //继承时被编译器隐藏了,因此访问不到,但会被继承下去
};

class Son :public Base1
{
public:

	int m_D;
};

void test()
{
	Son s;
	//父类中所有非静态成员属性都会被子类继承下去
	cout << "sizeof(s)= " << sizeof(s) << endl;
}

int main()
{
	test();
	return 0;
}

利用开发人员命令提示工具查看对象模型:

1. 跳转盘符  D:

2. 跳转文件路径  cd 具体路径

3.查看命名  cl /d1 reportSingleClassLayout类名 文件名

6.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(int a) 中func的调用" << endl;
	}

	int m_A;
};

class Son :public Base
{
public:
	Son()
	{
		m_A = 200;
	}

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

	int m_A;
};

// 同名成员属性
void test()
{
	Son s;
	cout << "Son m_A= " << s.m_A << endl; //200
	//如果通过子类对象访问父类中同名成员,需要加作用域
	cout << "Base m_A= " << s.Base::m_A << endl;

}

// 同名成员函数
void test1()
{
	Son s;
	s.func();  // 直接调用是子类中的同名函数
	//s.func(100); //如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员

	s.Base::func();  //加作用域
	s.Base::func(100);
}

int main()
{
	//test();
	test1();
	return 0;
}
6.6 继承同名静态成员处理

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

#include <iostream>
using namespace std;


class Base
{
public:
	static int m_A;

	static void func()
	{
		cout << "Base func的调用" << endl;
	}
};
int Base::m_A = 100;

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

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

int Son::m_A = 200;

// 同名静态成员属性
void test1()
{
	Son s;

	// 1.通过对象访问
	cout << "通过对象访问" << endl;
	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 test2()
{
	Son s;
	// 1. 通过对象访问
	cout << "通过对象访问" << endl;
	s.func();
	s.Base::func();

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



int main()
{
	//test1();
	test2();
	return 0;
}
6.7 多继承语法

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

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

在开发的时候不建议使用多继承

#include <iostream>
using namespace std;

class Base1
{
public:
	Base1()
	{
		m_A = 100;
	}

	int m_A;
};

class Base2
{
public:
	Base2()
	{
		m_A = 200;
	}

	int m_A;
};

class Son :public Base1, public Base2
{
public:

	Son()
	{
		m_C = 300;
		m_D = 400;
	}

	int m_C;
	int m_D;
};

void test()
{
	Son s;

	cout << "sizeof(s) = " << sizeof(s) << endl;

	// 当父类中出现同名成员,此时会有二义性,需要加作用域区分
	cout << "Base1 m_A = " << s.Base1::m_A << endl;  
	cout << "Base2 m_A = " << s.Base2::m_A << endl;
}

int main()
{
	test();
	return 0;
}
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 test()
{
	SheepTuo st;

	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 28;

	// 当菱形继承,两个父亲用有相同数据,需要加作用域区分
	cout << "Sheep m_Age = " << st.Sheep::m_Age << endl;
	cout << "Tuo m_Age = " << st.Tuo::m_Age << endl;

	cout << "st m_Age = " << st.m_Age << endl;
}

int main()
{
	test();
	return 0;
}

此时,继承的不是两个数据而是两个指针vbptr,这两个指针会通过偏移量找到唯一的数据。

vbptr : virtual  base pointer 这个指针会指向对应的vbtable

七:多态

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

7.1 多态的基本概念

多态分为两类:

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

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

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

// 动物类
class Animal
{
public:
	// 虚函数
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

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

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

// 执行说话的函数
// 地址早绑定 在编译阶段就确定函数地址
// 如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &a)  //Animal &a = c 父子之间允许转换
{
	a.speak();
}

void test()
{
	Cat c;
	doSpeak(c);

	Dog d;
	doSpeak(d);
}

int main()
{
	test();
	return 0;
}

多态的内部原理:

  1. Animal内部存在一个虚函数指针vfptr(virtual function pointer)指向虚函数表vftable,表内记录Animal 类虚函数的地址。
  2. Cat类 继承且没有重写Animal类 的虚函数时,Cat类 内部存在一个虚函数指针指向虚函数表vftable,表内记录Animal类 虚函数的地址。
  3. 当Cat类 重写Animal类 的虚函数,Cat类 的虚函数表内部会替换成Cat类 的虚函数地址。

动态多态的满足条件:

1. 有继承关系

2. 子类重写父类的虚函数

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

多态的好处:

  • 代码组织结构清晰。
  • 可读性强。
  • 利于前期和后期的扩展以及维护。

例:设计实现两个操作数进行运算的计算器类。

#include <iostream>
using namespace std;

// 普通写法
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;
	int m_Num2;
};

void test()
{
	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;
}

int main()
{
	test();
	return 0;
}
#include <iostream>
using namespace std;

// 利用多态实现
// 实现计算器抽象类
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 test()
{
	// 加法
	AbstractCalculator* abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;

	cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getresult() << endl;

	delete abc;

	// 加法
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;

	cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getresult() << endl;

	delete abc;

	// 乘法
    abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;

	cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getresult() << endl;

	delete abc;
}

int main()
{
	test();
	return 0;
}
7.2 纯虚函数和抽象类

多态中,父类的虚函数一般是没有意义的,主要是调用子类重写的内容,因此可以将虚函数改为纯虚函数。

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

当类中有纯虚函数时,这个类被称为抽象类。

抽象类的特点:

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

class Base
{
public:

	// 纯虚函数
	virtual void func() = 0;

};

class Son :public Base
{
public:
	void func() 
	{
		cout << "func 的调用" << endl;
	}
};

void test()
{
	//Base b; //报错,不允许使用抽象类实例化

	//Son s; //子类必须重新父类的纯虚函数,函数内什么都不写都可以
	
	Base* base = new Son;
	base->func();
	delete base;
}

int main()
{
	test();
	return 0;
}
7.3 虚析构和纯虚析构

如果子类中有属性开辟到堆区,父类指针在释放时无法调用到子类的析构函数。此时需要将父类中的析构函数改为虚析构或纯虚析构。

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

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

虚析构和纯虚析构的区别:如果是纯虚析构,那么该类为抽象类,无法实例化对象。

虚析构语法:~virtual 类名(){}

纯虚析构语法:~virtual 类名()= 0;

#include <iostream>
using namespace std;

class Animal
{
public:
	virtual void speak() = 0;

	Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}

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

class Cat :public Animal
{
public:

	Cat(string name)
	{
		cout << "Cat构造函数调用" << endl;
		m_Name = new string(name);
	}

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

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

	string *m_Name;
};

void test1()
{
	Animal* animal = new Cat("Tom");
	animal->speak();
	delete animal;
}

int main()
{
	test1();
	return 0;
}

此时没有Cat的析构函数,说明堆区的数据没有释放。

#include <iostream>
using namespace std;

class Animal
{
public:
	virtual void speak() = 0;

	Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}

	// 利用虚析构可以解决释放子类对象时不干净的问题
	//virtual ~Animal()
	//{
	//	cout << "Animal析构函数调用" << endl;
	//}

	//纯虚析构
	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal纯虚析构函数调用" << endl;
}

class Cat :public Animal
{
public:

	Cat(string name)
	{
		cout << "Cat构造函数调用" << endl;
		m_Name = new string(name);
	}

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

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

	string *m_Name;
};

void test1()
{
	Animal* animal = new Cat("Tom");
	animal->speak();

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

int main()
{
	test1();
	return 0;
}

虚析构和纯虚析构 需要声明也需要实现。

更多推荐