C++_类和对象02_运算符重载、继承、多态_学习笔记

5 运算符重载

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

5.1 + 运算符重载

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_B + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
int main()
{
	Person p1, p2;
	p1.m_A = 10;
	p1.m_B = 10;
	p2.m_A = 10;
	p2.m_B = 10;
	Person p3 = p1 + p2; // 想要创建一个p3,对应属性相加,直接这样写肯定报错
	cout << p3.m_A << endl << p3.m_B;
}

5.2 < <运算符重载

//左移运算符一般只使用全局函数重载
ostream & operator<< (ostream &cout, Person &p)
{
	cout << "m_A = " << p.m_A << "m_B = " <<  p.m_B;
	return cout;
}

int main()
{
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;

	cout << p1 ;  //想要输出p1中的所有属性,直接这样写肯定报错
	cout << "hello" << endl;//当不满足重载条件时,充当其原来的角色。可以用函数参数重载来理解。
}

5.3 ++运算符重载

class MyInt
{
	friend ostream& operator<< (ostream& cout, MyInt myint);
public:
	MyInt()
	{
		m_num = 0;
	}
	//重载前置++
	MyInt& operator++() //返回引用是为了一直对一个数据进行递增操作
	{
		m_num++;
		return *this;
	}
	//重载后置++
	MyInt operator++(int)//int代表占位参数,用于区分前置后置。后置要返回值而不是引用
	{
		//先 记录当时结果
		MyInt temp = *this;
		//后 递增
		m_num++;
		//最后将记录的结果返回
		return temp;
	}
private:
	int m_num; 
};

//为了输出,首先要重载左移运算符
ostream& operator<< (ostream& cout, MyInt myint)//注意这里不是引用
{
	cout << myint.m_num;
	return cout;
}
int main()
{
	MyInt myint;
	cout << myint++ << myint; //结果:01
}

5.4 赋值运算符重载

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;

		//由于p2本身在堆区有属性,应该先释放再深拷贝
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
		m_age = new int(*p.m_age);//深拷贝
		return *this; //返回自身
	}
	int* m_age;
};
int main()
{
	Person p1(18);
	Person p2(20);
	Person p3(30);
	p3 = p2 = p1;
	cout << *p1.m_age << *p2.m_age << *p3.m_age << endl;
}

5.5 关系运算符重载

class Person
{
public:
	Person(string name, int age)
	{
		m_age = age;
		m_name = name;
	}
	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;
};
int main()
{
	Person p1("Leo", 18);
	Person p2("Tim", 20);
	if (p1 == p2)
	{
		cout << "相等" << endl;
	}
	else
	{
		cout << "不相等" << endl;
	}
}

5.6 函数调用运算符重载(仿函数)

仿函数实质上是对()的重载。该用法在STL中会用到很多。

class MyPrint
{
public:
	void operator()(string test)//重载小括号
	{
		cout << test << endl;
	}
};
int main()
{
	MyPrint mp;
	mp("Hello World"); //由于使用起来很像函数调用,因此叫仿函数
}

6 继承

继承是面向对象三大特性之一。(封装继承多态)

6.1 继承的基本语法

作用:减少重复代码。
class 子类 : 继承方式 父类
子类也称为派生类
父类也称为基类
class 子类 : 继承方式 父类

class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
};
class JavaPage : public BasePage
{
public:
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};
class Cpp : public BasePage
{
public:
	void content()
	{
		cout << "Cpp学科视频" << endl;
	}
};

6.2 继承方式

在这里插入图片描述
继承方式:public, protected, private。
父类的private内容,子类无论如何也无法访问。

6.3 继承中的构造和析构顺序

构造父类->构造子类->析构子类->析构父类

6.4 继承同名成员处理方式

//Son继承了Base,二者都有m_A属性时
Son s;
//同名属性
cout << "Son下的m_A: " << s.m_A << endl;//子类属性可以直接调用
cout << "Base下的m_A: " << s.Base::m_A << endl;//父类属性要加作用域
//同名函数
cout << "Son下的func(): " << s.func() << endl;//子类属性可以直接调用
cout << "Base下的func(): " << s.Base::func() << endl;//父类属性要加作用域

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

class Base
{
public:
	static int m_A;
	static void func(){...};
};
int Base::m_A = 100; //静态成员类内声明,类外初始化
class Son : public Base
{
public:
	static int m_A;
	static void func(){...};
};
int Son::m_A = 200;

int main()
{	//1.通过对象访问
	Son s;
	cout << s.m_A;// 输出200
	cout << s.Base::m_A;// 输出100
	s.func();
	s.Base::func();
	
	//2.通过类名访问(所有对象使用同一个静态成员,可以通过类名访问)
	cout << Son::m_A;//输出200
	cout << Base::m_A;//输出100
	cout << Son::Base::m_A;//效果和上一行一样
	Son::func();
	Son::Base::func();
}

6.6 多继承语法

class 子类 : 继承方式 父类1, 继承方式 父类2, 继承方式 父类3,…
一个儿子认多个爹
实际开发中不建议使用

6.7 菱形继承/钻石继承

两个子类继承同一个父类,又有一个类同时继承两个子类
在这里插入图片描述
假设动物、羊、驼、羊驼都有属性age,有以下两个问题。
问题1:调用羊驼的age时,会有二义性,不知道age继承自羊还是驼。
问题2:羊驼继承时,只需要取一个age就足够,取两个多余了。

解决1:添加作用域
解决2:利用虚继承,在继承钱加入关键词virtual

class Animal{...};
class Sheep:virtual public Animal{};
class Camel:virtual public Animal{};
class CNM:public Sheep, public Camel{};
//加入虚继承后,Sheep::age和Camel::age就用同一个内存空间了。

6 多态

6.1 多态基本语法

分类:
静态多态:函数重载、运算符重载
动态多态:派生类和虚函数实现运行时多态
区别:
静态多态的函数地址早绑定,在编译阶段确定
动态多态的函数地址晚绑定,在运行阶段确定

class Animal
{
public:
	void speak()
	{
		cout << "动物在叫唤" << endl;
	}
};
class Cat :public Animal
{
public:
	void speak()
	{
		cout << "猫叫" << endl;
	}
};
void doSpeak(Animal& animal)//地址早绑定 编译阶段确定
{
	animal.speak();
}
int main()
{
	Cat cat;
	doSpeak(cat);  //由于地址早绑定,运行结果是Animal类中的函数
	//如果想执行Cat类中的函数,需要在运行阶段绑定地址
}

在上述代码段中,执行doSpeak(cat)会运行Animal类中的函数,因为函数地址在编译时就决定了。要想执行Cat类中的函数,需要在运行阶段绑定地址。只需要将Animal类的代码改为:

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在叫唤" << endl;
	}
};

这样各类中的speak会共用一个地址空间。

动态多态条件:

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

动态多态的调用:
父类的引用指向子类对象。在上述例程中为:

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

6.1.1 计算器案例

该例子可以体现多态的优点:易读、易维护。

//实现一个抽象类,这样便于将功能一步一步地添加进去
class AbsCalculator
{
public:
	int m_num1;
	int m_num2;
	virtual int getRes()
	{
		return 0;
	}
};
class AddCal : public AbsCalculator
{
public:
	virtual int getRes()  // 这里的virtual可加可不加
	{
		return m_num1 + m_num2;
	}
};
class SubCal : public AbsCalculator
{
public:
	virtual int getRes()
	{
		return m_num1 - m_num2;
	}
};
class mulCal : public AbsCalculator
{
public:
	virtual int getRes()
	{
		return m_num1 * m_num2;
	}
};
class divCal : public AbsCalculator
{
public:
	virtual int getRes()
	{
		return m_num1 / m_num2;
	}
};

int main()
{
	//多态的使用条件:父类指针或引用指向子类对象
	//此处使用指针
	AbsCalculator* abc = new AddCal;
	abc->m_num1 = 10;
	abc->m_num2 = 10;
	cout << abc->getRes() << endl;
	//用完后记得释放堆区数据
	delete abc;
}

6.2 纯虚函数和抽象类

上面各例程中,父类的虚函数没有作用,要调用的都是子类中被重写了的同名函数。因此可以改写为纯虚函数
virtual 返回类型 函数名( 参数列表)=0;
当类中有纯虚函数,这个类被称为抽象类
特点:子类必须重写抽象类中的纯虚函数,否则也属于抽象类,无法实例化对象。

6.2.1 做饮料例子

//实现一个抽象类,这样便于将功能一步一步地添加进去
class AbsDrink
{
public:
	void boil()
	{
		cout << "煮水" << endl;
	}
	virtual void brew() = 0;
	virtual void pour()
	{
		cout << "装杯" << endl;
	}
	virtual void fill() = 0;
	void makeDrink()
	{
		boil();
		brew();
		pour();
		fill();
	}
};
class Coffe : public AbsDrink
{
public:
	void brew()
	{
		cout << "放入咖啡粉" << endl;
	}
	void fill()
	{
		cout << "加奶" << endl;
	}
};
class Tea : public AbsDrink
{
public:
	void brew()
	{
		cout << "放入茶叶" << endl;
	}
	void fill()
	{
		cout << "加柠檬" << endl;
	}
};
void doDrink(AbsDrink* abs)
{
	abs->makeDrink();
	delete abs;
}
int main()
{
	doDrink(new Coffe);
	doDrink(new Tea);
}

6.3 虚析构和纯虚析构

多态使用时,如果子类有属性开辟到堆区,父类指针释放时无法调用子类析构代码。
解决方法:将父类中的析构函数改为虚析构或纯虚析构。
虚析构和纯虚析构的区别在于是否抽象。
~virtual 类名()

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值