C++三大特性之多态(详细)
作者:互联网
C++特性详解
C++分为三大特性:_封装、_继承、_多态
_封装: 类是面向对象程序设计语言中的一个概念。类是对某个对象的定义。包含有关对象动作方式的信息,包括它的名称、方法、属性和事件。
封装的类有如下的访问类型:
1)公有( public )成员可以在类外访问; 2)私有( private )成员只能被该类的成员函数访问;
3)保护( protected )成员只能被该类的成员函数或派生类的成员函数访问。
数据成员通常是私有的,成员函数通常有一部分是公有的,一部分是私有的。因为类的公有的函数可以在类外被访问,也称之为类的接口。(实际中具体的访问权限情况根据实际情况而定)
网上查到的封装的有点: 程序更模块化,更易读易写,提升了代码重用到一个更高的层次。其实我认为更重要的是保密性和跨平台性。
[x]封装成dll动态链接库后也可以实现程序的保密性(需要提供项目代码时,部分核心算法可以封装起来!)
[x]同时一个Vber使用他的VB程序调用你的c++算法时,通过c++封装成dll可以实现跨平台性的使用。(反过来调用比较麻烦,参考COM组件的使用)
值得注意的是类的封装要满足单一职责原则,只完成单独的使命,万万不可涂一时之快,写成了武林全集的dll,后期维护就很头疼了。
_继承:面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。
这样做,也达到了重用代码功能和提高执行时间的效果。当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。
这个已有的类称为基类,新建的类称为派生类。例如,定义车是基类,那么派生类可以是大众或者是奔驰,等等。
_多态:顾名思义就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
先看一段程序引用:
class Base {
public:
void TestFunc(){
cout << "基类函数调用" << endl;
}
};
class Derived : public Base {
public:
//void TestFunc(){
// cout << "派生类函数调用" << endl;
//}
};
int main()
{
Base b;
Derived d;
//调用基类的TestFunc()
b.TestFunc(); //输出->基类函数调用
//子类继承了基类的TestFunc()且子类本身没有TestFunc(),因此此处还是调用基类的TestFunc()
d.TestFunc(); //输出->基类函数调用
getchar();
return 0;
}
输出结果:
基类函数调用
基类函数调用
上述程序中,可以看出派生类中没有TestFunc()函数,所以调用的为基类的TestFunc()函数;接下来我们将注释去掉再看看效果会怎么样
class Base {
public:
void TestFunc(){
cout << "基类函数调用" << endl;
}
};
class Derived : public Base {
public:
void TestFunc(){
cout << "派生类函数调用" << endl;
}
};
int main()
{
Base b;
Derived d;
//调用基类的TestFunc()
b.TestFunc(); //输出->基类函数调用
d.TestFunc(); //输出->派生类函数调用
d.Base::TestFunc();//输出->基类函数调用
getchar();
return 0;
}
输出结果:
基类函数调用
派生类函数调用
基类函数调用
根据上面的输出,可以看出来派生类虽然继承了基类的TestFunc()函数但是派生类本身中TestFunc()函数,此时构成了重定义,即基类中的TestFunc()函数被隐藏,因此调用的是派生类的TestFunc()函数
当然可以根据 d.Base::TestFunc()来获取基类的成员函数
接下来保持上面的类定义不变,我们试试用指针来调用实现
class Base {
public:
void TestFunc(){
cout << "基类函数调用" << endl;
}
};
class Derived : public Base {
public:
void TestFunc(){
cout << "派生类函数调用" << endl;
}
};
int main()
{
Base b;
Derived d;
//指针方法
Base *pb = &b;
Derived *pd = &d;
pb->TestFunc();//pb指向基类,打印Base::TestFunc()
pd->TestFunc();//pd指向子类,打印Derived::TestFunc()
pb = &d;
pb->TestFunc();//pb指向子类,打印Bas::TestFunc()
//引用方法
Base &rb = b;
Derived &rd = d;
rb.TestFunc();//rb引用基类,打印Base::TestFunc()
rd.TestFunc();//rd引用子类,打印Derived::TestFunc()
Base &rd2 = d;
rd2.TestFunc();//rd2引用子类,打印Base::TestFunc()
getchar();
return 0;
}
输出结果:
基类函数调用
派生类函数调用
基类函数调用
基类函数调用
派生类函数调用
基类函数调用
在上面的代码如果我们在基类的fun函数前加virtual即可实现动态绑定:
class Base {
public:
virtual void TestFunc(){
cout << "基类函数调用" << endl;
}
};
class Derived : public Base {
public:
void TestFunc(){
cout << "派生类函数调用" << endl;
}
};
int main()
{
Base b;
Derived d;
//指针方法
Base *pb = &b;
Derived *pd = &d;
pb->TestFunc();//pb指向基类,打印Base::TestFunc()
pd->TestFunc();//pd指向子类,打印Derived::TestFunc()
pb = &d;
pb->TestFunc();//pb指向子类,打印Derived::TestFunc()
//引用方法
Base &rb = b;
Derived &rd = d;
rb.TestFunc();//rb引用基类,打印Base::TestFunc()
rd.TestFunc();//rd引用子类,打印Derived::TestFunc()
Base &rd2 = d;
rd2.TestFunc();//rd2引用子类,打印Derived::TestFunc()
getchar();
return 0;
}
输出结果:
基类函数调用
派生类函数调用
派生类函数调用
基类函数调用
派生类函数调用
派生类函数调用
我们知道,C++继承中有赋值兼容,即基类指针可以指向子类,那么为什么还会出现基类指针指向子类或者基类对象引用子类对象,却调用基类自己的TestFunc()函数呢?
这就是静态联编,在编译时期就将函数实现和函数调用关联起来,不管是引用还是指针在编译时期都是Base类的自然调用Base类的TestFunc()。为了避免这种情况,我们引入了动态多态。 所谓的动态多态是通过继承+虚函数来实现的,只有在程序运行期间(非编译期)才能判断所引用对象的实际类型,根据其实际类型调用相应的方法。具体格式就是使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,并且派生类需要重新实现该成员函数,编译器将实现动态绑定。
顺便下面百度了一下:
联编是指一个程序自身彼此关联的过程。按照联编所进行的阶段不同,可分为静态联编和动态联编。
静态联编又称静态绑定,指在调用同名函数(即重载函数)时编译器将根据调用时所使用的实参在编译时就确定下来应该调用的函数实现。它是在程序编译连接阶段进行联编的,这种联编又称为早期联编,这是因为这种联编工作是在程序运行之前完成的。它的优点是速度快,效率高,但灵活性不够。编译时所进行的联编又称为静态束定。束定是指确定所调用的函数与执行该函数代码之间的关系。
动态联编也称动态绑定,是指在程序运行时,根据当时的情况来确定调用的同名函数的实现,实际上就是在运行时选择虚函数的实现。这种联编又称为晚期联编或动态(束定。实现条件:①要有继承性且要求创建子类型关系;)②要有虚函数;③通过基类的对象指针或引用访问虚函数。继承是动态联编的基础,虚函数是动态联编的关键,虚函数经过派生之后,在类族中就可以实现运行过程中的多态。动态联编要求在运行时解决程序中的函数调用与执行该函数代码间的关系,调用虚函数的对象是在运行时确定的。对于同一个对象的引用,采用不同的联编方式将会被联编到不同类的对象上。即不同联编可以选择不同的实现,这便是多态性。它的优点是灵活性强,但效率较低。
联编说白了就是为了满足实际机制,特别设计出来的一个语法规则!!!
或者我们可以将主函数这么写(精简写法):
class Base {
public:
void TestFunc(){
cout << "基类函数调用" << endl;
}
};
class Derived : public Base {
public:
void TestFunc(){
cout << "派生类函数调用" << endl;
}
};
int main()
{
Base *point = new Derived();
point->TestFunc();
getchar();
}
输出结果:
基类函数调用
class Base {
public:
virtual void TestFunc(){
cout << "基类函数调用" << endl;
}
};
class Derived : public Base {
public:
void TestFunc(){
cout << "派生类函数调用" << endl;
}
};
int main()
{
Base *point = new Derived();
point->TestFunc();
getchar();
}
输出结果:
派生类函数调用
注意上诉两个程序的区别,前者没有virtual()函数
父类子类指针函数调用注意事项
1,如果以一个基础类指针指向一个衍生类对象(派生类对象),那么经由该指针只能访问基础类定义的函数(静态联翩)
2,如果以一个衍生类指针指向一个基础类对象,必须先做强制转型动作(explicit cast),这种做法很危险,也不符合生活习惯,在程序设计上也会给程序员带来困扰。(一般不会这么去定义)
3,如果基础类和衍生类定义了相同名称的成员函数,那么通过对象指针调用成员函数时,到底调用那个函数要根据指针的原型来确定,而不是根据指针实际指向的对象类型确定。
另外补充一点:
我们知道C++中虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。在这里我觉得有必要要明白几个概念的区别:即重载,重写(覆盖),以及重定义(同名隐藏)。
重载:指在同一作用域中允许有多个同名函数,而这些函数的参数列表不同,包括参数个数不同,类型不同,次序不同,需要注意的是返回值相同与否并不影响是否重载。
重写(覆盖)和重定义(同名隐藏)则有点像,区别就是在写重写的函数是否是虚函数,只有重写了虚函数的才能算作是体现了C++多态性,否则即为重定义,在之前的代码中,我们看到子类继承了基类的函数,若是子类(派生类)没有函数,依旧会调用基类的函数,若是子类已重定义,则调用自己的函数,这就叫做同名隐藏。
(当然此时如果还想调用基类的TestFunc函数,只需在调用TestFunc函数前加基类和作用域限定符即可)
综上他们的关系和区别如下图表明:
虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
我们可以把基类中的虚函数 area() 改写如下:
class Shape {
protected: int width, height;
public: Shape( int a=0, int b=0) { width = a; height = b; } // pure virtual function
virtual int area() = 0;
};
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
值得注意的是,纯虚函数不能实例化,只是用来给子类提供便利的;
标签:函数,联编,子类,多态,C++,三大,基类,函数调用,TestFunc 来源: https://blog.csdn.net/qq_35886593/article/details/94546303