Skip to content

Latest commit

 

History

History
454 lines (350 loc) · 11.4 KB

04.md

File metadata and controls

454 lines (350 loc) · 11.4 KB

文章内容

一.C++中的虚函数和抽象

二.C++的封装

CPlusPlus中的虚函数和抽象

在讨论虚函数时,先要明白两个概念早绑定和延迟绑定。

绑定意味着编译器将函数调用与正确的函数定义匹配起来。它要么在编译时发生,要么在运行时发生。

静态绑定

静态绑定中,编译器在编译时将函数调用与正确的函数定义匹配起来。它也被称为提前绑定或编译时绑定。默认情况下,编译器将转到在编译期间调用的函数定义。除虚函数之外的函数调用往往是这种绑定方式。

函数覆盖-即基类和派生类具有具有相同名称、参数和返回类型的函数。在这种情况下,也会发生早期绑定。

在函数覆盖中,我们使用类的对象来调用函数。现在写一个范例,这次使用指向基类的指针来调用函数,引用基类对象。

#include <iostream>

using namespace std;

class Animals
{
	public:
		void sound();
};

void Animals::sound()
{
	cout<<"This is parent class" <<endl;
}

class Dogs : public Animals
{
	public:
		void sound();
}

void Dogs::sound()
{
	cout<< "Dogs bark" <<endl;
}


int main()
{
	Animals *a;
	Dogs d;
	a= &d;
	a -> sound();   //  早绑定
	return 0;
}
//OUTPUT:This is parent class

创建了一个指向基类Animals的指针a。然后通过写a= &d,指针“a”开始指向类Dogs的对象d。

a->sound()在通过指针“a”调用两个类中都存在的函数sound()时,基类的函数被调用,即使指针指向的是类Dogs的对象。

这是由于早绑定。a是基类指向子类对象的指针。由于早绑定发生在编译时,因此当编译器看到a是基类的指针时,它会将调用与基类的'sound()'函数进行匹配,而不考虑指针指向的是哪个对象。

动态绑定

对于动态绑定,编译器会在运行时将函数调用与正确的函数定义匹配起来。它也称为延迟绑定或运行时绑定。

在延迟绑定中,编译器在运行时识别对象的类型,然后用正确的函数定义匹配函数调用。

默认情况下,会进行早绑定。因此,如果通过任何方式告诉编译器执行后期绑定,则可以解决前面示例中的问题。 延迟绑定可以通过声明一个虚函数来实现。

虚函数

虚函数是在派生类中被重写的基类的成员函数。编译器对这个函数执行后期绑定。

为了使函数成为虚函数,在函数定义之前写入关键字virtual。

#include <iostream>

using namespace std;

class Animals
{
	public:
		virtual void sound();
};

void Animals::sound()
{
	cout << "This is parent class" << endl;
}

class Dogs : public Animals
{
	public:
		void sound();

};

void Dogs::sound()
{
	cout << "Dogs bark" << endl;
}

int main()
{
	Animals *a;
	Dogs d;
	a= &d;
	a -> sound();
	return 0;
}
//OUTPUT:Dogs bark

因为基类的sound()函数是虚函数,所以编译器现在对这个函数执行延迟绑定。现在,函数调用将在运行时与函数定义匹配。由于编译器现在将指针a标识为引用派生类Dogs的对象“d”,所以它将调用该类Dogs的sound()函数。 如果将基类中的成员函数声明为虚函数,那么该函数在其所有派生类中都会自动变为虚函数。 如果使基类中的任何函数为虚函数,那么该函数在其所有派生类中都变为虚函数。这意味着不需要在派生类中分别将该函数声明为虚函数。 也可以从基类指针中调用派生类的私有函数,方法是将基类中的函数声明为虚函数。(父可以啃子了)

编译器只在编译时而不是运行时检查类的成员是私有的、公共的还是受保护的。因为函数是在运行时调用的,所以我们可以调用任何类型的函数,私有的或公共的。 如下面的例子所示。

#include <iostream>

using namespace std;

class Animals
{
	public:
		virtual void sound();
};

void Animals::sound()
{
	cout << "This is parent class" << endl;
}

class Dogs : public Animals
{
	private:
		void sound();//这里改为virtual void sound()输出相同
};

void Dogs::sound()
{
	cout << "Dogs bark" << endl;
}

int main()
{
	Animals *a;
	Dogs b;
	a = &b;
	a->sound();
	return 0;
}
//OUTPUT:Dogs bark

同一个函数(虚函数)在不同的类中具有不同的定义,这取决于调用该函数的对象的类型,所以这也是多态性的一部分。

纯虚函数

纯虚函数是无定义的虚函数。纯虚函数也称为抽象函数。

创建一个纯虚函数,如下。 virtual void sound()=0;可以看出纯虚函数就是虚函数=0

抽象类

抽象类是无法创建实例(对象)的类。只能创建它的子类的对象(如果它们不是抽象的)。抽象类也称为抽象基类。它是多种有共性事物的高度概括。

抽象类至少有一个抽象函数(纯虚函数)。

假设有一些雇员在一家公司工作。该公司只雇佣两种类型的员工——司机和开发人员。现在,要开发一个软件来存储关于他们的信息。

因此,这里有一个实现办法——不需要创建employee类的对象。将使对象只限于司机或开发人员。而且,两类人都必须有一定的薪水。所以,必定两个类都拥有薪水这个数据成员和与薪水有关的成员函数。

这种需要最适合用抽象类来实现。

将“Employee”作为抽象类,将“Developer”和“Driver”作为派生类。

#include <iostream>

using namespace std;

class Employee
{
	virtual int getSalary() = 0;
};

class Developer : public Employee
{
private:
	int salary;
public:
	Developer(int s):salary(s){}
	int getSalary();
};

int Developer::getSalary()
{
	return salary;
}

class Driver : public Employee
{
private:
	int salary;
public:
	Driver(int t):salary(t){}
	int getSalary();
};

int Driver::getSalary()
{
	return salary;
}

int main()
{
	Developer d1(5000);
	Driver d2(3000);
	int a, b;
	a = d1.getSalary();
	b = d2.getSalary();
	cout << "Salary of Developer :" << a << endl;
	cout << "Salary of Driver :" << b << endl;
	return 0;
}

//OUTPUT:
/*
Salary of Developer : 5000
Salary of Driver : 3000
*/

Employee类中的getSalary()函数是一个纯虚函数。因为Employee类包含这个纯虚函数,所以它是一个抽象基类。

因为抽象函数是在派生类中定义的,所以函数“getSalary()”是在类Employee的两个派生类中定义的。

抽象基类的子类必须定义抽象方法,否则它们也将成为抽象类。 在抽象类中,除了纯虚函数外,还可以有其他函数和变量。

#include <iostream>

using namespace std;

class Animals
{
	public:
		virtual void sound() = 0;
};

class Dogs
{
	public:
		void sound()
		{
			cout << "Dogs bark" << endl;
		}
};

class Cats
{
	public:
		void sound()
		{
			cout << "Cats meow" << endl;
		}
};

class Pigs
{
	public:
		void sound()
		{
			cout << "Pigs snort" << endl;
		}
};

int main()
{
	Dogs d;
	Cats c;
	Pigs p;
	d.sound();
	c.sound();
	p.sound();
	return 0;
}
//output:
/*
Dogs bark
Cats meow
Pigs snort
*/

接口类(Interface)

接口或接口类是与抽象类相同的类,不同之处在于它的所有函数都是纯虚的,没有成员变量。它的派生类必须实现它的每一个虚函数。,为基类的每个纯虚函数提供定义。 像抽象类一样,不能创建接口的对象。也可以说接口是一个抽象类,但它没有成员变量,所有成员函数都是纯虚的。 接口类的名称通常以字母I开头。

class IShape
{
public:
	virtual getArea() = 0;
	virtual getPerimeter() = 0;
}
//IShape是一个接口,因为它只包含纯虚拟函数。

利用接口类来实现Rectangle函数

#include <iostream>

using namespace std;

class IShape
{
	public:
		virtual int getArea() = 0;
		virtual int getPerimeter() = 0;
};

class Rectangle : public IShape
{
	int length;
	int breadth;
	public:
		Rectangle(int l, int b):length(l),breadth(b){}

		int getArea();

		int getPerimeter();

};

int Rectangle::getArea()
{
	return length * breadth;
}

int Rectangle::getPerimeter()
{
	return 2*(length + breadth);
}

class Square : public IShape
{
	int side;
	public:
		Square(int a):side(a){}

		int getArea();

		int getPerimeter();

};

int Square::getArea()
{
	return side * side;
}

int Square::getPerimeter()
{
	return 4 * side;
}

int main()
{
	Rectangle rt(7, 4);
	Square s(4);
	cout << "Rectangle :" << endl;
	cout << "Area : " << rt.getArea() << " Perimeter : " << rt.getPerimeter() << endl;
	cout << "Square :" << endl;
	cout << "Area : " << s.getArea() << " Perimeter : " << s.getPerimeter() << endl;
	return 0;
}
//OUTPUT
/*
Rectangle :
Area : 28 Perimeter : 22
Square :
Area : 16 Perimeter : 16
*/

IShape是两个纯虚函数的接口。这些虚函数是在其子类Rectangle和Square中根据它们的需求实现(定义)的。

因此,接口只是一个具有所有纯虚方法的抽象类。

CPlusPlus的封装--Encapsulation

封装-它是在类中组合数据和函数的方法。实质上,封装就是把数据隐藏起来,不能从类外部直接访问。这类似于胶囊,胶囊中有几种药物,可以隐藏它们是什么药物,不让它们被外部直接消耗。默认情况下,类的所有成员都是私有的,从而防止从类外部访问它们。

为什么需要封装

封装是必要的,以保持一个对象的细节对该对象的用户隐藏。对象的详细信息存储在其数据成员中。这就是为什么将一个类的所有数据成员都设为private,而将大多数成员函数设为public。数据成员是由private属性所以无法从内外部进行访问(这样可以隐藏该类中任何对象的详细信息,如有关对象的数据是如何实现的),所以大多数成员函数都是公开的,允许用户通过这些函数访问数据成员。

例如,通过洗衣机的电源按钮来操作它。打开电源按钮,机器启动,当关掉电源时,机器停止。操作者无需知道里面发生了什么。这是封装。

封装的好处

  • 封装降低了代码复杂性,增加了可读性。
  • 帮助保护数据。 以银行为例,如果将Account(银行账户其中数据成员有利息balance)封装了,那么前台不能改变一个帐户的利息,
  • 封装类更容易更改。 通过使用访问修饰符(public, private, protected),可以在不改变整个程序的情况下,根据需求改变数据的隐私。例如,如果一个数据成员被声明为私有的,并且希望它可以从类之外的任何地方直接访问,那么我们只需要将private替换为public。
#include <iostream>

using namespace std;

class Rectangle
{
	int length;
	int breadth;
	public:
		void setDimension(int l, int b):length(l),breadth(b){}

		int getArea();
};

int Rectangle::getArea()
{
	return length * breadth;
}

int main()
{
	Rectangle rt;
	rt.setDimension(7, 4);
	cout << rt.getArea() << endl;
	return 0;
}
//OUTPUT:28

数据成员length和breadth封装在Rectangle类中。因为声明了这些私有变量,所以这些变量不能从类外部直接访问。因此,使用函数'setDimension'和'getArea'来访问它们。