C++回顾 Day8

发布于:2025-07-16 ⋅ 阅读:(19) ⋅ 点赞:(0)

赋值兼容(实现多态的前提)

赋值兼容指的是在需要基类的任何地方都可以用共有派生类的对应形式来代替

赋值兼容后派生类就可以作为基类对象来使用,所以也只能访问基类的成员

赋值兼容的几种情景

子类的对象赋给父类的对象

子类的对象赋给父类的引用

子类的指针赋给父类的指针 //主要使用方式

之前的函数重载是静多态,在编译阶段决定的

动多态是在运行时决定的

动多态的条件:

  1. 父类中有虚函数

  2. 子类中覆写了父类的虚函数

  3. 将子类对象地址赋给父类的指针并发生虚函数调用

#include <iostream>
using namespace std;

class shape
{
	public:
		shape(int x,int y):_x(x),_y(y){}
		virtual void show()
		{
			cout << "shape(" << _x << "," << _y << ")" << endl;
		}
	protected:	//一定要注意这里是protect,不然子类里面覆写好的show()是无法访问_x和_y的 
		int _x;
		int _y;
};

class circle : public shape
{
	public:
		circle(int x,int y,int r):shape(x,y),_r(r){}
		virtual void show()
		{
			cout << "circle(" << _x << "," << _y << "," << _r << ")" << endl;
		}
	private:
		int _r;
};

class tangent : public shape
{
	public:
		tangent(int x,int y,int l,int w):shape(x,y),_l(l),_w(w){}
		virtual void show()
		{
			cout << "tangent(" << _x << "," << _y << "," << _l << "," << _w << ")" << endl;
		}
	private:
		int _l;
		int _w;
};

int main()
{
	shape s(1,2);
	circle c(3,4,5);
	tangent t(6,7,8,9);
	shape* p = &c;
	p->show();
	return 0;
}

注意:

  1. 虚函数可以在类内声明类外定义,但是在类外不能加virtual关键字,因为virtual是一个声明类关键字

  2. 存在多态进行函数调用,调用的是这个指针本来的类型对应的函数,而不一定是父类的

  3. 子类中覆写出来的函数不一定要和父类中保持一致,具体要看子类的需求

  4. 对于父类中的虚函数,无论子类是否进行了override,那子类的子类依旧能继续对这个函数进行override

  5. 在使用多态性的时候,将一个子类的指针赋给父类的指针是可以的,赋给父类的父类的指针也是可以的,赋给父类的父类的父类的指针还是可以的

    实现原理:每个包含虚函数的类都有一个虚函数表,该表记录了该类及其所有父类中虚函数的地址。当通过基类指针调用虚函数时,程序会根据该指针所指向对象的实际类型(即运行时类型),在虚函数表中找到并调用相应的函数实现。

覆写(override):

重载(overload):同一作用域下,函数名相同,参数列表不同

shadow:父子类之间存在同名成员,父类函数有virtual声明,子类中函数和这个函数同参同名同返回

纯虚函数

class 类名
{
    virtual 函数声明 = 0;
};

纯虚函数没有实现体

具有纯虚函数的类被称为抽象基类,抽象基类不能实例化

但是抽象基类里面的纯虚函数还能进行覆写,专门用于给族类提供公共接口

对于继承了抽象基类的子类,如果没有对纯虚函数进行覆写,则子类中的这个函数也为纯虚函数,子类就变成了抽象基类,这时候子类也不能进行实例化了

#include <iostream>
using namespace std;

class animal
{
	public:
		animal()
		{
			cout << "animal()" << endl;
		}
		~animal()
		{
			cout << "~animal()" << endl;
		}
		virtual void sound() = 0;
};

class dog : public animal
{
	public:
		dog()
		{
			cout << "dog()" << endl; 
		}
		~dog()
		{
			cout << "~dog()" << endl;
		}
		virtual void sound()
		{
			cout << "I am a dog.--------" << endl;
		}
}; 

class cat : public animal
{
	public:
		cat()
		{
			cout << "cat()" << endl;
		}
		~cat()
		{
			cout << "~cat()" << endl;
		}
		virtual void sound()
		{
			cout << "I am a cat.++++++++" << endl;
		}
};

int main()
{
	dog d;
	cat a;
	animal* p = nullptr;
	p = &d;
	p->sound();
	p = &a;
	p->sound();
	return 0;
}

这里是没有问题的,将animal中的虚函数变为纯虚函数,animal变为抽象基类作为接口,两个子类对其进行覆写(override),然后进行多态调用,各个类的构造和析构没有问题(抽象基类也是有自己的构造函数和析构函数的)

#include <iostream>
using namespace std;

class animal
{
	public:
		animal()
		{
			cout << "animal()" << endl;
		}
		~animal()
        //virtual ~animal()	//使用虚析构是为了析构完全
		{
			cout << "~animal()" << endl;
		}
		virtual void sound() = 0;
};

class dog : public animal
{
	public:
		dog()
		{
			cout << "dog()" << endl; 
		}
		~dog()
		{
			cout << "~dog()" << endl;
		}
		virtual void sound()
		{
			cout << "I am a dog.--------" << endl;
		}
}; 

class cat : public animal
{
	public:
		cat()
		{
			cout << "cat()" << endl;
		}
		~cat()
		{
			cout << "~cat()" << endl;
		}
		virtual void sound()
		{
			cout << "I am a cat.++++++++" << endl;
		}
};

int main()
{
	animal *p = nullptr;
	p = new(cat);
	p->sound();
	delete p;
	return 0;
}

我们发现,对于栈上的对象的创建,对其进行析构的时候只调用了抽象基类的析构函数,而没有调用子类的析构函数,为了析构完全,我们要将抽象基类的析构函数变为虚析构

注意:

  1. 不是存在纯虚函数时要自实现虚析构函数,而是存在虚函数就要自实现虚析构函数

  2. 对于虚析构函数,本质上是一个虚函数,也就是说子类会对其进行继承,但是如果子类自实现了析构函数,那么就会覆盖这个虚析构函数

  3. 当一个基类有虚函数,其子类又被用作多态基类,也就是这个子类的子类(孙类)又重写虚函数并且体现多态性,那么这个子类就必须实现虚析构,无论是不自实现默认调用父类的虚析构还是自实现

基于多态实现的设计模式——依赖倒置原则

#include <iostream>
using namespace std;

class book
{
	public:
		void getstory()
		{
			cout << "bookstory…………bookstory" << endl;
		}
};

class newspaper
{
	public:
		void getstory()
		{
			cout << "newspaperstory————newspaperstory" << endl;
		}
};

class mum
{
	public:
		void show(book* b)
		{
			b->getstory();
		}
		void show(newspaper* n)
		{
			n->getstory();
		}
};

int main()
{
	mum m;
	book b;
	newspaper n;
	m.show(&b);
	m.show(&n); 
}

这是最基本的设计,这里是自顶向下的,最顶层的业务逻辑为mum和其show(),底层为book和newspaper,这种设计一旦顶层业务逻辑发生了改变,那么修改的代码量将是巨大的

#include <iostream>
using namespace std;

class idea
{
	public:
		virtual void getstory() = 0;
};

class book : public idea
{
	public:
		virtual void getstory()
		{
			cout << "bookstory…………bookstory" << endl;
		}
};

class newspaper : public idea
{
	public:
		virtual void getstory()
		{
			cout << "newspaperstory————newspaperstory" << endl;
		}
};

class mum
{
	public:
		void show(idea* i)
		{
			i->getstory();
		}
};

int main()
{
	mum m;
	book b;
	newspaper n;
	m.show(&b);
	m.show(&n); 
}

这时候我们设置了一个抽象基类,作为上层业务逻辑调用的对象,只需要为上层传入一个基类指针即可;同时作为底层实现的基类,提供其函数的对应的纯虚函数允许其重写

依赖倒置原则:高层模块不应该依赖底层模块,二者都应该依赖抽象模块;抽象不应该依赖细节,细节应该依赖抽象

模板template

泛型编程即依赖于模板进行实现,其中STL就是泛型编程的代表作

//函数模板,会根据具体的类型产生模板函数
template<typename T>
返回类型 函数模板名(函数参数列表)
{
    函数模板定义体
}
//类模板->产生模板类->产生模板类对象
template<typename T>
class mystack
{
    成员变量及成员函数
};

注意:如果成员函数要在类外实现,前面要加template< typename T>,作用域要写成mystack< T>::


网站公告

今日签到

点亮在社区的每一天
去签到